-
Notifications
You must be signed in to change notification settings - Fork 134
test: hermetic e2e suite for Altimate Base (registration, catalog, inference, rate-limit/budget, error surfacing) #1248
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: codex/altimate-base-release-final
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| // Shared hermetic-test bootstrap for the Altimate Base e2e suites. | ||
| // | ||
| // Extracted from `test/altimate/altimate-base.test.ts`'s top-of-file isolated-environment | ||
| // pattern so every new suite (and that file) imports one implementation instead of | ||
| // re-copy-pasting it. See docs/internal/2026-09-04-altimate-base-e2e-harness-plan.md, | ||
| // Deliverable 2, for the full design rationale. | ||
| import { randomBytes } from "node:crypto" | ||
| import fs from "node:fs" | ||
| import os from "node:os" | ||
| import path from "node:path" | ||
| import { afterAll } from "bun:test" | ||
| import { FreeTierCapability } from "../../../src/altimate/free/capability" | ||
|
|
||
| const ISOLATED_ENV = [ | ||
| "XDG_DATA_HOME", | ||
| "XDG_CONFIG_HOME", | ||
| "XDG_CACHE_HOME", | ||
| "XDG_STATE_HOME", | ||
| "OPENCODE_TEST_HOME", | ||
| ] as const | ||
|
|
||
| /** | ||
| * Call once at module scope in each suite file, BEFORE importing `../../src/altimate/free/*` | ||
| * (the client reads Global.Path lazily per-call, but isolating env before any import keeps every | ||
| * suite file identical to how the existing altimate-base.test.ts already does it). | ||
| * | ||
| * Gives the file its own temp XDG/home tree so its credential store, config, and cache never | ||
| * touch a real user directory or another suite file's directory. Registers an `afterAll` that | ||
| * restores the previous env values and removes the temp tree. | ||
| */ | ||
| export function isolateAltimateBaseHome(prefix: string): string { | ||
| const original = Object.fromEntries(ISOLATED_ENV.map((key) => [key, process.env[key]])) | ||
| const home = fs.mkdtempSync(path.join(os.tmpdir(), `${prefix}-`)) | ||
| process.env.XDG_DATA_HOME = path.join(home, "data") | ||
| process.env.XDG_CONFIG_HOME = path.join(home, "config") | ||
| process.env.XDG_CACHE_HOME = path.join(home, "cache") | ||
| process.env.XDG_STATE_HOME = path.join(home, "state") | ||
| process.env.OPENCODE_TEST_HOME = home | ||
|
|
||
| afterAll(() => { | ||
| for (const key of ISOLATED_ENV) { | ||
| const value = original[key] | ||
| if (value === undefined) delete process.env[key] | ||
| else process.env[key] = value | ||
| } | ||
| fs.rmSync(home, { recursive: true, force: true }) | ||
| }) | ||
| return home | ||
| } | ||
|
|
||
| /** | ||
| * Call in `beforeEach`: clears both current and legacy gateway env vars, then points the client | ||
| * at the fake gateway's URL. Mirrors `altimate-base.test.ts`'s existing `beforeEach` gateway-env | ||
| * reset so every suite starts from the same known configuration state. | ||
| */ | ||
| export function resetGatewayEnv(gatewayUrl: string): void { | ||
| delete process.env.ALTIMATE_BASE_GATEWAY_URL | ||
| delete process.env.ALTIMATE_FREE_GATEWAY_URL | ||
| process.env.ALTIMATE_BASE_GATEWAY_URL = gatewayUrl | ||
| } | ||
|
|
||
| // `FreeTierCapability.issueArmer()` hands out the process's ONE consent-arming capability and | ||
| // throws on a second call — see `src/altimate/free/capability.ts`. In production that single call | ||
| // happens once, at TUI worker boot (`cli/tui/worker.ts`). Every Altimate Base e2e suite plays the | ||
| // role of that TUI host and needs the same capability, but `bun test test/altimate/` loads multiple | ||
| // suite files into ONE worker process, so if each file called `issueArmer()` itself at module | ||
| // scope, the second (and every subsequent) file to load would crash with "Altimate Base consent | ||
| // armer already issued for this process" — reproducible even with just the two pre-existing files | ||
| // (`altimate-base.test.ts` and `altimate-base-harness-smoke.test.ts`). | ||
| // | ||
| // This module-level singleton is the fix: it claims `issueArmer()` lazily, the first time any | ||
| // suite asks for a token, and caches the returned armer closure here. Bun caches modules per | ||
| // process, so every suite file that imports `consented()` from this file — regardless of how many | ||
| // separate test files load it — shares this exact module instance and therefore this exact cache. | ||
| // `issueArmer()` is still claimed exactly once per process; this adds no way to reset, re-claim, or | ||
| // otherwise bypass that one-shot guarantee. It is purely a shared cache in front of the single | ||
| // legitimate call, so the underlying security property (only one in-process caller can ever obtain | ||
| // the ability to arm the production consent authority) is unchanged. | ||
| let cachedArmer: ((token: string) => void) | undefined | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: When the unforgeable-consent test runs alone with Prompt for AI agents |
||
|
|
||
| function armer(): (token: string) => void { | ||
| if (!cachedArmer) cachedArmer = FreeTierCapability.issueArmer() | ||
| return cachedArmer | ||
|
Comment on lines
+81
to
+83
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Because the shared armer is now claimed lazily, running only the existing Useful? React with 👍 / 👎. |
||
| } | ||
|
|
||
| /** | ||
| * Mints a fresh one-shot consent token and arms it against the production consent authority, | ||
| * via the shared, process-wide armer above. Every suite should call this instead of claiming | ||
| * `FreeTierCapability.issueArmer()` itself. | ||
| */ | ||
| export function consented(): string { | ||
| const token = randomBytes(32).toString("hex") | ||
| armer()(token) | ||
| return token | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,174 @@ | ||||||||||||||||||
| // Fetch-shaped fake for the two real Altimate Base gateway routes (`POST /register`, | ||||||||||||||||||
| // `POST /v1/chat/completions`). Installed via `spyOn(globalThis, "fetch")` — the same seam | ||||||||||||||||||
| // `test/altimate/altimate-base.test.ts` already uses — so every suite stays hermetic: no port | ||||||||||||||||||
| // binding, no real network, no external dependency. See | ||||||||||||||||||
| // docs/internal/2026-09-04-altimate-base-e2e-harness-plan.md, Deliverable 2, for why an in-process | ||||||||||||||||||
| // fetch fake was chosen over a real local HTTP server. | ||||||||||||||||||
| import { spyOn } from "bun:test" | ||||||||||||||||||
|
|
||||||||||||||||||
| export const GATEWAY_URL = "https://gateway.test" | ||||||||||||||||||
| export const MODEL_ID = "altimate-base" | ||||||||||||||||||
|
|
||||||||||||||||||
| export interface RegisterCall { | ||||||||||||||||||
| url: string | ||||||||||||||||||
| installSecretHash: string | ||||||||||||||||||
| cliVersion: string | ||||||||||||||||||
| } | ||||||||||||||||||
| export interface ChatCall { | ||||||||||||||||||
| url: string | ||||||||||||||||||
| authorization: string | null | ||||||||||||||||||
| body: unknown | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| export type RegisterMode = | ||||||||||||||||||
| | { kind: "ok"; apiKey?: string; expiresAt?: string | null; baseUrl?: string; model?: string } | ||||||||||||||||||
| | { kind: "http"; status: number; headers?: Record<string, string> } | ||||||||||||||||||
| | { kind: "network" } | ||||||||||||||||||
| | { kind: "malformed-json" } | ||||||||||||||||||
|
|
||||||||||||||||||
| export type ChatMode = | ||||||||||||||||||
| | { kind: "ok"; content?: string; status?: number } | ||||||||||||||||||
| | { kind: "throttle-tokens" } // 429 throttling_error, "Limit type: tokens" — non-retryable | ||||||||||||||||||
| | { kind: "throttle-burst"; retryAfterSeconds?: number } // 429 throttling_error, generic — retryable | ||||||||||||||||||
| | { kind: "budget-wallet" } // 429 budget_exceeded, "ExceededBudget: User=" | ||||||||||||||||||
| | { kind: "budget-global" } // 429 budget_exceeded, "Budget has been exceeded" | ||||||||||||||||||
| | { kind: "budget-unknown" } // 429 budget_exceeded, neither substring | ||||||||||||||||||
| | { kind: "too-large"; requestBytes?: number; limitBytes?: number } // 413 request_too_large | ||||||||||||||||||
| | { kind: "unauthorized" } // 401 | ||||||||||||||||||
| | { kind: "server-error"; status?: number } // 5xx | ||||||||||||||||||
| | { kind: "timeout" } // never resolves until the request's AbortSignal fires | ||||||||||||||||||
| | { kind: "malformed-json" } | ||||||||||||||||||
|
|
||||||||||||||||||
| /** | ||||||||||||||||||
| * Fetch-shaped fake for the two real gateway routes. Install with `.install()` (typically in | ||||||||||||||||||
| * `beforeEach`), script the next response with `.registerNext()` / `.chatNext()` (each call | ||||||||||||||||||
| * enqueues one response; unscripted calls default to `{ kind: "ok" }`), and read | ||||||||||||||||||
| * `.registerCalls` / `.chatCalls` to assert what was actually sent. Restore with `.restore()` | ||||||||||||||||||
| * (typically in `afterEach`) to remove the `fetch` spy. | ||||||||||||||||||
| * | ||||||||||||||||||
| * One instance per test file. Do not share an instance across files — `bun test` runs each file | ||||||||||||||||||
| * in its own worker process by default, so there is no cross-file state to worry about, but | ||||||||||||||||||
| * sharing an instance across `describe` blocks within one file mixes their scripted queues. | ||||||||||||||||||
| */ | ||||||||||||||||||
| export class FakeGateway { | ||||||||||||||||||
| registerCalls: RegisterCall[] = [] | ||||||||||||||||||
| chatCalls: ChatCall[] = [] | ||||||||||||||||||
| private registerQueue: RegisterMode[] = [] | ||||||||||||||||||
| private chatQueue: ChatMode[] = [] | ||||||||||||||||||
| private spy?: ReturnType<typeof spyOn> | ||||||||||||||||||
|
|
||||||||||||||||||
| registerNext(mode: RegisterMode): this { | ||||||||||||||||||
| this.registerQueue.push(mode) | ||||||||||||||||||
| return this | ||||||||||||||||||
| } | ||||||||||||||||||
| chatNext(mode: ChatMode): this { | ||||||||||||||||||
| this.chatQueue.push(mode) | ||||||||||||||||||
| return this | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /** Clears scripted queues and call logs without touching the installed spy. */ | ||||||||||||||||||
| reset(): this { | ||||||||||||||||||
| this.registerCalls = [] | ||||||||||||||||||
| this.chatCalls = [] | ||||||||||||||||||
| this.registerQueue = [] | ||||||||||||||||||
| this.chatQueue = [] | ||||||||||||||||||
| return this | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| install(): this { | ||||||||||||||||||
| this.spy = spyOn(globalThis, "fetch").mockImplementation( | ||||||||||||||||||
| (async (input: RequestInfo | URL, init?: RequestInit) => { | ||||||||||||||||||
| const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url | ||||||||||||||||||
| if (url.endsWith("/register")) return this.handleRegister(url, init) | ||||||||||||||||||
| if (url.includes("/v1/chat/completions")) return this.handleChat(url, init) | ||||||||||||||||||
| throw new Error(`FakeGateway: unhandled URL ${url}`) | ||||||||||||||||||
|
Comment on lines
+81
to
+84
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The fake dispatches only by URL suffix/substring and never checks Useful? React with 👍 / 👎. |
||||||||||||||||||
| }) as typeof fetch, | ||||||||||||||||||
| ) | ||||||||||||||||||
| return this | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| restore(): void { | ||||||||||||||||||
| this.spy?.mockRestore() | ||||||||||||||||||
| this.spy = undefined | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| private async handleRegister(url: string, init?: RequestInit): Promise<Response> { | ||||||||||||||||||
| const body = JSON.parse(String(init?.body)) | ||||||||||||||||||
| this.registerCalls.push({ url, installSecretHash: body.install_secret_hash, cliVersion: body.cli_version }) | ||||||||||||||||||
| const mode = this.registerQueue.shift() ?? { kind: "ok" as const } | ||||||||||||||||||
| if (mode.kind === "network") throw new Error("connection reset") | ||||||||||||||||||
| if (mode.kind === "http") return new Response("", { status: mode.status, headers: mode.headers }) | ||||||||||||||||||
| if (mode.kind === "malformed-json") return new Response("{not json", { status: 200 }) | ||||||||||||||||||
| return json({ | ||||||||||||||||||
| api_key: mode.apiKey ?? "sk-altimate-base-fake", | ||||||||||||||||||
| base_url: mode.baseUrl ?? GATEWAY_URL, | ||||||||||||||||||
| model: mode.model ?? MODEL_ID, | ||||||||||||||||||
| ...(mode.expiresAt === null | ||||||||||||||||||
| ? {} | ||||||||||||||||||
| : { expires_at: mode.expiresAt ?? new Date(Date.now() + 86_400_000).toISOString() }), | ||||||||||||||||||
| }) | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| private async handleChat(url: string, init?: RequestInit): Promise<Response> { | ||||||||||||||||||
| const authorization = new Headers(init?.headers).get("Authorization") | ||||||||||||||||||
| this.chatCalls.push({ url, authorization, body: init?.body ? JSON.parse(String(init.body)) : undefined }) | ||||||||||||||||||
| const mode = this.chatQueue.shift() ?? { kind: "ok" as const } | ||||||||||||||||||
| switch (mode.kind) { | ||||||||||||||||||
| case "ok": | ||||||||||||||||||
| return json( | ||||||||||||||||||
| { choices: [{ message: { content: mode.content ?? "hello from altimate-base" } }] }, | ||||||||||||||||||
| mode.status ?? 200, | ||||||||||||||||||
| ) | ||||||||||||||||||
| case "throttle-tokens": | ||||||||||||||||||
| return throttleError("Limit type: tokens. Key=sk-fake. Current: 300000, Limit: 262144") | ||||||||||||||||||
| case "throttle-burst": | ||||||||||||||||||
| return throttleError("burst limit exceeded", mode.retryAfterSeconds) | ||||||||||||||||||
| case "budget-wallet": | ||||||||||||||||||
| return budgetError("ExceededBudget: User=principal-fake over budget. Spend=0.26, Budget=0.25") | ||||||||||||||||||
| case "budget-global": | ||||||||||||||||||
| return budgetError("Budget has been exceeded! Current cost: 50.01, Max budget: 50") | ||||||||||||||||||
| case "budget-unknown": | ||||||||||||||||||
| return budgetError("spend limit reached") | ||||||||||||||||||
| case "too-large": { | ||||||||||||||||||
| const size = mode.requestBytes ?? 179_608 | ||||||||||||||||||
| const limit = mode.limitBytes ?? 128_000 | ||||||||||||||||||
| const message = `Request is ${size} bytes; the free tier limit is ${limit} bytes.` | ||||||||||||||||||
| return json( | ||||||||||||||||||
| { | ||||||||||||||||||
| error: { | ||||||||||||||||||
| message, | ||||||||||||||||||
| code: "413", | ||||||||||||||||||
| provider_specific_fields: { error: { code: "request_too_large", message } }, | ||||||||||||||||||
| }, | ||||||||||||||||||
| }, | ||||||||||||||||||
| 413, | ||||||||||||||||||
| ) | ||||||||||||||||||
| } | ||||||||||||||||||
| case "unauthorized": | ||||||||||||||||||
| return new Response("", { status: 401 }) | ||||||||||||||||||
| case "server-error": | ||||||||||||||||||
| return new Response("upstream error", { status: mode.status ?? 500 }) | ||||||||||||||||||
| case "timeout": | ||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3: The Prompt for AI agents
Suggested change
|
||||||||||||||||||
| return new Promise<Response>((_resolve, reject) => { | ||||||||||||||||||
| init?.signal?.addEventListener("abort", () => reject(init.signal?.reason), { once: true }) | ||||||||||||||||||
| }) | ||||||||||||||||||
| case "malformed-json": | ||||||||||||||||||
| return new Response("{not json", { status: 200 }) | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| function json(body: Record<string, unknown>, status = 200): Response { | ||||||||||||||||||
| return new Response(JSON.stringify(body), { status, headers: { "Content-Type": "application/json" } }) | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| function throttleError(message: string, retryAfterSeconds?: number): Response { | ||||||||||||||||||
| return new Response(JSON.stringify({ error: { type: "throttling_error", message } }), { | ||||||||||||||||||
| status: 429, | ||||||||||||||||||
| headers: retryAfterSeconds !== undefined ? { "retry-after": String(retryAfterSeconds) } : {}, | ||||||||||||||||||
| }) | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| function budgetError(message: string): Response { | ||||||||||||||||||
| return new Response(JSON.stringify({ error: { type: "budget_exceeded", message } }), { status: 429 }) | ||||||||||||||||||
| } | ||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P3: resetGatewayEnv() permanently mutates process env: it deletes ALTIMATE_FREE_GATEWAY_URL and sets ALTIMATE_BASE_GATEWAY_URL to the fake gateway URL, but no teardown restores the prior values. isolateAltimateBaseHome()'s afterAll only restores the ISOLATED_ENV keys, so in a shared
bun testworker the fake gateway URL stays set and the legacy var stays deleted after the suite. Follow the file's own restoration pattern: capture the original values once (e.g., alongside the ISOLATED_ENV snapshot in isolateAltimateBaseHome) and restore them in the afterAll.Prompt for AI agents