diff --git a/README.md b/README.md index 1715168..2544660 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ users = norbix.membership.get_users() print("Users response:", users) ``` -### 3) Account-scoped Hub call (requires account_id) +### 3) Account Hub call (token only) ```python from norbix_python import NorbixHub @@ -91,8 +91,7 @@ from norbix_python import NorbixHub norbix = NorbixHub( api_key="sk_live_xxx", project_id="proj_123", - account_id="acc_456", # required for account-scoped endpoints -) +) # no account_id: the gateway takes the account from the signed-in session account = norbix.account.get_account_profile() print(account) @@ -202,7 +201,7 @@ Two related calls: triggers that need a fix (for example, their provider is gone); `{"items": [...]}`. - `norbix.hub.account.check_project_languages(project_id, languages=["en", "de"])` — a dry run before `update_project_languages`: which templates miss a language; - `{"templates": [...]}`. Account scope. + `{"templates": [...]}`. Needs only a key or a token. SMS campaigns can also go to the **account owner and team members** (`deliveryType="AccountUsers"`, `accountUsers={"recipientsSourceType": "AccountUsers", "recipients": [...]}`); @@ -340,15 +339,15 @@ Custom base URLs (`base_url_api=`, `base_url_hub=`, `NORBIX_API_URL`, deployments are unaffected; the `nb-region` header is still sent when a region is configured. -### Managing regions (Hub, account scope) +### Managing regions (Hub) -These endpoints require `account_id` (see -[Project vs account scope](#project-vs-account-scope)). +`regions.list()` needs no token at all; `update_project_regions` needs a key +or a bearer token (see [Project vs account scope](#project-vs-account-scope)). ```python from norbix_python import NorbixHub -norbix = NorbixHub(api_key="sk_live_xxx", project_id="proj_123", account_id="acc_456") +norbix = NorbixHub(api_key="sk_live_xxx", project_id="proj_123") # Regions available to the account. # Response shape: {"items": [{"id": ..., "continent": ..., "name": ...}, ...]} @@ -382,7 +381,6 @@ async def main() -> None: async with AsyncNorbix( api_key="sk_live_xxx", project_id="proj_123", - account_id="acc_456", region="nb-eu-germany", ) as client: regions = await client.hub.regions.list() @@ -397,8 +395,9 @@ async def main() -> None: ## Project vs account scope - `project_id` is required (set explicitly or via env). -- `account_id` is optional -- Account-scoped Hub methods raise `NORBIX_ACCOUNT_SCOPE_REQUIRED` if `account_id` is not configured +- `account_id` is optional, and no Hub account method needs it: the gateway takes the account from the signed-in session or the project id in the path +- Account routes (profile, projects, project settings, team, licenses, …) need only a key or a bearer token +- Sign-up (`hub.account.create_account`), accepting an invitation (`hub.account.create_team_member_from_invitation`), the region list (`hub.account.get_account_regions`, `hub.regions.list`) and `hub.account.verify_account(accountId=..., token=...)` need no token at all — they are public on the gateway, and no `Authorization` header is sent - Optional-scope methods (the signed notification preview links) send auth when the client has a token and never require one ## SDK maintenance diff --git a/docs/hub/account.md b/docs/hub/account.md index ca7b607..e23811c 100644 --- a/docs/hub/account.md +++ b/docs/hub/account.md @@ -2,47 +2,59 @@ Access with `norbix.hub.account`. +Every method here needs only a key or a bearer token (scope `project`): the +gateway takes the account from the signed-in session, or from the project id +in the path, and never reads the `X-CM-AccountId` header. Four routes are +public on the gateway and need no token and no `account_id` at all (scope +`unauthenticated`, no `Authorization` header is sent): sign-up +(`create_account`), accepting an invitation +(`create_team_member_from_invitation`), the region list (`get_account_regions`, +also `norbix.hub.regions.list`) and `verify_account`, which takes the account +id once, in the request: `verify_account(accountId="acc_456", token="...")`. +Sync and async clients behave the same. Tests: +`tests/hub/test_account_token_only.py`. + | Method | Verb | Path | Scope | | --- | --- | --- | --- | -| `get_account_profile` | `GET` | `/{version}/account/profile` | `account` | -| `update_account_profile` | `PUT` | `/{version}/account/profile` | `account` | -| `resend_account_verification_token` | `GET` | `/{version}/account/verify/resend` | `account` | -| `get_account_status` | `GET` | `/{version}/account/status` | `account` | -| `create_stripe_checkout_session` | `POST` | `/{version}/account/stripe/create-checkout-session` | `account` | -| `get_stripe_billing_portal_url` | `POST` | `/{version}/account/stripe/get-portal-url` | `account` | -| `create_team_member_from_invitation` | `POST` | `/{version}/account/team/member` | `account` | -| `verify_account` | `GET` | `/{version}/account/verify` | `account` | -| `delete_notifications_group` | `DELETE` | `/{version}/account/projects/{projectId}/notifications/settings/group` | `account` | -| `delete_notifications_tag` | `DELETE` | `/{version}/account/projects/{projectId}/notifications/settings/tag` | `account` | -| `remove_tag_from_notifications_group` | `DELETE` | `/{version}/account/projects/{projectId}/notifications/settings/group/tag` | `account` | -| `save_notifications_group` | `POST` | `/{version}/account/projects/{projectId}/notifications/settings/group` | `account` | -| `save_notifications_tag` | `POST` | `/{version}/account/projects/{projectId}/notifications/settings/tag` | `account` | -| `create_project` | `POST` | `/{version}/account/projects` | `account` | -| `delete_project` | `DELETE` | `/{version}/account/projects/{projectId}` | `account` | -| `get_project` | `GET` | `/{version}/account/projects/{projectId}` | `account` | -| `get_projects` | `GET` | `/{version}/account/projects` | `account` | -| `get_account_regions` | `GET` | `/{version}/account/regions` | `account` | -| `get_project_tokens` | `GET` | `/{version}/account/projects/{projectId}/tokens` | `account` | -| `update_project_accent_color` | `PATCH` | `/{version}/account/projects/{projectId}/settings/accent-color` | `account` | -| `update_project_icon` | `PATCH` | `/{version}/account/projects/{projectId}/settings/icon` | `account` | -| `update_project_logo` | `PATCH` | `/{version}/account/projects/{projectId}/settings/logo` | `account` | -| `update_project_main_color` | `PATCH` | `/{version}/account/projects/{projectId}/settings/main-color` | `account` | -| `update_project_allowed_origins` | `PATCH` | `/{version}/account/projects/{projectId}/settings/origins` | `account` | -| `update_project_default_language` | `PATCH` | `/{version}/account/projects/{projectId}/settings/default-language` | `account` | -| `update_project_description` | `PATCH` | `/{version}/account/projects/{projectId}/settings/description` | `account` | -| `disable_project` | `PATCH` | `/{version}/account/projects/{projectId}/disable` | `account` | -| `enable_project` | `PATCH` | `/{version}/account/projects/{projectId}/enable` | `account` | -| `check_project_languages` | `POST` | `/{version}/account/projects/{projectId}/settings/languages/check` | `account` | -| `update_project_languages` | `PATCH` | `/{version}/account/projects/{projectId}/settings/languages` | `account` | -| `update_project_url` | `PATCH` | `/{version}/account/projects/{projectId}/settings/url` | `account` | -| `update_project_name` | `PATCH` | `/{version}/account/projects/{projectId}/settings/name` | `account` | -| `update_project_regions` | `PATCH` | `/{version}/account/projects/{projectId}/settings/regions` | `account` | -| `create_account` | `POST` | `/{version}/account` | `account` | +| `get_account_profile` | `GET` | `/{version}/account/profile` | `project` | +| `update_account_profile` | `PUT` | `/{version}/account/profile` | `project` | +| `resend_account_verification_token` | `GET` | `/{version}/account/verify/resend` | `project` | +| `get_account_status` | `GET` | `/{version}/account/status` | `project` | +| `create_stripe_checkout_session` | `POST` | `/{version}/account/stripe/create-checkout-session` | `project` | +| `get_stripe_billing_portal_url` | `POST` | `/{version}/account/stripe/get-portal-url` | `project` | +| `create_team_member_from_invitation` | `POST` | `/{version}/account/team/member` | `unauthenticated` | +| `verify_account` | `GET` | `/{version}/account/verify` | `unauthenticated` | +| `delete_notifications_group` | `DELETE` | `/{version}/account/projects/{projectId}/notifications/settings/group` | `project` | +| `delete_notifications_tag` | `DELETE` | `/{version}/account/projects/{projectId}/notifications/settings/tag` | `project` | +| `remove_tag_from_notifications_group` | `DELETE` | `/{version}/account/projects/{projectId}/notifications/settings/group/tag` | `project` | +| `save_notifications_group` | `POST` | `/{version}/account/projects/{projectId}/notifications/settings/group` | `project` | +| `save_notifications_tag` | `POST` | `/{version}/account/projects/{projectId}/notifications/settings/tag` | `project` | +| `create_project` | `POST` | `/{version}/account/projects` | `project` | +| `delete_project` | `DELETE` | `/{version}/account/projects/{projectId}` | `project` | +| `get_project` | `GET` | `/{version}/account/projects/{projectId}` | `project` | +| `get_projects` | `GET` | `/{version}/account/projects` | `project` | +| `get_account_regions` | `GET` | `/{version}/account/regions` | `unauthenticated` | +| `get_project_tokens` | `GET` | `/{version}/account/projects/{projectId}/tokens` | `project` | +| `update_project_accent_color` | `PATCH` | `/{version}/account/projects/{projectId}/settings/accent-color` | `project` | +| `update_project_icon` | `PATCH` | `/{version}/account/projects/{projectId}/settings/icon` | `project` | +| `update_project_logo` | `PATCH` | `/{version}/account/projects/{projectId}/settings/logo` | `project` | +| `update_project_main_color` | `PATCH` | `/{version}/account/projects/{projectId}/settings/main-color` | `project` | +| `update_project_allowed_origins` | `PATCH` | `/{version}/account/projects/{projectId}/settings/origins` | `project` | +| `update_project_default_language` | `PATCH` | `/{version}/account/projects/{projectId}/settings/default-language` | `project` | +| `update_project_description` | `PATCH` | `/{version}/account/projects/{projectId}/settings/description` | `project` | +| `disable_project` | `PATCH` | `/{version}/account/projects/{projectId}/disable` | `project` | +| `enable_project` | `PATCH` | `/{version}/account/projects/{projectId}/enable` | `project` | +| `check_project_languages` | `POST` | `/{version}/account/projects/{projectId}/settings/languages/check` | `project` | +| `update_project_languages` | `PATCH` | `/{version}/account/projects/{projectId}/settings/languages` | `project` | +| `update_project_url` | `PATCH` | `/{version}/account/projects/{projectId}/settings/url` | `project` | +| `update_project_name` | `PATCH` | `/{version}/account/projects/{projectId}/settings/name` | `project` | +| `update_project_regions` | `PATCH` | `/{version}/account/projects/{projectId}/settings/regions` | `project` | +| `create_account` | `POST` | `/{version}/account` | `unauthenticated` | | `get_account_collaborators` | `GET` | `/{version}/account/collaborators` | `project` | | `get_my_account_user_profile` | `GET` | `/{version}/account/me` | `project` | | `update_my_account_user_phone` | `PUT` | `/{version}/account/me/phone` | `project` | -| `send_invite_to_team_member` | `POST` | `/{version}/account/team/member/invite` | `account` | -| `get_licenses` | `GET` | `/{version}/account/licenses` | `account` | +| `send_invite_to_team_member` | `POST` | `/{version}/account/team/member/invite` | `project` | +| `get_licenses` | `GET` | `/{version}/account/licenses` | `project` | | `get_project_ai_settings` | `GET` | `/{version}/account/projects/{projectId}/ai/settings` | `project` | | `update_project_ai_settings` | `PUT` | `/{version}/account/projects/{projectId}/ai/settings` | `project` | | `create_project_ai_assistant` | `POST` | `/{version}/account/projects/{projectId}/ai/assistants` | `project` | diff --git a/src/norbix_python/hub/account.py b/src/norbix_python/hub/account.py index 5a1b25c..66ce095 100644 --- a/src/norbix_python/hub/account.py +++ b/src/norbix_python/hub/account.py @@ -75,7 +75,7 @@ def get_account_profile(self, *, timeout: float | None = None, bearer_token: str method="GET", path_params={}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -88,7 +88,7 @@ def update_account_profile(self, *, timeout: float | None = None, bearer_token: method="PUT", path_params={}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -101,7 +101,7 @@ def resend_account_verification_token(self, *, timeout: float | None = None, bea method="GET", path_params={}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -114,7 +114,7 @@ def get_account_status(self, *, timeout: float | None = None, bearer_token: str method="GET", path_params={}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -127,7 +127,7 @@ def create_stripe_checkout_session(self, *, timeout: float | None = None, bearer method="POST", path_params={}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -140,7 +140,7 @@ def get_stripe_billing_portal_url(self, *, timeout: float | None = None, bearer_ method="POST", path_params={}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -153,20 +153,25 @@ def create_team_member_from_invitation(self, *, timeout: float | None = None, be method="POST", path_params={}, request=request, - scope="account", + scope="unauthenticated", timeout=timeout, bearer_token=bearer_token, ) def verify_account(self, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: - """GET /{version}/account/verify""" + """GET /{version}/account/verify + + Needs no token and no ``account_id`` on the client: the route is + public on the gateway, which reads the account id and the token from + this request. Pass both here: ``verify_account(accountId=..., token=...)``. + """ return self._transport.send( target="hub", path="/{version}/account/verify", method="GET", path_params={}, request=request, - scope="account", + scope="unauthenticated", timeout=timeout, bearer_token=bearer_token, ) @@ -179,7 +184,7 @@ def delete_notifications_group(self, project_id: str, *, timeout: float | None = method="DELETE", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -192,7 +197,7 @@ def delete_notifications_tag(self, project_id: str, *, timeout: float | None = N method="DELETE", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -205,7 +210,7 @@ def remove_tag_from_notifications_group(self, project_id: str, *, timeout: float method="DELETE", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -218,7 +223,7 @@ def save_notifications_group(self, project_id: str, *, timeout: float | None = N method="POST", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -231,7 +236,7 @@ def save_notifications_tag(self, project_id: str, *, timeout: float | None = Non method="POST", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -252,7 +257,7 @@ def create_project(self, *, primary_region: str | None = None, additional_region "additionalRegions": additional_regions, **request, }, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -265,7 +270,7 @@ def delete_project(self, project_id: str, *, timeout: float | None = None, beare method="DELETE", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -278,7 +283,7 @@ def get_project(self, project_id: str, *, timeout: float | None = None, bearer_t method="GET", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -291,7 +296,7 @@ def get_projects(self, *, timeout: float | None = None, bearer_token: str | None method="GET", path_params={}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -304,7 +309,7 @@ def get_account_regions(self, *, timeout: float | None = None, bearer_token: str method="GET", path_params={}, request=request, - scope="account", + scope="unauthenticated", timeout=timeout, bearer_token=bearer_token, ) @@ -317,7 +322,7 @@ def get_project_tokens(self, project_id: str, *, timeout: float | None = None, b method="GET", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -330,7 +335,7 @@ def update_project_accent_color(self, project_id: str, *, timeout: float | None method="PATCH", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -343,7 +348,7 @@ def update_project_icon(self, project_id: str, *, timeout: float | None = None, method="PATCH", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -356,7 +361,7 @@ def update_project_logo(self, project_id: str, *, timeout: float | None = None, method="PATCH", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -369,7 +374,7 @@ def update_project_main_color(self, project_id: str, *, timeout: float | None = method="PATCH", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -382,7 +387,7 @@ def update_project_allowed_origins(self, project_id: str, *, timeout: float | No method="PATCH", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -395,7 +400,7 @@ def update_project_default_language(self, project_id: str, *, timeout: float | N method="PATCH", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -408,7 +413,7 @@ def update_project_description(self, project_id: str, *, timeout: float | None = method="PATCH", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -421,7 +426,7 @@ def disable_project(self, project_id: str, *, timeout: float | None = None, bear method="PATCH", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -434,7 +439,7 @@ def enable_project(self, project_id: str, *, timeout: float | None = None, beare method="PATCH", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -454,7 +459,7 @@ def check_project_languages(self, project_id: str, *, timeout: float | None = No method="POST", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -467,7 +472,7 @@ def update_project_languages(self, project_id: str, *, timeout: float | None = N method="PATCH", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -480,7 +485,7 @@ def update_project_url(self, project_id: str, *, timeout: float | None = None, b method="PATCH", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -493,7 +498,7 @@ def update_project_name(self, project_id: str, *, timeout: float | None = None, method="PATCH", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -506,7 +511,7 @@ def update_project_regions(self, project_id: str, *, timeout: float | None = Non method="PATCH", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -519,7 +524,7 @@ def create_account(self, *, timeout: float | None = None, bearer_token: str | No method="POST", path_params={}, request=request, - scope="account", + scope="unauthenticated", timeout=timeout, bearer_token=bearer_token, ) @@ -587,7 +592,7 @@ def send_invite_to_team_member(self, *, timeout: float | None = None, bearer_tok method="POST", path_params={}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -600,7 +605,7 @@ def get_licenses(self, *, timeout: float | None = None, bearer_token: str | None method="GET", path_params={}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -1033,7 +1038,7 @@ async def get_account_profile(self, *, timeout: float | None = None, bearer_toke method="GET", path_params={}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -1046,7 +1051,7 @@ async def update_account_profile(self, *, timeout: float | None = None, bearer_t method="PUT", path_params={}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -1059,7 +1064,7 @@ async def resend_account_verification_token(self, *, timeout: float | None = Non method="GET", path_params={}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -1072,7 +1077,7 @@ async def get_account_status(self, *, timeout: float | None = None, bearer_token method="GET", path_params={}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -1085,7 +1090,7 @@ async def create_stripe_checkout_session(self, *, timeout: float | None = None, method="POST", path_params={}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -1098,7 +1103,7 @@ async def get_stripe_billing_portal_url(self, *, timeout: float | None = None, b method="POST", path_params={}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -1111,20 +1116,25 @@ async def create_team_member_from_invitation(self, *, timeout: float | None = No method="POST", path_params={}, request=request, - scope="account", + scope="unauthenticated", timeout=timeout, bearer_token=bearer_token, ) async def verify_account(self, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: - """GET /{version}/account/verify""" + """GET /{version}/account/verify + + Needs no token and no ``account_id`` on the client: the route is + public on the gateway, which reads the account id and the token from + this request. Pass both here: ``verify_account(accountId=..., token=...)``. + """ return await self._transport.send( target="hub", path="/{version}/account/verify", method="GET", path_params={}, request=request, - scope="account", + scope="unauthenticated", timeout=timeout, bearer_token=bearer_token, ) @@ -1137,7 +1147,7 @@ async def delete_notifications_group(self, project_id: str, *, timeout: float | method="DELETE", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -1150,7 +1160,7 @@ async def delete_notifications_tag(self, project_id: str, *, timeout: float | No method="DELETE", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -1163,7 +1173,7 @@ async def remove_tag_from_notifications_group(self, project_id: str, *, timeout: method="DELETE", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -1176,7 +1186,7 @@ async def save_notifications_group(self, project_id: str, *, timeout: float | No method="POST", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -1189,7 +1199,7 @@ async def save_notifications_tag(self, project_id: str, *, timeout: float | None method="POST", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -1210,7 +1220,7 @@ async def create_project(self, *, primary_region: str | None = None, additional_ "additionalRegions": additional_regions, **request, }, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -1223,7 +1233,7 @@ async def delete_project(self, project_id: str, *, timeout: float | None = None, method="DELETE", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -1236,7 +1246,7 @@ async def get_project(self, project_id: str, *, timeout: float | None = None, be method="GET", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -1249,7 +1259,7 @@ async def get_projects(self, *, timeout: float | None = None, bearer_token: str method="GET", path_params={}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -1262,7 +1272,7 @@ async def get_account_regions(self, *, timeout: float | None = None, bearer_toke method="GET", path_params={}, request=request, - scope="account", + scope="unauthenticated", timeout=timeout, bearer_token=bearer_token, ) @@ -1275,7 +1285,7 @@ async def get_project_tokens(self, project_id: str, *, timeout: float | None = N method="GET", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -1288,7 +1298,7 @@ async def update_project_accent_color(self, project_id: str, *, timeout: float | method="PATCH", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -1301,7 +1311,7 @@ async def update_project_icon(self, project_id: str, *, timeout: float | None = method="PATCH", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -1314,7 +1324,7 @@ async def update_project_logo(self, project_id: str, *, timeout: float | None = method="PATCH", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -1327,7 +1337,7 @@ async def update_project_main_color(self, project_id: str, *, timeout: float | N method="PATCH", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -1340,7 +1350,7 @@ async def update_project_allowed_origins(self, project_id: str, *, timeout: floa method="PATCH", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -1353,7 +1363,7 @@ async def update_project_default_language(self, project_id: str, *, timeout: flo method="PATCH", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -1366,7 +1376,7 @@ async def update_project_description(self, project_id: str, *, timeout: float | method="PATCH", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -1379,7 +1389,7 @@ async def disable_project(self, project_id: str, *, timeout: float | None = None method="PATCH", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -1392,7 +1402,7 @@ async def enable_project(self, project_id: str, *, timeout: float | None = None, method="PATCH", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -1412,7 +1422,7 @@ async def check_project_languages(self, project_id: str, *, timeout: float | Non method="POST", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -1425,7 +1435,7 @@ async def update_project_languages(self, project_id: str, *, timeout: float | No method="PATCH", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -1438,7 +1448,7 @@ async def update_project_url(self, project_id: str, *, timeout: float | None = N method="PATCH", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -1451,7 +1461,7 @@ async def update_project_name(self, project_id: str, *, timeout: float | None = method="PATCH", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -1464,7 +1474,7 @@ async def update_project_regions(self, project_id: str, *, timeout: float | None method="PATCH", path_params={"projectId": project_id}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -1477,7 +1487,7 @@ async def create_account(self, *, timeout: float | None = None, bearer_token: st method="POST", path_params={}, request=request, - scope="account", + scope="unauthenticated", timeout=timeout, bearer_token=bearer_token, ) @@ -1545,7 +1555,7 @@ async def send_invite_to_team_member(self, *, timeout: float | None = None, bear method="POST", path_params={}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) @@ -1558,7 +1568,7 @@ async def get_licenses(self, *, timeout: float | None = None, bearer_token: str method="GET", path_params={}, request=request, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, ) diff --git a/src/norbix_python/hub/regions.py b/src/norbix_python/hub/regions.py index f2b525c..0369e21 100644 --- a/src/norbix_python/hub/regions.py +++ b/src/norbix_python/hub/regions.py @@ -35,7 +35,7 @@ def list( method="GET", path_params={}, request=request, - scope="account", + scope="unauthenticated", timeout=timeout, bearer_token=bearer_token, region=region, @@ -67,7 +67,7 @@ def update_project_regions( "additionalRegions": additional_regions, **request, }, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, region=region, @@ -88,7 +88,7 @@ async def list( method="GET", path_params={}, request=request, - scope="account", + scope="unauthenticated", timeout=timeout, bearer_token=bearer_token, region=region, @@ -116,7 +116,7 @@ async def update_project_regions( "additionalRegions": additional_regions, **request, }, - scope="account", + scope="project", timeout=timeout, bearer_token=bearer_token, region=region, diff --git a/tests/hub/test_account.py b/tests/hub/test_account.py index 7c9792c..525104c 100644 --- a/tests/hub/test_account.py +++ b/tests/hub/test_account.py @@ -1,6 +1,6 @@ from __future__ import annotations -from norbix_python import Norbix, NorbixError +from norbix_python import Norbix from ..helpers import make_client @@ -54,15 +54,6 @@ def test_hub_account_get_account_profile_request_shape() -> None: assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') -def test_hub_account_get_account_profile_requires_account_scope() -> None: - client = Norbix(project_id='p1', bearer_token='token') - try: - client.hub.account.get_account_profile() - except NorbixError as exc: - assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' - else: - raise AssertionError('Expected account scope error') - def test_hub_account_update_account_profile_request_shape() -> None: client, transport = make_client(account_id='acc-1') client.hub.account.update_account_profile() @@ -70,15 +61,6 @@ def test_hub_account_update_account_profile_request_shape() -> None: assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') -def test_hub_account_update_account_profile_requires_account_scope() -> None: - client = Norbix(project_id='p1', bearer_token='token') - try: - client.hub.account.update_account_profile() - except NorbixError as exc: - assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' - else: - raise AssertionError('Expected account scope error') - def test_hub_account_resend_account_verification_token_request_shape() -> None: client, transport = make_client(account_id='acc-1') client.hub.account.resend_account_verification_token() @@ -86,15 +68,6 @@ def test_hub_account_resend_account_verification_token_request_shape() -> None: assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') -def test_hub_account_resend_account_verification_token_requires_account_scope() -> None: - client = Norbix(project_id='p1', bearer_token='token') - try: - client.hub.account.resend_account_verification_token() - except NorbixError as exc: - assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' - else: - raise AssertionError('Expected account scope error') - def test_hub_account_get_account_status_request_shape() -> None: client, transport = make_client(account_id='acc-1') client.hub.account.get_account_status() @@ -102,15 +75,6 @@ def test_hub_account_get_account_status_request_shape() -> None: assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') -def test_hub_account_get_account_status_requires_account_scope() -> None: - client = Norbix(project_id='p1', bearer_token='token') - try: - client.hub.account.get_account_status() - except NorbixError as exc: - assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' - else: - raise AssertionError('Expected account scope error') - def test_hub_account_create_stripe_checkout_session_request_shape() -> None: client, transport = make_client(account_id='acc-1') client.hub.account.create_stripe_checkout_session() @@ -118,15 +82,6 @@ def test_hub_account_create_stripe_checkout_session_request_shape() -> None: assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') -def test_hub_account_create_stripe_checkout_session_requires_account_scope() -> None: - client = Norbix(project_id='p1', bearer_token='token') - try: - client.hub.account.create_stripe_checkout_session() - except NorbixError as exc: - assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' - else: - raise AssertionError('Expected account scope error') - def test_hub_account_get_stripe_billing_portal_url_request_shape() -> None: client, transport = make_client(account_id='acc-1') client.hub.account.get_stripe_billing_portal_url() @@ -134,15 +89,6 @@ def test_hub_account_get_stripe_billing_portal_url_request_shape() -> None: assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') -def test_hub_account_get_stripe_billing_portal_url_requires_account_scope() -> None: - client = Norbix(project_id='p1', bearer_token='token') - try: - client.hub.account.get_stripe_billing_portal_url() - except NorbixError as exc: - assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' - else: - raise AssertionError('Expected account scope error') - def test_hub_account_create_team_member_from_invitation_request_shape() -> None: client, transport = make_client(account_id='acc-1') client.hub.account.create_team_member_from_invitation() @@ -150,15 +96,6 @@ def test_hub_account_create_team_member_from_invitation_request_shape() -> None: assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') -def test_hub_account_create_team_member_from_invitation_requires_account_scope() -> None: - client = Norbix(project_id='p1', bearer_token='token') - try: - client.hub.account.create_team_member_from_invitation() - except NorbixError as exc: - assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' - else: - raise AssertionError('Expected account scope error') - def test_hub_account_verify_account_request_shape() -> None: client, transport = make_client(account_id='acc-1') client.hub.account.verify_account() @@ -166,15 +103,6 @@ def test_hub_account_verify_account_request_shape() -> None: assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') -def test_hub_account_verify_account_requires_account_scope() -> None: - client = Norbix(project_id='p1', bearer_token='token') - try: - client.hub.account.verify_account() - except NorbixError as exc: - assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' - else: - raise AssertionError('Expected account scope error') - def test_hub_account_delete_notifications_group_request_shape() -> None: client, transport = make_client(account_id='acc-1') client.hub.account.delete_notifications_group(project_id="stub-projectId") @@ -182,15 +110,6 @@ def test_hub_account_delete_notifications_group_request_shape() -> None: assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') -def test_hub_account_delete_notifications_group_requires_account_scope() -> None: - client = Norbix(project_id='p1', bearer_token='token') - try: - client.hub.account.delete_notifications_group(project_id="stub") - except NorbixError as exc: - assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' - else: - raise AssertionError('Expected account scope error') - def test_hub_account_delete_notifications_tag_request_shape() -> None: client, transport = make_client(account_id='acc-1') client.hub.account.delete_notifications_tag(project_id="stub-projectId") @@ -198,15 +117,6 @@ def test_hub_account_delete_notifications_tag_request_shape() -> None: assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') -def test_hub_account_delete_notifications_tag_requires_account_scope() -> None: - client = Norbix(project_id='p1', bearer_token='token') - try: - client.hub.account.delete_notifications_tag(project_id="stub") - except NorbixError as exc: - assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' - else: - raise AssertionError('Expected account scope error') - def test_hub_account_remove_tag_from_notifications_group_request_shape() -> None: client, transport = make_client(account_id='acc-1') client.hub.account.remove_tag_from_notifications_group(project_id="stub-projectId") @@ -214,15 +124,6 @@ def test_hub_account_remove_tag_from_notifications_group_request_shape() -> None assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') -def test_hub_account_remove_tag_from_notifications_group_requires_account_scope() -> None: - client = Norbix(project_id='p1', bearer_token='token') - try: - client.hub.account.remove_tag_from_notifications_group(project_id="stub") - except NorbixError as exc: - assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' - else: - raise AssertionError('Expected account scope error') - def test_hub_account_save_notifications_group_request_shape() -> None: client, transport = make_client(account_id='acc-1') client.hub.account.save_notifications_group(project_id="stub-projectId") @@ -230,15 +131,6 @@ def test_hub_account_save_notifications_group_request_shape() -> None: assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') -def test_hub_account_save_notifications_group_requires_account_scope() -> None: - client = Norbix(project_id='p1', bearer_token='token') - try: - client.hub.account.save_notifications_group(project_id="stub") - except NorbixError as exc: - assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' - else: - raise AssertionError('Expected account scope error') - def test_hub_account_save_notifications_tag_request_shape() -> None: client, transport = make_client(account_id='acc-1') client.hub.account.save_notifications_tag(project_id="stub-projectId") @@ -246,15 +138,6 @@ def test_hub_account_save_notifications_tag_request_shape() -> None: assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') -def test_hub_account_save_notifications_tag_requires_account_scope() -> None: - client = Norbix(project_id='p1', bearer_token='token') - try: - client.hub.account.save_notifications_tag(project_id="stub") - except NorbixError as exc: - assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' - else: - raise AssertionError('Expected account scope error') - def test_hub_account_create_project_request_shape() -> None: client, transport = make_client(account_id='acc-1') client.hub.account.create_project() @@ -262,15 +145,6 @@ def test_hub_account_create_project_request_shape() -> None: assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') -def test_hub_account_create_project_requires_account_scope() -> None: - client = Norbix(project_id='p1', bearer_token='token') - try: - client.hub.account.create_project() - except NorbixError as exc: - assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' - else: - raise AssertionError('Expected account scope error') - def test_hub_account_delete_project_request_shape() -> None: client, transport = make_client(account_id='acc-1') client.hub.account.delete_project(project_id="stub-projectId") @@ -278,15 +152,6 @@ def test_hub_account_delete_project_request_shape() -> None: assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') -def test_hub_account_delete_project_requires_account_scope() -> None: - client = Norbix(project_id='p1', bearer_token='token') - try: - client.hub.account.delete_project(project_id="stub") - except NorbixError as exc: - assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' - else: - raise AssertionError('Expected account scope error') - def test_hub_account_get_project_request_shape() -> None: client, transport = make_client(account_id='acc-1') client.hub.account.get_project(project_id="stub-projectId") @@ -294,15 +159,6 @@ def test_hub_account_get_project_request_shape() -> None: assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') -def test_hub_account_get_project_requires_account_scope() -> None: - client = Norbix(project_id='p1', bearer_token='token') - try: - client.hub.account.get_project(project_id="stub") - except NorbixError as exc: - assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' - else: - raise AssertionError('Expected account scope error') - def test_hub_account_get_projects_request_shape() -> None: client, transport = make_client(account_id='acc-1') client.hub.account.get_projects() @@ -310,15 +166,6 @@ def test_hub_account_get_projects_request_shape() -> None: assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') -def test_hub_account_get_projects_requires_account_scope() -> None: - client = Norbix(project_id='p1', bearer_token='token') - try: - client.hub.account.get_projects() - except NorbixError as exc: - assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' - else: - raise AssertionError('Expected account scope error') - def test_hub_account_get_account_regions_request_shape() -> None: client, transport = make_client(account_id='acc-1') client.hub.account.get_account_regions() @@ -326,15 +173,6 @@ def test_hub_account_get_account_regions_request_shape() -> None: assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') -def test_hub_account_get_account_regions_requires_account_scope() -> None: - client = Norbix(project_id='p1', bearer_token='token') - try: - client.hub.account.get_account_regions() - except NorbixError as exc: - assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' - else: - raise AssertionError('Expected account scope error') - def test_hub_account_get_project_tokens_request_shape() -> None: client, transport = make_client(account_id='acc-1') client.hub.account.get_project_tokens(project_id="stub-projectId") @@ -342,15 +180,6 @@ def test_hub_account_get_project_tokens_request_shape() -> None: assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') -def test_hub_account_get_project_tokens_requires_account_scope() -> None: - client = Norbix(project_id='p1', bearer_token='token') - try: - client.hub.account.get_project_tokens(project_id="stub") - except NorbixError as exc: - assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' - else: - raise AssertionError('Expected account scope error') - def test_hub_account_update_project_accent_color_request_shape() -> None: client, transport = make_client(account_id='acc-1') client.hub.account.update_project_accent_color(project_id="stub-projectId") @@ -358,15 +187,6 @@ def test_hub_account_update_project_accent_color_request_shape() -> None: assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') -def test_hub_account_update_project_accent_color_requires_account_scope() -> None: - client = Norbix(project_id='p1', bearer_token='token') - try: - client.hub.account.update_project_accent_color(project_id="stub") - except NorbixError as exc: - assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' - else: - raise AssertionError('Expected account scope error') - def test_hub_account_update_project_icon_request_shape() -> None: client, transport = make_client(account_id='acc-1') client.hub.account.update_project_icon(project_id="stub-projectId") @@ -374,15 +194,6 @@ def test_hub_account_update_project_icon_request_shape() -> None: assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') -def test_hub_account_update_project_icon_requires_account_scope() -> None: - client = Norbix(project_id='p1', bearer_token='token') - try: - client.hub.account.update_project_icon(project_id="stub") - except NorbixError as exc: - assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' - else: - raise AssertionError('Expected account scope error') - def test_hub_account_update_project_logo_request_shape() -> None: client, transport = make_client(account_id='acc-1') client.hub.account.update_project_logo(project_id="stub-projectId") @@ -390,15 +201,6 @@ def test_hub_account_update_project_logo_request_shape() -> None: assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') -def test_hub_account_update_project_logo_requires_account_scope() -> None: - client = Norbix(project_id='p1', bearer_token='token') - try: - client.hub.account.update_project_logo(project_id="stub") - except NorbixError as exc: - assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' - else: - raise AssertionError('Expected account scope error') - def test_hub_account_update_project_main_color_request_shape() -> None: client, transport = make_client(account_id='acc-1') client.hub.account.update_project_main_color(project_id="stub-projectId") @@ -406,15 +208,6 @@ def test_hub_account_update_project_main_color_request_shape() -> None: assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') -def test_hub_account_update_project_main_color_requires_account_scope() -> None: - client = Norbix(project_id='p1', bearer_token='token') - try: - client.hub.account.update_project_main_color(project_id="stub") - except NorbixError as exc: - assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' - else: - raise AssertionError('Expected account scope error') - def test_hub_account_update_project_allowed_origins_request_shape() -> None: client, transport = make_client(account_id='acc-1') client.hub.account.update_project_allowed_origins(project_id="stub-projectId") @@ -422,15 +215,6 @@ def test_hub_account_update_project_allowed_origins_request_shape() -> None: assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') -def test_hub_account_update_project_allowed_origins_requires_account_scope() -> None: - client = Norbix(project_id='p1', bearer_token='token') - try: - client.hub.account.update_project_allowed_origins(project_id="stub") - except NorbixError as exc: - assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' - else: - raise AssertionError('Expected account scope error') - def test_hub_account_update_project_default_language_request_shape() -> None: client, transport = make_client(account_id='acc-1') client.hub.account.update_project_default_language(project_id="stub-projectId") @@ -438,15 +222,6 @@ def test_hub_account_update_project_default_language_request_shape() -> None: assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') -def test_hub_account_update_project_default_language_requires_account_scope() -> None: - client = Norbix(project_id='p1', bearer_token='token') - try: - client.hub.account.update_project_default_language(project_id="stub") - except NorbixError as exc: - assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' - else: - raise AssertionError('Expected account scope error') - def test_hub_account_update_project_description_request_shape() -> None: client, transport = make_client(account_id='acc-1') client.hub.account.update_project_description(project_id="stub-projectId") @@ -454,15 +229,6 @@ def test_hub_account_update_project_description_request_shape() -> None: assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') -def test_hub_account_update_project_description_requires_account_scope() -> None: - client = Norbix(project_id='p1', bearer_token='token') - try: - client.hub.account.update_project_description(project_id="stub") - except NorbixError as exc: - assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' - else: - raise AssertionError('Expected account scope error') - def test_hub_account_disable_project_request_shape() -> None: client, transport = make_client(account_id='acc-1') client.hub.account.disable_project(project_id="stub-projectId") @@ -470,15 +236,6 @@ def test_hub_account_disable_project_request_shape() -> None: assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') -def test_hub_account_disable_project_requires_account_scope() -> None: - client = Norbix(project_id='p1', bearer_token='token') - try: - client.hub.account.disable_project(project_id="stub") - except NorbixError as exc: - assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' - else: - raise AssertionError('Expected account scope error') - def test_hub_account_enable_project_request_shape() -> None: client, transport = make_client(account_id='acc-1') client.hub.account.enable_project(project_id="stub-projectId") @@ -486,15 +243,6 @@ def test_hub_account_enable_project_request_shape() -> None: assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') -def test_hub_account_enable_project_requires_account_scope() -> None: - client = Norbix(project_id='p1', bearer_token='token') - try: - client.hub.account.enable_project(project_id="stub") - except NorbixError as exc: - assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' - else: - raise AssertionError('Expected account scope error') - def test_hub_account_update_project_languages_request_shape() -> None: client, transport = make_client(account_id='acc-1') client.hub.account.update_project_languages(project_id="stub-projectId") @@ -502,15 +250,6 @@ def test_hub_account_update_project_languages_request_shape() -> None: assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') -def test_hub_account_update_project_languages_requires_account_scope() -> None: - client = Norbix(project_id='p1', bearer_token='token') - try: - client.hub.account.update_project_languages(project_id="stub") - except NorbixError as exc: - assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' - else: - raise AssertionError('Expected account scope error') - def test_hub_account_update_project_url_request_shape() -> None: client, transport = make_client(account_id='acc-1') client.hub.account.update_project_url(project_id="stub-projectId") @@ -518,15 +257,6 @@ def test_hub_account_update_project_url_request_shape() -> None: assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') -def test_hub_account_update_project_url_requires_account_scope() -> None: - client = Norbix(project_id='p1', bearer_token='token') - try: - client.hub.account.update_project_url(project_id="stub") - except NorbixError as exc: - assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' - else: - raise AssertionError('Expected account scope error') - def test_hub_account_update_project_name_request_shape() -> None: client, transport = make_client(account_id='acc-1') client.hub.account.update_project_name(project_id="stub-projectId") @@ -534,15 +264,6 @@ def test_hub_account_update_project_name_request_shape() -> None: assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') -def test_hub_account_update_project_name_requires_account_scope() -> None: - client = Norbix(project_id='p1', bearer_token='token') - try: - client.hub.account.update_project_name(project_id="stub") - except NorbixError as exc: - assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' - else: - raise AssertionError('Expected account scope error') - def test_hub_account_update_project_regions_request_shape() -> None: client, transport = make_client(account_id='acc-1') client.hub.account.update_project_regions(project_id="stub-projectId") @@ -550,15 +271,6 @@ def test_hub_account_update_project_regions_request_shape() -> None: assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') -def test_hub_account_update_project_regions_requires_account_scope() -> None: - client = Norbix(project_id='p1', bearer_token='token') - try: - client.hub.account.update_project_regions(project_id="stub") - except NorbixError as exc: - assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' - else: - raise AssertionError('Expected account scope error') - def test_hub_account_create_account_request_shape() -> None: client, transport = make_client(account_id='acc-1') client.hub.account.create_account() @@ -566,15 +278,6 @@ def test_hub_account_create_account_request_shape() -> None: assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') -def test_hub_account_create_account_requires_account_scope() -> None: - client = Norbix(project_id='p1', bearer_token='token') - try: - client.hub.account.create_account() - except NorbixError as exc: - assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' - else: - raise AssertionError('Expected account scope error') - def test_hub_account_get_account_collaborators_request_shape() -> None: client, transport = make_client(account_id='acc-1') client.hub.account.get_account_collaborators() @@ -598,15 +301,6 @@ def test_hub_account_send_invite_to_team_member_request_shape() -> None: assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') -def test_hub_account_send_invite_to_team_member_requires_account_scope() -> None: - client = Norbix(project_id='p1', bearer_token='token') - try: - client.hub.account.send_invite_to_team_member() - except NorbixError as exc: - assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' - else: - raise AssertionError('Expected account scope error') - def test_hub_account_get_licenses_request_shape() -> None: client, transport = make_client(account_id='acc-1') client.hub.account.get_licenses() @@ -614,15 +308,6 @@ def test_hub_account_get_licenses_request_shape() -> None: assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') -def test_hub_account_get_licenses_requires_account_scope() -> None: - client = Norbix(project_id='p1', bearer_token='token') - try: - client.hub.account.get_licenses() - except NorbixError as exc: - assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' - else: - raise AssertionError('Expected account scope error') - def test_hub_account_wave3_module_surface() -> None: client, _ = make_client() module = client.hub.account @@ -719,15 +404,6 @@ def test_hub_account_check_project_languages_request_shape() -> None: assert json.loads(transport.last_request['body']) == {"defaultLanguage": "en", "languages": ["en", "de"]} assert transport.last_request['headers']['x-cm-accountid'] == 'acc-1' -def test_hub_account_check_project_languages_requires_account_scope() -> None: - client = Norbix(project_id='p1', bearer_token='token') - try: - client.hub.account.check_project_languages(project_id="stub") - except NorbixError as exc: - assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' - else: - raise AssertionError('Expected account scope error') - def test_async_hub_account_check_project_languages_hits_the_route() -> None: import asyncio import json diff --git a/tests/hub/test_account_token_only.py b/tests/hub/test_account_token_only.py new file mode 100644 index 0000000..e980e1b --- /dev/null +++ b/tests/hub/test_account_token_only.py @@ -0,0 +1,186 @@ +"""Every account route that needs only a token (no ``account_id``). + +The gateway takes the account from the signed-in session on each route below +(``UserSession.UserAuth.AccountId``), or from the project id in the path, or +needs no account at all — and it never reads the ``X-CM-AccountId`` header. +So each method must work on a client WITHOUT ``account_id``, like the +TypeScript, .NET, Go, Kotlin and Swift SDKs. One test per method, for the +sync and the async client: verb, resolved path, Bearer header and no account +header. Nothing leaves the process. + +Four routes are public on the gateway (no ``[Authenticate]``): sign-up +(``create_account``), accepting an invitation +(``create_team_member_from_invitation``), the region list +(``get_account_regions`` / ``regions.list``) and ``verify_account``. They go +out with NO token at all; ``verify_account`` sends the account id once, in the +request (the gateway reads ``VerifyAccount.AccountId``, ``Account/Verify.cs``). +""" +from __future__ import annotations + +import asyncio +import json +from typing import Any +from urllib.parse import parse_qsl, urlparse + +import httpx +import pytest + +from norbix_python import AsyncNorbix, Norbix + +# (module, method, verb, path, positional route ids) +CASES: list[tuple[str, str, str, str, dict[str, str]]] = [ + ("account", "get_account_profile", "GET", "/v2/account/profile", {}), + ("account", "update_account_profile", "PUT", "/v2/account/profile", {}), + ("account", "resend_account_verification_token", "GET", "/v2/account/verify/resend", {}), + ("account", "get_account_status", "GET", "/v2/account/status", {}), + ("account", "create_stripe_checkout_session", "POST", "/v2/account/stripe/create-checkout-session", {}), + ("account", "get_stripe_billing_portal_url", "POST", "/v2/account/stripe/get-portal-url", {}), + ("account", "delete_notifications_group", "DELETE", "/v2/account/projects/project_id_1/notifications/settings/group", {'project_id': "project_id_1"}), + ("account", "delete_notifications_tag", "DELETE", "/v2/account/projects/project_id_1/notifications/settings/tag", {'project_id': "project_id_1"}), + ("account", "remove_tag_from_notifications_group", "DELETE", "/v2/account/projects/project_id_1/notifications/settings/group/tag", {'project_id': "project_id_1"}), + ("account", "save_notifications_group", "POST", "/v2/account/projects/project_id_1/notifications/settings/group", {'project_id': "project_id_1"}), + ("account", "save_notifications_tag", "POST", "/v2/account/projects/project_id_1/notifications/settings/tag", {'project_id': "project_id_1"}), + ("account", "create_project", "POST", "/v2/account/projects", {}), + ("account", "delete_project", "DELETE", "/v2/account/projects/project_id_1", {'project_id': "project_id_1"}), + ("account", "get_project", "GET", "/v2/account/projects/project_id_1", {'project_id': "project_id_1"}), + ("account", "get_projects", "GET", "/v2/account/projects", {}), + ("account", "get_project_tokens", "GET", "/v2/account/projects/project_id_1/tokens", {'project_id': "project_id_1"}), + ("account", "update_project_accent_color", "PATCH", "/v2/account/projects/project_id_1/settings/accent-color", {'project_id': "project_id_1"}), + ("account", "update_project_icon", "PATCH", "/v2/account/projects/project_id_1/settings/icon", {'project_id': "project_id_1"}), + ("account", "update_project_logo", "PATCH", "/v2/account/projects/project_id_1/settings/logo", {'project_id': "project_id_1"}), + ("account", "update_project_main_color", "PATCH", "/v2/account/projects/project_id_1/settings/main-color", {'project_id': "project_id_1"}), + ("account", "update_project_allowed_origins", "PATCH", "/v2/account/projects/project_id_1/settings/origins", {'project_id': "project_id_1"}), + ("account", "update_project_default_language", "PATCH", "/v2/account/projects/project_id_1/settings/default-language", {'project_id': "project_id_1"}), + ("account", "update_project_description", "PATCH", "/v2/account/projects/project_id_1/settings/description", {'project_id': "project_id_1"}), + ("account", "disable_project", "PATCH", "/v2/account/projects/project_id_1/disable", {'project_id': "project_id_1"}), + ("account", "enable_project", "PATCH", "/v2/account/projects/project_id_1/enable", {'project_id': "project_id_1"}), + ("account", "check_project_languages", "POST", "/v2/account/projects/project_id_1/settings/languages/check", {'project_id': "project_id_1"}), + ("account", "update_project_languages", "PATCH", "/v2/account/projects/project_id_1/settings/languages", {'project_id': "project_id_1"}), + ("account", "update_project_url", "PATCH", "/v2/account/projects/project_id_1/settings/url", {'project_id': "project_id_1"}), + ("account", "update_project_name", "PATCH", "/v2/account/projects/project_id_1/settings/name", {'project_id': "project_id_1"}), + ("account", "update_project_regions", "PATCH", "/v2/account/projects/project_id_1/settings/regions", {'project_id': "project_id_1"}), + ("account", "send_invite_to_team_member", "POST", "/v2/account/team/member/invite", {}), + ("account", "get_licenses", "GET", "/v2/account/licenses", {}), + ("regions", "update_project_regions", "PATCH", "/v2/account/projects/project_id_1/settings/regions", {'project_id': "project_id_1"}), +] + +IDS = [f"{module}.{method}" for module, method, *_ in CASES] + + +@pytest.fixture(autouse=True) +def _no_account_id_from_the_environment(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.delenv("NORBIX_ACCOUNT_ID", raising=False) + + +def _handler(seen: list[httpx.Request]) -> Any: + def handler(request: httpx.Request) -> httpx.Response: + seen.append(request) + return httpx.Response(200, json={}) + + return handler + + +def _assert_token_only(seen: list[httpx.Request], verb: str, path: str) -> None: + assert [(r.method, urlparse(str(r.url)).path) for r in seen] == [(verb, path)] + assert seen[0].headers["authorization"] == "Bearer test-token" + assert "x-cm-accountid" not in seen[0].headers + + +@pytest.mark.parametrize(("module", "method", "verb", "path", "ids"), CASES, ids=IDS) +def test_account_route_works_with_a_token_and_no_account_id( + module: str, method: str, verb: str, path: str, ids: dict[str, str] +) -> None: + seen: list[httpx.Request] = [] + client = Norbix( + project_id="test-project", + bearer_token="test-token", + http_client=httpx.Client(transport=httpx.MockTransport(_handler(seen))), + ) + + getattr(getattr(client.hub, module), method)(**ids) + + _assert_token_only(seen, verb, path) + + +@pytest.mark.parametrize(("module", "method", "verb", "path", "ids"), CASES, ids=IDS) +def test_async_account_route_works_with_a_token_and_no_account_id( + module: str, method: str, verb: str, path: str, ids: dict[str, str] +) -> None: + seen: list[httpx.Request] = [] + + async def run() -> None: + client = AsyncNorbix( + project_id="test-project", + bearer_token="test-token", + http_client=httpx.AsyncClient(transport=httpx.MockTransport(_handler(seen))), + ) + try: + await getattr(getattr(client.hub, module), method)(**ids) + finally: + await client.aclose() + + asyncio.run(run()) + + _assert_token_only(seen, verb, path) + + +# (module, method, verb, path, request fields) +PUBLIC_CASES: list[tuple[str, str, str, str, dict[str, str]]] = [ + ("account", "create_account", "POST", "/v2/account", {"email": "ada@example.test", "password": "pw", "displayName": "Ada"}), + ("account", "create_team_member_from_invitation", "POST", "/v2/account/team/member", {"token": "invite-1", "password": "pw", "displayName": "Ada"}), + ("account", "get_account_regions", "GET", "/v2/account/regions", {}), + ("regions", "list", "GET", "/v2/account/regions", {}), + ("account", "verify_account", "GET", "/v2/account/verify", {"accountId": "acc-1", "token": "verify-1"}), +] + +PUBLIC_IDS = [f"{module}.{method}" for module, method, *_ in PUBLIC_CASES] + + +def _assert_public(seen: list[httpx.Request], verb: str, path: str, fields: dict[str, str]) -> None: + assert [(r.method, urlparse(str(r.url)).path) for r in seen] == [(verb, path)] + assert "authorization" not in seen[0].headers + assert "x-cm-accountid" not in seen[0].headers + if verb == "GET": + assert dict(parse_qsl(urlparse(str(seen[0].url)).query)) == fields + else: + assert json.loads(seen[0].content) == fields + + +@pytest.mark.parametrize(("module", "method", "verb", "path", "fields"), PUBLIC_CASES, ids=PUBLIC_IDS) +def test_public_account_route_works_with_no_token_and_no_account_id( + module: str, method: str, verb: str, path: str, fields: dict[str, str], monkeypatch: pytest.MonkeyPatch +) -> None: + for name in ("NORBIX_API_KEY", "NORBIX_BEARER_TOKEN"): + monkeypatch.delenv(name, raising=False) + seen: list[httpx.Request] = [] + client = Norbix( + project_id="test-project", + http_client=httpx.Client(transport=httpx.MockTransport(_handler(seen))), + ) + + getattr(getattr(client.hub, module), method)(**fields) + + _assert_public(seen, verb, path, fields) + + +@pytest.mark.parametrize(("module", "method", "verb", "path", "fields"), PUBLIC_CASES, ids=PUBLIC_IDS) +def test_async_public_account_route_works_with_no_token_and_no_account_id( + module: str, method: str, verb: str, path: str, fields: dict[str, str], monkeypatch: pytest.MonkeyPatch +) -> None: + for name in ("NORBIX_API_KEY", "NORBIX_BEARER_TOKEN"): + monkeypatch.delenv(name, raising=False) + seen: list[httpx.Request] = [] + + async def run() -> None: + client = AsyncNorbix( + project_id="test-project", + http_client=httpx.AsyncClient(transport=httpx.MockTransport(_handler(seen))), + ) + try: + await getattr(getattr(client.hub, module), method)(**fields) + finally: + await client.aclose() + + asyncio.run(run()) + + _assert_public(seen, verb, path, fields)