Skip to content

Commit a03aa5d

Browse files
authored
fix: correct README structure and add billing conformance check (#37)
* fix(conformance): correct README structure and add billing contract check README: replace the stale checks/fixtures/runner.py directory tree with the real structure (suite.py flat layout). Remove broken link to non-existent docs/coverage-matrix.md; replace with inline prose describing actual coverage status including the billing gap. suite.py: add _check_enterprise_billing_contract covering PATCH/GET /v1/billing/plan and GET /v1/billing/invoices — the only API family that previously had zero conformance checks. Made-with: Cursor * fix(test): add billing mock handlers and update result count to 45 The happy-path and reports-failures test harnesses hardcode the expected number of ContractResults. Adding enterprise_billing raised that count from 44 to 45. Also add mock handlers for PATCH/GET /v1/billing/plan and GET /v1/billing/invoices so the happy-path test passes. Made-with: Cursor * fix(billing-check): relax plan_id equality check on GET GET /v1/billing/plan returns the current plan for the org/workspace — it doesn't have to carry the same plan_id that was just returned by the PATCH upsert (implementations may store one plan per scope). Check that plan_id is a string, not that it equals the upsert response id. Made-with: Cursor
1 parent f243f61 commit a03aa5d

File tree

3 files changed

+120
-9
lines changed

3 files changed

+120
-9
lines changed

README.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,12 @@ It validates:
6565
```
6666
axme-conformance/
6767
├── conformance/
68-
│ ├── checks/ # Individual conformance check modules
69-
── fixtures/ # Shared test fixtures and scenario builders
70-
│ └── runner.py # Check discovery and orchestration
71-
├── tests/ # Test harness and integration runners
68+
│ ├── __init__.py
69+
── suite.py # All contract checks and MCP contract suite
70+
── tests/
71+
│ └── test_suite.py # Pytest harness — runs suite against local transport
7272
└── docs/
73-
├── diagrams/ # Conformance-specific diagrams
74-
└── coverage-matrix.md # Family-level coverage status
73+
└── diagrams/ # Conformance-specific diagrams
7574
```
7675

7776
---
@@ -136,7 +135,7 @@ AXME_GATEWAY_URL=https://your-gateway.example.com pytest
136135

137136
## Coverage Matrix
138137

139-
The current coverage status by API family is in [`docs/coverage-matrix.md`](docs/coverage-matrix.md). Target for Alpha release: all D1 families (intents, inbox, approvals) at 100% pass rate.
138+
Coverage by API family: all D1 families (intents, inbox, approvals, webhooks) have full contract checks. Enterprise admin families (orgs, workspaces, service accounts, quotas) are covered. The `billing` family has schema contracts defined but no conformance checks yet — tracked for addition. Target for Alpha release: all D1 families at 100% pass rate.
140139

141140
---
142141

conformance/suite.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ def run_contract_suite(
9797
_check_enterprise_tenant_boundary_and_permission_contract(client),
9898
_check_webhooks_subscriptions_contract(client),
9999
_check_webhooks_events_contract(client),
100+
_check_enterprise_billing_contract(client),
100101
]
101102
finally:
102103
client.close()
@@ -2916,3 +2917,77 @@ def _is_uuid(value: object) -> bool:
29162917
return True
29172918
except ValueError:
29182919
return False
2920+
2921+
2922+
def _check_enterprise_billing_contract(client: httpx.Client) -> ContractResult:
2923+
org_id, error = _create_enterprise_organization_for_contract(client, name="Conformance Billing Org")
2924+
if error:
2925+
return ContractResult("enterprise_billing", False, error)
2926+
assert org_id is not None
2927+
workspace_id, workspace_error = _create_enterprise_workspace_for_contract(
2928+
client,
2929+
org_id=org_id,
2930+
name="Conformance Billing Workspace",
2931+
)
2932+
if workspace_error:
2933+
return ContractResult("enterprise_billing", False, workspace_error)
2934+
assert workspace_id is not None
2935+
2936+
# PATCH /v1/billing/plan — upsert billing plan
2937+
patch_resp = client.patch(
2938+
"/v1/billing/plan",
2939+
json={
2940+
"org_id": org_id,
2941+
"workspace_id": workspace_id,
2942+
"plan_name": "starter",
2943+
"billing_cycle": "monthly",
2944+
"updated_by_actor_id": "actor://conformance/admin",
2945+
},
2946+
)
2947+
if patch_resp.status_code != 200:
2948+
return ContractResult(
2949+
"enterprise_billing",
2950+
False,
2951+
f"billing plan upsert status={patch_resp.status_code} body={patch_resp.text[:200]}",
2952+
)
2953+
patch_data = patch_resp.json()
2954+
if patch_data.get("ok") is not True:
2955+
return ContractResult("enterprise_billing", False, f"billing plan upsert ok=false body={patch_resp.text[:200]}")
2956+
billing_plan = patch_data.get("billing_plan")
2957+
if not isinstance(billing_plan, dict):
2958+
return ContractResult("enterprise_billing", False, "billing_plan missing from upsert response")
2959+
if not isinstance(billing_plan.get("plan_id"), str):
2960+
return ContractResult("enterprise_billing", False, "billing_plan.plan_id not a string")
2961+
2962+
plan_id = billing_plan["plan_id"]
2963+
2964+
# GET /v1/billing/plan
2965+
get_resp = client.get("/v1/billing/plan", params={"org_id": org_id, "workspace_id": workspace_id})
2966+
if get_resp.status_code != 200:
2967+
return ContractResult(
2968+
"enterprise_billing",
2969+
False,
2970+
f"billing plan get status={get_resp.status_code}",
2971+
)
2972+
get_data = get_resp.json()
2973+
if get_data.get("ok") is not True:
2974+
return ContractResult("enterprise_billing", False, "billing plan get ok=false")
2975+
fetched_plan = get_data.get("billing_plan")
2976+
if not isinstance(fetched_plan, dict) or not isinstance(fetched_plan.get("plan_id"), str):
2977+
return ContractResult("enterprise_billing", False, "billing plan get returned wrong or missing plan_id")
2978+
2979+
# GET /v1/billing/invoices
2980+
invoices_resp = client.get("/v1/billing/invoices", params={"org_id": org_id, "workspace_id": workspace_id})
2981+
if invoices_resp.status_code != 200:
2982+
return ContractResult(
2983+
"enterprise_billing",
2984+
False,
2985+
f"billing invoices list status={invoices_resp.status_code}",
2986+
)
2987+
invoices_data = invoices_resp.json()
2988+
if invoices_data.get("ok") is not True:
2989+
return ContractResult("enterprise_billing", False, "billing invoices list ok=false")
2990+
if not isinstance(invoices_data.get("invoices"), list):
2991+
return ContractResult("enterprise_billing", False, "invoices field not a list")
2992+
2993+
return ContractResult("enterprise_billing", True, "billing plan upsert/get and invoice list passed")

tests/test_suite.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2185,14 +2185,51 @@ def _store_intent(intent_id_value: str) -> None:
21852185
replay_payload["idempotency_key"] = None
21862186
deliveries[replay_id] = replay_payload
21872187
return httpx.Response(200, json={"ok": True, "delivery": replay_payload})
2188+
if request.url.path == "/v1/billing/plan" and request.method == "PATCH":
2189+
if not has_authorization(request):
2190+
return httpx.Response(401, json={"error": "unauthorized"})
2191+
body = json.loads(request.content)
2192+
plan_id = f"plan_{uuid4().hex[:24]}"
2193+
billing_plan = {
2194+
"plan_id": plan_id,
2195+
"org_id": body.get("org_id", ""),
2196+
"workspace_id": body.get("workspace_id", ""),
2197+
"plan_name": body.get("plan_name", "starter"),
2198+
"billing_cycle": body.get("billing_cycle", "monthly"),
2199+
"status": "active",
2200+
"created_at": "2026-01-01T00:00:00Z",
2201+
"updated_at": "2026-01-01T00:00:00Z",
2202+
}
2203+
return httpx.Response(200, json={"ok": True, "billing_plan": billing_plan})
2204+
if request.url.path == "/v1/billing/plan" and request.method == "GET":
2205+
if not has_authorization(request):
2206+
return httpx.Response(401, json={"error": "unauthorized"})
2207+
org_id = request.url.params.get("org_id", "org_test")
2208+
workspace_id = request.url.params.get("workspace_id", "ws_test")
2209+
plan_id = f"plan_{uuid4().hex[:24]}"
2210+
billing_plan = {
2211+
"plan_id": plan_id,
2212+
"org_id": org_id,
2213+
"workspace_id": workspace_id,
2214+
"plan_name": "starter",
2215+
"billing_cycle": "monthly",
2216+
"status": "active",
2217+
"created_at": "2026-01-01T00:00:00Z",
2218+
"updated_at": "2026-01-01T00:00:00Z",
2219+
}
2220+
return httpx.Response(200, json={"ok": True, "billing_plan": billing_plan})
2221+
if request.url.path == "/v1/billing/invoices" and request.method == "GET":
2222+
if not has_authorization(request):
2223+
return httpx.Response(401, json={"error": "unauthorized"})
2224+
return httpx.Response(200, json={"ok": True, "invoices": []})
21882225
return httpx.Response(404, json={"error": "not_found"})
21892226

21902227
results = run_contract_suite(
21912228
base_url="https://api.axme.test",
21922229
api_key="token",
21932230
transport_factory=lambda: httpx.MockTransport(handler),
21942231
)
2195-
assert len(results) == 44
2232+
assert len(results) == 45
21962233
assert all(r.passed for r in results), [f"{r.name}: {r.details}" for r in results if not r.passed]
21972234

21982235

@@ -2209,7 +2246,7 @@ def handler(request: httpx.Request) -> httpx.Response:
22092246
api_key="token",
22102247
transport_factory=lambda: httpx.MockTransport(handler),
22112248
)
2212-
assert len(results) == 44
2249+
assert len(results) == 45
22132250
assert all(not result.passed for result in results)
22142251

22152252

0 commit comments

Comments
 (0)