fix: always send tenant headers in OpenViking _headers() when account/user are set

OpenViking 0.3.x requires X-OpenViking-Account and X-OpenViking-User headers for ROOT API key requests to tenant-scoped APIs. Previously the `!="default"` guard skipped these headers when account/user were the literal string "default", causing INVALID_ARGUMENT errors.

Remove the `!="default"` guard so headers are sent whenever account/user are truthy. Empty strings are still correctly skipped since `""` is falsy.

Update tests to reflect the new behavior:
- test_viking_client_headers_send_tenant_when_default: asserts "default" headers ARE present
- test_viking_client_headers_send_tenant_when_empty_falls_back_to_default: asserts "default" headers ARE present from constructor fallback

Based on #21775 by @happy5318
This commit is contained in:
kshitij 2026-05-09 01:53:19 -07:00 committed by GitHub
parent c7e8add120
commit 8fb3e2d63a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 20 additions and 16 deletions

View file

@ -100,18 +100,19 @@ class _VikingClient:
raise ImportError("httpx is required for OpenViking: pip install httpx") raise ImportError("httpx is required for OpenViking: pip install httpx")
def _headers(self) -> dict: def _headers(self) -> dict:
# Only send tenant headers when the user actually configured them. # Always send tenant headers when account/user are configured.
# Legacy installs had account/user defaulted to the literal string # OpenViking 0.3.x requires X-OpenViking-Account and X-OpenViking-User
# "default" — treat that as unset so authenticated remote servers # for ROOT API key requests to tenant-scoped APIs — omitting them
# that derive tenancy from the Bearer key aren't overridden by a # causes INVALID_ARGUMENT errors even when account="default".
# bogus tenant value. # User-level keys can omit them (server derives tenancy from the key),
# but ROOT keys must always include them explicitly.
h = { h = {
"Content-Type": "application/json", "Content-Type": "application/json",
"X-OpenViking-Agent": self._agent, "X-OpenViking-Agent": self._agent,
} }
if self._account and self._account != "default": if self._account:
h["X-OpenViking-Account"] = self._account h["X-OpenViking-Account"] = self._account
if self._user and self._user != "default": if self._user:
h["X-OpenViking-User"] = self._user h["X-OpenViking-User"] = self._user
if self._api_key: if self._api_key:
h["X-API-Key"] = self._api_key h["X-API-Key"] = self._api_key

View file

@ -314,10 +314,11 @@ def test_viking_client_headers_include_bearer_when_api_key_set():
assert headers["Authorization"] == "Bearer test-key" assert headers["Authorization"] == "Bearer test-key"
def test_viking_client_headers_omit_tenant_when_legacy_default(): def test_viking_client_headers_send_tenant_when_default():
# Existing installs have account/user set to the literal string "default". # account/user set to the literal string "default". OpenViking 0.3.x
# Those should NOT be sent as headers — the server would interpret that # requires X-OpenViking-Account and X-OpenViking-User for ROOT API key
# as a real tenant override and reject/misroute requests. # requests to tenant-scoped APIs — omitting them causes
# INVALID_ARGUMENT errors even when account="default".
client = _VikingClient( client = _VikingClient(
"https://example.com", "https://example.com",
api_key="test-key", api_key="test-key",
@ -326,13 +327,15 @@ def test_viking_client_headers_omit_tenant_when_legacy_default():
agent="hermes", agent="hermes",
) )
headers = client._headers() headers = client._headers()
assert "X-OpenViking-Account" not in headers assert headers["X-OpenViking-Account"] == "default"
assert "X-OpenViking-User" not in headers assert headers["X-OpenViking-User"] == "default"
assert headers["X-OpenViking-Agent"] == "hermes" assert headers["X-OpenViking-Agent"] == "hermes"
assert headers["Authorization"] == "Bearer test-key" assert headers["Authorization"] == "Bearer test-key"
def test_viking_client_headers_omit_tenant_when_empty(): def test_viking_client_headers_send_tenant_when_empty_falls_back_to_default():
# Empty account/user strings fall back to "default" via the constructor.
# Headers are sent even for the default value — ROOT API keys need them.
client = _VikingClient( client = _VikingClient(
"https://example.com", "https://example.com",
api_key="", api_key="",
@ -341,8 +344,8 @@ def test_viking_client_headers_omit_tenant_when_empty():
agent="hermes", agent="hermes",
) )
headers = client._headers() headers = client._headers()
assert "X-OpenViking-Account" not in headers assert headers["X-OpenViking-Account"] == "default"
assert "X-OpenViking-User" not in headers assert headers["X-OpenViking-User"] == "default"
assert "Authorization" not in headers assert "Authorization" not in headers
assert "X-API-Key" not in headers assert "X-API-Key" not in headers