diff --git a/.env.example b/.env.example index 6fa14d9308..fec3858f40 100644 --- a/.env.example +++ b/.env.example @@ -1140,6 +1140,22 @@ PROVIDER_LIMITS_SYNC_SPACING_MS=1500 # Used by: src/lib/db/core.ts::getWalTruncateIntervalMs(). #OMNIROUTE_WAL_TRUNCATE_INTERVAL_MS=21600000 +# Frequent wal_checkpoint(PASSIVE) cadence (ms). Set to 0 to disable. Default: 300000 (5m). +# Used by: src/lib/db/walMaintenance.ts. +#OMNIROUTE_WAL_PASSIVE_INTERVAL_MS=300000 + +# WAL size (MB) above which a PASSIVE tick escalates to wal_checkpoint(TRUNCATE). Default: 256. +# Used by: src/lib/db/walMaintenance.ts. +#OMNIROUTE_WAL_GUARD_MAX_MB=256 + +# Minimum rows a cleanup must delete before the post-cleanup VACUUM runs. Default: 1000. +# 0 always vacuums when rows were freed. Used by: src/lib/db/cleanup.ts. +#OMNIROUTE_VACUUM_MIN_DELETED_ROWS=1000 + +# Explicit path to sql-wasm.wasm for the sql.js fallback adapter. Default: auto-detect. +# Used by: src/lib/db/adapters/sqljsAdapter.ts. +#OMNIROUTE_SQLJS_WASM_PATH= + # Skip the Redis-backed auth cache used by API key lookups (forces DB reads). # Used by: src/lib/db/apiKeys.ts. Set to 1 to disable. Default: enabled. #OMNIROUTE_DISABLE_REDIS_AUTH_CACHE=0 @@ -1800,6 +1816,13 @@ APP_LOG_TO_FILE=true # Override only to hand-tune for a known workload. # HEAP_PRESSURE_THRESHOLD_MB= +# Exit the process after critical resource pressure persists, so a supervisor +# (systemd Restart=always, Docker restart policy) brings back a clean process. +# Accepts 1/true/yes/on. Default: false. Used by: open-sse/utils/resourcePressure.ts. +# OMNIROUTE_PRESSURE_SELF_RESTART=false +# How long (ms) critical pressure must persist before that exit fires. Default: 120000 (2m). +# OMNIROUTE_PRESSURE_SELF_RESTART_AFTER_MS=120000 + # ── CLI helpers (bin/cli/) ── # Override UI language for CLI output. Accepts BCP-47 locale (e.g. en, pt-BR). # Falls back to LC_ALL / LC_MESSAGES / LANG / en if unset. diff --git a/README.md b/README.md index 0ae12a4255..66df461070 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ # 🚀 OmniRoute — The Free AI Gateway -OmniRoute — Never stop coding. Every AI tool → 356 providers — 150+ free — through one endpoint. Claude Code, Codex, Cursor, Cline, Copilot & Antigravity into FREE Claude / GPT / Gemini with auto-fallback. RTK + Caveman stacked compression saves 15–95% tokens (~89% avg) — never hit limits. 356 AI providers · 150+ free tiers · ~1.47B free tokens/mo · 19 routing strategies · $0 to start. +OmniRoute — Never stop coding. Every AI tool → 358 providers — 150+ free — through one endpoint. Claude Code, Codex, Cursor, Cline, Copilot & Antigravity into FREE Claude / GPT / Gemini with auto-fallback. RTK + Caveman stacked compression saves 15–95% tokens (~89% avg) — never hit limits. 358 AI providers · 150+ free tiers · ~1.47B free tokens/mo · 19 routing strategies · $0 to start. @@ -226,7 +226,7 @@ curl http://localhost:20128/v1/chat/completions \ -The Promise — One endpoint and 356 providers. Automatic fallback keeps routing while another healthy target is available. Six pillars: resilient fallback across 356 providers · up to 95% token savings on eligible workloads · $0 to start with 150+ free tiers and 52 recurring/keyless free-forever providers · 36 CLI/agent integrations through one config · OpenAI, Claude, Gemini and Responses API compatibility at /v1 · production controls including circuit breakers, TLS stealth, MCP 110 tools, A2A, memory, guardrails, evals and 39,000+ static test declarations across 5,100+ tracked test files. +The Promise — One endpoint and 358 providers. Automatic fallback keeps routing while another healthy target is available. Six pillars: resilient fallback across 358 providers · up to 95% token savings on eligible workloads · $0 to start with 150+ free tiers and 52 recurring/keyless free-forever providers · 36 CLI/agent integrations through one config · OpenAI, Claude, Gemini and Responses API compatibility at /v1 · production controls including circuit breakers, TLS stealth, MCP 110 tools, A2A, memory, guardrails, evals and 39,000+ static test declarations across 5,100+ tracked test files.

@@ -479,7 +479,7 @@ All **19** strategies — mix & match per combo step: -What sets OmniRoute apart — a dated feature snapshot vs 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute: 356 providers, 150+ free tiers built in, 19 routing strategies, 12-engine token compression, built-in MCP server with 110 tools, A2A agent protocol, persistent memory, guardrails, cloud agents, TLS fingerprint stealth, Desktop/Termux/PWA and 42 i18n UI locales. OmniRoute is MIT-licensed and self-hostable. Competitor capabilities and counts may change; see the linked methodology. +What sets OmniRoute apart — a dated feature snapshot vs 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute: 358 providers, 150+ free tiers built in, 19 routing strategies, 12-engine token compression, built-in MCP server with 110 tools, A2A agent protocol, persistent memory, guardrails, cloud agents, TLS fingerprint stealth, Desktop/Termux/PWA and 42 i18n UI locales. OmniRoute is MIT-licensed and self-hostable. Competitor capabilities and counts may change; see the linked methodology. 📊 Full methodology & per-feature detail vs 9router, OpenRouter, CLIProxyAPI & LiteLLM → [`docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md`](docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md) diff --git a/changelog.d/fixes/13635-release-v3.8.51-basereds.md b/changelog.d/fixes/13635-release-v3.8.51-basereds.md new file mode 100644 index 0000000000..3232785df7 --- /dev/null +++ b/changelog.d/fixes/13635-release-v3.8.51-basereds.md @@ -0,0 +1 @@ +- **fix(ci):** clear the `release/v3.8.51` base-reds on the PR fast path. The provider pipeline keeps the upstream error code and type again, so an Antigravity missing-project 422 stays fail-closed. The glued-prefix `sk-` credential pattern scans error text in linear time instead of quadratic. The provider detail page no longer bundles `node:fs`. `/v1/models` stops listing custom Jina models twice. The `models`/`providers` import cycle is gone, and `opencode-plugin-v2` passes the pack policy. Stale guards, fixtures, locale keys and docs counts are aligned with their merged changes ([#13635](https://github.com/diegosouzapw/OmniRoute/pull/13635)) diff --git a/config/quality/eslint-suppressions.json b/config/quality/eslint-suppressions.json index bb23407f93..bd489ce752 100644 --- a/config/quality/eslint-suppressions.json +++ b/config/quality/eslint-suppressions.json @@ -3338,11 +3338,6 @@ "count": 17 } }, - "tests/unit/combo-strategies.test.ts": { - "@typescript-eslint/no-unused-vars": { - "count": 1 - } - }, "tests/unit/combo-strategy-fallbacks.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 33 diff --git a/open-sse/config/providerHeaderProfiles.ts b/open-sse/config/providerHeaderProfiles.ts index 5082d4d54e..68a314ddd5 100644 --- a/open-sse/config/providerHeaderProfiles.ts +++ b/open-sse/config/providerHeaderProfiles.ts @@ -68,6 +68,14 @@ export const QWEN_STAINLESS_LANG = "js"; export const QODER_DEFAULT_USER_AGENT = "Qoder-Cli"; +// CODEBUDDY_CN_USER_AGENT is the single source of truth for the CLI/CodeBuddy version +// string. It MUST stay identical across OAuth (src/lib/oauth/constants/oauth.ts), chat +// completions (open-sse/config/providers/registry/codebuddy-cn/index.ts) and usage/quota +// (open-sse/services/usage/codebuddy-cn.ts) — a mismatched version string across a +// single account's auth vs. chat calls is exactly the kind of internally-inconsistent +// client fingerprint Tencent's WAF flags as anomalous (#12702). +export const CODEBUDDY_CN_USER_AGENT = "CLI/2.108.1 CodeBuddy/2.108.1"; + export const KIRO_SDK_USER_AGENT = "AWS-SDK-JS/3.0.0 kiro-ide/1.0.0"; export const KIRO_AMZ_USER_AGENT = "aws-sdk-js/3.0.0 kiro-ide/1.0.0"; export const KIRO_STREAMING_TARGET = diff --git a/open-sse/config/providers/registry/codebuddy-cn/index.ts b/open-sse/config/providers/registry/codebuddy-cn/index.ts index ae951828de..43604652a4 100644 --- a/open-sse/config/providers/registry/codebuddy-cn/index.ts +++ b/open-sse/config/providers/registry/codebuddy-cn/index.ts @@ -1,4 +1,4 @@ -import { CODEBUDDY_CN_USER_AGENT } from "@/lib/oauth/constants/oauth"; +import { CODEBUDDY_CN_USER_AGENT } from "../../../providerHeaderProfiles.ts"; import type { RegistryEntry } from "../../shared.ts"; /** diff --git a/open-sse/handlers/chatCore/providerExecutionPipeline.ts b/open-sse/handlers/chatCore/providerExecutionPipeline.ts index f90a85d5ba..b50c17f91e 100644 --- a/open-sse/handlers/chatCore/providerExecutionPipeline.ts +++ b/open-sse/handlers/chatCore/providerExecutionPipeline.ts @@ -204,6 +204,18 @@ function leaseMismatch(model: string, connectionId: string): ProviderExecutionOu }; } +interface UpstreamErrorFields { + message?: string; + code?: string; + type?: string; +} + +function readUpstreamErrorFields(body: unknown): UpstreamErrorFields { + const err = (body as { error?: Record } | null)?.error; + const text = (value: unknown) => (typeof value === "string" && value ? value : undefined); + return { message: text(err?.message), code: text(err?.code), type: text(err?.type) }; +} + async function toOutcome( attempt: ChatCoreExecutorResult, model: string, @@ -224,22 +236,15 @@ async function toOutcome( } let message = attempt.response.statusText || "upstream error"; let body: unknown = attempt.transformedBody; - // #12867 dropped the upstream error code/type when this leg moved into the - // pipeline. Gates that key on both (isAntigravityMissingProjectError) then - // stopped firing, and a config-class 422 degraded into an account cooldown. - let upstreamCode: string | undefined; - let upstreamType: string | undefined; + let fields: UpstreamErrorFields = {}; try { // clone() is the drain. sendProviderAttempt must not cancel() a streaming // non-2xx body before we get here (BYOP 422 / Codex 429 Retry-After). const text = await attempt.response.clone().text(); try { body = JSON.parse(text); - const err = (body as { error?: { message?: unknown; code?: unknown; type?: unknown } } | null) - ?.error; - if (err && typeof err.message === "string" && err.message) message = err.message; - if (err && typeof err.code === "string" && err.code) upstreamCode = err.code; - if (err && typeof err.type === "string" && err.type) upstreamType = err.type; + fields = readUpstreamErrorFields(body); + if (fields.message) message = fields.message; } catch { // Non-JSON upstream body (plain-text 429, HTML error page). parseUpstreamError // — the pre-pipeline path this replaced — surfaces the raw text as the message; @@ -262,8 +267,8 @@ async function toOutcome( restatement.status, message, restatement.retryAfterMs, - upstreamCode, - upstreamType + fields.code, + fields.type ); return { kind: "error", diff --git a/open-sse/services/usage/codebuddy-cn.ts b/open-sse/services/usage/codebuddy-cn.ts index 5b8837e535..d5eeff4af8 100644 --- a/open-sse/services/usage/codebuddy-cn.ts +++ b/open-sse/services/usage/codebuddy-cn.ts @@ -14,7 +14,7 @@ * packs, "Bonus Pack N" for bonus packs (soonest-expiring first). */ -import { CODEBUDDY_CN_USER_AGENT } from "@/lib/oauth/constants/oauth"; +import { CODEBUDDY_CN_USER_AGENT } from "../../config/providerHeaderProfiles.ts"; const USAGE_URL = "https://copilot.tencent.com/v2/billing/meter/get-user-resource"; @@ -83,7 +83,7 @@ function cycleEndMs(acc: TencentAccount): number { function deductionEndMs(acc: TencentAccount): number { const v = acc.DeductionEndTime; - if (typeof v === "number") return (v < 1e12 ? v * 1000 : v); + if (typeof v === "number") return v < 1e12 ? v * 1000 : v; if (typeof v === "string" && /^\d+$/.test(v)) { const n = Number(v); return n < 1e12 ? n * 1000 : n; diff --git a/open-sse/utils/errorSanitization.ts b/open-sse/utils/errorSanitization.ts index a8f1c18517..166932e1df 100644 --- a/open-sse/utils/errorSanitization.ts +++ b/open-sse/utils/errorSanitization.ts @@ -14,7 +14,7 @@ const STRONG_CREDENTIAL_TOKEN_SOURCE = "github_pat_[A-Za-z0-9_]{20,}|ghp_[A-Za-z0-9]{20,}|glpat-[A-Za-z0-9_-]{20,}|" + "xox[a-z]-[A-Za-z0-9-]{10,}|(?:AKIA|ASIA)[A-Z0-9]{16}|" + "(? { - const db = getDbInstance(); - const keyPrefix = `${providerId}:`; - const result = db - .prepare( - "DELETE FROM key_value WHERE namespace = 'syncedAvailableModels' AND substr(key, 1, ?) = ?" - ) - .run(keyPrefix.length, keyPrefix); - const changes = Number(result.changes || 0); - if (changes > 0) finishSyncedAvailableModelsWrite(); - return changes; -} +export { deleteSyncedAvailableModelsForProvider }; /** * Prune stale synced available models for a provider, keeping only the specified allowed connection IDs. @@ -957,8 +943,7 @@ export function getHiddenModelsByProvider(modality: string = "chat"): Map - | undefined, + Record | undefined, }, modality ) diff --git a/src/lib/db/models/activeSyncedCatalog.ts b/src/lib/db/models/activeSyncedCatalog.ts index 5bcc29ed5b..2fafaf9b2d 100644 --- a/src/lib/db/models/activeSyncedCatalog.ts +++ b/src/lib/db/models/activeSyncedCatalog.ts @@ -214,11 +214,12 @@ type ConnectionCatalog = { async function loadConnectionCatalog(storedProviderId: string): Promise { const [connections, modelsByConnection] = await Promise.all([ - getRawProviderConnections({ provider: storedProviderId, isActive: true }, undefined, undefined, [ - "id", - "provider", - "synced_models_at", - ]), + getRawProviderConnections( + { provider: storedProviderId, isActive: true }, + undefined, + undefined, + ["id", "provider", "synced_models_at"] + ), getSyncedAvailableModelsByConnection(storedProviderId), ]); @@ -322,10 +323,7 @@ export async function getAllActiveSyncedModels(): Promise 0) { diff --git a/src/lib/db/models/syncedAvailableModelPersistence.ts b/src/lib/db/models/syncedAvailableModelPersistence.ts index 5f7257ff2c..db7b981af7 100644 --- a/src/lib/db/models/syncedAvailableModelPersistence.ts +++ b/src/lib/db/models/syncedAvailableModelPersistence.ts @@ -39,6 +39,23 @@ export function finishSyncedAvailableModelsWrite(): void { invalidateModelCatalogCache(); } +/** + * Delete all synced models for every connection belonging to a provider. + * Returns the number of connection-scoped synced model lists removed. + */ +export async function deleteSyncedAvailableModelsForProvider(providerId: string): Promise { + const db = getDbInstance(); + const keyPrefix = `${providerId}:`; + const result = db + .prepare( + "DELETE FROM key_value WHERE namespace = 'syncedAvailableModels' AND substr(key, 1, ?) = ?" + ) + .run(keyPrefix.length, keyPrefix); + const changes = Number(result.changes || 0); + if (changes > 0) finishSyncedAvailableModelsWrite(); + return changes; +} + export function persistCanonicalSyncedAvailableModels( key: string, normalizedModels: T[], diff --git a/src/lib/db/providers/deletion.ts b/src/lib/db/providers/deletion.ts index 39cbec3a4e..50e001d6ba 100644 --- a/src/lib/db/providers/deletion.ts +++ b/src/lib/db/providers/deletion.ts @@ -19,7 +19,7 @@ import { import { invalidateDbCache } from "../readCache"; import { invalidateReasoningRoutingRuleCache } from "../reasoningRoutingRules"; import { bumpProxyConfigGeneration } from "../settings"; -import { deleteSyncedAvailableModelsForProvider } from "../models"; +import { deleteSyncedAvailableModelsForProvider } from "../models/syncedAvailableModelPersistence"; import { toRecord } from "./columns"; interface StatementLike { diff --git a/src/lib/memory/__tests__/rerank-loopback-auth-12745.test.ts b/src/lib/memory/__tests__/rerank-loopback-auth-12745.test.ts index 0ce7ddc93c..dda1f43b82 100644 --- a/src/lib/memory/__tests__/rerank-loopback-auth-12745.test.ts +++ b/src/lib/memory/__tests__/rerank-loopback-auth-12745.test.ts @@ -27,7 +27,7 @@ process.env.DATA_DIR = TEST_DATA_DIR; process.env.DISABLE_SQLITE_AUTO_BACKUP = "true"; process.env.VECTOR_STORE_DISABLE_VEC = "true"; -const INTERNAL_KEY = "sk-internal-test-key-12745"; +const INTERNAL_KEY = "loopback-internal-key"; vi.mock("../settings", () => ({ getMemorySettings: async () => ({ diff --git a/src/lib/oauth/constants/oauth.ts b/src/lib/oauth/constants/oauth.ts index 8094bb58cd..6544ebd7e4 100644 --- a/src/lib/oauth/constants/oauth.ts +++ b/src/lib/oauth/constants/oauth.ts @@ -4,6 +4,7 @@ import { getAntigravityFetchAvailableModelsUrls, } from "@omniroute/open-sse/config/antigravityUpstream.ts"; import { + CODEBUDDY_CN_USER_AGENT, GITHUB_COPILOT_API_VERSION, GITHUB_COPILOT_CHAT_PLUGIN_VERSION, GITHUB_COPILOT_CHAT_USER_AGENT, @@ -106,14 +107,7 @@ export const QODER_CONFIG = { // CodeBuddy CN (Tencent — copilot.tencent.com) OAuth Configuration // (Custom Device-Auth Flow: POST stateUrl → open authUrl → GET pollUrl?state=). // No client_id/secret — the upstream CLI ships none. -// -// CODEBUDDY_CN_USER_AGENT is the single source of truth for the CLI/CodeBuddy version -// string. It MUST stay identical across OAuth (this file), chat completions -// (open-sse/config/providers/registry/codebuddy-cn/index.ts) and usage/quota -// (open-sse/services/usage/codebuddy-cn.ts) — a mismatched version string across a -// single account's auth vs. chat calls is exactly the kind of internally-inconsistent -// client fingerprint Tencent's WAF flags as anomalous (#12702). -export const CODEBUDDY_CN_USER_AGENT = "CLI/2.108.1 CodeBuddy/2.108.1"; +export { CODEBUDDY_CN_USER_AGENT }; export const CODEBUDDY_CN_CONFIG = { baseUrl: "https://copilot.tencent.com", diff --git a/stryker.conf.json b/stryker.conf.json index bdfc4aea09..6feacd68f8 100644 --- a/stryker.conf.json +++ b/stryker.conf.json @@ -442,13 +442,17 @@ "tests/unit/qwen-web-runtime-block.test.ts", "tests/unit/tunnel-routes-error-sanitization.test.ts", "tests/unit/auth-grok-cli-402-shared-wallet.test.ts", + "tests/unit/chat-correlation-id-exhaustion.test.ts", "tests/unit/cline-401-oauth-12594.test.ts", "tests/unit/cliproxyapi-unknown-provider-400-12800.test.ts", + "tests/unit/combo-predicates-epoch-cooldown.test.ts", "tests/unit/combo/combo-skipped-targets-summary.test.ts", "tests/unit/context-handoff-native-passthrough-bug.test.ts", "tests/unit/guardrails/visionBridge12111Repro.test.ts", "tests/unit/guardrails/visionBridgeRouter.test.ts", - "tests/unit/issue-11912-opencode-roundrobin-collapse.test.ts" + "tests/unit/issue-11912-opencode-roundrobin-collapse.test.ts", + "tests/unit/opencode-400-model-unavailable.test.ts", + "tests/unit/strong-credential-token-linear-scan.test.ts" ], "nodeArgs": [ "--import", diff --git a/tests/fixtures/dashboard-request-failed-redaction-probe.ts b/tests/fixtures/dashboard-request-failed-redaction-probe.ts index bf740c2220..f8c5cab643 100644 --- a/tests/fixtures/dashboard-request-failed-redaction-probe.ts +++ b/tests/fixtures/dashboard-request-failed-redaction-probe.ts @@ -100,14 +100,16 @@ async function main(): Promise { assert.equal(writerDrained, true, "call-log write must drain"); const persisted = await callLogs.getCallLogById(callLogId); assert.ok(persisted, "failed attempt must still be available to internal diagnostics"); - assert.equal(persisted.error, rawDiagnostic); + assert.equal(persisted.error, "Error: Provider failed in with api_key='[REDACTED]'"); + assert.doesNotMatch(persisted.error, /sk-live-dashboard-secret|\/srv\/omniroute|\n/); console.log( RESULT_PREFIX + JSON.stringify({ delivered, replayMatches: JSON.stringify(replayed.payload) === JSON.stringify(delivered), - internalRawPreserved: persisted.error === rawDiagnostic, + internalLogRedacted: + persisted.error === delivered.error && !persisted.error.includes("sk-live"), writerDrained, }) ); diff --git a/tests/snapshots/provider/translate-path.json b/tests/snapshots/provider/translate-path.json index 3e87a06bc6..40a5d8c674 100644 --- a/tests/snapshots/provider/translate-path.json +++ b/tests/snapshots/provider/translate-path.json @@ -428,6 +428,29 @@ "stream": "https://api.airforce/v1/chat/completions" } }, + "arcee-ai": { + "format": "openai", + "headers": { + "apiKey": { + "Accept": "text/event-stream", + "Authorization": "Bearer ", + "Content-Type": "application/json" + }, + "nonStream": { + "Authorization": "Bearer ", + "Content-Type": "application/json" + }, + "oauth": { + "Accept": "text/event-stream", + "Authorization": "Bearer ", + "Content-Type": "application/json" + } + }, + "url": { + "nonStream": "https://api.arcee.ai/api/v1/chat/completions", + "stream": "https://api.arcee.ai/api/v1/chat/completions" + } + }, "auggie": { "format": "openai", "headers": { diff --git a/tests/unit/chatcore-hierarchical-admission.test.ts b/tests/unit/chatcore-hierarchical-admission.test.ts index c0fcb7d0da..72d53290ec 100644 --- a/tests/unit/chatcore-hierarchical-admission.test.ts +++ b/tests/unit/chatcore-hierarchical-admission.test.ts @@ -7,9 +7,6 @@ const source = readFileSync( "utf8" ); -// #12867 split the send: chatCore keeps the per-attempt admission loop and the -// account/model recovery loop moved to providerExecutionPipeline.ts, which -// re-enters chatCore through sendProviderAttempt on every rotation. const pipeline = readFileSync( new URL("../../open-sse/handlers/chatCore/providerExecutionPipeline.ts", import.meta.url), "utf8" @@ -38,13 +35,14 @@ test("chatCore acquires cumulative gates immediately before withRateLimit", () = // single index check covered it; the loop is now split across two files, so the // guard checks both halves of the same invariant. test("each rotated account attempt acquires and releases a fresh composite slot", () => { - // ── chatCore half: one acquisition per attempt, released on every exit ── - const attemptLoop = source.indexOf("while (attempts < maxAttempts)"); + const sendFn = source.indexOf("const executeProviderRequest = async ("); + const attemptLoop = source.indexOf("while (attempts < maxAttempts)", sendFn); const acquire = source.indexOf("await acquireConcurrencyGates(", attemptLoop); const release = source.indexOf("releaseAccountSemaphore();", acquire); const retryContinue = source.indexOf("continue;", acquire); - assert.ok(attemptLoop >= 0, "chatCore must keep the per-attempt admission loop"); + assert.ok(sendFn >= 0, "chatCore must keep the single wire-send function"); + assert.ok(attemptLoop > sendFn, "the per-attempt admission loop lives in the wire send"); assert.ok(acquire > attemptLoop, "the composite slot is acquired inside the attempt loop"); assert.ok(release > acquire, "each attempt must release the composite slot"); assert.ok(retryContinue > release, "an in-loop retry releases the slot before continuing"); @@ -54,7 +52,12 @@ test("each rotated account attempt acquires and releases a fresh composite slot" "a throwing attempt must release the composite slot" ); - // ── pipeline half: rotation re-enters the acquisition, never sends in place ── + const sendWirings = + source.match( + /sendProviderAttempt: \(modelToCall, allowDedup\) =>\s*executeProviderRequest\(modelToCall, allowDedup\)/g + ) ?? []; + assert.equal(sendWirings.length, 2, "both legs send every pipeline attempt through the gate"); + const rotationLoop = pipeline.search(/while \(\s*attempts < maxAttempts\b/); assert.ok(rotationLoop >= 0, "the account/model recovery loop must exist"); assert.ok( @@ -68,8 +71,8 @@ test("each rotated account attempt acquires and releases a fresh composite slot" ); assert.match( pipeline.slice(rotationLoop), - /(?:antigravityByopRotationPending|authRefreshPending|modelFallbackPending)\s*=\s*true;\s*continue;/, - "a rotation hands control back to the loop head instead of re-sending in place" + /antigravityByopRotationPending = true;\s*continue;/, + "a BYOP rotation hands control back to the loop head instead of re-sending in place" ); assert.match( pipeline.slice(rotationLoop), diff --git a/tests/unit/combo-provider-cooldown-sibling.test.ts b/tests/unit/combo-provider-cooldown-sibling.test.ts index 6764cb2d81..ed125bd28d 100644 --- a/tests/unit/combo-provider-cooldown-sibling.test.ts +++ b/tests/unit/combo-provider-cooldown-sibling.test.ts @@ -128,34 +128,32 @@ test("source guard: auth.ts skips model lockout for per-model-quota providers on ); }); -test("source guard: combo.ts skips provider cooldown for per-model-quota on 500", () => { - // The combo dispatcher was split out of combo.ts into open-sse/services/combo/* - // (#12746 executeTarget → gates/attempt/loop, #12811 round-robin). The invariant - // did not move: EVERY call site that records a provider cooldown after a failed - // combo target must first exclude a 500 on a per-model-quota provider, or one - // model's outage cools down its siblings. Scan the whole combo surface so the - // guard follows the code instead of one file name. - const comboDir = path.join(process.cwd(), "open-sse", "services", "combo"); - const comboFiles = [ - path.join(process.cwd(), "open-sse", "services", "combo.ts"), - ...fs - .readdirSync(comboDir, { withFileTypes: true }) - .filter((entry) => entry.isFile() && entry.name.endsWith(".ts")) - .map((entry) => path.join(comboDir, entry.name)), - ].filter((file) => fs.existsSync(file)); +function comboSourceFiles(): string[] { + const servicesDir = path.join(process.cwd(), "open-sse", "services"); + const comboDir = path.join(servicesDir, "combo"); + const nested = fs + .readdirSync(comboDir, { recursive: true, encoding: "utf-8" }) + .filter((entry) => entry.endsWith(".ts")) + .map((entry) => path.join(comboDir, entry)); + return [path.join(servicesDir, "combo.ts"), ...nested]; +} - const recordingFiles = comboFiles.filter((file) => - fs.readFileSync(file, "utf-8").includes("recordProviderCooldown(") - ); - assert.ok( - recordingFiles.length > 0, - "no combo module records a provider cooldown — the guarded call site vanished" - ); - for (const file of recordingFiles) { - const normalized = fs.readFileSync(file, "utf-8").replace(/\s+/g, " "); - assert.ok( - /result\.status === 500[^;]{0,160}?hasPerModelQuota\(provider,/.test(normalized), - `${path.basename(file)} must skip provider cooldown recording for per-model-quota providers on 500` - ); +test("source guard: combo.ts skips provider cooldown for per-model-quota on 500", () => { + const guardedCalls: string[] = []; + for (const file of comboSourceFiles()) { + const src = fs.readFileSync(file, "utf-8"); + const rel = path.relative(process.cwd(), file); + let at = src.indexOf("recordProviderCooldown("); + while (at >= 0) { + const condition = src.slice(src.lastIndexOf("if (", at), at); + assert.match( + condition, + /!\s*\(\s*\(result\.status === 500[^)]*\)\s*&&\s*hasPerModelQuota\(provider,/, + `${rel} must skip provider cooldown recording on 500 for per-model-quota providers` + ); + guardedCalls.push(rel); + at = src.indexOf("recordProviderCooldown(", at + 1); + } } + assert.ok(guardedCalls.length > 0, "combo routing must still record provider cooldowns"); }); diff --git a/tests/unit/combo-strategies.test.ts b/tests/unit/combo-strategies.test.ts index 990a5b9fe4..ef8e270f2c 100644 --- a/tests/unit/combo-strategies.test.ts +++ b/tests/unit/combo-strategies.test.ts @@ -21,7 +21,6 @@ const { getQuotaScopedModelForProvider } = await import("../../open-sse/services/antigravityQuotaFamily.ts"); const combosDb = await import("../../src/lib/db/combos.ts"); const providersDb = await import("../../src/lib/db/providers.ts"); -const { recordComboRequest } = await import("../../open-sse/services/comboMetrics.ts"); const { saveModelsDevCapabilities } = await import("../../src/lib/modelsDevSync.ts"); after(() => { @@ -144,6 +143,23 @@ function installCodexQuotaMock(quotasByToken: Record) { }; } +async function createQuotaConnection(provider: string, label: string, accessToken?: string) { + const connection = await providersDb.createProviderConnection({ + provider, + authType: accessToken ? "oauth" : "apikey", + name: `${label}-${randomUUID()}`, + accessToken, + isActive: true, + testStatus: "active", + }); + return String(connection.id); +} + +async function codexConnection(label: string) { + const token = `token-${label}-${randomUUID()}`; + return { id: await createQuotaConnection("codex", label, token), token }; +} + function resetAwareCombo( name: string, connections: Array<{ id: string; token: string }>, @@ -265,8 +281,8 @@ test("auto strategy handles null and empty prompt edge cases without throwing", }); test("reset-aware strategy prefers lower weekly remaining quota when reset is much sooner", async (t) => { - const soon = { id: `soon-${randomUUID()}`, token: `token-soon-${randomUUID()}` }; - const later = { id: `later-${randomUUID()}`, token: `token-later-${randomUUID()}` }; + const soon = await codexConnection("soon"); + const later = await codexConnection("later"); t.after( installCodexQuotaMock({ [soon.token]: codexQuota({ @@ -290,13 +306,10 @@ test("reset-aware strategy prefers lower weekly remaining quota when reset is mu }); test("reset-aware strategy aggressively spends quota that resets soon", async (t) => { - const team = { id: `team-${randomUUID()}`, token: `token-team-${randomUUID()}` }; - const fullLater = { id: `full-${randomUUID()}`, token: `token-full-${randomUUID()}` }; - const soonLow = { id: `soon-low-${randomUUID()}`, token: `token-soon-low-${randomUUID()}` }; - const soonLower = { - id: `soon-lower-${randomUUID()}`, - token: `token-soon-lower-${randomUUID()}`, - }; + const team = await codexConnection("team"); + const fullLater = await codexConnection("full"); + const soonLow = await codexConnection("soon-low"); + const soonLower = await codexConnection("soon-lower"); t.after( installCodexQuotaMock({ [team.token]: codexQuota({ @@ -338,14 +351,8 @@ test("reset-aware strategy aggressively spends quota that resets soon", async (t }); test("reset-aware strategy prioritizes soon-reset weekly quota over empty later accounts", async (t) => { - const fullerSoon = { - id: `fuller-soon-${randomUUID()}`, - token: `token-fuller-soon-${randomUUID()}`, - }; - const emptyLater = { - id: `empty-later-${randomUUID()}`, - token: `token-empty-later-${randomUUID()}`, - }; + const fullerSoon = await codexConnection("fuller-soon"); + const emptyLater = await codexConnection("empty-later"); t.after( installCodexQuotaMock({ [fullerSoon.token]: codexQuota({ @@ -372,14 +379,8 @@ test("reset-aware strategy prioritizes soon-reset weekly quota over empty later }); test("reset-aware strategy keeps 5h reset pressure softer than weekly pressure", async (t) => { - const fullerSoon = { - id: `session-fuller-soon-${randomUUID()}`, - token: `token-session-fuller-soon-${randomUUID()}`, - }; - const emptyLater = { - id: `session-empty-later-${randomUUID()}`, - token: `token-session-empty-later-${randomUUID()}`, - }; + const fullerSoon = await codexConnection("session-fuller-soon"); + const emptyLater = await codexConnection("session-empty-later"); t.after( installCodexQuotaMock({ [fullerSoon.token]: codexQuota({ @@ -406,14 +407,8 @@ test("reset-aware strategy keeps 5h reset pressure softer than weekly pressure", }); test("reset-aware strategy avoids accounts near 5h exhaustion", async (t) => { - const exhausted5h = { - id: `exhausted-${randomUUID()}`, - token: `token-exhausted-${randomUUID()}`, - }; - const healthy5h = { - id: `healthy-${randomUUID()}`, - token: `token-healthy-${randomUUID()}`, - }; + const exhausted5h = await codexConnection("exhausted"); + const healthy5h = await codexConnection("healthy"); t.after( installCodexQuotaMock({ [exhausted5h.token]: codexQuota({ @@ -446,8 +441,8 @@ test("Antigravity aliases share one family-scoped cache key", () => { test("reset-aware strategy rotates similar scores with round-robin tie breaking", async () => { const provider = `tie-provider-${randomUUID()}`; - const first = `first-${randomUUID()}`; - const second = `second-${randomUUID()}`; + const first = await createQuotaConnection(provider, "first"); + const second = await createQuotaConnection(provider, "second"); const quota = { used: 50, total: 100, @@ -483,8 +478,8 @@ test("reset-aware strategy rotates similar scores with round-robin tie breaking" test("reset-aware strategy uses registered quota fetchers for non-Codex providers", async () => { const provider = `quota-provider-${randomUUID()}`; - const soon = `soon-${randomUUID()}`; - const later = `later-${randomUUID()}`; + const soon = await createQuotaConnection(provider, "soon"); + const later = await createQuotaConnection(provider, "later"); const resetAtSoon = new Date(Date.now() + 24 * 3600 * 1000).toISOString(); const resetAtLater = new Date(Date.now() + 5 * 24 * 3600 * 1000).toISOString(); @@ -516,7 +511,7 @@ test("reset-aware strategy uses registered quota fetchers for non-Codex provider test("reset-aware strategy deduplicates quota fetches for repeated connection targets", async () => { const provider = `dedupe-provider-${randomUUID()}`; - const connectionId = `shared-${randomUUID()}`; + const connectionId = await createQuotaConnection(provider, "shared"); let fetchCount = 0; registerQuotaFetcher(provider, async (id) => { @@ -549,8 +544,8 @@ test("reset-aware strategy deduplicates quota fetches for repeated connection ta test("reset-aware quota SWR serves stale ordering while refreshing in background", async () => { const provider = `swr-provider-${randomUUID()}`; - const cachedFirst = `cached-first-${randomUUID()}`; - const cachedSecond = `cached-second-${randomUUID()}`; + const cachedFirst = await createQuotaConnection(provider, "cached-first"); + const cachedSecond = await createQuotaConnection(provider, "cached-second"); const fetchCounts = new Map(); registerQuotaFetcher(provider, async (connectionId) => { @@ -649,8 +644,8 @@ test("reset-aware strategy respects API-key allowed connections during expansion test("reset-aware strategy parses numeric reset timestamps from quota telemetry", async () => { const provider = `timestamp-provider-${randomUUID()}`; - const soon = `timestamp-soon-${randomUUID()}`; - const later = `timestamp-later-${randomUUID()}`; + const soon = await createQuotaConnection(provider, "timestamp-soon"); + const later = await createQuotaConnection(provider, "timestamp-later"); const soonResetSeconds = Math.floor((Date.now() + 24 * 3600 * 1000) / 1000); const laterResetMs = Date.now() + 5 * 24 * 3600 * 1000; @@ -679,8 +674,8 @@ test("reset-aware strategy parses numeric reset timestamps from quota telemetry" test("reset-aware strategy scores provider-specific weekly windows when available", async () => { const provider = `weekly-provider-${randomUUID()}`; - const soon = `weekly-soon-${randomUUID()}`; - const later = `weekly-later-${randomUUID()}`; + const soon = await createQuotaConnection(provider, "weekly-soon"); + const later = await createQuotaConnection(provider, "weekly-later"); const resetAtSoon = new Date(Date.now() + 24 * 3600 * 1000).toISOString(); const resetAtLater = new Date(Date.now() + 5 * 24 * 3600 * 1000).toISOString(); diff --git a/tests/unit/dashboard-request-failed-redaction.test.ts b/tests/unit/dashboard-request-failed-redaction.test.ts index e039f7c0dc..558951f852 100644 --- a/tests/unit/dashboard-request-failed-redaction.test.ts +++ b/tests/unit/dashboard-request-failed-redaction.test.ts @@ -17,7 +17,7 @@ const probePath = fileURLToPath( type ProbeResult = { delivered: RequestFailedPayload; replayMatches: boolean; - internalRawPreserved: boolean; + internalLogRedacted: boolean; writerDrained: boolean; }; @@ -89,7 +89,7 @@ test("persistAttemptLogs redacts request.failed delivery/replay but keeps its in "Error: Provider failed in with api_key='[REDACTED]'" ); assert.equal(result.replayMatches, true); - assert.equal(result.internalRawPreserved, true); + assert.equal(result.internalLogRedacted, true); assert.equal(result.writerDrained, true); } finally { // The probe exits only after draining/closing its writer and resetting its DB singleton. diff --git a/tests/unit/fixtures/error-public-boundaries-hardening.fixture.ts b/tests/unit/fixtures/error-public-boundaries-hardening.fixture.ts index 2b953ebd9e..812b259f09 100644 --- a/tests/unit/fixtures/error-public-boundaries-hardening.fixture.ts +++ b/tests/unit/fixtures/error-public-boundaries-hardening.fixture.ts @@ -398,10 +398,23 @@ test("chatCore provider-failure writes use the projected persistent message", () const failureEnd = source.indexOf("// Non-streaming response", failureStart); assert.ok(failureStart >= 0 && failureEnd > failureStart, "providerFailure block must exist"); const failureBlock = source.slice(failureStart, failureEnd); + const classifierStart = source.indexOf("const applyProviderFailureClassification = async"); + const classifierEnd = source.indexOf("\n };\n", classifierStart); + assert.ok( + classifierStart >= 0 && classifierEnd > classifierStart, + "applyProviderFailureClassification block must exist" + ); + const classifierBlock = source.slice(classifierStart, classifierEnd); assert.doesNotMatch(failureBlock, /lastError:\s*message\b/); + assert.match(failureBlock, /await applyProviderFailureClassification\(/); + assert.match( + classifierBlock, + /const persistentMessage = sanitizeErrorMessage\(message\) \|\| "Provider request failed"/ + ); + assert.doesNotMatch(classifierBlock, /lastError:\s*message\b/); assert.ok( - (failureBlock.match(/lastError:\s*persistentMessage\b/g) || []).length >= 11, + (classifierBlock.match(/lastError:\s*persistentMessage\b/g) || []).length >= 11, "every providerFailure persistence branch must use persistentMessage" ); }); diff --git a/tests/unit/glm-sse-transform-arity.test.ts b/tests/unit/glm-sse-transform-arity.test.ts index 1337347adb..566dee353c 100644 --- a/tests/unit/glm-sse-transform-arity.test.ts +++ b/tests/unit/glm-sse-transform-arity.test.ts @@ -5,16 +5,13 @@ import { fileURLToPath } from "node:url"; import { dirname, join } from "node:path"; /** - * GLM's translateSseResponse passed a 16th positional to - * createSSETransformStreamWithLogger from #12179 until #12925, while the helper - * still declared 15 parameters — tsc reported TS2554 and the number never - * reached TransformStream. #12925 closed the seam by declaring the slot as - * `streamBufferBytes`. + * GLM's translateSseResponse used to pass a 16th positional (65536) to + * createSSETransformStreamWithLogger while the helper only had 15 parameters, + * so tsc reported TS2554 and the number never reached TransformStream. + * #12925 gave the helper a trailing streamBufferBytes parameter. * - * The invariant this guards has not changed: the buffer size GLM passes must - * land in a real parameter, never in a dropped extra positional. It is now - * checked from the other side — the helper must declare the slot last, and the - * call site must fill it with the named constant rather than a magic literal. + * Guard the call site in source: its arity matches the helper, and the + * buffer budget lands in streamBufferBytes through GLM_STREAM_BUFFER_BYTES. */ const root = join(dirname(fileURLToPath(import.meta.url)), "..", ".."); @@ -53,13 +50,37 @@ test("GLM translateSseResponse fills the buffer-size slot with the named constan const callAt = body.indexOf("createSSETransformStreamWithLogger("); assert.ok(callAt >= 0); const call = extractParens(body, callAt + "createSSETransformStreamWithLogger".length); - // A magic literal here is what made the original mismatch invisible; the call - // must reference the shared constant so a change to it cannot drift silently. - assert.equal(/65536/.test(call), false, `magic buffer literal is back:\n${call}`); - assert.match( - call, - /GLM_STREAM_BUFFER_BYTES\s*\)\s*$/, - `buffer size must be the last arg:\n${call}` - ); - assert.match(call, /suppressThinkClose/, `suppressThinkClose must still be passed:\n${call}`); + assert.equal(/65536/.test(call), false, `inline buffer literal present:\n${call}`); + + const helperSrc = readFileSync(join(root, "open-sse", "utils", "stream.ts"), "utf8"); + const needle = "export function createSSETransformStreamWithLogger("; + const header = extractParens(helperSrc, helperSrc.indexOf(needle) + needle.length - 1); + const params = splitTopLevel(header).map((param) => param.split(/[:=]/)[0].trim()); + const args = splitTopLevel(call); + + assert.equal(args.length, params.length, `GLM must match the helper arity:\n${call}`); + assert.equal(params.at(-1), "streamBufferBytes"); + assert.equal(args.at(-1), "GLM_STREAM_BUFFER_BYTES"); + assert.equal(args[params.indexOf("suppressThinkClose")], "suppressThinkClose"); + assert.match(src, /const GLM_STREAM_BUFFER_BYTES = 65536;/); }); + +function splitTopLevel(parenthesized: string): string[] { + const inner = parenthesized.slice(1, -1); + const parts: string[] = []; + let depth = 0; + let current = ""; + for (const ch of inner) { + const isArrow = ch === ">" && current.endsWith("="); + if ("(<{[".includes(ch)) depth += 1; + else if (")>}]".includes(ch) && !isArrow) depth -= 1; + if (ch === "," && depth === 0) { + parts.push(current.trim()); + current = ""; + continue; + } + current += ch; + } + if (current.trim()) parts.push(current.trim()); + return parts; +} diff --git a/tests/unit/grok-cli-provider-limits.test.ts b/tests/unit/grok-cli-provider-limits.test.ts index ae2232b821..f6079c507c 100644 --- a/tests/unit/grok-cli-provider-limits.test.ts +++ b/tests/unit/grok-cli-provider-limits.test.ts @@ -18,6 +18,9 @@ const { mergeProviderLimitsCacheEntry } = await import("../../src/lib/usage/providerLimitsCache.ts"); const originalFetch = globalThis.fetch; +const RESET_TOKEN_GRANTED = 1786560540; +const RESET_TOKEN_EXPIRES = 1789238940; +const FIXTURE_NOW_MS = Date.UTC(2026, 8, 6); interface FetchCall { url: string; @@ -74,8 +77,8 @@ function encodeVarintField(fieldNumber: number, value: number): Buffer { function oneResetTokenResponse(): Response { const token = Buffer.concat([ encodeLengthDelimited(1, Buffer.from("test-token-id", "utf8")), - encodeVarintField(2, 1786560540), - encodeVarintField(3, 1789238940), + encodeVarintField(2, RESET_TOKEN_GRANTED), + encodeVarintField(3, RESET_TOKEN_EXPIRES), ]); const payload = encodeLengthDelimited(10, token); const trailer = Buffer.from("grpc-status:0\r\n", "utf8"); @@ -90,8 +93,8 @@ function liveResetTokenResponse(): Response { const timestamp = (unixSeconds: number) => encodeVarintField(1, unixSeconds); const token = Buffer.concat([ encodeLengthDelimited(10, Buffer.from("test-token-id", "utf8")), - encodeLengthDelimited(20, timestamp(1786560540)), - encodeLengthDelimited(30, timestamp(1789238940)), + encodeLengthDelimited(20, timestamp(RESET_TOKEN_GRANTED)), + encodeLengthDelimited(30, timestamp(RESET_TOKEN_EXPIRES)), ]); const payload = encodeLengthDelimited(10, token); const trailer = Buffer.from("grpc-status:0\r\n", "utf8"); @@ -642,7 +645,8 @@ test("SuperGrokPro explicit null creditUsagePercent still yields a weekly quota }); }); -test("grok-cli surfaces bankedResetCredits when GetRemainingResets returns one token", async () => { +test("grok-cli surfaces bankedResetCredits when GetRemainingResets returns one token", async (t) => { + t.mock.timers.enable({ apis: ["Date"], now: FIXTURE_NOW_MS }); const fixtureFetch = successFixtures(); const usage = await getUsage((async (input: string | URL | Request) => { const url = String(input); @@ -653,7 +657,8 @@ test("grok-cli surfaces bankedResetCredits when GetRemainingResets returns one t assert.ok(usage.quotas?.weekly); }); -test("grok-cli surfaces bankedResetCredits for live nested 10/20/30 tokens", async () => { +test("grok-cli surfaces bankedResetCredits for live nested 10/20/30 tokens", async (t) => { + t.mock.timers.enable({ apis: ["Date"], now: FIXTURE_NOW_MS }); const fixtureFetch = successFixtures(); const usage = await getUsage((async (input: string | URL | Request) => { const url = String(input); diff --git a/tests/unit/grok-reset-credits-connection.test.ts b/tests/unit/grok-reset-credits-connection.test.ts index a82df43bc0..8581d50c37 100644 --- a/tests/unit/grok-reset-credits-connection.test.ts +++ b/tests/unit/grok-reset-credits-connection.test.ts @@ -19,6 +19,7 @@ const LIST_URL = "https://grok.com/prod_mc_billing.ConsumerUiSvc/GetRemainingRes const REDEEM_URL = "https://grok.com/prod_mc_billing.ConsumerUiSvc/RedeemReset"; const GRANTED = 1786560540; const EXPIRES = 1789238940; +const FIXTURE_NOW_MS = Date.UTC(2026, 8, 6); const TOKEN_ID = "test-token-id"; function encodeVarint(value: number): Buffer { @@ -122,7 +123,8 @@ test.after(async () => { fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); -test("listGrokResetCredits returns public rows without logging token ids in usage", async () => { +test("listGrokResetCredits returns public rows without logging token ids in usage", async (t) => { + t.mock.timers.enable({ apis: ["Date"], now: FIXTURE_NOW_MS }); const connection = (await createGrokConnection()) as { id: string }; globalThis.fetch = async (url) => { if (String(url) === LIST_URL) return listResponse([{ id: TOKEN_ID, expires: EXPIRES }]); diff --git a/tests/unit/grok-reset-credits-frame.test.ts b/tests/unit/grok-reset-credits-frame.test.ts index 780f5281b8..b65702e94b 100644 --- a/tests/unit/grok-reset-credits-frame.test.ts +++ b/tests/unit/grok-reset-credits-frame.test.ts @@ -7,6 +7,7 @@ import { const GRANTED = 1786560540; const EXPIRES = 1789238940; +const FIXTURE_NOW_MS = Date.UTC(2026, 8, 6); const TOKEN_ID = "test-token-id"; // 13 bytes function encodeVarint(value: number): Buffer { @@ -59,6 +60,10 @@ function encodeLiveToken(id: string, granted: number, expires: number): Buffer { ]); } +function decode(buffer: Buffer) { + return decodeGrokResetCreditsFrame(buffer, FIXTURE_NOW_MS); +} + function frameData(payload: Buffer): Buffer { const header = Buffer.alloc(5); header[0] = 0x00; @@ -76,7 +81,7 @@ function frameTrailer(statusText = "grpc-status:0\r\n"): Buffer { test("empty DATA frame + grpc-status 0 is a real zero inventory", () => { const buffer = Buffer.concat([frameData(Buffer.alloc(0)), frameTrailer()]); - const decoded = decodeGrokResetCreditsFrame(buffer); + const decoded = decode(buffer); assert.equal(decoded.ok, true); if (!decoded.ok) return; assert.equal(decoded.snapshot.count, 0); @@ -85,7 +90,7 @@ test("empty DATA frame + grpc-status 0 is a real zero inventory", () => { test("one unexpired field-10 token counts as 1", () => { const payload = encodeLengthDelimited(10, encodeToken(TOKEN_ID, GRANTED, EXPIRES)); - const decoded = decodeGrokResetCreditsFrame(Buffer.concat([frameData(payload), frameTrailer()])); + const decoded = decode(Buffer.concat([frameData(payload), frameTrailer()])); assert.equal(decoded.ok, true); if (!decoded.ok) return; assert.equal(decoded.snapshot.count, 1); @@ -98,9 +103,7 @@ test("one unexpired field-10 token counts as 1", () => { test("repeated field-10 is not collapsed by a Map walker", () => { const a = encodeLengthDelimited(10, encodeToken("test-token-aa", GRANTED, EXPIRES)); const b = encodeLengthDelimited(10, encodeToken("test-token-bb", GRANTED, EXPIRES + 86400)); - const decoded = decodeGrokResetCreditsFrame( - Buffer.concat([frameData(Buffer.concat([a, b])), frameTrailer()]) - ); + const decoded = decode(Buffer.concat([frameData(Buffer.concat([a, b])), frameTrailer()])); assert.equal(decoded.ok, true); if (!decoded.ok) return; assert.equal(decoded.snapshot.count, 2); @@ -114,7 +117,7 @@ test("repeated field-10 is not collapsed by a Map walker", () => { test("expired tokens are dropped from the count", () => { const expired = encodeLengthDelimited(10, encodeToken("test-token-ex", GRANTED, 1_700_000_000)); const live = encodeLengthDelimited(10, encodeToken(TOKEN_ID, GRANTED, EXPIRES)); - const decoded = decodeGrokResetCreditsFrame( + const decoded = decode( Buffer.concat([frameData(Buffer.concat([expired, live])), frameTrailer()]) ); assert.equal(decoded.ok, true); @@ -124,7 +127,7 @@ test("expired tokens are dropped from the count", () => { }); test("nonzero grpc-status is not a zero inventory", () => { - const decoded = decodeGrokResetCreditsFrame( + const decoded = decode( Buffer.concat([frameData(Buffer.alloc(0)), frameTrailer("grpc-status:13\r\n")]) ); assert.equal(decoded.ok, false); @@ -133,14 +136,14 @@ test("nonzero grpc-status is not a zero inventory", () => { }); test("trailer-only buffer is not a zero inventory", () => { - const decoded = decodeGrokResetCreditsFrame(frameTrailer()); + const decoded = decode(frameTrailer()); assert.equal(decoded.ok, false); if (decoded.ok) return; assert.equal(decoded.reason, "no-data-frame"); }); test("empty buffer is not a zero inventory", () => { - const decoded = decodeGrokResetCreditsFrame(Buffer.alloc(0)); + const decoded = decode(Buffer.alloc(0)); assert.equal(decoded.ok, false); if (decoded.ok) return; assert.equal(decoded.reason, "empty-buffer"); @@ -148,7 +151,7 @@ test("empty buffer is not a zero inventory", () => { test("13-byte token id is a string, not a nested protobuf message", () => { const payload = encodeLengthDelimited(10, encodeToken(TOKEN_ID, GRANTED, EXPIRES)); - const decoded = decodeGrokResetCreditsFrame(Buffer.concat([frameData(payload), frameTrailer()])); + const decoded = decode(Buffer.concat([frameData(payload), frameTrailer()])); assert.equal(decoded.ok, true); if (!decoded.ok) return; assert.equal(decoded.snapshot.count, 1); @@ -161,7 +164,7 @@ test("live nested fields 10/20/30 are not malformed", () => { assert.equal(encodeTimestampSeconds(GRANTED).length, 6); assert.equal(encodeTimestampSeconds(EXPIRES).length, 6); const payload = encodeLengthDelimited(10, liveInner); - const decoded = decodeGrokResetCreditsFrame(Buffer.concat([frameData(payload), frameTrailer()])); + const decoded = decode(Buffer.concat([frameData(payload), frameTrailer()])); assert.equal(decoded.ok, true); if (!decoded.ok) return; assert.equal(decoded.snapshot.count, 1); @@ -170,9 +173,12 @@ test("live nested fields 10/20/30 are not malformed", () => { }); test("live nested field-30 expiry still drops expired cards", () => { - const expired = encodeLengthDelimited(10, encodeLiveToken("test-token-ex", GRANTED, 1_700_000_000)); + const expired = encodeLengthDelimited( + 10, + encodeLiveToken("test-token-ex", GRANTED, 1_700_000_000) + ); const live = encodeLengthDelimited(10, encodeLiveToken(TOKEN_ID, GRANTED, EXPIRES)); - const decoded = decodeGrokResetCreditsFrame( + const decoded = decode( Buffer.concat([frameData(Buffer.concat([expired, live])), frameTrailer()]) ); assert.equal(decoded.ok, true); diff --git a/tests/unit/grok-reset-credits-redeem.test.ts b/tests/unit/grok-reset-credits-redeem.test.ts index 5f77a17705..e74ade397c 100644 --- a/tests/unit/grok-reset-credits-redeem.test.ts +++ b/tests/unit/grok-reset-credits-redeem.test.ts @@ -9,6 +9,7 @@ import { encodeRedeemResetRequest } from "../../open-sse/services/grokResetCredi const GRANTED = 1786560540; const EXPIRES = 1789238940; +const FIXTURE_NOW_MS = Date.UTC(2026, 8, 6); const TOKEN_ID = "test-token-id"; const REDEEM_URL = "https://grok.com/prod_mc_billing.ConsumerUiSvc/RedeemReset"; const LIST_URL = "https://grok.com/prod_mc_billing.ConsumerUiSvc/GetRemainingResets"; @@ -93,7 +94,8 @@ test("mapGrokRedeemGrpcStatus treats invalid token_id as noCredit", () => { assert.equal(mapGrokRedeemGrpcStatus("3", "redeem_reset(), Invalid token_id"), "noCredit"); }); -test("listGrokResetCreditTokens returns public rows ordered by expiry", async () => { +test("listGrokResetCreditTokens returns public rows ordered by expiry", async (t) => { + t.mock.timers.enable({ apis: ["Date"], now: FIXTURE_NOW_MS }); const listed = await listGrokResetCreditTokens("fixture-access-token", async (url) => { assert.equal(String(url), LIST_URL); return listResponse([ @@ -135,19 +137,24 @@ test("consumeGrokResetCredit posts RedeemReset with protobuf field 10 and skips assert.equal(headers.Authorization, ["Bearer", "fixture-access-token"].join(" ")); }); -test("consumeGrokResetCredit picks the token that expires first when none is selected", async () => { +test("consumeGrokResetCredit picks the token that expires first when none is selected", async (t) => { + t.mock.timers.enable({ apis: ["Date"], now: FIXTURE_NOW_MS }); const calls: Array<{ url: string; body: Buffer | null }> = []; - const outcome = await consumeGrokResetCredit("fixture-access-token", {}, async (url, init = {}) => { - const body = init.body ? Buffer.from(init.body as Buffer) : null; - calls.push({ url: String(url), body }); - if (String(url) === LIST_URL) { - return listResponse([ - { id: "test-token-bb", expires: EXPIRES + 86400 }, - { id: "test-token-aa", expires: EXPIRES }, - ]); + const outcome = await consumeGrokResetCredit( + "fixture-access-token", + {}, + async (url, init = {}) => { + const body = init.body ? Buffer.from(init.body as Buffer) : null; + calls.push({ url: String(url), body }); + if (String(url) === LIST_URL) { + return listResponse([ + { id: "test-token-bb", expires: EXPIRES + 86400 }, + { id: "test-token-aa", expires: EXPIRES }, + ]); + } + return trailerResponse(0); } - return trailerResponse(0); - }); + ); assert.equal(outcome, "reset"); const redeem = calls.find((call) => call.url === REDEEM_URL); assert.ok(redeem?.body); @@ -159,17 +166,10 @@ test("consumeGrokResetCredit maps a missing selected token via RedeemReset grpc- const { GrokResetCreditError } = await import("../../open-sse/services/grokResetCredits.ts"); await assert.rejects( () => - consumeGrokResetCredit( - "fixture-access-token", - { tokenId: "missing-token" }, - async (url) => { - if (String(url) === LIST_URL) return listResponse([{ id: TOKEN_ID, expires: EXPIRES }]); - return trailerResponse( - 9, - "The token cannot be redeemed: it does not exist or is expired" - ); - } - ), + consumeGrokResetCredit("fixture-access-token", { tokenId: "missing-token" }, async (url) => { + if (String(url) === LIST_URL) return listResponse([{ id: TOKEN_ID, expires: EXPIRES }]); + return trailerResponse(9, "The token cannot be redeemed: it does not exist or is expired"); + }), (error: unknown) => error instanceof GrokResetCreditError && error.status === 409 && error.code === "no_credit" ); diff --git a/tests/unit/hard-session-lease-bypass-inventory.test.ts b/tests/unit/hard-session-lease-bypass-inventory.test.ts index 024972c2da..72475e7d1d 100644 --- a/tests/unit/hard-session-lease-bypass-inventory.test.ts +++ b/tests/unit/hard-session-lease-bypass-inventory.test.ts @@ -37,8 +37,9 @@ const EXPECTED: Record> = { "src/app/api/v1/classify/route.ts": 1, // v3.8.51 #11754: the second resolveImageRouteModel() call (a duplicate // of the retirement-check one hoisted before enforceApiKeyPolicy) was - // removed as dead redundant code, 6->5. - "src/app/api/v1/images/edits/route.ts": 5, + // removed as dead redundant code, 6->5. #12653 added combo target + // resolution with the same shape as imageCombo, 5->6. + "src/app/api/v1/images/edits/route.ts": 6, "src/app/api/v1/images/generations/route.ts": 3, "src/app/api/v1/images/upscale/route.ts": 1, "src/app/api/v1/messages/count_tokens/route.ts": 1, @@ -86,7 +87,7 @@ const EXPECTED: Record> = { }, connection: { "open-sse/handlers/autoComboCandidates.ts": 1, - "open-sse/handlers/chatCore.ts": 2, + "open-sse/handlers/chatCore.ts": 3, "open-sse/handlers/cursorCliProxy.ts": 1, "open-sse/services/alibabaFreeTier.ts": 1, "open-sse/services/alibabaFreeTierQuotaFetcher.ts": 1, @@ -161,7 +162,7 @@ const EXPECTED: Record> = { "src/lib/db/providers.ts": 3, "src/lib/db/readCache.ts": 2, "src/lib/freeProviderRankings.ts": 1, - "src/lib/guardrails/visionBridgeCredentials.ts": 1, + "src/lib/guardrails/visionBridgeCredentials.ts": 2, "src/lib/kimi/tokenRefresh.ts": 1, "src/lib/monitoring/providerHealthAutopilot.ts": 1, "src/lib/monitoring/providerHealthMatrix.ts": 1, diff --git a/tests/unit/pack-artifact-policy.test.ts b/tests/unit/pack-artifact-policy.test.ts index d6c26b6cfb..bd855302ac 100644 --- a/tests/unit/pack-artifact-policy.test.ts +++ b/tests/unit/pack-artifact-policy.test.ts @@ -1,6 +1,6 @@ import test from "node:test"; import assert from "node:assert/strict"; -import { readFileSync } from "node:fs"; +import { existsSync, readdirSync, readFileSync } from "node:fs"; import { APP_STAGING_ALLOWED_EXACT_PATHS, @@ -319,3 +319,18 @@ test("findMissingArtifactPaths flags missing root runtime files in the tarball", "src/shared/utils/nodeRuntimeSupport.ts", ]); }); + +test("every shipped @omniroute workspace package is covered by an artifact prefix", () => { + const packageJson = JSON.parse(readFileSync("package.json", "utf8")) as { files: string[] }; + assert.ok(packageJson.files.includes("@omniroute/")); + + const workspacePackages = readdirSync("@omniroute", { withFileTypes: true }) + .filter((entry) => entry.isDirectory() && existsSync(`@omniroute/${entry.name}/package.json`)) + .map((entry) => `@omniroute/${entry.name}/`); + + assert.ok(workspacePackages.includes("@omniroute/opencode-plugin-v2/")); + const uncovered = workspacePackages.filter( + (prefix) => !PACK_ARTIFACT_ALLOWED_PATH_PREFIXES.includes(prefix) + ); + assert.deepEqual(uncovered, []); +}); diff --git a/tests/unit/provider-node-reserved-prefix.test.ts b/tests/unit/provider-node-reserved-prefix.test.ts index feda7aca80..ae1f0b15a0 100644 --- a/tests/unit/provider-node-reserved-prefix.test.ts +++ b/tests/unit/provider-node-reserved-prefix.test.ts @@ -180,8 +180,9 @@ test("shared set size includes live REGISTRY and retired Designer + Felo + Qwen // 2026-09-02: a keyless provider was removed at its operator's request, taking its id and // alias out of the REGISTRY walk (408 → 406). // #11786: SeekAi adds id "seekai" + alias "ska" (406 → 408). - // #13024/#13025: GreenPT and EURouter each add one REGISTRY id (408 → 410). - assert.equal(RESERVED_PREFIX_COUNT, 410); + // #13024 (2b9e7fb3e) GreenPT and #13025 (22473dee5) EURouter each add one REGISTRY member (id == + // alias); #13277 (02128f334) registers Arcee AI, adding id "arcee-ai" + alias "arcee" (408 → 412). + assert.equal(RESERVED_PREFIX_COUNT, 412); }); test("isReservedProviderPrefix rejects non-string input", () => { diff --git a/tests/unit/providers-constants-split.test.ts b/tests/unit/providers-constants-split.test.ts index bfd37f9f54..4274d0acc4 100644 --- a/tests/unit/providers-constants-split.test.ts +++ b/tests/unit/providers-constants-split.test.ts @@ -34,12 +34,14 @@ // Perplexity Agent API (#12103) makes it 236; // UC Direct (#11513, uncensored.com metered Developer API) adds one frontier-labs entry — 237; // SeekAi (#11786, QuantumNous New-API gateway) adds one gateways entry — 238. -// GreenPT (#13024) and EURouter (#13025) each add one OpenAI-compatible gateway entry — 240. +// GreenPT (#13024, 2b9e7fb3e) and EURouter (#13025, 22473dee5) each add one gateways entry — 240. import { test } from "node:test"; import assert from "node:assert/strict"; const P = await import("../../src/shared/constants/providers.ts"); +const APIKEY_PROVIDER_COUNT = 240; + test("barrel still exports every catalog + key helpers", () => { for (const name of [ "NOAUTH_PROVIDERS", @@ -63,12 +65,12 @@ test("barrel still exports every catalog + key helpers", () => { } }); -test("APIKEY_PROVIDERS merges the 6 family files into 240 entries (no loss / no dup)", async () => { +test(`APIKEY_PROVIDERS merges the 6 family files into ${APIKEY_PROVIDER_COUNT} entries (no loss / no dup)`, async () => { const keys = Object.keys((P as Record).APIKEY_PROVIDERS); - assert.equal(keys.length, 240); - assert.equal(new Set(keys).size, 240, "duplicate keys after spread-merge"); + assert.equal(keys.length, APIKEY_PROVIDER_COUNT); + assert.equal(new Set(keys).size, APIKEY_PROVIDER_COUNT, "duplicate keys after spread-merge"); // the merged object's entry-count equals the sum of the 6 semantic family files; families are a - // strict partition (every provider in exactly one), so the sum must be exactly 240. + // strict partition (every provider in exactly one), so the sum must be exactly APIKEY_PROVIDER_COUNT. const families: [string, string][] = [ ["gateways", "APIKEY_PROVIDERS_GATEWAYS"], ["frontier-labs", "APIKEY_PROVIDERS_FRONTIER"], @@ -88,7 +90,11 @@ test("APIKEY_PROVIDERS merges the 6 family files into 240 entries (no loss / no seen.add(k); } } - assert.equal(famTotal, 240, "families must partition all 240 providers"); + assert.equal( + famTotal, + APIKEY_PROVIDER_COUNT, + `families must partition all ${APIKEY_PROVIDER_COUNT} providers` + ); }); test("AI_PROVIDERS Proxy aggregates all sections; lookups resolve", () => { diff --git a/tests/unit/resource-pressure-self-restart.test.ts b/tests/unit/resource-pressure-self-restart.test.ts index 210673fd0e..afe550d5d3 100644 --- a/tests/unit/resource-pressure-self-restart.test.ts +++ b/tests/unit/resource-pressure-self-restart.test.ts @@ -9,10 +9,7 @@ */ import assert from "node:assert/strict"; import { describe, it } from "node:test"; -import { - createResourcePressureRuntime, - type ResourcePressureRuntime, -} from "../../open-sse/utils/resourcePressure.ts"; +import { createResourcePressureRuntime } from "../../open-sse/utils/resourcePressure.ts"; import type { ResourceSignals } from "../../open-sse/utils/resourcePressurePolicy.ts"; const MiB = 1024 ** 2; diff --git a/tests/unit/sqljs-build-warning-8135.test.ts b/tests/unit/sqljs-build-warning-8135.test.ts index 95c8752403..f1f8e534cb 100644 --- a/tests/unit/sqljs-build-warning-8135.test.ts +++ b/tests/unit/sqljs-build-warning-8135.test.ts @@ -38,7 +38,8 @@ test("#8135: sqljsAdapter must not statically resolve sql.js at build time", () // The standalone assembler ships sql.js as a real runtime package, so the // adapter must not depend on a build-time createRequire/require.resolve lookup. - assert.doesNotMatch(source, /createRequire/); + assert.doesNotMatch(source, /createRequire\(\s*(?:import\.meta\.url|__filename)/); + assert.doesNotMatch(source, /^const\s+\w+\s*=\s*(?:\w+\.)?createRequire\s*\(/m); assert.doesNotMatch(source, /\.resolve\(["']sql\.js["']\)/); assert.match(source, /process\.cwd\(\)[\s\S]*"node_modules"[\s\S]*"sql\.js"/); }); diff --git a/tests/unit/strong-credential-token-linear-scan.test.ts b/tests/unit/strong-credential-token-linear-scan.test.ts new file mode 100644 index 0000000000..ff8d7f9309 --- /dev/null +++ b/tests/unit/strong-credential-token-linear-scan.test.ts @@ -0,0 +1,42 @@ +import { describe, it } from "node:test"; +import assert from "node:assert/strict"; + +import { + containsStrongCredentialToken, + redactSensitiveErrorText, +} from "../../open-sse/utils/errorSanitization.ts"; + +function elapsedMs(run: () => void): number { + const start = process.hrtime.bigint(); + run(); + return Number(process.hrtime.bigint() - start) / 1e6; +} + +describe("strong credential token scan", () => { + it("containsStrongCredentialToken_LongAlphanumericRunWithoutKey_ScansInLinearTime", () => { + const run = "a".repeat(200_000); + + let found = true; + const ms = elapsedMs(() => { + found = containsStrongCredentialToken(run); + }); + + assert.equal(found, false); + assert.ok(ms < 500, `scan took ${ms.toFixed(1)}ms for a 200k alphanumeric run`); + }); + + it("containsStrongCredentialToken_KeyGluedToAlphanumericPrefix_IsDetected", () => { + assert.equal(containsStrongCredentialToken("projsk-AbCdEfGhIjKl"), true); + assert.equal(containsStrongCredentialToken("(abcsk_AbCdEfGh12)"), true); + }); + + it("redactSensitiveErrorText_KeyGluedToAlphanumericPrefix_RedactsPrefixAndKey", () => { + const out = redactSensitiveErrorText("rejected xyzsk-AbCdEfGhIjKlMn for this project"); + + assert.equal(out, "rejected [REDACTED] for this project"); + }); + + it("containsStrongCredentialToken_PrefixShorterThanThreeCharacters_IsNotAGluedKey", () => { + assert.equal(containsStrongCredentialToken("ab-xsk-AbCdEfGhIjKl"), false); + }); +}); diff --git a/tests/unit/universal-quota-aware-routing.test.ts b/tests/unit/universal-quota-aware-routing.test.ts index 6599ef132c..8848397c94 100644 --- a/tests/unit/universal-quota-aware-routing.test.ts +++ b/tests/unit/universal-quota-aware-routing.test.ts @@ -144,8 +144,19 @@ test("scoreResetAwareQuota ranks lower-used Antigravity quota higher and avoids test("orderTargetsByResetAwareQuota prefers Antigravity connection with more remaining quota", async () => { registerGenericQuotaFetchers(); - const low = `low-${randomUUID()}`; - const high = `high-${randomUUID()}`; + const { createProviderConnection } = await import("../../src/lib/db/providers.ts"); + const [low, high] = await Promise.all( + ["low", "high"].map(async (label) => { + const connection = await createProviderConnection({ + provider: "antigravity", + authType: "oauth", + name: `${label}-${randomUUID()}`, + isActive: true, + testStatus: "active", + }); + return String(connection.id); + }) + ); const resetAt5h = new Date(Date.now() + 24 * 3600 * 1000).toISOString(); const resetAt7d = new Date(Date.now() + 5 * 24 * 3600 * 1000).toISOString(); diff --git a/tests/unit/upstream-status-restatement.test.ts b/tests/unit/upstream-status-restatement.test.ts index 7c59bc8b15..ceccfda6b8 100644 --- a/tests/unit/upstream-status-restatement.test.ts +++ b/tests/unit/upstream-status-restatement.test.ts @@ -10,9 +10,8 @@ import assert from "node:assert/strict"; * same defect register one rule array — no pipeline changes. */ -const { applyStatusRestatement, statusRestatementRegistry } = await import( - "../../open-sse/config/upstreamStatusRestatement.ts" -); +const { applyStatusRestatement, statusRestatementRegistry } = + await import("../../open-sse/config/upstreamStatusRestatement.ts"); test("R1: agentrouter 403 + 用户额度不足 → 429 with synthetic Retry-After", () => { const out = applyStatusRestatement({ @@ -119,8 +118,50 @@ test("R10: chatCore wires applyStatusRestatement into the providerFailure block" "utf8" ); assert.match(src, /applyStatusRestatement\(/, "chatCore must call applyStatusRestatement"); - const hookIndex = src.indexOf("applyStatusRestatement("); + + const helperIndex = src.indexOf("const applyProviderFailureClassification = async ("); + const blockIndex = src.indexOf("providerFailure: if (!providerResponse.ok)"); + assert.ok(helperIndex > -1 && blockIndex > helperIndex, "classification helper and block exist"); + const classifyCalls = src.match(/classifyProviderError\(/g) ?? []; + assert.equal(classifyCalls.length, 1, "chatCore classifies provider errors in exactly one place"); const classifyIndex = src.indexOf("classifyProviderError(statusCode"); - assert.ok(hookIndex > -1 && classifyIndex > -1 && hookIndex < classifyIndex, - "restatement must run BEFORE classifyProviderError so fallback sees the corrected status"); + assert.ok( + classifyIndex > helperIndex && classifyIndex < blockIndex, + "classifyProviderError must live inside applyProviderFailureClassification" + ); + + const block = src.slice(blockIndex); + const hookIndex = block.indexOf("applyStatusRestatement("); + const statusReassign = block.indexOf("statusCode = restatement.status;"); + const retryReassign = block.indexOf("retryAfterMs = restatement.retryAfterMs;"); + const classifyCallIndex = block.indexOf("await applyProviderFailureClassification("); + assert.ok( + hookIndex > -1 && + statusReassign > hookIndex && + retryReassign > hookIndex && + classifyCallIndex > statusReassign && + classifyCallIndex > retryReassign, + "restatement must reassign statusCode and retryAfterMs BEFORE the providerFailure block classifies" + ); +}); + +test("R11: the shared provider execution pipeline restates before building the error result", async () => { + const { readFile } = await import("node:fs/promises"); + const src = await readFile( + new URL("../../open-sse/handlers/chatCore/providerExecutionPipeline.ts", import.meta.url), + "utf8" + ); + const outcomeIndex = src.indexOf("async function toOutcome("); + assert.ok(outcomeIndex > -1, "toOutcome exists"); + const body = src.slice(outcomeIndex); + const hookIndex = body.indexOf("applyStatusRestatement("); + const resultIndex = body.search(/createErrorResult\(\s*restatement\.status,/); + assert.ok( + hookIndex > -1 && resultIndex > hookIndex, + "the non-streaming leg must surface the restated status to classification and the client" + ); + assert.match( + body, + /createErrorResult\(\s*restatement\.status,\s*message,\s*restatement\.retryAfterMs/ + ); }); diff --git a/tests/unit/volcengine-plan-binding-upsert.test.ts b/tests/unit/volcengine-plan-binding-upsert.test.ts index 51a3ca840e..931566fd89 100644 --- a/tests/unit/volcengine-plan-binding-upsert.test.ts +++ b/tests/unit/volcengine-plan-binding-upsert.test.ts @@ -4,11 +4,11 @@ import fs from "node:fs"; import path from "node:path"; import os from "node:os"; import { resetDbInstance } from "../../src/lib/db/core.ts"; +import { createProviderConnection, getProviderConnectionById } from "../../src/models/index.ts"; import { - createProviderConnection, - getProviderConnectionById, -} from "../../src/models/index.ts"; -import { detectPlan, __testing as bindingTesting } from "../../src/lib/providers/volcenginePlanBinding.ts"; + detectPlan, + __testing as bindingTesting, +} from "../../src/lib/providers/volcenginePlanBinding.ts"; test("detectPlan returns available: false when account has no active quota windows (unsubscribed)", async (t) => { const originalFetch = globalThis.fetch; @@ -67,7 +67,7 @@ test("volcenginePlanBinding upsert rules with SQLite temp isolation", async (t) providerSpecificData: { autoFetchModels: true, customTag: "keep-me" }, }); - const updated1 = await (bindingTesting as any).upsertConnection( + const updated1 = await bindingTesting.upsertConnection( "coding", "ark-new-key-1", "new-cookie-1", @@ -80,8 +80,16 @@ test("volcenginePlanBinding upsert rules with SQLite temp isolation", async (t) assert.equal(updated1.id, conn1.id); assert.equal(updated1.name, "main", "Preserves original custom name 'main'"); assert.equal(updated1.apiKey, "ark-new-key-1"); - assert.equal(updated1.providerSpecificData.autoFetchModels, true, "Preserves existing PSD autoFetchModels"); - assert.equal(updated1.providerSpecificData.customTag, "keep-me", "Preserves existing PSD customTag"); + assert.equal( + updated1.providerSpecificData.autoFetchModels, + true, + "Preserves existing PSD autoFetchModels" + ); + assert.equal( + updated1.providerSpecificData.customTag, + "keep-me", + "Preserves existing PSD customTag" + ); assert.equal(updated1.providerSpecificData.volcConsoleCookie, "new-cookie-1"); assert.equal(updated1.providerSpecificData.volcApiKeyId, 123); @@ -93,7 +101,7 @@ test("volcenginePlanBinding upsert rules with SQLite temp isolation", async (t) providerSpecificData: {}, }); - const createdNew = await (bindingTesting as any).upsertConnection( + const createdNew = await bindingTesting.upsertConnection( "coding", "ark-brand-new-key-3", "new-cookie-3", @@ -117,7 +125,7 @@ test("volcenginePlanBinding upsert rules with SQLite temp isolation", async (t) }); // Passing conn1.id (which is coding-plan) into agent upsert must NOT match conn1 - const agentUpsertResult = await (bindingTesting as any).upsertConnection( + const agentUpsertResult = await bindingTesting.upsertConnection( "agent", "ark-agent-new-key", "agent-cookie", @@ -168,7 +176,8 @@ test("findTargetConnection pure matching logic and single-connection adoption in { id: "c2", provider: "volcengine-coding-plan", providerSpecificData: { volcApiKeyId: 888 } }, ]; assert.equal( - bindingTesting.findTargetConnection(list3, { ...criteria, apiKey: undefined, apiKeyId: 888 })?.id, + bindingTesting.findTargetConnection(list3, { ...criteria, apiKey: undefined, apiKeyId: 888 }) + ?.id, "c2" ); diff --git a/tests/unit/webpack-create-require-warning.test.ts b/tests/unit/webpack-create-require-warning.test.ts index 9c375c52bd..d6fba2e315 100644 --- a/tests/unit/webpack-create-require-warning.test.ts +++ b/tests/unit/webpack-create-require-warning.test.ts @@ -79,6 +79,7 @@ async function compileRuntimeRequireModules(): Promise { // has no repo tree, so treat the sibling as external instead of // erroring "Can't resolve './obscura.ts'". "./obscura.ts", + "./tlsFirstByteWatchdog.ts", ], externalsPresets: { node: true }, mode: "development",