diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2082ca3..21c4e2b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,5 +39,20 @@ jobs: - name: Compile run: mix compile --warnings-as-errors + - name: Checkout panel API contract + uses: actions/checkout@v4 + with: + repository: cleat-cloud/cleat-deploy + path: .panel + sparse-checkout: priv/api_contract.json + sparse-checkout-cone-mode: false + + - name: Fixture matches canonical panel contract + run: scripts/check_panel_contract.sh + env: + CLEAT_DEPLOY_CONTRACT: ${{ github.workspace }}/.panel/priv/api_contract.json + - name: Test run: mix test + env: + CLEAT_DEPLOY_CONTRACT: ${{ github.workspace }}/.panel/priv/api_contract.json diff --git a/README.md b/README.md index e80a8eb..5aa7ba5 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,31 @@ mix install `mix install /usr/local/bin` or `CLEAT_INSTALL_DIR`). Make sure the destination is on your `PATH`, then check `cleat version`. +## API contract + +The panel is the source of truth: `cleat-deploy` `priv/api_contract.json` +(`CleatDeployWeb.Api.Contract`). This repo vendors a copy at +`test/fixtures/api_contract.json`. + +CI checks the fixture against `cleat-cloud/cleat-deploy` `main`. Locally: + +```bash +# sibling checkout of the panel +mix test test/cleat/contract_test.exs +scripts/check_panel_contract.sh + +# or point at a specific file +CLEAT_DEPLOY_CONTRACT=/path/to/cleat-web/priv/api_contract.json mix test +``` + +When the panel adds a resource or key (compatible change): merge the panel PR +first, then copy `priv/api_contract.json` into the fixture and list the new +resource in `test/cleat/contract_test.exs` `@used`. + +When the panel renames or drops a key the CLI reads (breaking change): update +the CLI in the same window as the panel merge. `mix test` fails on missing +keys even before the full-file diff. + ## Quick start ```bash diff --git a/scripts/check_panel_contract.sh b/scripts/check_panel_contract.sh new file mode 100755 index 0000000..e4a5513 --- /dev/null +++ b/scripts/check_panel_contract.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# Compare the vendored CLI fixture with the canonical panel contract. +# Usage: scripts/check_panel_contract.sh +# Optional: CLEAT_DEPLOY_CONTRACT=/path/to/priv/api_contract.json +set -euo pipefail + +root="$(cd "$(dirname "$0")/.." && pwd)" +fixture="$root/test/fixtures/api_contract.json" +canonical="${CLEAT_DEPLOY_CONTRACT:-}" + +if [[ -z "$canonical" && -f "$root/../cleat-web/priv/api_contract.json" ]]; then + canonical="$root/../cleat-web/priv/api_contract.json" +fi + +if [[ -z "$canonical" ]]; then + canonical="$(mktemp)" + trap 'rm -f "$canonical"' EXIT + curl -fsSL "https://raw.githubusercontent.com/cleat-cloud/cleat-deploy/main/priv/api_contract.json" \ + -o "$canonical" +fi + +if [[ ! -f "$fixture" ]]; then + echo "missing CLI fixture: $fixture" >&2 + exit 1 +fi + +if [[ ! -f "$canonical" ]]; then + echo "missing panel contract: $canonical" >&2 + exit 1 +fi + +python3 - "$fixture" "$canonical" <<'PY' +import json, sys + +fixture_path, canonical_path = sys.argv[1], sys.argv[2] +with open(fixture_path) as fh: + fixture = json.load(fh) +with open(canonical_path) as fh: + canonical = json.load(fh) +if fixture != canonical: + print( + f"CLI fixture {fixture_path} drifted from panel contract {canonical_path}", + file=sys.stderr, + ) + sys.exit(1) +print(f"ok: {fixture_path} matches {canonical_path}") +PY diff --git a/test/cleat/contract_test.exs b/test/cleat/contract_test.exs index 9fc31aa..78910d4 100644 --- a/test/cleat/contract_test.exs +++ b/test/cleat/contract_test.exs @@ -65,4 +65,39 @@ defmodule Cleat.ContractTest do assert unknown == [], "new panel resources are not covered by the CLI contract test: #{inspect(unknown)}" end + + test "vendored fixture matches the canonical panel contract" do + case panel_contract_path() do + nil -> + if System.get_env("CI") in ["true", "1"] do + flunk(""" + CI must compare the fixture against cleat-deploy priv/api_contract.json. + Set CLEAT_DEPLOY_CONTRACT to that file. + """) + else + :ok + end + + path -> + panel = path |> File.read!() |> Jason.decode!() + fixture = @fixture |> File.read!() |> Jason.decode!() + + assert fixture == panel, """ + CLI fixture drifted from the panel contract at #{path}. + Copy priv/api_contract.json from cleat-cloud/cleat-deploy into + test/fixtures/api_contract.json and update @used in this test. + """ + end + end + + defp panel_contract_path do + env = System.get_env("CLEAT_DEPLOY_CONTRACT") + sibling = Path.expand("../../../cleat-web/priv/api_contract.json", __DIR__) + + cond do + is_binary(env) and env != "" -> env + File.exists?(sibling) -> sibling + true -> nil + end + end end