From cf5385413afd253442a5681e903b54e56a2369cd Mon Sep 17 00:00:00 2001 From: Jerel John Velarde Date: Tue, 10 Nov 2026 00:26:29 -0800 Subject: [PATCH 1/3] Make main's tests pass again on today's date and current SDKs The first-day test posted a fixed date of 2026-10-26. The route drops a date more than a day from the server's own clock, so the test has failed on every run since Oct 28. It now posts today's UTC date. The LangGraph provider tests pinned details the SDKs have since changed: anthropic 2.x authenticates with a Bearer header instead of x-api-key, and google-genai 3.x calls the v1 API instead of v1beta. The tests now accept either, and compare anthropic-version with the SDK's own default. --- .../tests/test_provider_boundaries.py | 29 +++++++++++++++---- openbot/server/tests/channel-routes.test.ts | 9 +++--- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/openbot/agent-langgraph-agui/tests/test_provider_boundaries.py b/openbot/agent-langgraph-agui/tests/test_provider_boundaries.py index a0a824f13..f0c27b044 100644 --- a/openbot/agent-langgraph-agui/tests/test_provider_boundaries.py +++ b/openbot/agent-langgraph-agui/tests/test_provider_boundaries.py @@ -1,5 +1,6 @@ import json import os +import re import shutil import subprocess import sys @@ -12,6 +13,7 @@ import httpx2 import pytest +from anthropic import Anthropic sys.path.insert(0, str(Path(__file__).resolve().parents[1])) @@ -337,8 +339,14 @@ async def test_anthropic_selection_reaches_anthropic_boundary_without_openai_key assert result["messages"][0].content == "anthropic proof" assert "OPENAI_API_KEY" not in os.environ assert captured[0]["url"] == "http://127.0.0.1:4311/v1/messages" - assert captured[0]["headers"]["x-api-key"] == "sk-ant-openbot-ci" - assert captured[0]["headers"]["anthropic-version"] == "2023-06-01" + # Anthropic 2.3 uses Bearer authentication; older SDKs send x-api-key. + headers = captured[0]["headers"] + assert ( + headers.get("x-api-key") == "sk-ant-openbot-ci" + or headers.get("authorization") == "Bearer sk-ant-openbot-ci" + ) + with Anthropic(api_key="sk-ant-openbot-ci") as sdk: + assert headers["anthropic-version"] == sdk.default_headers["anthropic-version"] assert captured[0]["body"]["model"] == request_model assert captured[0]["body"]["messages"] == [ {"role": "user", "content": "Say hello."} @@ -469,6 +477,11 @@ async def test_openai_keeps_a_skill_turn_beside_the_message_it_was_picked_for( ] + +def _without_api_version(path): + # google-genai 3.x calls the v1 API; older SDKs call v1beta. + return re.sub(r"^/v1(beta)?/", "/", path) + @pytest.mark.asyncio @pytest.mark.parametrize( ("provider", "model"), @@ -497,9 +510,12 @@ async def test_google_provider_reaches_google_genai_boundary( ) assert result["messages"][0].content == "google loopback proof" - assert captured == [ + assert [ + {**request, "path": _without_api_version(request["path"])} + for request in captured + ] == [ { - "path": "/v1beta/models/gemini-2.5-flash:generateContent", + "path": "/models/gemini-2.5-flash:generateContent", "x_goog_api_key_present": True, "body": { "contents": [ @@ -545,7 +561,10 @@ async def test_google_spec_model_reaches_google_request( assert result["messages"][0].content == "google loopback proof" assert len(captured) == 1 - assert captured[0]["path"] == "/v1beta/models/gemini-spec-model:generateContent" + assert ( + _without_api_version(captured[0]["path"]) + == "/models/gemini-spec-model:generateContent" + ) def _write_synthetic_chatgpt_store(path: Path): diff --git a/openbot/server/tests/channel-routes.test.ts b/openbot/server/tests/channel-routes.test.ts index 20d30b4be..e3c12515f 100644 --- a/openbot/server/tests/channel-routes.test.ts +++ b/openbot/server/tests/channel-routes.test.ts @@ -722,12 +722,11 @@ describe("the first day's date", () => { return { channel: channel(), claimed: true }; }, }); - const response = await post(store, { - agentId: "agent-1", - date: "2026-10-26", - }); + // The server drops a date more than a day from its own clock, so the date follows the clock. + const date = new Date().toISOString().slice(0, 10); + const response = await post(store, { agentId: "agent-1", date }); expect(response.status).toBe(200); - expect(calls).toEqual([[actor, "agent-1", undefined, "2026-10-26"]]); + expect(calls).toEqual([[actor, "agent-1", undefined, date]]); }); /* From 02aa5ef061420c937855033c30e71780a2449951 Mon Sep 17 00:00:00 2001 From: Jerel John Velarde Date: Tue, 10 Nov 2026 00:39:48 -0800 Subject: [PATCH 2/3] Settle React's pending work before the banner test takes the document away Its last test leaves a React callback posted. afterAll unregistered happy-dom right away, so the callback ran against a missing window and bun reported an unhandled error between tests: no failing test, but a red tests job on every run since Oct 28. It now awaits settleReactWork first, as the other component tests do. --- openbot/app/tests/self-host-banner.test.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openbot/app/tests/self-host-banner.test.tsx b/openbot/app/tests/self-host-banner.test.tsx index dff127726..b32e32d80 100644 --- a/openbot/app/tests/self-host-banner.test.tsx +++ b/openbot/app/tests/self-host-banner.test.tsx @@ -17,6 +17,7 @@ import { authKeys } from "@/lib/auth/queries"; import { deploymentKeys } from "@/lib/deployment/queries"; import { userPreferencesQueryOptions } from "@/lib/settings/message-list"; import type { UserPreferences } from "@openbot/shared/user-preferences"; +import { settleReactWork } from "./settle-react-work"; const originalFetch = globalThis.fetch; const clients: QueryClient[] = []; @@ -50,7 +51,10 @@ afterEach(() => { clients.length = 0; globalThis.fetch = originalFetch; }); -afterAll(() => GlobalRegistrator.unregister()); +afterAll(async () => { + await settleReactWork(); + GlobalRegistrator.unregister(); +}); const banner = { name: "Self-host OpenDuet" } as const; From c1e5183455ef7ed32955eefdb8128c4e5e806eaa Mon Sep 17 00:00:00 2001 From: Jerel John Velarde Date: Tue, 10 Nov 2026 00:42:02 -0800 Subject: [PATCH 3/3] Read the Anthropic key from either header in the AG2 and Microsoft tests anthropic 2.x sends the key as a Bearer token, so AG2's blank-URL test saw no x-api-key and failed. The Microsoft agent still resolves anthropic 1.x, but its test reads the same header and would fail on its next bump. --- openbot/agent-ag2/tests/test_main.py | 10 +++++++++- openbot/agent-microsoft/tests/test_main.py | 10 +++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/openbot/agent-ag2/tests/test_main.py b/openbot/agent-ag2/tests/test_main.py index 93cf26a8c..992cbc6fb 100644 --- a/openbot/agent-ag2/tests/test_main.py +++ b/openbot/agent-ag2/tests/test_main.py @@ -185,12 +185,20 @@ def test_a_run_reaches_the_model_the_setup_screen_chose(monkeypatch, provider, c assert seen == [expected] +def _anthropic_key(headers): + # anthropic 2.x sends the key as a Bearer token; older SDKs send x-api-key. + bearer = headers.get("authorization", "") + if bearer.startswith("Bearer "): + return bearer.removeprefix("Bearer ") + return headers.get("x-api-key") + + def test_an_anthropic_key_uses_the_official_endpoint_when_compose_sets_a_blank_url(monkeypatch): seen = [] async def respond(transport, request): body = json.loads(request.content) - seen.append((str(request.url), request.headers.get("x-api-key"), body["model"])) + seen.append((str(request.url), _anthropic_key(request.headers), body["model"])) return httpx2.Response( 200, request=request, diff --git a/openbot/agent-microsoft/tests/test_main.py b/openbot/agent-microsoft/tests/test_main.py index 9063d0119..0bb769b0d 100644 --- a/openbot/agent-microsoft/tests/test_main.py +++ b/openbot/agent-microsoft/tests/test_main.py @@ -182,6 +182,14 @@ def test_a_run_reaches_the_model_the_setup_screen_chose(monkeypatch, provider, c assert seen == [expected] +def _anthropic_key(headers): + # anthropic 2.x sends the key as a Bearer token; older SDKs send x-api-key. + bearer = headers.get("authorization", "") + if bearer.startswith("Bearer "): + return bearer.removeprefix("Bearer ") + return headers.get("x-api-key") + + def test_an_anthropic_key_uses_the_official_endpoint_when_compose_sets_a_blank_url(monkeypatch): seen = [] provider_seen = [] @@ -189,7 +197,7 @@ def test_an_anthropic_key_uses_the_official_endpoint_when_compose_sets_a_blank_u async def respond(transport, request): seen.append( - (request.url.scheme, request.url.host, request.url.path, request.headers.get("x-api-key")) + (request.url.scheme, request.url.host, request.url.path, _anthropic_key(request.headers)) ) async with httpx2.ASGITransport(app=provider_app) as local_provider: return await local_provider.handle_async_request(request)