From 4e34b6e1738cea2457d15dbe7da9f3af8f453b70 Mon Sep 17 00:00:00 2001 From: zoltanf Date: Wed, 20 May 2026 09:11:08 +0200 Subject: [PATCH] fix(acme): repoint zone-exists to /api/v1/acme/zones/{zone} MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RcodeZero moved CheckACMEZoneExists from /api/v1/acme/{zone} to /api/v1/acme/zones/{zone} (aligning with the sibling /zones/{zone}/rrsets path). `rc0 acme zone-exists` was hitting the old path and returning 404 against the live API. Refreshes tests/fixtures/openapi.json from the live spec, rewires the two zone-exists integration tests, and updates docstrings + the mission-plan endpoint table. Drive-by: scripts/gen_api_coverage.py had stale `record` verb names from before the v1.0 set/append/import rename — corrected so regenerated docs/api-coverage.md stays current. Closes #15. --- CHANGELOG.md | 13 +++++++++++++ docs/api-coverage.md | 2 +- docs/rc0-cli-mission-plan.md | 2 +- scripts/gen_api_coverage.py | 6 +++--- src/rc0/api/acme.py | 4 ++-- src/rc0/commands/acme.py | 2 +- tests/fixtures/openapi.json | 2 +- tests/integration/test_acme_commands.py | 4 ++-- uv.lock | 2 +- 9 files changed, 25 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87610e5..13e9374 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed +- `rc0 acme zone-exists` now hits `GET /api/v1/acme/zones/{zone}`, the + current upstream path. RcodeZero moved the endpoint from + `/api/v1/acme/{zone}` to `/api/v1/acme/zones/{zone}` (consistent with + the sibling `/zones/{zone}/rrsets`), which broke the command against + the live API. Pinned `tests/fixtures/openapi.json` refreshed from the + live spec, integration tests rewired to the new path, and the nightly + spec-drift workflow is green again. Closes #15. +- `scripts/gen_api_coverage.py`: corrected stale `rc0 record` verb + mapping (`add / update / replace-all` → `set / append / import`) that + predated the v1.0 rename, so regenerated `docs/api-coverage.md` no + longer regresses to the old names. + ## [2.1.1] — 2026-05-01 ### Changed diff --git a/docs/api-coverage.md b/docs/api-coverage.md index 6493993..5339bbc 100644 --- a/docs/api-coverage.md +++ b/docs/api-coverage.md @@ -4,9 +4,9 @@ Generated from pinned OpenAPI spec v2.9. | Status | Method | Endpoint | CLI Command | Notes | |--------|--------|----------|-------------|-------| +| ✅ | `GET` | `/api/v1/acme/zones/{zone}` | `rc0 acme zone-exists` | | | ✅ | `GET` | `/api/v1/acme/zones/{zone}/rrsets` | `rc0 acme list-challenges` | | | ✅ | `PATCH` | `/api/v1/acme/zones/{zone}/rrsets` | `rc0 acme add-challenge / remove-challenge` | | -| ✅ | `GET` | `/api/v1/acme/{zone}` | `rc0 acme zone-exists` | | | ✅ | `GET` | `/api/v2/messages` | `rc0 messages poll` | | | ✅ | `GET` | `/api/v2/messages/list` | `rc0 messages list` | | | ✅ | `DELETE` | `/api/v2/messages/{id}` | `rc0 messages ack` | | diff --git a/docs/rc0-cli-mission-plan.md b/docs/rc0-cli-mission-plan.md index 5eb14be..61717b8 100644 --- a/docs/rc0-cli-mission-plan.md +++ b/docs/rc0-cli-mission-plan.md @@ -164,7 +164,7 @@ The CLI must expose **all** of the following. Endpoints marked `[DEPRECATED]` ar | Endpoint | Command | |---|---| -| `/api/v1/acme/{zone}` GET | `rc0 acme zone-exists` | +| `/api/v1/acme/zones/{zone}` GET | `rc0 acme zone-exists` | | `/api/v1/acme/zones/{zone}/rrsets` GET | `rc0 acme list-challenges` | | `/api/v1/acme/zones/{zone}/rrsets` PATCH | `rc0 acme add-challenge` / `rc0 acme remove-challenge` | diff --git a/scripts/gen_api_coverage.py b/scripts/gen_api_coverage.py index fc9003d..22db5e7 100644 --- a/scripts/gen_api_coverage.py +++ b/scripts/gen_api_coverage.py @@ -18,7 +18,7 @@ # Full endpoint → CLI command mapping (extends contract test map with mutations) ENDPOINT_TO_COMMAND: dict[tuple[str, str], tuple[str, ...]] = { # ACME (v1) - ("GET", "/api/v1/acme/{zone}"): ("acme", "zone-exists"), + ("GET", "/api/v1/acme/zones/{zone}"): ("acme", "zone-exists"), ("GET", "/api/v1/acme/zones/{zone}/rrsets"): ("acme", "list-challenges"), ("PATCH", "/api/v1/acme/zones/{zone}/rrsets"): ("acme", "add-challenge / remove-challenge"), # Messages @@ -73,8 +73,8 @@ ("POST", "/api/v2/zones/{zone}/outbound"): ("zone", "xfr-out", "set"), ("DELETE", "/api/v2/zones/{zone}/outbound"): ("zone", "xfr-out", "unset"), ("GET", "/api/v2/zones/{zone}/rrsets"): ("record", "list"), - ("PATCH", "/api/v2/zones/{zone}/rrsets"): ("record", "add / update / delete / apply"), - ("PUT", "/api/v2/zones/{zone}/rrsets"): ("record", "replace-all"), + ("PATCH", "/api/v2/zones/{zone}/rrsets"): ("record", "set / append / delete / apply"), + ("PUT", "/api/v2/zones/{zone}/rrsets"): ("record", "import"), ("DELETE", "/api/v2/zones/{zone}/rrsets"): ("record", "clear"), ("POST", "/api/v2/zones/{zone}/retrieve"): ("zone", "retrieve"), ("POST", "/api/v2/zones/{zone}/sign"): ("dnssec", "sign"), diff --git a/src/rc0/api/acme.py b/src/rc0/api/acme.py index d7b9b05..2d89366 100644 --- a/src/rc0/api/acme.py +++ b/src/rc0/api/acme.py @@ -12,8 +12,8 @@ def zone_exists(client: Client, zone: str) -> list[str]: - """GET /api/v1/acme/{zone} — returns ["found"] if configured.""" - response = client.get(f"/api/v1/acme/{zone}") + """GET /api/v1/acme/zones/{zone} — returns ["found"] if configured.""" + response = client.get(f"/api/v1/acme/zones/{zone}") result: list[str] = response.json() return result diff --git a/src/rc0/commands/acme.py b/src/rc0/commands/acme.py index efd9140..6913b43 100644 --- a/src/rc0/commands/acme.py +++ b/src/rc0/commands/acme.py @@ -54,7 +54,7 @@ def _acme_client(state: AppState) -> Generator[Client]: @app.command("zone-exists") def zone_exists_cmd(ctx: typer.Context, zone: ZoneArg) -> None: - """Check if a zone is configured for ACME. API: GET /api/v1/acme/{zone} + """Check if a zone is configured for ACME. API: GET /api/v1/acme/zones/{zone} Examples: diff --git a/tests/fixtures/openapi.json b/tests/fixtures/openapi.json index 16a2527..c7e3a3a 100644 --- a/tests/fixtures/openapi.json +++ b/tests/fixtures/openapi.json @@ -3243,7 +3243,7 @@ "deprecated": false } }, - "/api/v1/acme/{zone}": { + "/api/v1/acme/zones/{zone}": { "get": { "tags": [ "API: ACME" diff --git a/tests/integration/test_acme_commands.py b/tests/integration/test_acme_commands.py index 73816ea..e068c4c 100644 --- a/tests/integration/test_acme_commands.py +++ b/tests/integration/test_acme_commands.py @@ -55,7 +55,7 @@ def _invoke(cli: CliRunner, *args: str, input: str | None = None) -> object: @respx.mock def test_zone_exists_found(cli: CliRunner, isolated_config: Path) -> None: - respx.get(f"{_BASE_V1}/{_ZONE}").mock(return_value=httpx.Response(200, json=["found"])) + respx.get(f"{_BASE_V1}/zones/{_ZONE}").mock(return_value=httpx.Response(200, json=["found"])) r = _invoke(cli, "acme", "zone-exists", _ZONE) assert r.exit_code == 0, r.output assert json.loads(r.output) == ["found"] @@ -63,7 +63,7 @@ def test_zone_exists_found(cli: CliRunner, isolated_config: Path) -> None: @respx.mock def test_zone_exists_not_found(cli: CliRunner, isolated_config: Path) -> None: - respx.get(f"{_BASE_V1}/{_ZONE}").mock( + respx.get(f"{_BASE_V1}/zones/{_ZONE}").mock( return_value=httpx.Response(404, json={"message": "not found"}) ) r = _invoke(cli, "acme", "zone-exists", _ZONE) diff --git a/uv.lock b/uv.lock index a90b8f5..7b5c6c9 100644 --- a/uv.lock +++ b/uv.lock @@ -667,7 +667,7 @@ wheels = [ [[package]] name = "rc0-cli" -version = "2.1.0" +version = "2.1.1" source = { editable = "." } dependencies = [ { name = "dnspython" },