diff --git a/plugins/memory/openviking/__init__.py b/plugins/memory/openviking/__init__.py index 4c2a4bf15f..c9cbfcad4b 100644 --- a/plugins/memory/openviking/__init__.py +++ b/plugins/memory/openviking/__init__.py @@ -100,14 +100,22 @@ class _VikingClient: raise ImportError("httpx is required for OpenViking: pip install httpx") def _headers(self) -> dict: + # Only send tenant headers when the user actually configured them. + # Legacy installs had account/user defaulted to the literal string + # "default" — treat that as unset so authenticated remote servers + # that derive tenancy from the Bearer key aren't overridden by a + # bogus tenant value. h = { "Content-Type": "application/json", - "X-OpenViking-Account": self._account, - "X-OpenViking-User": self._user, "X-OpenViking-Agent": self._agent, } + if self._account and self._account != "default": + h["X-OpenViking-Account"] = self._account + if self._user and self._user != "default": + h["X-OpenViking-User"] = self._user if self._api_key: h["X-API-Key"] = self._api_key + h["Authorization"] = "Bearer " + self._api_key return h def _url(self, path: str) -> str: @@ -179,7 +187,7 @@ class _VikingClient: def health(self) -> bool: try: resp = self._httpx.get( - self._url("/health"), timeout=3.0 + self._url("/health"), headers=self._headers(), timeout=3.0 ) return resp.status_code == 200 except Exception: diff --git a/tests/plugins/memory/test_openviking_provider.py b/tests/plugins/memory/test_openviking_provider.py index 56691ec7e2..76d69224e3 100644 --- a/tests/plugins/memory/test_openviking_provider.py +++ b/tests/plugins/memory/test_openviking_provider.py @@ -299,3 +299,83 @@ def test_viking_client_raises_structured_server_error(): with pytest.raises(RuntimeError, match="PERMISSION_DENIED"): client._parse_response(response) + + +def test_viking_client_headers_include_bearer_when_api_key_set(): + client = _VikingClient( + "https://example.com", + api_key="test-key", + account="acct", + user="usr", + agent="hermes", + ) + headers = client._headers() + assert headers["X-API-Key"] == "test-key" + assert headers["Authorization"] == "Bearer test-key" + + +def test_viking_client_headers_omit_tenant_when_legacy_default(): + # Existing installs have account/user set to the literal string "default". + # Those should NOT be sent as headers — the server would interpret that + # as a real tenant override and reject/misroute requests. + client = _VikingClient( + "https://example.com", + api_key="test-key", + account="default", + user="default", + agent="hermes", + ) + headers = client._headers() + assert "X-OpenViking-Account" not in headers + assert "X-OpenViking-User" not in headers + assert headers["X-OpenViking-Agent"] == "hermes" + assert headers["Authorization"] == "Bearer test-key" + + +def test_viking_client_headers_omit_tenant_when_empty(): + client = _VikingClient( + "https://example.com", + api_key="", + account="", + user="", + agent="hermes", + ) + headers = client._headers() + assert "X-OpenViking-Account" not in headers + assert "X-OpenViking-User" not in headers + assert "Authorization" not in headers + assert "X-API-Key" not in headers + + +def test_viking_client_headers_sent_with_real_tenant_values(): + client = _VikingClient( + "https://example.com", + api_key="test-key", + account="real-account", + user="real-user", + agent="hermes", + ) + headers = client._headers() + assert headers["X-OpenViking-Account"] == "real-account" + assert headers["X-OpenViking-User"] == "real-user" + + +def test_viking_client_health_sends_auth_headers(monkeypatch): + client = _VikingClient( + "https://example.com", + api_key="test-key", + account="", + user="", + agent="hermes", + ) + captured = {} + + def capture_get(url, **kwargs): + captured["url"] = url + captured["headers"] = kwargs.get("headers") or {} + return SimpleNamespace(status_code=200) + + monkeypatch.setattr(client._httpx, "get", capture_get) + assert client.health() is True + assert captured["url"] == "https://example.com/health" + assert captured["headers"]["Authorization"] == "Bearer test-key"