From e4700bf1c05aaeb31a9406f056eacec3281d27c6 Mon Sep 17 00:00:00 2001 From: George-iam Date: Mon, 2 Mar 2026 21:15:28 +0000 Subject: [PATCH] feat: extend Python SDK for full Track F enterprise families Add client methods for organizations, access-requests, quotas/usage, routing/transports/deliveries, and billing plan/invoices. Include coverage tests to guarantee endpoint wiring parity beyond service-account lifecycle. Made-with: Cursor --- axme_sdk/client.py | 546 +++++++++++++++++++++++++++++++++++++++++++ tests/test_client.py | 142 +++++++++++ 2 files changed, 688 insertions(+) diff --git a/axme_sdk/client.py b/axme_sdk/client.py index bd1d2f5..11a05e0 100644 --- a/axme_sdk/client.py +++ b/axme_sdk/client.py @@ -637,6 +637,552 @@ def revoke_service_account_key( retryable=idempotency_key is not None, ) + def create_organization( + self, + payload: dict[str, Any], + *, + idempotency_key: str | None = None, + trace_id: str | None = None, + ) -> dict[str, Any]: + return self._request_json( + "POST", + "/v1/organizations", + json_body=payload, + idempotency_key=idempotency_key, + trace_id=trace_id, + retryable=idempotency_key is not None, + ) + + def get_organization(self, org_id: str, *, trace_id: str | None = None) -> dict[str, Any]: + return self._request_json("GET", f"/v1/organizations/{org_id}", trace_id=trace_id, retryable=True) + + def update_organization( + self, + org_id: str, + payload: dict[str, Any], + *, + idempotency_key: str | None = None, + trace_id: str | None = None, + ) -> dict[str, Any]: + return self._request_json( + "PATCH", + f"/v1/organizations/{org_id}", + json_body=payload, + idempotency_key=idempotency_key, + trace_id=trace_id, + retryable=idempotency_key is not None, + ) + + def create_workspace( + self, + org_id: str, + payload: dict[str, Any], + *, + idempotency_key: str | None = None, + trace_id: str | None = None, + ) -> dict[str, Any]: + return self._request_json( + "POST", + f"/v1/organizations/{org_id}/workspaces", + json_body=payload, + idempotency_key=idempotency_key, + trace_id=trace_id, + retryable=idempotency_key is not None, + ) + + def list_workspaces(self, org_id: str, *, trace_id: str | None = None) -> dict[str, Any]: + return self._request_json("GET", f"/v1/organizations/{org_id}/workspaces", trace_id=trace_id, retryable=True) + + def update_workspace( + self, + org_id: str, + workspace_id: str, + payload: dict[str, Any], + *, + idempotency_key: str | None = None, + trace_id: str | None = None, + ) -> dict[str, Any]: + return self._request_json( + "PATCH", + f"/v1/organizations/{org_id}/workspaces/{workspace_id}", + json_body=payload, + idempotency_key=idempotency_key, + trace_id=trace_id, + retryable=idempotency_key is not None, + ) + + def list_organization_members( + self, + org_id: str, + *, + workspace_id: str | None = None, + trace_id: str | None = None, + ) -> dict[str, Any]: + params: dict[str, str] | None = None + if workspace_id is not None: + params = {"workspace_id": workspace_id} + return self._request_json( + "GET", + f"/v1/organizations/{org_id}/members", + params=params, + trace_id=trace_id, + retryable=True, + ) + + def add_organization_member( + self, + org_id: str, + payload: dict[str, Any], + *, + idempotency_key: str | None = None, + trace_id: str | None = None, + ) -> dict[str, Any]: + return self._request_json( + "POST", + f"/v1/organizations/{org_id}/members", + json_body=payload, + idempotency_key=idempotency_key, + trace_id=trace_id, + retryable=idempotency_key is not None, + ) + + def update_organization_member( + self, + org_id: str, + member_id: str, + payload: dict[str, Any], + *, + idempotency_key: str | None = None, + trace_id: str | None = None, + ) -> dict[str, Any]: + return self._request_json( + "PATCH", + f"/v1/organizations/{org_id}/members/{member_id}", + json_body=payload, + idempotency_key=idempotency_key, + trace_id=trace_id, + retryable=idempotency_key is not None, + ) + + def remove_organization_member( + self, + org_id: str, + member_id: str, + *, + trace_id: str | None = None, + ) -> dict[str, Any]: + return self._request_json( + "DELETE", + f"/v1/organizations/{org_id}/members/{member_id}", + trace_id=trace_id, + retryable=True, + ) + + def create_access_request( + self, + payload: dict[str, Any], + *, + idempotency_key: str | None = None, + trace_id: str | None = None, + ) -> dict[str, Any]: + return self._request_json( + "POST", + "/v1/access-requests", + json_body=payload, + idempotency_key=idempotency_key, + trace_id=trace_id, + retryable=idempotency_key is not None, + ) + + def list_access_requests( + self, + *, + org_id: str | None = None, + workspace_id: str | None = None, + state: str | None = None, + trace_id: str | None = None, + ) -> dict[str, Any]: + params: dict[str, str] = {} + if org_id is not None: + params["org_id"] = org_id + if workspace_id is not None: + params["workspace_id"] = workspace_id + if state is not None: + params["state"] = state + return self._request_json( + "GET", + "/v1/access-requests", + params=params or None, + trace_id=trace_id, + retryable=True, + ) + + def get_access_request(self, access_request_id: str, *, trace_id: str | None = None) -> dict[str, Any]: + return self._request_json("GET", f"/v1/access-requests/{access_request_id}", trace_id=trace_id, retryable=True) + + def review_access_request( + self, + access_request_id: str, + payload: dict[str, Any], + *, + idempotency_key: str | None = None, + trace_id: str | None = None, + ) -> dict[str, Any]: + return self._request_json( + "POST", + f"/v1/access-requests/{access_request_id}/review", + json_body=payload, + idempotency_key=idempotency_key, + trace_id=trace_id, + retryable=idempotency_key is not None, + ) + + def update_quota( + self, + payload: dict[str, Any], + *, + idempotency_key: str | None = None, + trace_id: str | None = None, + ) -> dict[str, Any]: + return self._request_json( + "PATCH", + "/v1/quotas", + json_body=payload, + idempotency_key=idempotency_key, + trace_id=trace_id, + retryable=idempotency_key is not None, + ) + + def get_quota(self, *, org_id: str, workspace_id: str, trace_id: str | None = None) -> dict[str, Any]: + return self._request_json( + "GET", + "/v1/quotas", + params={"org_id": org_id, "workspace_id": workspace_id}, + trace_id=trace_id, + retryable=True, + ) + + def get_usage_summary( + self, + *, + org_id: str, + workspace_id: str, + window: str | None = None, + trace_id: str | None = None, + ) -> dict[str, Any]: + params: dict[str, str] = {"org_id": org_id, "workspace_id": workspace_id} + if window is not None: + params["window"] = window + return self._request_json( + "GET", + "/v1/usage/summary", + params=params, + trace_id=trace_id, + retryable=True, + ) + + def get_usage_timeseries( + self, + *, + org_id: str, + workspace_id: str, + window_days: int | None = None, + trace_id: str | None = None, + ) -> dict[str, Any]: + params: dict[str, str] = {"org_id": org_id, "workspace_id": workspace_id} + if window_days is not None: + params["window_days"] = str(window_days) + return self._request_json( + "GET", + "/v1/usage/timeseries", + params=params, + trace_id=trace_id, + retryable=True, + ) + + def create_principal( + self, + payload: dict[str, Any], + *, + idempotency_key: str | None = None, + trace_id: str | None = None, + ) -> dict[str, Any]: + return self._request_json( + "POST", + "/v1/principals", + json_body=payload, + idempotency_key=idempotency_key, + trace_id=trace_id, + retryable=idempotency_key is not None, + ) + + def get_principal(self, principal_id: str, *, trace_id: str | None = None) -> dict[str, Any]: + return self._request_json("GET", f"/v1/principals/{principal_id}", trace_id=trace_id, retryable=True) + + def bind_alias( + self, + payload: dict[str, Any], + *, + idempotency_key: str | None = None, + trace_id: str | None = None, + ) -> dict[str, Any]: + return self._request_json( + "POST", + "/v1/aliases", + json_body=payload, + idempotency_key=idempotency_key, + trace_id=trace_id, + retryable=idempotency_key is not None, + ) + + def list_aliases(self, *, org_id: str, workspace_id: str, trace_id: str | None = None) -> dict[str, Any]: + return self._request_json( + "GET", + "/v1/aliases", + params={"org_id": org_id, "workspace_id": workspace_id}, + trace_id=trace_id, + retryable=True, + ) + + def revoke_alias( + self, + alias_id: str, + *, + idempotency_key: str | None = None, + trace_id: str | None = None, + ) -> dict[str, Any]: + return self._request_json( + "POST", + f"/v1/aliases/{alias_id}/revoke", + idempotency_key=idempotency_key, + trace_id=trace_id, + retryable=idempotency_key is not None, + ) + + def resolve_alias( + self, + *, + org_id: str, + workspace_id: str, + alias: str, + trace_id: str | None = None, + ) -> dict[str, Any]: + return self._request_json( + "GET", + "/v1/aliases/resolve", + params={"org_id": org_id, "workspace_id": workspace_id, "alias": alias}, + trace_id=trace_id, + retryable=True, + ) + + def register_routing_endpoint( + self, + payload: dict[str, Any], + *, + idempotency_key: str | None = None, + trace_id: str | None = None, + ) -> dict[str, Any]: + return self._request_json( + "POST", + "/v1/routing/endpoints", + json_body=payload, + idempotency_key=idempotency_key, + trace_id=trace_id, + retryable=idempotency_key is not None, + ) + + def list_routing_endpoints(self, *, org_id: str, workspace_id: str, trace_id: str | None = None) -> dict[str, Any]: + return self._request_json( + "GET", + "/v1/routing/endpoints", + params={"org_id": org_id, "workspace_id": workspace_id}, + trace_id=trace_id, + retryable=True, + ) + + def update_routing_endpoint( + self, + route_id: str, + payload: dict[str, Any], + *, + idempotency_key: str | None = None, + trace_id: str | None = None, + ) -> dict[str, Any]: + return self._request_json( + "PATCH", + f"/v1/routing/endpoints/{route_id}", + json_body=payload, + idempotency_key=idempotency_key, + trace_id=trace_id, + retryable=idempotency_key is not None, + ) + + def remove_routing_endpoint(self, route_id: str, *, trace_id: str | None = None) -> dict[str, Any]: + return self._request_json( + "DELETE", + f"/v1/routing/endpoints/{route_id}", + trace_id=trace_id, + retryable=True, + ) + + def resolve_routing( + self, + payload: dict[str, Any], + *, + idempotency_key: str | None = None, + trace_id: str | None = None, + ) -> dict[str, Any]: + return self._request_json( + "POST", + "/v1/routing/resolve", + json_body=payload, + idempotency_key=idempotency_key, + trace_id=trace_id, + retryable=idempotency_key is not None, + ) + + def upsert_transport_binding( + self, + payload: dict[str, Any], + *, + idempotency_key: str | None = None, + trace_id: str | None = None, + ) -> dict[str, Any]: + return self._request_json( + "POST", + "/v1/transports/bindings", + json_body=payload, + idempotency_key=idempotency_key, + trace_id=trace_id, + retryable=idempotency_key is not None, + ) + + def list_transport_bindings( + self, + *, + org_id: str, + workspace_id: str, + trace_id: str | None = None, + ) -> dict[str, Any]: + return self._request_json( + "GET", + "/v1/transports/bindings", + params={"org_id": org_id, "workspace_id": workspace_id}, + trace_id=trace_id, + retryable=True, + ) + + def remove_transport_binding(self, binding_id: str, *, trace_id: str | None = None) -> dict[str, Any]: + return self._request_json( + "DELETE", + f"/v1/transports/bindings/{binding_id}", + trace_id=trace_id, + retryable=True, + ) + + def submit_delivery( + self, + payload: dict[str, Any], + *, + idempotency_key: str | None = None, + trace_id: str | None = None, + ) -> dict[str, Any]: + return self._request_json( + "POST", + "/v1/deliveries", + json_body=payload, + idempotency_key=idempotency_key, + trace_id=trace_id, + retryable=idempotency_key is not None, + ) + + def list_deliveries( + self, + *, + org_id: str, + workspace_id: str, + principal_id: str | None = None, + status: str | None = None, + trace_id: str | None = None, + ) -> dict[str, Any]: + params: dict[str, str] = {"org_id": org_id, "workspace_id": workspace_id} + if principal_id is not None: + params["principal_id"] = principal_id + if status is not None: + params["status"] = status + return self._request_json( + "GET", + "/v1/deliveries", + params=params, + trace_id=trace_id, + retryable=True, + ) + + def get_delivery(self, delivery_id: str, *, trace_id: str | None = None) -> dict[str, Any]: + return self._request_json("GET", f"/v1/deliveries/{delivery_id}", trace_id=trace_id, retryable=True) + + def replay_delivery( + self, + delivery_id: str, + *, + idempotency_key: str | None = None, + trace_id: str | None = None, + ) -> dict[str, Any]: + return self._request_json( + "POST", + f"/v1/deliveries/{delivery_id}/replay", + idempotency_key=idempotency_key, + trace_id=trace_id, + retryable=idempotency_key is not None, + ) + + def update_billing_plan( + self, + payload: dict[str, Any], + *, + idempotency_key: str | None = None, + trace_id: str | None = None, + ) -> dict[str, Any]: + return self._request_json( + "PATCH", + "/v1/billing/plan", + json_body=payload, + idempotency_key=idempotency_key, + trace_id=trace_id, + retryable=idempotency_key is not None, + ) + + def get_billing_plan(self, *, org_id: str, workspace_id: str, trace_id: str | None = None) -> dict[str, Any]: + return self._request_json( + "GET", + "/v1/billing/plan", + params={"org_id": org_id, "workspace_id": workspace_id}, + trace_id=trace_id, + retryable=True, + ) + + def list_billing_invoices( + self, + *, + org_id: str, + workspace_id: str, + status: str | None = None, + trace_id: str | None = None, + ) -> dict[str, Any]: + params: dict[str, str] = {"org_id": org_id, "workspace_id": workspace_id} + if status is not None: + params["status"] = status + return self._request_json( + "GET", + "/v1/billing/invoices", + params=params, + trace_id=trace_id, + retryable=True, + ) + + def get_billing_invoice(self, invoice_id: str, *, trace_id: str | None = None) -> dict[str, Any]: + return self._request_json("GET", f"/v1/billing/invoices/{invoice_id}", trace_id=trace_id, retryable=True) + def upsert_webhook_subscription( self, payload: dict[str, Any], diff --git a/tests/test_client.py b/tests/test_client.py index fd01247..526d07a 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1070,6 +1070,148 @@ def handler(request: httpx.Request) -> httpx.Response: assert revoked["key"]["status"] == "revoked" +def test_enterprise_track_f_family_methods_success() -> None: + org_id = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + workspace_id = "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb" + member_id = "mem_123" + access_request_id = "ar_123" + principal_id = "prn_123" + alias_id = "als_123" + route_id = "rte_123" + binding_id = "bnd_123" + delivery_id = "dlv_123" + invoice_id = "inv_123" + calls: list[tuple[str, str]] = [] + + def handler(request: httpx.Request) -> httpx.Response: + method_path = (request.method, request.url.path) + calls.append(method_path) + if method_path == ("POST", "/v1/organizations"): + return httpx.Response(200, json={"ok": True, "organization": {"org_id": org_id}}) + if method_path == ("GET", f"/v1/organizations/{org_id}"): + return httpx.Response(200, json={"ok": True, "organization": {"org_id": org_id}}) + if method_path == ("PATCH", f"/v1/organizations/{org_id}"): + return httpx.Response(200, json={"ok": True, "organization": {"org_id": org_id}}) + if method_path == ("POST", f"/v1/organizations/{org_id}/workspaces"): + return httpx.Response(200, json={"ok": True, "workspace": {"workspace_id": workspace_id}}) + if method_path == ("GET", f"/v1/organizations/{org_id}/workspaces"): + return httpx.Response(200, json={"ok": True, "workspaces": [{"workspace_id": workspace_id}]}) + if method_path == ("PATCH", f"/v1/organizations/{org_id}/workspaces/{workspace_id}"): + return httpx.Response(200, json={"ok": True, "workspace": {"workspace_id": workspace_id}}) + if method_path == ("GET", f"/v1/organizations/{org_id}/members"): + return httpx.Response(200, json={"ok": True, "members": [{"member_id": member_id}]}) + if method_path == ("POST", f"/v1/organizations/{org_id}/members"): + return httpx.Response(200, json={"ok": True, "member": {"member_id": member_id}}) + if method_path == ("PATCH", f"/v1/organizations/{org_id}/members/{member_id}"): + return httpx.Response(200, json={"ok": True, "member": {"member_id": member_id}}) + if method_path == ("DELETE", f"/v1/organizations/{org_id}/members/{member_id}"): + return httpx.Response(200, json={"ok": True, "result": {"member_id": member_id}}) + if method_path == ("POST", "/v1/access-requests"): + return httpx.Response(200, json={"ok": True, "access_request": {"access_request_id": access_request_id}}) + if method_path == ("GET", "/v1/access-requests"): + return httpx.Response(200, json={"ok": True, "access_requests": [{"access_request_id": access_request_id}]}) + if method_path == ("GET", f"/v1/access-requests/{access_request_id}"): + return httpx.Response(200, json={"ok": True, "access_request": {"access_request_id": access_request_id}}) + if method_path == ("POST", f"/v1/access-requests/{access_request_id}/review"): + return httpx.Response(200, json={"ok": True, "access_request": {"access_request_id": access_request_id}}) + if method_path == ("PATCH", "/v1/quotas"): + return httpx.Response(200, json={"ok": True, "quota_policy": {"org_id": org_id}}) + if method_path == ("GET", "/v1/quotas"): + return httpx.Response(200, json={"ok": True, "quota_policy": {"org_id": org_id}}) + if method_path == ("GET", "/v1/usage/summary"): + return httpx.Response(200, json={"ok": True, "summary": {"org_id": org_id}}) + if method_path == ("GET", "/v1/usage/timeseries"): + return httpx.Response(200, json={"ok": True, "series": {"org_id": org_id}}) + if method_path == ("POST", "/v1/principals"): + return httpx.Response(200, json={"ok": True, "principal": {"principal_id": principal_id}}) + if method_path == ("GET", f"/v1/principals/{principal_id}"): + return httpx.Response(200, json={"ok": True, "principal": {"principal_id": principal_id}}) + if method_path == ("POST", "/v1/aliases"): + return httpx.Response(200, json={"ok": True, "alias": {"alias_id": alias_id}}) + if method_path == ("GET", "/v1/aliases"): + return httpx.Response(200, json={"ok": True, "aliases": [{"alias_id": alias_id}]}) + if method_path == ("GET", "/v1/aliases/resolve"): + return httpx.Response(200, json={"ok": True, "resolution": {"principal": {"principal_id": principal_id}}}) + if method_path == ("POST", f"/v1/aliases/{alias_id}/revoke"): + return httpx.Response(200, json={"ok": True, "alias": {"alias_id": alias_id, "status": "revoked"}}) + if method_path == ("POST", "/v1/routing/endpoints"): + return httpx.Response(200, json={"ok": True, "route": {"route_id": route_id}}) + if method_path == ("GET", "/v1/routing/endpoints"): + return httpx.Response(200, json={"ok": True, "routes": [{"route_id": route_id}]}) + if method_path == ("PATCH", f"/v1/routing/endpoints/{route_id}"): + return httpx.Response(200, json={"ok": True, "route": {"route_id": route_id}}) + if method_path == ("DELETE", f"/v1/routing/endpoints/{route_id}"): + return httpx.Response(200, json={"ok": True, "result": {"route_id": route_id}}) + if method_path == ("POST", "/v1/routing/resolve"): + return httpx.Response(200, json={"ok": True, "resolution": {"selected_route": {"route_id": route_id}}}) + if method_path == ("POST", "/v1/transports/bindings"): + return httpx.Response(200, json={"ok": True, "binding": {"binding_id": binding_id}}) + if method_path == ("GET", "/v1/transports/bindings"): + return httpx.Response(200, json={"ok": True, "bindings": [{"binding_id": binding_id}]}) + if method_path == ("DELETE", f"/v1/transports/bindings/{binding_id}"): + return httpx.Response(200, json={"ok": True, "result": {"binding_id": binding_id}}) + if method_path == ("POST", "/v1/deliveries"): + return httpx.Response(200, json={"ok": True, "delivery": {"delivery_id": delivery_id}}) + if method_path == ("GET", "/v1/deliveries"): + return httpx.Response(200, json={"ok": True, "deliveries": [{"delivery_id": delivery_id}]}) + if method_path == ("GET", f"/v1/deliveries/{delivery_id}"): + return httpx.Response(200, json={"ok": True, "delivery": {"delivery_id": delivery_id}}) + if method_path == ("POST", f"/v1/deliveries/{delivery_id}/replay"): + return httpx.Response(200, json={"ok": True, "delivery": {"delivery_id": "dlv_replay"}}) + if method_path == ("PATCH", "/v1/billing/plan"): + return httpx.Response(200, json={"ok": True, "billing_plan": {"org_id": org_id}}) + if method_path == ("GET", "/v1/billing/plan"): + return httpx.Response(200, json={"ok": True, "billing_plan": {"org_id": org_id}}) + if method_path == ("GET", "/v1/billing/invoices"): + return httpx.Response(200, json={"ok": True, "invoices": [{"invoice_id": invoice_id}]}) + if method_path == ("GET", f"/v1/billing/invoices/{invoice_id}"): + return httpx.Response(200, json={"ok": True, "invoice": {"invoice_id": invoice_id}}) + raise AssertionError(f"unexpected request: {method_path}") + + client = _client(handler) + assert client.create_organization({"org_id": org_id, "name": "Acme"})["ok"] is True + assert client.get_organization(org_id)["ok"] is True + assert client.update_organization(org_id, {"name": "Acme Updated"})["ok"] is True + assert client.create_workspace(org_id, {"workspace_id": workspace_id, "name": "Prod", "environment": "production"})["ok"] is True + assert client.list_workspaces(org_id)["ok"] is True + assert client.update_workspace(org_id, workspace_id, {"name": "Production"})["ok"] is True + assert client.list_organization_members(org_id, workspace_id=workspace_id)["ok"] is True + assert client.add_organization_member(org_id, {"actor_id": "actor_member", "role": "member", "workspace_id": workspace_id})["ok"] is True + assert client.update_organization_member(org_id, member_id, {"status": "suspended"})["ok"] is True + assert client.remove_organization_member(org_id, member_id)["ok"] is True + assert client.create_access_request({"request_type": "workspace_join", "requester_actor_id": "actor_member"})["ok"] is True + assert client.list_access_requests(org_id=org_id, workspace_id=workspace_id, state="pending")["ok"] is True + assert client.get_access_request(access_request_id)["ok"] is True + assert client.review_access_request(access_request_id, {"decision": "approve", "reviewer_actor_id": "actor_admin"})["ok"] is True + assert client.update_quota({"org_id": org_id, "workspace_id": workspace_id, "dimensions": {}, "overage_mode": "block", "updated_by_actor_id": "actor_admin"})["ok"] is True + assert client.get_quota(org_id=org_id, workspace_id=workspace_id)["ok"] is True + assert client.get_usage_summary(org_id=org_id, workspace_id=workspace_id)["ok"] is True + assert client.get_usage_timeseries(org_id=org_id, workspace_id=workspace_id)["ok"] is True + assert client.create_principal({"org_id": org_id, "workspace_id": workspace_id, "principal_type": "service_agent"})["ok"] is True + assert client.get_principal(principal_id)["ok"] is True + assert client.bind_alias({"principal_id": principal_id, "alias": "agent://acme/billing", "alias_type": "service"})["ok"] is True + assert client.list_aliases(org_id=org_id, workspace_id=workspace_id)["ok"] is True + assert client.resolve_alias(org_id=org_id, workspace_id=workspace_id, alias="agent://acme/billing")["ok"] is True + assert client.revoke_alias(alias_id)["ok"] is True + assert client.register_routing_endpoint({"principal_id": principal_id, "transport_type": "http", "endpoint_url": "https://example", "auth_mode": "jwt"})["ok"] is True + assert client.list_routing_endpoints(org_id=org_id, workspace_id=workspace_id)["ok"] is True + assert client.update_routing_endpoint(route_id, {"priority": 1})["ok"] is True + assert client.remove_routing_endpoint(route_id)["ok"] is True + assert client.resolve_routing({"org_id": org_id, "workspace_id": workspace_id, "principal_id": principal_id})["ok"] is True + assert client.upsert_transport_binding({"principal_id": principal_id, "transport_type": "http", "transport_handle": "https://example"})["ok"] is True + assert client.list_transport_bindings(org_id=org_id, workspace_id=workspace_id)["ok"] is True + assert client.remove_transport_binding(binding_id)["ok"] is True + assert client.submit_delivery({"org_id": org_id, "workspace_id": workspace_id, "principal_id": principal_id, "payload": {"event": "test"}})["ok"] is True + assert client.list_deliveries(org_id=org_id, workspace_id=workspace_id)["ok"] is True + assert client.get_delivery(delivery_id)["ok"] is True + assert client.replay_delivery(delivery_id)["ok"] is True + assert client.update_billing_plan({"org_id": org_id, "workspace_id": workspace_id, "plan_code": "enterprise", "currency": "USD", "monthly_commit_minor": 100, "overage_unit_price_minor": 1, "updated_by_actor_id": "actor_admin"})["ok"] is True + assert client.get_billing_plan(org_id=org_id, workspace_id=workspace_id)["ok"] is True + assert client.list_billing_invoices(org_id=org_id, workspace_id=workspace_id)["ok"] is True + assert client.get_billing_invoice(invoice_id)["ok"] is True + assert len(calls) == 40 + + def test_upsert_webhook_subscription_success() -> None: subscription = _webhook_subscription_payload() request_payload = {