|
| 1 | +import { test, expect, describe, mock } from "bun:test" |
| 2 | + |
| 3 | +// Mock BunProc + default auth plugins so importing Provider never shells out or |
| 4 | +// hits the network (mirrors test/provider/provider.test.ts). |
| 5 | +mock.module("../../src/bun/index", () => ({ |
| 6 | + BunProc: { |
| 7 | + install: async (pkg: string, _version?: string) => { |
| 8 | + const at = pkg.lastIndexOf("@") |
| 9 | + return at > 0 ? pkg.substring(0, at) : pkg |
| 10 | + }, |
| 11 | + run: async () => { |
| 12 | + throw new Error("BunProc.run should not be called in tests") |
| 13 | + }, |
| 14 | + which: () => process.execPath, |
| 15 | + InstallFailedError: class extends Error {}, |
| 16 | + }, |
| 17 | +})) |
| 18 | +const mockPlugin = () => ({}) |
| 19 | +mock.module("openscience-copilot-auth", () => ({ default: mockPlugin })) |
| 20 | +mock.module("openscience-anthropic-auth", () => ({ default: mockPlugin })) |
| 21 | +mock.module("@gitlab/openscience-gitlab-auth", () => ({ default: mockPlugin })) |
| 22 | + |
| 23 | +import { tmpdir } from "../fixture/fixture" |
| 24 | +import { Instance } from "../../src/project/instance" |
| 25 | +import { Provider } from "../../src/provider/provider" |
| 26 | +import { Env } from "../../src/env" |
| 27 | + |
| 28 | +function clearManagedLLMEnv() { |
| 29 | + for (const key of [ |
| 30 | + "ANTHROPIC_API_KEY", |
| 31 | + "ANTHROPIC_BASE_URL", |
| 32 | + "OPENAI_API_KEY", |
| 33 | + "OPENAI_BASE_URL", |
| 34 | + "GOOGLE_GENERATIVE_AI_API_KEY", |
| 35 | + "GOOGLE_GENERATIVE_AI_BASE_URL", |
| 36 | + "GEMINI_API_KEY", |
| 37 | + "GEMINI_BASE_URL", |
| 38 | + "OPENROUTER_API_KEY", |
| 39 | + "OPENROUTER_BASE_URL", |
| 40 | + ]) { |
| 41 | + Env.remove(key) |
| 42 | + } |
| 43 | +} |
| 44 | + |
| 45 | +const PROXY = "https://atlas.test/api/llm/proxy" |
| 46 | + |
| 47 | +// ── Pure decision helpers ──────────────────────────────────────────────────── |
| 48 | + |
| 49 | +describe("Provider.managedRoutesOpenRouterOnly (pure)", () => { |
| 50 | + test("true only when billing.llm === 'managed'", () => { |
| 51 | + expect(Provider.managedRoutesOpenRouterOnly({ billing: { llm: "managed" } } as any)).toBe(true) |
| 52 | + }) |
| 53 | + test("false for explicit byok", () => { |
| 54 | + expect(Provider.managedRoutesOpenRouterOnly({ billing: { llm: "byok" } } as any)).toBe(false) |
| 55 | + }) |
| 56 | + test("false for auto-detect (unset / empty billing / null)", () => { |
| 57 | + expect(Provider.managedRoutesOpenRouterOnly({} as any)).toBe(false) |
| 58 | + expect(Provider.managedRoutesOpenRouterOnly({ billing: {} } as any)).toBe(false) |
| 59 | + expect(Provider.managedRoutesOpenRouterOnly({ billing: { llm: null } } as any)).toBe(false) |
| 60 | + }) |
| 61 | +}) |
| 62 | + |
| 63 | +describe("Provider.managedProviderAllowed (pure)", () => { |
| 64 | + test("OpenRouter and the hosted synsci demo are the only allowed providers", () => { |
| 65 | + expect(Provider.managedProviderAllowed("openrouter")).toBe(true) |
| 66 | + expect(Provider.managedProviderAllowed("synsci")).toBe(true) |
| 67 | + expect(Provider.managedProviderAllowed("synsci-hosted")).toBe(true) |
| 68 | + }) |
| 69 | + test("first-party managed proxies + everything else are rejected", () => { |
| 70 | + for (const id of ["anthropic", "openai", "google", "openai-codex", "github-copilot", "gateway", "azure"]) { |
| 71 | + expect(Provider.managedProviderAllowed(id)).toBe(false) |
| 72 | + } |
| 73 | + }) |
| 74 | +}) |
| 75 | + |
| 76 | +// ── Availability filter (hermetic, catalog-backed) ─────────────────────────── |
| 77 | + |
| 78 | +describe("managed session availability", () => { |
| 79 | + test("managed ⇒ only OpenRouter (+ demo) load; first-party proxies are dropped", async () => { |
| 80 | + await using tmp = await tmpdir({ config: { billing: { llm: "managed" } } }) |
| 81 | + await Instance.provide({ |
| 82 | + directory: tmp.path, |
| 83 | + init: async () => { |
| 84 | + clearManagedLLMEnv() |
| 85 | + Env.set("ANTHROPIC_API_KEY", "thk_anthropic") |
| 86 | + Env.set("ANTHROPIC_BASE_URL", `${PROXY}/anthropic/v1`) |
| 87 | + Env.set("OPENAI_API_KEY", "thk_openai") |
| 88 | + Env.set("OPENAI_BASE_URL", `${PROXY}/openai/v1`) |
| 89 | + Env.set("GOOGLE_GENERATIVE_AI_API_KEY", "thk_google") |
| 90 | + Env.set("GOOGLE_GENERATIVE_AI_BASE_URL", `${PROXY}/gemini/v1beta`) |
| 91 | + Env.set("OPENROUTER_API_KEY", "thk_openrouter") |
| 92 | + Env.set("OPENROUTER_BASE_URL", `${PROXY}/openrouter/v1`) |
| 93 | + Provider.invalidate() |
| 94 | + }, |
| 95 | + fn: async () => { |
| 96 | + const providers = await Provider.list() |
| 97 | + expect(providers["openrouter"]).toBeDefined() |
| 98 | + expect(providers["openrouter"].options.baseURL).toBe(`${PROXY}/openrouter/v1`) |
| 99 | + expect(providers["anthropic"]).toBeUndefined() |
| 100 | + expect(providers["openai"]).toBeUndefined() |
| 101 | + expect(providers["google"]).toBeUndefined() |
| 102 | + }, |
| 103 | + }) |
| 104 | + }) |
| 105 | + |
| 106 | + test("BYOK (managed off): anthropic keeps its public endpoint and no wallet token", async () => { |
| 107 | + await using tmp = await tmpdir({ config: { billing: { llm: "byok" } } }) |
| 108 | + await Instance.provide({ |
| 109 | + directory: tmp.path, |
| 110 | + init: async () => { |
| 111 | + clearManagedLLMEnv() |
| 112 | + Env.set("ANTHROPIC_API_KEY", "sk-ant-byok-key") |
| 113 | + Provider.invalidate() |
| 114 | + }, |
| 115 | + fn: async () => { |
| 116 | + const providers = await Provider.list() |
| 117 | + const anthropic = providers["anthropic"] |
| 118 | + expect(anthropic).toBeDefined() |
| 119 | + // No Atlas proxy baseURL injected → getSDK falls back to the public |
| 120 | + // model.api.url; the BYOK key is the only credential. |
| 121 | + expect(anthropic.options.baseURL).toBeUndefined() |
| 122 | + expect(anthropic.options.apiKey).toBeUndefined() |
| 123 | + expect(anthropic.key).toBe("sk-ant-byok-key") |
| 124 | + }, |
| 125 | + }) |
| 126 | + }) |
| 127 | + |
| 128 | + test("legacy auto-detect (thk_ present, billing.llm unset) is unchanged — proxies still load", async () => { |
| 129 | + await using tmp = await tmpdir({ config: {} }) |
| 130 | + await Instance.provide({ |
| 131 | + directory: tmp.path, |
| 132 | + init: async () => { |
| 133 | + clearManagedLLMEnv() |
| 134 | + Env.set("ANTHROPIC_API_KEY", "thk_anthropic") |
| 135 | + Env.set("ANTHROPIC_BASE_URL", `${PROXY}/anthropic/v1`) |
| 136 | + Env.set("OPENROUTER_API_KEY", "thk_openrouter") |
| 137 | + Env.set("OPENROUTER_BASE_URL", `${PROXY}/openrouter/v1`) |
| 138 | + Provider.invalidate() |
| 139 | + }, |
| 140 | + fn: async () => { |
| 141 | + const providers = await Provider.list() |
| 142 | + // Gating is scoped to the explicit toggle: without it, nothing is dropped. |
| 143 | + expect(providers["anthropic"]).toBeDefined() |
| 144 | + expect(providers["anthropic"].options.baseURL).toBe(`${PROXY}/anthropic/v1`) |
| 145 | + expect(providers["openrouter"]).toBeDefined() |
| 146 | + }, |
| 147 | + }) |
| 148 | + }) |
| 149 | +}) |
0 commit comments