Skip to content

Commit b8b4ec4

Browse files
committed
test: dek orphan-report/orphan-confirm af en valideer item-type en realm
- client-tests voor get_orphan_report en confirm_orphans - CLI-tests voor orphan-report/orphan-confirm (dry-run, ontbrekend item, ongeldig formaat, onbekend type, keycloak_client zonder realm) - valideer item-type client-side en eis een realm voor keycloak_client
1 parent e70a62e commit b8b4ec4

3 files changed

Lines changed: 129 additions & 1 deletion

File tree

src/zad_cli/commands/admin.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
no_args_is_help=True,
1414
)
1515

16+
# Valid orphan item types. keycloak_client additionally requires a realm.
17+
ORPHAN_TYPES = ("postgresql_database", "postgresql_user", "minio_bucket", "keycloak_client")
18+
ORPHAN_TYPES_REQUIRING_REALM = ("keycloak_client",)
19+
1620

1721
@app.command("list")
1822
@handle_api_errors
@@ -117,9 +121,16 @@ def orphan_confirm(
117121
if len(parts) < 2:
118122
formatter.render_error(f"Invalid item format '{raw}'. Expected TYPE:NAME or TYPE:NAME:REALM.")
119123
raise typer.Exit(1)
120-
entry: dict = {"type": parts[0], "name": parts[1]}
124+
item_type, name = parts[0], parts[1]
125+
if item_type not in ORPHAN_TYPES:
126+
formatter.render_error(f"Invalid item type '{item_type}'. Valid types: {', '.join(ORPHAN_TYPES)}.")
127+
raise typer.Exit(1)
128+
entry: dict = {"type": item_type, "name": name}
121129
if len(parts) == 3:
122130
entry["realm"] = parts[2]
131+
if item_type in ORPHAN_TYPES_REQUIRING_REALM and "realm" not in entry:
132+
formatter.render_error(f"Item type '{item_type}' requires a realm. Use TYPE:NAME:REALM.")
133+
raise typer.Exit(1)
123134
parsed.append(entry)
124135

125136
payload = {"items": parsed}

tests/test_cli.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,96 @@ def test_admin_delete_dry_run_shows_endpoint():
354354
assert '"method": "DELETE"' in out
355355

356356

357+
def test_admin_help_shows_orphan_commands():
358+
result = _run_help("admin")
359+
out = _strip_ansi(result.stdout)
360+
assert result.returncode == 0
361+
assert "orphan-report" in out
362+
assert "orphan-confirm" in out
363+
364+
365+
def test_admin_orphan_report_dry_run_not_applicable_help():
366+
"""orphan-report is read-only: no --yes / --dry-run flags."""
367+
result = _run_help("admin", "orphan-report")
368+
out = _strip_ansi(result.stdout)
369+
assert result.returncode == 0
370+
assert "--dry-run" not in out
371+
assert "--yes" not in out
372+
373+
374+
def test_admin_orphan_confirm_dry_run_shows_endpoint():
375+
"""--dry-run must short-circuit before any confirmation or API call."""
376+
result = subprocess.run(
377+
[
378+
sys.executable,
379+
"-m",
380+
"zad_cli",
381+
"admin",
382+
"orphan-confirm",
383+
"--item",
384+
"postgresql_database:regel_k4c_pr104",
385+
"--dry-run",
386+
"--output",
387+
"json",
388+
],
389+
capture_output=True,
390+
text=True,
391+
env={**_MINIMAL_ENV, "ZAD_API_KEY": "k", "ZAD_PROJECT_ID": "p"},
392+
)
393+
out = _strip_ansi(result.stdout)
394+
assert result.returncode == 0
395+
assert "/v2/admin/orphans/confirm" in out
396+
assert '"method": "POST"' in out
397+
assert "regel_k4c_pr104" in out
398+
399+
400+
def test_admin_orphan_confirm_requires_item():
401+
"""No --item is a user error: exit 1, no API call."""
402+
result = subprocess.run(
403+
[sys.executable, "-m", "zad_cli", "admin", "orphan-confirm"],
404+
capture_output=True,
405+
text=True,
406+
env={**_MINIMAL_ENV, "ZAD_API_KEY": "k", "ZAD_PROJECT_ID": "p"},
407+
)
408+
assert result.returncode == 1
409+
assert "item is required" in _strip_ansi(result.stderr).lower()
410+
411+
412+
def test_admin_orphan_confirm_rejects_bad_format():
413+
"""An item missing the TYPE:NAME separator is rejected client-side."""
414+
result = subprocess.run(
415+
[sys.executable, "-m", "zad_cli", "admin", "orphan-confirm", "--item", "justaname"],
416+
capture_output=True,
417+
text=True,
418+
env={**_MINIMAL_ENV, "ZAD_API_KEY": "k", "ZAD_PROJECT_ID": "p"},
419+
)
420+
assert result.returncode == 1
421+
assert "invalid item format" in _strip_ansi(result.stderr).lower()
422+
423+
424+
def test_admin_orphan_confirm_rejects_unknown_type():
425+
result = subprocess.run(
426+
[sys.executable, "-m", "zad_cli", "admin", "orphan-confirm", "--item", "bogus_type:name"],
427+
capture_output=True,
428+
text=True,
429+
env={**_MINIMAL_ENV, "ZAD_API_KEY": "k", "ZAD_PROJECT_ID": "p"},
430+
)
431+
assert result.returncode == 1
432+
assert "invalid item type" in _strip_ansi(result.stderr).lower()
433+
434+
435+
def test_admin_orphan_confirm_keycloak_requires_realm():
436+
"""keycloak_client without a realm is rejected before any API call."""
437+
result = subprocess.run(
438+
[sys.executable, "-m", "zad_cli", "admin", "orphan-confirm", "--item", "keycloak_client:my-client"],
439+
capture_output=True,
440+
text=True,
441+
env={**_MINIMAL_ENV, "ZAD_API_KEY": "k", "ZAD_PROJECT_ID": "p"},
442+
)
443+
assert result.returncode == 1
444+
assert "requires a realm" in _strip_ansi(result.stderr).lower()
445+
446+
357447
def test_restore_help_shows_deployment_and_pvc_snapshots():
358448
result = _run_help("restore")
359449
out = _strip_ansi(result.stdout)

tests/test_client.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,33 @@ def test_delete_admin_mark_handles_empty_body(client):
352352
assert client.delete_admin_mark("mark-1") == {}
353353

354354

355+
@respx.mock
356+
def test_get_orphan_report_returns_json(client):
357+
"""orphan-report is a read-only v1 GET that returns the sweep report as-is."""
358+
report = {"orphan_candidates": [{"type": "postgresql_database", "name": "regel_k4c_pr104"}]}
359+
route = respx.get("https://api.example.com/v2/admin/orphans/report").mock(
360+
return_value=httpx.Response(200, json=report)
361+
)
362+
363+
result = client.get_orphan_report()
364+
365+
assert result == report
366+
assert route.call_count == 1
367+
368+
369+
@respx.mock
370+
def test_confirm_orphans_sends_items_payload(client):
371+
route = respx.post("https://api.example.com/v2/admin/orphans/confirm").mock(
372+
return_value=httpx.Response(200, json={"marked": 1})
373+
)
374+
payload = {"items": [{"type": "postgresql_database", "name": "regel_k4c_pr104"}]}
375+
376+
result = client.confirm_orphans(payload)
377+
378+
assert result == {"marked": 1}
379+
assert json.loads(route.calls.last.request.content) == payload
380+
381+
355382
@respx.mock
356383
def test_restore_deployment_resource_sends_payload(client):
357384
route = respx.post("https://api.example.com/v1/restore/project/my-project/deployment/staging").mock(

0 commit comments

Comments
 (0)