Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/hub/account.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ 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
in the path, and never reads the `norbix-account-id` 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
Expand Down
8 changes: 4 additions & 4 deletions src/norbix_python/transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,9 @@ def send(
if token:
headers["Authorization"] = f"Bearer {token}"

headers["X-CM-ProjectId"] = self._cfg.project_id
headers["norbix-project-id"] = self._cfg.project_id
if self._cfg.account_id:
headers["X-CM-AccountId"] = self._cfg.account_id
headers["norbix-account-id"] = self._cfg.account_id
# Environment selector: per-call override wins over the client default.
# "PROD" is the backend default, so the header is omitted for it.
resolved_env = env if env is not None else self._cfg.env
Expand Down Expand Up @@ -275,9 +275,9 @@ async def send(
if token:
headers["Authorization"] = f"Bearer {token}"

headers["X-CM-ProjectId"] = self._cfg.project_id
headers["norbix-project-id"] = self._cfg.project_id
if self._cfg.account_id:
headers["X-CM-AccountId"] = self._cfg.account_id
headers["norbix-account-id"] = self._cfg.account_id
# Environment selector: per-call override wins over the client default.
# "PROD" is the backend default, so the header is omitted for it.
resolved_env = env if env is not None else self._cfg.env
Expand Down
32 changes: 16 additions & 16 deletions tests/api/test_ai.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def test_api_ai_get_end_user_chat_availability_request_shape() -> None:
assert transport.last_request['method'] == 'GET'
assert transport.last_request['url'].endswith('/v3/ai/chat/availability')
assert transport.last_request['headers']['authorization'] == 'Bearer test-token'
assert transport.last_request['headers']['x-cm-projectid'] == 'test-project'
assert transport.last_request['headers']['norbix-project-id'] == 'test-project'


def test_api_ai_list_end_user_chat_sessions_request_shape() -> None:
Expand All @@ -39,7 +39,7 @@ def test_api_ai_list_end_user_chat_sessions_request_shape() -> None:
assert transport.last_request['method'] == 'GET'
assert transport.last_request['url'].endswith('/v3/ai/chat/sessions')
assert transport.last_request['headers']['authorization'] == 'Bearer test-token'
assert transport.last_request['headers']['x-cm-projectid'] == 'test-project'
assert transport.last_request['headers']['norbix-project-id'] == 'test-project'


def test_api_ai_create_end_user_chat_session_request_shape() -> None:
Expand All @@ -49,7 +49,7 @@ def test_api_ai_create_end_user_chat_session_request_shape() -> None:
assert transport.last_request['method'] == 'POST'
assert transport.last_request['url'].endswith('/v3/ai/chat/sessions')
assert transport.last_request['headers']['authorization'] == 'Bearer test-token'
assert transport.last_request['headers']['x-cm-projectid'] == 'test-project'
assert transport.last_request['headers']['norbix-project-id'] == 'test-project'


def test_api_ai_get_end_user_chat_session_request_shape() -> None:
Expand All @@ -59,7 +59,7 @@ def test_api_ai_get_end_user_chat_session_request_shape() -> None:
assert transport.last_request['method'] == 'GET'
assert transport.last_request['url'].endswith('/v3/ai/chat/sessions/stub-SessionId')
assert transport.last_request['headers']['authorization'] == 'Bearer test-token'
assert transport.last_request['headers']['x-cm-projectid'] == 'test-project'
assert transport.last_request['headers']['norbix-project-id'] == 'test-project'


def test_api_ai_rename_end_user_chat_session_request_shape() -> None:
Expand All @@ -69,7 +69,7 @@ def test_api_ai_rename_end_user_chat_session_request_shape() -> None:
assert transport.last_request['method'] == 'PATCH'
assert transport.last_request['url'].endswith('/v3/ai/chat/sessions/stub-SessionId')
assert transport.last_request['headers']['authorization'] == 'Bearer test-token'
assert transport.last_request['headers']['x-cm-projectid'] == 'test-project'
assert transport.last_request['headers']['norbix-project-id'] == 'test-project'


def test_api_ai_delete_end_user_chat_session_request_shape() -> None:
Expand All @@ -79,7 +79,7 @@ def test_api_ai_delete_end_user_chat_session_request_shape() -> None:
assert transport.last_request['method'] == 'DELETE'
assert transport.last_request['url'].endswith('/v3/ai/chat/sessions/stub-SessionId')
assert transport.last_request['headers']['authorization'] == 'Bearer test-token'
assert transport.last_request['headers']['x-cm-projectid'] == 'test-project'
assert transport.last_request['headers']['norbix-project-id'] == 'test-project'


def test_api_ai_pin_end_user_chat_session_request_shape() -> None:
Expand All @@ -89,7 +89,7 @@ def test_api_ai_pin_end_user_chat_session_request_shape() -> None:
assert transport.last_request['method'] == 'PUT'
assert transport.last_request['url'].endswith('/v3/ai/chat/sessions/stub-SessionId/pin')
assert transport.last_request['headers']['authorization'] == 'Bearer test-token'
assert transport.last_request['headers']['x-cm-projectid'] == 'test-project'
assert transport.last_request['headers']['norbix-project-id'] == 'test-project'


def test_api_ai_archive_end_user_chat_session_request_shape() -> None:
Expand All @@ -99,7 +99,7 @@ def test_api_ai_archive_end_user_chat_session_request_shape() -> None:
assert transport.last_request['method'] == 'PUT'
assert transport.last_request['url'].endswith('/v3/ai/chat/sessions/stub-SessionId/archive')
assert transport.last_request['headers']['authorization'] == 'Bearer test-token'
assert transport.last_request['headers']['x-cm-projectid'] == 'test-project'
assert transport.last_request['headers']['norbix-project-id'] == 'test-project'


def test_api_ai_get_end_user_chat_entries_request_shape() -> None:
Expand All @@ -109,7 +109,7 @@ def test_api_ai_get_end_user_chat_entries_request_shape() -> None:
assert transport.last_request['method'] == 'GET'
assert transport.last_request['url'].endswith('/v3/ai/chat/sessions/stub-SessionId/entries')
assert transport.last_request['headers']['authorization'] == 'Bearer test-token'
assert transport.last_request['headers']['x-cm-projectid'] == 'test-project'
assert transport.last_request['headers']['norbix-project-id'] == 'test-project'


def test_api_ai_set_end_user_chat_entry_feedback_request_shape() -> None:
Expand All @@ -119,7 +119,7 @@ def test_api_ai_set_end_user_chat_entry_feedback_request_shape() -> None:
assert transport.last_request['method'] == 'PUT'
assert transport.last_request['url'].endswith('/v3/ai/chat/sessions/stub-SessionId/entries/stub-EntryId/feedback')
assert transport.last_request['headers']['authorization'] == 'Bearer test-token'
assert transport.last_request['headers']['x-cm-projectid'] == 'test-project'
assert transport.last_request['headers']['norbix-project-id'] == 'test-project'


def test_api_ai_list_end_user_chat_attachments_request_shape() -> None:
Expand All @@ -129,7 +129,7 @@ def test_api_ai_list_end_user_chat_attachments_request_shape() -> None:
assert transport.last_request['method'] == 'GET'
assert transport.last_request['url'].endswith('/v3/ai/chat/sessions/stub-SessionId/attachments')
assert transport.last_request['headers']['authorization'] == 'Bearer test-token'
assert transport.last_request['headers']['x-cm-projectid'] == 'test-project'
assert transport.last_request['headers']['norbix-project-id'] == 'test-project'


def test_api_ai_upload_end_user_chat_attachment_request_shape() -> None:
Expand All @@ -139,7 +139,7 @@ def test_api_ai_upload_end_user_chat_attachment_request_shape() -> None:
assert transport.last_request['method'] == 'POST'
assert transport.last_request['url'].endswith('/v3/ai/chat/sessions/stub-SessionId/attachments')
assert transport.last_request['headers']['authorization'] == 'Bearer test-token'
assert transport.last_request['headers']['x-cm-projectid'] == 'test-project'
assert transport.last_request['headers']['norbix-project-id'] == 'test-project'


def test_api_ai_delete_end_user_chat_attachment_request_shape() -> None:
Expand All @@ -149,7 +149,7 @@ def test_api_ai_delete_end_user_chat_attachment_request_shape() -> None:
assert transport.last_request['method'] == 'DELETE'
assert transport.last_request['url'].endswith('/v3/ai/chat/attachments/stub-AttachmentId')
assert transport.last_request['headers']['authorization'] == 'Bearer test-token'
assert transport.last_request['headers']['x-cm-projectid'] == 'test-project'
assert transport.last_request['headers']['norbix-project-id'] == 'test-project'


def test_api_ai_list_end_user_chat_memory_request_shape() -> None:
Expand All @@ -159,7 +159,7 @@ def test_api_ai_list_end_user_chat_memory_request_shape() -> None:
assert transport.last_request['method'] == 'GET'
assert transport.last_request['url'].endswith('/v3/ai/chat/memory')
assert transport.last_request['headers']['authorization'] == 'Bearer test-token'
assert transport.last_request['headers']['x-cm-projectid'] == 'test-project'
assert transport.last_request['headers']['norbix-project-id'] == 'test-project'


def test_api_ai_forget_end_user_chat_memory_request_shape() -> None:
Expand All @@ -169,7 +169,7 @@ def test_api_ai_forget_end_user_chat_memory_request_shape() -> None:
assert transport.last_request['method'] == 'DELETE'
assert transport.last_request['url'].endswith('/v3/ai/chat/memory/stub-NoteId')
assert transport.last_request['headers']['authorization'] == 'Bearer test-token'
assert transport.last_request['headers']['x-cm-projectid'] == 'test-project'
assert transport.last_request['headers']['norbix-project-id'] == 'test-project'


def test_api_ai_start_end_user_chat_turn_request_shape() -> None:
Expand All @@ -179,5 +179,5 @@ def test_api_ai_start_end_user_chat_turn_request_shape() -> None:
assert transport.last_request['method'] == 'POST'
assert transport.last_request['url'].endswith('/v3/ai/chat/turn')
assert transport.last_request['headers']['authorization'] == 'Bearer test-token'
assert transport.last_request['headers']['x-cm-projectid'] == 'test-project'
assert transport.last_request['headers']['norbix-project-id'] == 'test-project'

18 changes: 9 additions & 9 deletions tests/hub/test_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ def test_hub_account_get_account_collaborators_needs_only_a_token() -> None:
client.hub.account.get_account_collaborators()
assert transport.last_request is not None
assert transport.last_request['method'] == 'GET'
assert 'x-cm-accountid' not in transport.last_request['headers']
assert 'norbix-account-id' not in transport.last_request['headers']

def test_hub_account_send_invite_to_team_member_request_shape() -> None:
client, transport = make_client(account_id='acc-1')
Expand Down Expand Up @@ -327,7 +327,7 @@ def test_hub_account_get_project_ai_settings_request_shape() -> None:
assert transport.last_request['method'] == 'GET'
assert transport.last_request['url'].endswith('/v3/account/projects/stub-projectId/ai/settings')
assert transport.last_request['headers']['authorization'] == 'Bearer test-token'
assert transport.last_request['headers']['x-cm-projectid'] == 'test-project'
assert transport.last_request['headers']['norbix-project-id'] == 'test-project'


def test_hub_account_update_project_ai_settings_request_shape() -> None:
Expand All @@ -337,7 +337,7 @@ def test_hub_account_update_project_ai_settings_request_shape() -> None:
assert transport.last_request['method'] == 'PUT'
assert transport.last_request['url'].endswith('/v3/account/projects/stub-projectId/ai/settings')
assert transport.last_request['headers']['authorization'] == 'Bearer test-token'
assert transport.last_request['headers']['x-cm-projectid'] == 'test-project'
assert transport.last_request['headers']['norbix-project-id'] == 'test-project'


def test_hub_account_create_project_ai_assistant_request_shape() -> None:
Expand All @@ -347,7 +347,7 @@ def test_hub_account_create_project_ai_assistant_request_shape() -> None:
assert transport.last_request['method'] == 'POST'
assert transport.last_request['url'].endswith('/v3/account/projects/stub-projectId/ai/assistants')
assert transport.last_request['headers']['authorization'] == 'Bearer test-token'
assert transport.last_request['headers']['x-cm-projectid'] == 'test-project'
assert transport.last_request['headers']['norbix-project-id'] == 'test-project'


def test_hub_account_update_project_ai_assistant_request_shape() -> None:
Expand All @@ -357,7 +357,7 @@ def test_hub_account_update_project_ai_assistant_request_shape() -> None:
assert transport.last_request['method'] == 'PUT'
assert transport.last_request['url'].endswith('/v3/account/projects/stub-projectId/ai/assistants/stub-assistantId')
assert transport.last_request['headers']['authorization'] == 'Bearer test-token'
assert transport.last_request['headers']['x-cm-projectid'] == 'test-project'
assert transport.last_request['headers']['norbix-project-id'] == 'test-project'


def test_hub_account_delete_project_ai_assistant_request_shape() -> None:
Expand All @@ -367,7 +367,7 @@ def test_hub_account_delete_project_ai_assistant_request_shape() -> None:
assert transport.last_request['method'] == 'DELETE'
assert transport.last_request['url'].endswith('/v3/account/projects/stub-projectId/ai/assistants/stub-assistantId')
assert transport.last_request['headers']['authorization'] == 'Bearer test-token'
assert transport.last_request['headers']['x-cm-projectid'] == 'test-project'
assert transport.last_request['headers']['norbix-project-id'] == 'test-project'


def test_hub_account_get_project_ai_usage_request_shape() -> None:
Expand All @@ -377,7 +377,7 @@ def test_hub_account_get_project_ai_usage_request_shape() -> None:
assert transport.last_request['method'] == 'GET'
assert transport.last_request['url'].endswith('/v3/account/projects/stub-projectId/ai/usage')
assert transport.last_request['headers']['authorization'] == 'Bearer test-token'
assert transport.last_request['headers']['x-cm-projectid'] == 'test-project'
assert transport.last_request['headers']['norbix-project-id'] == 'test-project'


def test_hub_account_set_admin_portal_enabled_request_shape() -> None:
Expand All @@ -387,7 +387,7 @@ def test_hub_account_set_admin_portal_enabled_request_shape() -> None:
assert transport.last_request['method'] == 'PUT'
assert transport.last_request['url'].endswith('/v3/account/projects/stub-projectId/admin-portal/enabled')
assert transport.last_request['headers']['authorization'] == 'Bearer test-token'
assert transport.last_request['headers']['x-cm-projectid'] == 'test-project'
assert transport.last_request['headers']['norbix-project-id'] == 'test-project'


def test_hub_account_check_project_languages_request_shape() -> None:
Expand All @@ -402,7 +402,7 @@ def test_hub_account_check_project_languages_request_shape() -> None:
assert transport.last_request['method'] == 'POST'
assert urlparse(transport.last_request['url']).path == '/v3/account/projects/proj-1/settings/languages/check'
assert json.loads(transport.last_request['body']) == {"defaultLanguage": "en", "languages": ["en", "de"]}
assert transport.last_request['headers']['x-cm-accountid'] == 'acc-1'
assert transport.last_request['headers']['norbix-account-id'] == 'acc-1'

def test_async_hub_account_check_project_languages_hits_the_route() -> None:
import asyncio
Expand Down
4 changes: 2 additions & 2 deletions tests/hub/test_account_me.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def test_get_my_account_user_profile_reads_the_profile_and_phone() -> None:
profile = _sync_client(seen, PROFILE).hub.account.get_my_account_user_profile()

assert [(r.method, urlparse(str(r.url)).path) for r in seen] == [("GET", "/v3/account/me")]
assert "x-cm-accountid" not in seen[0].headers
assert "norbix-account-id" not in seen[0].headers
assert profile["item"]["email"] == "ada@example.test"
assert profile["item"]["generalInfo"]["phone"] == "+37060000000"

Expand All @@ -72,7 +72,7 @@ def test_get_account_collaborators_sends_flat_paging_and_the_project_filter() ->
url = urlparse(str(seen[0].url))
assert (seen[0].method, url.path) == ("GET", "/v3/account/collaborators")
assert parse_qs(url.query) == {"projectId": ["proj_2"], "pageSize": ["50"], "startingAfter": ["member_20"]}
assert "x-cm-accountid" not in seen[0].headers
assert "norbix-account-id" not in seen[0].headers


def test_async_account_me_and_team_list_need_only_a_token() -> None:
Expand Down
2 changes: 1 addition & 1 deletion tests/hub/test_account_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def _check(request: httpx.Request, verb: str, path: str, body: dict[str, Any] |
assert request.method == verb
assert request.url.path == "/v3" + path
assert request.headers["authorization"] == "Bearer test-token"
assert request.headers["x-cm-projectid"] == "test-project"
assert request.headers["norbix-project-id"] == "test-project"
if body is None:
assert request.content == b""
else:
Expand Down
6 changes: 3 additions & 3 deletions tests/hub/test_account_token_only.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

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.
needs no account at all — and it never reads the ``norbix-account-id`` 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
Expand Down Expand Up @@ -83,7 +83,7 @@ def handler(request: httpx.Request) -> httpx.Response:
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
assert "norbix-account-id" not in seen[0].headers


@pytest.mark.parametrize(("module", "method", "verb", "path", "ids"), CASES, ids=IDS)
Expand Down Expand Up @@ -139,7 +139,7 @@ async def run() -> None:
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
assert "norbix-account-id" not in seen[0].headers
if verb == "GET":
assert dict(parse_qsl(urlparse(str(seen[0].url)).query)) == fields
else:
Expand Down
Loading
Loading