From 6cbcba7edeec6620ac15b4d3ec12831d1a65e8e9 Mon Sep 17 00:00:00 2001 From: b3nw Date: Sat, 29 Aug 2026 03:46:28 +0000 Subject: [PATCH 01/91] feat(dashboard): show cache percentage in request logs --- .../features/dashboard-cache-percentage.md | 1 + src/shared/components/RequestLoggerV2.tsx | 12 ++++++- .../request-logger-cache-percentage.test.ts | 33 +++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 changelog.d/features/dashboard-cache-percentage.md create mode 100644 tests/unit/request-logger-cache-percentage.test.ts diff --git a/changelog.d/features/dashboard-cache-percentage.md b/changelog.d/features/dashboard-cache-percentage.md new file mode 100644 index 000000000000..784cc34f8e46 --- /dev/null +++ b/changelog.d/features/dashboard-cache-percentage.md @@ -0,0 +1 @@ +- **feat(dashboard):** display clamped `[0, 100]%` cached input token ratio in request logs table ([#PR_NUMBER](https://github.com/diegosouzapw/OmniRoute/pull/PR_NUMBER)) diff --git a/src/shared/components/RequestLoggerV2.tsx b/src/shared/components/RequestLoggerV2.tsx index 699bf1f36d56..22fb40b85add 100644 --- a/src/shared/components/RequestLoggerV2.tsx +++ b/src/shared/components/RequestLoggerV2.tsx @@ -71,6 +71,15 @@ function formatTps(tps: number): string { return tps.toFixed(1); } +export function formatCachePercentage( + tokensIn: number | null | undefined, + cacheRead: number | null | undefined +): number { + if (!tokensIn || tokensIn <= 0) return 0; + if (!cacheRead || cacheRead <= 0) return 0; + return Math.min(100, Math.round((cacheRead / tokensIn) * 100)); +} + function getCacheSourceMeta(cacheSource: unknown) { if (cacheSource === "semantic") { return { @@ -1572,7 +1581,8 @@ const RequestLoggerV2 = forwardRef - {log.tokens.cacheRead.toLocaleString()} + {log.tokens.cacheRead.toLocaleString()} ( + {formatCachePercentage(log.tokens.in, log.tokens.cacheRead)}%) )} diff --git a/tests/unit/request-logger-cache-percentage.test.ts b/tests/unit/request-logger-cache-percentage.test.ts new file mode 100644 index 000000000000..b118a04c0eda --- /dev/null +++ b/tests/unit/request-logger-cache-percentage.test.ts @@ -0,0 +1,33 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { formatCachePercentage } from "../../src/shared/components/RequestLoggerV2"; + +test("formatCachePercentage", async (t) => { + await t.test("returns 0 when tokensIn is 0, null, or undefined", () => { + assert.strictEqual(formatCachePercentage(0, 100), 0); + assert.strictEqual(formatCachePercentage(null, 100), 0); + assert.strictEqual(formatCachePercentage(undefined, 100), 0); + }); + + await t.test("returns 0 when cacheRead is 0, null, or undefined", () => { + assert.strictEqual(formatCachePercentage(100, 0), 0); + assert.strictEqual(formatCachePercentage(100, null), 0); + assert.strictEqual(formatCachePercentage(100, undefined), 0); + }); + + await t.test("returns 0 for negative values", () => { + assert.strictEqual(formatCachePercentage(-100, 50), 0); + assert.strictEqual(formatCachePercentage(100, -50), 0); + }); + + await t.test("calculates standard percentages correctly", () => { + assert.strictEqual(formatCachePercentage(100, 50), 50); + assert.strictEqual(formatCachePercentage(1000, 250), 25); + assert.strictEqual(formatCachePercentage(3, 1), 33); + }); + + await t.test("clamps percentage at 100 when cacheRead > tokensIn", () => { + assert.strictEqual(formatCachePercentage(100, 150), 100); + assert.strictEqual(formatCachePercentage(50, 1000), 100); + }); +}); From 3d4f3e496080105da550bd43239c0f6a218b1077 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sat, 29 Aug 2026 01:17:40 -0300 Subject: [PATCH 02/91] test(infra): retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) (#11968) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test(infra): retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) Two shards on release/v3.8.51 went red in one day with the same signature — "ENOTEMPTY, Directory not empty: /tmp/omniroute--XXXXXX" — from combo-same-provider-cascade (Unit Tests fast-path 4/4, on a PR that touches only .github/) and auth-policy-embeddings-webfetch-7785 (the 20k-test TIA step). Both pass alone and on re-run: the cleanup races something still writing into the directory (SQLite WAL/-shm checkpoint, a worker, the backup) and under a loaded hosted runner the window opens. 1154 test files do their own cleanup with fs.rmSync(dir, { recursive: true, force: true }); 57 already asked for retries. One-shot codemod (scripts/ad-hoc/codemod-rm-maxretries.mjs, kept for the record): every rm / rmSync / rmdirSync option object with `recursive: true` and no `maxRetries` gains `maxRetries: 5, retryDelay: 100` — Node itself then retries ENOTEMPTY/EBUSY/EPERM for up to ~0.5 s before giving up. 2243 call sites in 1292 files under tests/, the shared tests/_setup/isolateDataDir.ts exit hook included. Only the option object changes: no call site, assertion or import is touched. Validation: prettier and ESLint (with the frozen suppressions) clean on all 1292 files; a random 20-file sample runs green (quota-redis-store hangs identically on the untouched tree — it needs a Redis on localhost, an environment matter). The four unit shards on this PR are the full run. * fix(quality): let check-forgotten-sibling-tests read a 1,000-file diff The gate shells out to `git diff` through execFileSync with Node's default 1 MB maxBuffer; the 1,292-file codemod in this PR is the first diff large enough to overflow it, and the gate died with `spawnSync git ENOBUFS` before comparing anything. 64 MB is far above any real PR and costs nothing when unused. --- scripts/ad-hoc/codemod-rm-maxretries.mjs | 101 +++++ .../check/check-forgotten-sibling-tests.mjs | 2 +- tests/_setup/isolateDataDir.ts | 2 +- tests/e2e/system-failover.test.ts | 2 +- tests/integration/_chatPipelineHarness.ts | 4 +- .../agent-bridge-bypass-flow.test.ts | 33 +- .../agent-bridge-cert-flow.test.ts | 21 +- .../integration/agent-bridge-mappings.test.ts | 58 +-- tests/integration/agent-bridge-routes.test.ts | 4 +- tests/integration/all-statuses-route.test.ts | 6 +- ...y-projectid-discovery-persist-8491.test.ts | 13 +- tests/integration/api-keys.test.ts | 4 +- tests/integration/api-routes-critical.test.ts | 4 +- .../audit-log-level-filter.test.ts | 4 +- .../integration/batch-e2e-rate-limit.test.ts | 2 +- tests/integration/chat-pipeline.test.ts | 4 +- .../chatcore-compression-integration.test.ts | 4 +- .../chatcore-context-window-boundary.test.ts | 2 +- .../cli-settings-codewhale.test.ts | 15 +- .../cli-settings-deepseek-tui.test.ts | 29 +- tests/integration/cli-settings-forge.test.ts | 24 +- .../cli-settings-grok-build.test.ts | 16 +- tests/integration/cli-settings-jcode.test.ts | 8 +- tests/integration/cli-settings-letta.test.ts | 10 +- tests/integration/cli-settings-omp.test.ts | 6 +- tests/integration/cli-settings-pi.test.ts | 22 +- tests/integration/cli-settings-smelt.test.ts | 22 +- .../codex-account-pool-restart-http.test.ts | 2 +- .../codex-chat-reasoning-http-e2e.test.ts | 2 +- tests/integration/combo-live/_liveHarness.ts | 47 +- .../integration/fingerprint-expansion.test.ts | 2 +- tests/integration/heap-growth.test.ts | 2 +- tests/integration/llama-cpp-provider.test.ts | 25 +- .../memory-embedding-providers.test.ts | 9 +- .../integration/memory-engine-status.test.ts | 16 +- tests/integration/memory-reindex.test.ts | 12 +- .../memory-retrieve-preview.test.ts | 16 +- tests/integration/memory-route-put.test.ts | 4 +- tests/integration/memory-summarize.test.ts | 14 +- .../model-catalog-responsiveness-9199.test.ts | 4 +- .../opencode-config-startup.test.ts | 2 +- .../performance-regression.test.ts | 2 +- .../playground-improve-prompt.test.ts | 40 +- .../playground-presets-crud.test.ts | 38 +- .../playground-presets-zod.test.ts | 41 +- tests/integration/plugins-lifecycle.test.ts | 42 +- .../provider-journey.contract.test.ts | 4 +- tests/integration/proxy-registry-flow.test.ts | 4 +- tests/integration/qdrant-routes.test.ts | 4 +- tests/integration/quota-plans-crud.test.ts | 28 +- .../quota-pool-delete-combo-cleanup.test.ts | 4 +- ...ota-pool-usage-provider-resolution.test.ts | 4 +- tests/integration/quota-pools-crud.test.ts | 15 +- tests/integration/quota-pools-usage.test.ts | 10 +- tests/integration/quota-preview.test.ts | 16 +- .../quota-routes-error-sanitization.test.ts | 61 +-- .../integration/quota-store-settings.test.ts | 56 +-- tests/integration/resilience-http-e2e.test.ts | 2 +- .../search-providers-catalog.test.ts | 4 +- .../test-model-compression-off-6240.test.ts | 4 +- .../traffic-inspector-capture-modes.test.ts | 90 ++-- ...affic-inspector-error-sanitization.test.ts | 111 ++--- .../traffic-inspector-hosts.test.ts | 32 +- .../traffic-inspector-internal-ingest.test.ts | 41 +- .../traffic-inspector-localonly.test.ts | 6 +- .../traffic-inspector-requests.test.ts | 66 ++- ...traffic-inspector-session-requests.test.ts | 20 +- .../traffic-inspector-sessions.test.ts | 68 ++- .../integration/traffic-inspector-ws.test.ts | 10 +- .../v1-models-swr-response-flush-8728.test.ts | 6 +- .../video-bridge-sampler-ffmpeg.test.ts | 4 +- ...-control-lines-leak-openai-clients.test.ts | 16 +- ...patible-generic-vs-uuid-credential.test.ts | 4 +- ...10197-openrouter-image-edits-route.test.ts | 12 +- .../10313-catalog-cache-key-hashing.test.ts | 26 +- tests/unit/10347-embed-402-cooldown.test.ts | 16 +- tests/unit/7993-noauth-proxy-routing.test.ts | 2 +- .../8200-perplexity-web-401-cooldown.test.ts | 4 +- tests/unit/8326-compatible-id-regex.test.ts | 12 +- .../unit/8327-models-owned-by-prefix.test.ts | 9 +- tests/unit/8332-combo-vision-fallback.test.ts | 8 +- tests/unit/8336-audit-loopback-login.test.ts | 4 +- .../unit/8374-plugins-status-optional.test.ts | 2 +- .../8385-perkey-proxy-global-toggle.test.ts | 4 +- .../8388-compression-detail-persist.test.ts | 12 +- tests/unit/8395-plugin-hooks-fire.test.ts | 37 +- .../8431-multiwindow-quota-eviction.test.ts | 11 +- ...8488-capability-filter-fail-closed.test.ts | 4 +- .../8510-adobe-firefly-edits-route.test.ts | 19 +- .../8779-agy-prefix-credential-lookup.test.ts | 4 +- .../8958-alias-backed-node-prefix.test.ts | 4 +- .../9034-alias-backed-prefix-id-repro.test.ts | 9 +- .../9134-repro-audio-combo-rejection.test.ts | 7 +- .../unit/9147-catalog-eventloop-yield.test.ts | 4 +- tests/unit/9201-search-proxy-bypass.test.ts | 2 +- ...-purge-proxy-assignments-on-delete.test.ts | 5 +- tests/unit/_mocks/settings.ts | 4 +- tests/unit/a2a-auth-timing-safe.test.ts | 4 +- tests/unit/a2a-enabled-route.test.ts | 4 +- tests/unit/a2a-route-require-api-key.test.ts | 2 +- tests/unit/a2a-task-owner-idor.test.ts | 2 +- tests/unit/a2a-v1-compat-10839.test.ts | 4 +- tests/unit/access-tokens-db.test.ts | 2 +- tests/unit/account-concurrency-cap.test.ts | 4 +- tests/unit/account-fallback-service.test.ts | 4 +- tests/unit/acp-agents-route.test.ts | 4 +- ...ve-request-stream-chunks-lifecycle.test.ts | 2 +- tests/unit/admin-audit-events.test.ts | 4 +- ...bridge-cert-regenerate-force-10467.test.ts | 2 +- .../agent-bridge-config-portability.test.ts | 13 +- .../agent-bridge-mappings-sync-8656.test.ts | 4 +- ...bridge-server-route-dynamic-import.test.ts | 4 +- ...ent-bridge-state-full-payload-8656.test.ts | 4 +- .../agentSkills-cliRegistryParser.test.ts | 4 +- tests/unit/agentSkills-generator.test.ts | 2 +- tests/unit/agentSkills-openapiParser.test.ts | 4 +- tests/unit/agentSkills-routes.test.ts | 4 +- ...bridge-antigravity-cert-hosts-6494.test.ts | 7 +- .../agentbridge-mitm-router-key-6403.test.ts | 9 +- .../agentrouter-chatcore-protocols.test.ts | 4 +- .../unit/agentrouter-lock-scope-10334.test.ts | 9 +- tests/unit/agnes-provider.test.ts | 2 +- tests/unit/aihorde-optional-api-key.test.ts | 2 +- .../airforce-v1-double-prefix-5899.test.ts | 2 +- .../unit/alibaba-free-tier-allowlist.test.ts | 7 +- .../antigravity-429-quota-cooldown.test.ts | 2 +- .../antigravity-client-identity-paths.test.ts | 2 +- ...igravity-local-usage-fallback-3821.test.ts | 2 +- ...ravity-missing-project-autodisable.test.ts | 8 +- .../antigravity-mitm-model-resolution.test.ts | 2 +- .../antigravity-project-persistence.test.ts | 2 +- .../unit/antigravity-quota-host-8965.test.ts | 8 +- tests/unit/antigravity-quota-skipping.test.ts | 2 +- .../antigravity-weekly-quota-4017.test.ts | 2 +- tests/unit/api-auth.test.ts | 4 +- .../api-key-compression-enabled-2101.test.ts | 4 +- tests/unit/api-key-lifecycle.test.ts | 4 +- ...-policy-noauth-allowed-connections.test.ts | 2 +- tests/unit/api-key-policy.test.ts | 4 +- tests/unit/api-key-regeneration.test.ts | 4 +- tests/unit/api-key-reveal-route.test.ts | 4 +- tests/unit/api-key-usage-limits.test.ts | 4 +- .../unit/api-keys-create-no-hang-6570.test.ts | 4 +- tests/unit/api-malformed-json-400.test.ts | 2 +- .../api-manager-provider-permissions.test.ts | 6 +- tests/unit/api-models-hide-paid-6328.test.ts | 2 +- ...pi-models-v1-models-mismatch-10615.test.ts | 2 +- .../auto-combo-candidates-route-7819.test.ts | 11 +- .../cli-tools/apply-container-guard.test.ts | 4 +- tests/unit/api/cli-tools/detect.test.ts | 2 +- .../api/compression-engines-route.test.ts | 4 +- ...n-preview-caveman-and-stacked-6425.test.ts | 42 +- .../api/compression-preview-engine.test.ts | 4 +- .../api/compression/compression-api.test.ts | 5 +- .../rtk-learn-discover-routes.test.ts | 9 +- .../compression/rtk-toml-import-route.test.ts | 4 +- .../context-analytics-engine-route.test.ts | 4 +- .../api/context-combos-default-route.test.ts | 9 +- tests/unit/api/discovery-routes.test.ts | 21 +- .../unit/api/free-proxies-list-route.test.ts | 4 +- tests/unit/api/free-proxies-route.test.ts | 4 +- tests/unit/api/jobs.test.ts | 4 +- .../api/providers-import-route-6836.test.ts | 59 ++- tests/unit/api/proxies-repair-relay.test.ts | 4 +- .../unit/api/services/9router-models.test.ts | 2 +- .../services/9router-provider-expose.test.ts | 2 +- .../services/9router-status-reveal.test.ts | 2 +- .../api/services/cliproxy-accounts.test.ts | 6 +- .../services/cliproxy-provider-expose.test.ts | 2 +- .../webhooks/webhook-url-ssrf-guard.test.ts | 2 +- .../apikey-connection-health-check.test.ts | 4 +- .../apikey-policy-default-rate-limits.test.ts | 7 +- .../apikeypolicy-disable-non-public.test.ts | 4 +- tests/unit/apikeypolicy-quota-only.test.ts | 30 +- tests/unit/apikeys-allowed-quotas.test.ts | 4 +- tests/unit/apikeys-disable-non-public.test.ts | 9 +- tests/unit/apikeys-usage-command.test.ts | 4 +- ...tandalone-onnxruntime-native-asset.test.ts | 2 +- ...empt-logging-early-keepalive-merge.test.ts | 2 +- ...io-transcriptions-combo-resolution.test.ts | 2 +- tests/unit/auggie-executor.test.ts | 2 +- .../auth-anonymous-fallback-toggle.test.ts | 2 +- .../auth-antigravity-account-retry-v2.test.ts | 11 +- tests/unit/auth-clear-account-error.test.ts | 4 +- tests/unit/auth-clear-provider-routes.test.ts | 4 +- tests/unit/auth-disable-cooling-2997.test.ts | 4 +- tests/unit/auth-login-route.test.ts | 4 +- .../auth-noauth-fallback-loop-3061.test.ts | 3 +- ...th-ollama-cloud-per-model-403-3027.test.ts | 4 +- .../auth-opencode-zen-noauth-fallback.test.ts | 2 +- ...th-policy-embeddings-webfetch-7785.test.ts | 2 +- tests/unit/auth-terminal-status.test.ts | 4 +- .../authz/client-api-policy-fallback.test.ts | 10 +- tests/unit/authz/client-api-policy.test.ts | 4 +- .../authz/ip-filter-enforcement-6131.test.ts | 4 +- tests/unit/authz/management-policy.test.ts | 4 +- tests/unit/authz/pipeline.test.ts | 4 +- tests/unit/authz/probe-9033-repro.test.ts | 4 +- .../auto-candidate-overrides-7819.test.ts | 4 +- ...andidate-overrides-regression-7819.test.ts | 4 +- .../auto-combo-context-advertising.test.ts | 2 +- ...auto-combo-credentialed-model-pool.test.ts | 4 +- .../auto-combo-hidden-models-4558.test.ts | 2 +- tests/unit/auto-combos-enhanced-4235.test.ts | 4 +- .../auto-combos-free-models-routes.test.ts | 6 +- tests/unit/auto-combos-suffixes-4235.test.ts | 4 +- tests/unit/auto-custom-provider-5873.test.ts | 4 +- .../auto-empty-pool-fastfail-6458.test.ts | 6 +- ...auto-keyless-custom-provider-11180.test.ts | 4 +- tests/unit/auto-routing-analytics-db.test.ts | 2 +- tests/unit/auto-update.test.ts | 4 +- .../autoCombo/provider-family-combos.test.ts | 4 +- tests/unit/bai-provider.test.ts | 4 +- tests/unit/batch-file-download.test.ts | 4 +- tests/unit/batch-processor.test.ts | 4 +- .../bedrock-image-log-redaction-7297.test.ts | 7 +- tests/unit/binaryManager.test.ts | 14 +- tests/unit/bootstrap-env.test.ts | 2 +- ...204-agy-provider-alias-credentials.test.ts | 8 +- .../bug-9204-agy-reimport-reactivates.test.ts | 13 +- ...ld-next-isolated-windows-home-2402.test.ts | 9 +- tests/unit/build-next-isolated.test.ts | 3 +- tests/unit/build-sha-provenance-10427.test.ts | 2 +- tests/unit/build/assemble-standalone.test.ts | 12 +- .../build/build-tool-runner-win-shim.test.ts | 8 +- tests/unit/build/check-bundle-size.test.ts | 2 +- tests/unit/build/check-lockfile.test.ts | 2 +- tests/unit/build/check-secrets.test.ts | 2 +- .../unit/build/check-test-runner-api.test.ts | 6 +- tests/unit/build/check-vuln-ratchet.test.ts | 10 +- tests/unit/build/check-workflows.test.ts | 10 +- .../colocate-standalone-esm-scope.test.ts | 10 +- .../build/mcp-bundle-no-eager-ioredis.test.ts | 4 +- tests/unit/build/mcp-bundle-startup.test.ts | 2 +- .../build/mitm-server-bundle-contents.test.ts | 4 +- .../build/optional-pack-installer.test.ts | 2 +- ...empty-external-package-dirs-nested.test.ts | 17 +- .../build/should-promote-latest-5301.test.ts | 2 +- tests/unit/build/standalone-bundle.test.ts | 28 +- tests/unit/build/sync-changelog-i18n.test.ts | 4 +- .../bulk-add-keys-no-overwrite-2587.test.ts | 8 +- tests/unit/cache-config-route-8219.test.ts | 2 +- tests/unit/call-log-artifact-worker.test.ts | 4 +- tests/unit/call-log-cap.test.ts | 4 +- tests/unit/call-log-file-rotation.test.ts | 11 +- .../unit/call-log-oom-unbounded-5618.test.ts | 4 +- tests/unit/call-log-provider-display.test.ts | 6 +- tests/unit/call-log-save-drain.test.ts | 2 +- tests/unit/call-log-startup.test.ts | 2 +- tests/unit/call-log-stream-debug.test.ts | 4 +- .../unit/call-log-trim-sql-vars-5217.test.ts | 4 +- .../call-logs-correlation-substring.test.ts | 2 +- .../call-logs-exclude-tests-allowlist.test.ts | 4 +- tests/unit/call-logs-pagination.test.ts | 4 +- tests/unit/call-logs-requested-model.test.ts | 4 +- tests/unit/call-logs-session-tag.test.ts | 2 +- tests/unit/capture-critical-db-state.test.ts | 2 +- ...atalog-auto-routing-disabled-10831.test.ts | 2 +- tests/unit/catalog-hide-auto-no-think.test.ts | 44 +- tests/unit/catalog-order-contract.test.ts | 24 +- .../unit/cc-compatible-model-catalog.test.ts | 4 +- tests/unit/cc-compatible-provider.test.ts | 4 +- tests/unit/cc-discovery-alias-api.test.ts | 4 +- tests/unit/cc-discovery-aliases-gate.test.ts | 6 +- tests/unit/cc-discovery-metrics.test.ts | 2 +- tests/unit/changelog-fragments.test.ts | 25 +- tests/unit/chaos-api-routes.test.ts | 25 +- tests/unit/chaos-config.test.ts | 4 +- tests/unit/chaos-executor.test.ts | 4 +- tests/unit/chat-combo-live-test.test.ts | 4 +- .../chat-completions-parse-once-7847.test.ts | 2 +- .../chat-completions-route-shape-gate.test.ts | 4 +- tests/unit/chat-core-intercept-fetch.test.ts | 14 +- tests/unit/chat-helpers.test.ts | 4 +- ...hat-routing-synced-inventory-11089.test.ts | 4 +- tests/unit/chat-safetynet-reqid-6097.test.ts | 153 ++++--- tests/unit/chatcore-attempt-logging.test.ts | 2 +- .../chatcore-caveman-output-analytics.test.ts | 7 +- .../unit/chatcore-codex-account-pool.test.ts | 4 +- .../chatcore-combo-context-limit-8378.test.ts | 9 +- ...core-combo-context-override-rescue.test.ts | 2 +- ...atcore-compression-analytics-write.test.ts | 2 +- .../chatcore-compression-cache-stats.test.ts | 7 +- .../chatcore-compression-settings.test.ts | 2 +- ...chatcore-compression-usage-receipt.test.ts | 21 +- ...chatcore-context-editing-telemetry.test.ts | 7 +- tests/unit/chatcore-executor-proxy.test.ts | 2 +- .../unit/chatcore-gamification-event.test.ts | 7 +- .../chatcore-memory-skills-injection.test.ts | 8 +- .../chatcore-model-output-cap-wiring.test.ts | 2 +- ...chatcore-non-streaming-usage-stats.test.ts | 7 +- .../chatcore-output-style-telemetry.test.ts | 14 +- .../chatcore-quota-share-consumption.test.ts | 7 +- ...atcore-reasoning-cache-write-guard.test.ts | 2 +- tests/unit/chatcore-sanitization.test.ts | 2 +- tests/unit/chatcore-semantic-cache.test.ts | 2 +- .../chatcore-streaming-quota-share.test.ts | 7 +- .../chatcore-streaming-usage-stats.test.ts | 7 +- tests/unit/chatcore-telemetry-helpers.test.ts | 2 +- tests/unit/chatcore-translation-paths.test.ts | 4 +- tests/unit/chatcore-upstream-body.test.ts | 2 +- tests/unit/chatgpt-web-codex.test.ts | 4 +- .../chatgpt-web-management-retirement.test.ts | 4 +- tests/unit/chatgpt-web-runtime-block.test.ts | 4 +- tests/unit/check-changelog-integrity.test.ts | 20 +- tests/unit/check-fabricated-docs.test.ts | 2 +- .../check-openapi-breaking-ratchet.test.ts | 2 +- .../check-provider-asset-provenance.test.ts | 32 +- tests/unit/claude-classifier-compat.test.ts | 2 +- .../unit/claude-code-rendering-fixes.test.ts | 2 +- ...aude-directive-midconv-passthrough.test.ts | 8 +- .../claude-empty-stream-error-3685.test.ts | 7 +- ...AuthImport-bootstrap-headers-10144.test.ts | 9 +- .../unit/cli-api-generator-ref-params.test.ts | 14 +- tests/unit/cli-auth-export-command.test.ts | 2 +- tests/unit/cli-backup-command.test.ts | 2 +- .../cli-combo-create-models-10954.test.ts | 4 +- tests/unit/cli-contexts.test.ts | 2 +- tests/unit/cli-data-dir-env-loading.test.ts | 4 +- tests/unit/cli-data-dir-env.test.ts | 7 +- tests/unit/cli-doctor-command.test.ts | 2 +- ...octor-prebuilt-native-binary-10083.test.ts | 4 +- ...n-to-cli-migration-server-env-7302.test.ts | 9 +- tests/unit/cli-env-collision.test.ts | 8 +- tests/unit/cli-expanded-commands.test.ts | 11 +- .../cli-helper/config-generator-codex.test.ts | 2 +- ...tool-detector-opencode-jsonc-10227.test.ts | 2 +- tests/unit/cli-ipv4-first-dns-2699.test.ts | 2 +- tests/unit/cli-keys-command.test.ts | 2 +- tests/unit/cli-lang-commands.test.ts | 2 +- tests/unit/cli-logs-route.test.ts | 2 +- ...nstall-runtime-allow-scripts-10713.test.ts | 4 +- tests/unit/cli-plugin-system.test.ts | 8 +- .../cli-provider-catalog-full-10080.test.ts | 4 +- .../cli-provider-test-routes-10570.test.ts | 2 +- tests/unit/cli-providers-command.test.ts | 2 +- tests/unit/cli-providers-rotate.test.ts | 2 +- tests/unit/cli-remote-mode.test.ts | 2 +- tests/unit/cli-repl.test.ts | 8 +- tests/unit/cli-runtime-detection.test.ts | 4 +- tests/unit/cli-runtime-extended.test.ts | 2 +- ...ntime-known-path-shortcircuit-7774.test.ts | 9 +- tests/unit/cli-runtime.test.ts | 16 +- tests/unit/cli-serve-stop-command.test.ts | 2 +- tests/unit/cli-setup-command.test.ts | 2 +- ...i-setup-opencode-nested-alias-7682.test.ts | 2 +- tests/unit/cli-setup-opencode.test.ts | 9 +- ...-sqlite-construction-fallback-8826.test.ts | 7 +- .../cli-stop-supervisor-respawn-9455.test.ts | 2 +- tests/unit/cli-storage-key-bootstrap.test.ts | 6 +- .../cli-tools-apply-container-422.test.ts | 3 +- .../cli-tools-apply-opencode-jsonc.test.ts | 5 +- tests/unit/cli-tools-crush.test.ts | 8 +- tests/unit/cli-tools-settings-jsonc.test.ts | 14 +- tests/unit/cli-tray-systray2.test.ts | 6 +- tests/unit/cli-tray.test.ts | 2 +- .../unit/cli-update-global-paths-3295.test.ts | 11 +- .../cli-update-shadow-install-9475.test.ts | 2 +- tests/unit/cli/alias-resolver-7791.test.ts | 4 +- tests/unit/cli/autostart-linux.test.ts | 2 +- tests/unit/cli/autostart-windows.test.ts | 2 +- .../launch-codex-windows-spawn-args.test.ts | 2 +- .../cli/launch-windows-spawn-args.test.ts | 2 +- tests/unit/cli/run-execution.test.ts | 12 +- tests/unit/cli/setup-claude.test.ts | 6 +- tests/unit/cli/setup-codex.test.ts | 2 +- tests/unit/cli/setup-qwen.test.ts | 4 +- .../cliRuntime-codex-shebang-8036.test.ts | 2 +- .../cliRuntime-symlink-escape-7753.test.ts | 11 +- tests/unit/client-identity-profiles.test.ts | 2 +- tests/unit/cliproxy-auth-import-1934.test.ts | 2 +- ...proxyapi-dedicated-credential-7645.test.ts | 25 +- .../unit/cliproxyapi-fallback-wiring.test.ts | 15 +- ...cliproxyapi-model-mapping-dispatch.test.ts | 3 +- tests/unit/cloud-agent-credentials.test.ts | 2 +- .../unit/cloud-agent-tasks-route-auth.test.ts | 4 +- tests/unit/cloud-sync.test.ts | 10 +- tests/unit/cloud-write-auth.test.ts | 4 +- .../unit/cloudflare-models-uuid-4259.test.ts | 4 +- tests/unit/cloudflaredTunnel-extended.test.ts | 2 +- tests/unit/cloudflaredTunnel.test.ts | 2 +- .../unit/codex-account-cooldown-write.test.ts | 4 +- ...odex-auth-import-userid-dedup-6301.test.ts | 4 +- ...codex-catalog-revalidation-runtime.test.ts | 4 +- tests/unit/codex-catalog-revalidation.test.ts | 2 +- tests/unit/codex-connection-defaults.test.ts | 7 +- tests/unit/codex-connection-edit-6562.test.ts | 4 +- ...codex-fingerprint-seed-persistence.test.ts | 4 +- tests/unit/codex-gpt55-effort-routing.test.ts | 2 +- tests/unit/codex-gpt55-routing-5887.test.ts | 2 +- ...dex-import-refresh-validation-7522.test.ts | 19 +- tests/unit/codex-import-token-route.test.ts | 2 +- .../unit/codex-models-catalog-refresh.test.ts | 4 +- .../codex-oauth-refresh-persist-6352.test.ts | 4 +- .../codex-orphaned-tool-outputs-2928.test.ts | 2 +- .../codex-quota-selection-hydration.test.ts | 4 +- tests/unit/codex-reset-credits.test.ts | 4 +- ...x-responses-passthrough-strip-3317.test.ts | 2 +- .../codex-responses-ws-fingerprint.test.ts | 4 +- ...-same-account-transport-retry-9708.test.ts | 4 +- ...-session-affinity-reset-aware-5903.test.ts | 16 +- .../codex-settings-wire-api-default.test.ts | 4 +- tests/unit/codex-stream-false.test.ts | 4 +- .../codex-synced-bare-model-routing.test.ts | 4 +- .../codex-ws-policy-enforcement-6564.test.ts | 4 +- tests/unit/colocate-optionals.test.ts | 33 +- .../unit/combo-account-allowlist-3266.test.ts | 4 +- .../combo-attempt-body-isolation-7847.test.ts | 2 +- .../combo-auto-candidate-expansion.test.ts | 4 +- .../unit/combo-auto-pool-visible-only.test.ts | 8 +- tests/unit/combo-bracket-names.test.ts | 4 +- ...combo-builder-effort-variants-8072.test.ts | 2 +- .../combo-builder-model-source-5477.test.ts | 2 +- .../combo-builder-opencode-prefix.test.ts | 2 +- .../unit/combo-builder-options-route.test.ts | 4 +- tests/unit/combo-cache-invalidation.test.ts | 4 +- ...ombo-context-generic-default-10734.test.ts | 2 +- tests/unit/combo-context-length.test.ts | 4 +- ...context-overflow-compression-probe.test.ts | 2 +- .../combo-context-prefix-resolution.test.ts | 2 +- tests/unit/combo-context-relay.test.ts | 4 +- .../unit/combo-context-window-filter.test.ts | 2 +- tests/unit/combo-description-5005.test.ts | 9 +- tests/unit/combo-dispatch-prelude.test.ts | 2 +- tests/unit/combo-empty-models.test.ts | 2 +- ...combo-fallback-token-estimate-7847.test.ts | 2 +- tests/unit/combo-forecast.test.ts | 4 +- tests/unit/combo-health-dashboard.test.ts | 4 +- tests/unit/combo-health-route.test.ts | 4 +- tests/unit/combo-hidden-leaf-routing.test.ts | 4 +- tests/unit/combo-id-resolution-4446.test.ts | 4 +- .../combo-lockout-quota-reset-6863.test.ts | 2 +- .../combo-model-name-collision-8530.test.ts | 4 +- tests/unit/combo-patch-verb.test.ts | 2 +- tests/unit/combo-prescreen.test.ts | 2 +- ...ority-quota-exhaustion-cutoff-5923.test.ts | 2 +- ...mbo-quota-exhaustion-only-fallback.test.ts | 2 +- .../combo-quota-share-cooldown-wait.test.ts | 4 +- tests/unit/combo-quota-token-limit.test.ts | 2 +- tests/unit/combo-resource-404-health.test.ts | 2 +- ...bo-roundrobin-compat-fallback-6238.test.ts | 2 +- .../unit/combo-routes-composite-tiers.test.ts | 5 +- tests/unit/combo-routing-engine.test.ts | 2 +- tests/unit/combo-rr-diagnostics-11462.test.ts | 7 +- .../combo-rr-fallback-advance-948.test.ts | 29 +- .../combo-rr-session-stickiness-3825.test.ts | 39 +- .../combo-runtime-unit-concurrency.test.ts | 2 +- .../unit/combo-scope-proxy-dead-7149.test.ts | 4 +- tests/unit/combo-scoring-inspector.test.ts | 4 +- .../combo-selected-connection-success.test.ts | 2 +- tests/unit/combo-sessionless-pin-3825.test.ts | 2 +- tests/unit/combo-silent-stop-gaps.test.ts | 2 +- tests/unit/combo-speed-telemetry-6875.test.ts | 6 +- ...bo-stickiness-responses-input-7270.test.ts | 29 +- tests/unit/combo-strategies.test.ts | 2 +- tests/unit/combo-strategy-fallbacks.test.ts | 2 +- ...bo-strict-random-distribution-3959.test.ts | 7 +- ...combo-system-prompt-templates-5501.test.ts | 25 +- .../combo-target-resolution-split.test.ts | 2 +- tests/unit/combo-test-route.test.ts | 4 +- tests/unit/combo-vision-aware-routing.test.ts | 2 +- ...-failure-tracker-session-isolation.test.ts | 2 +- .../combo/connection-aware-expansion.test.ts | 2 +- tests/unit/combo/image-combo.test.ts | 9 +- .../combo/reset-window-strategy-9330.test.ts | 2 +- tests/unit/combo/speech-combo.test.ts | 2 +- .../strict-context-failopen-8786.test.ts | 7 +- tests/unit/combo/video-combo.test.ts | 2 +- .../combos-duplicate-resolution-audit.test.ts | 2 +- tests/unit/combos-duplicate-route.test.ts | 2 +- tests/unit/combos-quota-protected.test.ts | 4 +- tests/unit/command-code-auth-assist.test.ts | 4 +- tests/unit/command-code-executor.test.ts | 33 +- .../unit/command-code-user-array-5166.test.ts | 2 +- tests/unit/command-code-vision.test.ts | 2 +- tests/unit/compliance-audit-route.test.ts | 4 +- tests/unit/compliance-index.test.ts | 4 +- tests/unit/compression-settings-cache.test.ts | 27 +- tests/unit/compression-tokens.test.ts | 22 +- .../active-combo-integration.test.ts | 12 +- .../adaptive-context-budget-config.test.ts | 26 +- tests/unit/compression/caveman-db.test.ts | 4 +- tests/unit/compression/compareRoute.test.ts | 19 +- .../compression/compression-combos-db.test.ts | 4 +- .../compression-engines-map-migration.test.ts | 9 +- .../compression-preview-auth.test.ts | 4 +- .../compression/compressionAnalytics.test.ts | 2 +- tests/unit/compression/db.test.ts | 4 +- .../headroom-minrows-persist-8056.test.ts | 4 +- .../compression/llmlingua-model-store.test.ts | 2 +- .../llmlingua-worker-resolution.test.ts | 2 +- .../mcp-accessibility-config.test.ts | 4 +- .../omniglyph-profile-config.test.ts | 9 +- .../compression/omniglyph-registries.test.ts | 4 +- .../preserve-system-prompt-mode-db.test.ts | 7 +- .../preview-fallback-reasons-6461.test.ts | 2 +- ...-outer-engine-token-reconcile-6488.test.ts | 2 +- .../compression/previewRouteBreakdown.test.ts | 19 +- .../compression/previewRouteFidelity.test.ts | 31 +- .../compression/previewRouteFuzzy.test.ts | 34 +- .../compression/previewRouteIonizer.test.ts | 18 +- .../compression/previewRoutePipeline.test.ts | 30 +- .../compression/previewRouteTokens.test.ts | 13 +- .../unit/compression/previewRouteToon.test.ts | 2 +- tests/unit/compression/retrieveRoute.test.ts | 9 +- .../compression/retrieveRouteRanged.test.ts | 2 +- .../compression/rtk-command-samples.test.ts | 2 +- .../compression/rtk-grouping-config.test.ts | 9 +- tests/unit/compression/rtk-mcp-tools.test.ts | 19 +- .../compression/rtk-raw-output-route.test.ts | 4 +- .../compression/rtk-renderers-config.test.ts | 2 +- .../compression/rtk-strip-comments.test.ts | 14 +- tests/unit/conductor-a2a-post.test.ts | 18 +- tests/unit/conductor-ask-route.test.ts | 9 +- tests/unit/conductor-fleet-route.test.ts | 32 +- tests/unit/config-audit-persistence.test.ts | 27 +- tests/unit/config-expiry-time-bomb.test.ts | 2 +- tests/unit/config-hot-reload.test.ts | 2 +- ...nsole-interceptor-message-fidelity.test.ts | 4 +- tests/unit/context-handoff.test.ts | 4 +- tests/unit/context-manager.test.ts | 2 +- ...ndow-reconcile-persisted-overrides.test.ts | 4 +- tests/unit/conversationTurnContent.test.ts | 2 +- .../conversations-active-call-log-id.test.ts | 2 +- tests/unit/cooldown-epoch-string-3954.test.ts | 7 +- tests/unit/correctness/goldenSnapshot.test.ts | 4 +- ...t-availability-route-authenticated.test.ts | 4 +- .../cursor-agent-availability-route.test.ts | 4 +- tests/unit/cursor-agent-cli-version.test.ts | 24 +- tests/unit/cursor-agent-models.test.ts | 10 +- tests/unit/cursor-renewal.test.ts | 8 +- tests/unit/cursor-token-extractor.test.ts | 4 +- tests/unit/cursor-version-detector.test.mjs | 8 +- .../custom-headers-provider-nodes.test.ts | 4 +- tests/unit/custom-model-target-format.test.ts | 2 +- .../dashscope-text-models-discovery.test.ts | 4 +- tests/unit/data-dir-writable-fallback.test.ts | 59 +-- .../database-settings-maintenance.test.ts | 2 +- .../datadir-test-context-guard-10428.test.ts | 45 +- tests/unit/db-adapters/driverFactory.test.ts | 4 +- tests/unit/db-agent-bridge-bypass.test.ts | 8 +- tests/unit/db-agent-bridge-mappings.test.ts | 8 +- tests/unit/db-agent-bridge-state.test.ts | 4 +- tests/unit/db-apiKeys-crud.test.ts | 14 +- .../db-backup-autobackup-setting-5871.test.ts | 4 +- tests/unit/db-backup-extended.test.ts | 8 +- tests/unit/db-backups-skills-3500.test.ts | 6 +- tests/unit/db-call-log-stats-3500.test.ts | 81 ++-- .../db-ccr-migration-renumber-134.test.ts | 2 +- tests/unit/db-cleanup-xp-audit-log.test.ts | 2 +- tests/unit/db-combos-crud.test.ts | 4 +- tests/unit/db-command-code-auth.test.ts | 4 +- tests/unit/db-core-extended.test.ts | 2 +- tests/unit/db-core-init.test.ts | 60 +-- tests/unit/db-core-migration.test.ts | 2 +- tests/unit/db-core.test.ts | 2 +- tests/unit/db-detailed-logs.test.ts | 4 +- tests/unit/db-domainState-crud.test.ts | 12 +- tests/unit/db-fresh-setup-9934.test.ts | 8 +- .../db-gamification-federation-3500.test.ts | 29 +- tests/unit/db-health-check.test.ts | 4 +- tests/unit/db-health-driver.test.ts | 2 +- tests/unit/db-health-route.test.ts | 4 +- tests/unit/db-inspector-custom-hosts.test.ts | 4 +- tests/unit/db-inspector-sessions.test.ts | 8 +- .../db-install-upgrade-schema-parity.test.ts | 2 +- ...ob-registry-migration-renumber-139.test.ts | 2 +- tests/unit/db-logs-cache-3500.test.ts | 2 +- .../db-migration-runner-extra-dirs.test.ts | 4 +- tests/unit/db-model-aliases-cascade.test.ts | 4 +- tests/unit/db-model-context-overrides.test.ts | 17 +- tests/unit/db-models-crud.test.ts | 4 +- tests/unit/db-models-extended.test.ts | 2 +- tests/unit/db-playground-presets.test.ts | 4 +- ...e-migration-backup-retention-10421.test.ts | 4 +- .../db-provider-cookie-dedup-3368.test.ts | 4 +- .../unit/db-provider-daily-usage-4009.test.ts | 2 +- tests/unit/db-provider-limits.test.ts | 4 +- tests/unit/db-provider-plans.test.ts | 18 +- tests/unit/db-provider-stats.test.ts | 2 +- .../db-providers-access-token-1290.test.ts | 4 +- .../db-providers-cross-idp-dedup-2244.test.ts | 8 +- tests/unit/db-providers-crud.test.ts | 31 +- tests/unit/db-proxies-crud.test.ts | 4 +- tests/unit/db-quota-consumption.test.ts | 10 +- .../db-quota-migrations-idempotency.test.ts | 15 +- tests/unit/db-quota-pools.test.ts | 4 +- tests/unit/db-quota-snapshots.test.ts | 4 +- tests/unit/db-read-cache.test.ts | 4 +- tests/unit/db-recovery.test.ts | 2 +- tests/unit/db-registered-keys.test.ts | 4 +- tests/unit/db-registeredKeys-crud.test.ts | 2 +- tests/unit/db-reset-module-state.test.ts | 4 +- tests/unit/db-secrets.test.ts | 4 +- tests/unit/db-settings-crud.test.ts | 4 +- ...-settings-debug-mode-default-10312.test.ts | 2 +- tests/unit/db-settings-extended.test.ts | 2 +- tests/unit/db-sqljs-atomic-persist.test.ts | 4 +- tests/unit/db-sqljs-close-poison-7494.test.ts | 24 +- ...db-sqljs-preinit-ordering-gap-7288.test.ts | 9 +- ...ed-model-catalog-invalidation-8728.test.ts | 4 +- tests/unit/db-upstreamProxy.test.ts | 7 +- tests/unit/db-usage-analytics-3500.test.ts | 2 +- tests/unit/db-versionManager.test.ts | 7 +- tests/unit/db-webhooks.test.ts | 4 +- tests/unit/db/api-keys.test.ts | 4 +- tests/unit/db/connectionRuntimeState.test.ts | 4 +- .../context-editing-telemetry-record.test.ts | 4 +- tests/unit/db/default-combo-toggle.test.ts | 4 +- tests/unit/db/discovery-results.test.ts | 3 +- tests/unit/db/jobRegistryDb.test.ts | 4 +- tests/unit/db/migration-071.test.ts | 4 +- tests/unit/db/migration-163.test.ts | 4 +- tests/unit/db/omp.test.ts | 9 +- tests/unit/db/per-engine-analytics.test.ts | 4 +- .../db/per-engine-breakdown-analytics.test.ts | 4 +- tests/unit/db/quota-pools.test.ts | 4 +- .../sqliteComboRepositories.test.ts | 4 +- tests/unit/db/serviceModels.test.ts | 4 +- tests/unit/db/vacuum-scheduler.test.ts | 2 +- tests/unit/db/weak-rng-fixes.test.ts | 2 +- tests/unit/deepseek-thinking-efforts.test.ts | 4 +- ...-connection-clears-combo-pins-8887.test.ts | 6 + ...r-connection-invalidates-lkgp-8887.test.ts | 4 +- ...-connection-purges-key-health-7740.test.ts | 5 +- tests/unit/devin-bridge-network-guard.test.ts | 6 +- tests/unit/dgrid-provider.test.ts | 4 +- tests/unit/dns-config-generic.test.ts | 2 +- .../docker-llmlingua-optionals-9166.test.ts | 123 ++--- tests/unit/docs-validate-svg.test.ts | 6 +- tests/unit/domain-branch-hardening.test.ts | 4 +- tests/unit/domain-cost-rules.test.ts | 4 +- tests/unit/domain-fallback-policy.test.ts | 4 +- tests/unit/domain-lockout-policy.test.ts | 4 +- tests/unit/domain-persistence.test.ts | 3 +- ...kgo-vqd-429-misclassification-6996.test.ts | 15 +- ...fort-thinking-standardization-6241.test.ts | 4 +- .../effort-tiers-loop-catalog-e2e.test.ts | 4 +- tests/unit/egress-ip-lock-10880.test.ts | 4 +- tests/unit/electron-main.test.ts | 2 +- tests/unit/electron-packaging.test.ts | 2 +- tests/unit/electron-remote-server.test.ts | 2 +- tests/unit/electron-smoke-script.test.ts | 4 +- tests/unit/electron-sqlite-prebuild.test.ts | 2 +- tests/unit/elevenlabs-native-routes.test.ts | 33 +- .../embedding-account-cooldown-10347.test.ts | 4 +- ...bedding-cooldown-integration-10347.test.ts | 4 +- .../embeddings-cost-telemetry-headers.test.ts | 2 +- tests/unit/embeddings-lan-noauth-6925.test.ts | 2 +- .../unit/embeddings-proxy-forwarding.test.ts | 7 +- .../embeddings-route-apikeymeta-6929.test.ts | 2 +- tests/unit/emergency-fallback-service.test.ts | 4 +- tests/unit/empty-choices-no-inject.test.ts | 2 +- .../unit/endpoint-restrictions-policy.test.ts | 9 +- tests/unit/error-message-sanitization.test.ts | 4 +- tests/unit/evals-history.test.ts | 4 +- tests/unit/evals-route.test.ts | 4 +- .../unit/exclusive-connection-leases.test.ts | 4 +- .../exclusive-lease-api-key-policy.test.ts | 4 +- ...xclusive-lease-auxiliary-isolation.test.ts | 4 +- ...ve-lease-connection-test-isolation.test.ts | 2 +- .../unit/exclusive-lease-managed-set.test.ts | 2 +- .../exclusive-session-observability.test.ts | 2 +- ...ute-chat-resource-pressure-breaker.test.ts | 4 +- .../execute-web-search-fallback-11524.test.ts | 4 +- ...ecutor-devin-cli-acp-protocol-8406.test.ts | 2 +- .../executor-devin-cli-agentic-acp.test.ts | 20 +- tests/unit/executor-map-golden.test.ts | 10 +- tests/unit/executor-registry.test.ts | 7 +- tests/unit/feature-flags-settings.test.ts | 16 +- .../unit/feature-triage/integration.test.mjs | 2 +- tests/unit/felo-web-runtime-block.test.ts | 4 +- tests/unit/file-deletion.test.ts | 2 +- tests/unit/fix-bare-model-precedence.test.ts | 7 +- tests/unit/fix-bare-routing-fallback.test.ts | 4 +- .../fix-tls-client-node-binary-7802.test.ts | 12 +- tests/unit/fixes-p1.test.ts | 4 +- ...ovider-rankings-custom-models-6368.test.ts | 2 +- ...free-provider-rankings-usage-route.test.ts | 2 +- tests/unit/free-proxies-add-to-pool.test.ts | 4 +- tests/unit/free-proxies-db.test.ts | 4 +- tests/unit/free-proxies-list-search.test.ts | 4 +- .../free-proxy-auto-sync-scheduler.test.ts | 9 +- tests/unit/free-proxy-providers.test.ts | 9 +- tests/unit/free-proxy-sync-cycle.test.ts | 4 +- .../free-tier-summary-radar-overlay.test.ts | 3 +- tests/unit/fusion-vision-panel-3378.test.ts | 12 +- tests/unit/g13-combo-chatcore-golden.test.ts | 2 +- .../aggregate-profile-3484.test.ts | 5 +- .../github-copilot-retired-models.test.ts | 2 +- .../glm-provider-model-import-route.test.ts | 4 +- tests/unit/gpt-max-input-tokens-6191.test.ts | 4 +- tests/unit/grok-cli-device-route.test.ts | 2 +- .../unit/grok-cli-provider-limits-ui.test.ts | 2 +- tests/unit/grok-cli-provider-limits.test.ts | 2 +- tests/unit/guardrails-api-3496.test.ts | 6 +- .../videoBridgeFu07StructuralSampling.test.ts | 4 +- .../guardrails/videoBridgeRuntime.test.ts | 2 +- .../vision-bridge-callmodel.test.ts | 25 +- ...e-credentials-alias-mismatch-10702.test.ts | 7 +- .../visionBridge-combo-reroute.test.ts | 4 +- .../visionBridgeCredentials.test.ts | 4 +- tests/unit/guide-settings-route.test.ts | 4 +- ...headroom-codex-quota-snapshot-6379.test.ts | 11 +- tests/unit/health-ping-route.test.ts | 2 +- tests/unit/helpers/decollidedMigrationsDir.ts | 2 +- ...s-agent-settings-route-keyid-10711.test.ts | 7 +- ...hidden-models-leak-v1-models-11300.test.ts | 14 +- .../image-compat-node-alias-shadow.test.ts | 6 +- tests/unit/image-edits-multipart-3273.test.ts | 12 +- .../unit/image-generation-route-auth.test.ts | 4 +- tests/unit/image-generation-route.test.ts | 4 +- ...age-model-not-in-chat-catalog-6457.test.ts | 4 +- ...image-routes-combo-edits-3214-3215.test.ts | 2 +- .../unit/inspector-agent-bridge-hook.test.ts | 13 +- ...instrumentation-warm-catalog-cache.test.ts | 4 +- tests/unit/intercept-fetch-resolver.test.ts | 9 +- tests/unit/interception-rules.test.ts | 4 +- tests/unit/internal-service-auth.test.ts | 2 +- tests/unit/ip-filter-persistence-6131.test.ts | 4 +- tests/unit/ip-filter.test.ts | 4 +- .../issue-6343-v0-web-alias-collision.test.ts | 2 +- ...ssue-6686-quota-preflight-coverage.test.ts | 4 +- .../unit/issue-agent-route-execution.test.ts | 2 +- tests/unit/json-migration-combos.test.ts | 3 +- .../unit/key-health-402-disable-5239.test.ts | 11 +- tests/unit/kimi-coding-billing.test.ts | 2 +- tests/unit/kimi-quota-reset-recovery.test.ts | 2 +- tests/unit/kimi-web-models-discovery.test.ts | 4 +- tests/unit/kiro-auto-import-idc-2059.test.ts | 6 +- .../kiro-auto-import-name-dedup-3615.test.ts | 2 +- .../unit/kiro-builder-id-import-3333.test.ts | 2 +- tests/unit/kiro-import-error-3589.test.ts | 2 +- ...kiro-second-oauth-connection-10815.test.ts | 2 +- ...iro-sso-cache-direct-clientid-1253.test.ts | 18 +- .../kiro-windows-auto-import-3363.test.ts | 21 +- tests/unit/latency-stats-ttft-6875.test.ts | 4 +- tests/unit/least-used-rotation-10945.test.ts | 4 +- .../unit/lib/consoleInterceptor-epipe.test.ts | 7 +- .../lib/consoleInterceptor-writes.test.ts | 6 +- tests/unit/lib/jobRegistry/registry.test.ts | 4 +- tests/unit/lib/jobs/backupScheduleJob.test.ts | 2 +- tests/unit/lib/managementCliToken.test.ts | 2 +- tests/unit/lib/quota-reset-events.test.ts | 2 +- .../circuitBreakerFactory.test.ts | 4 +- .../circuitBreakerFactoryConcurrency.test.ts | 2 +- .../circuitBreakerFactoryRelease.test.ts | 2 +- .../sqliteCircuitBreakerStore.test.ts | 4 +- tests/unit/limiter-lifecycle.test.ts | 4 +- ...-model-catalog-reconciliation-8926.test.ts | 4 +- tests/unit/live-ws-public-url.test.ts | 4 +- tests/unit/llamacpp-model-delete.test.ts | 4 +- .../llm7-byteplus-models-fetch-3976.test.ts | 4 +- .../lmstudio-connection-baseurl-11233.test.ts | 2 +- tests/unit/local-corpus-index.test.ts | 2 +- tests/unit/local-corpus-lru-cache.test.ts | 22 +- tests/unit/local-rerank-logging.test.ts | 2 +- tests/unit/log-export-routes.test.mjs | 4 +- tests/unit/log-retention.test.ts | 8 +- ...r-write-after-datadir-removed-6360.test.ts | 4 +- tests/unit/login-bootstrap-route.test.ts | 4 +- tests/unit/managed-available-models.test.ts | 4 +- tests/unit/managed-model-import.test.ts | 4 +- ...nagement-password-insecure-default.test.ts | 10 +- tests/unit/management-password.test.ts | 4 +- ...nt-unavailable-numeric-epoch-guard.test.ts | 2 +- ...asked-200-exhaustion-fallback-6427.test.ts | 19 +- .../unit/materialize-bundled-symlinks.test.ts | 18 +- tests/unit/mcp-connect-scope.test.ts | 38 +- tests/unit/mcp-memory-tools-strategy.test.ts | 37 +- tests/unit/mcp-route-scope-carveout.test.ts | 4 +- .../unit/mcp/bundle-no-sync-esm-await.test.ts | 4 +- .../unit/media-cost-headers-handlers.test.ts | 2 +- tests/unit/media-cost-headers.test.ts | 6 +- tests/unit/memory-engine-status.test.ts | 10 +- tests/unit/memory-needs-reindex.test.ts | 10 +- tests/unit/memory-reindex-batch.test.ts | 4 +- tests/unit/memory-retrieval-hybrid.test.ts | 2 +- tests/unit/memory-retrieval-rerank.test.ts | 11 +- tests/unit/memory-retrieval-semantic.test.ts | 36 +- tests/unit/memory-retrieve-preview.test.ts | 7 +- tests/unit/memory-route.test.ts | 4 +- tests/unit/memory-store-sync.test.ts | 20 +- tests/unit/memory-store.test.ts | 4 +- .../memory-summarization-older-than.test.ts | 10 +- tests/unit/memory-summarization.test.ts | 4 +- tests/unit/memory-tools.test.ts | 4 +- tests/unit/memory-vec-meta.test.ts | 12 +- tests/unit/memory-vectorstore-crud.test.ts | 14 +- .../memory-vectorstore-ensure-ready.test.ts | 14 +- .../memory-vectorstore-int8-quant.test.ts | 19 +- tests/unit/memory-vectorstore-load.test.ts | 8 +- tests/unit/memory-vectorstore-rrf.test.ts | 21 +- tests/unit/memory-vectorstore-stats.test.ts | 11 +- ...emory-vectorstore-upsert-self-heal.test.ts | 14 +- tests/unit/memory/typed-decay.test.ts | 16 +- tests/unit/merge-train-plan.test.ts | 2 +- .../unit/messages-count-tokens-route.test.ts | 4 +- ...t-designer-web-image-handler-block.test.ts | 2 +- ...crosoft-designer-web-model-routing.test.ts | 4 +- ...crosoft-designer-web-runtime-block.test.ts | 4 +- .../migration-135-numbering-collision.test.ts | 2 +- ...ation-159-remove-mimocode-provider.test.ts | 2 +- .../migration-165-retire-felo-web.test.ts | 2 +- ...n-166-retire-gpl-derived-providers.test.ts | 2 +- .../migration-167-retire-qwen-web.test.ts | 2 +- .../migration-168-retire-chatgpt-web.test.ts | 2 +- tests/unit/minimax-m3-maxtokens.test.ts | 7 +- .../unit/mitm-cert-install-mode-9442.test.ts | 2 +- tests/unit/mitm-cert-migration-6684.test.ts | 10 +- tests/unit/mitm-hosts-cleanup-on-exit.test.ts | 10 +- tests/unit/mitm-manager-bypass-json.test.ts | 20 +- .../mitm-manager-cleanup-symmetry.test.ts | 14 +- tests/unit/mitm-manager-repair.test.ts | 13 +- .../mitm-privileged-steps-sudo-gate.test.ts | 17 +- .../mitm-root-ca-persistence-6684.test.ts | 28 +- tests/unit/mitm-start-guard.test.ts | 2 +- .../mitm-stop-dns-before-kill-1809.test.ts | 11 +- tests/unit/mitm-upstream-ca-wiring.test.ts | 40 +- ...odality-bridge-video-runtime-route.test.ts | 4 +- tests/unit/model-alias-route.test.ts | 4 +- tests/unit/model-alias-seed-fallback.test.ts | 2 +- tests/unit/model-alias-seed.test.ts | 4 +- ...el-aliases-settings-route-selfheal.test.ts | 4 +- ...l-capabilities-kimi-k3-vision-8250.test.ts | 4 +- ...abilities-mistral-vision-sync-4073.test.ts | 14 +- ...pabilities-path-shaped-vision-8032.test.ts | 8 +- .../unit/model-capabilities-registry.test.ts | 4 +- tests/unit/model-capability-overrides.test.ts | 9 +- ...apability-resolution-snapshot-9199.test.ts | 12 +- .../unit/model-catalog-cache-swr-8728.test.ts | 2 +- ...l-catalog-policy-invalidation-8728.test.ts | 8 +- ...model-catalog-runtime-invalidation.test.ts | 4 +- ...l-catalog-source-invalidation-8728.test.ts | 8 +- tests/unit/model-combo-mappings-db.test.ts | 4 +- ...l-connid-prefix-normalization-6772.test.ts | 2 +- .../model-context-override-readpath.test.ts | 4 +- tests/unit/model-cooldowns-route-auth.test.ts | 4 +- tests/unit/model-intelligence-db.test.ts | 114 +++-- tests/unit/model-latency-stats-route.test.ts | 4 +- .../unit/model-lifecycle-integration.test.ts | 4 +- tests/unit/model-lockout-max-cooldown.test.ts | 4 +- tests/unit/model-metadata-registry.test.ts | 4 +- ...output-cap-synced-fallthrough-6714.test.ts | 4 +- ...del-overrides-provider-prefix-9557.test.ts | 4 +- tests/unit/model-resolver.test.ts | 8 +- .../model-sync-custom-preservation.test.ts | 2 +- tests/unit/model-sync-route.test.ts | 4 +- tests/unit/model-sync-scheduler.test.ts | 4 +- tests/unit/model-test-route.test.ts | 4 +- tests/unit/model-token-limit-catalog.test.ts | 4 +- .../models-catalog-auto-combos-4164.test.ts | 10 +- .../models-catalog-block-auto-5192.test.ts | 4 +- .../models-catalog-combo-metadata.test.ts | 2 +- .../models-catalog-custom-node-prefix.test.ts | 4 +- tests/unit/models-catalog-envkey-6406.test.ts | 4 +- ...log-functional-gateway-permissions.test.ts | 4 +- ...models-catalog-hidden-combo-leaves.test.ts | 4 +- tests/unit/models-catalog-hide-paid.test.ts | 2 +- .../models-catalog-low-noise-flag.test.ts | 4 +- tests/unit/models-catalog-route.test.ts | 4 +- ...-catalog-static-synced-suppression.test.ts | 12 +- tests/unit/models-db-isfree.test.ts | 120 ++++- .../models-dev-pricing-caching-9300.test.ts | 8 +- tests/unit/models-test-error-shape.test.ts | 2 +- tests/unit/modelsDevSync-extended.test.ts | 4 +- .../monitoring-health-public-view.test.ts | 2 +- tests/unit/native-binary-compat.test.ts | 2 +- tests/unit/noauth-autocombo-allowlist.test.ts | 4 +- .../noauth-autocombo-exclude-7622.test.ts | 4 +- .../unit/noauth-autocombo-hidden-7620.test.ts | 4 +- .../noauth-autocombo-lockout-7623.test.ts | 4 +- .../unit/noauth-imported-models-3200.test.ts | 4 +- .../unit/notion-web-models-discovery.test.ts | 4 +- tests/unit/nvidia-410-model-scope.test.ts | 4 +- tests/unit/oauth-400-recovery.test.ts | 2 +- ...connection-persistence-codex-dedup.test.ts | 16 +- ...uth-connection-tokenexpiresat-5326.test.ts | 2 +- .../oauth-device-code-region-ssrf.test.ts | 2 +- tests/unit/oauth-grok-cli-browser.test.ts | 16 +- tests/unit/oauth-import-manage-scope.test.ts | 8 +- .../oauth-keychain-import-only-6041.test.ts | 8 +- .../oauth-paste-credentials-route.test.ts | 2 +- ...auth-refresh-connection-dedup-8059.test.ts | 4 +- tests/unit/obsidian-config.test.ts | 16 +- tests/unit/obsidian-webdav-route.test.ts | 49 +- tests/unit/oidc-callback.test.ts | 4 +- tests/unit/oidc-login-state.test.ts | 4 +- .../ollama-404-model-lockout-11071.test.ts | 18 +- .../ollama-local-capabilities-routing.test.ts | 4 +- .../unit/ollama-local-embedding-2824.test.ts | 2 +- ...nai-style-providers-4239-4155-3841.test.ts | 4 +- tests/unit/openapi-try-route.test.ts | 4 +- ...ee-tier-routing-shortcircuit-10571.test.ts | 2 +- .../unit/opencode-noauth-models-route.test.ts | 2 +- .../unit/opencode-zen-alias-combo-e2e.test.ts | 2 +- ...openrouter-embeddings-catalog-6976.test.ts | 9 +- ...outer-free-model-credits-exhausted.test.ts | 4 +- tests/unit/openrouter-provider-stats.test.ts | 2 +- .../unit/openrouter-vision-sync-4264.test.ts | 8 +- tests/unit/ops-scripts.test.ts | 4 +- tests/unit/optional-packs.test.ts | 6 +- .../paid-model-target-routes-6540.test.ts | 9 +- tests/unit/param-filters-db.test.ts | 2 +- .../payload-rules-restart-persistence.test.ts | 2 +- tests/unit/payload-rules-route.test.ts | 4 +- tests/unit/payload-rules.test.ts | 2 +- tests/unit/perf-waterfall-elimination.test.ts | 83 +++- ...sist-429-cooldown-account-fallback.test.ts | 419 +++++++++--------- tests/unit/pick-internal-api-key-6372.test.ts | 4 +- tests/unit/piiReproduction.test.ts | 84 +++- tests/unit/piiSanitizer.test.ts | 2 +- tests/unit/piiSanitizerIpv6.test.ts | 41 +- tests/unit/playground-key-policy-3503.test.ts | 8 +- ...und-simulate-route-persisted-combo.test.ts | 12 +- tests/unit/plugins-config-route.test.ts | 20 +- tests/unit/plugins-dev-mode.test.ts | 8 +- tests/unit/plugins-doctor.test.ts | 37 +- tests/unit/plugins-edge-cases.test.ts | 146 ++++-- tests/unit/plugins-fs-safety.test.ts | 30 +- tests/unit/plugins-loader.test.ts | 8 +- tests/unit/plugins-logger.test.ts | 4 +- tests/unit/plugins-manager-lifecycle.test.ts | 28 +- ...lugins-manager-restart-reload-7806.test.ts | 14 +- tests/unit/plugins-metrics.test.ts | 19 +- tests/unit/plugins-scanner.test.ts | 8 +- tests/unit/plugins-signing.test.ts | 8 +- tests/unit/plugins-tools.test.ts | 42 +- tests/unit/plugins-upgrade.test.ts | 66 +-- tests/unit/plugins-welcome-banner-e2e.test.ts | 2 +- .../unit/poe-api-executor-regression.test.ts | 2 +- .../unit/poe-provider-models-baseurl.test.ts | 4 +- tests/unit/policy-engine.test.ts | 2 +- tests/unit/postinstall-support.test.ts | 4 +- tests/unit/pricing-route-sources.test.ts | 4 +- .../unit/pricing-sync-cross-instance.test.ts | 2 +- tests/unit/pricing-sync-extended.test.ts | 4 +- tests/unit/probe-6835-cyclebreaker.test.ts | 2 +- tests/unit/probe-6835-oom-uncapped.test.ts | 5 +- tests/unit/probe-9541-repro.test.ts | 2 +- .../unit/probe-autodisable-isolation.test.ts | 2 +- tests/unit/probe-gate-autodisable.test.ts | 2 +- tests/unit/probe-policy.test.ts | 2 +- tests/unit/probe-production-path.test.ts | 2 +- tests/unit/probe-testall-isolation.test.ts | 2 +- .../prompt-injection-guard-db-flag.test.ts | 4 +- tests/unit/prompt-required-routes.test.ts | 2 +- .../provider-connection-apikey-dedup.test.ts | 4 +- ...nnection-healthcheck-interval-zero.test.ts | 10 +- ...rovider-connection-test-key-health.test.ts | 4 +- ...ovider-connections-pagination-2998.test.ts | 4 +- ...ovider-connections-quota-threshold.test.ts | 4 +- tests/unit/provider-health-matrix.test.ts | 4 +- ...r-limits-local-apikey-sync-spacing.test.ts | 4 +- ...vider-limits-oauth-sequential-sync.test.ts | 4 +- .../provider-limits-proxy-fail-closed.test.ts | 4 +- tests/unit/provider-limits-recovery.test.ts | 4 +- ...ider-limits-rotating-expired-guard.test.ts | 2 +- ...rovider-limits-sanitize-scope-3821.test.ts | 4 +- ...mits-sync-scheduler-public-surface.test.ts | 2 +- .../provider-login-timeout-validation.test.ts | 2 +- .../provider-metrics-deleted-provider.test.ts | 17 +- tests/unit/provider-metrics-route.test.ts | 4 +- ...odels-context-window-override-4125.test.ts | 10 +- .../provider-models-custom-merge-6247.test.ts | 4 +- .../provider-models-management-route.test.ts | 4 +- .../unit/provider-models-route-codex.test.ts | 4 +- .../provider-models-route-lan-guard.test.ts | 4 +- tests/unit/provider-models-route.test.ts | 4 +- .../unit/provider-models-token-limits.test.ts | 4 +- tests/unit/provider-models-v1-route.test.ts | 6 +- ...ovider-models-vision-override-1904.test.ts | 4 +- tests/unit/provider-node-icon-url.test.ts | 4 +- .../provider-node-reserved-prefix.test.ts | 4 +- tests/unit/provider-nodes-route.test.ts | 4 +- .../provider-nodes-validate-modelid.test.ts | 4 +- .../provider-nodes-vibeproxy-preset.test.ts | 4 +- ...r-patch-ratelimit-protection-11278.test.ts | 4 +- tests/unit/provider-probe-target.test.ts | 2 +- .../provider-request-failure-pipeline.test.ts | 4 +- .../unit/provider-scoped-models-route.test.ts | 4 +- .../provider-sweep-live-discovery.test.ts | 10 +- .../provider-translate-path-golden.test.ts | 2 +- ...der-validation-unsupported-neutral.test.ts | 3 + tests/unit/provider-window-costs.test.ts | 4 +- tests/unit/providers-batch-update.test.ts | 9 +- ...providers-route-codex-account-pool.test.ts | 2 +- .../providers-route-managed-catalog.test.ts | 4 +- ...viders-route-model-autofetch-optin.test.ts | 4 +- tests/unit/providers-validate-route.test.ts | 4 +- tests/unit/proxy-10348-log-redaction.test.ts | 4 +- .../proxy-assigned-unavailable-6246.test.ts | 4 +- .../unit/proxy-autoselect-optin-3332.test.ts | 7 +- tests/unit/proxy-batch-routes-5918.test.ts | 14 +- .../unit/proxy-bulk-import-dedup-7594.test.ts | 4 +- tests/unit/proxy-egress-route-summary.test.ts | 30 +- ...proxy-egress-validate-pool-default.test.ts | 10 +- ...lback-candidates-listproxies-shape.test.ts | 2 +- tests/unit/proxy-fallback-ssrf.test.ts | 8 +- tests/unit/proxy-health-6246.test.ts | 24 +- tests/unit/proxy-health-egress-line.test.ts | 80 +++- ...health-scheduler-listproxies-shape.test.ts | 4 +- tests/unit/proxy-logger-client-ip.test.ts | 2 +- tests/unit/proxy-logs-egress-ip.test.ts | 4 +- .../proxy-logs-egress-lookup-10880.test.ts | 27 +- tests/unit/proxy-logs-route.test.ts | 4 +- tests/unit/proxy-management-v1-route.test.ts | 4 +- tests/unit/proxy-noauth-provider-6272.test.ts | 2 +- tests/unit/proxy-pool-rotation-6365.test.ts | 9 +- tests/unit/proxy-pool-route-6365.test.ts | 9 +- tests/unit/proxy-pool-sync-4878.test.ts | 9 +- .../proxy-registry-route-handlers.test.ts | 9 +- tests/unit/proxy-registry.test.ts | 9 +- .../proxy-resolution-status-filter.test.ts | 4 +- tests/unit/proxy-rotation-latency.test.ts | 4 +- ...oxy-subscriptions-route-validation.test.ts | 22 +- tests/unit/proxySubscription.service.test.ts | 8 +- tests/unit/puter-provider-removed.test.ts | 2 +- tests/unit/qiniu-provider.test.ts | 4 +- tests/unit/qoder-cli.test.ts | 8 +- tests/unit/qoder-executor.test.ts | 4 +- .../unit/qoder-jobtoken-exchange-4683.test.ts | 2 +- tests/unit/quota-cache-hydrate-5015.test.ts | 2 +- ...cache-is-exhausted-per-window-5923.test.ts | 2 +- tests/unit/quota-combo-balancing.test.ts | 2 +- tests/unit/quota-combo-cli-providers.test.ts | 5 +- tests/unit/quota-combo-groups.test.ts | 27 +- tests/unit/quota-combos-sync.test.ts | 20 +- .../quota-epsilon-unconfigured-allow.test.ts | 2 +- .../unit/quota-exclusive-catalog-4806.test.ts | 4 +- ...ta-exclusive-catalog-short-circuit.test.ts | 2 +- .../unit/quota-exclusivity-reconcile.test.ts | 23 +- .../quota-exhaustion-cutoff-opencode.test.ts | 32 +- tests/unit/quota-group-allocations.test.ts | 80 +++- tests/unit/quota-group-scope.test.ts | 25 +- tests/unit/quota-groups-crud.test.ts | 4 +- tests/unit/quota-groups-migration.test.ts | 33 +- tests/unit/quota-key-resolve.test.ts | 4 +- tests/unit/quota-multiprovider.test.ts | 2 +- .../unit/quota-per-key-model-hotpath.test.ts | 27 +- tests/unit/quota-per-key-model.test.ts | 27 +- tests/unit/quota-phase2.test.ts | 29 +- tests/unit/quota-plan-resolver.test.ts | 22 +- tests/unit/quota-pool-connections.test.ts | 4 +- tests/unit/quota-pool-delete-prune.test.ts | 4 +- tests/unit/quota-pool-single-provider.test.ts | 4 +- tests/unit/quota-pool-update-full.test.ts | 14 +- tests/unit/quota-redis-store.test.ts | 4 +- tests/unit/quota-scheduler.test.ts | 4 +- tests/unit/quota-sharing-fixes.test.ts | 28 +- tests/unit/quota-sqlite-store.test.ts | 4 +- tests/unit/quota-store-factory.test.ts | 22 +- tests/unit/quota-store-pool-total.test.ts | 4 +- tests/unit/qwen-settings-route.test.ts | 4 +- tests/unit/qwen-web-runtime-block.test.ts | 4 +- tests/unit/radar-api-routes.test.ts | 4 +- tests/unit/radar-db.test.ts | 4 +- tests/unit/radar-export.test.mjs | 7 +- .../radar-feed-cache-generated-at.test.ts | 2 +- tests/unit/radar-inertia.test.ts | 58 ++- tests/unit/radar-intel-db.test.ts | 4 +- tests/unit/radar-intel-routes.test.ts | 4 +- tests/unit/radar-local-state-db.test.ts | 4 +- tests/unit/radar-local-state-route.test.ts | 4 +- tests/unit/radar-offers-db.test.ts | 4 +- tests/unit/radar-offers-routes.test.ts | 4 +- tests/unit/radar-referrals-route.test.ts | 6 +- .../unit/radar-supporter-gamification.test.ts | 2 +- ...mit-execution-timeout-message-4165.test.ts | 2 +- ...e-limit-local-error-classification.test.ts | 4 +- tests/unit/rate-limit-manager.test.ts | 4 +- .../rate-limit-queue-timeout-lockout.test.ts | 2 +- .../ratelimit-admission-control-6593.test.ts | 2 +- ...ing-probe-truncated-response-10281.test.ts | 2 +- tests/unit/reasoning-routing-api.test.ts | 4 +- .../reasoning-routing-decision-guards.test.ts | 2 +- tests/unit/reasoning-routing.test.ts | 4 +- .../unit/reasoning-token-buffer-6274.test.ts | 2 +- .../unit/reasoning-token-buffer-9507.test.ts | 2 +- tests/unit/refresh-cursor-route.test.ts | 4 +- ...ject-management-password-as-apikey.test.ts | 4 +- tests/unit/rejected-request-usage.test.ts | 4 +- ...ay-check-rate-limit-existing-token.test.ts | 8 +- tests/unit/relay-deploy-5128.test.ts | 4 +- ...custom-models-preserve-hidden-5086.test.ts | 2 +- ...o-10139-claude-thinking-output-cap.test.ts | 2 +- tests/unit/repro-6524.test.ts | 2 +- ...-noauth-connection-disable-ignored.test.ts | 4 +- .../repro-6701-claude-detect-fallback.test.ts | 2 +- ...2-volcengine-max-completion-tokens.test.ts | 26 +- tests/unit/repro-6952-commentary.test.ts | 7 +- tests/unit/repro-6957.test.ts | 2 +- tests/unit/repro-6975.test.ts | 20 +- ...ro-8065-quota-cache-cross-instance.test.ts | 7 +- ...o-8429-capability-canonicalization.test.ts | 31 +- ...pro-8841-context-overflow-opencode.test.ts | 2 +- tests/unit/repro-8847.test.ts | 4 +- tests/unit/repro-8956.test.ts | 4 +- tests/unit/repro-8995.test.ts | 6 +- tests/unit/repro-9625.test.ts | 4 +- ...repro-compression-run-telemetry-ms.test.ts | 13 +- tests/unit/request-log-migration.test.ts | 9 +- tests/unit/request-logger-endpoints.test.ts | 2 +- ...quire-management-auth-access-token.test.ts | 2 +- tests/unit/rerank-proxy-pinning-7350.test.ts | 2 +- tests/unit/rerank-voyage-7809.test.ts | 2 +- tests/unit/reset-connection-backoff.test.ts | 2 +- .../unit/reset-password-cli-6261-6258.test.ts | 12 +- ...ence-stream-recovery-feature-flags.test.ts | 2 +- tests/unit/resolve-proxy-family.test.ts | 11 +- ...onses-case-insensitive-combo-guard.test.ts | 2 +- ...ponses-commentary-event-frame-6561.test.ts | 2 +- ...ponses-commentary-passthrough-6199.test.ts | 2 +- .../unit/responses-continuation-store.test.ts | 2 +- tests/unit/responses-handler.test.ts | 11 +- tests/unit/responses-parse-once-4041.test.ts | 2 +- ...onses-route-early-keepalive-wiring.test.ts | 2 +- tests/unit/responses-transformer.test.ts | 7 +- tests/unit/review-reviews-v3814-fixes.test.ts | 4 +- tests/unit/route-edge-coverage.test.ts | 4 +- tests/unit/route-explainability.test.ts | 4 +- tests/unit/router-eval-check.test.ts | 10 +- tests/unit/router-eval-cli.test.ts | 12 +- tests/unit/router-eval-compare.test.ts | 2 +- tests/unit/router-eval-e2e-chain.test.ts | 2 +- tests/unit/router-eval-patch-compare.test.ts | 10 +- tests/unit/router-eval-search.test.ts | 6 +- tests/unit/router-eval-trends.test.ts | 6 +- .../rule12-error-sanitization-sweep.test.ts | 2 +- tests/unit/run-next-playwright.test.ts | 2 +- tests/unit/runner-janitor.test.ts | 10 +- .../runtime-deps-save-exact-no-prune.test.ts | 12 +- tests/unit/runtime/magicBytes.test.ts | 2 +- tests/unit/sanitizer-residual-policy.test.ts | 23 +- .../search-provider-opaque-400-10849.test.ts | 7 +- tests/unit/search-route.test.ts | 4 +- tests/unit/security-s1-s2-s4.test.ts | 87 ++-- .../serial/combo-health-autopilot.test.ts | 4 +- ...o-quota-share-cooldown-wait-timing.test.ts | 4 +- ...trategy-fallbacks-half-open-timing.test.ts | 6 +- .../serial/provider-health-autopilot.test.ts | 4 +- .../unit/serial/quota-division-blocks.test.ts | 184 ++++---- tests/unit/services-branch-hardening.test.ts | 2 +- tests/unit/services/ServiceSupervisor.test.ts | 2 +- .../cliproxy-health-model-auth.test.ts | 2 +- .../unit/services/emergency-fallback.test.ts | 4 +- tests/unit/services/end-to-end-shape.test.ts | 2 +- .../bifrost-transport-version-format.test.ts | 28 +- .../unit/services/installers/bifrost.test.ts | 4 +- .../cliproxy-resolve-spawn-args-6877.test.ts | 4 +- .../services/installers/ninerouter.test.ts | 4 +- tests/unit/services/lifecycle.test.ts | 2 +- tests/unit/services/modelSync.test.ts | 2 +- tests/unit/services/portProbePid.test.ts | 2 +- tests/unit/services/ringBuffer.test.ts | 2 +- .../serviceSupervisorSpawnError.test.ts | 7 +- ...on-affinity-combo-timeout-eviction.test.ts | 4 +- .../session-affinity-generic-7274.test.ts | 36 +- tests/unit/session-leases-route.test.ts | 4 +- tests/unit/settings-api.test.ts | 4 +- tests/unit/settings-cas-7784.test.ts | 4 +- tests/unit/settings-debugmode-default.test.ts | 2 +- tests/unit/settings-route-password.test.ts | 4 +- .../structuredLogger-raw-write-guard.test.ts | 2 +- tests/unit/siliconflow-model-sync.test.ts | 4 +- tests/unit/skills-builtins-sandbox.test.ts | 55 +-- tests/unit/skills-collect-routes.test.ts | 4 +- tests/unit/skills-executor.test.ts | 4 +- tests/unit/skills-injection.test.ts | 4 +- tests/unit/skills-interception.test.ts | 14 +- tests/unit/skills-marketplace.test.ts | 4 +- tests/unit/skills-memory-builtins.test.ts | 6 +- tests/unit/skills-registry.test.ts | 4 +- .../skills-routes-error-sanitization.test.ts | 4 +- tests/unit/skills-routes.test.ts | 4 +- tests/unit/skills-skillssh.test.ts | 4 +- tests/unit/sonar-quality-gate-fixes.test.ts | 4 +- .../specialty-model-catalog-routes.test.ts | 4 +- ...ialty-model-hidden-openrouter-9293.test.ts | 4 +- tests/unit/spend-batch-writer.test.ts | 4 +- tests/unit/sre-tcp-close-analyzer.test.ts | 2 +- .../unit/sse-auth-antigravity-credits.test.ts | 4 +- .../unit/sse-auth-codex-account-pool.test.ts | 4 +- tests/unit/sse-auth-exclusive-leases.test.ts | 18 +- tests/unit/sse-auth-resource-404.test.ts | 2 +- tests/unit/sse-auth.test.ts | 4 +- tests/unit/sse-comments-optout-9305.test.ts | 2 +- tests/unit/sse-shim-contract.test.ts | 2 +- .../startup-stale-cooldown-recovery.test.ts | 4 +- .../sticky-affinity-failover-6219.test.ts | 4 +- tests/unit/stmt-cache-lru.test.ts | 8 +- .../unit/stream-claude-delta-contract.test.ts | 2 +- .../stream-impossible-input-usage.test.ts | 2 +- tests/unit/stream-non-json-sse.test.ts | 13 +- tests/unit/stream-numeric-ids.test.ts | 6 +- .../stream-onfailure-callback-logging.test.ts | 7 +- ...stream-prompt-tokens-zero-upstream.test.ts | 7 +- ...stream-request-body-size-mark-7045.test.ts | 6 +- tests/unit/stream-utils.test.ts | 7 +- tests/unit/streamingPiiTransform.test.ts | 2 +- tests/unit/strict-random-deck.test.ts | 7 +- tests/unit/suggested-models-route.test.ts | 2 +- tests/unit/sync-bundle.test.ts | 4 +- .../sync-env-bundled-require-5006.test.ts | 2 +- tests/unit/sync-env.test.ts | 10 +- ...c-reasoning-supported-efforts-7694.test.ts | 14 +- tests/unit/sync-routes.test.ts | 4 +- ...d-effort-suffix-learned-validation.test.ts | 4 +- ...ced-model-context-window-reconcile.test.ts | 4 +- ...synced-model-delete-custom-sibling.test.ts | 2 +- tests/unit/synced-model-delete-resync.test.ts | 2 +- .../synced-model-hide-persist-3782.test.ts | 2 +- tests/unit/system-trust-test-guard.test.ts | 2 +- tests/unit/t07-no-log-key-config.test.ts | 4 +- tests/unit/t08-allowed-connections.test.ts | 4 +- tests/unit/tag-routing.test.ts | 4 +- tests/unit/tailscaleTunnel.test.ts | 4 +- .../unit/telemetry-auto-cleanup-6848.test.ts | 2 +- tests/unit/terminal-status-origin.test.ts | 2 +- tests/unit/termux-android-cache-dir.test.ts | 8 +- .../thinking-budget-hydration-5312.test.ts | 2 +- ...ier-config-provider-override-route.test.ts | 19 +- .../tier-resolver-provider-override.test.ts | 4 +- tests/unit/token-health-check-cursor.test.ts | 6 +- .../token-health-check-devin-cli-8407.test.ts | 4 +- ...en-health-check-retry-deactivation.test.ts | 4 +- tests/unit/token-health-check-sweep.test.ts | 4 +- tests/unit/token-health-check.test.ts | 4 +- ...alth-no-refresh-token-expired-5326.test.ts | 4 +- tests/unit/token-limits.test.ts | 31 +- .../unit/token-refresh-route-service.test.ts | 4 +- tests/unit/tokenHealthCheck-batchSize.test.ts | 4 +- tests/unit/transform-stream-hwm.test.ts | 2 +- .../tunnel-routes-error-sanitization.test.ts | 2 +- tests/unit/turbopack-cache-heal-6289.test.ts | 2 +- .../unit/upstream-ca-test-route-3488.test.ts | 4 +- .../usage-account-analytics-route.test.ts | 4 +- .../usage-analytics-model-dedup-7535.test.ts | 23 +- ...alytics-provider-display-name-7534.test.ts | 4 +- tests/unit/usage-analytics-route.test.ts | 4 +- tests/unit/usage-analytics.test.ts | 34 +- tests/unit/usage-cache-health-route.test.ts | 2 +- tests/unit/usage-endpoint-dimension.test.ts | 4 +- tests/unit/usage-history-db.test.ts | 4 +- tests/unit/usage-history-reset.test.ts | 116 +++-- ...e-migrations-legacy-archive-safety.test.ts | 2 +- tests/unit/usage-migrations.test.ts | 2 +- .../usage-utilization-connection-meta.test.ts | 2 +- tests/unit/usage-vertex-split.test.ts | 2 +- tests/unit/usage-xai-split.test.ts | 2 +- tests/unit/usage-xiaomi-mimo-split.test.ts | 2 +- tests/unit/usage/usageHistoryDedup.test.ts | 2 +- ...chat-completions-content-type-6414.test.ts | 2 +- tests/unit/v1-models-auth-leak-9320.test.ts | 18 +- .../v1-models-catalog-generation-race.test.ts | 4 +- tests/unit/v1-models-catalog-ttl.test.ts | 4 +- tests/unit/v1-models-concurrent-6408.test.ts | 4 +- .../v1-models-discovery-conformance.test.ts | 4 +- tests/unit/v1-ws-route.test.ts | 4 +- tests/unit/v1beta-models-route.test.ts | 4 +- tests/unit/veoaifree-video-route.test.ts | 2 +- .../vercel-gateway-models-fetch-4249.test.ts | 10 +- ...rified-connection-activation-11446.test.ts | 2 +- tests/unit/version-manager.test.ts | 4 +- .../vertex-passthrough-model-lockout.test.ts | 4 +- tests/unit/vertex-spend-usage.test.ts | 210 ++++----- tests/unit/video-combo-route.test.ts | 7 +- .../unit/video-custom-provider-route.test.ts | 2 +- tests/unit/virtual-auto-combo.test.ts | 4 +- ...volcengine-plan-connect-validation.test.ts | 12 +- tests/unit/vscode-responses-models.test.ts | 8 +- tests/unit/vscode-token-routes-gpt56.test.ts | 4 +- ...ode-token-routes-responses-listing.test.ts | 8 +- tests/unit/vscode-token-routes.test.ts | 4 +- tests/unit/warmupScheduler.test.ts | 4 +- tests/unit/web-fetch-dispatch.test.ts | 9 +- tests/unit/web-fetch-quota-fallback.test.ts | 36 +- tests/unit/webdav-server-3485.test.ts | 33 +- tests/unit/webhook-deliveries-db.test.ts | 4 +- .../unit/webhook-metadata-guard-3269.test.ts | 17 +- tests/unit/webhook-private-optin-3269.test.ts | 12 +- tests/unit/webshare-sync.test.ts | 12 +- ...rd-alias-settings-not-applied-7693.test.ts | 4 +- tests/unit/windows-cert-identity-7275.test.ts | 12 +- tests/unit/xai-oauth-usage.test.ts | 2 +- tests/unit/xai-usage.test.ts | 11 +- .../unit/xiaomi-mimo-selftrack-usage.test.ts | 17 +- .../zai-glm-max-tokens-clamp-7364.test.ts | 8 +- .../zai-glm-target-format-override.test.ts | 8 +- tests/unit/zai-web-model-sync-route.test.ts | 4 +- .../zai-web-models-discovery-7678.test.ts | 4 +- tests/unit/zcode-executor.test.ts | 4 +- .../zed-hosted-models-discovery-route.test.ts | 4 +- tests/unit/zenmux-models-fetch-4202.test.ts | 9 +- 1294 files changed, 6389 insertions(+), 5234 deletions(-) create mode 100644 scripts/ad-hoc/codemod-rm-maxretries.mjs diff --git a/scripts/ad-hoc/codemod-rm-maxretries.mjs b/scripts/ad-hoc/codemod-rm-maxretries.mjs new file mode 100644 index 000000000000..e7ac34465675 --- /dev/null +++ b/scripts/ad-hoc/codemod-rm-maxretries.mjs @@ -0,0 +1,101 @@ +#!/usr/bin/env node +/** + * One-shot codemod (#11966): give every recursive temp-dir removal in tests the retry + * options Node already supports, so a WAL/backup/worker still writing into the directory + * turns into a retried delete instead of a red shard: + * + * rmSync(dir, { recursive: true, force: true }) + * → rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }) + * + * Applies to `rmSync(`, `fs.rmSync(`, `rm(` / `fs.rm(` / `fs.promises.rm(` (async) and + * `rmdirSync(` calls whose option object literal contains `recursive: true` and no + * `maxRetries`. Only the option object is touched — call sites, assertions and imports are + * left as they are. Usage: node scripts/ad-hoc/codemod-rm-maxretries.mjs [dir=tests] + */ +import fs from "node:fs"; +import path from "node:path"; + +const root = process.argv[2] || "tests"; +const CALL = /\b(?:fs\.promises\.|fsp\.|fs\.|promises\.)?(?:rmSync|rmdirSync|rm)\(/g; +let files = 0; +let sites = 0; + +function walk(dir, out = []) { + for (const e of fs.readdirSync(dir, { withFileTypes: true })) { + const p = path.join(dir, e.name); + if (e.isDirectory()) { + if (e.name === "node_modules" || e.name === "fixtures") continue; + walk(p, out); + } else if (/\.(ts|tsx|mts|cts|js|mjs|cjs)$/.test(e.name)) out.push(p); + } + return out; +} + +// Find the closing brace of the option object literal that starts at `open`. +function objectEnd(src, open) { + let depth = 0; + for (let i = open; i < src.length; i++) { + const c = src[i]; + if (c === "{") depth++; + else if (c === "}") { + depth--; + if (depth === 0) return i; + } else if (c === '"' || c === "'" || c === "`") { + const q = c; + i++; + while (i < src.length && src[i] !== q) { + if (src[i] === "\\") i++; + i++; + } + } + } + return -1; +} + +for (const file of walk(root)) { + const src = fs.readFileSync(file, "utf8"); + let out = ""; + let last = 0; + let touched = 0; + for (const m of src.matchAll(CALL)) { + const callStart = m.index + m[0].length; + // Locate the option object: the first `{` before the call's closing paren at depth 0. + let depth = 0; + let objOpen = -1; + for (let i = callStart; i < src.length; i++) { + const c = src[i]; + if (c === "(" || c === "[") depth++; + else if (c === ")" || c === "]") { + if (depth === 0) break; + depth--; + } else if (c === "{" && depth === 0) { + objOpen = i; + break; + } + } + if (objOpen === -1) continue; + const objClose = objectEnd(src, objOpen); + if (objClose === -1) continue; + const obj = src.slice(objOpen, objClose + 1); + if (!/\brecursive:\s*true\b/.test(obj) || /\bmaxRetries\b/.test(obj)) continue; + // Insert before the closing brace, respecting an existing trailing comma / newline. + const inner = obj.slice(1, -1); + const trimmed = inner.replace(/\s+$/, ""); + const trailing = inner.slice(trimmed.length); + const sep = trimmed.endsWith(",") ? " " : ", "; + const multiline = /\n/.test(trailing); + const insert = multiline + ? `${trimmed}${trimmed.endsWith(",") ? "" : ","}\n${trailing.replace(/\n$/, "")} maxRetries: 5,\n retryDelay: 100,${trailing}` + : `${trimmed}${sep}maxRetries: 5, retryDelay: 100${trailing}`; + out += src.slice(last, objOpen + 1) + insert; + last = objClose; + touched++; + } + if (touched) { + out += src.slice(last); + fs.writeFileSync(file, out); + files++; + sites += touched; + } +} +console.log(`[codemod-rm-maxretries] ${sites} call site(s) in ${files} file(s) under ${root}`); diff --git a/scripts/check/check-forgotten-sibling-tests.mjs b/scripts/check/check-forgotten-sibling-tests.mjs index bc9dd7ba4dda..6707fc8aa8a8 100644 --- a/scripts/check/check-forgotten-sibling-tests.mjs +++ b/scripts/check/check-forgotten-sibling-tests.mjs @@ -173,7 +173,7 @@ function arg(name, fallback = "") { } function git(root, args) { - return execFileSync("git", args, { cwd: root, encoding: "utf8" }); + return execFileSync("git", args, { cwd: root, encoding: "utf8", maxBuffer: 64 * 1024 * 1024 }); } function changedEntries(root, base) { diff --git a/tests/_setup/isolateDataDir.ts b/tests/_setup/isolateDataDir.ts index 528930d7c2f9..b1add14b894d 100644 --- a/tests/_setup/isolateDataDir.ts +++ b/tests/_setup/isolateDataDir.ts @@ -33,7 +33,7 @@ if (!process.env.DATA_DIR) { // Best-effort cleanup so a long suite run does not leak hundreds of temp DBs. process.on("exit", () => { try { - fs.rmSync(dir, { recursive: true, force: true }); + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // ignore — the OS reaps its temp dir eventually. } diff --git a/tests/e2e/system-failover.test.ts b/tests/e2e/system-failover.test.ts index 5141fadb7f3f..175deab5b55b 100644 --- a/tests/e2e/system-failover.test.ts +++ b/tests/e2e/system-failover.test.ts @@ -366,7 +366,7 @@ test.after(async () => { await serverA.stop(); await serverB.stop(); core.closeDbInstance(); - await fsp.rm(TEST_DATA_DIR, { recursive: true, force: true }); + await fsp.rm(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("primary healthy: request routes to Server A only", async () => { diff --git a/tests/integration/_chatPipelineHarness.ts b/tests/integration/_chatPipelineHarness.ts index 3eb7c2fb7230..c154255914fe 100644 --- a/tests/integration/_chatPipelineHarness.ts +++ b/tests/integration/_chatPipelineHarness.ts @@ -286,7 +286,7 @@ export async function createChatPipelineHarness(prefix) { clearSkillState(); await new Promise((resolve) => setTimeout(resolve, 20)); core.resetDbInstance(); - fs.rmSync(testDataDir, { recursive: true, force: true }); + fs.rmSync(testDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(testDataDir, { recursive: true }); initTranslators(); } @@ -300,7 +300,7 @@ export async function createChatPipelineHarness(prefix) { clearSkillState(); resetAllCircuitBreakers(); core.resetDbInstance(); - fs.rmSync(testDataDir, { recursive: true, force: true }); + fs.rmSync(testDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } async function seedConnection(provider: string, overrides: SeedConnectionOverrides = {}) { diff --git a/tests/integration/agent-bridge-bypass-flow.test.ts b/tests/integration/agent-bridge-bypass-flow.test.ts index 9abe58e3b593..e16f2086582f 100644 --- a/tests/integration/agent-bridge-bypass-flow.test.ts +++ b/tests/integration/agent-bridge-bypass-flow.test.ts @@ -24,7 +24,7 @@ const DEFAULT_PATTERNS = [".bank.", ".gov.", "okta.com", "auth0.com"]; function resetDb() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -34,7 +34,11 @@ test.beforeEach(() => { }); test.after(() => { - try { fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); } catch { /* noop */ } + try { + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + } catch { + /* noop */ + } }); // ── POST patterns ────────────────────────────────────────────────────────── @@ -48,7 +52,10 @@ test("POST /bypass: stores user patterns", async () => { }) ); assert.equal(res.status, 200); - const body = await res.json() as { ok: boolean; patterns: Array<{ pattern: string; source: string }> }; + const body = (await res.json()) as { + ok: boolean; + patterns: Array<{ pattern: string; source: string }>; + }; assert.equal(body.ok, true); assert.ok(Array.isArray(body.patterns)); const userPatterns = body.patterns.filter((p) => p.source === "user"); @@ -64,7 +71,7 @@ test("POST /bypass: invalid body returns 400", async () => { }) ); assert.equal(res.status, 400); - const body = await res.json() as Record; + const body = (await res.json()) as Record; const errMsg = (body.error as Record)?.message as string; assert.ok(!errMsg.includes("at /"), "stack trace leaked in 400 error"); }); @@ -83,7 +90,7 @@ test("GET /bypass: shows default + user patterns", async () => { const res = await bypassRoute.GET(); assert.equal(res.status, 200); - const body = await res.json() as { patterns: Array<{ pattern: string; source: string }> }; + const body = (await res.json()) as { patterns: Array<{ pattern: string; source: string }> }; assert.ok(Array.isArray(body.patterns)); const sources = new Set(body.patterns.map((p) => p.source)); @@ -119,7 +126,10 @@ test("DELETE /bypass?pattern=X: removes a user pattern", async () => { }) ); assert.equal(deleteRes.status, 200); - const deleteBody = await deleteRes.json() as { ok: boolean; patterns: Array<{ pattern: string; source: string }> }; + const deleteBody = (await deleteRes.json()) as { + ok: boolean; + patterns: Array<{ pattern: string; source: string }>; + }; assert.equal(deleteBody.ok, true); // Verify it's gone @@ -142,19 +152,18 @@ test("DELETE /bypass: missing pattern param returns 400", async () => { }) ); assert.equal(res.status, 400); - const body = await res.json() as Record; + const body = (await res.json()) as Record; const errMsg = (body.error as Record)?.message as string; assert.ok(!errMsg.includes("at /"), "stack trace leaked in DELETE 400"); }); test("DELETE /bypass?pattern=X: no-op when pattern not in user list", async () => { const res = await bypassRoute.DELETE( - new Request( - "http://localhost/api/tools/agent-bridge/bypass?pattern=not-in-list.com", - { method: "DELETE" } - ) + new Request("http://localhost/api/tools/agent-bridge/bypass?pattern=not-in-list.com", { + method: "DELETE", + }) ); assert.equal(res.status, 200); - const body = await res.json() as { ok: boolean }; + const body = (await res.json()) as { ok: boolean }; assert.equal(body.ok, true); }); diff --git a/tests/integration/agent-bridge-cert-flow.test.ts b/tests/integration/agent-bridge-cert-flow.test.ts index ee26b9fadec5..717e66dae06d 100644 --- a/tests/integration/agent-bridge-cert-flow.test.ts +++ b/tests/integration/agent-bridge-cert-flow.test.ts @@ -19,7 +19,8 @@ process.env.DISABLE_SQLITE_AUTO_BACKUP = "true"; const certRoute = await import("../../src/app/api/tools/agent-bridge/cert/route.ts"); const downloadRoute = await import("../../src/app/api/tools/agent-bridge/cert/download/route.ts"); -const regenerateRoute = await import("../../src/app/api/tools/agent-bridge/cert/regenerate/route.ts"); +const regenerateRoute = + await import("../../src/app/api/tools/agent-bridge/cert/regenerate/route.ts"); function certDir() { return path.join(TEST_DATA_DIR, "mitm"); @@ -30,7 +31,7 @@ function certFilePath() { } function resetCertDir() { - fs.rmSync(certDir(), { recursive: true, force: true }); + fs.rmSync(certDir(), { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(certDir(), { recursive: true }); } @@ -39,7 +40,11 @@ test.beforeEach(() => { }); test.after(() => { - try { fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); } catch { /* noop */ } + try { + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + } catch { + /* noop */ + } }); // ── GET /cert ───────────────────────────────────────────────────────────── @@ -47,7 +52,7 @@ test.after(() => { test("GET /cert: returns exists:false when no cert file", async () => { const res = await certRoute.GET(); assert.equal(res.status, 200); - const body = await res.json() as Record; + const body = (await res.json()) as Record; assert.equal(body.exists, false); assert.equal(body.trusted, false); assert.equal(body.path, null); @@ -59,7 +64,7 @@ test("GET /cert: returns exists:true when cert file present", async () => { const res = await certRoute.GET(); assert.equal(res.status, 200); - const body = await res.json() as Record; + const body = (await res.json()) as Record; assert.equal(body.exists, true); // trusted may be false in test env (no system store) assert.ok(typeof body.trusted === "boolean"); @@ -83,7 +88,7 @@ test("POST /cert: returns 404 when no cert file", async () => { }) ); assert.equal(res.status, 404); - const body = await res.json() as Record; + const body = (await res.json()) as Record; const errMsg = (body.error as Record)?.message as string; assert.ok(!errMsg.includes("at /"), "stack trace leaked in 404 error message"); }); @@ -106,7 +111,7 @@ MIIBpDCCAQ2gAwIBAgIUFakeMITMCertForTestingOnlyXX== // In test env: installCert may throw because the PEM is fake; we accept // either 200 (mocked) or 500 (real OS failure) — NOT a 500 with stack trace - const body = await res.json() as Record; + const body = (await res.json()) as Record; const errMsg = (body.error as Record)?.message as string | undefined; if (errMsg) { assert.ok(!errMsg.includes("at /"), "stack trace leaked in POST /cert error"); @@ -118,7 +123,7 @@ MIIBpDCCAQ2gAwIBAgIUFakeMITMCertForTestingOnlyXX== test("GET /cert/download: 404 when no cert file", async () => { const res = await downloadRoute.GET(); assert.equal(res.status, 404); - const body = await res.json() as Record; + const body = (await res.json()) as Record; const errMsg = (body.error as Record)?.message as string; assert.ok(!errMsg.includes("at /"), "stack trace leaked in download 404"); }); diff --git a/tests/integration/agent-bridge-mappings.test.ts b/tests/integration/agent-bridge-mappings.test.ts index 0dec4bb1e411..9c5d8773a60f 100644 --- a/tests/integration/agent-bridge-mappings.test.ts +++ b/tests/integration/agent-bridge-mappings.test.ts @@ -18,13 +18,12 @@ process.env.DATA_DIR = TEST_DATA_DIR; process.env.DISABLE_SQLITE_AUTO_BACKUP = "true"; const core = await import("../../src/lib/db/core.ts"); -const mappingsRoute = await import( - "../../src/app/api/tools/agent-bridge/agents/[id]/mappings/route.ts" -); +const mappingsRoute = + await import("../../src/app/api/tools/agent-bridge/agents/[id]/mappings/route.ts"); function resetDb() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -33,18 +32,21 @@ test.beforeEach(() => { }); test.after(() => { - try { fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); } catch { /* noop */ } + try { + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + } catch { + /* noop */ + } }); // ── GET (empty) ──────────────────────────────────────────────────────────── test("GET /mappings: returns empty array for new agent", async () => { - const res = await mappingsRoute.GET( - new Request("http://localhost/"), - { params: { id: "copilot" } } - ); + const res = await mappingsRoute.GET(new Request("http://localhost/"), { + params: { id: "copilot" }, + }); assert.equal(res.status, 200); - const body = await res.json() as { mappings: unknown[] }; + const body = (await res.json()) as { mappings: unknown[] }; assert.ok(Array.isArray(body.mappings)); assert.equal(body.mappings.length, 0); }); @@ -66,17 +68,21 @@ test("PUT → GET round-trip: stores and retrieves mappings", async () => { { params: { id: "copilot" } } ); assert.equal(putRes.status, 200); - const putBody = await putRes.json() as { ok: boolean; mappings: Array<{ agent_id: string; source_model: string; target_model: string }> }; + const putBody = (await putRes.json()) as { + ok: boolean; + mappings: Array<{ agent_id: string; source_model: string; target_model: string }>; + }; assert.equal(putBody.ok, true); assert.equal(putBody.mappings.length, 2); // GET reads back the same data - const getRes = await mappingsRoute.GET( - new Request("http://localhost/"), - { params: { id: "copilot" } } - ); + const getRes = await mappingsRoute.GET(new Request("http://localhost/"), { + params: { id: "copilot" }, + }); assert.equal(getRes.status, 200); - const getBody = await getRes.json() as { mappings: Array<{ source_model: string; target_model: string }> }; + const getBody = (await getRes.json()) as { + mappings: Array<{ source_model: string; target_model: string }>; + }; assert.equal(getBody.mappings.length, 2); const sources = getBody.mappings.map((m) => m.source_model).sort(); @@ -108,11 +114,10 @@ test("PUT: replaces all previous mappings", async () => { ); assert.equal(putRes.status, 200); - const getRes = await mappingsRoute.GET( - new Request("http://localhost/"), - { params: { id: "cursor" } } - ); - const body = await getRes.json() as { mappings: Array<{ source_model: string }> }; + const getRes = await mappingsRoute.GET(new Request("http://localhost/"), { + params: { id: "cursor" }, + }); + const body = (await getRes.json()) as { mappings: Array<{ source_model: string }> }; assert.equal(body.mappings.length, 1); assert.equal(body.mappings[0].source_model, "new-model"); }); @@ -136,7 +141,7 @@ test("PUT: empty mappings array clears all mappings", async () => { { params: { id: "zed" } } ); assert.equal(putRes.status, 200); - const body = await putRes.json() as { mappings: unknown[] }; + const body = (await putRes.json()) as { mappings: unknown[] }; assert.equal(body.mappings.length, 0); }); @@ -152,7 +157,7 @@ test("PUT: invalid body (missing mappings) returns 400", async () => { { params: { id: "antigravity" } } ); assert.equal(res.status, 400); - const body = await res.json() as Record; + const body = (await res.json()) as Record; const errMsg = (body.error as Record)?.message as string; assert.ok(!errMsg.includes("at /"), "stack trace leaked in 400 error"); }); @@ -183,10 +188,9 @@ test("PUT: error responses do not leak stack traces", async () => { }); test("GET: error responses do not leak stack traces", async () => { - const res = await mappingsRoute.GET( - new Request("http://localhost/"), - { params: { id: "antigravity" } } - ); + const res = await mappingsRoute.GET(new Request("http://localhost/"), { + params: { id: "antigravity" }, + }); const text = await res.text(); assert.ok(!text.includes("at /"), "stack trace leaked in GET /mappings response"); }); diff --git a/tests/integration/agent-bridge-routes.test.ts b/tests/integration/agent-bridge-routes.test.ts index 21b59b20c7fd..d4f18138ac66 100644 --- a/tests/integration/agent-bridge-routes.test.ts +++ b/tests/integration/agent-bridge-routes.test.ts @@ -39,7 +39,7 @@ const routeGuard = await import("../../src/server/authz/routeGuard.ts"); function resetDb() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -49,7 +49,7 @@ test.beforeEach(() => { test.after(() => { try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { /* noop */ } diff --git a/tests/integration/all-statuses-route.test.ts b/tests/integration/all-statuses-route.test.ts index 858b09e99387..4c3924020c0c 100644 --- a/tests/integration/all-statuses-route.test.ts +++ b/tests/integration/all-statuses-route.test.ts @@ -39,7 +39,7 @@ async function resetStorage() { delete process.env.INITIAL_PASSWORD; core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -55,7 +55,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ── Auth tests ──────────────────────────────────────────────────────────────── @@ -288,6 +288,6 @@ test("grok-build status uses GROK_HOME and returns its managed endpoint", async } finally { if (original === undefined) delete process.env.GROK_HOME; else process.env.GROK_HOME = original; - fs.rmSync(grokHome, { recursive: true, force: true }); + fs.rmSync(grokHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/integration/antigravity-projectid-discovery-persist-8491.test.ts b/tests/integration/antigravity-projectid-discovery-persist-8491.test.ts index e2d5b402b5ef..96dae418c3b3 100644 --- a/tests/integration/antigravity-projectid-discovery-persist-8491.test.ts +++ b/tests/integration/antigravity-projectid-discovery-persist-8491.test.ts @@ -25,14 +25,13 @@ process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "test-8491-antigravit const core = await import("../../src/lib/db/core.ts"); const providersDb = await import("../../src/lib/db/providers.ts"); const { AntigravityExecutor } = await import("../../open-sse/executors/antigravity.ts"); -const { clearAntigravityProjectCache } = await import( - "../../open-sse/services/antigravityProjectBootstrap.ts" -); +const { clearAntigravityProjectCache } = + await import("../../open-sse/services/antigravityProjectBootstrap.ts"); test.after(() => { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -91,7 +90,11 @@ test("#8491 PART A: runtime-discovered projectId must be persisted to the connec throw new Error(`Expected an envelope but got a ${result.status} Response`); } assert.equal(loadCodeAssistCalls, 1, "loadCodeAssist must be called to recover the project"); - assert.equal(result.project, DISCOVERED_PROJECT_ID, "the in-flight request uses the discovered id"); + assert.equal( + result.project, + DISCOVERED_PROJECT_ID, + "the in-flight request uses the discovered id" + ); const persisted = await providersDb.getProviderConnectionById(connection.id); assert.equal( diff --git a/tests/integration/api-keys.test.ts b/tests/integration/api-keys.test.ts index 0dfc6c33713f..51e667e1bd86 100644 --- a/tests/integration/api-keys.test.ts +++ b/tests/integration/api-keys.test.ts @@ -25,7 +25,7 @@ async function resetStorage() { delete process.env.INITIAL_PASSWORD; core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -63,7 +63,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("API keys routes require management auth when login protection is enabled", async () => { diff --git a/tests/integration/api-routes-critical.test.ts b/tests/integration/api-routes-critical.test.ts index 10534d2808ab..7f04c9245bb7 100644 --- a/tests/integration/api-routes-critical.test.ts +++ b/tests/integration/api-routes-critical.test.ts @@ -24,7 +24,7 @@ async function resetStorage() { delete process.env.ENABLE_SOCKS5_PROXY; core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -59,7 +59,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("critical routes: v1 management proxies covers auth, lookup, where-used, patch, and delete branches", async () => { diff --git a/tests/integration/audit-log-level-filter.test.ts b/tests/integration/audit-log-level-filter.test.ts index 2517da850dbf..d694fe44be36 100644 --- a/tests/integration/audit-log-level-filter.test.ts +++ b/tests/integration/audit-log-level-filter.test.ts @@ -20,7 +20,7 @@ const auditRoute = await import("../../src/app/api/compliance/audit-log/route.ts function resetDb() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -38,7 +38,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); /** diff --git a/tests/integration/batch-e2e-rate-limit.test.ts b/tests/integration/batch-e2e-rate-limit.test.ts index beeaea7e62fc..3460efbf9557 100644 --- a/tests/integration/batch-e2e-rate-limit.test.ts +++ b/tests/integration/batch-e2e-rate-limit.test.ts @@ -268,7 +268,7 @@ async function stopProcess(child: ReturnType) { async function removeDirWithRetry(dir: string) { for (let attempt = 0; attempt < 5; attempt++) { try { - fs.rmSync(dir, { recursive: true, force: true }); + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); return; } catch (error) { if (attempt === 4) throw error; diff --git a/tests/integration/chat-pipeline.test.ts b/tests/integration/chat-pipeline.test.ts index c17ce8eb6e50..a7d441a9a5e0 100644 --- a/tests/integration/chat-pipeline.test.ts +++ b/tests/integration/chat-pipeline.test.ts @@ -373,7 +373,7 @@ async function resetStorage() { invalidateMemorySettingsCache(); await new Promise((resolve) => setTimeout(resolve, 20)); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); initTranslators(); } @@ -512,7 +512,7 @@ test.after(async () => { clearInflight(); resetAllCircuitBreakers(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("chat pipeline handles OpenAI passthrough with valid API key auth", async () => { diff --git a/tests/integration/chatcore-compression-integration.test.ts b/tests/integration/chatcore-compression-integration.test.ts index ab59dc0347c3..fd5038e711a1 100644 --- a/tests/integration/chatcore-compression-integration.test.ts +++ b/tests/integration/chatcore-compression-integration.test.ts @@ -28,7 +28,7 @@ async function resetStorage() { readCacheDb.invalidateDbCache(); await new Promise((resolve) => setTimeout(resolve, 20)); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -40,7 +40,7 @@ test.after(async () => { globalThis.fetch = originalFetch; core.closeDbInstance(); try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch {} }); diff --git a/tests/integration/chatcore-context-window-boundary.test.ts b/tests/integration/chatcore-context-window-boundary.test.ts index 9d48704a6f18..5d517e8337c1 100644 --- a/tests/integration/chatcore-context-window-boundary.test.ts +++ b/tests/integration/chatcore-context-window-boundary.test.ts @@ -21,7 +21,7 @@ test.after(async () => { globalThis.fetch = originalFetch; core.closeDbInstance(); try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch {} }); diff --git a/tests/integration/cli-settings-codewhale.test.ts b/tests/integration/cli-settings-codewhale.test.ts index 8a98ff547b3e..ed843a1373fc 100644 --- a/tests/integration/cli-settings-codewhale.test.ts +++ b/tests/integration/cli-settings-codewhale.test.ts @@ -22,12 +22,13 @@ process.env.JWT_SECRET = "test-jwt-secret-codewhale"; const core = await import("../../src/lib/db/core.ts"); const localDb = await import("../../src/lib/localDb.ts"); -const { GET, POST, DELETE } = await import("../../src/app/api/cli-tools/codewhale-settings/route.ts"); +const { GET, POST, DELETE } = + await import("../../src/app/api/cli-tools/codewhale-settings/route.ts"); async function resetStorage() { delete process.env.INITIAL_PASSWORD; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -126,7 +127,7 @@ test("codewhale-settings POST: writes primary ~/.codewhale/config.toml for a fre } } finally { process.env.HOME = origHome; - fs.rmSync(tmpHome, { recursive: true, force: true }); + fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -173,7 +174,7 @@ test("codewhale-settings POST: syncs an existing legacy ~/.deepseek/config.toml" } } finally { process.env.HOME = origHome; - fs.rmSync(tmpHome, { recursive: true, force: true }); + fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -201,7 +202,7 @@ test("codewhale-settings GET: falls back to legacy ~/.deepseek/config.toml when } } finally { process.env.HOME = origHome; - fs.rmSync(tmpHome, { recursive: true, force: true }); + fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -238,7 +239,7 @@ test("codewhale-settings DELETE: removes primary and legacy config files", async } } finally { process.env.HOME = origHome; - fs.rmSync(tmpHome, { recursive: true, force: true }); + fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -272,7 +273,7 @@ test("codewhale-settings route.ts: does not call exec() or spawn() directly", () test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); delete process.env.DATA_DIR; delete process.env.API_KEY_SECRET; delete process.env.JWT_SECRET; diff --git a/tests/integration/cli-settings-deepseek-tui.test.ts b/tests/integration/cli-settings-deepseek-tui.test.ts index 7bd6b83944ec..a4d7ffee60de 100644 --- a/tests/integration/cli-settings-deepseek-tui.test.ts +++ b/tests/integration/cli-settings-deepseek-tui.test.ts @@ -8,9 +8,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -const TEST_DATA_DIR = fs.mkdtempSync( - path.join(os.tmpdir(), "omniroute-deepseek-tui-settings-") -); +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-deepseek-tui-settings-")); process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = "test-api-key-secret-deepseek-tui"; process.env.JWT_SECRET = "test-jwt-secret-deepseek-tui"; @@ -18,14 +16,13 @@ process.env.JWT_SECRET = "test-jwt-secret-deepseek-tui"; const core = await import("../../src/lib/db/core.ts"); const localDb = await import("../../src/lib/localDb.ts"); -const { GET, POST, DELETE } = await import( - "../../src/app/api/cli-tools/deepseek-tui-settings/route.ts" -); +const { GET, POST, DELETE } = + await import("../../src/app/api/cli-tools/deepseek-tui-settings/route.ts"); async function resetStorage() { delete process.env.INITIAL_PASSWORD; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -103,10 +100,7 @@ test("deepseek-tui-settings POST: writes config.toml with valid body", async () }), }) ); - assert.ok( - [200, 403, 500].includes(res.status), - `Unexpected status ${res.status}` - ); + assert.ok([200, 403, 500].includes(res.status), `Unexpected status ${res.status}`); if (res.status === 200) { const body = await res.json(); assert.equal(body.success, true); @@ -120,7 +114,7 @@ test("deepseek-tui-settings POST: writes config.toml with valid body", async () } } finally { process.env.HOME = origHome; - fs.rmSync(tmpHome, { recursive: true, force: true }); + fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -136,23 +130,20 @@ test("deepseek-tui-settings DELETE: removes config file", async () => { fs.mkdirSync(configDir, { recursive: true }); fs.writeFileSync( path.join(configDir, "config.toml"), - "# managed by OmniRoute (plan 14)\n[openai]\nbase_url = \"http://localhost:20128\"\n" + '# managed by OmniRoute (plan 14)\n[openai]\nbase_url = "http://localhost:20128"\n' ); const res = await DELETE( new Request("http://localhost/api/cli-tools/deepseek-tui-settings", { method: "DELETE" }) ); - assert.ok( - [200, 403, 500].includes(res.status), - `Expected 200/403/500, got ${res.status}` - ); + assert.ok([200, 403, 500].includes(res.status), `Expected 200/403/500, got ${res.status}`); if (res.status === 200) { const body = await res.json(); assert.equal(body.success, true); } } finally { process.env.HOME = origHome; - fs.rmSync(tmpHome, { recursive: true, force: true }); + fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -186,7 +177,7 @@ test("deepseek-tui-settings route.ts: does not call exec() or spawn() directly", test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); delete process.env.DATA_DIR; delete process.env.API_KEY_SECRET; delete process.env.JWT_SECRET; diff --git a/tests/integration/cli-settings-forge.test.ts b/tests/integration/cli-settings-forge.test.ts index 1398b0354436..8cb4b7538e31 100644 --- a/tests/integration/cli-settings-forge.test.ts +++ b/tests/integration/cli-settings-forge.test.ts @@ -19,14 +19,12 @@ const core = await import("../../src/lib/db/core.ts"); const localDb = await import("../../src/lib/localDb.ts"); // Import route handlers -const { GET, POST, DELETE } = await import( - "../../src/app/api/cli-tools/forge-settings/route.ts" -); +const { GET, POST, DELETE } = await import("../../src/app/api/cli-tools/forge-settings/route.ts"); async function resetStorage() { delete process.env.INITIAL_PASSWORD; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -107,10 +105,7 @@ test("forge-settings POST: writes config.toml with valid body", async () => { ); // 200 = success; 403 = write guard active (test env); 500 = backup dir issue - assert.ok( - [200, 403, 500].includes(res.status), - `Unexpected status ${res.status}` - ); + assert.ok([200, 403, 500].includes(res.status), `Unexpected status ${res.status}`); if (res.status === 200) { const body = await res.json(); @@ -126,7 +121,7 @@ test("forge-settings POST: writes config.toml with valid body", async () => { } } finally { process.env.HOME = origHome; - fs.rmSync(tmpHome, { recursive: true, force: true }); + fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -143,16 +138,13 @@ test("forge-settings DELETE: removes config file when it exists", async () => { fs.mkdirSync(forgeDir, { recursive: true }); fs.writeFileSync( path.join(forgeDir, "config.toml"), - "# managed by OmniRoute (plan 14)\n[openai]\nbase_url = \"http://localhost:20128\"\n" + '# managed by OmniRoute (plan 14)\n[openai]\nbase_url = "http://localhost:20128"\n' ); const res = await DELETE( new Request("http://localhost/api/cli-tools/forge-settings", { method: "DELETE" }) ); - assert.ok( - [200, 403, 500].includes(res.status), - `Expected 200/403/500, got ${res.status}` - ); + assert.ok([200, 403, 500].includes(res.status), `Expected 200/403/500, got ${res.status}`); if (res.status === 200) { const body = await res.json(); @@ -160,7 +152,7 @@ test("forge-settings DELETE: removes config file when it exists", async () => { } } finally { process.env.HOME = origHome; - fs.rmSync(tmpHome, { recursive: true, force: true }); + fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -194,7 +186,7 @@ test("forge-settings route.ts: does not call exec() or spawn() directly", () => test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); delete process.env.DATA_DIR; delete process.env.API_KEY_SECRET; delete process.env.JWT_SECRET; diff --git a/tests/integration/cli-settings-grok-build.test.ts b/tests/integration/cli-settings-grok-build.test.ts index 3353d3602a94..8ab679d9c27e 100644 --- a/tests/integration/cli-settings-grok-build.test.ts +++ b/tests/integration/cli-settings-grok-build.test.ts @@ -39,7 +39,7 @@ const { GET, POST, DELETE } = async function resetStorage() { delete process.env.INITIAL_PASSWORD; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -163,7 +163,7 @@ test("grok-build-settings POST: writes [model.omniroute] section and preserves e } } finally { process.env.HOME = origHome; - fs.rmSync(tmpHome, { recursive: true, force: true }); + fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -219,7 +219,7 @@ test("grok-build-settings DELETE: removes our section, preserves the rest, resto } } finally { process.env.HOME = origHome; - fs.rmSync(tmpHome, { recursive: true, force: true }); + fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -237,7 +237,7 @@ test("grok-build-settings DELETE: no-op success when no config file exists", asy assert.equal(body.success, true); } finally { process.env.HOME = origHome; - fs.rmSync(tmpHome, { recursive: true, force: true }); + fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -289,7 +289,7 @@ test("grok-build-settings: honors GROK_HOME and rejects a relative value", async } finally { if (original === undefined) delete process.env.GROK_HOME; else process.env.GROK_HOME = original; - fs.rmSync(grokHome, { recursive: true, force: true }); + fs.rmSync(grokHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -313,7 +313,7 @@ test("grok-build-settings POST: returns 409 for an unowned omniroute slot", asyn } finally { if (original === undefined) delete process.env.GROK_HOME; else process.env.GROK_HOME = original; - fs.rmSync(grokHome, { recursive: true, force: true }); + fs.rmSync(grokHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -342,7 +342,7 @@ test("grok-build-settings POST: resolves keyId to an unmasked key", async () => } finally { if (original === undefined) delete process.env.GROK_HOME; else process.env.GROK_HOME = original; - fs.rmSync(grokHome, { recursive: true, force: true }); + fs.rmSync(grokHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -376,7 +376,7 @@ test("grok-build-settings route.ts: does not call exec() or spawn() directly", ( test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); delete process.env.DATA_DIR; delete process.env.API_KEY_SECRET; delete process.env.JWT_SECRET; diff --git a/tests/integration/cli-settings-jcode.test.ts b/tests/integration/cli-settings-jcode.test.ts index 712b4186ba19..6d1930aa83ea 100644 --- a/tests/integration/cli-settings-jcode.test.ts +++ b/tests/integration/cli-settings-jcode.test.ts @@ -21,7 +21,7 @@ const { GET, POST, DELETE } = await import("../../src/app/api/cli-tools/jcode-se async function resetStorage() { delete process.env.INITIAL_PASSWORD; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -115,7 +115,7 @@ test("jcode-settings POST: writes [providers.omniroute] into config.toml", async } } finally { process.env.HOME = origHome; - fs.rmSync(tmpHome, { recursive: true, force: true }); + fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -159,7 +159,7 @@ test("jcode-settings DELETE: removes only the OmniRoute-managed block", async () } } finally { process.env.HOME = origHome; - fs.rmSync(tmpHome, { recursive: true, force: true }); + fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -193,7 +193,7 @@ test("jcode-settings route.ts: does not call exec() or spawn() directly", () => test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); delete process.env.DATA_DIR; delete process.env.API_KEY_SECRET; delete process.env.JWT_SECRET; diff --git a/tests/integration/cli-settings-letta.test.ts b/tests/integration/cli-settings-letta.test.ts index 881e8a32bf62..d6a07ddc69b5 100644 --- a/tests/integration/cli-settings-letta.test.ts +++ b/tests/integration/cli-settings-letta.test.ts @@ -22,9 +22,7 @@ process.env.JWT_SECRET = "test-jwt-secret-letta"; const core = await import("../../src/lib/db/core.ts"); const localDb = await import("../../src/lib/localDb.ts"); -const { GET, POST, DELETE } = await import( - "../../src/app/api/cli-tools/letta-settings/route.ts" -); +const { GET, POST, DELETE } = await import("../../src/app/api/cli-tools/letta-settings/route.ts"); let tmpHome: string; let origHome: string | undefined; @@ -40,7 +38,7 @@ function req(init?: RequestInit) { async function resetStorage() { delete process.env.INITIAL_PASSWORD; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -58,7 +56,7 @@ test.beforeEach(async () => { test.afterEach(() => { process.env.HOME = origHome; - fs.rmSync(tmpHome, { recursive: true, force: true }); + fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ── Test 1: GET without auth → 401 ────────────────────────────────────────── @@ -189,7 +187,7 @@ test("letta-settings: error responses do not leak stack traces", async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); delete process.env.DATA_DIR; delete process.env.API_KEY_SECRET; delete process.env.JWT_SECRET; diff --git a/tests/integration/cli-settings-omp.test.ts b/tests/integration/cli-settings-omp.test.ts index 54ae67f3a100..c07715ff4fbc 100644 --- a/tests/integration/cli-settings-omp.test.ts +++ b/tests/integration/cli-settings-omp.test.ts @@ -59,7 +59,7 @@ function seedOmpDb() { async function resetStorage() { delete process.env.INITIAL_PASSWORD; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -77,7 +77,7 @@ test.beforeEach(async () => { test.afterEach(() => { process.env.HOME = origHome; - fs.rmSync(tmpHome, { recursive: true, force: true }); + fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ── Test 1: GET without auth → 401 ────────────────────────────────────────── @@ -190,7 +190,7 @@ test("omp-settings: error responses do not leak stack traces", async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); delete process.env.DATA_DIR; delete process.env.API_KEY_SECRET; delete process.env.JWT_SECRET; diff --git a/tests/integration/cli-settings-pi.test.ts b/tests/integration/cli-settings-pi.test.ts index 92faea9b0a3c..29133efd51eb 100644 --- a/tests/integration/cli-settings-pi.test.ts +++ b/tests/integration/cli-settings-pi.test.ts @@ -16,14 +16,12 @@ process.env.JWT_SECRET = "test-jwt-secret-pi"; const core = await import("../../src/lib/db/core.ts"); const localDb = await import("../../src/lib/localDb.ts"); -const { GET, POST, DELETE } = await import( - "../../src/app/api/cli-tools/pi-settings/route.ts" -); +const { GET, POST, DELETE } = await import("../../src/app/api/cli-tools/pi-settings/route.ts"); async function resetStorage() { delete process.env.INITIAL_PASSWORD; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -101,10 +99,7 @@ test("pi-settings POST: writes config.json with valid body", async () => { }), }) ); - assert.ok( - [200, 403, 500].includes(res.status), - `Unexpected status ${res.status}` - ); + assert.ok([200, 403, 500].includes(res.status), `Unexpected status ${res.status}`); if (res.status === 200) { const body = await res.json(); assert.equal(body.success, true); @@ -118,7 +113,7 @@ test("pi-settings POST: writes config.json with valid body", async () => { } } finally { process.env.HOME = origHome; - fs.rmSync(tmpHome, { recursive: true, force: true }); + fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -145,17 +140,14 @@ test("pi-settings DELETE: removes OmniRoute fields from existing config", async const res = await DELETE( new Request("http://localhost/api/cli-tools/pi-settings", { method: "DELETE" }) ); - assert.ok( - [200, 403, 500].includes(res.status), - `Expected 200/403/500, got ${res.status}` - ); + assert.ok([200, 403, 500].includes(res.status), `Expected 200/403/500, got ${res.status}`); if (res.status === 200) { const body = await res.json(); assert.equal(body.success, true); } } finally { process.env.HOME = origHome; - fs.rmSync(tmpHome, { recursive: true, force: true }); + fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -189,7 +181,7 @@ test("pi-settings route.ts: does not call exec() or spawn() directly", () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); delete process.env.DATA_DIR; delete process.env.API_KEY_SECRET; delete process.env.JWT_SECRET; diff --git a/tests/integration/cli-settings-smelt.test.ts b/tests/integration/cli-settings-smelt.test.ts index 7689fb58da70..8395b4f1eb95 100644 --- a/tests/integration/cli-settings-smelt.test.ts +++ b/tests/integration/cli-settings-smelt.test.ts @@ -16,14 +16,12 @@ process.env.JWT_SECRET = "test-jwt-secret-smelt"; const core = await import("../../src/lib/db/core.ts"); const localDb = await import("../../src/lib/localDb.ts"); -const { GET, POST, DELETE } = await import( - "../../src/app/api/cli-tools/smelt-settings/route.ts" -); +const { GET, POST, DELETE } = await import("../../src/app/api/cli-tools/smelt-settings/route.ts"); async function resetStorage() { delete process.env.INITIAL_PASSWORD; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -101,10 +99,7 @@ test("smelt-settings POST: writes config.json with valid body", async () => { }), }) ); - assert.ok( - [200, 403, 500].includes(res.status), - `Unexpected status ${res.status}` - ); + assert.ok([200, 403, 500].includes(res.status), `Unexpected status ${res.status}`); if (res.status === 200) { const body = await res.json(); assert.equal(body.success, true); @@ -118,7 +113,7 @@ test("smelt-settings POST: writes config.json with valid body", async () => { } } finally { process.env.HOME = origHome; - fs.rmSync(tmpHome, { recursive: true, force: true }); + fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -145,17 +140,14 @@ test("smelt-settings DELETE: removes OmniRoute fields from existing config", asy const res = await DELETE( new Request("http://localhost/api/cli-tools/smelt-settings", { method: "DELETE" }) ); - assert.ok( - [200, 403, 500].includes(res.status), - `Expected 200/403/500, got ${res.status}` - ); + assert.ok([200, 403, 500].includes(res.status), `Expected 200/403/500, got ${res.status}`); if (res.status === 200) { const body = await res.json(); assert.equal(body.success, true); } } finally { process.env.HOME = origHome; - fs.rmSync(tmpHome, { recursive: true, force: true }); + fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -189,7 +181,7 @@ test("smelt-settings route.ts: does not call exec() or spawn() directly", () => test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); delete process.env.DATA_DIR; delete process.env.API_KEY_SECRET; delete process.env.JWT_SECRET; diff --git a/tests/integration/codex-account-pool-restart-http.test.ts b/tests/integration/codex-account-pool-restart-http.test.ts index 66018cc93f1a..2787c8d29336 100644 --- a/tests/integration/codex-account-pool-restart-http.test.ts +++ b/tests/integration/codex-account-pool-restart-http.test.ts @@ -44,6 +44,6 @@ test("Codex Spark cooldown survives a fresh process without creating child conne assert.equal(after.connectionId, before.connectionId); assert.deepEqual(after.upstreamModels, ["gpt-5.5"]); } finally { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/integration/codex-chat-reasoning-http-e2e.test.ts b/tests/integration/codex-chat-reasoning-http-e2e.test.ts index 051b5ba23549..28bea94534ac 100644 --- a/tests/integration/codex-chat-reasoning-http-e2e.test.ts +++ b/tests/integration/codex-chat-reasoning-http-e2e.test.ts @@ -305,6 +305,6 @@ test("chat completions streams Codex Responses reasoning through real route HTTP globalThis.fetch = originalFetch; if (routeServer) await closeServer(routeServer); core.closeDbInstance({ checkpointMode: null }); - await fsp.rm(TEST_DATA_DIR, { recursive: true, force: true }); + await fsp.rm(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/integration/combo-live/_liveHarness.ts b/tests/integration/combo-live/_liveHarness.ts index c415bc648b30..98f6a05f5b1d 100644 --- a/tests/integration/combo-live/_liveHarness.ts +++ b/tests/integration/combo-live/_liveHarness.ts @@ -46,18 +46,18 @@ const IN_SCOPE_PROVIDERS = new Set([ // Provider → sensible default model (fallback when default_model is null). const PROVIDER_DEFAULT_MODELS: Record = { - "claude": "claude-3-5-haiku-20241022", - "glm": "glm-4-flash", - "minimax": "minimax-text-01", + claude: "claude-3-5-haiku-20241022", + glm: "glm-4-flash", + minimax: "minimax-text-01", "kimi-coding-apikey": "moonshot-v1-8k", "ollama-cloud": "llama3.2:3b", "opencode-go": "gpt-4o-mini", - "gemini": "gemini-2.0-flash-lite", - "deepseek": "deepseek-chat", - "groq": "llama-3.1-8b-instant", - "cerebras": "llama-3.1-8b", - "openrouter": "openai/gpt-4o-mini", - "together": "meta-llama/Llama-3-8b-chat-hf", + gemini: "gemini-2.0-flash-lite", + deepseek: "deepseek-chat", + groq: "llama-3.1-8b-instant", + cerebras: "llama-3.1-8b", + openrouter: "openai/gpt-4o-mini", + together: "meta-llama/Llama-3-8b-chat-hf", }; // --------------------------------------------------------------------------- @@ -79,9 +79,11 @@ export type ComboModelEntry = { connectionId: string; }; -export type LiveHarness = { - LIVE_ENABLED: false; -} | LiveHarnessEnabled; +export type LiveHarness = + | { + LIVE_ENABLED: false; + } + | LiveHarnessEnabled; export type LiveHarnessEnabled = { LIVE_ENABLED: true; @@ -146,12 +148,12 @@ export async function createLiveHarness(prefix: string): Promise { } } } catch (err: any) { - fs.rmSync(snapshotDir, { recursive: true, force: true }); + fs.rmSync(snapshotDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); throw new Error(`[liveHarness] Failed to fetch VPS secrets via ssh: ${err.message}`); } if (!storageEncryptionKey || !apiKeySecret) { - fs.rmSync(snapshotDir, { recursive: true, force: true }); + fs.rmSync(snapshotDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); throw new Error( "[liveHarness] Could not parse STORAGE_ENCRYPTION_KEY or API_KEY_SECRET from VPS .env" ); @@ -176,13 +178,11 @@ export async function createLiveHarness(prefix: string): Promise { const snapshotDbPath = path.join(snapshotDir, "storage.sqlite"); try { - execFileSync( - "scp", - ["root@192.168.0.15:/root/.omniroute/storage.sqlite", snapshotDbPath], - { timeout: 60_000 } - ); + execFileSync("scp", ["root@192.168.0.15:/root/.omniroute/storage.sqlite", snapshotDbPath], { + timeout: 60_000, + }); } catch (err: any) { - fs.rmSync(snapshotDir, { recursive: true, force: true }); + fs.rmSync(snapshotDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); throw new Error(`[liveHarness] Failed to scp production DB: ${err.message}`); } @@ -262,7 +262,10 @@ export async function createLiveHarness(prefix: string): Promise { }); } - function liveBody(model: string, overrides: Record = {}): Record { + function liveBody( + model: string, + overrides: Record = {} + ): Record { return { model, stream: false, @@ -400,7 +403,7 @@ export async function createLiveHarness(prefix: string): Promise { resetAllCircuitBreakers(); core.resetDbInstance(); // Destroy the snapshot — targets only the temp dir, NEVER /root/.omniroute. - fs.rmSync(snapshotDir, { recursive: true, force: true }); + fs.rmSync(snapshotDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } // Populate the map eagerly so servedProvider (sync) works right after diff --git a/tests/integration/fingerprint-expansion.test.ts b/tests/integration/fingerprint-expansion.test.ts index 0bfed13cc593..70dc0cf7c814 100644 --- a/tests/integration/fingerprint-expansion.test.ts +++ b/tests/integration/fingerprint-expansion.test.ts @@ -280,7 +280,7 @@ test.after(async () => { if (app) await stopProcess(app.child); await upstream.stop(); core.closeDbInstance(); - await fsp.rm(TEST_DATA_DIR, { recursive: true, force: true }); + await fsp.rm(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ── Tests ────────────────────────────────────────────────────────────────── diff --git a/tests/integration/heap-growth.test.ts b/tests/integration/heap-growth.test.ts index d541a9e9a24a..9c23eef9e524 100644 --- a/tests/integration/heap-growth.test.ts +++ b/tests/integration/heap-growth.test.ts @@ -14,7 +14,7 @@ const { createSSEStream } = await import("../../open-sse/utils/stream.ts"); test.after(() => { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/integration/llama-cpp-provider.test.ts b/tests/integration/llama-cpp-provider.test.ts index 7c1cf517d1fa..c39ed2190b27 100644 --- a/tests/integration/llama-cpp-provider.test.ts +++ b/tests/integration/llama-cpp-provider.test.ts @@ -88,7 +88,7 @@ test.afterEach(() => { clearInflight(); resetAllCircuitBreakers(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); }); @@ -112,7 +112,10 @@ test("llama-cpp provider: routes request to custom baseUrl with no auth header", headers: toPlainHeaders(init.headers), body: init.body ? JSON.parse(String(init.body)) : null, }); - return buildLlamaResponse("Why did the programmer go broke? Because he used up all his cache!", "unsloth/gemma-4-26B-A4B-it-GGUF:UD-IQ2_M"); + return buildLlamaResponse( + "Why did the programmer go broke? Because he used up all his cache!", + "unsloth/gemma-4-26B-A4B-it-GGUF:UD-IQ2_M" + ); }; const response = await handleChat( @@ -135,7 +138,10 @@ test("llama-cpp provider: routes request to custom baseUrl with no auth header", assert.equal(upstream.headers.Authorization, undefined, "no auth header for local provider"); assert.equal(upstream.body.messages[0].content, "Tell me a joke."); assert.equal(upstream.body.model, "unsloth/gemma-4-26B-A4B-it-GGUF:UD-IQ2_M"); - assert.equal(json.choices[0].message.content, "Why did the programmer go broke? Because he used up all his cache!"); + assert.equal( + json.choices[0].message.content, + "Why did the programmer go broke? Because he used up all his cache!" + ); }); test("llama-cpp provider: alias matching works via model catalog prefix", async () => { @@ -152,7 +158,12 @@ test("llama-cpp provider: alias matching works via model catalog prefix", async const fetchCalls: FetchCall[] = []; globalThis.fetch = async (url, init: RequestInit = {}) => { - fetchCalls.push({ url: String(url), method: init.method, headers: toPlainHeaders(init.headers), body: init.body ? JSON.parse(String(init.body)) : null }); + fetchCalls.push({ + url: String(url), + method: init.method, + headers: toPlainHeaders(init.headers), + body: init.body ? JSON.parse(String(init.body)) : null, + }); return buildLlamaResponse("42", "unsloth/gemma-4-26B-A4B-it-GGUF:UD-IQ2_M"); }; @@ -167,7 +178,11 @@ test("llama-cpp provider: alias matching works via model catalog prefix", async ); const json = (await response.json()) as any; - assert.equal(response.status, 200, `expected 200, got ${response.status}: ${JSON.stringify(json)}`); + assert.equal( + response.status, + 200, + `expected 200, got ${response.status}: ${JSON.stringify(json)}` + ); assert.equal(json.choices[0].message.content, "42"); }); diff --git a/tests/integration/memory-embedding-providers.test.ts b/tests/integration/memory-embedding-providers.test.ts index 1481f0bad93e..74bf51fe799e 100644 --- a/tests/integration/memory-embedding-providers.test.ts +++ b/tests/integration/memory-embedding-providers.test.ts @@ -22,16 +22,15 @@ const core = await import("../../src/lib/db/core.ts"); const localDb = await import("../../src/lib/localDb.ts"); // Import route AFTER setting DATA_DIR -const embeddingProvidersRoute = await import( - "../../src/app/api/memory/embedding-providers/route.ts" -); +const embeddingProvidersRoute = + await import("../../src/app/api/memory/embedding-providers/route.ts"); const { GET } = embeddingProvidersRoute; // ── Helpers ── async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -44,7 +43,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ── Tests ── diff --git a/tests/integration/memory-engine-status.test.ts b/tests/integration/memory-engine-status.test.ts index e40079628a58..3cff56385bec 100644 --- a/tests/integration/memory-engine-status.test.ts +++ b/tests/integration/memory-engine-status.test.ts @@ -22,16 +22,14 @@ const core = await import("../../src/lib/db/core.ts"); const localDb = await import("../../src/lib/localDb.ts"); // Import route AFTER setting DATA_DIR -const engineStatusRoute = await import( - "../../src/app/api/memory/engine-status/route.ts" -); +const engineStatusRoute = await import("../../src/app/api/memory/engine-status/route.ts"); const { GET } = engineStatusRoute; // ── Helpers ── async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -44,7 +42,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ── Tests ── @@ -67,7 +65,11 @@ test("GET /api/memory/engine-status — 200 + valid MemoryEngineStatusSchema sha assert.strictEqual(body.keyword.backend, "FTS5", "keyword.backend should be FTS5"); assert.ok(body.embedding, "should have embedding section"); - assert.strictEqual(typeof body.embedding.available, "boolean", "embedding.available should be boolean"); + assert.strictEqual( + typeof body.embedding.available, + "boolean", + "embedding.available should be boolean" + ); assert.ok(typeof body.embedding.reason === "string", "embedding.reason should be a string"); assert.ok(body.embedding.cacheStats, "should have cacheStats in embedding"); assert.strictEqual(typeof body.embedding.cacheStats.hits, "number"); @@ -77,7 +79,7 @@ test("GET /api/memory/engine-status — 200 + valid MemoryEngineStatusSchema sha assert.ok(body.vectorStore, "should have vectorStore section"); assert.ok( ["sqlite-vec", "qdrant", "none"].includes(body.vectorStore.backend), - `vectorStore.backend should be valid: ${body.vectorStore.backend}`, + `vectorStore.backend should be valid: ${body.vectorStore.backend}` ); assert.strictEqual(typeof body.vectorStore.available, "boolean"); assert.strictEqual(typeof body.vectorStore.rowCount, "number"); diff --git a/tests/integration/memory-reindex.test.ts b/tests/integration/memory-reindex.test.ts index 6f11dc6083e6..4eb2188c9890 100644 --- a/tests/integration/memory-reindex.test.ts +++ b/tests/integration/memory-reindex.test.ts @@ -13,9 +13,7 @@ import assert from "node:assert/strict"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -import { - makeManagementSessionRequest, -} from "../helpers/managementSession.ts"; +import { makeManagementSessionRequest } from "../helpers/managementSession.ts"; const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-reindex-")); process.env.DATA_DIR = TEST_DATA_DIR; @@ -25,16 +23,14 @@ const core = await import("../../src/lib/db/core.ts"); const localDb = await import("../../src/lib/localDb.ts"); const memoryStore = await import("../../src/lib/memory/store.ts"); -const reindexRoute = await import( - "../../src/app/api/memory/reindex/route.ts" -); +const reindexRoute = await import("../../src/app/api/memory/reindex/route.ts"); const { POST } = reindexRoute; // ── Helpers ── async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -66,7 +62,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ── Tests ── diff --git a/tests/integration/memory-retrieve-preview.test.ts b/tests/integration/memory-retrieve-preview.test.ts index 3a403ff4d412..36c4437d0504 100644 --- a/tests/integration/memory-retrieve-preview.test.ts +++ b/tests/integration/memory-retrieve-preview.test.ts @@ -12,9 +12,7 @@ import assert from "node:assert/strict"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -import { - makeManagementSessionRequest, -} from "../helpers/managementSession.ts"; +import { makeManagementSessionRequest } from "../helpers/managementSession.ts"; const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-retrieve-preview-")); process.env.DATA_DIR = TEST_DATA_DIR; @@ -24,16 +22,14 @@ const core = await import("../../src/lib/db/core.ts"); const localDb = await import("../../src/lib/localDb.ts"); // Import route AFTER setting DATA_DIR -const retrieveRoute = await import( - "../../src/app/api/memory/retrieve-preview/route.ts" -); +const retrieveRoute = await import("../../src/app/api/memory/retrieve-preview/route.ts"); const { POST } = retrieveRoute; // ── Helpers ── async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -53,7 +49,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ── Tests ── @@ -106,9 +102,7 @@ test("POST /api/memory/retrieve-preview — 401 without auth when requireLogin=t test("POST /api/memory/retrieve-preview — error path: no stack trace (invalid JSON)", async () => { // Test via invalid JSON body — the parse step should return 400 without a stack trace - const { createManagementSessionHeaders } = await import( - "../helpers/managementSession.ts" - ); + const { createManagementSessionHeaders } = await import("../helpers/managementSession.ts"); const headers = await createManagementSessionHeaders(); const req = new Request("http://localhost/api/memory/retrieve-preview", { diff --git a/tests/integration/memory-route-put.test.ts b/tests/integration/memory-route-put.test.ts index 8ab591ac8e3a..3d952cb00234 100644 --- a/tests/integration/memory-route-put.test.ts +++ b/tests/integration/memory-route-put.test.ts @@ -33,7 +33,7 @@ const { createMemory, getMemory } = memoryStore; async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -69,7 +69,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ── Tests ── diff --git a/tests/integration/memory-summarize.test.ts b/tests/integration/memory-summarize.test.ts index 6771ca4719f9..fa8c3b131e9e 100644 --- a/tests/integration/memory-summarize.test.ts +++ b/tests/integration/memory-summarize.test.ts @@ -12,9 +12,7 @@ import assert from "node:assert/strict"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -import { - makeManagementSessionRequest, -} from "../helpers/managementSession.ts"; +import { makeManagementSessionRequest } from "../helpers/managementSession.ts"; const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-summarize-")); process.env.DATA_DIR = TEST_DATA_DIR; @@ -24,16 +22,14 @@ const core = await import("../../src/lib/db/core.ts"); const localDb = await import("../../src/lib/localDb.ts"); const memoryStore = await import("../../src/lib/memory/store.ts"); -const summarizeRoute = await import( - "../../src/app/api/memory/summarize/route.ts" -); +const summarizeRoute = await import("../../src/app/api/memory/summarize/route.ts"); const { POST } = summarizeRoute; // ── Helpers ── async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -61,7 +57,7 @@ async function seedOldMemory(daysAgo: number, apiKeyId = "api-key-1") { db.prepare("UPDATE memories SET created_at = ?, updated_at = ? WHERE id = ?").run( oldTs, oldTs, - mem.id, + mem.id ); return mem; } @@ -75,7 +71,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ── Tests ── diff --git a/tests/integration/model-catalog-responsiveness-9199.test.ts b/tests/integration/model-catalog-responsiveness-9199.test.ts index 89c0cd5750c4..19608b1d3cae 100644 --- a/tests/integration/model-catalog-responsiveness-9199.test.ts +++ b/tests/integration/model-catalog-responsiveness-9199.test.ts @@ -19,7 +19,7 @@ const healthRoute = await import("../../src/app/api/health/ping/route.ts"); async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); modelsCatalog.__resetCatalogBuilderRunsForTest(); } @@ -30,7 +30,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test( diff --git a/tests/integration/opencode-config-startup.test.ts b/tests/integration/opencode-config-startup.test.ts index 09161d8186cf..b0bbb3f76b84 100644 --- a/tests/integration/opencode-config-startup.test.ts +++ b/tests/integration/opencode-config-startup.test.ts @@ -23,7 +23,7 @@ after(() => { globalThis.fetch = originalFetch; if (originalHome === undefined) delete process.env.HOME; else process.env.HOME = originalHome; - fs.rmSync(testHome, { recursive: true, force: true }); + fs.rmSync(testHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function runOpencode(binary: string, args: string[]) { diff --git a/tests/integration/performance-regression.test.ts b/tests/integration/performance-regression.test.ts index 87f80215d950..641e869f9794 100644 --- a/tests/integration/performance-regression.test.ts +++ b/tests/integration/performance-regression.test.ts @@ -226,7 +226,7 @@ describe("Performance: memory API route handler (1000 records)", () => { db.prepare("DELETE FROM memories WHERE api_key_id = ?").run(TEST_API_KEY_ID); // Final cleanup: reset DB instance and remove temp dir core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); it(`should handle GET /api/memory?limit=50 in <${THRESHOLD_API_ROUTE_MS}ms`, async () => { diff --git a/tests/integration/playground-improve-prompt.test.ts b/tests/integration/playground-improve-prompt.test.ts index aaa67d67cc6c..e962184b959d 100644 --- a/tests/integration/playground-improve-prompt.test.ts +++ b/tests/integration/playground-improve-prompt.test.ts @@ -19,16 +19,12 @@ import os from "node:os"; import path from "node:path"; // Set up a temp DATA_DIR so getDbInstance() initialises cleanly -const TEST_DATA_DIR = fs.mkdtempSync( - path.join(os.tmpdir(), "omniroute-improve-prompt-") -); +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-improve-prompt-")); process.env.DATA_DIR = TEST_DATA_DIR; // Disable mandatory auth for most tests process.env.REQUIRE_API_KEY = "false"; -const { POST, OPTIONS } = await import( - "../../src/app/api/playground/improve-prompt/route.ts" -); +const { POST, OPTIONS } = await import("../../src/app/api/playground/improve-prompt/route.ts"); const BASE_URL = "http://localhost:20128"; @@ -61,7 +57,7 @@ function postRequest(body: unknown): Request { // ─── Cleanup ───────────────────────────────────────────────────────────────── test.after(() => { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ─── OPTIONS ───────────────────────────────────────────────────────────────── @@ -84,7 +80,11 @@ test("happy path: system + prompt both provided", async () => { ) as typeof fetch; const res = await POST( - postRequest({ system: "You are a helper.", prompt: "Tell me about AI.", model: "gpt-4o-mini" }) + postRequest({ + system: "You are a helper.", + prompt: "Tell me about AI.", + model: "gpt-4o-mini", + }) ); assert.equal(res.status, 200); @@ -161,15 +161,13 @@ test("happy path: usage defaults to 0 when not in upstream response", async () = try { // Return response without usage field globalThis.fetch = (async (_url: unknown, _opts: unknown) => { - return new Response( - JSON.stringify({ choices: [{ message: { content: "improved" } }] }), - { status: 200, headers: { "Content-Type": "application/json" } } - ); + return new Response(JSON.stringify({ choices: [{ message: { content: "improved" } }] }), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); }) as typeof fetch; - const res = await POST( - postRequest({ prompt: "Hello world", model: "gpt-4o-mini" }) - ); + const res = await POST(postRequest({ prompt: "Hello world", model: "gpt-4o-mini" })); assert.equal(res.status, 200); const body = (await res.json()) as { tokensIn: number; tokensOut: number }; @@ -245,9 +243,7 @@ test("upstream error returns sanitized error message — no stack trace in body" "Internal error\n at /home/user/project/src/handler.ts:42:10\n at process.nextTick" ) as typeof fetch; - const res = await POST( - postRequest({ prompt: "Hello", model: "gpt-4o-mini" }) - ); + const res = await POST(postRequest({ prompt: "Hello", model: "gpt-4o-mini" })); // Should be an error response (not 200) assert.ok(res.status >= 400); @@ -270,9 +266,7 @@ test("upstream network error is sanitized", async () => { throw new Error("ECONNREFUSED connect ECONNREFUSED 127.0.0.1:20128"); }) as typeof fetch; - const res = await POST( - postRequest({ prompt: "Hello", model: "gpt-4o-mini" }) - ); + const res = await POST(postRequest({ prompt: "Hello", model: "gpt-4o-mini" })); assert.ok(res.status >= 500); const body = (await res.json()) as { error: { message: string } }; @@ -289,9 +283,7 @@ test("401 when REQUIRE_API_KEY=true and no key provided", async () => { const originalRequired = process.env.REQUIRE_API_KEY; try { process.env.REQUIRE_API_KEY = "true"; - const res = await POST( - postRequest({ prompt: "Test", model: "gpt-4o-mini" }) - ); + const res = await POST(postRequest({ prompt: "Test", model: "gpt-4o-mini" })); assert.equal(res.status, 401); const body = (await res.json()) as { error: { message: string } }; diff --git a/tests/integration/playground-presets-crud.test.ts b/tests/integration/playground-presets-crud.test.ts index 655fdfdc53db..57377ec0ab7a 100644 --- a/tests/integration/playground-presets-crud.test.ts +++ b/tests/integration/playground-presets-crud.test.ts @@ -22,26 +22,28 @@ import os from "node:os"; import path from "node:path"; // Isolated DB per test file -const TEST_DATA_DIR = fs.mkdtempSync( - path.join(os.tmpdir(), "omniroute-presets-crud-") -); +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-presets-crud-")); process.env.DATA_DIR = TEST_DATA_DIR; process.env.REQUIRE_API_KEY = "false"; const core = await import("../../src/lib/db/core.ts"); // Import route handlers -const { GET: listGet, POST: createPost, OPTIONS: listOptions } = await import( - "../../src/app/api/playground/presets/route.ts" -); -const { GET: idGet, PUT: idPut, DELETE: idDelete, OPTIONS: idOptions } = await import( - "../../src/app/api/playground/presets/[id]/route.ts" -); +const { + GET: listGet, + POST: createPost, + OPTIONS: listOptions, +} = await import("../../src/app/api/playground/presets/route.ts"); +const { + GET: idGet, + PUT: idPut, + DELETE: idDelete, + OPTIONS: idOptions, +} = await import("../../src/app/api/playground/presets/[id]/route.ts"); const BASE_URL = "http://localhost:20128"; -const UUID_V4_REGEX = - /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; +const UUID_V4_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; // ─── Helpers ───────────────────────────────────────────────────────────────── @@ -86,7 +88,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ─── OPTIONS ───────────────────────────────────────────────────────────────── @@ -186,7 +188,12 @@ test("PUT /presets/[id] partial patch (name only) updates correctly", async () = ); assert.equal(putRes.status, 200); - const updated = (await putRes.json()) as { id: string; name: string; endpoint: string; model: string }; + const updated = (await putRes.json()) as { + id: string; + name: string; + endpoint: string; + model: string; + }; assert.equal(updated.id, created.id); assert.equal(updated.name, "Updated Name"); // Other fields should be preserved @@ -295,10 +302,7 @@ test("GET /presets/[id] with non-UUID id → 400", async () => { test("PUT /presets/[id] with non-UUID id → 400", async () => { const badId = "also-not-a-uuid"; - const res = await idPut( - putReq(badId, { name: "Whatever" }), - await resolveParams(badId) - ); + const res = await idPut(putReq(badId, { name: "Whatever" }), await resolveParams(badId)); assert.equal(res.status, 400); const body = (await res.json()) as { error: { message: string } }; diff --git a/tests/integration/playground-presets-zod.test.ts b/tests/integration/playground-presets-zod.test.ts index 3150eaed1540..6010921de53b 100644 --- a/tests/integration/playground-presets-zod.test.ts +++ b/tests/integration/playground-presets-zod.test.ts @@ -19,20 +19,18 @@ import os from "node:os"; import path from "node:path"; // Isolated DB per test file -const TEST_DATA_DIR = fs.mkdtempSync( - path.join(os.tmpdir(), "omniroute-presets-zod-") -); +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-presets-zod-")); process.env.DATA_DIR = TEST_DATA_DIR; process.env.REQUIRE_API_KEY = "false"; const core = await import("../../src/lib/db/core.ts"); -const { POST: createPost } = await import( - "../../src/app/api/playground/presets/route.ts" -); -const { GET: idGet, PUT: idPut, DELETE: idDelete } = await import( - "../../src/app/api/playground/presets/[id]/route.ts" -); +const { POST: createPost } = await import("../../src/app/api/playground/presets/route.ts"); +const { + GET: idGet, + PUT: idPut, + DELETE: idDelete, +} = await import("../../src/app/api/playground/presets/[id]/route.ts"); const BASE_URL = "http://localhost:20128"; @@ -83,7 +81,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ─── POST validation ───────────────────────────────────────────────────────── @@ -133,9 +131,7 @@ test("POST with missing model → 400", async () => { }); test("POST with empty model → 400", async () => { - const res = await createPost( - postReq({ name: "Test", endpoint: "chat.completions", model: "" }) - ); + const res = await createPost(postReq({ name: "Test", endpoint: "chat.completions", model: "" })); assert.equal(res.status, 400); const body = (await res.json()) as { error: { message: string } }; assert.ok(body.error); @@ -145,7 +141,12 @@ test("POST with empty model → 400", async () => { test("POST with system > 50000 chars → 400", async () => { const longSystem = "x".repeat(50001); const res = await createPost( - postReq({ name: "Big System", endpoint: "chat.completions", model: "gpt-4o", system: longSystem }) + postReq({ + name: "Big System", + endpoint: "chat.completions", + model: "gpt-4o", + system: longSystem, + }) ); assert.equal(res.status, 400); const body = (await res.json()) as { error: { message: string } }; @@ -156,7 +157,12 @@ test("POST with system > 50000 chars → 400", async () => { test("POST with system exactly 50000 chars → 201 (boundary: valid)", async () => { const maxSystem = "x".repeat(50000); const res = await createPost( - postReq({ name: "Max System", endpoint: "chat.completions", model: "gpt-4o", system: maxSystem }) + postReq({ + name: "Max System", + endpoint: "chat.completions", + model: "gpt-4o", + system: maxSystem, + }) ); assert.equal(res.status, 201); }); @@ -205,10 +211,7 @@ test("PUT with empty name → 400", async () => { test("PUT with system > 50000 chars → 400", async () => { const validId = "00000000-0000-4000-8000-000000000001"; const longSystem = "y".repeat(50001); - const res = await idPut( - putReq(validId, { system: longSystem }), - await resolveParams(validId) - ); + const res = await idPut(putReq(validId, { system: longSystem }), await resolveParams(validId)); assert.equal(res.status, 400); const body = (await res.json()) as { error: { message: string } }; assert.ok(body.error); diff --git a/tests/integration/plugins-lifecycle.test.ts b/tests/integration/plugins-lifecycle.test.ts index 7e3fe91e61c5..539578d05e63 100644 --- a/tests/integration/plugins-lifecycle.test.ts +++ b/tests/integration/plugins-lifecycle.test.ts @@ -20,7 +20,11 @@ const { pluginManager } = await import("../../src/lib/plugins/manager.ts"); // Scanner expects: sourceDir//plugin.json + index.js // Returns the sourceDir (parent) to pass to pluginManager.install() -function writeTestPlugin(opts?: { name?: string; onRequest?: boolean; enabledByDefault?: boolean }) { +function writeTestPlugin(opts?: { + name?: string; + onRequest?: boolean; + enabledByDefault?: boolean; +}) { const name = opts?.name ?? "test-lifecycle-plugin"; const onRequest = opts?.onRequest ?? true; const enabledByDefault = opts?.enabledByDefault ?? false; @@ -57,7 +61,7 @@ function writeTestPlugin(opts?: { name?: string; onRequest?: boolean; enabledByD // ── Helpers ── function cleanupDir(dir: string) { - fs.rmSync(dir, { recursive: true, force: true }); + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } // Track temp source dirs for cleanup @@ -65,7 +69,9 @@ const activeSourceDirs: string[] = []; function cleanupSourceDirs() { for (const dir of activeSourceDirs) { - try { fs.rmSync(dir, { recursive: true, force: true }); } catch {} + try { + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + } catch {} } activeSourceDirs.length = 0; } @@ -83,7 +89,9 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); cleanupSourceDirs(); - try { cleanupDir(TEST_DATA_DIR); } catch {} + try { + cleanupDir(TEST_DATA_DIR); + } catch {} }); // ── Tests: Install ── @@ -230,7 +238,11 @@ test("deactivate: unregisters all hooks for the plugin", async () => { // Hook should be gone const after = hooks.getHooks("onRequest"); - assert.equal(after.find((r) => r.pluginName === name), undefined, "hook should be unregistered"); + assert.equal( + after.find((r) => r.pluginName === name), + undefined, + "hook should be unregistered" + ); await pluginManager.uninstall(name); }); @@ -300,7 +312,10 @@ test("uninstall: deactivates before removing if active", async () => { // Plugin should be fully gone assert.equal(dbPlugins.getPluginByName(name), null); - assert.equal(hooks.getHooks("onRequest").find((r) => r.pluginName === name), undefined); + assert.equal( + hooks.getHooks("onRequest").find((r) => r.pluginName === name), + undefined + ); }); test("uninstall: throws for nonexistent plugin", async () => { @@ -322,7 +337,10 @@ test("full lifecycle: install -> activate -> hook fires -> deactivate -> uninsta await pluginManager.activate(name); const afterActivate = dbPlugins.getPluginByName(name); assert.equal(afterActivate!.status, "active"); - assert.ok(hooks.getHooks("onRequest").find((r) => r.pluginName === name), "hook registered"); + assert.ok( + hooks.getHooks("onRequest").find((r) => r.pluginName === name), + "hook registered" + ); // 3. Fire hook (use emitHookBlocking — child-process isolation means plugins cannot // mutate the parent's in-memory payload object; check the returned merged result). @@ -370,8 +388,14 @@ test("multiple plugins: hooks are isolated per plugin", async () => { await pluginManager.deactivate("multi-p1"); const afterDeactivate = hooks.getHooks("onRequest"); - assert.equal(afterDeactivate.find((r) => r.pluginName === "multi-p1"), undefined); - assert.ok(afterDeactivate.find((r) => r.pluginName === "multi-p2"), "p2 hook still registered"); + assert.equal( + afterDeactivate.find((r) => r.pluginName === "multi-p1"), + undefined + ); + assert.ok( + afterDeactivate.find((r) => r.pluginName === "multi-p2"), + "p2 hook still registered" + ); // Cleanup await pluginManager.uninstall("multi-p1"); diff --git a/tests/integration/provider-journey.contract.test.ts b/tests/integration/provider-journey.contract.test.ts index 8f88f273c638..655f6f7b8fbc 100644 --- a/tests/integration/provider-journey.contract.test.ts +++ b/tests/integration/provider-journey.contract.test.ts @@ -118,7 +118,7 @@ async function fetchCatalog( test.before(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); // requireLogin + requireAuthForModels ON so the API-key surface is gated. await localDb.updateSettings({ requireLogin: true, requireAuthForModels: true, password: "" }); @@ -127,7 +127,7 @@ test.before(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test.describe("provider journey — in-process contract (#8330)", () => { diff --git a/tests/integration/proxy-registry-flow.test.ts b/tests/integration/proxy-registry-flow.test.ts index 4de8f0b1a2ca..6a33c22732f9 100644 --- a/tests/integration/proxy-registry-flow.test.ts +++ b/tests/integration/proxy-registry-flow.test.ts @@ -23,13 +23,13 @@ const proxyLogger = await import("../../src/lib/proxyLogger.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("integration: proxy create with inline assignment is atomic and clears legacy config", async () => { diff --git a/tests/integration/qdrant-routes.test.ts b/tests/integration/qdrant-routes.test.ts index 34444154d56c..451074ffea1e 100644 --- a/tests/integration/qdrant-routes.test.ts +++ b/tests/integration/qdrant-routes.test.ts @@ -45,7 +45,7 @@ const asNextRequest = (req: Request) => req as unknown as import("next/server"). async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); // #5597 follow-up: the memory-settings cache is a module-level singleton that // survives per-test DB resets — bust it so each test starts from a clean read. @@ -88,7 +88,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ── Settings GET ── diff --git a/tests/integration/quota-plans-crud.test.ts b/tests/integration/quota-plans-crud.test.ts index c580c3613db0..77c294330395 100644 --- a/tests/integration/quota-plans-crud.test.ts +++ b/tests/integration/quota-plans-crud.test.ts @@ -35,7 +35,7 @@ async function enableManagementAuth() { function resetDb() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -46,7 +46,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // --------------------------------------------------------------------------- @@ -91,7 +91,9 @@ test("GET /api/quota/plans includes DB override plans", async () => { // List should include the override const listReq = await makeManagementSessionRequest("http://localhost/api/quota/plans"); const listRes = await plansRoute.GET(listReq); - const body = (await listRes.json()) as { plans: Array<{ connectionId: string | null; source: string }> }; + const body = (await listRes.json()) as { + plans: Array<{ connectionId: string | null; source: string }>; + }; const override = body.plans.find((p) => p.connectionId === "conn-override-1"); assert.ok(override, "Override plan should appear in list"); assert.equal(override?.source, "manual"); @@ -160,13 +162,10 @@ test("PUT /api/quota/plans/[connectionId] without auth → 401", async () => { }); test("PUT /api/quota/plans/[connectionId] with invalid body → 400", async () => { - const req = await makeManagementSessionRequest( - "http://localhost/api/quota/plans/conn-bad-body", - { - method: "PUT", - body: { dimensions: [] }, // PlanUpsertSchema requires min(1) dimensions - } - ); + const req = await makeManagementSessionRequest("http://localhost/api/quota/plans/conn-bad-body", { + method: "PUT", + body: { dimensions: [] }, // PlanUpsertSchema requires min(1) dimensions + }); const res = await planIdRoute.PUT(req, { params: Promise.resolve({ connectionId: "conn-bad-body" }), }); @@ -226,7 +225,9 @@ test("DELETE /api/quota/plans/[connectionId] clears override → 204; GET revert `http://localhost/api/quota/plans/${connectionId}`, { method: "DELETE" } ); - const deleteRes = await planIdRoute.DELETE(deleteReq, { params: Promise.resolve({ connectionId }) }); + const deleteRes = await planIdRoute.DELETE(deleteReq, { + params: Promise.resolve({ connectionId }), + }); assert.equal(deleteRes.status, 204); // GET should now return auto/empty plan (no DB override) @@ -250,7 +251,10 @@ test("DELETE /api/quota/plans/[connectionId] clears override → 204; GET revert (e as Record).target === connectionId && (e as { metadata?: { reverted?: boolean } }).metadata?.reverted === true ); - assert.ok(deleteEvt, "quota.plan.updated audit event (reverted=true) must be present after DELETE"); + assert.ok( + deleteEvt, + "quota.plan.updated audit event (reverted=true) must be present after DELETE" + ); }); test("DELETE /api/quota/plans/[connectionId] is idempotent → 204 even when not found", async () => { diff --git a/tests/integration/quota-pool-delete-combo-cleanup.test.ts b/tests/integration/quota-pool-delete-combo-cleanup.test.ts index e9d6b2d96640..e09f923c48cb 100644 --- a/tests/integration/quota-pool-delete-combo-cleanup.test.ts +++ b/tests/integration/quota-pool-delete-combo-cleanup.test.ts @@ -36,7 +36,7 @@ type Db = { function resetDb() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -95,7 +95,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("DELETE pool waits for scoped quota-combo cleanup before returning 204", async () => { diff --git a/tests/integration/quota-pool-usage-provider-resolution.test.ts b/tests/integration/quota-pool-usage-provider-resolution.test.ts index c45784cce52c..533ddb642f32 100644 --- a/tests/integration/quota-pool-usage-provider-resolution.test.ts +++ b/tests/integration/quota-pool-usage-provider-resolution.test.ts @@ -35,7 +35,7 @@ const usageRoute = await import("../../src/app/api/quota/pools/[id]/usage/route. function resetDb() { core.resetDbInstance(); resetQuotaStoreSingleton(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -46,7 +46,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); resetQuotaStoreSingleton(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("GET /usage surfaces catalog dimensions for a catalog-only pool (provider resolved from connection)", async () => { diff --git a/tests/integration/quota-pools-crud.test.ts b/tests/integration/quota-pools-crud.test.ts index dad624cecedf..da3d3e8bb262 100644 --- a/tests/integration/quota-pools-crud.test.ts +++ b/tests/integration/quota-pools-crud.test.ts @@ -38,7 +38,7 @@ async function enableManagementAuth() { function resetDb() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -49,7 +49,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // --------------------------------------------------------------------------- @@ -91,7 +91,7 @@ test("POST /api/quota/pools with auth + valid body → 201 + pool returned", asy }); const res = await poolsRoute.POST(req); assert.equal(res.status, 201); - const body = await res.json() as { pool: { id: string; name: string; connectionId: string } }; + const body = (await res.json()) as { pool: { id: string; name: string; connectionId: string } }; assert.ok(body.pool.id, "Pool should have an id"); assert.equal(body.pool.name, "Test Pool Alpha"); assert.equal(body.pool.connectionId, "conn-test-1"); @@ -109,7 +109,10 @@ test("POST /api/quota/pools → audit event logged", async () => { const events = Array.isArray(logs) ? logs : []; assert.ok(events.length >= 1, "Should have at least one quota.pool.created audit event"); const evt = events.find( - (e) => typeof e === "object" && e !== null && (e as Record).action === "quota.pool.created" + (e) => + typeof e === "object" && + e !== null && + (e as Record).action === "quota.pool.created" ); assert.ok(evt, "quota.pool.created audit event must be present"); }); @@ -267,9 +270,7 @@ test("DELETE /api/quota/pools/[id] → 204 + audit event; subsequent GET → 404 assert.ok(evt, "quota.pool.deleted audit event must be present"); // Subsequent GET → 404 - const getReq = await makeManagementSessionRequest( - `http://localhost/api/quota/pools/${poolId}` - ); + const getReq = await makeManagementSessionRequest(`http://localhost/api/quota/pools/${poolId}`); const getRes = await poolIdRoute.GET(getReq, { params: Promise.resolve({ id: poolId }) }); assert.equal(getRes.status, 404); }); diff --git a/tests/integration/quota-pools-usage.test.ts b/tests/integration/quota-pools-usage.test.ts index 08a847183061..9672958e25fb 100644 --- a/tests/integration/quota-pools-usage.test.ts +++ b/tests/integration/quota-pools-usage.test.ts @@ -42,7 +42,7 @@ async function enableManagementAuth() { function resetDb() { core.resetDbInstance(); resetQuotaStoreSingleton(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -54,7 +54,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); resetQuotaStoreSingleton(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("GET /api/quota/pools/[id]/usage without auth → 401", async () => { @@ -137,11 +137,7 @@ test("GET /api/quota/pools/[id]/usage → PoolUsageSnapshot shape with correct f // Even with no plan dimensions (empty plan for unknown provider), the response // is valid with an empty dimensions array — endpoint falls back to poolUsage() // which returns what's available from the store. - assert.doesNotMatch( - JSON.stringify(body), - /\s+at\s+\//, - "No stack trace in usage response" - ); + assert.doesNotMatch(JSON.stringify(body), /\s+at\s+\//, "No stack trace in usage response"); }); test("GET /api/quota/pools/[id]/usage response has required PoolUsageSnapshot fields", async () => { diff --git a/tests/integration/quota-preview.test.ts b/tests/integration/quota-preview.test.ts index 2afd58547acb..867d95ba7da5 100644 --- a/tests/integration/quota-preview.test.ts +++ b/tests/integration/quota-preview.test.ts @@ -40,7 +40,7 @@ async function enableManagementAuth() { function resetDb() { core.resetDbInstance(); resetQuotaStoreSingleton(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -52,14 +52,12 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); resetQuotaStoreSingleton(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("GET /api/quota/preview without auth → 401", async () => { await enableManagementAuth(); - const req = new Request( - "http://localhost/api/quota/preview?apiKeyId=k1&poolId=p1" - ); + const req = new Request("http://localhost/api/quota/preview?apiKeyId=k1&poolId=p1"); const res = await previewRoute.GET(req); assert.equal(res.status, 401); }); @@ -89,9 +87,7 @@ test("GET /api/quota/preview with nonexistent poolId → 404", async () => { test("GET /api/quota/preview with valid params → { decision } with kind", async () => { // Create a real pool const pool = createPool({ connectionId: "conn-preview", name: "Preview Pool" }); - upsertAllocations(pool.id, [ - { apiKeyId: "preview-key-1", weight: 100, policy: "soft" }, - ]); + upsertAllocations(pool.id, [{ apiKeyId: "preview-key-1", weight: 100, policy: "soft" }]); const req = await makeManagementSessionRequest( `http://localhost/api/quota/preview?apiKeyId=preview-key-1&poolId=${pool.id}&estimatedTokens=100` @@ -110,9 +106,7 @@ test("GET /api/quota/preview with valid params → { decision } with kind", asyn test("GET /api/quota/preview is dry-run: store counters unchanged after call", async () => { // Create pool and seed some consumption const pool = createPool({ connectionId: "conn-dryrun", name: "Dry Run Pool" }); - upsertAllocations(pool.id, [ - { apiKeyId: "dryrun-key", weight: 100, policy: "hard" }, - ]); + upsertAllocations(pool.id, [{ apiKeyId: "dryrun-key", weight: 100, policy: "hard" }]); const store = getSqliteQuotaStore(); const dim = { poolId: pool.id, unit: "tokens" as const, window: "daily" as const }; diff --git a/tests/integration/quota-routes-error-sanitization.test.ts b/tests/integration/quota-routes-error-sanitization.test.ts index b786b658986a..aca138fb07ee 100644 --- a/tests/integration/quota-routes-error-sanitization.test.ts +++ b/tests/integration/quota-routes-error-sanitization.test.ts @@ -17,9 +17,7 @@ import os from "node:os"; import path from "node:path"; import { makeManagementSessionRequest } from "../helpers/managementSession.ts"; -const TEST_DATA_DIR = fs.mkdtempSync( - path.join(os.tmpdir(), "omniroute-quota-err-sanitization-") -); +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-quota-err-sanitization-")); process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = "test-quota-sanitization-secret"; process.env.QUOTA_STORE_DRIVER = "sqlite"; @@ -42,7 +40,7 @@ const settingsRoute = await import("../../src/app/api/settings/quota-store/route function resetDb() { core.resetDbInstance(); resetQuotaStoreSingleton(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -68,16 +66,8 @@ async function assertNoStackTrace(res: Response, label: string) { // Helper to assert secret URL not in response body text function assertNoSecretUrlText(text: string, label: string) { - assert.doesNotMatch( - text, - /secret-host/, - `${label}: Response must not contain secret Redis host` - ); - assert.doesNotMatch( - text, - /redis:\/\/secret/, - `${label}: Response must not contain Redis URL` - ); + assert.doesNotMatch(text, /secret-host/, `${label}: Response must not contain secret Redis host`); + assert.doesNotMatch(text, /redis:\/\/secret/, `${label}: Response must not contain Redis URL`); } // Reads the response body once and runs both assertions (body cannot be read twice) @@ -96,7 +86,7 @@ test.after(() => { core.resetDbInstance(); resetQuotaStoreSingleton(); delete process.env.QUOTA_STORE_REDIS_URL; - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // --------------------------------------------------------------------------- @@ -118,9 +108,7 @@ test("POST /api/quota/pools 400 error response has no stack trace", async () => // --------------------------------------------------------------------------- test("GET /api/quota/pools/[id] 404 response has no stack trace", async () => { - const req = await makeManagementSessionRequest( - "http://localhost/api/quota/pools/does-not-exist" - ); + const req = await makeManagementSessionRequest("http://localhost/api/quota/pools/does-not-exist"); const res = await poolIdRoute.GET(req, { params: Promise.resolve({ id: "does-not-exist" }), }); @@ -179,13 +167,10 @@ test("GET /api/quota/plans 200 response has no stack trace or path leak", async // --------------------------------------------------------------------------- test("PUT /api/quota/plans/[connectionId] 400 error response has no stack trace", async () => { - const req = await makeManagementSessionRequest( - "http://localhost/api/quota/plans/conn-bad", - { - method: "PUT", - body: { dimensions: [] }, // PlanUpsertSchema requires min(1) - } - ); + const req = await makeManagementSessionRequest("http://localhost/api/quota/plans/conn-bad", { + method: "PUT", + body: { dimensions: [] }, // PlanUpsertSchema requires min(1) + }); const res = await planIdRoute.PUT(req, { params: Promise.resolve({ connectionId: "conn-bad" }), }); @@ -212,9 +197,7 @@ test("GET /api/quota/preview 400 error response has no stack trace", async () => // --------------------------------------------------------------------------- test("GET /api/settings/quota-store response does not contain Redis URL (Hard Rule #12/#1)", async () => { - const req = await makeManagementSessionRequest( - "http://localhost/api/settings/quota-store" - ); + const req = await makeManagementSessionRequest("http://localhost/api/settings/quota-store"); const res = await settingsRoute.GET(req); assert.equal(res.status, 200); await assertNoStackTraceAndNoSecretUrl(res, "GET /api/settings/quota-store 200"); @@ -225,13 +208,10 @@ test("GET /api/settings/quota-store response does not contain Redis URL (Hard Ru // --------------------------------------------------------------------------- test("PUT /api/settings/quota-store 400 error response has no stack trace", async () => { - const req = await makeManagementSessionRequest( - "http://localhost/api/settings/quota-store", - { - method: "PUT", - body: { driver: "baddriver" }, - } - ); + const req = await makeManagementSessionRequest("http://localhost/api/settings/quota-store", { + method: "PUT", + body: { driver: "baddriver" }, + }); const res = await settingsRoute.PUT(req); assert.equal(res.status, 400); await assertNoStackTrace(res, "PUT /api/settings/quota-store 400"); @@ -242,13 +222,10 @@ test("PUT /api/settings/quota-store 400 error response has no stack trace", asyn // --------------------------------------------------------------------------- test("PUT /api/settings/quota-store redis+no-URL error response does not leak Redis URL", async () => { - const req = await makeManagementSessionRequest( - "http://localhost/api/settings/quota-store", - { - method: "PUT", - body: { driver: "redis" }, // No URL provided - } - ); + const req = await makeManagementSessionRequest("http://localhost/api/settings/quota-store", { + method: "PUT", + body: { driver: "redis" }, // No URL provided + }); const res = await settingsRoute.PUT(req); assert.equal(res.status, 400); await assertNoStackTraceAndNoSecretUrl(res, "PUT /api/settings/quota-store redis-no-url 400"); diff --git a/tests/integration/quota-store-settings.test.ts b/tests/integration/quota-store-settings.test.ts index bcf2e6bc75a1..aceaaca3e3fb 100644 --- a/tests/integration/quota-store-settings.test.ts +++ b/tests/integration/quota-store-settings.test.ts @@ -41,7 +41,7 @@ function resetDb() { resetQuotaStoreSingleton(); delete process.env.QUOTA_STORE_REDIS_URL; delete process.env.INITIAL_PASSWORD; - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -53,7 +53,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); resetQuotaStoreSingleton(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // --------------------------------------------------------------------------- @@ -68,9 +68,7 @@ test("GET /api/settings/quota-store without auth → 401", async () => { }); test("GET /api/settings/quota-store returns driver + redisUrlConfigured (not URL)", async () => { - const req = await makeManagementSessionRequest( - "http://localhost/api/settings/quota-store" - ); + const req = await makeManagementSessionRequest("http://localhost/api/settings/quota-store"); const res = await settingsRoute.GET(req); assert.equal(res.status, 200); const body = (await res.json()) as { @@ -93,9 +91,7 @@ test("GET /api/settings/quota-store returns driver + redisUrlConfigured (not URL test("GET /api/settings/quota-store redisUrlConfigured=false when no URL configured", async () => { delete process.env.QUOTA_STORE_REDIS_URL; - const req = await makeManagementSessionRequest( - "http://localhost/api/settings/quota-store" - ); + const req = await makeManagementSessionRequest("http://localhost/api/settings/quota-store"); const res = await settingsRoute.GET(req); const body = (await res.json()) as { redisUrlConfigured: boolean }; assert.equal(body.redisUrlConfigured, false); @@ -117,13 +113,10 @@ test("PUT /api/settings/quota-store without auth → 401", async () => { }); test("PUT /api/settings/quota-store driver=sqlite → 200", async () => { - const req = await makeManagementSessionRequest( - "http://localhost/api/settings/quota-store", - { - method: "PUT", - body: { driver: "sqlite" }, - } - ); + const req = await makeManagementSessionRequest("http://localhost/api/settings/quota-store", { + method: "PUT", + body: { driver: "sqlite" }, + }); const res = await settingsRoute.PUT(req); assert.equal(res.status, 200); const body = (await res.json()) as { driver: string; redisUrl: null }; @@ -132,13 +125,10 @@ test("PUT /api/settings/quota-store driver=sqlite → 200", async () => { }); test("PUT /api/settings/quota-store driver=redis without URL → 400", async () => { - const req = await makeManagementSessionRequest( - "http://localhost/api/settings/quota-store", - { - method: "PUT", - body: { driver: "redis" }, // No redisUrl - } - ); + const req = await makeManagementSessionRequest("http://localhost/api/settings/quota-store", { + method: "PUT", + body: { driver: "redis" }, // No redisUrl + }); const res = await settingsRoute.PUT(req); assert.equal(res.status, 400); const body = await res.json(); @@ -147,13 +137,10 @@ test("PUT /api/settings/quota-store driver=redis without URL → 400", async () }); test("PUT /api/settings/quota-store driver=redis with valid URL → 200 + audit event", async () => { - const req = await makeManagementSessionRequest( - "http://localhost/api/settings/quota-store", - { - method: "PUT", - body: { driver: "redis", redisUrl: "redis://localhost:6379" }, - } - ); + const req = await makeManagementSessionRequest("http://localhost/api/settings/quota-store", { + method: "PUT", + body: { driver: "redis", redisUrl: "redis://localhost:6379" }, + }); const res = await settingsRoute.PUT(req); assert.equal(res.status, 200); const body = (await res.json()) as { @@ -187,13 +174,10 @@ test("PUT /api/settings/quota-store driver=redis with valid URL → 200 + audit }); test("PUT /api/settings/quota-store with invalid driver → 400 (Zod)", async () => { - const req = await makeManagementSessionRequest( - "http://localhost/api/settings/quota-store", - { - method: "PUT", - body: { driver: "memcached" }, // Not in enum - } - ); + const req = await makeManagementSessionRequest("http://localhost/api/settings/quota-store", { + method: "PUT", + body: { driver: "memcached" }, // Not in enum + }); const res = await settingsRoute.PUT(req); assert.equal(res.status, 400); const body = await res.json(); diff --git a/tests/integration/resilience-http-e2e.test.ts b/tests/integration/resilience-http-e2e.test.ts index 6f94fc02f61a..c3d0b655663c 100644 --- a/tests/integration/resilience-http-e2e.test.ts +++ b/tests/integration/resilience-http-e2e.test.ts @@ -547,7 +547,7 @@ test.after(async () => { } await relay.stop(); core.closeDbInstance(); - await fsp.rm(TEST_DATA_DIR, { recursive: true, force: true }); + await fsp.rm(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("resilience API only exposes configuration, not runtime breaker state", async () => { diff --git a/tests/integration/search-providers-catalog.test.ts b/tests/integration/search-providers-catalog.test.ts index 86c47e7e0b29..bab5557fa73f 100644 --- a/tests/integration/search-providers-catalog.test.ts +++ b/tests/integration/search-providers-catalog.test.ts @@ -102,7 +102,7 @@ async function seedRateLimitedConnection(provider: string) { /** Reset DB state between tests. */ async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -116,7 +116,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // --------------------------------------------------------------------------- diff --git a/tests/integration/test-model-compression-off-6240.test.ts b/tests/integration/test-model-compression-off-6240.test.ts index 79340503427b..c4bb43cd2ed9 100644 --- a/tests/integration/test-model-compression-off-6240.test.ts +++ b/tests/integration/test-model-compression-off-6240.test.ts @@ -37,7 +37,7 @@ async function resetStorage() { readCacheDb.invalidateDbCache(); await new Promise((resolve) => setTimeout(resolve, 20)); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -49,7 +49,7 @@ test.after(async () => { globalThis.fetch = originalFetch; core.closeDbInstance(); try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // best-effort cleanup } diff --git a/tests/integration/traffic-inspector-capture-modes.test.ts b/tests/integration/traffic-inspector-capture-modes.test.ts index bf9b46c7a28b..86148eb7c467 100644 --- a/tests/integration/traffic-inspector-capture-modes.test.ts +++ b/tests/integration/traffic-inspector-capture-modes.test.ts @@ -19,30 +19,25 @@ const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-ti-captur process.env.DATA_DIR = TEST_DATA_DIR; process.env.INSPECTOR_HTTP_PROXY_PORT = "0"; // ephemeral port -const captureModesRoute = await import( - "../../src/app/api/tools/traffic-inspector/capture-modes/route.ts" -); -const httpProxyRoute = await import( - "../../src/app/api/tools/traffic-inspector/capture-modes/http-proxy/route.ts" -); -const systemProxyRoute = await import( - "../../src/app/api/tools/traffic-inspector/capture-modes/system-proxy/route.ts" -); -const tlsInterceptRoute = await import( - "../../src/app/api/tools/traffic-inspector/capture-modes/tls-intercept/route.ts" -); -const { setHttpProxyHandle, getHttpProxyHandle, clearSystemProxy } = await import( - "../../src/lib/inspector/captureState.ts" -); -const { __setExec } = await import( - "../../src/mitm/inspector/systemProxyConfig.ts" -); +const captureModesRoute = + await import("../../src/app/api/tools/traffic-inspector/capture-modes/route.ts"); +const httpProxyRoute = + await import("../../src/app/api/tools/traffic-inspector/capture-modes/http-proxy/route.ts"); +const systemProxyRoute = + await import("../../src/app/api/tools/traffic-inspector/capture-modes/system-proxy/route.ts"); +const tlsInterceptRoute = + await import("../../src/app/api/tools/traffic-inspector/capture-modes/tls-intercept/route.ts"); +const { setHttpProxyHandle, getHttpProxyHandle, clearSystemProxy } = + await import("../../src/lib/inspector/captureState.ts"); +const { __setExec } = await import("../../src/mitm/inspector/systemProxyConfig.ts"); test.beforeEach(() => { // Ensure no running proxy handle leaks between tests const handle = getHttpProxyHandle(); if (handle) { - handle.stop().catch(() => {/* ignore */}); + handle.stop().catch(() => { + /* ignore */ + }); setHttpProxyHandle(null); } clearSystemProxy(); @@ -52,10 +47,12 @@ test.after(() => { // Clean up any running proxy const handle = getHttpProxyHandle(); if (handle) { - handle.stop().catch(() => {/* ignore */}); + handle.stop().catch(() => { + /* ignore */ + }); setHttpProxyHandle(null); } - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ── GET /capture-modes ────────────────────────────────────────────────────── @@ -63,7 +60,7 @@ test.after(() => { test("GET /capture-modes: returns status of all modes", async () => { const res = await captureModesRoute.GET(); assert.equal(res.status, 200); - const body = await res.json() as { + const body = (await res.json()) as { agentBridge: boolean; httpProxy: { running: boolean; port: number | null }; systemProxy: { applied: boolean }; @@ -78,17 +75,14 @@ test("GET /capture-modes: returns status of all modes", async () => { // ── POST /capture-modes/http-proxy ───────────────────────────────────────── test("http-proxy: start binds an ephemeral port", async () => { - const req = new Request( - "http://localhost/api/tools/traffic-inspector/capture-modes/http-proxy", - { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify({ action: "start" }), - } - ); + const req = new Request("http://localhost/api/tools/traffic-inspector/capture-modes/http-proxy", { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ action: "start" }), + }); const res = await httpProxyRoute.POST(req); assert.equal(res.status, 201); - const body = await res.json() as { ok: boolean; running: boolean; port: number }; + const body = (await res.json()) as { ok: boolean; running: boolean; port: number }; assert.equal(body.ok, true); assert.equal(body.running, true); assert.ok(body.port > 0, "should have a bound port"); @@ -102,17 +96,14 @@ test("http-proxy: start binds an ephemeral port", async () => { }); test("http-proxy: stop when not running returns ok", async () => { - const req = new Request( - "http://localhost/api/tools/traffic-inspector/capture-modes/http-proxy", - { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify({ action: "stop" }), - } - ); + const req = new Request("http://localhost/api/tools/traffic-inspector/capture-modes/http-proxy", { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ action: "stop" }), + }); const res = await httpProxyRoute.POST(req); assert.equal(res.status, 200); - const body = await res.json() as { ok: boolean; running: boolean }; + const body = (await res.json()) as { ok: boolean; running: boolean }; assert.equal(body.ok, true); assert.equal(body.running, false); }); @@ -139,16 +130,14 @@ test("http-proxy: start then stop lifecycle", async () => { ); const stopRes = await httpProxyRoute.POST(stopReq); assert.equal(stopRes.status, 200); - const body = await stopRes.json() as { running: boolean }; + const body = (await stopRes.json()) as { running: boolean }; assert.equal(body.running, false); }); test("http-proxy: EADDRINUSE returns 409 with structured error", async () => { // Import startHttpProxyServer directly so we can test the low-level error path // without depending on the module-cached DEFAULT_PORT. - const { startHttpProxyServer } = await import( - "../../src/mitm/inspector/httpProxyServer.ts" - ); + const { startHttpProxyServer } = await import("../../src/mitm/inspector/httpProxyServer.ts"); // Occupy a random port const blocker = net.createServer(); @@ -173,7 +162,10 @@ test("http-proxy: EADDRINUSE returns 409 with structured error", async () => { // ── POST /capture-modes/system-proxy ─────────────────────────────────────── test("system-proxy: apply with mocked OS commands", async () => { - const restore = __setExec(async (_file, _args) => ({ stdout: "Enabled: No\nServer: \nPort: 0", stderr: "" })); + const restore = __setExec(async (_file, _args) => ({ + stdout: "Enabled: No\nServer: \nPort: 0", + stderr: "", + })); try { const req = new Request( "http://localhost/api/tools/traffic-inspector/capture-modes/system-proxy", @@ -185,7 +177,7 @@ test("system-proxy: apply with mocked OS commands", async () => { ); const res = await systemProxyRoute.POST(req); assert.equal(res.status, 200); - const body = await res.json() as { ok: boolean; applied: boolean }; + const body = (await res.json()) as { ok: boolean; applied: boolean }; assert.equal(body.ok, true); assert.equal(body.applied, true); } finally { @@ -207,7 +199,7 @@ test("system-proxy: revert without prior apply is a no-op", async () => { ); const res = await systemProxyRoute.POST(req); assert.equal(res.status, 200); - const body = await res.json() as { applied: boolean }; + const body = (await res.json()) as { applied: boolean }; assert.equal(body.applied, false); } finally { restore(); @@ -240,7 +232,7 @@ test("tls-intercept: toggle on/off", async () => { ); const enableRes = await tlsInterceptRoute.POST(enableReq); assert.equal(enableRes.status, 200); - const enableBody = await enableRes.json() as { tlsIntercept: { enabled: boolean } }; + const enableBody = (await enableRes.json()) as { tlsIntercept: { enabled: boolean } }; assert.equal(enableBody.tlsIntercept.enabled, true); const disableReq = new Request( @@ -253,6 +245,6 @@ test("tls-intercept: toggle on/off", async () => { ); const disableRes = await tlsInterceptRoute.POST(disableReq); assert.equal(disableRes.status, 200); - const disableBody = await disableRes.json() as { tlsIntercept: { enabled: boolean } }; + const disableBody = (await disableRes.json()) as { tlsIntercept: { enabled: boolean } }; assert.equal(disableBody.tlsIntercept.enabled, false); }); diff --git a/tests/integration/traffic-inspector-error-sanitization.test.ts b/tests/integration/traffic-inspector-error-sanitization.test.ts index fd076bd3ca16..6b2883cbc030 100644 --- a/tests/integration/traffic-inspector-error-sanitization.test.ts +++ b/tests/integration/traffic-inspector-error-sanitization.test.ts @@ -17,53 +17,36 @@ process.env.DATA_DIR = TEST_DATA_DIR; const { globalTrafficBuffer } = await import("../../src/mitm/inspector/buffer.ts"); -const requestsRoute = await import( - "../../src/app/api/tools/traffic-inspector/requests/route.ts" -); -const requestDetailRoute = await import( - "../../src/app/api/tools/traffic-inspector/requests/[id]/route.ts" -); -const annotationRoute = await import( - "../../src/app/api/tools/traffic-inspector/requests/[id]/annotation/route.ts" -); -const hostsRoute = await import( - "../../src/app/api/tools/traffic-inspector/hosts/route.ts" -); -const hostDetailRoute = await import( - "../../src/app/api/tools/traffic-inspector/hosts/[host]/route.ts" -); -const sessionsRoute = await import( - "../../src/app/api/tools/traffic-inspector/sessions/route.ts" -); -const sessionDetailRoute = await import( - "../../src/app/api/tools/traffic-inspector/sessions/[id]/route.ts" -); -const ingestRoute = await import( - "../../src/app/api/tools/traffic-inspector/internal/ingest/route.ts" -); -const httpProxyRoute = await import( - "../../src/app/api/tools/traffic-inspector/capture-modes/http-proxy/route.ts" -); -const systemProxyRoute = await import( - "../../src/app/api/tools/traffic-inspector/capture-modes/system-proxy/route.ts" -); -const tlsInterceptRoute = await import( - "../../src/app/api/tools/traffic-inspector/capture-modes/tls-intercept/route.ts" -); +const requestsRoute = await import("../../src/app/api/tools/traffic-inspector/requests/route.ts"); +const requestDetailRoute = + await import("../../src/app/api/tools/traffic-inspector/requests/[id]/route.ts"); +const annotationRoute = + await import("../../src/app/api/tools/traffic-inspector/requests/[id]/annotation/route.ts"); +const hostsRoute = await import("../../src/app/api/tools/traffic-inspector/hosts/route.ts"); +const hostDetailRoute = + await import("../../src/app/api/tools/traffic-inspector/hosts/[host]/route.ts"); +const sessionsRoute = await import("../../src/app/api/tools/traffic-inspector/sessions/route.ts"); +const sessionDetailRoute = + await import("../../src/app/api/tools/traffic-inspector/sessions/[id]/route.ts"); +const ingestRoute = + await import("../../src/app/api/tools/traffic-inspector/internal/ingest/route.ts"); +const httpProxyRoute = + await import("../../src/app/api/tools/traffic-inspector/capture-modes/http-proxy/route.ts"); +const systemProxyRoute = + await import("../../src/app/api/tools/traffic-inspector/capture-modes/system-proxy/route.ts"); +const tlsInterceptRoute = + await import("../../src/app/api/tools/traffic-inspector/capture-modes/tls-intercept/route.ts"); function noStackTrace(msg: string, label: string): void { assert.ok( !msg.includes("at /"), `${label}: error message must not contain stack trace (found "at /")` ); - assert.ok( - !msg.includes(".ts:"), - `${label}: error message must not include TS file paths` - ); + assert.ok(!msg.includes(".ts:"), `${label}: error message must not include TS file paths`); } async function getErrorMessage(res: Response): Promise { - const body = await res.json() as { error: { message: string } }; + const body = (await res.json()) as { error: { message: string } }; return body.error?.message ?? ""; } @@ -72,23 +55,20 @@ test.beforeEach(() => { }); test.after(() => { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("requests: invalid profile param does not leak stack", async () => { - const req = new Request( - "http://localhost/api/tools/traffic-inspector/requests?profile=BAD" - ); + const req = new Request("http://localhost/api/tools/traffic-inspector/requests?profile=BAD"); const res = await requestsRoute.GET(req); assert.equal(res.status, 400); noStackTrace(await getErrorMessage(res), "GET /requests"); }); test("requests/[id]: unknown id does not leak stack", async () => { - const res = await requestDetailRoute.GET( - new Request("http://localhost/"), - { params: Promise.resolve({ id: randomUUID() }) } - ); + const res = await requestDetailRoute.GET(new Request("http://localhost/"), { + params: Promise.resolve({ id: randomUUID() }), + }); assert.equal(res.status, 404); noStackTrace(await getErrorMessage(res), "GET /requests/[id]"); }); @@ -148,40 +128,33 @@ test("hosts/[host] PATCH: invalid body does not leak stack", async () => { }); test("sessions: 404 does not leak stack", async () => { - const res = await sessionDetailRoute.GET( - new Request("http://localhost/"), - { params: Promise.resolve({ id: randomUUID() }) } - ); + const res = await sessionDetailRoute.GET(new Request("http://localhost/"), { + params: Promise.resolve({ id: randomUUID() }), + }); assert.equal(res.status, 404); noStackTrace(await getErrorMessage(res), "GET /sessions/[id]"); }); test("ingest: 403 does not leak stack", async () => { - const req = new Request( - "http://localhost/api/tools/traffic-inspector/internal/ingest", - { - method: "POST", - headers: { - "content-type": "application/json", - authorization: "Bearer wrong-token", - }, - body: JSON.stringify({}), - } - ); + const req = new Request("http://localhost/api/tools/traffic-inspector/internal/ingest", { + method: "POST", + headers: { + "content-type": "application/json", + authorization: "Bearer wrong-token", + }, + body: JSON.stringify({}), + }); const res = await ingestRoute.POST(req); assert.equal(res.status, 403); noStackTrace(await getErrorMessage(res), "POST /internal/ingest (403)"); }); test("http-proxy: invalid action does not leak stack", async () => { - const req = new Request( - "http://localhost/api/tools/traffic-inspector/capture-modes/http-proxy", - { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify({ action: "invalid" }), - } - ); + const req = new Request("http://localhost/api/tools/traffic-inspector/capture-modes/http-proxy", { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ action: "invalid" }), + }); const res = await httpProxyRoute.POST(req); assert.equal(res.status, 400); noStackTrace(await getErrorMessage(res), "POST /capture-modes/http-proxy"); diff --git a/tests/integration/traffic-inspector-hosts.test.ts b/tests/integration/traffic-inspector-hosts.test.ts index 234eb8be6155..f6efcc57a66c 100644 --- a/tests/integration/traffic-inspector-hosts.test.ts +++ b/tests/integration/traffic-inspector-hosts.test.ts @@ -18,29 +18,26 @@ process.env.DATA_DIR = TEST_DATA_DIR; const { resetDbInstance } = await import("../../src/lib/db/core.ts"); const localDb = await import("../../src/lib/localDb.ts"); -const hostsRoute = await import( - "../../src/app/api/tools/traffic-inspector/hosts/route.ts" -); -const hostDetailRoute = await import( - "../../src/app/api/tools/traffic-inspector/hosts/[host]/route.ts" -); +const hostsRoute = await import("../../src/app/api/tools/traffic-inspector/hosts/route.ts"); +const hostDetailRoute = + await import("../../src/app/api/tools/traffic-inspector/hosts/[host]/route.ts"); test.beforeEach(async () => { resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); // Re-init DB with fresh migrations await import("../../src/lib/db/core.ts").then((m) => m.getDbInstance()); }); test.after(() => { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("GET /hosts: returns empty list initially", async () => { const res = await hostsRoute.GET(); assert.equal(res.status, 200); - const body = await res.json() as { hosts: unknown[] }; + const body = (await res.json()) as { hosts: unknown[] }; assert.deepEqual(body.hosts, []); }); @@ -52,13 +49,13 @@ test("POST /hosts: adds a host", async () => { }); const res = await hostsRoute.POST(req); assert.equal(res.status, 201); - const body = await res.json() as { ok: boolean; host: string }; + const body = (await res.json()) as { ok: boolean; host: string }; assert.equal(body.ok, true); assert.equal(body.host, "api.openai.com"); // Verify it appears in list const listRes = await hostsRoute.GET(); - const list = await listRes.json() as { hosts: Array<{ host: string }> }; + const list = (await listRes.json()) as { hosts: Array<{ host: string }> }; assert.ok(list.hosts.some((h) => h.host === "api.openai.com")); }); @@ -70,7 +67,7 @@ test("POST /hosts: rejects empty host string", async () => { }); const res = await hostsRoute.POST(req); assert.equal(res.status, 400); - const body = await res.json() as { error: { message: string } }; + const body = (await res.json()) as { error: { message: string } }; assert.ok(!body.error.message.includes("at /"), "must not leak stack trace"); }); @@ -94,15 +91,14 @@ test("DELETE /hosts/[host]: removes existing host", async () => { await hostsRoute.POST(addReq); // Now delete it - const delRes = await hostDetailRoute.DELETE( - new Request("http://localhost/"), - { params: Promise.resolve({ host: "remove-me.example.com" }) } - ); + const delRes = await hostDetailRoute.DELETE(new Request("http://localhost/"), { + params: Promise.resolve({ host: "remove-me.example.com" }), + }); assert.equal(delRes.status, 204); // Verify gone const listRes = await hostsRoute.GET(); - const list = await listRes.json() as { hosts: Array<{ host: string }> }; + const list = (await listRes.json()) as { hosts: Array<{ host: string }> }; assert.ok(!list.hosts.some((h) => h.host === "remove-me.example.com")); }); @@ -125,7 +121,7 @@ test("PATCH /hosts/[host]: toggles enabled flag", async () => { { params: Promise.resolve({ host: "toggle-me.example.com" }) } ); assert.equal(patchRes.status, 200); - const body = await patchRes.json() as { enabled: boolean }; + const body = (await patchRes.json()) as { enabled: boolean }; assert.equal(body.enabled, false); }); diff --git a/tests/integration/traffic-inspector-internal-ingest.test.ts b/tests/integration/traffic-inspector-internal-ingest.test.ts index 3cf2ef1048d5..8690993d38bc 100644 --- a/tests/integration/traffic-inspector-internal-ingest.test.ts +++ b/tests/integration/traffic-inspector-internal-ingest.test.ts @@ -23,9 +23,8 @@ const VALID_TOKEN = "test-ingest-token-abc123xyz789-longer-than-16"; process.env.INSPECTOR_INTERNAL_INGEST_TOKEN = VALID_TOKEN; const { globalTrafficBuffer } = await import("../../src/mitm/inspector/buffer.ts"); -const ingestRoute = await import( - "../../src/app/api/tools/traffic-inspector/internal/ingest/route.ts" -); +const ingestRoute = + await import("../../src/app/api/tools/traffic-inspector/internal/ingest/route.ts"); function makeIngestRequest(token: string | null, body: unknown): Request { const headers: Record = { @@ -34,14 +33,11 @@ function makeIngestRequest(token: string | null, body: unknown): Request { if (token !== null) { headers["authorization"] = `Bearer ${token}`; } - return new Request( - "http://localhost/api/tools/traffic-inspector/internal/ingest", - { - method: "POST", - headers, - body: JSON.stringify(body), - } - ); + return new Request("http://localhost/api/tools/traffic-inspector/internal/ingest", { + method: "POST", + headers, + body: JSON.stringify(body), + }); } function minimalEntry(overrides: Record = {}): Record { @@ -66,14 +62,14 @@ test.beforeEach(() => { }); test.after(() => { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("ingest: POST without Authorization header → 403", async () => { const req = makeIngestRequest(null, minimalEntry()); const res = await ingestRoute.POST(req); assert.equal(res.status, 403); - const body = await res.json() as { error: { message: string } }; + const body = (await res.json()) as { error: { message: string } }; assert.ok(!body.error.message.includes("at /"), "must not leak stack trace"); }); @@ -94,7 +90,7 @@ test("ingest: POST with valid token + valid body → 200 + buffer push", async ( const req = makeIngestRequest(VALID_TOKEN, minimalEntry({ id })); const res = await ingestRoute.POST(req); assert.equal(res.status, 200); - const body = await res.json() as { ok: boolean; id: string }; + const body = (await res.json()) as { ok: boolean; id: string }; assert.equal(body.ok, true); assert.equal(body.id, id); @@ -113,23 +109,20 @@ test("ingest: valid token + missing required field → 400", async () => { }); const res = await ingestRoute.POST(req); assert.equal(res.status, 400); - const body = await res.json() as { error: { message: string } }; + const body = (await res.json()) as { error: { message: string } }; assert.ok(!body.error.message.includes("at /"), "must not leak stack trace"); }); test("ingest: valid token + invalid JSON → 400", async () => { const headers: Record = { "content-type": "application/json", - "authorization": `Bearer ${VALID_TOKEN}`, + authorization: `Bearer ${VALID_TOKEN}`, }; - const req = new Request( - "http://localhost/api/tools/traffic-inspector/internal/ingest", - { - method: "POST", - headers, - body: "not valid json", - } - ); + const req = new Request("http://localhost/api/tools/traffic-inspector/internal/ingest", { + method: "POST", + headers, + body: "not valid json", + }); const res = await ingestRoute.POST(req); assert.equal(res.status, 400); }); diff --git a/tests/integration/traffic-inspector-localonly.test.ts b/tests/integration/traffic-inspector-localonly.test.ts index 0fc352b51dc6..396ee1043f8f 100644 --- a/tests/integration/traffic-inspector-localonly.test.ts +++ b/tests/integration/traffic-inspector-localonly.test.ts @@ -14,12 +14,10 @@ import path from "node:path"; const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-ti-local-")); process.env.DATA_DIR = TEST_DATA_DIR; -const { isLocalOnlyPath, isLoopbackHost } = await import( - "../../src/server/authz/routeGuard.ts" -); +const { isLocalOnlyPath, isLoopbackHost } = await import("../../src/server/authz/routeGuard.ts"); test.after(() => { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ── isLocalOnlyPath assertions ────────────────────────────────────────────── diff --git a/tests/integration/traffic-inspector-requests.test.ts b/tests/integration/traffic-inspector-requests.test.ts index 9f894c441f6e..7f46042abf9f 100644 --- a/tests/integration/traffic-inspector-requests.test.ts +++ b/tests/integration/traffic-inspector-requests.test.ts @@ -16,23 +16,21 @@ const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-ti-reqs-" process.env.DATA_DIR = TEST_DATA_DIR; const { globalTrafficBuffer } = await import("../../src/mitm/inspector/buffer.ts"); -const requestsRoute = await import( - "../../src/app/api/tools/traffic-inspector/requests/route.ts" -); -const requestDetailRoute = await import( - "../../src/app/api/tools/traffic-inspector/requests/[id]/route.ts" -); -const annotationRoute = await import( - "../../src/app/api/tools/traffic-inspector/requests/[id]/annotation/route.ts" -); - -function makeEntry(overrides: Partial<{ - id: string; - host: string; - detectedKind: "llm" | "app" | "unknown"; - status: number | "in-flight" | "error"; - source: "agent-bridge" | "custom-host" | "http-proxy" | "system-proxy"; -}> = {}) { +const requestsRoute = await import("../../src/app/api/tools/traffic-inspector/requests/route.ts"); +const requestDetailRoute = + await import("../../src/app/api/tools/traffic-inspector/requests/[id]/route.ts"); +const annotationRoute = + await import("../../src/app/api/tools/traffic-inspector/requests/[id]/annotation/route.ts"); + +function makeEntry( + overrides: Partial<{ + id: string; + host: string; + detectedKind: "llm" | "app" | "unknown"; + status: number | "in-flight" | "error"; + source: "agent-bridge" | "custom-host" | "http-proxy" | "system-proxy"; + }> = {} +) { return { id: randomUUID(), source: "agent-bridge" as const, @@ -57,14 +55,14 @@ test.beforeEach(() => { }); test.after(() => { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("GET /requests: returns empty list when buffer is empty", async () => { const req = new Request("http://localhost/api/tools/traffic-inspector/requests"); const res = await requestsRoute.GET(req); assert.equal(res.status, 200); - const body = await res.json() as { requests: unknown[]; total: number }; + const body = (await res.json()) as { requests: unknown[]; total: number }; assert.deepEqual(body.requests, []); assert.equal(body.total, 0); }); @@ -76,7 +74,7 @@ test("GET /requests: returns all entries without filter", async () => { const req = new Request("http://localhost/api/tools/traffic-inspector/requests"); const res = await requestsRoute.GET(req); assert.equal(res.status, 200); - const body = await res.json() as { requests: unknown[]; total: number }; + const body = (await res.json()) as { requests: unknown[]; total: number }; assert.equal(body.total, 2); }); @@ -84,12 +82,10 @@ test("GET /requests: filters by profile=llm", async () => { globalTrafficBuffer.push(makeEntry({ id: randomUUID(), detectedKind: "llm" })); globalTrafficBuffer.push(makeEntry({ id: randomUUID(), detectedKind: "app" })); - const req = new Request( - "http://localhost/api/tools/traffic-inspector/requests?profile=llm" - ); + const req = new Request("http://localhost/api/tools/traffic-inspector/requests?profile=llm"); const res = await requestsRoute.GET(req); assert.equal(res.status, 200); - const body = await res.json() as { requests: unknown[]; total: number }; + const body = (await res.json()) as { requests: unknown[]; total: number }; assert.equal(body.total, 1); }); @@ -97,23 +93,19 @@ test("GET /requests: filters by host", async () => { globalTrafficBuffer.push(makeEntry({ id: randomUUID(), host: "target.com" })); globalTrafficBuffer.push(makeEntry({ id: randomUUID(), host: "other.com" })); - const req = new Request( - "http://localhost/api/tools/traffic-inspector/requests?host=target.com" - ); + const req = new Request("http://localhost/api/tools/traffic-inspector/requests?host=target.com"); const res = await requestsRoute.GET(req); assert.equal(res.status, 200); - const body = await res.json() as { requests: Array<{ host: string }>; total: number }; + const body = (await res.json()) as { requests: Array<{ host: string }>; total: number }; assert.equal(body.total, 1); assert.equal(body.requests[0]?.host, "target.com"); }); test("GET /requests: rejects invalid profile param with 400", async () => { - const req = new Request( - "http://localhost/api/tools/traffic-inspector/requests?profile=invalid" - ); + const req = new Request("http://localhost/api/tools/traffic-inspector/requests?profile=invalid"); const res = await requestsRoute.GET(req); assert.equal(res.status, 400); - const body = await res.json() as { error: { message: string } }; + const body = (await res.json()) as { error: { message: string } }; assert.ok(!body.error.message.includes("at /"), "must not leak stack trace"); }); @@ -134,19 +126,17 @@ test("GET /requests/[id]: returns entry by id", async () => { params: Promise.resolve({ id: entry.id }), }); assert.equal(res.status, 200); - const body = await res.json() as { id: string }; + const body = (await res.json()) as { id: string }; assert.equal(body.id, entry.id); }); test("GET /requests/[id]: returns 404 for unknown id", async () => { - const req = new Request( - `http://localhost/api/tools/traffic-inspector/requests/${randomUUID()}` - ); + const req = new Request(`http://localhost/api/tools/traffic-inspector/requests/${randomUUID()}`); const res = await requestDetailRoute.GET(req, { params: Promise.resolve({ id: randomUUID() }), }); assert.equal(res.status, 404); - const body = await res.json() as { error: { message: string } }; + const body = (await res.json()) as { error: { message: string } }; assert.ok(!body.error.message.includes("at /"), "must not leak stack trace"); }); @@ -166,7 +156,7 @@ test("PUT /requests/[id]/annotation: attaches annotation", async () => { params: Promise.resolve({ id: entry.id }), }); assert.equal(res.status, 200); - const body = await res.json() as { annotation: string }; + const body = (await res.json()) as { annotation: string }; assert.equal(body.annotation, "my note"); // Confirm buffer was updated diff --git a/tests/integration/traffic-inspector-session-requests.test.ts b/tests/integration/traffic-inspector-session-requests.test.ts index d0b9bcb14e87..0da46d1ae827 100644 --- a/tests/integration/traffic-inspector-session-requests.test.ts +++ b/tests/integration/traffic-inspector-session-requests.test.ts @@ -17,20 +17,16 @@ const { resetDbInstance, getDbInstance } = await import("../../src/lib/db/core.t async function resetStorage() { resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); getDbInstance(); } -const sessionsRoute = await import( - "../../src/app/api/tools/traffic-inspector/sessions/route.ts" -); -const sessionDetailRoute = await import( - "../../src/app/api/tools/traffic-inspector/sessions/[id]/route.ts" -); -const sessionRequestsRoute = await import( - "../../src/app/api/tools/traffic-inspector/sessions/[id]/requests/route.ts" -); +const sessionsRoute = await import("../../src/app/api/tools/traffic-inspector/sessions/route.ts"); +const sessionDetailRoute = + await import("../../src/app/api/tools/traffic-inspector/sessions/[id]/route.ts"); +const sessionRequestsRoute = + await import("../../src/app/api/tools/traffic-inspector/sessions/[id]/requests/route.ts"); async function createSession(name?: string): Promise { const res = await sessionsRoute.POST( @@ -61,7 +57,7 @@ test.beforeEach(async () => { test.after(() => { resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("POST /sessions/[id]/requests: seq increments 1, 2, 3", async () => { @@ -131,7 +127,7 @@ test("POST /sessions/[id]/requests: error response does not leak stack trace", a // POST to non-existent session — exercises the 404 path error body const res = await postRequest("00000000-0000-4000-8000-000000000099", "data"); assert.equal(res.status, 404); - const body = await res.json() as { error?: { message?: string } }; + const body = (await res.json()) as { error?: { message?: string } }; const msg = body?.error?.message ?? ""; assert.ok(!msg.includes("at /"), "should not contain stack trace"); }); diff --git a/tests/integration/traffic-inspector-sessions.test.ts b/tests/integration/traffic-inspector-sessions.test.ts index 3687b170b820..f2bb759b971a 100644 --- a/tests/integration/traffic-inspector-sessions.test.ts +++ b/tests/integration/traffic-inspector-sessions.test.ts @@ -18,21 +18,17 @@ const { resetDbInstance, getDbInstance } = await import("../../src/lib/db/core.t async function resetStorage() { resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); // Re-initialize db getDbInstance(); } -const sessionsRoute = await import( - "../../src/app/api/tools/traffic-inspector/sessions/route.ts" -); -const sessionDetailRoute = await import( - "../../src/app/api/tools/traffic-inspector/sessions/[id]/route.ts" -); -const sessionHarRoute = await import( - "../../src/app/api/tools/traffic-inspector/sessions/[id]/export.har/route.ts" -); +const sessionsRoute = await import("../../src/app/api/tools/traffic-inspector/sessions/route.ts"); +const sessionDetailRoute = + await import("../../src/app/api/tools/traffic-inspector/sessions/[id]/route.ts"); +const sessionHarRoute = + await import("../../src/app/api/tools/traffic-inspector/sessions/[id]/export.har/route.ts"); const { appendSessionRequest } = await import("../../src/lib/db/inspectorSessions.ts"); test.beforeEach(async () => { @@ -40,7 +36,7 @@ test.beforeEach(async () => { }); test.after(() => { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("POST /sessions: creates a session", async () => { @@ -51,7 +47,7 @@ test("POST /sessions: creates a session", async () => { }); const res = await sessionsRoute.POST(req); assert.equal(res.status, 201); - const body = await res.json() as { id: string; started_at: string }; + const body = (await res.json()) as { id: string; started_at: string }; assert.ok(body.id, "should have an id"); assert.ok(body.started_at, "should have started_at"); }); @@ -85,7 +81,7 @@ test("GET /sessions: lists all sessions", async () => { const res = await sessionsRoute.GET(); assert.equal(res.status, 200); - const body = await res.json() as { sessions: unknown[] }; + const body = (await res.json()) as { sessions: unknown[] }; assert.equal(body.sessions.length, 2); }); @@ -97,7 +93,7 @@ test("PATCH /sessions/[id]: stop adds ended_at", async () => { body: JSON.stringify({}), }) ); - const session = await createRes.json() as { id: string }; + const session = (await createRes.json()) as { id: string }; const patchReq = new Request("http://localhost/", { method: "PATCH", @@ -108,7 +104,7 @@ test("PATCH /sessions/[id]: stop adds ended_at", async () => { params: Promise.resolve({ id: session.id }), }); assert.equal(patchRes.status, 200); - const body = await patchRes.json() as { ended_at: string | null }; + const body = (await patchRes.json()) as { ended_at: string | null }; assert.ok(body.ended_at !== null, "ended_at should be set after stop"); }); @@ -120,7 +116,7 @@ test("PATCH /sessions/[id]: rename updates name", async () => { body: JSON.stringify({ name: "old-name" }), }) ); - const session = await createRes.json() as { id: string }; + const session = (await createRes.json()) as { id: string }; const patchRes = await sessionDetailRoute.PATCH( new Request("http://localhost/", { @@ -131,7 +127,7 @@ test("PATCH /sessions/[id]: rename updates name", async () => { { params: Promise.resolve({ id: session.id }) } ); assert.equal(patchRes.status, 200); - const body = await patchRes.json() as { name: string }; + const body = (await patchRes.json()) as { name: string }; assert.equal(body.name, "new-name"); }); @@ -143,7 +139,7 @@ test("GET /sessions/[id]: returns session with requests", async () => { body: JSON.stringify({ name: "with-reqs" }), }) ); - const session = await createRes.json() as { id: string }; + const session = (await createRes.json()) as { id: string }; // Append a fake request const payload = JSON.stringify({ @@ -163,12 +159,11 @@ test("GET /sessions/[id]: returns session with requests", async () => { }); appendSessionRequest(session.id, payload); - const getRes = await sessionDetailRoute.GET( - new Request("http://localhost/"), - { params: Promise.resolve({ id: session.id }) } - ); + const getRes = await sessionDetailRoute.GET(new Request("http://localhost/"), { + params: Promise.resolve({ id: session.id }), + }); assert.equal(getRes.status, 200); - const body = await getRes.json() as { session: { id: string }; requests: unknown[] }; + const body = (await getRes.json()) as { session: { id: string }; requests: unknown[] }; assert.equal(body.session.id, session.id); assert.equal(body.requests.length, 1); }); @@ -181,21 +176,19 @@ test("DELETE /sessions/[id]: cascades requests", async () => { body: JSON.stringify({}), }) ); - const session = await createRes.json() as { id: string }; + const session = (await createRes.json()) as { id: string }; appendSessionRequest(session.id, JSON.stringify({ note: "test" })); - const delRes = await sessionDetailRoute.DELETE( - new Request("http://localhost/"), - { params: Promise.resolve({ id: session.id }) } - ); + const delRes = await sessionDetailRoute.DELETE(new Request("http://localhost/"), { + params: Promise.resolve({ id: session.id }), + }); assert.equal(delRes.status, 204); // Session should be gone - const getRes = await sessionDetailRoute.GET( - new Request("http://localhost/"), - { params: Promise.resolve({ id: session.id }) } - ); + const getRes = await sessionDetailRoute.GET(new Request("http://localhost/"), { + params: Promise.resolve({ id: session.id }), + }); assert.equal(getRes.status, 404); }); @@ -207,7 +200,7 @@ test("GET /sessions/[id]/export.har: returns HAR file", async () => { body: JSON.stringify({ name: "har-test" }), }) ); - const session = await createRes.json() as { id: string }; + const session = (await createRes.json()) as { id: string }; const reqPayload = { id: randomUUID(), @@ -226,16 +219,15 @@ test("GET /sessions/[id]/export.har: returns HAR file", async () => { }; appendSessionRequest(session.id, JSON.stringify(reqPayload)); - const harRes = await sessionHarRoute.GET( - new Request("http://localhost/"), - { params: Promise.resolve({ id: session.id }) } - ); + const harRes = await sessionHarRoute.GET(new Request("http://localhost/"), { + params: Promise.resolve({ id: session.id }), + }); assert.equal(harRes.status, 200); assert.ok( harRes.headers.get("content-disposition")?.includes(".har"), "should have .har filename" ); - const har = await harRes.json() as { log: { entries: unknown[] } }; + const har = (await harRes.json()) as { log: { entries: unknown[] } }; assert.ok(har.log, "should be a HAR object"); assert.equal(har.log.entries.length, 1); }); diff --git a/tests/integration/traffic-inspector-ws.test.ts b/tests/integration/traffic-inspector-ws.test.ts index 8bd12cad5cc2..656d6ebf8646 100644 --- a/tests/integration/traffic-inspector-ws.test.ts +++ b/tests/integration/traffic-inspector-ws.test.ts @@ -18,9 +18,7 @@ process.env.DATA_DIR = TEST_DATA_DIR; process.env.INSPECTOR_BUFFER_SIZE = "100"; const { TrafficBuffer } = await import("../../src/mitm/inspector/buffer.ts"); -const wsRoute = await import( - "../../src/app/api/tools/traffic-inspector/ws/route.ts" -); +const wsRoute = await import("../../src/app/api/tools/traffic-inspector/ws/route.ts"); function makeRequest(upgrade = "websocket", clientKey = "dGhlIHNhbXBsZSBub25jZQ=="): Request { return new Request("http://localhost/api/tools/traffic-inspector/ws", { @@ -33,14 +31,14 @@ function makeRequest(upgrade = "websocket", clientKey = "dGhlIHNhbXBsZSBub25jZQ= } test.after(() => { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("ws/route: rejects non-WebSocket GET with 426", async () => { const req = new Request("http://localhost/api/tools/traffic-inspector/ws"); const res = await wsRoute.GET(req); assert.equal(res.status, 426); - const body = await res.json() as { error: { message: string } }; + const body = (await res.json()) as { error: { message: string } }; assert.ok(body.error.message.includes("Upgrade"), "should mention upgrade"); }); @@ -57,7 +55,7 @@ test("ws/route: rejects when no raw socket available with 500", async () => { // No `.socket` property injected — Next.js standalone would attach it const res = await wsRoute.GET(req); assert.equal(res.status, 500); - const body = await res.json() as { error: { message: string } }; + const body = (await res.json()) as { error: { message: string } }; assert.ok(!body.error.message.includes("at /"), "must not leak stack trace"); }); diff --git a/tests/integration/v1-models-swr-response-flush-8728.test.ts b/tests/integration/v1-models-swr-response-flush-8728.test.ts index 60aa72c89462..e876244bbb0e 100644 --- a/tests/integration/v1-models-swr-response-flush-8728.test.ts +++ b/tests/integration/v1-models-swr-response-flush-8728.test.ts @@ -81,7 +81,7 @@ function productionShapedSynchronousRefresh() { } test.after(() => { - fs.rmSync(CACHE_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(CACHE_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("the /v1/models route wires Next after() as its response-flush-safe scheduler", () => { @@ -152,7 +152,7 @@ test("an external client receives the stale body before synchronous refresh fini } catch (error) { if ((error as NodeJS.ErrnoException).code === "EPERM") { t.skip("sandbox does not permit opening HTTP listener sockets"); - fs.rmSync(socketDir, { recursive: true, force: true }); + fs.rmSync(socketDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); return; } throw error; @@ -177,7 +177,7 @@ test("an external client receives the stale body before synchronous refresh fini ); } finally { await close(server); - fs.rmSync(socketDir, { recursive: true, force: true }); + fs.rmSync(socketDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); catalogCache.__resetCatalogBuilderRunsForTest(); } }); diff --git a/tests/integration/video-bridge-sampler-ffmpeg.test.ts b/tests/integration/video-bridge-sampler-ffmpeg.test.ts index c33bb770d9c3..e864786bbef4 100644 --- a/tests/integration/video-bridge-sampler-ffmpeg.test.ts +++ b/tests/integration/video-bridge-sampler-ffmpeg.test.ts @@ -112,7 +112,9 @@ test( { skip: REAL_FFMPEG_SKIP }, async (context) => { const directory = await mkdtemp(join(tmpdir(), "omniroute-video-sampler-fixtures-")); - context.after(async () => rm(directory, { force: true, recursive: true })); + context.after(async () => + rm(directory, { force: true, recursive: true, maxRetries: 5, retryDelay: 100 }) + ); const rapidCuts = await createRapidEdgeCutFixture(directory); await context.test("rapid cuts near both ends retain coverage within the cap", async () => { diff --git a/tests/unit/10017-sse-control-lines-leak-openai-clients.test.ts b/tests/unit/10017-sse-control-lines-leak-openai-clients.test.ts index 8e3e4f949578..47ade4adc837 100644 --- a/tests/unit/10017-sse-control-lines-leak-openai-clients.test.ts +++ b/tests/unit/10017-sse-control-lines-leak-openai-clients.test.ts @@ -49,7 +49,7 @@ async function readTransformed(chunks: string[], options: object): Promise { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -59,7 +59,8 @@ function leakedUpstreamControlLines(output: string): string[] { .trim() .split("\n") .filter( - (l) => /^(?:id:|event:|retry:)/i.test(l) || (l.startsWith(":") && !l.startsWith(": x-omniroute-")) + (l) => + /^(?:id:|event:|retry:)/i.test(l) || (l.startsWith(":") && !l.startsWith(": x-omniroute-")) ); } @@ -166,7 +167,9 @@ test("#10017: OpenAI Responses passthrough KEEPS event framing (regression guard "Responses output_text.delta event framing must be preserved" ); assert.ok( - !lines.some((l) => l.startsWith("id:") || (l.startsWith(":") && !l.startsWith(": x-omniroute-"))), + !lines.some( + (l) => l.startsWith("id:") || (l.startsWith(":") && !l.startsWith(": x-omniroute-")) + ), "Responses passthrough must still strip id:/comment control lines" ); }); @@ -191,5 +194,8 @@ test("#10017: Claude Messages passthrough KEEPS event framing", async () => { const lines = text.trim().split("\n"); assert.ok(lines.includes("event: message_start"), "Claude event framing must be preserved"); - assert.ok(lines.includes("event: content_block_delta"), "Claude delta event framing must be preserved"); -}); \ No newline at end of file + assert.ok( + lines.includes("event: content_block_delta"), + "Claude delta event framing must be preserved" + ); +}); diff --git a/tests/unit/10085-compatible-generic-vs-uuid-credential.test.ts b/tests/unit/10085-compatible-generic-vs-uuid-credential.test.ts index cf1a72ad7eba..5bb697f88db4 100644 --- a/tests/unit/10085-compatible-generic-vs-uuid-credential.test.ts +++ b/tests/unit/10085-compatible-generic-vs-uuid-credential.test.ts @@ -33,13 +33,13 @@ const NODE_B_ID = `openai-compatible-chat-558d982b-0000-4000-8000-000000000000`; async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); async function seedNode() { diff --git a/tests/unit/10197-openrouter-image-edits-route.test.ts b/tests/unit/10197-openrouter-image-edits-route.test.ts index a99846ef3a48..eaf8c969a2a9 100644 --- a/tests/unit/10197-openrouter-image-edits-route.test.ts +++ b/tests/unit/10197-openrouter-image-edits-route.test.ts @@ -37,7 +37,7 @@ async function resetStorage() { globalThis.fetch = originalFetch; apiKeysDb.resetApiKeyState(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); v1ModelsCatalog.__resetCatalogBuilderRunsForTest(); } @@ -68,7 +68,7 @@ test.after(() => { globalThis.fetch = originalFetch; apiKeysDb.resetApiKeyState(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#10197 v1 image edit POST forwards built-in openrouter edits to the unified Image API", async () => { @@ -95,10 +95,14 @@ test("#10197 v1 image edit POST forwards built-in openrouter edits to the unifie else if (raw instanceof Uint8Array) hitBody = Buffer.from(raw).toString("utf8"); else if (raw instanceof ArrayBuffer) hitBody = Buffer.from(raw).toString("utf8"); else if (raw && typeof (raw as { arrayBuffer?: unknown }).arrayBuffer === "function") { - hitBody = Buffer.from(await (raw as { arrayBuffer(): Promise }).arrayBuffer()).toString("utf8"); + hitBody = Buffer.from( + await (raw as { arrayBuffer(): Promise }).arrayBuffer() + ).toString("utf8"); } return new Response( - JSON.stringify({ data: [{ b64_json: Buffer.from([0x89, 0x50, 0x4e, 0x47]).toString("base64") }] }), + JSON.stringify({ + data: [{ b64_json: Buffer.from([0x89, 0x50, 0x4e, 0x47]).toString("base64") }], + }), { status: 200, headers: { "content-type": "application/json" } } ); }; diff --git a/tests/unit/10313-catalog-cache-key-hashing.test.ts b/tests/unit/10313-catalog-cache-key-hashing.test.ts index 265fed11505e..ca5b4d1947d5 100644 --- a/tests/unit/10313-catalog-cache-key-hashing.test.ts +++ b/tests/unit/10313-catalog-cache-key-hashing.test.ts @@ -18,7 +18,7 @@ const SECRET = "sk-live-PROBE-10313-SUPER-SECRET-TOKEN"; test.beforeEach(() => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); v1ModelsCatalog.__resetCatalogBuilderRunsForTest(); }); @@ -26,7 +26,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function captureMapKeys(): { keys: string[]; restore: () => void } { @@ -118,14 +118,26 @@ test("cache keys embed the sha256 digest of the secret, never the raw secret (#1 // The hashed fingerprint, not the raw secret, rides in the cache keys. const keysWithDigestA = catalogKeys.filter((k) => k.includes(digestA)); const keysWithDigestB = catalogKeys.filter((k) => k.includes(digestB)); - assert.ok(keysWithDigestA.length > 0, `expected a cache key embedding the fingerprint of A: ${catalogKeys.join(",")}`); - assert.ok(keysWithDigestB.length > 0, `expected a cache key embedding the fingerprint of B: ${catalogKeys.join(",")}`); + assert.ok( + keysWithDigestA.length > 0, + `expected a cache key embedding the fingerprint of A: ${catalogKeys.join(",")}` + ); + assert.ok( + keysWithDigestB.length > 0, + `expected a cache key embedding the fingerprint of B: ${catalogKeys.join(",")}` + ); // Raw secrets must never appear (issue #10313 root cause). assert.ok(!catalogKeys.some((k) => k.includes(rawA) || k.includes(rawB))); // Identical secrets ⇒ identical key (memoized reuse); different ⇒ distinct. - assert.ok(keysWithDigestA.every((k) => k === keysWithDigestA[0]), "all A keys must be identical"); - assert.ok(keysWithDigestB.every((k) => k === keysWithDigestB[0]), "all B keys must be identical"); + assert.ok( + keysWithDigestA.every((k) => k === keysWithDigestA[0]), + "all A keys must be identical" + ); + assert.ok( + keysWithDigestB.every((k) => k === keysWithDigestB[0]), + "all B keys must be identical" + ); assert.notEqual(keysWithDigestA[0], keysWithDigestB[0]); -}); \ No newline at end of file +}); diff --git a/tests/unit/10347-embed-402-cooldown.test.ts b/tests/unit/10347-embed-402-cooldown.test.ts index ddb98259baef..4392ea0f7c81 100644 --- a/tests/unit/10347-embed-402-cooldown.test.ts +++ b/tests/unit/10347-embed-402-cooldown.test.ts @@ -27,17 +27,19 @@ const { handleEmbedding } = await import("../../open-sse/handlers/embeddings.ts" test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function readConnectionRow(connId: string) { const db = core.getDbInstance() as unknown as { prepare: (sql: string) => { - get: (id: string) => { - test_status: unknown; - rate_limited_until: unknown; - last_error_type: unknown; - } | undefined; + get: (id: string) => + | { + test_status: unknown; + rate_limited_until: unknown; + last_error_type: unknown; + } + | undefined; }; }; return db @@ -100,4 +102,4 @@ test("embed 402 marks the connection terminal credits_exhausted (stops re-select } finally { globalThis.fetch = originalFetch; } -}); \ No newline at end of file +}); diff --git a/tests/unit/7993-noauth-proxy-routing.test.ts b/tests/unit/7993-noauth-proxy-routing.test.ts index 32467f14e2f3..dec0ac47a85c 100644 --- a/tests/unit/7993-noauth-proxy-routing.test.ts +++ b/tests/unit/7993-noauth-proxy-routing.test.ts @@ -79,7 +79,7 @@ test.before(async () => { test.after(() => { proxyServer?.close(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#7993 getProviderCredentials('opencode-zen') hydrates the proxy saved under the sibling 'opencode' connection", async () => { diff --git a/tests/unit/8200-perplexity-web-401-cooldown.test.ts b/tests/unit/8200-perplexity-web-401-cooldown.test.ts index d98b22351b54..ee65e5b8171d 100644 --- a/tests/unit/8200-perplexity-web-401-cooldown.test.ts +++ b/tests/unit/8200-perplexity-web-401-cooldown.test.ts @@ -20,13 +20,13 @@ const auth = await import("../../src/sse/services/auth.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("BUG #8200: single perplexity-web 401 (cookie expiry) does not terminal-expire the only connection", async () => { diff --git a/tests/unit/8326-compatible-id-regex.test.ts b/tests/unit/8326-compatible-id-regex.test.ts index 35901f861ba9..a3d491e734f4 100644 --- a/tests/unit/8326-compatible-id-regex.test.ts +++ b/tests/unit/8326-compatible-id-regex.test.ts @@ -29,9 +29,8 @@ process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); const routeModule = await import("../../src/app/api/v1/providers/[provider]/models/route.ts"); -const { isCompatibleProviderConnectionId } = await import( - "../../src/shared/utils/compatibleProviderId.ts" -); +const { isCompatibleProviderConnectionId } = + await import("../../src/shared/utils/compatibleProviderId.ts"); const { getProviderDisplayName } = await import("../../src/lib/display/names.ts"); function makeRequest(provider: string) { @@ -50,7 +49,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); const UUID = "02669115-2545-4896-b003-cb4dac09d441"; @@ -105,10 +104,7 @@ test("GET /v1/providers/:provider/models still rejects unrelated look-alike pref }); test("getProviderDisplayName simplifies all 4 generated compatible id shapes", () => { - assert.equal( - getProviderDisplayName("openai-compatible-chat-" + UUID), - "Compatible (openai)" - ); + assert.equal(getProviderDisplayName("openai-compatible-chat-" + UUID), "Compatible (openai)"); assert.equal( getProviderDisplayName("openai-compatible-responses-" + UUID), "Compatible (openai)" diff --git a/tests/unit/8327-models-owned-by-prefix.test.ts b/tests/unit/8327-models-owned-by-prefix.test.ts index 464845dc90cc..8a4147ffaa42 100644 --- a/tests/unit/8327-models-owned-by-prefix.test.ts +++ b/tests/unit/8327-models-owned-by-prefix.test.ts @@ -39,7 +39,7 @@ const CONFIGURED_PREFIX = "pix4k-talk"; async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); v1ModelsCatalog.__resetCatalogBuilderRunsForTest(); } @@ -50,7 +50,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#8327: synced models on a compatible provider node expose the configured prefix as owned_by, not the raw UUID", async () => { @@ -361,10 +361,7 @@ test("#9416: provider with configured prefix still uses the configured prefix (r // Must still use the configured prefix, NOT slugified name const entry = body.data.find((m) => m.id === `${CONFIGURED_PREFIX}/glm-5.2`); - assert.ok( - entry, - `expected entry with configured prefix "${CONFIGURED_PREFIX}/glm-5.2"` - ); + assert.ok(entry, `expected entry with configured prefix "${CONFIGURED_PREFIX}/glm-5.2"`); assert.equal(entry!.owned_by, CONFIGURED_PREFIX); assert.notEqual(entry!.owned_by, "pix4k-talk-probe"); // not slugified }); diff --git a/tests/unit/8332-combo-vision-fallback.test.ts b/tests/unit/8332-combo-vision-fallback.test.ts index 2e04b8194c88..86ec24e9f2cf 100644 --- a/tests/unit/8332-combo-vision-fallback.test.ts +++ b/tests/unit/8332-combo-vision-fallback.test.ts @@ -96,7 +96,7 @@ test.after(() => { clearModelsDevCapabilities(); settingsDb.clearAllLKGP(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; } else { @@ -153,7 +153,11 @@ test( [], "vision-incapable rr-blind must never receive the image_url body, even as a last-resort fallback" ); - assert.notEqual(result.status, 200, "must not silently succeed via the vision-incapable target"); + assert.notEqual( + result.status, + 200, + "must not silently succeed via the vision-incapable target" + ); } ); diff --git a/tests/unit/8336-audit-loopback-login.test.ts b/tests/unit/8336-audit-loopback-login.test.ts index 2159bac0ba36..038311cf1be1 100644 --- a/tests/unit/8336-audit-loopback-login.test.ts +++ b/tests/unit/8336-audit-loopback-login.test.ts @@ -36,7 +36,7 @@ const originalGetCookieStore = loginRoute.authRouteInternals.getCookieStore; async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); process.env.INITIAL_PASSWORD = "correct-secret-8336"; } @@ -52,7 +52,7 @@ test.afterEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_INITIAL_PASSWORD === undefined) { delete process.env.INITIAL_PASSWORD; } else { diff --git a/tests/unit/8374-plugins-status-optional.test.ts b/tests/unit/8374-plugins-status-optional.test.ts index f5936b7e8760..5c5bae11d520 100644 --- a/tests/unit/8374-plugins-status-optional.test.ts +++ b/tests/unit/8374-plugins-status-optional.test.ts @@ -44,7 +44,7 @@ before(() => { after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (originalDataDir === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = originalDataDir; }); diff --git a/tests/unit/8385-perkey-proxy-global-toggle.test.ts b/tests/unit/8385-perkey-proxy-global-toggle.test.ts index 84a0226ca825..264518c8e75f 100644 --- a/tests/unit/8385-perkey-proxy-global-toggle.test.ts +++ b/tests/unit/8385-perkey-proxy-global-toggle.test.ts @@ -27,13 +27,13 @@ async function resetStorage() { delete process.env.INITIAL_PASSWORD; core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("issue #8385: global perKeyProxyEnabled=false must override a connection's per_key_proxy_enabled=1", async () => { diff --git a/tests/unit/8388-compression-detail-persist.test.ts b/tests/unit/8388-compression-detail-persist.test.ts index c65d29ba95c0..cdddcf219b1a 100644 --- a/tests/unit/8388-compression-detail-persist.test.ts +++ b/tests/unit/8388-compression-detail-persist.test.ts @@ -18,17 +18,15 @@ import path from "node:path"; const tmpDataDir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-8388-")); process.env.DATA_DIR = tmpDataDir; -const { compressionSettingsUpdateSchema } = await import( - "../../src/shared/validation/compressionConfigSchemas.ts" -); +const { compressionSettingsUpdateSchema } = + await import("../../src/shared/validation/compressionConfigSchemas.ts"); const { resetDbInstance } = await import("../../src/lib/db/core.ts"); -const { getCompressionSettings, updateCompressionSettings } = await import( - "../../src/lib/db/compression.ts" -); +const { getCompressionSettings, updateCompressionSettings } = + await import("../../src/lib/db/compression.ts"); test.after(() => { resetDbInstance(); - fs.rmSync(tmpDataDir, { recursive: true, force: true }); + fs.rmSync(tmpDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#8388: PUT body carrying ccr detail (minChars/retrievalRampFactor) is ACCEPTED by the schema", () => { diff --git a/tests/unit/8395-plugin-hooks-fire.test.ts b/tests/unit/8395-plugin-hooks-fire.test.ts index 2d91d3611efc..1d8c2b561edd 100644 --- a/tests/unit/8395-plugin-hooks-fire.test.ts +++ b/tests/unit/8395-plugin-hooks-fire.test.ts @@ -26,7 +26,7 @@ test( t.after(async () => { loaded?.cleanup(); - await rm(pluginDir, { recursive: true, force: true }); + await rm(pluginDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); await writeFile( @@ -98,23 +98,20 @@ export async function onRequest(ctx) { } ); -test( - "loadPlugin no longer spawns the plugin host with stdout/stderr fully ignored", - async () => { - const source = await readFile( - join(import.meta.dirname, "../../src/lib/plugins/loader.ts"), - "utf-8" - ); - // The original bug: stdio: ["ignore", "ignore", "ignore", "ipc"] discards - // stdout (fd 1) and stderr (fd 2) at the OS level unconditionally. - assert.doesNotMatch( - source, - /stdio:\s*\[\s*["']ignore["']\s*,\s*["']ignore["']\s*,\s*["']ignore["']\s*,\s*["']ipc["']\s*\]/, - "loader.ts must not spawn the plugin host with stdout+stderr both set to " + - "'ignore' — that silently discards all plugin console.log/console.error output" - ); - } -); +test("loadPlugin no longer spawns the plugin host with stdout/stderr fully ignored", async () => { + const source = await readFile( + join(import.meta.dirname, "../../src/lib/plugins/loader.ts"), + "utf-8" + ); + // The original bug: stdio: ["ignore", "ignore", "ignore", "ipc"] discards + // stdout (fd 1) and stderr (fd 2) at the OS level unconditionally. + assert.doesNotMatch( + source, + /stdio:\s*\[\s*["']ignore["']\s*,\s*["']ignore["']\s*,\s*["']ignore["']\s*,\s*["']ipc["']\s*\]/, + "loader.ts must not spawn the plugin host with stdout+stderr both set to " + + "'ignore' — that silently discards all plugin console.log/console.error output" + ); +}); // Secondary #8395 finding: runPluginOnResponseHook was only wired into chatCore.ts's // STREAMING success path — the non-streaming (stream:false) JSON-return branch @@ -131,7 +128,9 @@ test("chatCore.ts calls runPluginOnResponseHook from both the non-streaming and "utf-8" ); - const nonStreamingReturnIndex = source.indexOf("buildNonStreamingJsonResponse(translatedResponse"); + const nonStreamingReturnIndex = source.indexOf( + "buildNonStreamingJsonResponse(translatedResponse" + ); const hookCallNeedle = "await runPluginOnResponseHook({"; const hookCallIndex = source.indexOf(hookCallNeedle); const secondHookCallIndex = source.indexOf(hookCallNeedle, hookCallIndex + 1); diff --git a/tests/unit/8431-multiwindow-quota-eviction.test.ts b/tests/unit/8431-multiwindow-quota-eviction.test.ts index ebe0767a50c2..f62306db6ba6 100644 --- a/tests/unit/8431-multiwindow-quota-eviction.test.ts +++ b/tests/unit/8431-multiwindow-quota-eviction.test.ts @@ -35,10 +35,17 @@ const quotaCache = await import("../../src/domain/quotaCache.ts"); test.after(() => { coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); -const COLD_WINDOWS = ["Bonus Pack 1", "Bonus Pack 2", "Bonus Pack 3", "Bonus Pack 4", "Weekly", "Daily"]; +const COLD_WINDOWS = [ + "Bonus Pack 1", + "Bonus Pack 2", + "Bonus Pack 3", + "Bonus Pack 4", + "Weekly", + "Daily", +]; const HOT_WINDOWS = ["Monthly", "Bonus Pack 5", "Bonus Pack 6"]; test("#8431 idle healthy windows survive rehydration even when hot windows accumulate >200 rows", () => { diff --git a/tests/unit/8488-capability-filter-fail-closed.test.ts b/tests/unit/8488-capability-filter-fail-closed.test.ts index 6a77e104347a..2be07aa7e71a 100644 --- a/tests/unit/8488-capability-filter-fail-closed.test.ts +++ b/tests/unit/8488-capability-filter-fail-closed.test.ts @@ -67,13 +67,13 @@ const log = { test.beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); }); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#8488 filter: some tool-capable targets kept (unchanged)", () => { diff --git a/tests/unit/8510-adobe-firefly-edits-route.test.ts b/tests/unit/8510-adobe-firefly-edits-route.test.ts index 5e80cad4d4c8..edbb8c4b2418 100644 --- a/tests/unit/8510-adobe-firefly-edits-route.test.ts +++ b/tests/unit/8510-adobe-firefly-edits-route.test.ts @@ -19,10 +19,8 @@ const providersDb = await import("../../src/lib/db/providers.ts"); const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); const imageEditRoute = await import("../../src/app/api/v1/images/edits/route.ts"); const v1ModelsCatalog = await import("../../src/app/api/v1/models/catalog.ts"); -const { - ADOBE_FIREFLY_IMAGE_UPLOAD_URL, - ADOBE_FIREFLY_IMAGE_SUBMIT_URL, -} = await import("../../open-sse/services/adobeFireflyClient.ts"); +const { ADOBE_FIREFLY_IMAGE_UPLOAD_URL, ADOBE_FIREFLY_IMAGE_SUBMIT_URL } = + await import("../../open-sse/services/adobeFireflyClient.ts"); interface ErrorResponseBody { error: { message: string; code?: string }; @@ -38,7 +36,7 @@ async function resetStorage() { globalThis.fetch = originalFetch; apiKeysDb.resetApiKeyState(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); v1ModelsCatalog.__resetCatalogBuilderRunsForTest(); } @@ -86,7 +84,7 @@ test.after(() => { globalThis.fetch = originalFetch; apiKeysDb.resetApiKeyState(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#8510 v1 image edit POST uploads Adobe Firefly reference images and dispatches referenceBlobs", async () => { @@ -148,10 +146,7 @@ test("#8510 v1 image edit POST uploads Adobe Firefly reference images and dispat id: string; }>; assert.ok(Array.isArray(referenceBlobs), "generate-async payload must carry referenceBlobs"); - assert.deepEqual( - referenceBlobs.map((r) => r.id).sort(), - [...uploadedIds].sort() - ); + assert.deepEqual(referenceBlobs.map((r) => r.id).sort(), [...uploadedIds].sort()); }); test("#8510 v1 image edit POST rejects more than 4 Adobe Firefly reference images", async () => { @@ -206,7 +201,9 @@ test("#8510 v1 image edit POST surfaces missing Adobe Firefly credentials", asyn }); test("#8510 v1 image edit POST surfaces Adobe Firefly rate-limit sentinel", async () => { - await seedAdobeFireflyConnection({ rateLimitedUntil: new Date(Date.now() + 60_000).toISOString() }); + await seedAdobeFireflyConnection({ + rateLimitedUntil: new Date(Date.now() + 60_000).toISOString(), + }); globalThis.fetch = async () => { throw new Error("Rate-limited path must not reach upstream"); }; diff --git a/tests/unit/8779-agy-prefix-credential-lookup.test.ts b/tests/unit/8779-agy-prefix-credential-lookup.test.ts index 446f1e708b48..0a9ddb81d520 100644 --- a/tests/unit/8779-agy-prefix-credential-lookup.test.ts +++ b/tests/unit/8779-agy-prefix-credential-lookup.test.ts @@ -33,7 +33,7 @@ const model = await import("../../open-sse/services/model.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -52,7 +52,7 @@ async function seedOnly(provider: string) { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("the agy/ prefix still canonicalizes to antigravity (#8013 unchanged)", () => { diff --git a/tests/unit/8958-alias-backed-node-prefix.test.ts b/tests/unit/8958-alias-backed-node-prefix.test.ts index f823d7dd625b..1ec37506f734 100644 --- a/tests/unit/8958-alias-backed-node-prefix.test.ts +++ b/tests/unit/8958-alias-backed-node-prefix.test.ts @@ -38,7 +38,7 @@ const MODEL_ID = "opc/big-pickle"; async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); v1ModelsCatalog.__resetCatalogBuilderRunsForTest(); } @@ -85,7 +85,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#8958: alias-backed model on a compatible node is not duplicated under the raw UUID prefix (alias mode)", async () => { diff --git a/tests/unit/9034-alias-backed-prefix-id-repro.test.ts b/tests/unit/9034-alias-backed-prefix-id-repro.test.ts index 76188b69bd9f..81d470c7ff93 100644 --- a/tests/unit/9034-alias-backed-prefix-id-repro.test.ts +++ b/tests/unit/9034-alias-backed-prefix-id-repro.test.ts @@ -29,7 +29,8 @@ type CoreModule = typeof import("../../src/lib/db/core.ts"); type ProvidersDbModule = typeof import("../../src/lib/db/providers.ts"); type ModelsDbModule = typeof import("../../src/lib/db/models.ts"); type CatalogModule = typeof import("../../src/app/api/v1/models/catalog.ts"); -type ManagedAvailableModelsModule = typeof import("../../src/lib/providerModels/managedAvailableModels.ts"); +type ManagedAvailableModelsModule = + typeof import("../../src/lib/providerModels/managedAvailableModels.ts"); let core: CoreModule; let providersDb: ProvidersDbModule; @@ -45,7 +46,7 @@ const MODEL_NAME = "kimi-k2"; async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); v1ModelsCatalog.__resetCatalogBuilderRunsForTest(); } @@ -61,7 +62,7 @@ test.before(async () => { test.after(async () => { if (core) core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#9034: alias-backed model id must use the configured prefix, not the raw provider-node UUID", async () => { @@ -117,4 +118,4 @@ test("#9034: alias-backed model id must use the configured prefix, not the raw p `entry id "${id}" must not start with the raw provider-node UUID "${NODE_ID}" when a prefix ("${CONFIGURED_PREFIX}") is configured` ); } -}); \ No newline at end of file +}); diff --git a/tests/unit/9134-repro-audio-combo-rejection.test.ts b/tests/unit/9134-repro-audio-combo-rejection.test.ts index cdf26933cd3f..a54a63765102 100644 --- a/tests/unit/9134-repro-audio-combo-rejection.test.ts +++ b/tests/unit/9134-repro-audio-combo-rejection.test.ts @@ -22,7 +22,7 @@ const originalFetch = globalThis.fetch; test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); /** Minimal but structurally valid WAV so nothing rejects the upload shape. */ @@ -72,8 +72,7 @@ test("#9134 combo name is rejected instead of resolved", async () => { new Response(JSON.stringify({ text: "ok" }), { status: 200, headers: { "Content-Type": "application/json" }, - }) - ) as typeof fetch; + })) as typeof fetch; const res = await route.POST(transcriptionRequest("transcricao")); const body = await res.text(); @@ -92,4 +91,4 @@ test("#9134 combo name is rejected instead of resolved", async () => { !body.includes("Invalid transcription model"), `BUG #9134: combo name was not resolved — got: ${body}` ); -}); \ No newline at end of file +}); diff --git a/tests/unit/9147-catalog-eventloop-yield.test.ts b/tests/unit/9147-catalog-eventloop-yield.test.ts index 91068f367ead..a5102eebc6d7 100644 --- a/tests/unit/9147-catalog-eventloop-yield.test.ts +++ b/tests/unit/9147-catalog-eventloop-yield.test.ts @@ -18,7 +18,7 @@ const MODELS_PER_CONNECTION = 12; // ~720 synced models total async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); v1ModelsCatalog.__resetCatalogBuilderRunsForTest(); } @@ -55,7 +55,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#9147 — catalog build at catalog-scale must not pin the event loop for a long stretch", async (t) => { diff --git a/tests/unit/9201-search-proxy-bypass.test.ts b/tests/unit/9201-search-proxy-bypass.test.ts index 48fcff4858b6..98440bd35df4 100644 --- a/tests/unit/9201-search-proxy-bypass.test.ts +++ b/tests/unit/9201-search-proxy-bypass.test.ts @@ -66,7 +66,7 @@ test.after(async () => { searchRegistry.SEARCH_PROVIDERS["serper-search"].baseUrl = originalSerperBaseUrl; await new Promise((resolve) => proxyServer.close(() => resolve())); core.resetDbInstance(); - fs.rmSync(dataDir, { recursive: true, force: true }); + fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function installProxyResponseCounter() { diff --git a/tests/unit/9232-purge-proxy-assignments-on-delete.test.ts b/tests/unit/9232-purge-proxy-assignments-on-delete.test.ts index aad652d00525..2c56c8d6f3ff 100644 --- a/tests/unit/9232-purge-proxy-assignments-on-delete.test.ts +++ b/tests/unit/9232-purge-proxy-assignments-on-delete.test.ts @@ -17,7 +17,8 @@ async function resetStorage() { core.resetDbInstance(); for (let attempt = 0; attempt < 10; attempt++) { try { - if (fs.existsSync(TEST_DATA_DIR)) fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + if (fs.existsSync(TEST_DATA_DIR)) + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); break; } catch (error: unknown) { const code = (error as { code?: string } | undefined)?.code; @@ -34,7 +35,7 @@ test.beforeEach(async () => { }); test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); async function setupConnectionWithAssignment() { diff --git a/tests/unit/_mocks/settings.ts b/tests/unit/_mocks/settings.ts index 2ccd31da524d..d3d249f1fb57 100644 --- a/tests/unit/_mocks/settings.ts +++ b/tests/unit/_mocks/settings.ts @@ -46,11 +46,11 @@ export function setupSettingsFixture(slug: string): SettingsFixture { const runtime = await import("../../../src/lib/config/runtimeSettings.ts"); core.resetDbInstance(); runtime.resetRuntimeSettingsStateForTests(); - fs.rmSync(testDataDir, { recursive: true, force: true }); + fs.rmSync(testDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(testDataDir, { recursive: true }); }, cleanup() { - fs.rmSync(testDataDir, { recursive: true, force: true }); + fs.rmSync(testDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }, }; activeFixture = fixture; diff --git a/tests/unit/a2a-auth-timing-safe.test.ts b/tests/unit/a2a-auth-timing-safe.test.ts index 57ecbb499552..3e14c90cb890 100644 --- a/tests/unit/a2a-auth-timing-safe.test.ts +++ b/tests/unit/a2a-auth-timing-safe.test.ts @@ -34,14 +34,14 @@ function makeJsonRpcRequest(token?: string): NextRequest { test.beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); process.env.OMNIROUTE_API_KEY = API_KEY; }); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; diff --git a/tests/unit/a2a-enabled-route.test.ts b/tests/unit/a2a-enabled-route.test.ts index 2c52cdf9acb1..a5a6a44111ab 100644 --- a/tests/unit/a2a-enabled-route.test.ts +++ b/tests/unit/a2a-enabled-route.test.ts @@ -19,7 +19,7 @@ const a2aRoute = await import("../../src/app/a2a/route.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -38,7 +38,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; diff --git a/tests/unit/a2a-route-require-api-key.test.ts b/tests/unit/a2a-route-require-api-key.test.ts index 4fec1a55c806..592aeec9dd97 100644 --- a/tests/unit/a2a-route-require-api-key.test.ts +++ b/tests/unit/a2a-route-require-api-key.test.ts @@ -24,7 +24,7 @@ const ORIGINAL_A2A_KEY = process.env.OMNIROUTE_API_KEY; test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_REQUIRE === undefined) delete process.env.REQUIRE_API_KEY; else process.env.REQUIRE_API_KEY = ORIGINAL_REQUIRE; if (ORIGINAL_A2A_KEY === undefined) delete process.env.OMNIROUTE_API_KEY; diff --git a/tests/unit/a2a-task-owner-idor.test.ts b/tests/unit/a2a-task-owner-idor.test.ts index 365744ed33cb..aaa35142c8cc 100644 --- a/tests/unit/a2a-task-owner-idor.test.ts +++ b/tests/unit/a2a-task-owner-idor.test.ts @@ -35,7 +35,7 @@ const ORIGINAL_REQUIRE = process.env.REQUIRE_API_KEY; after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_REQUIRE === undefined) delete process.env.REQUIRE_API_KEY; else process.env.REQUIRE_API_KEY = ORIGINAL_REQUIRE; }); diff --git a/tests/unit/a2a-v1-compat-10839.test.ts b/tests/unit/a2a-v1-compat-10839.test.ts index 7aeea86542c4..6df10dbbc9ac 100644 --- a/tests/unit/a2a-v1-compat-10839.test.ts +++ b/tests/unit/a2a-v1-compat-10839.test.ts @@ -37,14 +37,14 @@ function makeJsonRpcRequest(body: unknown): NextRequest { test.beforeEach(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); await settingsDb.updateSettings({ a2aEnabled: true }); }); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#10839: v1.0 SendMessage is aliased to message/send and reshapes the response", async () => { diff --git a/tests/unit/access-tokens-db.test.ts b/tests/unit/access-tokens-db.test.ts index 9593b5161364..d0d6c4c89005 100644 --- a/tests/unit/access-tokens-db.test.ts +++ b/tests/unit/access-tokens-db.test.ts @@ -18,7 +18,7 @@ test.after(() => { core.resetDbInstance(); } catch {} try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch {} }); diff --git a/tests/unit/account-concurrency-cap.test.ts b/tests/unit/account-concurrency-cap.test.ts index 425f27b56509..0179f9335031 100644 --- a/tests/unit/account-concurrency-cap.test.ts +++ b/tests/unit/account-concurrency-cap.test.ts @@ -31,7 +31,7 @@ function getConnectionId(connection: NonNullable): string { async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -51,7 +51,7 @@ beforeEach(async () => { after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); describe("maxConcurrent DB round-trip", () => { diff --git a/tests/unit/account-fallback-service.test.ts b/tests/unit/account-fallback-service.test.ts index 266179ec7a53..d28075545a15 100644 --- a/tests/unit/account-fallback-service.test.ts +++ b/tests/unit/account-fallback-service.test.ts @@ -1617,7 +1617,7 @@ test("isAccountDeactivated matches a custom signal after setCustomBannedSignals" async function resetStorage10460() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR_10460, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR_10460, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR_10460, { recursive: true }); } @@ -1634,7 +1634,7 @@ async function seedConn10460(provider: string): Promise { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR_10460, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR_10460, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#10460: model-unsupported 400 returns shouldFallback:false (no account cooldown)", async () => { diff --git a/tests/unit/acp-agents-route.test.ts b/tests/unit/acp-agents-route.test.ts index 2d3065a9a85b..a64a2f0f9d94 100644 --- a/tests/unit/acp-agents-route.test.ts +++ b/tests/unit/acp-agents-route.test.ts @@ -18,7 +18,7 @@ const ORIGINAL_JWT_SECRET = process.env.JWT_SECRET; async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); delete process.env.INITIAL_PASSWORD; delete process.env.JWT_SECRET; @@ -50,7 +50,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_INITIAL_PASSWORD === undefined) { delete process.env.INITIAL_PASSWORD; diff --git a/tests/unit/active-request-stream-chunks-lifecycle.test.ts b/tests/unit/active-request-stream-chunks-lifecycle.test.ts index 95547d8e6138..35fd1cbc2d84 100644 --- a/tests/unit/active-request-stream-chunks-lifecycle.test.ts +++ b/tests/unit/active-request-stream-chunks-lifecycle.test.ts @@ -18,7 +18,7 @@ const stripChunkTs = (chunk: string): string => chunk.replace(/^\[\d{2}:\d{2}:\d test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ─── Helper: Simulates /api/logs/[id] API route logic ────────────────────── diff --git a/tests/unit/admin-audit-events.test.ts b/tests/unit/admin-audit-events.test.ts index b8aa06710b6e..d521d2a0d443 100644 --- a/tests/unit/admin-audit-events.test.ts +++ b/tests/unit/admin-audit-events.test.ts @@ -24,7 +24,7 @@ const originalGetLogoutCookieStore = logoutRoute.logoutRouteInternals.getCookieS function resetDb() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -39,7 +39,7 @@ test.afterEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("auth login/logout routes emit structured audit events with ip and request id", async () => { diff --git a/tests/unit/agent-bridge-cert-regenerate-force-10467.test.ts b/tests/unit/agent-bridge-cert-regenerate-force-10467.test.ts index fc600982c610..b8af604efcbe 100644 --- a/tests/unit/agent-bridge-cert-regenerate-force-10467.test.ts +++ b/tests/unit/agent-bridge-cert-regenerate-force-10467.test.ts @@ -23,7 +23,7 @@ async function withTempDataDir(fn: (dir: string) => Promise): Promise { } finally { if (previous === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = previous; - fs.rmSync(dir, { recursive: true, force: true }); + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } diff --git a/tests/unit/agent-bridge-config-portability.test.ts b/tests/unit/agent-bridge-config-portability.test.ts index 8a01a90167e6..a398ad980ff4 100644 --- a/tests/unit/agent-bridge-config-portability.test.ts +++ b/tests/unit/agent-bridge-config-portability.test.ts @@ -10,9 +10,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -const TEST_DATA_DIR = fs.mkdtempSync( - path.join(os.tmpdir(), "omniroute-agentbridge-config-") -); +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-agentbridge-config-")); process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); @@ -22,7 +20,8 @@ async function resetStorage() { core.resetDbInstance(); for (let attempt = 0; attempt < 10; attempt++) { try { - if (fs.existsSync(TEST_DATA_DIR)) fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + if (fs.existsSync(TEST_DATA_DIR)) + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); break; } catch (error: unknown) { const code = (error as { code?: string } | null)?.code; @@ -39,7 +38,7 @@ test.beforeEach(async () => { }); test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("AgentBridgeConfigSchema accepts a well-formed config", () => { @@ -76,9 +75,7 @@ test("import then export roundtrips bypass + custom hosts + mappings", () => { const config = { version: 1 as const, bypassPatterns: ["*.bank.test", "literal.example.com"], - customHosts: [ - { host: "api.internal.test", kind: "custom" as const, label: "Internal LLM" }, - ], + customHosts: [{ host: "api.internal.test", kind: "custom" as const, label: "Internal LLM" }], agentMappings: { cursor: [{ source: "gpt-4o", target: "claude-sonnet-4-5" }], }, diff --git a/tests/unit/agent-bridge-mappings-sync-8656.test.ts b/tests/unit/agent-bridge-mappings-sync-8656.test.ts index 7d98ef82b48f..ce477ef33e41 100644 --- a/tests/unit/agent-bridge-mappings-sync-8656.test.ts +++ b/tests/unit/agent-bridge-mappings-sync-8656.test.ts @@ -25,7 +25,7 @@ const { getMitmAlias } = await import("../../src/lib/db/models/mitmAlias.ts"); function resetDb() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -35,7 +35,7 @@ test.beforeEach(() => { test.after(() => { try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { /* noop */ } diff --git a/tests/unit/agent-bridge-server-route-dynamic-import.test.ts b/tests/unit/agent-bridge-server-route-dynamic-import.test.ts index a207fd890aa9..0970e0e90228 100644 --- a/tests/unit/agent-bridge-server-route-dynamic-import.test.ts +++ b/tests/unit/agent-bridge-server-route-dynamic-import.test.ts @@ -20,14 +20,14 @@ const serverRoute = await import("../../src/app/api/tools/agent-bridge/server/ro function resetDb() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.beforeEach(() => resetDb()); test.after(() => { try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { /* noop */ } diff --git a/tests/unit/agent-bridge-state-full-payload-8656.test.ts b/tests/unit/agent-bridge-state-full-payload-8656.test.ts index c53bc040c017..885248f16ef8 100644 --- a/tests/unit/agent-bridge-state-full-payload-8656.test.ts +++ b/tests/unit/agent-bridge-state-full-payload-8656.test.ts @@ -30,7 +30,7 @@ const { replaceUserBypassPatterns } = await import("../../src/lib/db/agentBridge function resetDb() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -40,7 +40,7 @@ test.beforeEach(() => { test.after(() => { try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { /* noop */ } diff --git a/tests/unit/agentSkills-cliRegistryParser.test.ts b/tests/unit/agentSkills-cliRegistryParser.test.ts index 0e18080e7082..1990c591d62d 100644 --- a/tests/unit/agentSkills-cliRegistryParser.test.ts +++ b/tests/unit/agentSkills-cliRegistryParser.test.ts @@ -29,7 +29,7 @@ function withFixtureCli(files: Record): { cleanup: () => void } return { cleanup() { process.chdir(originalCwd); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }, }; } @@ -315,7 +315,7 @@ test("parseCliRegistry() throws if commands directory is missing", () => { ); } finally { process.chdir(originalCwd); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/agentSkills-generator.test.ts b/tests/unit/agentSkills-generator.test.ts index 9391af4a7fdb..66360b34cef1 100644 --- a/tests/unit/agentSkills-generator.test.ts +++ b/tests/unit/agentSkills-generator.test.ts @@ -30,7 +30,7 @@ function mkTmpDir(): string { /** Cleanup a tmp directory. */ function rmTmpDir(dir: string): void { try { - fs.rmSync(dir, { recursive: true, force: true }); + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // best-effort cleanup } diff --git a/tests/unit/agentSkills-openapiParser.test.ts b/tests/unit/agentSkills-openapiParser.test.ts index a3273dbce831..c5988644dcab 100644 --- a/tests/unit/agentSkills-openapiParser.test.ts +++ b/tests/unit/agentSkills-openapiParser.test.ts @@ -28,7 +28,7 @@ function withFixtureOpenapi(yamlContent: string): { cleanup: () => void } { return { cleanup() { process.chdir(originalCwd); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }, }; } @@ -191,7 +191,7 @@ test("parseOpenapi() throws if openapi.yaml is missing", () => { ); } finally { process.chdir(originalCwd); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/agentSkills-routes.test.ts b/tests/unit/agentSkills-routes.test.ts index d2eaa5fbb8b1..18ab884fbc73 100644 --- a/tests/unit/agentSkills-routes.test.ts +++ b/tests/unit/agentSkills-routes.test.ts @@ -46,7 +46,7 @@ const generateRoute = await import("../../src/app/api/agent-skills/generate/rout async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); delete process.env.INITIAL_PASSWORD; } @@ -76,7 +76,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; diff --git a/tests/unit/agentbridge-antigravity-cert-hosts-6494.test.ts b/tests/unit/agentbridge-antigravity-cert-hosts-6494.test.ts index 16802be699d9..3218daa0b036 100644 --- a/tests/unit/agentbridge-antigravity-cert-hosts-6494.test.ts +++ b/tests/unit/agentbridge-antigravity-cert-hosts-6494.test.ts @@ -38,7 +38,7 @@ test("generateCert() issues a cert whose SAN list covers all 4 antigravity hosts t.after(() => { if (previousDataDir === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = previousDataDir; - fs.rmSync(tmpDataDir, { recursive: true, force: true }); + fs.rmSync(tmpDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // Fresh module instance so it re-reads process.env.DATA_DIR via resolveMitmDataDir(). @@ -50,9 +50,6 @@ test("generateCert() issues a cert whose SAN list covers all 4 antigravity hosts const san = cert.subjectAltName ?? ""; for (const host of EXPECTED_HOSTS) { - assert.ok( - san.includes(host), - `expected generated cert SAN to include "${host}" — got: ${san}` - ); + assert.ok(san.includes(host), `expected generated cert SAN to include "${host}" — got: ${san}`); } }); diff --git a/tests/unit/agentbridge-mitm-router-key-6403.test.ts b/tests/unit/agentbridge-mitm-router-key-6403.test.ts index 593ce713ba01..5f2009abafb4 100644 --- a/tests/unit/agentbridge-mitm-router-key-6403.test.ts +++ b/tests/unit/agentbridge-mitm-router-key-6403.test.ts @@ -27,13 +27,12 @@ process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "test-secret-for-agen const core = await import("../../src/lib/db/core.ts"); const { createApiKey } = await import("../../src/lib/db/apiKeys.ts"); -const { resolveRouterApiKey } = await import( - "../../src/app/api/tools/agent-bridge/server/route.ts" -); +const { resolveRouterApiKey } = + await import("../../src/app/api/tools/agent-bridge/server/route.ts"); function resetDb() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -45,7 +44,7 @@ test.beforeEach(() => { test.after(() => { delete process.env.ROUTER_API_KEY; try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { /* noop */ } diff --git a/tests/unit/agentrouter-chatcore-protocols.test.ts b/tests/unit/agentrouter-chatcore-protocols.test.ts index ece0f7c5f556..27c40bb8c5d3 100644 --- a/tests/unit/agentrouter-chatcore-protocols.test.ts +++ b/tests/unit/agentrouter-chatcore-protocols.test.ts @@ -38,14 +38,14 @@ test.afterEach(async () => { globalThis.fetch = originalFetch; await flushAsyncSideEffects(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); }); test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("AgentRouter Responses requests automatically use the native Responses protocol", async () => { diff --git a/tests/unit/agentrouter-lock-scope-10334.test.ts b/tests/unit/agentrouter-lock-scope-10334.test.ts index 1205ec5cd298..843627fa333b 100644 --- a/tests/unit/agentrouter-lock-scope-10334.test.ts +++ b/tests/unit/agentrouter-lock-scope-10334.test.ts @@ -21,9 +21,8 @@ const core = await import("../../src/lib/db/core.ts"); const providersDb = await import("../../src/lib/db/providers.ts"); const auth = await import("../../src/sse/services/auth.ts"); const accountFallback = await import("../../open-sse/services/accountFallback.ts"); -const { applyComboTargetExhaustion } = await import( - "../../open-sse/services/combo/targetExhaustion.ts" -); +const { applyComboTargetExhaustion } = + await import("../../open-sse/services/combo/targetExhaustion.ts"); const { classifyProviderError } = await import("../../open-sse/services/errorClassifier.ts"); const QUOTA_EXHAUSTED_429 = '{"error":{"message":"账户额度不足,请充值后重试"}}'; @@ -31,7 +30,7 @@ const MODEL_ACCESS_DENIED_403 = '{"error":{"message":"无权访问模型 claude- async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -52,7 +51,7 @@ async function seedConnection( test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("agentrouter 429 account quota exhausted -> connection cooldown, never terminal", async () => { diff --git a/tests/unit/agnes-provider.test.ts b/tests/unit/agnes-provider.test.ts index 8d4bdc52e01e..bc15ab9e8ce1 100644 --- a/tests/unit/agnes-provider.test.ts +++ b/tests/unit/agnes-provider.test.ts @@ -24,7 +24,7 @@ const dbCore = await import("../../src/lib/db/core.ts"); test.after(() => { dbCore.closeDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); const AGNES_CHAT_URL = "https://apihub.agnes-ai.com/v1/chat/completions"; diff --git a/tests/unit/aihorde-optional-api-key.test.ts b/tests/unit/aihorde-optional-api-key.test.ts index 9a6966986058..cdede1c2261c 100644 --- a/tests/unit/aihorde-optional-api-key.test.ts +++ b/tests/unit/aihorde-optional-api-key.test.ts @@ -20,7 +20,7 @@ const { isManagedProviderConnectionId } = await import("../../src/lib/providers/ test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("aihorde treats a registered key as optional, not required", () => { diff --git a/tests/unit/airforce-v1-double-prefix-5899.test.ts b/tests/unit/airforce-v1-double-prefix-5899.test.ts index 288fbd0d03eb..2daf2823111c 100644 --- a/tests/unit/airforce-v1-double-prefix-5899.test.ts +++ b/tests/unit/airforce-v1-double-prefix-5899.test.ts @@ -23,7 +23,7 @@ const modelsRoute = await import("../../src/app/api/providers/[id]/models/route. test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#5899 openai gateway baseUrl ending in /v1/chat/completions never probes /v1/v1/models", async () => { diff --git a/tests/unit/alibaba-free-tier-allowlist.test.ts b/tests/unit/alibaba-free-tier-allowlist.test.ts index df468d82e533..71a31a470c10 100644 --- a/tests/unit/alibaba-free-tier-allowlist.test.ts +++ b/tests/unit/alibaba-free-tier-allowlist.test.ts @@ -41,10 +41,7 @@ test("built-in allowlist includes operator free models and excludes paid blockli * the expiry, with packs this test owns and dates it controls — never the * freshness of the catalog that ships in the repo. */ -function withAllowlistPack( - pack: Record, - assertions: () => void -): void { +function withAllowlistPack(pack: Record, assertions: () => void): void { const dir = mkdtempSync(join(tmpdir(), "alibaba-allowlist-")); const packPath = join(dir, "allowlist.json"); writeFileSync(packPath, JSON.stringify(pack), "utf8"); @@ -58,7 +55,7 @@ function withAllowlistPack( if (previousPath) process.env.ALIBABA_FREE_TIER_ALLOWLIST_PATH = previousPath; else delete process.env.ALIBABA_FREE_TIER_ALLOWLIST_PATH; resetAlibabaFreeTierAllowlistCache(); - rmSync(dir, { recursive: true, force: true }); + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } diff --git a/tests/unit/antigravity-429-quota-cooldown.test.ts b/tests/unit/antigravity-429-quota-cooldown.test.ts index c420781f0d94..0191391d8a7c 100644 --- a/tests/unit/antigravity-429-quota-cooldown.test.ts +++ b/tests/unit/antigravity-429-quota-cooldown.test.ts @@ -42,7 +42,7 @@ import { test.after(() => { clearAllModelLockouts(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ── Engine contract (regression guard) ─────────────────────────────────────── diff --git a/tests/unit/antigravity-client-identity-paths.test.ts b/tests/unit/antigravity-client-identity-paths.test.ts index 8801750201c6..9ce32df8a5e6 100644 --- a/tests/unit/antigravity-client-identity-paths.test.ts +++ b/tests/unit/antigravity-client-identity-paths.test.ts @@ -32,7 +32,7 @@ test.afterEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("executor token refresh uses the selected CLI identity", async () => { diff --git a/tests/unit/antigravity-local-usage-fallback-3821.test.ts b/tests/unit/antigravity-local-usage-fallback-3821.test.ts index 38c5f1464d9e..23ba88bfef0d 100644 --- a/tests/unit/antigravity-local-usage-fallback-3821.test.ts +++ b/tests/unit/antigravity-local-usage-fallback-3821.test.ts @@ -30,7 +30,7 @@ const originalFetch = globalThis.fetch; test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("Antigravity fetchAvailableModels(used=0) → localUsageHistory when usage_history has rows", async () => { diff --git a/tests/unit/antigravity-missing-project-autodisable.test.ts b/tests/unit/antigravity-missing-project-autodisable.test.ts index aff0172790ff..bcf024e854ed 100644 --- a/tests/unit/antigravity-missing-project-autodisable.test.ts +++ b/tests/unit/antigravity-missing-project-autodisable.test.ts @@ -27,14 +27,12 @@ process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "ag-11284-test-secret const core = await import("../../src/lib/db/core.ts"); const providersDb = await import("../../src/lib/db/providers.ts"); -const { - markAntigravityMissingCloudCodeProject, - persistDiscoveredAntigravityProjectId, -} = await import("../../open-sse/services/antigravityProjectPersistence.ts"); +const { markAntigravityMissingCloudCodeProject, persistDiscoveredAntigravityProjectId } = + await import("../../open-sse/services/antigravityProjectPersistence.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } diff --git a/tests/unit/antigravity-mitm-model-resolution.test.ts b/tests/unit/antigravity-mitm-model-resolution.test.ts index 86a06718a252..8facfe626221 100644 --- a/tests/unit/antigravity-mitm-model-resolution.test.ts +++ b/tests/unit/antigravity-mitm-model-resolution.test.ts @@ -17,7 +17,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // #3144: the executor resolves the upstream model through the dynamic MITM alias diff --git a/tests/unit/antigravity-project-persistence.test.ts b/tests/unit/antigravity-project-persistence.test.ts index 1dd30c712f97..c0e20d86f1ba 100644 --- a/tests/unit/antigravity-project-persistence.test.ts +++ b/tests/unit/antigravity-project-persistence.test.ts @@ -28,7 +28,7 @@ const { async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } diff --git a/tests/unit/antigravity-quota-host-8965.test.ts b/tests/unit/antigravity-quota-host-8965.test.ts index a383292d8504..c5d7be291b54 100644 --- a/tests/unit/antigravity-quota-host-8965.test.ts +++ b/tests/unit/antigravity-quota-host-8965.test.ts @@ -32,7 +32,7 @@ const originalFetch = globalThis.fetch; test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); const RESET_IN_2_HOURS = new Date(Date.now() + 2 * 60 * 60 * 1000).toISOString(); @@ -57,7 +57,8 @@ test("#8965: quota reads use the runtime host (daily-cloudcode-pa), not cloudcod const cloudcodeCount = { value: 0 }; globalThis.fetch = (async (input: RequestInfo | URL) => { - const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; + const url = + typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; if (url.includes("daily-cloudcode-pa.googleapis.com")) { dailyCount.value++; @@ -167,7 +168,8 @@ test("#8965 behavioral impact: live quota source + weekly bucket unreachable whe core.resetDbInstance(); globalThis.fetch = (async (input: RequestInfo | URL) => { - const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; + const url = + typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; if (url.includes("daily-cloudcode-pa.googleapis.com")) { if (url.includes("retrieveUserQuotaSummary")) { diff --git a/tests/unit/antigravity-quota-skipping.test.ts b/tests/unit/antigravity-quota-skipping.test.ts index f23a43883af6..6f585bb3bca8 100644 --- a/tests/unit/antigravity-quota-skipping.test.ts +++ b/tests/unit/antigravity-quota-skipping.test.ts @@ -12,7 +12,7 @@ const quotaCache = await import("../../src/domain/quotaCache.ts"); test.after(() => { coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("isQuotaExhaustedForRequest isolates Claude and Gemini quota families for antigravity & agy", () => { diff --git a/tests/unit/antigravity-weekly-quota-4017.test.ts b/tests/unit/antigravity-weekly-quota-4017.test.ts index ee29b1c52dc1..3a331422299e 100644 --- a/tests/unit/antigravity-weekly-quota-4017.test.ts +++ b/tests/unit/antigravity-weekly-quota-4017.test.ts @@ -33,7 +33,7 @@ const originalFetch = globalThis.fetch; test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); const RESET_IN_3_DAYS = new Date(Date.now() + 3 * 24 * 60 * 60 * 1000).toISOString(); diff --git a/tests/unit/api-auth.test.ts b/tests/unit/api-auth.test.ts index dd4f874f3b11..a472e6db4d5a 100644 --- a/tests/unit/api-auth.test.ts +++ b/tests/unit/api-auth.test.ts @@ -24,7 +24,7 @@ const ORIGINAL_INITIAL_PASSWORD = process.env.INITIAL_PASSWORD; async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); delete process.env.JWT_SECRET; delete process.env.INITIAL_PASSWORD; @@ -48,7 +48,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_JWT_SECRET === undefined) { delete process.env.JWT_SECRET; diff --git a/tests/unit/api-key-compression-enabled-2101.test.ts b/tests/unit/api-key-compression-enabled-2101.test.ts index ddef25de9ffa..05145738f404 100644 --- a/tests/unit/api-key-compression-enabled-2101.test.ts +++ b/tests/unit/api-key-compression-enabled-2101.test.ts @@ -15,7 +15,7 @@ const { updateKeyPermissionsSchema } = await import("../../src/shared/validation async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -25,7 +25,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("API key prompt compression defaults on and round-trips an explicit opt-out", async () => { diff --git a/tests/unit/api-key-lifecycle.test.ts b/tests/unit/api-key-lifecycle.test.ts index 126856b56e51..5cb73eb7678b 100644 --- a/tests/unit/api-key-lifecycle.test.ts +++ b/tests/unit/api-key-lifecycle.test.ts @@ -17,7 +17,7 @@ const ORIGINAL_ROUTER_API_KEY = process.env.ROUTER_API_KEY; function reset() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); delete process.env.OMNIROUTE_API_KEY; delete process.env.ROUTER_API_KEY; @@ -28,7 +28,7 @@ test.beforeEach(() => { }); test.after(() => { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_OMNIROUTE_API_KEY === undefined) delete process.env.OMNIROUTE_API_KEY; else process.env.OMNIROUTE_API_KEY = ORIGINAL_OMNIROUTE_API_KEY; if (ORIGINAL_ROUTER_API_KEY === undefined) delete process.env.ROUTER_API_KEY; diff --git a/tests/unit/api-key-policy-noauth-allowed-connections.test.ts b/tests/unit/api-key-policy-noauth-allowed-connections.test.ts index 985c341c3769..55a693a1a265 100644 --- a/tests/unit/api-key-policy-noauth-allowed-connections.test.ts +++ b/tests/unit/api-key-policy-noauth-allowed-connections.test.ts @@ -26,7 +26,7 @@ const RESTRICTED_CONNECTION_UUID = "00000000-0000-4000-8000-000000000001"; test.after(() => { coreDb.resetDbInstance(); try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch {} }); diff --git a/tests/unit/api-key-policy.test.ts b/tests/unit/api-key-policy.test.ts index 63cf63be7b25..eff769271fb2 100644 --- a/tests/unit/api-key-policy.test.ts +++ b/tests/unit/api-key-policy.test.ts @@ -48,7 +48,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: unknown) { @@ -129,7 +129,7 @@ test.after(async () => { apiKeysDb.resetApiKeyState(); costRules.resetCostData(); coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ─── Replicate the isWithinSchedule logic for pure unit testing ─────────────── diff --git a/tests/unit/api-key-regeneration.test.ts b/tests/unit/api-key-regeneration.test.ts index a49242449855..920a83590252 100644 --- a/tests/unit/api-key-regeneration.test.ts +++ b/tests/unit/api-key-regeneration.test.ts @@ -15,7 +15,7 @@ function reset() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -25,7 +25,7 @@ test.beforeEach(() => { }); test.after(() => { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("regenerateApiKey creates a new key and invalidates the old one", async () => { diff --git a/tests/unit/api-key-reveal-route.test.ts b/tests/unit/api-key-reveal-route.test.ts index 34be3d41adb7..0c34f02bfb29 100644 --- a/tests/unit/api-key-reveal-route.test.ts +++ b/tests/unit/api-key-reveal-route.test.ts @@ -21,7 +21,7 @@ async function resetStorage() { delete process.env.INITIAL_PASSWORD; core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -37,7 +37,7 @@ test.after(async () => { delete process.env.ALLOW_API_KEY_REVEAL; core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("GET /api/keys stays masked even when reveal is enabled", async () => { diff --git a/tests/unit/api-key-usage-limits.test.ts b/tests/unit/api-key-usage-limits.test.ts index 5a3c27788725..7221cc883c7a 100644 --- a/tests/unit/api-key-usage-limits.test.ts +++ b/tests/unit/api-key-usage-limits.test.ts @@ -20,7 +20,7 @@ async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); usageHistory.clearPendingRequests(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -31,7 +31,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("API key USD usage limits persist and default off", async () => { diff --git a/tests/unit/api-keys-create-no-hang-6570.test.ts b/tests/unit/api-keys-create-no-hang-6570.test.ts index 7ba196ea4892..3a5d5bd53628 100644 --- a/tests/unit/api-keys-create-no-hang-6570.test.ts +++ b/tests/unit/api-keys-create-no-hang-6570.test.ts @@ -34,7 +34,7 @@ async function resetStorage() { delete process.env.INITIAL_PASSWORD; core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -49,7 +49,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); core.resetDbInstance(); }); diff --git a/tests/unit/api-malformed-json-400.test.ts b/tests/unit/api-malformed-json-400.test.ts index 1b0cce21c109..a4cbb589998d 100644 --- a/tests/unit/api-malformed-json-400.test.ts +++ b/tests/unit/api-malformed-json-400.test.ts @@ -65,7 +65,7 @@ function jsonRequest(url: string, body: unknown, method = "POST"): Request { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; diff --git a/tests/unit/api-manager-provider-permissions.test.ts b/tests/unit/api-manager-provider-permissions.test.ts index 5169219a2432..4df4b7adbf25 100644 --- a/tests/unit/api-manager-provider-permissions.test.ts +++ b/tests/unit/api-manager-provider-permissions.test.ts @@ -45,7 +45,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch { @@ -63,7 +63,7 @@ test.after(async () => { apiKeys.resetApiKeyState(); core.resetDbInstance(); try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // best-effort cleanup } @@ -258,7 +258,7 @@ test("R2/R5: JSON import preserves explicit restricted + empty and infers legacy test("R2/R5: startup db.json migration preserves explicit restricted-empty mode", async () => { apiKeys.resetApiKeyState(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); fs.writeFileSync( path.join(TEST_DATA_DIR, "db.json"), diff --git a/tests/unit/api-models-hide-paid-6328.test.ts b/tests/unit/api-models-hide-paid-6328.test.ts index f2e509c8641f..6e4e4a0fbc75 100644 --- a/tests/unit/api-models-hide-paid-6328.test.ts +++ b/tests/unit/api-models-hide-paid-6328.test.ts @@ -32,7 +32,7 @@ async function fetchModels(): Promise< test.after(() => { core.resetDbInstance(); try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { /* best-effort */ } diff --git a/tests/unit/api-models-v1-models-mismatch-10615.test.ts b/tests/unit/api-models-v1-models-mismatch-10615.test.ts index b6a241844a9f..f41976e3464d 100644 --- a/tests/unit/api-models-v1-models-mismatch-10615.test.ts +++ b/tests/unit/api-models-v1-models-mismatch-10615.test.ts @@ -16,7 +16,7 @@ const v1ModelsCatalog = await import("../../src/app/api/v1/models/catalog.ts"); test.after(() => { core.resetDbInstance(); try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch {} }); diff --git a/tests/unit/api/auto-combo-candidates-route-7819.test.ts b/tests/unit/api/auto-combo-candidates-route-7819.test.ts index bc5c8df47485..1679f4383a4b 100644 --- a/tests/unit/api/auto-combo-candidates-route-7819.test.ts +++ b/tests/unit/api/auto-combo-candidates-route-7819.test.ts @@ -12,12 +12,13 @@ const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-7819-rout process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../../src/lib/db/core.ts"); -const routeModule = await import( - "../../../src/app/api/v1/auto-combo/[channel]/candidates/route.ts" -); +const routeModule = + await import("../../../src/app/api/v1/auto-combo/[channel]/candidates/route.ts"); function makeRequest(channel: string) { - return new Request(`http://localhost/api/v1/auto-combo/${encodeURIComponent(channel)}/candidates`); + return new Request( + `http://localhost/api/v1/auto-combo/${encodeURIComponent(channel)}/candidates` + ); } async function callGET(channel: string) { @@ -30,7 +31,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#7819: GET /candidates for the base 'auto' channel returns 200 with a candidates array", async () => { diff --git a/tests/unit/api/cli-tools/apply-container-guard.test.ts b/tests/unit/api/cli-tools/apply-container-guard.test.ts index 5ef03013b8c7..74cceb6ae4b4 100644 --- a/tests/unit/api/cli-tools/apply-container-guard.test.ts +++ b/tests/unit/api/cli-tools/apply-container-guard.test.ts @@ -89,8 +89,8 @@ describe("POST /api/cli-tools/apply — container guard", () => { after(() => { catalogServer?.close(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); - fs.rmSync(TEST_XDG_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.rmSync(TEST_XDG_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (originalDataDir === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = originalDataDir; if (originalXdg === undefined) delete process.env.XDG_CONFIG_HOME; diff --git a/tests/unit/api/cli-tools/detect.test.ts b/tests/unit/api/cli-tools/detect.test.ts index 25b4905d68de..b2835ecdb1e3 100644 --- a/tests/unit/api/cli-tools/detect.test.ts +++ b/tests/unit/api/cli-tools/detect.test.ts @@ -28,7 +28,7 @@ describe("GET /api/cli-tools/detect", () => { after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (originalDataDir === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = originalDataDir; }); diff --git a/tests/unit/api/compression-engines-route.test.ts b/tests/unit/api/compression-engines-route.test.ts index 4ad921252ef9..ef7a88283bb9 100644 --- a/tests/unit/api/compression-engines-route.test.ts +++ b/tests/unit/api/compression-engines-route.test.ts @@ -31,7 +31,7 @@ const enginesRoute = await import("../../../src/app/api/compression/engines/rout async function setupAuth(): Promise { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); await settingsDb.updateSettings({ requireLogin: true, @@ -53,7 +53,7 @@ test.after(() => { if (originalJwtSecret === undefined) delete process.env.JWT_SECRET; else process.env.JWT_SECRET = originalJwtSecret; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ─── tests ──────────────────────────────────────────────────────────────────── diff --git a/tests/unit/api/compression-preview-caveman-and-stacked-6425.test.ts b/tests/unit/api/compression-preview-caveman-and-stacked-6425.test.ts index 409ba2077de9..009088d72354 100644 --- a/tests/unit/api/compression-preview-caveman-and-stacked-6425.test.ts +++ b/tests/unit/api/compression-preview-caveman-and-stacked-6425.test.ts @@ -26,9 +26,7 @@ import { makeManagementSessionRequest } from "../../helpers/managementSession.ts // ─── temp DB isolation ──────────────────────────────────────────────────────── -const TEST_DATA_DIR = fs.mkdtempSync( - path.join(os.tmpdir(), "omniroute-compression-preview-6425-") -); +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-compression-preview-6425-")); const originalDataDir = process.env.DATA_DIR; const originalJwtSecret = process.env.JWT_SECRET; @@ -46,7 +44,7 @@ const CAVEMAN_TRIGGER = async function setupAuth(): Promise { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); await settingsDb.updateSettings({ requireLogin: true, @@ -68,22 +66,19 @@ test.after(() => { if (originalJwtSecret === undefined) delete process.env.JWT_SECRET; else process.env.JWT_SECRET = originalJwtSecret; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ─── tests ──────────────────────────────────────────────────────────────────── test("#6425 (a): POST /api/compression/preview accepts mode:'caveman' and produces >0% savings", async () => { - const request = await makeManagementSessionRequest( - "http://localhost/api/compression/preview", - { - method: "POST", - body: { - messages: [{ role: "user", content: CAVEMAN_TRIGGER }], - mode: "caveman", - }, - } - ); + const request = await makeManagementSessionRequest("http://localhost/api/compression/preview", { + method: "POST", + body: { + messages: [{ role: "user", content: CAVEMAN_TRIGGER }], + mode: "caveman", + }, + }); const response = await previewRoute.POST(request); assert.equal( @@ -109,16 +104,13 @@ test("#6425 (a): POST /api/compression/preview accepts mode:'caveman' and produc }); test("#6425 (b): POST /api/compression/preview mode:'stacked' returns >0% on caveman-trigger prose", async () => { - const request = await makeManagementSessionRequest( - "http://localhost/api/compression/preview", - { - method: "POST", - body: { - messages: [{ role: "user", content: CAVEMAN_TRIGGER }], - mode: "stacked", - }, - } - ); + const request = await makeManagementSessionRequest("http://localhost/api/compression/preview", { + method: "POST", + body: { + messages: [{ role: "user", content: CAVEMAN_TRIGGER }], + mode: "stacked", + }, + }); const response = await previewRoute.POST(request); assert.equal(response.status, 200, `Expected 200, got ${response.status}`); diff --git a/tests/unit/api/compression-preview-engine.test.ts b/tests/unit/api/compression-preview-engine.test.ts index 847939fbd576..026eb118d31f 100644 --- a/tests/unit/api/compression-preview-engine.test.ts +++ b/tests/unit/api/compression-preview-engine.test.ts @@ -31,7 +31,7 @@ const previewRoute = await import("../../../src/app/api/compression/preview/rout async function setupAuth(): Promise { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); await settingsDb.updateSettings({ requireLogin: true, @@ -71,7 +71,7 @@ test.after(() => { if (originalJwtSecret === undefined) delete process.env.JWT_SECRET; else process.env.JWT_SECRET = originalJwtSecret; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ─── tests ──────────────────────────────────────────────────────────────────── diff --git a/tests/unit/api/compression/compression-api.test.ts b/tests/unit/api/compression/compression-api.test.ts index 85f735d564f1..90c08a7601b4 100644 --- a/tests/unit/api/compression/compression-api.test.ts +++ b/tests/unit/api/compression/compression-api.test.ts @@ -21,7 +21,6 @@ process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../../../src/lib/db/core.ts"); const route = await import("../../../../src/app/api/settings/compression/route.ts"); - describe("Compression Settings API Schema Validation", () => { const compressionModeValues = [ "off", @@ -140,13 +139,13 @@ function makeRequest(method: string, body?: unknown): Request { describe("settings/compression route — engines + activeComboId", () => { beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); }); after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; }); diff --git a/tests/unit/api/compression/rtk-learn-discover-routes.test.ts b/tests/unit/api/compression/rtk-learn-discover-routes.test.ts index 42b3cd30c513..6c9d020bd00f 100644 --- a/tests/unit/api/compression/rtk-learn-discover-routes.test.ts +++ b/tests/unit/api/compression/rtk-learn-discover-routes.test.ts @@ -24,9 +24,8 @@ const ORIGINAL_INITIAL_PASSWORD = process.env.INITIAL_PASSWORD; process.env.DATA_DIR = TEST_DATA_DIR; delete process.env.INITIAL_PASSWORD; -const { maybePersistRtkRawOutput } = await import( - "../../../../open-sse/services/compression/engines/rtk/index.ts" -); +const { maybePersistRtkRawOutput } = + await import("../../../../open-sse/services/compression/engines/rtk/index.ts"); const discoverRoute = await import("../../../../src/app/api/context/rtk/discover/route.ts"); const learnRoute = await import("../../../../src/app/api/context/rtk/learn/route.ts"); @@ -45,12 +44,12 @@ function get(url: string): Request { } test.beforeEach(() => { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); }); test.after(() => { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; if (ORIGINAL_INITIAL_PASSWORD !== undefined) diff --git a/tests/unit/api/compression/rtk-toml-import-route.test.ts b/tests/unit/api/compression/rtk-toml-import-route.test.ts index 959e43453a64..c09dc4f4c3b0 100644 --- a/tests/unit/api/compression/rtk-toml-import-route.test.ts +++ b/tests/unit/api/compression/rtk-toml-import-route.test.ts @@ -29,7 +29,7 @@ expected = "kept" async function reset() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); delete process.env.INITIAL_PASSWORD; } @@ -47,7 +47,7 @@ test.beforeEach(reset); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; if (ORIGINAL_INITIAL_PASSWORD === undefined) delete process.env.INITIAL_PASSWORD; diff --git a/tests/unit/api/context-analytics-engine-route.test.ts b/tests/unit/api/context-analytics-engine-route.test.ts index ef583ddcc2fe..a26f4337bfcf 100644 --- a/tests/unit/api/context-analytics-engine-route.test.ts +++ b/tests/unit/api/context-analytics-engine-route.test.ts @@ -31,7 +31,7 @@ const engineRoute = await import("../../../src/app/api/context/analytics/engine/ async function setupAuth(): Promise { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); await settingsDb.updateSettings({ requireLogin: true, @@ -53,7 +53,7 @@ test.after(() => { if (originalJwtSecret === undefined) delete process.env.JWT_SECRET; else process.env.JWT_SECRET = originalJwtSecret; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ─── tests ──────────────────────────────────────────────────────────────────── diff --git a/tests/unit/api/context-combos-default-route.test.ts b/tests/unit/api/context-combos-default-route.test.ts index c5b0da6af333..67407075afc2 100644 --- a/tests/unit/api/context-combos-default-route.test.ts +++ b/tests/unit/api/context-combos-default-route.test.ts @@ -36,7 +36,7 @@ const defaultRoute = await import("../../../src/app/api/context/combos/default/r async function setupAuth(): Promise { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); await settingsDb.updateSettings({ requireLogin: true, @@ -58,7 +58,7 @@ test.after(() => { if (originalJwtSecret === undefined) delete process.env.JWT_SECRET; else process.env.JWT_SECRET = originalJwtSecret; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ─── tests ──────────────────────────────────────────────────────────────────── @@ -110,7 +110,10 @@ test("GET /api/context/combos/default returns the derived stacked pipeline (refl assert.equal(body.mode, "stacked"); assert.deepEqual(body.pipeline, expected.stackedPipeline); const engineIds = body.pipeline.map((s) => s.engine); - assert.ok(engineIds.includes("caveman"), `expected caveman in derived pipeline, got: ${engineIds}`); + assert.ok( + engineIds.includes("caveman"), + `expected caveman in derived pipeline, got: ${engineIds}` + ); }); test("GET /api/context/combos/default returns off when master switch is disabled", async () => { diff --git a/tests/unit/api/discovery-routes.test.ts b/tests/unit/api/discovery-routes.test.ts index 99dc8dddccb1..ff135cc06881 100644 --- a/tests/unit/api/discovery-routes.test.ts +++ b/tests/unit/api/discovery-routes.test.ts @@ -38,7 +38,8 @@ before(async () => { after(() => { core.resetDbInstance(); - if (tmpDataDir) rmSync(tmpDataDir, { recursive: true, force: true }); + if (tmpDataDir) + rmSync(tmpDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); describe("discovery API routes", () => { @@ -135,13 +136,19 @@ describe("discovery API routes", () => { riskLevel: "none", status: "pending", }); - const first = await resultByIdRoute.DELETE(req("DELETE", `/api/discovery/results/${created.id}`), { - params: Promise.resolve({ id: String(created.id) }), - }); + const first = await resultByIdRoute.DELETE( + req("DELETE", `/api/discovery/results/${created.id}`), + { + params: Promise.resolve({ id: String(created.id) }), + } + ); assert.equal(first.status, 200); - const second = await resultByIdRoute.DELETE(req("DELETE", `/api/discovery/results/${created.id}`), { - params: Promise.resolve({ id: String(created.id) }), - }); + const second = await resultByIdRoute.DELETE( + req("DELETE", `/api/discovery/results/${created.id}`), + { + params: Promise.resolve({ id: String(created.id) }), + } + ); assert.equal(second.status, 404); }); diff --git a/tests/unit/api/free-proxies-list-route.test.ts b/tests/unit/api/free-proxies-list-route.test.ts index 334148de5be8..d3664c521736 100644 --- a/tests/unit/api/free-proxies-list-route.test.ts +++ b/tests/unit/api/free-proxies-list-route.test.ts @@ -19,7 +19,7 @@ const ORIGINAL_INITIAL_PASSWORD = process.env.INITIAL_PASSWORD; async function reset() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -39,7 +39,7 @@ function make(host: string, quality: number, latency: number): FreeProxyItem { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_INITIAL_PASSWORD === undefined) delete process.env.INITIAL_PASSWORD; else process.env.INITIAL_PASSWORD = ORIGINAL_INITIAL_PASSWORD; }); diff --git a/tests/unit/api/free-proxies-route.test.ts b/tests/unit/api/free-proxies-route.test.ts index 77f3c034ee02..85555bcfbbd0 100644 --- a/tests/unit/api/free-proxies-route.test.ts +++ b/tests/unit/api/free-proxies-route.test.ts @@ -19,7 +19,7 @@ const ORIGINAL_INITIAL_PASSWORD = process.env.INITIAL_PASSWORD; async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -29,7 +29,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_INITIAL_PASSWORD === undefined) delete process.env.INITIAL_PASSWORD; else process.env.INITIAL_PASSWORD = ORIGINAL_INITIAL_PASSWORD; }); diff --git a/tests/unit/api/jobs.test.ts b/tests/unit/api/jobs.test.ts index 53ff2b1474e8..f135810d0cdb 100644 --- a/tests/unit/api/jobs.test.ts +++ b/tests/unit/api/jobs.test.ts @@ -29,7 +29,7 @@ function resetAll() { } __resetJobRegistry(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -39,7 +39,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function params(id: string) { diff --git a/tests/unit/api/providers-import-route-6836.test.ts b/tests/unit/api/providers-import-route-6836.test.ts index 48c0cb69775f..0ec3f3b1e3aa 100644 --- a/tests/unit/api/providers-import-route-6836.test.ts +++ b/tests/unit/api/providers-import-route-6836.test.ts @@ -26,13 +26,13 @@ type ImportRouteResponse = { async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function postImport(body: unknown) { @@ -100,10 +100,7 @@ test("providers import route imports a heterogeneous list with 200 + per-row res assert.equal(body.created.length, 2); // Never echo the raw apiKey back. assert.ok(body.created.every((c) => c.apiKey === undefined)); - assert.deepEqual( - body.created.map((c) => c.provider).sort(), - ["anthropic", "openai"] - ); + assert.deepEqual(body.created.map((c) => c.provider).sort(), ["anthropic", "openai"]); }); test("providers import route: partial-failure — unresolvable compatible node fails its own row only", async () => { @@ -140,7 +137,11 @@ test("providers import route: same-batch (provider,name) collision does not over assert.equal(response.status, 200); const body = (await response.json()) as ImportRouteResponse; assert.equal(body.total, 2); - assert.equal(body.success, 2, "both rows must be created — the second must not silently upsert into the first"); + assert.equal( + body.success, + 2, + "both rows must be created — the second must not silently upsert into the first" + ); assert.equal(body.failed, 0); assert.equal(body.created.length, 2); @@ -148,7 +149,11 @@ test("providers import route: same-batch (provider,name) collision does not over const connections = (await providersDb.getProviderConnections({ provider: "openai", })) as Array<{ id: string; name?: string | null; apiKey?: string }>; - assert.equal(connections.length, 2, "the collision must produce TWO distinct connections, never one"); + assert.equal( + connections.length, + 2, + "the collision must produce TWO distinct connections, never one" + ); const first = connections.find((c) => c.apiKey === "sk-openai-first"); const second = connections.find((c) => c.apiKey === "sk-openai-second"); @@ -185,19 +190,45 @@ test("providers import route: re-importing an existing (provider,name) does not const connections = (await providersDb.getProviderConnections({ provider: "openai", - })) as Array<{ id: string; name?: string | null; apiKey?: string; testStatus?: string; lastError?: string }>; - assert.equal(connections.length, 2, "re-import must APPEND a new connection, not replace the existing one"); + })) as Array<{ + id: string; + name?: string | null; + apiKey?: string; + testStatus?: string; + lastError?: string; + }>; + assert.equal( + connections.length, + 2, + "re-import must APPEND a new connection, not replace the existing one" + ); const survivor = connections.find((c) => c.id === existing!.id); assert.ok(survivor, "the pre-existing connection must still exist, unreplaced"); - assert.equal(survivor!.apiKey, "sk-existing", "existing apiKey must not be overwritten by the re-import"); - assert.equal(survivor!.testStatus, "unavailable", "existing testStatus must survive the re-import"); - assert.equal(survivor!.lastError, "429 rate limited", "existing lastError must survive the re-import"); + assert.equal( + survivor!.apiKey, + "sk-existing", + "existing apiKey must not be overwritten by the re-import" + ); + assert.equal( + survivor!.testStatus, + "unavailable", + "existing testStatus must survive the re-import" + ); + assert.equal( + survivor!.lastError, + "429 rate limited", + "existing lastError must survive the re-import" + ); const imported = connections.find((c) => c.id !== existing!.id); assert.ok(imported, "the newly imported row must exist as a distinct connection"); assert.equal(imported!.apiKey, "sk-reimported"); - assert.notEqual(imported!.name, "Prod OpenAI", "the re-imported row must be disambiguated, not collide on name"); + assert.notEqual( + imported!.name, + "Prod OpenAI", + "the re-imported row must be disambiguated, not collide on name" + ); }); test("providers import route applies a per-entry baseUrl override for compatible providers", async () => { diff --git a/tests/unit/api/proxies-repair-relay.test.ts b/tests/unit/api/proxies-repair-relay.test.ts index 39d89ab6c45a..f6645c0d6483 100644 --- a/tests/unit/api/proxies-repair-relay.test.ts +++ b/tests/unit/api/proxies-repair-relay.test.ts @@ -20,7 +20,7 @@ const repairRelayRoute = async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -30,7 +30,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_KEY === undefined) delete process.env.STORAGE_ENCRYPTION_KEY; else process.env.STORAGE_ENCRYPTION_KEY = ORIGINAL_KEY; }); diff --git a/tests/unit/api/services/9router-models.test.ts b/tests/unit/api/services/9router-models.test.ts index a71df4d2eb0d..2956f9bc4578 100644 --- a/tests/unit/api/services/9router-models.test.ts +++ b/tests/unit/api/services/9router-models.test.ts @@ -28,7 +28,7 @@ const originalFetch = globalThis.fetch; function resetDb() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } diff --git a/tests/unit/api/services/9router-provider-expose.test.ts b/tests/unit/api/services/9router-provider-expose.test.ts index bd51066aac35..737f6357becc 100644 --- a/tests/unit/api/services/9router-provider-expose.test.ts +++ b/tests/unit/api/services/9router-provider-expose.test.ts @@ -23,7 +23,7 @@ const { POST } = await import("../../../../src/app/api/services/9router/provider function resetDb() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } diff --git a/tests/unit/api/services/9router-status-reveal.test.ts b/tests/unit/api/services/9router-status-reveal.test.ts index ad66f630507d..8d4927ed8f7e 100644 --- a/tests/unit/api/services/9router-status-reveal.test.ts +++ b/tests/unit/api/services/9router-status-reveal.test.ts @@ -46,7 +46,7 @@ function makeRequest(url: string, headers?: Record): Request { after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); describe("GET /api/services/9router/status", () => { diff --git a/tests/unit/api/services/cliproxy-accounts.test.ts b/tests/unit/api/services/cliproxy-accounts.test.ts index d282b7d11cbc..c287528d5b5c 100644 --- a/tests/unit/api/services/cliproxy-accounts.test.ts +++ b/tests/unit/api/services/cliproxy-accounts.test.ts @@ -22,13 +22,11 @@ before(async () => { after(() => { delete process.env.INITIAL_PASSWORD; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); it("requires OmniRoute management authentication", async () => { - const response = await GET( - new Request("http://localhost/api/services/cliproxy/accounts") - ); + const response = await GET(new Request("http://localhost/api/services/cliproxy/accounts")); assert.equal(response.status, 401); }); diff --git a/tests/unit/api/services/cliproxy-provider-expose.test.ts b/tests/unit/api/services/cliproxy-provider-expose.test.ts index 3376c532df61..ade24ad2311f 100644 --- a/tests/unit/api/services/cliproxy-provider-expose.test.ts +++ b/tests/unit/api/services/cliproxy-provider-expose.test.ts @@ -22,7 +22,7 @@ const { POST } = await import("../../../../src/app/api/services/cliproxy/provide function resetDb() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } diff --git a/tests/unit/api/webhooks/webhook-url-ssrf-guard.test.ts b/tests/unit/api/webhooks/webhook-url-ssrf-guard.test.ts index 26028cf6978c..44339f3d54e6 100644 --- a/tests/unit/api/webhooks/webhook-url-ssrf-guard.test.ts +++ b/tests/unit/api/webhooks/webhook-url-ssrf-guard.test.ts @@ -35,7 +35,7 @@ const { createWebhook } = await import("../../../../src/lib/db/webhooks.ts"); after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function jsonRequest(url: string, method: string, body: unknown): Request { diff --git a/tests/unit/apikey-connection-health-check.test.ts b/tests/unit/apikey-connection-health-check.test.ts index 572e10b16150..87d31523423e 100644 --- a/tests/unit/apikey-connection-health-check.test.ts +++ b/tests/unit/apikey-connection-health-check.test.ts @@ -29,7 +29,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: any) { @@ -45,7 +45,7 @@ async function resetStorage() { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("API-key-only gemini connection is NOT marked expired by health check", async () => { diff --git a/tests/unit/apikey-policy-default-rate-limits.test.ts b/tests/unit/apikey-policy-default-rate-limits.test.ts index 75c276e7f766..d4c92373278f 100644 --- a/tests/unit/apikey-policy-default-rate-limits.test.ts +++ b/tests/unit/apikey-policy-default-rate-limits.test.ts @@ -20,7 +20,7 @@ const LEGACY_DEFAULT = [ test.after(async () => { const coreDb = await import("../../src/lib/db/core.ts"); coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; } else { @@ -43,7 +43,10 @@ test("buildDefaultRateLimits: unset / empty env disables implicit fallback limit }); test("ENVIRONMENT.md documents unset DEFAULT_RATE_LIMIT_PER_DAY as unlimited (#11017)", () => { - const md = fs.readFileSync(new URL("../../docs/reference/ENVIRONMENT.md", import.meta.url), "utf8"); + const md = fs.readFileSync( + new URL("../../docs/reference/ENVIRONMENT.md", import.meta.url), + "utf8" + ); const row = md.split("\n").find((line) => line.includes("`DEFAULT_RATE_LIMIT_PER_DAY`")); assert.ok(row, "ENVIRONMENT.md must document DEFAULT_RATE_LIMIT_PER_DAY"); assert.match( diff --git a/tests/unit/apikeypolicy-disable-non-public.test.ts b/tests/unit/apikeypolicy-disable-non-public.test.ts index 04a7ff998130..61a19ccaa318 100644 --- a/tests/unit/apikeypolicy-disable-non-public.test.ts +++ b/tests/unit/apikeypolicy-disable-non-public.test.ts @@ -45,7 +45,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: unknown) { @@ -109,7 +109,7 @@ test.beforeEach(async () => { test.after(async () => { apiKeysDb.resetApiKeyState(); coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // --------------------------------------------------------------------------- diff --git a/tests/unit/apikeypolicy-quota-only.test.ts b/tests/unit/apikeypolicy-quota-only.test.ts index 3c2641cb102a..4caa4691b292 100644 --- a/tests/unit/apikeypolicy-quota-only.test.ts +++ b/tests/unit/apikeypolicy-quota-only.test.ts @@ -21,9 +21,7 @@ import os from "node:os"; import path from "node:path"; import { pathToFileURL } from "node:url"; -const TEST_DATA_DIR = fs.mkdtempSync( - path.join(os.tmpdir(), "omniroute-apikeypolicy-quota-only-") -); +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-apikeypolicy-quota-only-")); process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "quota-only-test-secret"; @@ -49,7 +47,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: unknown) { @@ -91,7 +89,7 @@ test.beforeEach(async () => { test.after(async () => { apiKeysDb.resetApiKeyState(); coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // --------------------------------------------------------------------------- @@ -194,7 +192,10 @@ test("quota-only key requesting a quotaShared-* model from a different pool is r const result = await policy.enforceApiKeyPolicy(makeRequest(created.key), otherPoolVirtualModel); - assert.ok(result.rejection, "should produce a rejection Response for other-pool quotaShared-* model"); + assert.ok( + result.rejection, + "should produce a rejection Response for other-pool quotaShared-* model" + ); assert.equal(result.rejection.status, 403, "rejection should be 403 Forbidden"); const body = await readBody(result.rejection); @@ -218,14 +219,13 @@ test("key with empty allowedQuotas is subject to normal model restriction checks // Allowed model should pass const allowed = await policy.enforceApiKeyPolicy(makeRequest(created.key), "openai/gpt-4.1"); - assert.equal( - allowed.rejection, - null, - "model in allowedModels should pass for a non-quota key" - ); + assert.equal(allowed.rejection, null, "model in allowedModels should pass for a non-quota key"); // Disallowed model should be rejected via the normal allowedModels path - const blocked = await policy.enforceApiKeyPolicy(makeRequest(created.key), "anthropic/claude-3-7-sonnet"); + const blocked = await policy.enforceApiKeyPolicy( + makeRequest(created.key), + "anthropic/claude-3-7-sonnet" + ); assert.ok(blocked.rejection, "disallowed model should be rejected"); assert.equal(blocked.rejection.status, 403); @@ -233,7 +233,11 @@ test("key with empty allowedQuotas is subject to normal model restriction checks assert.match(body.error.message, /not allowed for this API key/); // The code for this case comes from errorConfig (403 → "insufficient_quota") // rather than QUOTA_ONLY — confirming paths are separate - assert.notEqual(body.error.code, "QUOTA_ONLY", "normal key rejection must NOT use QUOTA_ONLY code"); + assert.notEqual( + body.error.code, + "QUOTA_ONLY", + "normal key rejection must NOT use QUOTA_ONLY code" + ); }); test("non-quota key (empty allowedQuotas) requesting a qtSd model is rejected 403 QUOTA_NOT_ALLOCATED", async () => { diff --git a/tests/unit/apikeys-allowed-quotas.test.ts b/tests/unit/apikeys-allowed-quotas.test.ts index 2f42565c4038..9010aeda6bdd 100644 --- a/tests/unit/apikeys-allowed-quotas.test.ts +++ b/tests/unit/apikeys-allowed-quotas.test.ts @@ -18,7 +18,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: unknown) { @@ -41,7 +41,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("allowedQuotas round-trips: create with pool IDs and read them back via getApiKeyMetadata", async () => { diff --git a/tests/unit/apikeys-disable-non-public.test.ts b/tests/unit/apikeys-disable-non-public.test.ts index e757eb073451..9a9e8fa96f7e 100644 --- a/tests/unit/apikeys-disable-non-public.test.ts +++ b/tests/unit/apikeys-disable-non-public.test.ts @@ -18,7 +18,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: unknown) { @@ -41,7 +41,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("disableNonPublicModels: set to true via updateApiKeyPermissions, read back via getApiKeyMetadata", async () => { @@ -97,10 +97,7 @@ test("3 columns coexist: disableNonPublicModels, allowedQuotas, streamDefaultMod ); // Verify streamDefaultMode is still present - assert.ok( - metadata.streamDefaultMode !== undefined, - "streamDefaultMode should be present" - ); + assert.ok(metadata.streamDefaultMode !== undefined, "streamDefaultMode should be present"); assert.equal(metadata.streamDefaultMode, "json", "streamDefaultMode should be 'json'"); }); diff --git a/tests/unit/apikeys-usage-command.test.ts b/tests/unit/apikeys-usage-command.test.ts index 479e9f642471..fe733d821365 100644 --- a/tests/unit/apikeys-usage-command.test.ts +++ b/tests/unit/apikeys-usage-command.test.ts @@ -14,7 +14,7 @@ const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -25,7 +25,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("allowUsageCommand defaults to false for new API keys", async () => { diff --git a/tests/unit/assemble-standalone-onnxruntime-native-asset.test.ts b/tests/unit/assemble-standalone-onnxruntime-native-asset.test.ts index d39beb812730..63e623eb3b40 100644 --- a/tests/unit/assemble-standalone-onnxruntime-native-asset.test.ts +++ b/tests/unit/assemble-standalone-onnxruntime-native-asset.test.ts @@ -60,6 +60,6 @@ test("syncStandaloneNativeAssets copies onnxruntime-node's libonnxruntime.so.1 i ); assert.ok(existsSync(destSo), "libonnxruntime.so.1 must be copied into the standalone bundle"); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/attempt-logging-early-keepalive-merge.test.ts b/tests/unit/attempt-logging-early-keepalive-merge.test.ts index da9ef6fbfeb7..2c346b4e221b 100644 --- a/tests/unit/attempt-logging-early-keepalive-merge.test.ts +++ b/tests/unit/attempt-logging-early-keepalive-merge.test.ts @@ -63,7 +63,7 @@ before(async () => { after(() => { coreDb.resetDbInstance(); - fs.rmSync(testDataDir, { recursive: true, force: true }); + fs.rmSync(testDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("bytes recorded before persistAttemptLogs are prepended into pipeline.streamChunks.client", async () => { diff --git a/tests/unit/audio-transcriptions-combo-resolution.test.ts b/tests/unit/audio-transcriptions-combo-resolution.test.ts index 15d7bfe4ea4f..84daa5a3312f 100644 --- a/tests/unit/audio-transcriptions-combo-resolution.test.ts +++ b/tests/unit/audio-transcriptions-combo-resolution.test.ts @@ -29,7 +29,7 @@ const originalFetch = globalThis.fetch; test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); /** Minimal but structurally valid WAV so nothing rejects the upload shape. */ diff --git a/tests/unit/auggie-executor.test.ts b/tests/unit/auggie-executor.test.ts index b44c3ec94fb8..cf8bc07d3198 100644 --- a/tests/unit/auggie-executor.test.ts +++ b/tests/unit/auggie-executor.test.ts @@ -39,7 +39,7 @@ async function readSseEvents(response: Response): Promise { - fs.rmSync(TMP_DIR, { recursive: true, force: true }); + fs.rmSync(TMP_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ─── buildAuggiePrompt ──────────────────────────────────────────────────── diff --git a/tests/unit/auth-anonymous-fallback-toggle.test.ts b/tests/unit/auth-anonymous-fallback-toggle.test.ts index 6795a24c975c..9382f0164562 100644 --- a/tests/unit/auth-anonymous-fallback-toggle.test.ts +++ b/tests/unit/auth-anonymous-fallback-toggle.test.ts @@ -33,7 +33,7 @@ const { updateSettings } = await import("../../src/lib/db/settings.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); /** Set the opt-out list; pass null to remove the key entirely (absent setting). */ diff --git a/tests/unit/auth-antigravity-account-retry-v2.test.ts b/tests/unit/auth-antigravity-account-retry-v2.test.ts index 59aa1e3e7173..282787632737 100644 --- a/tests/unit/auth-antigravity-account-retry-v2.test.ts +++ b/tests/unit/auth-antigravity-account-retry-v2.test.ts @@ -23,13 +23,13 @@ function connectionId(connection: unknown): string { async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("round-robin same-model retry treats multi-exclude as fallback LRU and skips all excluded accounts", async () => { @@ -152,12 +152,7 @@ test("Antigravity 429 rate-limited locks only the exact model so siblings stay e // The exhausted model itself is locked: getProviderCredentials reports // model-scope cooldown for that exact model on the only connection. - const sameModel = await auth.getProviderCredentials( - "antigravity", - null, - null, - "gemini-3-pro" - ); + const sameModel = await auth.getProviderCredentials("antigravity", null, null, "gemini-3-pro"); assert.ok(sameModel); assert.ok("allRateLimited" in sameModel && sameModel.allRateLimited); assert.equal(sameModel.cooldownScope, "model"); diff --git a/tests/unit/auth-clear-account-error.test.ts b/tests/unit/auth-clear-account-error.test.ts index a303da4e7ed4..6bfd0b0dda3c 100644 --- a/tests/unit/auth-clear-account-error.test.ts +++ b/tests/unit/auth-clear-account-error.test.ts @@ -13,13 +13,13 @@ const auth = await import("../../src/sse/services/auth.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("clearAccountError clears stale provider error metadata after recovery", async () => { diff --git a/tests/unit/auth-clear-provider-routes.test.ts b/tests/unit/auth-clear-provider-routes.test.ts index a81b13c17df7..ba536cc46672 100644 --- a/tests/unit/auth-clear-provider-routes.test.ts +++ b/tests/unit/auth-clear-provider-routes.test.ts @@ -33,7 +33,7 @@ async function withEnv(name, value, fn) { async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -60,7 +60,7 @@ async function readConnection(id) { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("moderations route clears stale provider error metadata on success", async () => { diff --git a/tests/unit/auth-disable-cooling-2997.test.ts b/tests/unit/auth-disable-cooling-2997.test.ts index 8c5dc95a579e..30a6023903a0 100644 --- a/tests/unit/auth-disable-cooling-2997.test.ts +++ b/tests/unit/auth-disable-cooling-2997.test.ts @@ -13,13 +13,13 @@ const auth = await import("../../src/sse/services/auth.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // #2997 — Test 1: a recoverable error on a connection flagged disableCooling diff --git a/tests/unit/auth-login-route.test.ts b/tests/unit/auth-login-route.test.ts index b44f54a3fc7a..e309cafc568e 100644 --- a/tests/unit/auth-login-route.test.ts +++ b/tests/unit/auth-login-route.test.ts @@ -19,7 +19,7 @@ const originalGetCookieStore = loginRoute.authRouteInternals.getCookieStore; async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); delete process.env.INITIAL_PASSWORD; } @@ -37,7 +37,7 @@ test.afterEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_INITIAL_PASSWORD === undefined) { delete process.env.INITIAL_PASSWORD; } else { diff --git a/tests/unit/auth-noauth-fallback-loop-3061.test.ts b/tests/unit/auth-noauth-fallback-loop-3061.test.ts index d640ffb1e22a..f0c95f67a9e5 100644 --- a/tests/unit/auth-noauth-fallback-loop-3061.test.ts +++ b/tests/unit/auth-noauth-fallback-loop-3061.test.ts @@ -31,7 +31,7 @@ const { getProviderCredentials } = await import("../../src/sse/services/auth.ts" test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ── Happy path preserved: first selection (nothing excluded) still works ── @@ -73,4 +73,3 @@ test("#3061 opencode-zen no-auth: excluding 'noauth' returns null (breaks the fa "excluded synthetic noauth must not be re-selected for the opencode-zen keyless path" ); }); - diff --git a/tests/unit/auth-ollama-cloud-per-model-403-3027.test.ts b/tests/unit/auth-ollama-cloud-per-model-403-3027.test.ts index 177989419d0e..83e589b7c910 100644 --- a/tests/unit/auth-ollama-cloud-per-model-403-3027.test.ts +++ b/tests/unit/auth-ollama-cloud-per-model-403-3027.test.ts @@ -22,7 +22,7 @@ const SUBSCRIPTION_403 = async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -38,7 +38,7 @@ async function seedOllamaCloud() { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("per-model subscription 403 locks only the paid model, connection stays active", async () => { diff --git a/tests/unit/auth-opencode-zen-noauth-fallback.test.ts b/tests/unit/auth-opencode-zen-noauth-fallback.test.ts index 218e186bcb7d..8d747949f39b 100644 --- a/tests/unit/auth-opencode-zen-noauth-fallback.test.ts +++ b/tests/unit/auth-opencode-zen-noauth-fallback.test.ts @@ -23,7 +23,7 @@ const { createProviderConnection } = await import("../../src/lib/db/providers.ts test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#2962 opencode-zen with no connection falls back to anonymous no-auth credentials", async () => { diff --git a/tests/unit/auth-policy-embeddings-webfetch-7785.test.ts b/tests/unit/auth-policy-embeddings-webfetch-7785.test.ts index fe252cdf4acb..376660481684 100644 --- a/tests/unit/auth-policy-embeddings-webfetch-7785.test.ts +++ b/tests/unit/auth-policy-embeddings-webfetch-7785.test.ts @@ -38,7 +38,7 @@ const INVALID_BEARER = "Bearer sk-invalid-key-that-does-not-exist-7785"; test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function embeddingsRequest(): Request { diff --git a/tests/unit/auth-terminal-status.test.ts b/tests/unit/auth-terminal-status.test.ts index 28e36217c64d..6be5ae247306 100644 --- a/tests/unit/auth-terminal-status.test.ts +++ b/tests/unit/auth-terminal-status.test.ts @@ -14,13 +14,13 @@ const accountFallback = await import("../../open-sse/services/accountFallback.ts async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("getProviderCredentials skips credits_exhausted connections", async () => { diff --git a/tests/unit/authz/client-api-policy-fallback.test.ts b/tests/unit/authz/client-api-policy-fallback.test.ts index ddb7e34935d7..163e3e85f2bc 100644 --- a/tests/unit/authz/client-api-policy-fallback.test.ts +++ b/tests/unit/authz/client-api-policy-fallback.test.ts @@ -65,7 +65,7 @@ test.after(() => { } catch { /* ignore */ } - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; }); @@ -235,9 +235,7 @@ test("#3504 — empty 'Bearer ' Authorization falls through to the URL path toke process.env.REQUIRE_API_KEY = "true"; const policy = await loadPolicy(); const headers = new Headers({ authorization: "Bearer " }); - const out = await policy.evaluate( - ctx(headers, "/api/v1/vscode/sk-url-token/chat/completions") - ); + const out = await policy.evaluate(ctx(headers, "/api/v1/vscode/sk-url-token/chat/completions")); assert.equal(out.allow, false); if (!out.allow) { assert.equal(out.status, 401); @@ -253,9 +251,7 @@ test("#3504 — a non-Bearer scheme (Basic) also falls through to the URL token" process.env.REQUIRE_API_KEY = "true"; const policy = await loadPolicy(); const headers = new Headers({ authorization: "Basic Zm9vOmJhcg==" }); - const out = await policy.evaluate( - ctx(headers, "/api/v1/vscode/sk-url-token/chat/completions") - ); + const out = await policy.evaluate(ctx(headers, "/api/v1/vscode/sk-url-token/chat/completions")); assert.equal(out.allow, false); if (!out.allow) assert.equal(out.message, "Invalid API key"); }); diff --git a/tests/unit/authz/client-api-policy.test.ts b/tests/unit/authz/client-api-policy.test.ts index 95613bad1582..68cf3b0a2575 100644 --- a/tests/unit/authz/client-api-policy.test.ts +++ b/tests/unit/authz/client-api-policy.test.ts @@ -21,7 +21,7 @@ const ORIGINAL_REQUIRE_API_KEY = process.env.REQUIRE_API_KEY; function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); delete process.env.OMNIROUTE_API_KEY; delete process.env.ROUTER_API_KEY; @@ -34,7 +34,7 @@ test.beforeEach(() => { }); test.after(() => { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_OMNIROUTE_API_KEY === undefined) delete process.env.OMNIROUTE_API_KEY; else process.env.OMNIROUTE_API_KEY = ORIGINAL_OMNIROUTE_API_KEY; if (ORIGINAL_ROUTER_API_KEY === undefined) delete process.env.ROUTER_API_KEY; diff --git a/tests/unit/authz/ip-filter-enforcement-6131.test.ts b/tests/unit/authz/ip-filter-enforcement-6131.test.ts index da55ff11ffbb..d68ce86f7c8c 100644 --- a/tests/unit/authz/ip-filter-enforcement-6131.test.ts +++ b/tests/unit/authz/ip-filter-enforcement-6131.test.ts @@ -22,14 +22,14 @@ const ORIGINAL_STAMP_TOKEN = process.env.OMNIROUTE_PEER_STAMP_TOKEN; test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_STAMP_TOKEN === undefined) delete process.env.OMNIROUTE_PEER_STAMP_TOKEN; else process.env.OMNIROUTE_PEER_STAMP_TOKEN = ORIGINAL_STAMP_TOKEN; }); test.beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); ipFilter.resetIPFilter(); delete process.env.OMNIROUTE_PEER_STAMP_TOKEN; diff --git a/tests/unit/authz/management-policy.test.ts b/tests/unit/authz/management-policy.test.ts index 2e0e69a55868..6e8feda318fc 100644 --- a/tests/unit/authz/management-policy.test.ts +++ b/tests/unit/authz/management-policy.test.ts @@ -24,7 +24,7 @@ const ORIGINAL_INITIAL = process.env.INITIAL_PASSWORD; function reset() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); delete process.env.JWT_SECRET; delete process.env.INITIAL_PASSWORD; @@ -35,7 +35,7 @@ test.beforeEach(() => { }); test.after(() => { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_JWT === undefined) delete process.env.JWT_SECRET; else process.env.JWT_SECRET = ORIGINAL_JWT; if (ORIGINAL_INITIAL === undefined) delete process.env.INITIAL_PASSWORD; diff --git a/tests/unit/authz/pipeline.test.ts b/tests/unit/authz/pipeline.test.ts index 6f6469e804eb..d89b152219eb 100644 --- a/tests/unit/authz/pipeline.test.ts +++ b/tests/unit/authz/pipeline.test.ts @@ -30,7 +30,7 @@ const ORIGINAL_OMNIROUTE_PEER_STAMP_TOKEN = process.env.OMNIROUTE_PEER_STAMP_TOK function resetEnvironment() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); process.env.JWT_SECRET = "pipeline-jwt-secret"; process.env.INITIAL_PASSWORD = "pipeline-initial-password"; @@ -67,7 +67,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_JWT === undefined) delete process.env.JWT_SECRET; else process.env.JWT_SECRET = ORIGINAL_JWT; if (ORIGINAL_INITIAL === undefined) delete process.env.INITIAL_PASSWORD; diff --git a/tests/unit/authz/probe-9033-repro.test.ts b/tests/unit/authz/probe-9033-repro.test.ts index 22d299eb5953..02886ecdb00e 100644 --- a/tests/unit/authz/probe-9033-repro.test.ts +++ b/tests/unit/authz/probe-9033-repro.test.ts @@ -28,14 +28,14 @@ const ORIGINAL_STAMP_TOKEN = process.env.OMNIROUTE_PEER_STAMP_TOKEN; test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_STAMP_TOKEN === undefined) delete process.env.OMNIROUTE_PEER_STAMP_TOKEN; else process.env.OMNIROUTE_PEER_STAMP_TOKEN = ORIGINAL_STAMP_TOKEN; }); test.beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); ipFilter.resetIPFilter(); delete process.env.OMNIROUTE_PEER_STAMP_TOKEN; diff --git a/tests/unit/auto-candidate-overrides-7819.test.ts b/tests/unit/auto-candidate-overrides-7819.test.ts index 50b21f2839d5..3b5ce76b59e2 100644 --- a/tests/unit/auto-candidate-overrides-7819.test.ts +++ b/tests/unit/auto-candidate-overrides-7819.test.ts @@ -19,7 +19,7 @@ const overridesDb = await import("../../src/lib/db/autoCandidateOverrides.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -29,7 +29,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; diff --git a/tests/unit/auto-candidate-overrides-regression-7819.test.ts b/tests/unit/auto-candidate-overrides-regression-7819.test.ts index ca1ba0c5ce2e..f36dc7a2e43a 100644 --- a/tests/unit/auto-candidate-overrides-regression-7819.test.ts +++ b/tests/unit/auto-candidate-overrides-regression-7819.test.ts @@ -28,7 +28,7 @@ const virtualFactory = await import("../../open-sse/services/autoCombo/virtualFa async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -38,7 +38,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; diff --git a/tests/unit/auto-combo-context-advertising.test.ts b/tests/unit/auto-combo-context-advertising.test.ts index 9e324c176685..cdac54d0a5c2 100644 --- a/tests/unit/auto-combo-context-advertising.test.ts +++ b/tests/unit/auto-combo-context-advertising.test.ts @@ -44,7 +44,7 @@ const combosAutoRoute = await import("../../src/app/api/combos/auto/route.ts"); test.after(() => { core.resetDbInstance(); try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // best-effort cleanup } diff --git a/tests/unit/auto-combo-credentialed-model-pool.test.ts b/tests/unit/auto-combo-credentialed-model-pool.test.ts index fb39b0d7c553..daea5e672e99 100644 --- a/tests/unit/auto-combo-credentialed-model-pool.test.ts +++ b/tests/unit/auto-combo-credentialed-model-pool.test.ts @@ -25,7 +25,7 @@ type LogicalCandidate = { async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -63,7 +63,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; diff --git a/tests/unit/auto-combo-hidden-models-4558.test.ts b/tests/unit/auto-combo-hidden-models-4558.test.ts index 29439b2698bf..f66da030fd20 100644 --- a/tests/unit/auto-combo-hidden-models-4558.test.ts +++ b/tests/unit/auto-combo-hidden-models-4558.test.ts @@ -42,7 +42,7 @@ before(() => { after(() => { resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); const PROVIDER = "openai"; diff --git a/tests/unit/auto-combos-enhanced-4235.test.ts b/tests/unit/auto-combos-enhanced-4235.test.ts index 262819c2d130..85a72992b8a4 100644 --- a/tests/unit/auto-combos-enhanced-4235.test.ts +++ b/tests/unit/auto-combos-enhanced-4235.test.ts @@ -24,7 +24,7 @@ const builtinCatalog = await import("../../open-sse/services/autoCombo/builtinCa function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -34,7 +34,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#4235 Phase A: README-advertised cheap/offline/smart are in the built-in catalog", () => { diff --git a/tests/unit/auto-combos-free-models-routes.test.ts b/tests/unit/auto-combos-free-models-routes.test.ts index ca7fa6772d4f..882d35ada962 100644 --- a/tests/unit/auto-combos-free-models-routes.test.ts +++ b/tests/unit/auto-combos-free-models-routes.test.ts @@ -12,9 +12,7 @@ import path from "node:path"; // ── DB / auth setup ─────────────────────────────────────────────────────────── -const TEST_DATA_DIR = fs.mkdtempSync( - path.join(os.tmpdir(), "omniroute-auto-combos-free-models-") -); +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-auto-combos-free-models-")); process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = process.env.API_KEY_SECRET ?? "auto-combos-free-models-test-secret"; @@ -38,7 +36,7 @@ function makeRequest(url: string, apiKey?: string): Request { test.after(() => { core.resetDbInstance(); try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // best-effort cleanup } diff --git a/tests/unit/auto-combos-suffixes-4235.test.ts b/tests/unit/auto-combos-suffixes-4235.test.ts index 6a2eaad83415..e09e7e9a4692 100644 --- a/tests/unit/auto-combos-suffixes-4235.test.ts +++ b/tests/unit/auto-combos-suffixes-4235.test.ts @@ -22,14 +22,14 @@ const v1ModelsCatalog = await import("../../src/app/api/v1/models/catalog.ts"); function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.beforeEach(() => resetStorage()); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#4235 parseAutoSuffix parses category and category:tier", () => { diff --git a/tests/unit/auto-custom-provider-5873.test.ts b/tests/unit/auto-custom-provider-5873.test.ts index 2dfbd9ae72f6..907bebd16223 100644 --- a/tests/unit/auto-custom-provider-5873.test.ts +++ b/tests/unit/auto-custom-provider-5873.test.ts @@ -24,7 +24,7 @@ type VirtualComboResult = Awaited { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; diff --git a/tests/unit/auto-empty-pool-fastfail-6458.test.ts b/tests/unit/auto-empty-pool-fastfail-6458.test.ts index 4db304bab9e2..89ad0d284be4 100644 --- a/tests/unit/auto-empty-pool-fastfail-6458.test.ts +++ b/tests/unit/auto-empty-pool-fastfail-6458.test.ts @@ -19,12 +19,14 @@ const { resolveModelOrError } = await import("../../src/sse/handlers/chatHelpers test.beforeEach(() => core.resetDbInstance()); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#6458 empty auto-combo pool returns a 503 instead of an empty combo", async () => { // No provider connections seeded → any auto category resolves to an empty pool. - const result = await resolveModelOrError("auto/coding:pro", { messages: [{ role: "user", content: "hi" }] }); + const result = await resolveModelOrError("auto/coding:pro", { + messages: [{ role: "user", content: "hi" }], + }); assert.ok(result.error, "expected an error result, not a combo"); assert.equal(result.error.status, 503, "empty auto pool must fail fast with 503"); assert.equal(result.combo, undefined, "must not return a combo for an empty pool"); diff --git a/tests/unit/auto-keyless-custom-provider-11180.test.ts b/tests/unit/auto-keyless-custom-provider-11180.test.ts index c2e97e8158d1..c6070e4b0aa8 100644 --- a/tests/unit/auto-keyless-custom-provider-11180.test.ts +++ b/tests/unit/auto-keyless-custom-provider-11180.test.ts @@ -28,7 +28,7 @@ type VirtualComboResult = Awaited { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; diff --git a/tests/unit/auto-routing-analytics-db.test.ts b/tests/unit/auto-routing-analytics-db.test.ts index 8216f535f34a..55dac3ed6da0 100644 --- a/tests/unit/auto-routing-analytics-db.test.ts +++ b/tests/unit/auto-routing-analytics-db.test.ts @@ -17,7 +17,7 @@ test.before(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(testDataDir, { recursive: true, force: true }); + fs.rmSync(testDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (originalDataDir === undefined) { delete process.env.DATA_DIR; } else { diff --git a/tests/unit/auto-update.test.ts b/tests/unit/auto-update.test.ts index 55112bae62d7..4b14e209e332 100644 --- a/tests/unit/auto-update.test.ts +++ b/tests/unit/auto-update.test.ts @@ -396,7 +396,7 @@ test("launchAutoUpdate returns validation failures and starts detached update sc assert.equal(spawnCalls[0].unrefCalled, true); assert.match(spawnCalls[0].args[1], /git cherry-pick --keep-redundant-commits 'abc123'/); } finally { - fs.rmSync(tempRoot, { recursive: true, force: true }); + fs.rmSync(tempRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -419,6 +419,6 @@ test("resolveProjectRoot walks up from start dir to nearest package.json or .git const lonelyResult = autoUpdate.resolveProjectRoot("/my-fallback", lonely); assert.equal(lonelyResult, "/my-fallback"); } finally { - fs.rmSync(tempRoot, { recursive: true, force: true }); + fs.rmSync(tempRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/autoCombo/provider-family-combos.test.ts b/tests/unit/autoCombo/provider-family-combos.test.ts index 45ff7eec948d..bdb492e6a3e3 100644 --- a/tests/unit/autoCombo/provider-family-combos.test.ts +++ b/tests/unit/autoCombo/provider-family-combos.test.ts @@ -34,7 +34,7 @@ const builtinCatalog = await import("../../../open-sse/services/autoCombo/builti async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -44,7 +44,7 @@ beforeEach(async () => { afterAll(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; } else { diff --git a/tests/unit/bai-provider.test.ts b/tests/unit/bai-provider.test.ts index 3669d53998c0..abb91d31e260 100644 --- a/tests/unit/bai-provider.test.ts +++ b/tests/unit/bai-provider.test.ts @@ -72,13 +72,13 @@ const modelsRoute = await import("../../src/app/api/providers/[id]/models/route. async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); interface ModelsBody { diff --git a/tests/unit/batch-file-download.test.ts b/tests/unit/batch-file-download.test.ts index 00c5288a1826..5cc891cb43a6 100644 --- a/tests/unit/batch-file-download.test.ts +++ b/tests/unit/batch-file-download.test.ts @@ -23,7 +23,7 @@ const fileContentRoute = await import("../../src/app/api/files/[id]/content/rout async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -33,7 +33,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ── Helper: create a real file in the DB ─────────────────────────────────── diff --git a/tests/unit/batch-processor.test.ts b/tests/unit/batch-processor.test.ts index b96df59ffb93..243fa6f90408 100644 --- a/tests/unit/batch-processor.test.ts +++ b/tests/unit/batch-processor.test.ts @@ -35,7 +35,7 @@ async function reset() { // Clean up the temp DB directory if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); @@ -52,7 +52,7 @@ test.beforeEach(async () => { test.after(async () => { await reset(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/bedrock-image-log-redaction-7297.test.ts b/tests/unit/bedrock-image-log-redaction-7297.test.ts index 074301e233cc..ce86e3393d0b 100644 --- a/tests/unit/bedrock-image-log-redaction-7297.test.ts +++ b/tests/unit/bedrock-image-log-redaction-7297.test.ts @@ -14,7 +14,7 @@ const bedrockExecutor = await import("../../open-sse/executors/bedrock.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function bedrockConverseBodyWithImages(nImages: number, imageBytes: number) { @@ -57,9 +57,8 @@ test("#7297 protectPayloadForLog stays fast on a 3-image Bedrock Converse body", `opaque buffer (see #7297)` ); - const redactedBytes = ( - result as { messages: Array<{ content: Array> }> } - ).messages[0].content[0] as { image?: { source?: { bytes?: unknown } } }; + const redactedBytes = (result as { messages: Array<{ content: Array> }> }) + .messages[0].content[0] as { image?: { source?: { bytes?: unknown } } }; assert.ok( !(redactedBytes.image?.source?.bytes instanceof Uint8Array) && !Array.isArray(redactedBytes.image?.source?.bytes), diff --git a/tests/unit/binaryManager.test.ts b/tests/unit/binaryManager.test.ts index 4165a326c706..f8aeffdc14b2 100644 --- a/tests/unit/binaryManager.test.ts +++ b/tests/unit/binaryManager.test.ts @@ -11,13 +11,15 @@ process.env.DATA_DIR = tmpDir; afterEach(() => { const binDir = path.join(tmpDir, "bin"); try { - if (fs.existsSync(binDir)) fs.rmSync(binDir, { recursive: true, force: true }); + if (fs.existsSync(binDir)) + fs.rmSync(binDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch {} }); after(() => { process.env.DATA_DIR = originalDataDir; - if (fs.existsSync(tmpDir)) fs.rmSync(tmpDir, { recursive: true, force: true }); + if (fs.existsSync(tmpDir)) + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); describe("binaryManager", () => { @@ -170,8 +172,8 @@ describe("binaryManager", () => { fs.mkdirSync(fakePowerShellDir, { recursive: true }); fs.writeFileSync( path.join(fakePowerShellDir, "powershell"), - "#!/bin/sh\nprintf '%s\\n' \"$@\" > \"$OMNI_TEST_COMMAND_LOG\"\n" - + "mkdir -p \"$OMNI_TEST_EXTRACT_DIR\"\nprintf 'installed-binary' > \"$OMNI_TEST_EXTRACT_DIR/cli-proxy-api\"\n" + '#!/bin/sh\nprintf \'%s\\n\' "$@" > "$OMNI_TEST_COMMAND_LOG"\n' + + 'mkdir -p "$OMNI_TEST_EXTRACT_DIR"\nprintf \'installed-binary\' > "$OMNI_TEST_EXTRACT_DIR/cli-proxy-api"\n' ); fs.chmodSync(path.join(fakePowerShellDir, "powershell"), 0o755); process.env.PATH = `${fakePowerShellDir}:${originalPath || ""}`; @@ -276,8 +278,8 @@ describe("binaryManager", () => { fs.mkdirSync(fakePowerShellDir, { recursive: true }); fs.writeFileSync( path.join(fakePowerShellDir, "powershell"), - "#!/bin/sh\nprintf '%s\\n' \"$@\" > \"$OMNI_TEST_COMMAND_LOG_PT\"\n" - + "mkdir -p \"$OMNI_TEST_EXTRACT_DIR_PT\"\nprintf 'installed-binary' > \"$OMNI_TEST_EXTRACT_DIR_PT/cli-proxy-api\"\n" + '#!/bin/sh\nprintf \'%s\\n\' "$@" > "$OMNI_TEST_COMMAND_LOG_PT"\n' + + 'mkdir -p "$OMNI_TEST_EXTRACT_DIR_PT"\nprintf \'installed-binary\' > "$OMNI_TEST_EXTRACT_DIR_PT/cli-proxy-api"\n' ); fs.chmodSync(path.join(fakePowerShellDir, "powershell"), 0o755); process.env.PATH = `${fakePowerShellDir}:${originalPath || ""}`; diff --git a/tests/unit/bootstrap-env.test.ts b/tests/unit/bootstrap-env.test.ts index cc997c6e2aad..f72a5d9b8347 100644 --- a/tests/unit/bootstrap-env.test.ts +++ b/tests/unit/bootstrap-env.test.ts @@ -48,7 +48,7 @@ function withTempEnv(fn) { for (const [key, value] of Object.entries(originalEnv)) { process.env[key] = value; } - fs.rmSync(tempRoot, { recursive: true, force: true }); + fs.rmSync(tempRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } diff --git a/tests/unit/bug-9204-agy-provider-alias-credentials.test.ts b/tests/unit/bug-9204-agy-provider-alias-credentials.test.ts index e45d020e66fd..f35d197bc8f4 100644 --- a/tests/unit/bug-9204-agy-provider-alias-credentials.test.ts +++ b/tests/unit/bug-9204-agy-provider-alias-credentials.test.ts @@ -8,15 +8,13 @@ const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-9204-agy- process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); -const { createConnectionFromAgyToken } = await import( - "../../src/lib/oauth/utils/agyAuthImport.ts" -); +const { createConnectionFromAgyToken } = await import("../../src/lib/oauth/utils/agyAuthImport.ts"); const { parseModel } = await import("../../open-sse/services/model.ts"); const { getProviderCredentials } = await import("../../src/sse/services/auth.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#9204: an Antigravity CLI login is eligible for an agy model request", async () => { @@ -45,4 +43,4 @@ test("#9204: an Antigravity CLI login is eligible for an agy model request", asy assert.ok(credentials, "the active Antigravity CLI connection must remain selectable"); assert.equal(credentials.connectionId, connection.id); assert.equal(credentials.accessToken, "fresh-access-token"); -}); \ No newline at end of file +}); diff --git a/tests/unit/bug-9204-agy-reimport-reactivates.test.ts b/tests/unit/bug-9204-agy-reimport-reactivates.test.ts index b57538663aca..29a668ce2c44 100644 --- a/tests/unit/bug-9204-agy-reimport-reactivates.test.ts +++ b/tests/unit/bug-9204-agy-reimport-reactivates.test.ts @@ -9,13 +9,11 @@ process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); const providersDb = await import("../../src/lib/db/providers.ts"); -const { createConnectionFromAgyToken } = await import( - "../../src/lib/oauth/utils/agyAuthImport.ts" -); +const { createConnectionFromAgyToken } = await import("../../src/lib/oauth/utils/agyAuthImport.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#9204: reimporting an inactive Antigravity CLI account reactivates it", async () => { @@ -49,5 +47,8 @@ test("#9204: reimporting an inactive Antigravity CLI account reactivates it", as assert.equal(stored?.isActive, true, "a successful reimport must reactivate the account"); const active = await providersDb.getProviderConnections({ provider: "agy", isActive: true }); - assert.deepEqual(active.map((connection) => connection.id), [existing.id]); -}); \ No newline at end of file + assert.deepEqual( + active.map((connection) => connection.id), + [existing.id] + ); +}); diff --git a/tests/unit/build-next-isolated-windows-home-2402.test.ts b/tests/unit/build-next-isolated-windows-home-2402.test.ts index 7c52ae73252f..b1b2bcd8f86e 100644 --- a/tests/unit/build-next-isolated-windows-home-2402.test.ts +++ b/tests/unit/build-next-isolated-windows-home-2402.test.ts @@ -5,11 +5,8 @@ import fsSync from "node:fs"; import os from "node:os"; import path from "node:path"; -const { - ensureWindowsBuildProfileDirs, - getWindowsBuildProfileDir, - resolveNextBuildEnv, -} = await import("../../scripts/build/build-next-isolated.mjs"); +const { ensureWindowsBuildProfileDirs, getWindowsBuildProfileDir, resolveNextBuildEnv } = + await import("../../scripts/build/build-next-isolated.mjs"); // Port of decolua/9router#2402 ("fix(build): isolate Windows HOME/AppData during // next build"). Upstream wraps `npm run build` in a new `scripts/build-app.js` @@ -88,6 +85,6 @@ test("ensureWindowsBuildProfileDirs creates the isolated AppData directories", a assert.equal(fsSync.existsSync(env.APPDATA), true); assert.equal(fsSync.existsSync(env.LOCALAPPDATA), true); } finally { - await fs.rm(tempDir, { recursive: true, force: true }); + await fs.rm(tempDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/build-next-isolated.test.ts b/tests/unit/build-next-isolated.test.ts index 13b2f8598cd5..70806b13eb85 100644 --- a/tests/unit/build-next-isolated.test.ts +++ b/tests/unit/build-next-isolated.test.ts @@ -13,14 +13,13 @@ import { syncStandaloneNativeAssets, } from "../../scripts/build/build-next-isolated.mjs"; - async function withTempDir(fn) { const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "omniroute-build-next-isolated-")); try { await fn(tempDir); } finally { - await fs.rm(tempDir, { recursive: true, force: true }); + await fs.rm(tempDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } diff --git a/tests/unit/build-sha-provenance-10427.test.ts b/tests/unit/build-sha-provenance-10427.test.ts index e7b3afb67567..2b4c562338cb 100644 --- a/tests/unit/build-sha-provenance-10427.test.ts +++ b/tests/unit/build-sha-provenance-10427.test.ts @@ -92,7 +92,7 @@ test("P6: readBuildSha returns the trimmed sentinel, or empty when absent", asyn fs.writeFileSync(path.join(repo, "dist", "BUILD_SHA"), "e05ac345da\n"); assert.equal(readBuildSha(repo), "e05ac345da"); } finally { - fs.rmSync(repo, { recursive: true, force: true }); + fs.rmSync(repo, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/build/assemble-standalone.test.ts b/tests/unit/build/assemble-standalone.test.ts index 14a8de854bc7..5894c3d82b1e 100644 --- a/tests/unit/build/assemble-standalone.test.ts +++ b/tests/unit/build/assemble-standalone.test.ts @@ -96,7 +96,7 @@ test("assembleStandalone copies standalone + static + public + sidecars into out "static is NOT placed under a literal .next (would 404 against distDir server)" ); assert.ok(fs.existsSync(path.join(outDir, "public/logo.svg")), "public copied"); - fs.rmSync(tmp, { recursive: true, force: true }); + fs.rmSync(tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("patchTurbopackChunks restores canonical external package names in a custom distDir", () => { @@ -116,7 +116,7 @@ test("patchTurbopackChunks restores canonical external package names in a custom assert.match(patched, /require\("ws"\)/); assert.match(patched, /require\("@ngrok\/ngrok"\)/); assert.doesNotMatch(patched, /-[0-9a-f]{16}/); - fs.rmSync(tmp, { recursive: true, force: true }); + fs.rmSync(tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // Drift guard: the async path (syncStandaloneNativeAssets / syncStandaloneExtraModules, @@ -173,7 +173,7 @@ test("async and sync sidecar copy paths produce identical bundle trees", async ( ]) { assert.ok(asyncTree.includes(sqlJsFile), `sql.js runtime file copied: ${sqlJsFile}`); } - fs.rmSync(tmp, { recursive: true, force: true }); + fs.rmSync(tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("the TPROXY addon source is skipped gracefully when it was not built (non-Linux)", async () => { @@ -189,7 +189,7 @@ test("the TPROXY addon source is skipped gracefully when it was not built (non-L !fs.existsSync(path.join(out, "src/mitm/tproxy/native/build/Release/transparent.node")), "absent addon is simply not copied (graceful skip)" ); - fs.rmSync(tmp, { recursive: true, force: true }); + fs.rmSync(tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // Regression guard (#deploy 2026-07-11): server-ws.mjs gained an import of @@ -214,7 +214,7 @@ test("every relative import of standalone-server-ws.mjs is shipped into the bund `server-ws.mjs imports ./${imp} but EXTRA_MODULE_ENTRIES does not ship it — the bundle would crash at boot (ERR_MODULE_NOT_FOUND)` ); } - fs.rmSync(tmp, { recursive: true, force: true }); + fs.rmSync(tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // Regression guard (deploy 2026-08-19): under heavy concurrent build I/O the bulk @@ -287,5 +287,5 @@ test("copy passes tolerate a dest that already resolves to src, or a stale-typed "sql.js content reachable through the pre-existing symlink" ); - fs.rmSync(tmp, { recursive: true, force: true }); + fs.rmSync(tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); diff --git a/tests/unit/build/build-tool-runner-win-shim.test.ts b/tests/unit/build/build-tool-runner-win-shim.test.ts index ffc406bc4c2b..d1f3a0644441 100644 --- a/tests/unit/build/build-tool-runner-win-shim.test.ts +++ b/tests/unit/build/build-tool-runner-win-shim.test.ts @@ -124,7 +124,7 @@ test("resolveLocalBinEntry reads the package's own bin map, never node_modules/. "the resolved entry must bypass the platform-specific .bin shim" ); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -145,7 +145,7 @@ test("resolveLocalBinEntry returns null for a missing package or a missing entry "an advertised entry that is not on disk must not be spawned" ); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -164,7 +164,7 @@ test("isNativeExecutable distinguishes an executable image from a JS shim", () = assert.equal(isNativeExecutable(pe), true); assert.equal(isNativeExecutable(join(root, "absent")), false, "a missing file is not native"); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -186,7 +186,7 @@ test("runBuildTool actually runs esbuild from this repo's dependency tree", () = assert.match(readFileSync(dest, "utf8"), /42/, "esbuild produced the bundle"); } finally { - rmSync(out, { recursive: true, force: true }); + rmSync(out, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/build/check-bundle-size.test.ts b/tests/unit/build/check-bundle-size.test.ts index 35b9f6388407..a6924ef6a0b1 100644 --- a/tests/unit/build/check-bundle-size.test.ts +++ b/tests/unit/build/check-bundle-size.test.ts @@ -196,7 +196,7 @@ function withTmpBundleBaseline(content: string | null, fn: (p: string) => void) try { fn(p); } finally { - fs.rmSync(dir, { recursive: true, force: true }); + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } diff --git a/tests/unit/build/check-lockfile.test.ts b/tests/unit/build/check-lockfile.test.ts index 860734a7d4e3..5bc03459e3dd 100644 --- a/tests/unit/build/check-lockfile.test.ts +++ b/tests/unit/build/check-lockfile.test.ts @@ -253,7 +253,7 @@ test("runWorkspaceDependencyCheck: reports npm ls failures without masking diagn test("workspace check validates lock entries independently of node_modules", (t) => { const root = mkdtempSync(path.join(os.tmpdir(), "omniroute-lockfile-check-")); - t.after(() => rmSync(root, { recursive: true, force: true })); + t.after(() => rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 })); mkdirSync(path.join(root, "packages", "example"), { recursive: true }); writeFileSync( path.join(root, "package.json"), diff --git a/tests/unit/build/check-secrets.test.ts b/tests/unit/build/check-secrets.test.ts index 9b5ce23f7187..51987e8d4306 100644 --- a/tests/unit/build/check-secrets.test.ts +++ b/tests/unit/build/check-secrets.test.ts @@ -298,7 +298,7 @@ function withTmpBaseline(content: string | null, fn: (p: string) => void) { try { fn(p); } finally { - fs.rmSync(dir, { recursive: true, force: true }); + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } diff --git a/tests/unit/build/check-test-runner-api.test.ts b/tests/unit/build/check-test-runner-api.test.ts index 37dd15a3d1cf..a938dc6d3f1e 100644 --- a/tests/unit/build/check-test-runner-api.test.ts +++ b/tests/unit/build/check-test-runner-api.test.ts @@ -21,7 +21,7 @@ test("flags a vitest-only-dir test that imports node:test", () => { assert.equal(bad.length, 1); assert.match(bad[0].file.replace(/\\/g, "/"), /autoCombo\/bad\.test\.ts$/); assert.match(bad[0].reason, /vitest-only/); - fs.rmSync(root, { recursive: true, force: true }); + fs.rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("accepts a vitest-only-dir test that imports vitest", () => { @@ -31,7 +31,7 @@ test("accepts a vitest-only-dir test that imports vitest", () => { `import { describe, it } from "vitest";\ndescribe("x", () => it("y", () => {}));\n` ); assert.equal(findRunnerMismatches(root).length, 0); - fs.rmSync(root, { recursive: true, force: true }); + fs.rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("flags node:test imports in the Vitest-only config roots", () => { @@ -52,5 +52,5 @@ test("flags node:test imports in the Vitest-only config roots", () => { } assert.equal(findRunnerMismatches(root).length, dirs.length); - fs.rmSync(root, { recursive: true, force: true }); + fs.rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); diff --git a/tests/unit/build/check-vuln-ratchet.test.ts b/tests/unit/build/check-vuln-ratchet.test.ts index 11a8b66fbd27..19b20ad95162 100644 --- a/tests/unit/build/check-vuln-ratchet.test.ts +++ b/tests/unit/build/check-vuln-ratchet.test.ts @@ -77,9 +77,7 @@ function makeResultTwoPkgs() { packages: [ { package: { name: "pkg-b", version: "2.0.0", ecosystem: "npm" }, - vulnerabilities: [ - { id: "GHSA-bbb-1", aliases: [], affected: [] }, - ], + vulnerabilities: [{ id: "GHSA-bbb-1", aliases: [], affected: [] }], }, ], }, @@ -183,7 +181,9 @@ test("parseOsvJson: results vazio retorna vulnCount=0", () => { }); test("parseOsvJson: result sem packages retorna vulnCount=0", () => { - const result = parseOsvJson({ results: [{ other: "data" }] } as unknown as { results: { packages: never[] }[] }); + const result = parseOsvJson({ results: [{ other: "data" }] } as unknown as { + results: { packages: never[] }[]; + }); assert.equal(result.vulnCount, 0); }); @@ -358,7 +358,7 @@ function withTmpBaseline(content: string | null, fn: (p: string) => void) { try { fn(p); } finally { - fs.rmSync(dir, { recursive: true, force: true }); + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } diff --git a/tests/unit/build/check-workflows.test.ts b/tests/unit/build/check-workflows.test.ts index 3018b686004a..fea3f3476ea4 100644 --- a/tests/unit/build/check-workflows.test.ts +++ b/tests/unit/build/check-workflows.test.ts @@ -179,7 +179,7 @@ test("collectWorkflowFiles: returns .yml files from directory", () => { assert.ok(files.some((f) => f.endsWith("deploy.yml"))); assert.ok(!files.some((f) => f.endsWith("README.md"))); } finally { - fs.rmSync(dir, { recursive: true, force: true }); + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -194,7 +194,7 @@ test("collectWorkflowFiles: also collects .yaml extension", () => { assert.ok(files.some((f) => f.endsWith(".yaml"))); assert.ok(files.some((f) => f.endsWith(".yml"))); } finally { - fs.rmSync(dir, { recursive: true, force: true }); + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -206,7 +206,7 @@ test("collectWorkflowFiles: returns absolute paths", () => { assert.equal(files.length, 1); assert.ok(path.isAbsolute(files[0])); } finally { - fs.rmSync(dir, { recursive: true, force: true }); + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -216,7 +216,7 @@ test("collectWorkflowFiles: empty directory returns empty array", () => { const files = collectWorkflowFiles(dir); assert.deepEqual(files, []); } finally { - fs.rmSync(dir, { recursive: true, force: true }); + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -282,7 +282,7 @@ function withTmpBaseline(content: string | null, fn: (p: string) => void) { try { fn(p); } finally { - fs.rmSync(dir, { recursive: true, force: true }); + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } diff --git a/tests/unit/build/colocate-standalone-esm-scope.test.ts b/tests/unit/build/colocate-standalone-esm-scope.test.ts index d31772d11a5e..0969dd4a242d 100644 --- a/tests/unit/build/colocate-standalone-esm-scope.test.ts +++ b/tests/unit/build/colocate-standalone-esm-scope.test.ts @@ -35,7 +35,7 @@ test("writeEsmWorkerScopes writes a scoped type:module beside each worker", () = assert.equal(pkg.type, "module", `${dir} declares type:module`); } } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -57,7 +57,7 @@ test("writeEsmWorkerScopes never touches the standalone root package.json", () = "root package.json stays type-less so server.js is parsed as CommonJS" ); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -75,7 +75,7 @@ test("writeEsmWorkerScopes is no-clobber: it leaves an existing package.json int const pkg = JSON.parse(readFileSync(join(workerDir, "package.json"), "utf8")); assert.equal(pkg.version, "9.9.9", "the traced manifest is preserved verbatim"); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -124,7 +124,7 @@ test("scoped layout runs a CJS server.js and an ESM worker.js side by side", () ); assert.ok(existsSync(join(workerDir, "package.json")), "worker scope package.json exists"); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -141,6 +141,6 @@ test("colocate-standalone bundles the required compression worker", () => { assert.equal(existsSync(join(workerDir, "compressionWorker.js")), true); assert.equal(JSON.parse(readFileSync(join(workerDir, "package.json"), "utf8")).type, "module"); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/build/mcp-bundle-no-eager-ioredis.test.ts b/tests/unit/build/mcp-bundle-no-eager-ioredis.test.ts index ab382c6a830b..03c5db3ef00d 100644 --- a/tests/unit/build/mcp-bundle-no-eager-ioredis.test.ts +++ b/tests/unit/build/mcp-bundle-no-eager-ioredis.test.ts @@ -51,7 +51,7 @@ test("MCP server bundle has no top-level static import of ioredis", () => { bundled, /^import\s+.*["']ioredis["'];?\s*$/m, "MCP bundle must not eagerly (statically) import 'ioredis' at the top level — " + - "it must stay a lazy `await import(\"ioredis\")` (see src/lib/quota/redisQuotaStore.ts)" + 'it must stay a lazy `await import("ioredis")` (see src/lib/quota/redisQuotaStore.ts)' ); // The lazy dynamic import from redisQuotaStore.ts must still be present — @@ -62,6 +62,6 @@ test("MCP server bundle has no top-level static import of ioredis", () => { "expected the existing lazy dynamic import of ioredis to remain in the bundle" ); } finally { - rmSync(outDir, { recursive: true, force: true }); + rmSync(outDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/build/mcp-bundle-startup.test.ts b/tests/unit/build/mcp-bundle-startup.test.ts index a441631f9ab5..8ed6853f239e 100644 --- a/tests/unit/build/mcp-bundle-startup.test.ts +++ b/tests/unit/build/mcp-bundle-startup.test.ts @@ -55,6 +55,6 @@ test("MCP server bundle imports successfully on Node 24", () => { }); }, "the generated MCP bundle must be importable by the supported Node runtime"); } finally { - rmSync(outDir, { recursive: true, force: true }); + rmSync(outDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/build/mitm-server-bundle-contents.test.ts b/tests/unit/build/mitm-server-bundle-contents.test.ts index 9253c23cbcba..9bd14e27a56e 100644 --- a/tests/unit/build/mitm-server-bundle-contents.test.ts +++ b/tests/unit/build/mitm-server-bundle-contents.test.ts @@ -31,7 +31,7 @@ test("EXTRA_MODULE_ENTRIES ships every relative require() of MITM server.cjs (#9 ); } } finally { - fs.rmSync(tmp, { recursive: true, force: true }); + fs.rmSync(tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -69,6 +69,6 @@ test("EXTRA_MODULE_ENTRIES ships every dynamic import() of MITM _internal shims ); } } finally { - fs.rmSync(tmp, { recursive: true, force: true }); + fs.rmSync(tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/build/optional-pack-installer.test.ts b/tests/unit/build/optional-pack-installer.test.ts index 2b6fdc69e956..5e7139511b96 100644 --- a/tests/unit/build/optional-pack-installer.test.ts +++ b/tests/unit/build/optional-pack-installer.test.ts @@ -188,7 +188,7 @@ test("installPack accepts tarball payloads (the desktop release asset layout)", stdio: "pipe", }); assert.equal(tarred.status, 0, "fixture tarball creation must succeed"); - fs.rmSync(packDir, { recursive: true, force: true }); // only the tarball remains + fs.rmSync(packDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); // only the tarball remains const source = resolvePackSource(pack.name, sourceDir, dataDir); assert.equal(source.kind, "tarball"); diff --git a/tests/unit/build/repair-empty-external-package-dirs-nested.test.ts b/tests/unit/build/repair-empty-external-package-dirs-nested.test.ts index 50af15cadceb..bd13e78406ab 100644 --- a/tests/unit/build/repair-empty-external-package-dirs-nested.test.ts +++ b/tests/unit/build/repair-empty-external-package-dirs-nested.test.ts @@ -35,7 +35,12 @@ test("assembleStandalone repairs a hollow externalized package dir in the nested const standaloneDir = path.join(distDir, "standalone"); fs.mkdirSync(standaloneDir, { recursive: true }); fs.writeFileSync(path.join(standaloneDir, "server.js"), "// server"); - const hollowNestedPkgDir = path.join(standaloneDir, relDistDir, "node_modules", "some-nested-pkg"); + const hollowNestedPkgDir = path.join( + standaloneDir, + relDistDir, + "node_modules", + "some-nested-pkg" + ); fs.mkdirSync(hollowNestedPkgDir, { recursive: true }); assembleStandalone({ @@ -45,11 +50,17 @@ test("assembleStandalone repairs a hollow externalized package dir in the nested copyNatives: true, }); - const repairedIndexPath = path.join(outDir, relDistDir, "node_modules", "some-nested-pkg", "index.js"); + const repairedIndexPath = path.join( + outDir, + relDistDir, + "node_modules", + "some-nested-pkg", + "index.js" + ); assert.ok( fs.existsSync(repairedIndexPath), "hollow nested externalized package dir must be repaired with the real source package (index.js present)" ); - fs.rmSync(tmp, { recursive: true, force: true }); + fs.rmSync(tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); diff --git a/tests/unit/build/should-promote-latest-5301.test.ts b/tests/unit/build/should-promote-latest-5301.test.ts index 61d6793ef8d4..84341be30097 100644 --- a/tests/unit/build/should-promote-latest-5301.test.ts +++ b/tests/unit/build/should-promote-latest-5301.test.ts @@ -62,7 +62,7 @@ function shouldPromote(version: string, tags: string[]): string { }).trim(); } finally { closeSync(fd); - rmSync(dir, { recursive: true, force: true }); + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } diff --git a/tests/unit/build/standalone-bundle.test.ts b/tests/unit/build/standalone-bundle.test.ts index 2444469202f4..1c8291e4434b 100644 --- a/tests/unit/build/standalone-bundle.test.ts +++ b/tests/unit/build/standalone-bundle.test.ts @@ -127,9 +127,9 @@ test("pack → restore roundtrip restores the tree byte-for-byte", async () => { ); } } finally { - fs.rmSync(src, { recursive: true, force: true }); - fs.rmSync(path.dirname(out), { recursive: true, force: true }); - fs.rmSync(dst, { recursive: true, force: true }); + fs.rmSync(src, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.rmSync(path.dirname(out), { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.rmSync(dst, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -144,8 +144,8 @@ test("packing is byte-deterministic across runs", async () => { await runPack({ dir: src, out: b }); assert.equal(sha256File(a), sha256File(b), "two packs of the same tree must be identical"); } finally { - fs.rmSync(src, { recursive: true, force: true }); - fs.rmSync(outDir, { recursive: true, force: true }); + fs.rmSync(src, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.rmSync(outDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -161,8 +161,8 @@ test("restore rejects a corrupted archive before extraction", async () => { fs.writeFileSync(out, raw); await assert.rejects(() => runRestore({ archive: out, dir: path.join(outDir, "dst") }), /sha/); } finally { - fs.rmSync(src, { recursive: true, force: true }); - fs.rmSync(outDir, { recursive: true, force: true }); + fs.rmSync(src, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.rmSync(outDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -191,9 +191,9 @@ test("manifest verification flags modified and smuggled files in a restored tree `smuggled file detected: ${verdict.errors.join("; ")}` ); } finally { - fs.rmSync(src, { recursive: true, force: true }); - fs.rmSync(outDir, { recursive: true, force: true }); - fs.rmSync(dst, { recursive: true, force: true }); + fs.rmSync(src, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.rmSync(outDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.rmSync(dst, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -207,7 +207,7 @@ test("manifest verification rejects an unsupported manifest version", async () = assert.equal(verdict.ok, false); assert.match(verdict.errors[0] ?? "", /unsupported manifest version/); } finally { - fs.rmSync(dst, { recursive: true, force: true }); + fs.rmSync(dst, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -257,8 +257,8 @@ test("hydratePlatformNatives swaps install-machine-forked packages for this leg" "fsevents dropped on non-matching leg" ); } finally { - fs.rmSync(standalone, { recursive: true, force: true }); - fs.rmSync(source, { recursive: true, force: true }); + fs.rmSync(standalone, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.rmSync(source, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -299,6 +299,6 @@ test("verifyBundledNatives asserts serviceability and honors the onnx darwin-x64 `darwin-x64 must pass via exemption: ${(exempted as { errors?: string[] }).errors?.join("; ")}` ); } finally { - fs.rmSync(root, { recursive: true, force: true }); + fs.rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/build/sync-changelog-i18n.test.ts b/tests/unit/build/sync-changelog-i18n.test.ts index 14eb8247b0a9..cc00973b40f1 100644 --- a/tests/unit/build/sync-changelog-i18n.test.ts +++ b/tests/unit/build/sync-changelog-i18n.test.ts @@ -28,7 +28,7 @@ test("replaces the version section in every mirror with the root section", () => const fr = fs.readFileSync(path.join(root, "docs/i18n/fr/CHANGELOG.md"), "utf8"); assert.match(fr, /big new thing/); assert.doesNotMatch(fr, /_stub_/); - fs.rmSync(root, { recursive: true, force: true }); + fs.rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("inserts the section when a mirror lacks it", () => { @@ -43,5 +43,5 @@ test("inserts the section when a mirror lacks it", () => { const fr = fs.readFileSync(path.join(root, "docs/i18n/fr/CHANGELOG.md"), "utf8"); assert.match(fr, /## \[9\.9\.9\]/); assert.match(fr, /big new thing/); - fs.rmSync(root, { recursive: true, force: true }); + fs.rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); diff --git a/tests/unit/bulk-add-keys-no-overwrite-2587.test.ts b/tests/unit/bulk-add-keys-no-overwrite-2587.test.ts index 27927316e195..79ecf2ccefb2 100644 --- a/tests/unit/bulk-add-keys-no-overwrite-2587.test.ts +++ b/tests/unit/bulk-add-keys-no-overwrite-2587.test.ts @@ -37,7 +37,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error) { @@ -58,7 +58,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("bulk-add appends N+M connections and preserves the existing connection's state (the #2587 fix)", async () => { @@ -138,9 +138,7 @@ test("bulk-add appends N+M connections and preserves the existing connection's s assert.equal(survivor!.rateLimitedUntil, future, "existing cooldown must survive"); assert.equal(survivor!.backoffLevel, 2, "existing backoffLevel must survive"); - const newNames = after - .filter((c) => c.id !== (existing as ConnectionRow).id) - .map((c) => c.name); + const newNames = after.filter((c) => c.id !== (existing as ConnectionRow).id).map((c) => c.name); assert.equal(new Set(newNames).size, newNames.length, "no duplicate names among new entries"); assert.ok(!newNames.includes("Key 1")); }); diff --git a/tests/unit/cache-config-route-8219.test.ts b/tests/unit/cache-config-route-8219.test.ts index 8fc0030972f1..4e71d4d0b2dc 100644 --- a/tests/unit/cache-config-route-8219.test.ts +++ b/tests/unit/cache-config-route-8219.test.ts @@ -21,7 +21,7 @@ const core = await import("../../src/lib/db/core.ts"); function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } diff --git a/tests/unit/call-log-artifact-worker.test.ts b/tests/unit/call-log-artifact-worker.test.ts index 948a3fd110d6..84cd2c241a2b 100644 --- a/tests/unit/call-log-artifact-worker.test.ts +++ b/tests/unit/call-log-artifact-worker.test.ts @@ -12,7 +12,7 @@ const { writeCallArtifactAsync, closeCallLogArtifactWriter, resolveCallLogArtifa test.after(async () => { await closeCallLogArtifactWriter(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function buildArtifact(id: string) { @@ -120,7 +120,7 @@ test("worker resolution covers npm, standalone, source, and missing layouts", () } ); } finally { - fs.rmSync(layoutRoot, { recursive: true, force: true }); + fs.rmSync(layoutRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } const resolved = resolveCallLogArtifactWorker(); diff --git a/tests/unit/call-log-cap.test.ts b/tests/unit/call-log-cap.test.ts index e1cbd7e95898..484ac0e92ea7 100644 --- a/tests/unit/call-log-cap.test.ts +++ b/tests/unit/call-log-cap.test.ts @@ -21,7 +21,7 @@ const providersDb = await import("../../src/lib/db/providers.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -88,7 +88,7 @@ test.beforeEach(async () => { test.after(() => { restorePipelineEnv(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("saveCallLog stores only summary metadata in SQLite and writes detailed artifact", async () => { diff --git a/tests/unit/call-log-file-rotation.test.ts b/tests/unit/call-log-file-rotation.test.ts index 06fe48aa1183..4e5cf8036879 100644 --- a/tests/unit/call-log-file-rotation.test.ts +++ b/tests/unit/call-log-file-rotation.test.ts @@ -33,7 +33,12 @@ async function resetTestDataDir() { if (/^storage\.sqlite(?:-shm|-wal)?$/i.test(entry)) { continue; } - fs.rmSync(path.join(TEST_DATA_DIR, entry), { recursive: true, force: true }); + fs.rmSync(path.join(TEST_DATA_DIR, entry), { + recursive: true, + force: true, + maxRetries: 5, + retryDelay: 100, + }); } const db = core.getDbInstance(); db.prepare("DELETE FROM call_logs").run(); @@ -121,7 +126,7 @@ test.after(async () => { test("call log file rotation honors both retention days and file count", () => { assert.ok(CALL_LOGS_DIR, "CALL_LOGS_DIR should resolve for test data dir"); - fs.rmSync(CALL_LOGS_DIR, { recursive: true, force: true }); + fs.rmSync(CALL_LOGS_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(CALL_LOGS_DIR, { recursive: true }); const now = Date.now(); @@ -235,7 +240,7 @@ test("rotateCallLogs swallows filesystem errors during cleanup", () => { test("cleanupOverflowCallLogFiles ignores rmSync failures for old artifacts", () => { assert.ok(CALL_LOGS_DIR, "CALL_LOGS_DIR should resolve for test data dir"); - fs.rmSync(CALL_LOGS_DIR, { recursive: true, force: true }); + fs.rmSync(CALL_LOGS_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(CALL_LOGS_DIR, { recursive: true }); const dayDir = path.join(CALL_LOGS_DIR, "2026-04-02"); diff --git a/tests/unit/call-log-oom-unbounded-5618.test.ts b/tests/unit/call-log-oom-unbounded-5618.test.ts index 49d52be057f6..d6551378f7c7 100644 --- a/tests/unit/call-log-oom-unbounded-5618.test.ts +++ b/tests/unit/call-log-oom-unbounded-5618.test.ts @@ -65,13 +65,13 @@ const unboundedSelectsOnCallLogs = (sqls: string[]) => test.beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); }); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#5618 collectReferencedArtifacts pages with LIMIT and collects across pages — no unbounded .all()", () => { diff --git a/tests/unit/call-log-provider-display.test.ts b/tests/unit/call-log-provider-display.test.ts index 4b1e9ee4ce5c..a5c806d2a5a2 100644 --- a/tests/unit/call-log-provider-display.test.ts +++ b/tests/unit/call-log-provider-display.test.ts @@ -4,7 +4,9 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-call-log-provider-display-")); +const TEST_DATA_DIR = fs.mkdtempSync( + path.join(os.tmpdir(), "omniroute-call-log-provider-display-") +); process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); @@ -33,7 +35,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("getCallLogs and getCallLogById expose providerDisplay from provider node name", async () => { diff --git a/tests/unit/call-log-save-drain.test.ts b/tests/unit/call-log-save-drain.test.ts index 6371d2785dc9..ba5c5b846c65 100644 --- a/tests/unit/call-log-save-drain.test.ts +++ b/tests/unit/call-log-save-drain.test.ts @@ -17,7 +17,7 @@ const artifactWriter = await import("../../src/lib/usage/callLogArtifactWriter.t test.after(async () => { await artifactWriter.closeCallLogArtifactWriter(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("call-log drain waits for artifact metadata and summary commit", async () => { diff --git a/tests/unit/call-log-startup.test.ts b/tests/unit/call-log-startup.test.ts index 9d556f7a8734..67063a07f861 100644 --- a/tests/unit/call-log-startup.test.ts +++ b/tests/unit/call-log-startup.test.ts @@ -13,7 +13,7 @@ async function removeTestDataDir() { for (let attempt = 0; attempt < 5; attempt += 1) { try { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); return; } catch (error: any) { lastError = error; diff --git a/tests/unit/call-log-stream-debug.test.ts b/tests/unit/call-log-stream-debug.test.ts index 33d3978f74a2..6af3e46f3238 100644 --- a/tests/unit/call-log-stream-debug.test.ts +++ b/tests/unit/call-log-stream-debug.test.ts @@ -13,7 +13,7 @@ const callLogs = await import("../../src/lib/usage/callLogs.ts"); async function resetStorage() { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -24,7 +24,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("saveCallLog preserves streamChunks in pipeline payloads", async () => { diff --git a/tests/unit/call-log-trim-sql-vars-5217.test.ts b/tests/unit/call-log-trim-sql-vars-5217.test.ts index 7f7dab57aaef..46963977efc6 100644 --- a/tests/unit/call-log-trim-sql-vars-5217.test.ts +++ b/tests/unit/call-log-trim-sql-vars-5217.test.ts @@ -37,13 +37,13 @@ function insertCallLog(id: string, timestamp: string) { test.beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); }); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("trimCallLogsToMaxRows deletes >999 rows in one pass without 'too many SQL variables'", () => { diff --git a/tests/unit/call-logs-correlation-substring.test.ts b/tests/unit/call-logs-correlation-substring.test.ts index c5fe01a53e29..277dbf0ddfe0 100644 --- a/tests/unit/call-logs-correlation-substring.test.ts +++ b/tests/unit/call-logs-correlation-substring.test.ts @@ -12,7 +12,7 @@ const callLogs = await import("../../src/lib/usage/callLogs.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // Seed test data diff --git a/tests/unit/call-logs-exclude-tests-allowlist.test.ts b/tests/unit/call-logs-exclude-tests-allowlist.test.ts index 78aa5728af8f..cec5b07d4d05 100644 --- a/tests/unit/call-logs-exclude-tests-allowlist.test.ts +++ b/tests/unit/call-logs-exclude-tests-allowlist.test.ts @@ -51,13 +51,13 @@ function insertCallLog(row: SeedRow) { test.beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); }); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("excludeTests keeps only /v1 and /api/v1 inference rows, drops all backend/management rows", async () => { diff --git a/tests/unit/call-logs-pagination.test.ts b/tests/unit/call-logs-pagination.test.ts index 99835b08f16b..41559a643405 100644 --- a/tests/unit/call-logs-pagination.test.ts +++ b/tests/unit/call-logs-pagination.test.ts @@ -70,7 +70,7 @@ function insertCallLog(row: Record) { test.before(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); // Seed 25 rows with strictly increasing timestamps (id N -> minute N). for (let i = 0; i < 25; i++) { @@ -81,7 +81,7 @@ test.before(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#2565: getCallLogs honors limit and returns newest-first", async () => { diff --git a/tests/unit/call-logs-requested-model.test.ts b/tests/unit/call-logs-requested-model.test.ts index 745854ad325b..bf89c1183a2e 100644 --- a/tests/unit/call-logs-requested-model.test.ts +++ b/tests/unit/call-logs-requested-model.test.ts @@ -13,7 +13,7 @@ const providers = await import("../../src/lib/db/providers.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -23,7 +23,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("call logs persist requestedModel and allow filtering by requested model", async () => { diff --git a/tests/unit/call-logs-session-tag.test.ts b/tests/unit/call-logs-session-tag.test.ts index 4e466af1f278..3fb92e035a3e 100644 --- a/tests/unit/call-logs-session-tag.test.ts +++ b/tests/unit/call-logs-session-tag.test.ts @@ -15,7 +15,7 @@ const callLogs = await import("../../src/lib/usage/callLogs.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("saveCallLog persists sessionTag when explicitly supplied", async () => { diff --git a/tests/unit/capture-critical-db-state.test.ts b/tests/unit/capture-critical-db-state.test.ts index 6ded65793ee8..4f8a2ecbff2f 100644 --- a/tests/unit/capture-critical-db-state.test.ts +++ b/tests/unit/capture-critical-db-state.test.ts @@ -37,7 +37,7 @@ after(() => { delete process.env.DATA_DIR; } try { - fs.rmSync(tempDir, { recursive: true, force: true }); + fs.rmSync(tempDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // ignore cleanup errors } diff --git a/tests/unit/catalog-auto-routing-disabled-10831.test.ts b/tests/unit/catalog-auto-routing-disabled-10831.test.ts index f2f96ef95fe0..cd40b633c3d3 100644 --- a/tests/unit/catalog-auto-routing-disabled-10831.test.ts +++ b/tests/unit/catalog-auto-routing-disabled-10831.test.ts @@ -40,7 +40,7 @@ const isAutoId = (m: { id: string }) => m.id.startsWith("auto/"); test.after(() => { core.resetDbInstance(); try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { /* best-effort */ } diff --git a/tests/unit/catalog-hide-auto-no-think.test.ts b/tests/unit/catalog-hide-auto-no-think.test.ts index c0db3cffa705..e4f2bff11454 100644 --- a/tests/unit/catalog-hide-auto-no-think.test.ts +++ b/tests/unit/catalog-hide-auto-no-think.test.ts @@ -33,7 +33,7 @@ async function fetchCatalog(): Promise> { test.after(() => { core.resetDbInstance(); try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { /* best-effort */ } @@ -60,7 +60,11 @@ test("hideAutoCombos=true removes auto/* ids from /v1/models", async () => { await settingsDb.updateSettings({ hideAutoCombos: false, hideNoThinkVariants: false }); const off = await fetchCatalog(); const autoWhenOff = off.filter(isAutoId).map((m) => m.id); - assert.equal(autoWhenOff.length > 0, true, `expected auto/* ids when toggle off, got ${autoWhenOff.length}`); + assert.equal( + autoWhenOff.length > 0, + true, + `expected auto/* ids when toggle off, got ${autoWhenOff.length}` + ); await settingsDb.updateSettings({ hideAutoCombos: true, hideNoThinkVariants: false }); const on = await fetchCatalog(); @@ -69,7 +73,11 @@ test("hideAutoCombos=true removes auto/* ids from /v1/models", async () => { // Original provider models must still be present const hasProviderModel = on.some((m) => m.id.startsWith("openai/") || m.id.startsWith("oa/")); - assert.equal(hasProviderModel, true, "original provider models must remain when hideAutoCombos=true"); + assert.equal( + hasProviderModel, + true, + "original provider models must remain when hideAutoCombos=true" + ); }); test("hideNoThinkVariants=true removes no-think/* ids from /v1/models", async () => { @@ -97,20 +105,32 @@ test("hideNoThinkVariants=true removes no-think/* ids from /v1/models", async () const hasProviderModel = on.some( (m) => m.id.startsWith("claude/") || m.id.startsWith("anthropic/") ); - assert.equal(hasProviderModel, true, "original provider models must remain when hideNoThinkVariants=true"); + assert.equal( + hasProviderModel, + true, + "original provider models must remain when hideNoThinkVariants=true" + ); return; } await settingsDb.updateSettings({ hideAutoCombos: false, hideNoThinkVariants: true }); const on = await fetchCatalog(); const leaked = on.filter(isNoThinkId).map((m) => m.id); - assert.deepEqual(leaked, [], `no-think/* ids leaked when hideNoThinkVariants=true: ${leaked.join(", ")}`); + assert.deepEqual( + leaked, + [], + `no-think/* ids leaked when hideNoThinkVariants=true: ${leaked.join(", ")}` + ); // Original provider models must still be present const hasProviderModel = on.some( (m) => m.id.startsWith("claude/") || m.id.startsWith("anthropic/") ); - assert.equal(hasProviderModel, true, "original provider models must remain when hideNoThinkVariants=true"); + assert.equal( + hasProviderModel, + true, + "original provider models must remain when hideNoThinkVariants=true" + ); }); test("both toggles on: neither auto/* nor no-think/* appear; original models present", async () => { @@ -125,7 +145,15 @@ test("both toggles on: neither auto/* nor no-think/* appear; original models pre assert.deepEqual(noThinkLeaked, [], `no-think/* ids leaked: ${noThinkLeaked.join(", ")}`); const hasProviderModel = on.some( - (m) => m.id.startsWith("openai/") || m.id.startsWith("oa/") || m.id.startsWith("claude/") || m.id.startsWith("anthropic/") + (m) => + m.id.startsWith("openai/") || + m.id.startsWith("oa/") || + m.id.startsWith("claude/") || + m.id.startsWith("anthropic/") + ); + assert.equal( + hasProviderModel, + true, + "original provider models must remain when both toggles are on" ); - assert.equal(hasProviderModel, true, "original provider models must remain when both toggles are on"); }); diff --git a/tests/unit/catalog-order-contract.test.ts b/tests/unit/catalog-order-contract.test.ts index e1c5c0248b35..d18dd13d721f 100644 --- a/tests/unit/catalog-order-contract.test.ts +++ b/tests/unit/catalog-order-contract.test.ts @@ -29,7 +29,7 @@ const v1ModelsCatalog = await import("../../src/app/api/v1/models/catalog.ts"); async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); v1ModelsCatalog.__resetCatalogBuilderRunsForTest(); } @@ -41,7 +41,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); async function seedConnection(provider: string, overrides: Record = {}) { @@ -83,13 +83,19 @@ test("catalog /v1/models: exact provider-grouped order (blocks === distinct owne { id: "gpt-4", name: "GPT-4" }, { id: "gpt-3.5-turbo", name: "GPT-3.5 Turbo" }, ]); - await modelsDb.replaceSyncedAvailableModelsForConnection("anthropic", (conn2 as { id: string }).id, [ - { id: "claude-3-opus", name: "Claude 3 Opus" }, - ]); - await modelsDb.replaceSyncedAvailableModelsForConnection("opencode", (conn3 as { id: string }).id, [ - { id: "kimi-k2", name: "Kimi K2" }, - { id: "glm-4", name: "GLM-4" }, - ]); + await modelsDb.replaceSyncedAvailableModelsForConnection( + "anthropic", + (conn2 as { id: string }).id, + [{ id: "claude-3-opus", name: "Claude 3 Opus" }] + ); + await modelsDb.replaceSyncedAvailableModelsForConnection( + "opencode", + (conn3 as { id: string }).id, + [ + { id: "kimi-k2", name: "Kimi K2" }, + { id: "glm-4", name: "GLM-4" }, + ] + ); const response = await v1ModelsCatalog.getUnifiedModelsResponse( new Request("http://localhost/v1/models?configuredOnly=true") diff --git a/tests/unit/cc-compatible-model-catalog.test.ts b/tests/unit/cc-compatible-model-catalog.test.ts index 8453abed6204..54fc4907711f 100644 --- a/tests/unit/cc-compatible-model-catalog.test.ts +++ b/tests/unit/cc-compatible-model-catalog.test.ts @@ -13,7 +13,7 @@ const v1ModelsCatalog = await import("../../src/app/api/v1/models/catalog.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -23,7 +23,7 @@ test.afterEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("v1 models exposes CC-compatible fallback models under the provider node prefix", async () => { diff --git a/tests/unit/cc-compatible-provider.test.ts b/tests/unit/cc-compatible-provider.test.ts index 8f8cc672af1d..e4c81bb07379 100644 --- a/tests/unit/cc-compatible-provider.test.ts +++ b/tests/unit/cc-compatible-provider.test.ts @@ -34,7 +34,7 @@ const originalAllowLocalProviderUrls = process.env.OMNIROUTE_ALLOW_LOCAL_PROVIDE async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -76,7 +76,7 @@ test.after(() => { process.env.OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS = originalAllowLocalProviderUrls; } core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("buildClaudeCodeCompatibleRequest keeps prior role history while dropping trailing assistant prefill", () => { diff --git a/tests/unit/cc-discovery-alias-api.test.ts b/tests/unit/cc-discovery-alias-api.test.ts index 9f466eeed204..3e03adbe5d44 100644 --- a/tests/unit/cc-discovery-alias-api.test.ts +++ b/tests/unit/cc-discovery-alias-api.test.ts @@ -14,7 +14,7 @@ const route = await import("../../src/app/api/providers/[id]/cc-alias/route.ts") function resetDb() { core.resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(tmpDir, { recursive: true }); } @@ -41,7 +41,7 @@ describe("PUT/GET /api/providers/[id]/cc-alias", () => { after(() => { core.resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); delete process.env.REQUIRE_API_KEY; }); diff --git a/tests/unit/cc-discovery-aliases-gate.test.ts b/tests/unit/cc-discovery-aliases-gate.test.ts index 74e26ad65311..5c4e9c67f78e 100644 --- a/tests/unit/cc-discovery-aliases-gate.test.ts +++ b/tests/unit/cc-discovery-aliases-gate.test.ts @@ -24,7 +24,7 @@ const { function resetDb() { core.resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(tmpDir, { recursive: true }); } @@ -95,7 +95,7 @@ describe("ccDiscoveryAliases storage", () => { after(() => { core.resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); it("getCcAliasProviderSetting returns null when unset", () => { @@ -171,7 +171,7 @@ describe("global CC alias state (env / DB / default)", () => { after(() => { core.resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); delete process.env.EXPOSE_CC_DISCOVERY_ALIASES; }); diff --git a/tests/unit/cc-discovery-metrics.test.ts b/tests/unit/cc-discovery-metrics.test.ts index 166ae74a793e..5d39e0a1cd41 100644 --- a/tests/unit/cc-discovery-metrics.test.ts +++ b/tests/unit/cc-discovery-metrics.test.ts @@ -17,7 +17,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // best-effort cleanup } diff --git a/tests/unit/changelog-fragments.test.ts b/tests/unit/changelog-fragments.test.ts index 6be69c0a4d69..2dddf036132f 100644 --- a/tests/unit/changelog-fragments.test.ts +++ b/tests/unit/changelog-fragments.test.ts @@ -78,7 +78,7 @@ test("collectFragments reads sections sorted and flags invalid files", () => { assert.equal(c.features.length, 1); assert.equal(c.invalid.length, 1); assert.match(c.invalid[0].file, /bad\.md/); - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("insertBullets appends at the END of each living section", () => { @@ -100,7 +100,11 @@ test("insertBullets appends at the END of each living section", () => { assert.ok(maintIdx > maintHeadIdx && maintIdx < lines.indexOf("## [3.8.46] - 2026-07-04")); // Only the FIRST (living) occurrence of a heading is touched — the shipped 3.8.46 // section is byte-identical. - assert.ok(out.includes("## [3.8.46] - 2026-07-04\n\n### ✨ New Features\n\n- **old feature**: shipped (#0)")); + assert.ok( + out.includes( + "## [3.8.46] - 2026-07-04\n\n### ✨ New Features\n\n- **old feature**: shipped (#0)" + ) + ); // No existing bullet lost. for (const existing of ["#1 — thanks @a", "existing fix (#2", "existing maintenance (#3"]) { assert.ok(out.includes(existing)); @@ -108,7 +112,10 @@ test("insertBullets appends at the END of each living section", () => { }); test("insertBullets throws when a needed heading is missing", () => { - const noMaint = CHANGELOG_FIXTURE.replace("### 📝 Maintenance\n\n- chore: existing maintenance (#3)\n", ""); + const noMaint = CHANGELOG_FIXTURE.replace( + "### 📝 Maintenance\n\n- chore: existing maintenance (#3)\n", + "" + ); assert.throws( () => insertBullets(noMaint, { maintenance: [{ text: "- x" }] }), /📝 Maintenance.*not found/s @@ -134,19 +141,19 @@ test("aggregate dry-run touches nothing; real run writes and deletes fragments", const again = aggregate({ root }); assert.equal(again.total, 0); assert.equal(readFileSync(join(root, "CHANGELOG.md"), "utf8"), after); - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("aggregate refuses invalid fragments loudly", () => { const root = makeRoot({ fragments: { "features/oops.md": "forgot the dash" } }); assert.throws(() => aggregate({ root }), /invalid changelog fragments/); - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("gate findInvalidFragments: clean tree passes, bad placement/content fail", () => { const clean = makeRoot({ fragments: { "maintenance/1-ok.md": "- ok (#1)" } }); assert.deepEqual(findInvalidFragments(clean), []); - rmSync(clean, { recursive: true, force: true }); + rmSync(clean, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); const dirty = makeRoot({ fragments: { @@ -161,7 +168,7 @@ test("gate findInvalidFragments: clean tree passes, bad placement/content fail", assert.ok(files.some((f) => f.includes("stray.md"))); assert.ok(files.some((f) => f.includes("unknown-section"))); assert.ok(files.some((f) => f.includes("3-bad.md"))); - rmSync(dirty, { recursive: true, force: true }); + rmSync(dirty, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("gate skips README.md and .gitkeep; absent changelog.d is fine", () => { @@ -170,11 +177,11 @@ test("gate skips README.md and .gitkeep; absent changelog.d is fine", () => { mkdirSync(join(root, "changelog.d/fixes"), { recursive: true }); writeFileSync(join(root, "changelog.d/fixes/.gitkeep"), ""); assert.deepEqual(findInvalidFragments(root), []); - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); const bare = mkdtempSync(join(tmpdir(), "chfrag-bare-")); assert.deepEqual(findInvalidFragments(bare), []); - rmSync(bare, { recursive: true, force: true }); + rmSync(bare, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("SECTIONS maps every dir to a real living-section heading in the fixture", () => { diff --git a/tests/unit/chaos-api-routes.test.ts b/tests/unit/chaos-api-routes.test.ts index 650ad2ed59fd..358270f28274 100644 --- a/tests/unit/chaos-api-routes.test.ts +++ b/tests/unit/chaos-api-routes.test.ts @@ -40,12 +40,17 @@ async function resetStorage() { // config cache too, or getChaosConfig() keeps serving a stale value (e.g. a // prior test's `enabled: true`) after resetDbInstance() below. chaosConfig.invalidateChaosConfigCache(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); delete process.env.INITIAL_PASSWORD; } -function makeRequest(method: string, url: string, body?: unknown, headers: Record = {}) { +function makeRequest( + method: string, + url: string, + body?: unknown, + headers: Record = {} +) { return new Request(url, { method, headers: { @@ -74,7 +79,7 @@ test.afterEach(() => { test.after(() => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; @@ -114,10 +119,7 @@ test("GET /api/chaos/config — returns defaults, PUT updates, DELETE resets", a const getBody = (await getRes.json()) as { config: typeof chaosConfig.DEFAULT_CHAOS_CONFIG }; // JSON.stringify drops keys whose value is `undefined` (systemPrompt), so compare // against the JSON round-tripped shape rather than the raw in-memory default. - assert.deepEqual( - getBody.config, - JSON.parse(JSON.stringify(chaosConfig.DEFAULT_CHAOS_CONFIG)) - ); + assert.deepEqual(getBody.config, JSON.parse(JSON.stringify(chaosConfig.DEFAULT_CHAOS_CONFIG))); const putRes = await configRoute.PUT( makeRequest("PUT", "http://localhost/api/chaos/config", { @@ -137,12 +139,11 @@ test("GET /api/chaos/config — returns defaults, PUT updates, DELETE resets", a makeRequest("DELETE", "http://localhost/api/chaos/config") ); assert.equal(deleteRes.status, 200); - const deleteBody = (await deleteRes.json()) as { config: typeof chaosConfig.DEFAULT_CHAOS_CONFIG }; + const deleteBody = (await deleteRes.json()) as { + config: typeof chaosConfig.DEFAULT_CHAOS_CONFIG; + }; // Same JSON.stringify undefined-key drop as the GET assertion above. - assert.deepEqual( - deleteBody.config, - JSON.parse(JSON.stringify(chaosConfig.DEFAULT_CHAOS_CONFIG)) - ); + assert.deepEqual(deleteBody.config, JSON.parse(JSON.stringify(chaosConfig.DEFAULT_CHAOS_CONFIG))); }); test("PUT /api/chaos/config — 400 on schema validation failure", async () => { diff --git a/tests/unit/chaos-config.test.ts b/tests/unit/chaos-config.test.ts index 3f37e7a406ce..e90a76ad5e43 100644 --- a/tests/unit/chaos-config.test.ts +++ b/tests/unit/chaos-config.test.ts @@ -24,7 +24,7 @@ const chaosConfig = await import("../../src/lib/chaos/chaosConfig.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -34,7 +34,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; } else { diff --git a/tests/unit/chaos-executor.test.ts b/tests/unit/chaos-executor.test.ts index b67bf0bad57e..ae44c2f0bdf7 100644 --- a/tests/unit/chaos-executor.test.ts +++ b/tests/unit/chaos-executor.test.ts @@ -28,7 +28,7 @@ const chaosExecutor = await import("../../src/lib/chaos/chaosExecutor.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -42,7 +42,7 @@ test.afterEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; } else { diff --git a/tests/unit/chat-combo-live-test.test.ts b/tests/unit/chat-combo-live-test.test.ts index 348154c40a2f..36952cb04ab1 100644 --- a/tests/unit/chat-combo-live-test.test.ts +++ b/tests/unit/chat-combo-live-test.test.ts @@ -25,7 +25,7 @@ async function flushBackgroundWork() { async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); resetAllCircuitBreakers(); } @@ -132,7 +132,7 @@ test.after(async () => { globalThis.fetch = originalFetch; resetAllCircuitBreakers(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("combo live test bypasses connection cooldown and breaker state to perform a real upstream request", async () => { diff --git a/tests/unit/chat-completions-parse-once-7847.test.ts b/tests/unit/chat-completions-parse-once-7847.test.ts index fd8f50fc99b1..5c014fac0d34 100644 --- a/tests/unit/chat-completions-parse-once-7847.test.ts +++ b/tests/unit/chat-completions-parse-once-7847.test.ts @@ -119,5 +119,5 @@ test("#7847 downstream body resolution preserves the parsed object's identity", test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); diff --git a/tests/unit/chat-completions-route-shape-gate.test.ts b/tests/unit/chat-completions-route-shape-gate.test.ts index 38d8beea761c..b0f7922a7002 100644 --- a/tests/unit/chat-completions-route-shape-gate.test.ts +++ b/tests/unit/chat-completions-route-shape-gate.test.ts @@ -45,7 +45,7 @@ async function flushBackgroundWork() { async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -63,7 +63,7 @@ test.after(async () => { await flushBackgroundWork(); globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function makeCountingRequest(body: string) { diff --git a/tests/unit/chat-core-intercept-fetch.test.ts b/tests/unit/chat-core-intercept-fetch.test.ts index 7c9dfa774de5..50ec13d062c4 100644 --- a/tests/unit/chat-core-intercept-fetch.test.ts +++ b/tests/unit/chat-core-intercept-fetch.test.ts @@ -15,12 +15,10 @@ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-test-chatcore-in process.env.DATA_DIR = tmpDir; const core = await import("../../src/lib/db/core.ts"); -const { setInterceptionRules, resolveInterceptFetch } = await import( - "../../src/lib/db/interceptionRules.ts" -); -const { prepareWebFetchFallbackBody } = await import( - "../../open-sse/services/webFetchInterception.ts" -); +const { setInterceptionRules, resolveInterceptFetch } = + await import("../../src/lib/db/interceptionRules.ts"); +const { prepareWebFetchFallbackBody } = + await import("../../open-sse/services/webFetchInterception.ts"); function buildRequestBody() { return { @@ -51,7 +49,7 @@ function runChatCoreInterceptFetchStep( describe("chatCore.ts interceptFetch call site — flag-off regression guard (#7339)", () => { function resetDb() { core.resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(tmpDir, { recursive: true }); } @@ -61,7 +59,7 @@ describe("chatCore.ts interceptFetch call site — flag-off regression guard (#7 after(() => { core.resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); it("leaves the outgoing body byte-identical when no interceptFetch rule is configured", () => { diff --git a/tests/unit/chat-helpers.test.ts b/tests/unit/chat-helpers.test.ts index 9007e0b33a00..a971101891eb 100644 --- a/tests/unit/chat-helpers.test.ts +++ b/tests/unit/chat-helpers.test.ts @@ -28,7 +28,7 @@ const { setTlsClientForTest } = await import("../../open-sse/utils/proxyFetch.ts async function resetStorage() { resetAllCircuitBreakers(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -51,7 +51,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("resolveModelOrError resolves built-in auto catalog ids without persisted combo rows", async () => { diff --git a/tests/unit/chat-routing-synced-inventory-11089.test.ts b/tests/unit/chat-routing-synced-inventory-11089.test.ts index 441b14893b6f..269a584e7257 100644 --- a/tests/unit/chat-routing-synced-inventory-11089.test.ts +++ b/tests/unit/chat-routing-synced-inventory-11089.test.ts @@ -41,13 +41,13 @@ const PROVIDER = "ollama-local"; async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); async function createConnection(data: Record): Promise { diff --git a/tests/unit/chat-safetynet-reqid-6097.test.ts b/tests/unit/chat-safetynet-reqid-6097.test.ts index 78121f3f76e9..9989b8778fdc 100644 --- a/tests/unit/chat-safetynet-reqid-6097.test.ts +++ b/tests/unit/chat-safetynet-reqid-6097.test.ts @@ -45,7 +45,7 @@ async function flushBackgroundWork() { async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -63,83 +63,80 @@ test.after(async () => { await flushBackgroundWork(); globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); -test( - "#6097 safety-net combo redirect does not throw ReferenceError: reqId is not defined", - async () => { - // A healthy provider connection so the inner auto/* combo has a candidate to - // dispatch to once the safety-net redirect completes. - await providersDb.createProviderConnection({ - provider: "openai", - authType: "apikey", - name: "openai-safetynet-6097", - apiKey: "sk-safetynet-6097", - isActive: true, - testStatus: "active", +test("#6097 safety-net combo redirect does not throw ReferenceError: reqId is not defined", async () => { + // A healthy provider connection so the inner auto/* combo has a candidate to + // dispatch to once the safety-net redirect completes. + await providersDb.createProviderConnection({ + provider: "openai", + authType: "apikey", + name: "openai-safetynet-6097", + apiKey: "sk-safetynet-6097", + isActive: true, + testStatus: "active", + }); + + // A persisted combo whose single member is a virtual `auto/*` combo. The + // top-level handler resolves the OUTER combo; only when handleComboChat calls + // handleSingleModelChat("auto/fast", …) does resolveModelOrError discover the + // auto combo and fire the safety-net redirect. + await combosDb.createCombo({ + name: "nested-auto-6097", + strategy: "priority", + models: [{ provider: "auto", model: "fast" }], + }); + + const fetchCalls: string[] = []; + globalThis.fetch = async (url: any) => { + fetchCalls.push(String(url)); + return Response.json({ + id: "chatcmpl-safetynet-6097", + choices: [{ message: { role: "assistant", content: "OK" } }], }); - - // A persisted combo whose single member is a virtual `auto/*` combo. The - // top-level handler resolves the OUTER combo; only when handleComboChat calls - // handleSingleModelChat("auto/fast", …) does resolveModelOrError discover the - // auto combo and fire the safety-net redirect. - await combosDb.createCombo({ - name: "nested-auto-6097", - strategy: "priority", - models: [{ provider: "auto", model: "fast" }], - }); - - const fetchCalls: string[] = []; - globalThis.fetch = async (url: any) => { - fetchCalls.push(String(url)); - return Response.json({ - id: "chatcmpl-safetynet-6097", - choices: [{ message: { role: "assistant", content: "OK" } }], - }); - }; - - const request = new Request("http://localhost/v1/chat/completions", { - method: "POST", - headers: { - "Content-Type": "application/json", - // Forces the combo target to be attempted (bypasses availability - // pre-skipping) so the redirect path is exercised deterministically. - "X-Internal-Test": "combo-health-check", - }, - body: JSON.stringify({ - model: "nested-auto-6097", - messages: [{ role: "user", content: "Reply with OK only." }], - max_tokens: 16, - stream: false, - temperature: 0, - }), - }); - - const response = await chatRoute.POST(request); - const bodyText = await response.text(); - - // Primary guard: the exact bug signature must never appear. - assert.ok( - !bodyText.includes("reqId is not defined"), - `safety-net redirect leaked a ReferenceError: ${bodyText.slice(0, 200)}` - ); - - // The redirect must complete successfully (buggy version returned 502). - assert.equal( - response.status, - 200, - `expected 200 after safety-net redirect, got ${response.status}: ${bodyText.slice(0, 200)}` - ); - - // And it must have proceeded past the redirect into a real upstream dispatch - // (buggy version threw before any fetch → zero calls). - assert.ok( - fetchCalls.length > 0, - "expected the redirected inner combo to reach a real upstream fetch" - ); - - const body = JSON.parse(bodyText) as any; - assert.equal(body.choices[0].message.content, "OK"); - } -); + }; + + const request = new Request("http://localhost/v1/chat/completions", { + method: "POST", + headers: { + "Content-Type": "application/json", + // Forces the combo target to be attempted (bypasses availability + // pre-skipping) so the redirect path is exercised deterministically. + "X-Internal-Test": "combo-health-check", + }, + body: JSON.stringify({ + model: "nested-auto-6097", + messages: [{ role: "user", content: "Reply with OK only." }], + max_tokens: 16, + stream: false, + temperature: 0, + }), + }); + + const response = await chatRoute.POST(request); + const bodyText = await response.text(); + + // Primary guard: the exact bug signature must never appear. + assert.ok( + !bodyText.includes("reqId is not defined"), + `safety-net redirect leaked a ReferenceError: ${bodyText.slice(0, 200)}` + ); + + // The redirect must complete successfully (buggy version returned 502). + assert.equal( + response.status, + 200, + `expected 200 after safety-net redirect, got ${response.status}: ${bodyText.slice(0, 200)}` + ); + + // And it must have proceeded past the redirect into a real upstream dispatch + // (buggy version threw before any fetch → zero calls). + assert.ok( + fetchCalls.length > 0, + "expected the redirected inner combo to reach a real upstream fetch" + ); + + const body = JSON.parse(bodyText) as any; + assert.equal(body.choices[0].message.content, "OK"); +}); diff --git a/tests/unit/chatcore-attempt-logging.test.ts b/tests/unit/chatcore-attempt-logging.test.ts index e41c58befa3f..b9fe57fbd3de 100644 --- a/tests/unit/chatcore-attempt-logging.test.ts +++ b/tests/unit/chatcore-attempt-logging.test.ts @@ -72,7 +72,7 @@ before(async () => { after(() => { coreDb.resetDbInstance(); - fs.rmSync(testDataDir, { recursive: true, force: true }); + fs.rmSync(testDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("persists a call log row with the mapped fields (default cacheSource=upstream)", async () => { diff --git a/tests/unit/chatcore-caveman-output-analytics.test.ts b/tests/unit/chatcore-caveman-output-analytics.test.ts index e776cacb28b9..e0acb70dcdc8 100644 --- a/tests/unit/chatcore-caveman-output-analytics.test.ts +++ b/tests/unit/chatcore-caveman-output-analytics.test.ts @@ -13,9 +13,8 @@ const testDataDir = fs.mkdtempSync(path.join(os.tmpdir(), "omni-caveman-test-")) process.env.DATA_DIR = testDataDir; const coreDb = await import("../../src/lib/db/core.ts"); -const { writeCavemanOutputAnalytics } = await import( - "../../open-sse/handlers/chatCore/cavemanOutputAnalytics.ts" -); +const { writeCavemanOutputAnalytics } = + await import("../../open-sse/handlers/chatCore/cavemanOutputAnalytics.ts"); function rowFor(requestId: string): Record | undefined { return coreDb @@ -33,7 +32,7 @@ before(async () => { after(() => { coreDb.resetDbInstance(); try { - fs.rmSync(testDataDir, { recursive: true, force: true }); + fs.rmSync(testDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // best-effort cleanup } diff --git a/tests/unit/chatcore-codex-account-pool.test.ts b/tests/unit/chatcore-codex-account-pool.test.ts index a828d7357cec..31439c3a1d0a 100644 --- a/tests/unit/chatcore-codex-account-pool.test.ts +++ b/tests/unit/chatcore-codex-account-pool.test.ts @@ -62,7 +62,7 @@ function buildResponsesResponse(text = "ok") { async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -135,7 +135,7 @@ test.after(async () => { globalThis.fetch = originalFetch; await waitForAsyncSideEffects(); await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("chatCore persists child cooldown for each rotated Codex attempt", async () => { diff --git a/tests/unit/chatcore-combo-context-limit-8378.test.ts b/tests/unit/chatcore-combo-context-limit-8378.test.ts index 29011fdc555d..2dd72a9b0ae1 100644 --- a/tests/unit/chatcore-combo-context-limit-8378.test.ts +++ b/tests/unit/chatcore-combo-context-limit-8378.test.ts @@ -37,7 +37,7 @@ const originalSiblingEnv = process.env[SIBLING_LIMIT_ENV]; async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -51,10 +51,7 @@ test.before(async () => { await combosDb.createCombo({ name: COMBO_NAME, - models: [ - `${MAIN_PROVIDER}/${MAIN_MODEL}`, - `${SIBLING_PROVIDER}/${SIBLING_MODEL}`, - ], + models: [`${MAIN_PROVIDER}/${MAIN_MODEL}`, `${SIBLING_PROVIDER}/${SIBLING_MODEL}`], }); // Defensive: nothing in the expected (fixed) code path should ever reach @@ -77,7 +74,7 @@ test.after(() => { process.env[SIBLING_LIMIT_ENV] = originalSiblingEnv; } core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#8378: enforceOutputTokenBudget honors the combo-resolved context limit, not the plain per-target lookup", async () => { diff --git a/tests/unit/chatcore-combo-context-override-rescue.test.ts b/tests/unit/chatcore-combo-context-override-rescue.test.ts index 67b716ed3cc3..717b8c644d9a 100644 --- a/tests/unit/chatcore-combo-context-override-rescue.test.ts +++ b/tests/unit/chatcore-combo-context-override-rescue.test.ts @@ -90,7 +90,7 @@ test.before(() => { test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test.beforeEach(() => { diff --git a/tests/unit/chatcore-compression-analytics-write.test.ts b/tests/unit/chatcore-compression-analytics-write.test.ts index ab3d056b8fb4..d8645451b3f3 100644 --- a/tests/unit/chatcore-compression-analytics-write.test.ts +++ b/tests/unit/chatcore-compression-analytics-write.test.ts @@ -85,7 +85,7 @@ before(async () => { after(() => { coreDb.resetDbInstance(); try { - fs.rmSync(testDataDir, { recursive: true, force: true }); + fs.rmSync(testDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // best-effort cleanup } diff --git a/tests/unit/chatcore-compression-cache-stats.test.ts b/tests/unit/chatcore-compression-cache-stats.test.ts index fe1e3904b264..4c21b78d5c9e 100644 --- a/tests/unit/chatcore-compression-cache-stats.test.ts +++ b/tests/unit/chatcore-compression-cache-stats.test.ts @@ -13,9 +13,8 @@ const testDataDir = fs.mkdtempSync(path.join(os.tmpdir(), "omni-comp-cache-test- process.env.DATA_DIR = testDataDir; const coreDb = await import("../../src/lib/db/core.ts"); -const { recordCompressionCacheStats } = await import( - "../../open-sse/handlers/chatCore/compressionCacheStats.ts" -); +const { recordCompressionCacheStats } = + await import("../../open-sse/handlers/chatCore/compressionCacheStats.ts"); function rowsFor(provider: string): Array> { return coreDb @@ -42,7 +41,7 @@ before(async () => { after(() => { coreDb.resetDbInstance(); try { - fs.rmSync(testDataDir, { recursive: true, force: true }); + fs.rmSync(testDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // best-effort cleanup } diff --git a/tests/unit/chatcore-compression-settings.test.ts b/tests/unit/chatcore-compression-settings.test.ts index ad3ddbf9c0d4..7299f9b6fb62 100644 --- a/tests/unit/chatcore-compression-settings.test.ts +++ b/tests/unit/chatcore-compression-settings.test.ts @@ -22,7 +22,7 @@ before(async () => { after(() => { coreDb.resetDbInstance(); try { - fs.rmSync(testDataDir, { recursive: true, force: true }); + fs.rmSync(testDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // best-effort cleanup } diff --git a/tests/unit/chatcore-compression-usage-receipt.test.ts b/tests/unit/chatcore-compression-usage-receipt.test.ts index fd3cb9711a62..39082a784390 100644 --- a/tests/unit/chatcore-compression-usage-receipt.test.ts +++ b/tests/unit/chatcore-compression-usage-receipt.test.ts @@ -13,12 +13,10 @@ const testDataDir = fs.mkdtempSync(path.join(os.tmpdir(), "omni-compression-rece process.env.DATA_DIR = testDataDir; const coreDb = await import("../../src/lib/db/core.ts"); -const { insertCompressionAnalyticsRow, getCompressionAnalyticsSummary } = await import( - "../../src/lib/db/compressionAnalytics.ts" -); -const { attachCompressionUsageReceiptAfterAnalytics } = await import( - "../../open-sse/handlers/chatCore/compressionUsageReceipt.ts" -); +const { insertCompressionAnalyticsRow, getCompressionAnalyticsSummary } = + await import("../../src/lib/db/compressionAnalytics.ts"); +const { attachCompressionUsageReceiptAfterAnalytics } = + await import("../../open-sse/handlers/chatCore/compressionUsageReceipt.ts"); const tick = (ms: number) => new Promise((r) => setTimeout(r, ms)); @@ -28,7 +26,7 @@ before(async () => { after(() => { coreDb.resetDbInstance(); - fs.rmSync(testDataDir, { recursive: true, force: true }); + fs.rmSync(testDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("attaches the usage receipt only after pendingWrite resolves", async () => { @@ -66,11 +64,10 @@ test("attaches the usage receipt only after pendingWrite resolves", async () => test("swallows the no-matching-row case without throwing or recording a receipt", async () => { assert.doesNotThrow(() => - attachCompressionUsageReceiptAfterAnalytics( - { prompt_tokens: 1, total_tokens: 1 }, - "provider", - { pendingWrite: null, skillRequestId: "does-not-exist" } - ) + attachCompressionUsageReceiptAfterAnalytics({ prompt_tokens: 1, total_tokens: 1 }, "provider", { + pendingWrite: null, + skillRequestId: "does-not-exist", + }) ); await tick(40); const summary = getCompressionAnalyticsSummary(); diff --git a/tests/unit/chatcore-context-editing-telemetry.test.ts b/tests/unit/chatcore-context-editing-telemetry.test.ts index bfb6cad524d0..93a257449895 100644 --- a/tests/unit/chatcore-context-editing-telemetry.test.ts +++ b/tests/unit/chatcore-context-editing-telemetry.test.ts @@ -13,9 +13,8 @@ const testDataDir = fs.mkdtempSync(path.join(os.tmpdir(), "omni-ctxedit-test-")) process.env.DATA_DIR = testDataDir; const coreDb = await import("../../src/lib/db/core.ts"); -const { recordContextEditingTelemetryHook } = await import( - "../../open-sse/handlers/chatCore/contextEditingTelemetry.ts" -); +const { recordContextEditingTelemetryHook } = + await import("../../open-sse/handlers/chatCore/contextEditingTelemetry.ts"); function makeLog() { const debug: string[] = []; @@ -45,7 +44,7 @@ before(async () => { after(() => { coreDb.resetDbInstance(); try { - fs.rmSync(testDataDir, { recursive: true, force: true }); + fs.rmSync(testDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // best-effort cleanup } diff --git a/tests/unit/chatcore-executor-proxy.test.ts b/tests/unit/chatcore-executor-proxy.test.ts index c276d5dbbf1b..77772272422e 100644 --- a/tests/unit/chatcore-executor-proxy.test.ts +++ b/tests/unit/chatcore-executor-proxy.test.ts @@ -33,7 +33,7 @@ beforeEach(() => { after(() => { coreDb.resetDbInstance(); - fs.rmSync(testDataDir, { recursive: true, force: true }); + fs.rmSync(testDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("no config (disabled by default) returns the provider's own executor", async () => { diff --git a/tests/unit/chatcore-gamification-event.test.ts b/tests/unit/chatcore-gamification-event.test.ts index edc5afb70ccc..32405782cd9b 100644 --- a/tests/unit/chatcore-gamification-event.test.ts +++ b/tests/unit/chatcore-gamification-event.test.ts @@ -13,9 +13,8 @@ const testDataDir = fs.mkdtempSync(path.join(os.tmpdir(), "omni-gamification-tes process.env.DATA_DIR = testDataDir; const coreDb = await import("../../src/lib/db/core.ts"); -const { emitRequestGamificationEvent } = await import( - "../../open-sse/handlers/chatCore/gamificationEvent.ts" -); +const { emitRequestGamificationEvent } = + await import("../../open-sse/handlers/chatCore/gamificationEvent.ts"); function countAuditRows(apiKeyId: string): number { const row = coreDb @@ -41,7 +40,7 @@ before(async () => { after(() => { coreDb.resetDbInstance(); try { - fs.rmSync(testDataDir, { recursive: true, force: true }); + fs.rmSync(testDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // best-effort cleanup } diff --git a/tests/unit/chatcore-memory-skills-injection.test.ts b/tests/unit/chatcore-memory-skills-injection.test.ts index 5ecf886dc87b..5867efa67774 100644 --- a/tests/unit/chatcore-memory-skills-injection.test.ts +++ b/tests/unit/chatcore-memory-skills-injection.test.ts @@ -16,7 +16,7 @@ const core = await import("../../src/lib/db/core.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ─── getSkillsProviderForFormat (pure switch) ──────────────────────────────── @@ -193,7 +193,8 @@ test("injectMemoryAndSkills does not inject server memory tools for stream reque }); assert.equal(result.memorySettings?.enabled, true); - const tools = (result.body.tools as { function?: { name?: string }; name?: string }[] | undefined) ?? []; + const tools = + (result.body.tools as { function?: { name?: string }; name?: string }[] | undefined) ?? []; const toolNames = tools.map((tool) => tool.function?.name ?? tool.name); for (const memoryTool of MEMORY_BUILTIN_TOOL_NAMES) { assert.equal( @@ -230,7 +231,8 @@ test("injectMemoryAndSkills does not inject memory tools when memory is disabled log: { debug: () => {} }, }); - const tools = (result.body.tools as { function?: { name?: string }; name?: string }[] | undefined) ?? []; + const tools = + (result.body.tools as { function?: { name?: string }; name?: string }[] | undefined) ?? []; const toolNames = tools.map((tool) => tool.function?.name ?? tool.name); for (const memoryTool of MEMORY_BUILTIN_TOOL_NAMES) { assert.equal( diff --git a/tests/unit/chatcore-model-output-cap-wiring.test.ts b/tests/unit/chatcore-model-output-cap-wiring.test.ts index 9cc0042a3eb6..9aa1b128f072 100644 --- a/tests/unit/chatcore-model-output-cap-wiring.test.ts +++ b/tests/unit/chatcore-model-output-cap-wiring.test.ts @@ -80,7 +80,7 @@ test.after(() => { globalThis.fetch = originalFetch; featureFlagsDb.removeFeatureFlagOverride("DISABLE_CONTEXT_WINDOW_CHECKS"); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("handleChatCore clamps an over-cap max_tokens to the model's output cap before dispatch", async () => { diff --git a/tests/unit/chatcore-non-streaming-usage-stats.test.ts b/tests/unit/chatcore-non-streaming-usage-stats.test.ts index 0065a83a752a..766ad42dc913 100644 --- a/tests/unit/chatcore-non-streaming-usage-stats.test.ts +++ b/tests/unit/chatcore-non-streaming-usage-stats.test.ts @@ -14,9 +14,8 @@ process.env.DATA_DIR = testDataDir; const coreDb = await import("../../src/lib/db/core.ts"); const { getUsageHistory } = await import("../../src/lib/usage/usageHistory.ts"); -const { recordNonStreamingUsageStats } = await import( - "../../open-sse/handlers/chatCore/nonStreamingUsageStats.ts" -); +const { recordNonStreamingUsageStats } = + await import("../../open-sse/handlers/chatCore/nonStreamingUsageStats.ts"); function baseCtx(overrides: Record = {}) { return { @@ -54,7 +53,7 @@ before(async () => { after(() => { coreDb.resetDbInstance(); try { - fs.rmSync(testDataDir, { recursive: true, force: true }); + fs.rmSync(testDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // best-effort cleanup } diff --git a/tests/unit/chatcore-output-style-telemetry.test.ts b/tests/unit/chatcore-output-style-telemetry.test.ts index d91b3e09100d..d0db5ec27eb8 100644 --- a/tests/unit/chatcore-output-style-telemetry.test.ts +++ b/tests/unit/chatcore-output-style-telemetry.test.ts @@ -13,9 +13,8 @@ const testDataDir = fs.mkdtempSync(path.join(os.tmpdir(), "omni-os-telemetry-tes process.env.DATA_DIR = testDataDir; const coreDb = await import("../../src/lib/db/core.ts"); -const { emitOutputStyleTelemetry } = await import( - "../../open-sse/handlers/chatCore/outputStyleTelemetry.ts" -); +const { emitOutputStyleTelemetry } = + await import("../../open-sse/handlers/chatCore/outputStyleTelemetry.ts"); function rowFor(requestId: string): Record | undefined { try { @@ -47,7 +46,7 @@ before(async () => { after(() => { coreDb.resetDbInstance(); try { - fs.rmSync(testDataDir, { recursive: true, force: true }); + fs.rmSync(testDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // best-effort cleanup } @@ -71,7 +70,12 @@ test("null outputStyleResult is a no-op (returns synchronously, no throw)", asyn test("applied output-style result records a run-telemetry row (source=active-profile when combo id set)", async () => { emitOutputStyleTelemetry({ - outputStyleResult: { body: {} as never, applied: true, appliedStyles: [], skippedReason: undefined }, + outputStyleResult: { + body: {} as never, + applied: true, + appliedStyles: [], + skippedReason: undefined, + }, skillRequestId: "os-req-1", traceId: "trace-1", effectiveModel: "gpt-os", diff --git a/tests/unit/chatcore-quota-share-consumption.test.ts b/tests/unit/chatcore-quota-share-consumption.test.ts index 7bf26fc2074f..7fb08384168f 100644 --- a/tests/unit/chatcore-quota-share-consumption.test.ts +++ b/tests/unit/chatcore-quota-share-consumption.test.ts @@ -12,9 +12,8 @@ const testDataDir = fs.mkdtempSync(path.join(os.tmpdir(), "omni-quota-share-test process.env.DATA_DIR = testDataDir; const coreDb = await import("../../src/lib/db/core.ts"); -const { scheduleQuotaShareConsumption } = await import( - "../../open-sse/handlers/chatCore/quotaShareConsumption.ts" -); +const { scheduleQuotaShareConsumption } = + await import("../../open-sse/handlers/chatCore/quotaShareConsumption.ts"); const validUsage = { prompt_tokens: 10, completion_tokens: 5 }; @@ -25,7 +24,7 @@ before(async () => { after(() => { coreDb.resetDbInstance(); try { - fs.rmSync(testDataDir, { recursive: true, force: true }); + fs.rmSync(testDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // best-effort cleanup } diff --git a/tests/unit/chatcore-reasoning-cache-write-guard.test.ts b/tests/unit/chatcore-reasoning-cache-write-guard.test.ts index d53008e4549f..7113c8298e01 100644 --- a/tests/unit/chatcore-reasoning-cache-write-guard.test.ts +++ b/tests/unit/chatcore-reasoning-cache-write-guard.test.ts @@ -168,7 +168,7 @@ test.after(() => { try { clearReasoningCacheAll(); } catch {} - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("non-streaming: a replay provider (xiaomi-mimo) populates the reasoning cache", async () => { diff --git a/tests/unit/chatcore-sanitization.test.ts b/tests/unit/chatcore-sanitization.test.ts index 90e9b1e2799d..c295631baee4 100644 --- a/tests/unit/chatcore-sanitization.test.ts +++ b/tests/unit/chatcore-sanitization.test.ts @@ -152,7 +152,7 @@ test.after(() => { db.close(); } catch {} - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("chatCore sanitization normalizes max_output_tokens into max_tokens", async () => { diff --git a/tests/unit/chatcore-semantic-cache.test.ts b/tests/unit/chatcore-semantic-cache.test.ts index 032bfcf9f9c4..61b86cc4a10d 100644 --- a/tests/unit/chatcore-semantic-cache.test.ts +++ b/tests/unit/chatcore-semantic-cache.test.ts @@ -22,7 +22,7 @@ const { formatOmniRouteCost } = await import("../../src/domain/omnirouteResponse test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // A reusable persistAttemptLogs spy + base args. The functions below should NEVER be diff --git a/tests/unit/chatcore-streaming-quota-share.test.ts b/tests/unit/chatcore-streaming-quota-share.test.ts index 6acc9fde7b81..7e0cc297bf88 100644 --- a/tests/unit/chatcore-streaming-quota-share.test.ts +++ b/tests/unit/chatcore-streaming-quota-share.test.ts @@ -13,9 +13,8 @@ const testDataDir = fs.mkdtempSync(path.join(os.tmpdir(), "omni-stream-quota-tes process.env.DATA_DIR = testDataDir; const coreDb = await import("../../src/lib/db/core.ts"); -const { scheduleStreamingQuotaShareConsumption } = await import( - "../../open-sse/handlers/chatCore/streamingQuotaShare.ts" -); +const { scheduleStreamingQuotaShareConsumption } = + await import("../../open-sse/handlers/chatCore/streamingQuotaShare.ts"); function makeCostSpy() { const calls: Array<{ provider: string; model: string }> = []; @@ -40,7 +39,7 @@ before(async () => { after(() => { coreDb.resetDbInstance(); try { - fs.rmSync(testDataDir, { recursive: true, force: true }); + fs.rmSync(testDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // best-effort cleanup } diff --git a/tests/unit/chatcore-streaming-usage-stats.test.ts b/tests/unit/chatcore-streaming-usage-stats.test.ts index e5db38305332..3aac96b7a8dd 100644 --- a/tests/unit/chatcore-streaming-usage-stats.test.ts +++ b/tests/unit/chatcore-streaming-usage-stats.test.ts @@ -13,9 +13,8 @@ process.env.DATA_DIR = testDataDir; const coreDb = await import("../../src/lib/db/core.ts"); const { getUsageHistory } = await import("../../src/lib/usage/usageHistory.ts"); -const { recordStreamingUsageStats } = await import( - "../../open-sse/handlers/chatCore/streamingUsageStats.ts" -); +const { recordStreamingUsageStats } = + await import("../../open-sse/handlers/chatCore/streamingUsageStats.ts"); function baseCtx(overrides: Record = {}) { return { @@ -55,7 +54,7 @@ before(async () => { after(() => { coreDb.resetDbInstance(); try { - fs.rmSync(testDataDir, { recursive: true, force: true }); + fs.rmSync(testDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // best-effort cleanup } diff --git a/tests/unit/chatcore-telemetry-helpers.test.ts b/tests/unit/chatcore-telemetry-helpers.test.ts index e7bc72debc5b..efcef8883deb 100644 --- a/tests/unit/chatcore-telemetry-helpers.test.ts +++ b/tests/unit/chatcore-telemetry-helpers.test.ts @@ -28,7 +28,7 @@ test.afterEach(() => { test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ─── forwardDashboardEventToLiveWs ─────────────────────────────────────────── diff --git a/tests/unit/chatcore-translation-paths.test.ts b/tests/unit/chatcore-translation-paths.test.ts index 93dd323ba910..d6a660abb79a 100644 --- a/tests/unit/chatcore-translation-paths.test.ts +++ b/tests/unit/chatcore-translation-paths.test.ts @@ -322,7 +322,7 @@ async function resetStorage() { resetBackgroundStats(); globalThis.setTimeout = originalSetTimeout; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -448,7 +448,7 @@ test.after(async () => { resetAccountSemaphores(); await flushAsyncSideEffects(); await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("chatCore times out upstream execution before provider response headers", async () => { // This test asserts pendingDetail.providerRequest — only attached when the diff --git a/tests/unit/chatcore-upstream-body.test.ts b/tests/unit/chatcore-upstream-body.test.ts index 6ad6bb961674..906331ab3318 100644 --- a/tests/unit/chatcore-upstream-body.test.ts +++ b/tests/unit/chatcore-upstream-body.test.ts @@ -25,7 +25,7 @@ before(async () => { after(() => { coreDb.resetDbInstance(); - fs.rmSync(testDataDir, { recursive: true, force: true }); + fs.rmSync(testDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("pins the target model when it differs from the translated body model", async () => { diff --git a/tests/unit/chatgpt-web-codex.test.ts b/tests/unit/chatgpt-web-codex.test.ts index c7ffb6319fba..71118fd30696 100644 --- a/tests/unit/chatgpt-web-codex.test.ts +++ b/tests/unit/chatgpt-web-codex.test.ts @@ -169,7 +169,7 @@ test("turn broker holds a tool invocation and rejects wrong or duplicate results assert.throws(() => broker.completeTool(token, request.callId, { content: [] }), /not pending/); } finally { await broker.close(); - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -207,7 +207,7 @@ test("revoking a turn rejects a pending connector invocation", async () => { await assert.rejects(invocation, /revoked/); } finally { await broker.close(); - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/chatgpt-web-management-retirement.test.ts b/tests/unit/chatgpt-web-management-retirement.test.ts index 75993ebaadf5..7687168662ad 100644 --- a/tests/unit/chatgpt-web-management-retirement.test.ts +++ b/tests/unit/chatgpt-web-management-retirement.test.ts @@ -28,7 +28,7 @@ let networkCalls = 0; async function resetStorage(): Promise { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); networkCalls = 0; } @@ -58,7 +58,7 @@ test.beforeEach(resetStorage); test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("create, bulk import and validation paths reject retired provider ids with 410", async () => { diff --git a/tests/unit/chatgpt-web-runtime-block.test.ts b/tests/unit/chatgpt-web-runtime-block.test.ts index e4f69c2bf2d7..ad6e4e7e6150 100644 --- a/tests/unit/chatgpt-web-runtime-block.test.ts +++ b/tests/unit/chatgpt-web-runtime-block.test.ts @@ -31,7 +31,7 @@ function isRetiredError(error: unknown): boolean { async function resetStorage(): Promise { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); core.getDbInstance(); modelAliasResolver.invalidateAliasCache(); @@ -49,7 +49,7 @@ test.afterEach(() => { test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("retired common ChatGPT Web prefixes cannot shadow compatible nodes", async () => { diff --git a/tests/unit/check-changelog-integrity.test.ts b/tests/unit/check-changelog-integrity.test.ts index b78c1c5a82ec..cc247531bb52 100644 --- a/tests/unit/check-changelog-integrity.test.ts +++ b/tests/unit/check-changelog-integrity.test.ts @@ -139,7 +139,7 @@ test("CLI rejects an unledgered loss", () => { assert.match(result.stderr, /1 bullet\(s\).*MISSING/s); assert.doesNotMatch(result.stderr, /reporting only, not failing/); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -152,7 +152,7 @@ test("CLI fails closed when the removed legacy bypass is still configured", () = assert.match(result.stderr, /ALLOW_CHANGELOG_REMOVALS.*removed/); assert.match(result.stderr, /changelog-reconciliations\.json/); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -179,7 +179,7 @@ test("CLI accepts only an exact, reviewable ledgered reconciliation", () => { assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`); assert.match(result.stdout, /OK.*ledgered reconciliation "clarify-fix-b"/s); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -211,7 +211,7 @@ test("CLI keeps an additional loss RED after an approved result is tampered with assert.match(result.stderr, /2 bullet\(s\).*MISSING/s); assert.doesNotMatch(result.stdout, /ledgered reconciliation/); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -239,7 +239,7 @@ test("CLI rejects exact file hashes when the ledger omits one removed occurrence assert.equal(result.status, 1, `${result.stdout}\n${result.stderr}`); assert.match(result.stderr, /2 bullet\(s\).*MISSING/s); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -267,7 +267,7 @@ test("CLI rejects exact file hashes when the ledger omits one removed duplicate" assert.equal(result.status, 1, `${result.stdout}\n${result.stderr}`); assert.match(result.stderr, /2 bullet\(s\).*MISSING/s); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -294,7 +294,7 @@ test("CLI rejects exact bullet deltas when the ledger base hash is wrong", () => assert.equal(result.status, 1, `${result.stdout}\n${result.stderr}`); assert.match(result.stderr, /1 bullet\(s\).*MISSING/s); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -311,7 +311,7 @@ test("CLI validates a new fragment without treating it as a reconciliation", () assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`); assert.match(result.stdout, /OK — no base bullets lost/); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -326,7 +326,7 @@ test("CLI fails closed on a malformed reconciliation ledger", () => { assert.match(result.stderr, /invalid reconciliation ledger/); assert.match(result.stderr, /reconciliations must be an array/); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -338,6 +338,6 @@ test("CLI fails closed when an explicit base ref is unreadable", () => { assert.equal(result.status, 1, `${result.stdout}\n${result.stderr}`); assert.match(result.stderr, /FAIL.*CHANGELOG\.md.*missing-explicit-base/s); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/check-fabricated-docs.test.ts b/tests/unit/check-fabricated-docs.test.ts index bcb07a48116c..24b2378dffb8 100644 --- a/tests/unit/check-fabricated-docs.test.ts +++ b/tests/unit/check-fabricated-docs.test.ts @@ -46,7 +46,7 @@ function findingsFor(fx: Fixture): Set { } return out; } finally { - fs.rmSync(root, { recursive: true, force: true }); + fs.rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } diff --git a/tests/unit/check-openapi-breaking-ratchet.test.ts b/tests/unit/check-openapi-breaking-ratchet.test.ts index c9d1333be048..e7641c411873 100644 --- a/tests/unit/check-openapi-breaking-ratchet.test.ts +++ b/tests/unit/check-openapi-breaking-ratchet.test.ts @@ -116,7 +116,7 @@ function withTmpBaseline(content: string | null, fn: (p: string) => void) { try { fn(p); } finally { - fs.rmSync(dir, { recursive: true, force: true }); + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } diff --git a/tests/unit/check-provider-asset-provenance.test.ts b/tests/unit/check-provider-asset-provenance.test.ts index 8a834f138cb1..b4e06c9da241 100644 --- a/tests/unit/check-provider-asset-provenance.test.ts +++ b/tests/unit/check-provider-asset-provenance.test.ts @@ -180,7 +180,7 @@ test("provider asset provenance gate rejects a new physical asset without a mani /missing from manifest: public\/providers\/surprise\.svg/ ); } finally { - rmSync(fixture.root, { recursive: true, force: true }); + rmSync(fixture.root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -199,7 +199,7 @@ test("provider asset provenance gate rejects a symlink that could evade physical /non-regular provider asset entry is not allowed: public\/providers\/unregistered-link\.svg/ ); } finally { - rmSync(fixture.root, { recursive: true, force: true }); + rmSync(fixture.root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -222,7 +222,7 @@ test("provider asset provenance gate rejects a stale SHA-256", () => { /sha256 mismatch: public\/providers\/registered\.svg/ ); } finally { - rmSync(fixture.root, { recursive: true, force: true }); + rmSync(fixture.root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -246,7 +246,7 @@ test("provider asset provenance gate validates magic MIME instead of trusting th /mediaType mismatch: public\/providers\/misleading\.png \(manifest image\/png, actual image\/jpeg\)/ ); } finally { - rmSync(fixture.root, { recursive: true, force: true }); + rmSync(fixture.root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -270,7 +270,7 @@ test("provider asset provenance gate does not accept a truncated JPEG prefix", ( /mediaType mismatch: public\/providers\/truncated\.jpg \(manifest image\/jpeg, actual unknown\)/ ); } finally { - rmSync(fixture.root, { recursive: true, force: true }); + rmSync(fixture.root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -286,7 +286,7 @@ test("provider asset provenance gate recognizes an SVG with an XML doctype", () assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`); } finally { - rmSync(fixture.root, { recursive: true, force: true }); + rmSync(fixture.root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -308,7 +308,7 @@ test("provider asset provenance gate scans adversarial SVG comment chains within /mediaType mismatch: public\/providers\/adversarial\.svg \(manifest image\/svg\+xml, actual unknown\)/ ); } finally { - rmSync(fixture.root, { recursive: true, force: true }); + rmSync(fixture.root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -331,7 +331,7 @@ test("provider asset provenance gate rejects a status that implies legal clearan /invalid provenanceStatus for public\/providers\/registered\.svg: licensed/ ); } finally { - rmSync(fixture.root, { recursive: true, force: true }); + rmSync(fixture.root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -353,7 +353,7 @@ test("provider asset provenance gate requires an alias record for duplicate cont new RegExp(`duplicate content missing alias record: sha256:${sha256(SVG)}`) ); } finally { - rmSync(fixture.root, { recursive: true, force: true }); + rmSync(fixture.root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -376,7 +376,7 @@ test("provider asset provenance gate requires immutable source evidence for prov /proven asset requires immutable source evidence: public\/providers\/registered\.svg/ ); } finally { - rmSync(fixture.root, { recursive: true, force: true }); + rmSync(fixture.root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -408,7 +408,7 @@ test("provider asset provenance gate rejects malformed pinned-source integrity", /proven asset requires immutable source evidence: public\/providers\/registered\.svg/ ); } finally { - rmSync(fixture.root, { recursive: true, force: true }); + rmSync(fixture.root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -431,7 +431,7 @@ test("provider asset provenance gate rejects an unstructured upstream license cl /invalid upstreamLicenseClaim for public\/providers\/registered\.svg/ ); } finally { - rmSync(fixture.root, { recursive: true, force: true }); + rmSync(fixture.root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -464,7 +464,7 @@ test("provider asset provenance gate allows probable and unresolved records and /2\/2 registered; proven=0 probable=1 unresolved=1; duplicate-groups=1/ ); } finally { - rmSync(fixture.root, { recursive: true, force: true }); + rmSync(fixture.root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -484,7 +484,7 @@ test("provider asset provenance gate rejects a stale expected asset count", () = /expectedAssetCount mismatch: manifest 225, records 1, physical 1/ ); } finally { - rmSync(fixture.root, { recursive: true, force: true }); + rmSync(fixture.root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -509,7 +509,7 @@ test("provider asset provenance gate rejects a missing or non-commit auditedComm assert.ok(`${result.stdout}\n${result.stderr}`.includes(expectedError)); } } finally { - rmSync(fixture.root, { recursive: true, force: true }); + rmSync(fixture.root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -537,7 +537,7 @@ test("provider asset provenance gate binds auditedCommit to the physical provide /auditedCommit provider snapshot (?:is missing|differs): public\/providers\// ); } finally { - rmSync(fixture.root, { recursive: true, force: true }); + rmSync(fixture.root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/claude-classifier-compat.test.ts b/tests/unit/claude-classifier-compat.test.ts index f7f2f59950b4..90eddf11d97f 100644 --- a/tests/unit/claude-classifier-compat.test.ts +++ b/tests/unit/claude-classifier-compat.test.ts @@ -68,7 +68,7 @@ const SEVERITY_CLASSIFIER_BODY = { test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ─── Settings default is opt-in (off) ──────────────────────────────────────── diff --git a/tests/unit/claude-code-rendering-fixes.test.ts b/tests/unit/claude-code-rendering-fixes.test.ts index fe50f4e4266a..dc3c689956e2 100644 --- a/tests/unit/claude-code-rendering-fixes.test.ts +++ b/tests/unit/claude-code-rendering-fixes.test.ts @@ -18,7 +18,7 @@ test.after(() => { resetDbInstance(); if (previousDataDir === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = previousDataDir; - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("Responses->Chat: output_item.done emits arguments when no delta chunks were sent", () => { diff --git a/tests/unit/claude-directive-midconv-passthrough.test.ts b/tests/unit/claude-directive-midconv-passthrough.test.ts index ca1a95684f67..1a63771fd48e 100644 --- a/tests/unit/claude-directive-midconv-passthrough.test.ts +++ b/tests/unit/claude-directive-midconv-passthrough.test.ts @@ -30,14 +30,14 @@ test.afterEach(async () => { globalThis.fetch = originalFetch; await flushAsyncSideEffects(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); }); test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("claude mid-conversation-system passthrough relocates a directive-only messages[0]", async () => { @@ -109,9 +109,7 @@ test("claude mid-conversation-system passthrough relocates a directive-only mess // The claude identity layer prepends its own blocks; assert the client's // block survived rather than an exact count. assert.ok( - captured.body.system.some( - (block) => block.type === "text" && block.text === "You are Claude." - ) + captured.body.system.some((block) => block.type === "text" && block.text === "You are Claude.") ); assert.equal(captured.body.tools.length, 1); // The directive stays message-level; the top level (if set) is the base diff --git a/tests/unit/claude-empty-stream-error-3685.test.ts b/tests/unit/claude-empty-stream-error-3685.test.ts index 719f2cb638a2..d77b243777de 100644 --- a/tests/unit/claude-empty-stream-error-3685.test.ts +++ b/tests/unit/claude-empty-stream-error-3685.test.ts @@ -33,7 +33,12 @@ test.after(() => { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { for (const entry of fs.readdirSync(TEST_DATA_DIR)) { - fs.rmSync(path.join(TEST_DATA_DIR, entry), { recursive: true, force: true }); + fs.rmSync(path.join(TEST_DATA_DIR, entry), { + recursive: true, + force: true, + maxRetries: 5, + retryDelay: 100, + }); } } }); diff --git a/tests/unit/claudeAuthImport-bootstrap-headers-10144.test.ts b/tests/unit/claudeAuthImport-bootstrap-headers-10144.test.ts index e37373290823..ccdd53010e5f 100644 --- a/tests/unit/claudeAuthImport-bootstrap-headers-10144.test.ts +++ b/tests/unit/claudeAuthImport-bootstrap-headers-10144.test.ts @@ -14,11 +14,8 @@ process.env.APP_LOG_TO_FILE = "false"; // Import the implementation under test. In particular, do not copy any of // these helpers here: the regression must fail if claudeAuthImport.ts loses a // required header or stops persisting the device identity. -const { - createConnectionFromAuthFile, - enrichWithBootstrap, - parseAndValidateClaudeAuth, -} = await import("../../src/lib/oauth/utils/claudeAuthImport.ts"); +const { createConnectionFromAuthFile, enrichWithBootstrap, parseAndValidateClaudeAuth } = + await import("../../src/lib/oauth/utils/claudeAuthImport.ts"); import { getClaudeCodeUserAgent } from "../../src/shared/constants/claudeCodeClient.ts"; const originalFetch = globalThis.fetch; @@ -28,7 +25,7 @@ test.afterEach(() => { }); test.after(() => { - fs.rmSync(testDataDir, { recursive: true, force: true }); + fs.rmSync(testDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("real enrichWithBootstrap sends the required CLI headers", async () => { diff --git a/tests/unit/cli-api-generator-ref-params.test.ts b/tests/unit/cli-api-generator-ref-params.test.ts index be6b55975314..5f6799e6e5de 100644 --- a/tests/unit/cli-api-generator-ref-params.test.ts +++ b/tests/unit/cli-api-generator-ref-params.test.ts @@ -90,7 +90,7 @@ test("generator resolves a $ref path parameter into --id and substitutes {id} in "generated command must declare --body for the requestBody" ); } finally { - rmSync(workDir, { recursive: true, force: true }); + rmSync(workDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -126,17 +126,23 @@ components: try { assert.throws(() => runGenerator(specPath, outDir)); } finally { - rmSync(workDir, { recursive: true, force: true }); + rmSync(workDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); test("real generated bin/cli/api-commands/combos.mjs has --id and --body on the PATCH combo command (#10955)", () => { const src = readFileSync(REAL_COMBOS, "utf8"); - const patchBlockMatch = src.match(/ {2}tag\.command\("patch-[^"]*"\)[\s\S]*?\n {2}(?=tag\.command\(|\})/); + const patchBlockMatch = src.match( + / {2}tag\.command\("patch-[^"]*"\)[\s\S]*?\n {2}(?=tag\.command\(|\})/ + ); assert.ok(patchBlockMatch, "combos.mjs must have a generated patch-* command block"); const patchBlock = patchBlockMatch[0]; - assert.match(patchBlock, /\.requiredOption\("--id "/, "PATCH combo command must require --id"); + assert.match( + patchBlock, + /\.requiredOption\("--id "/, + "PATCH combo command must require --id" + ); assert.match( patchBlock, /\.option\("--body "/, diff --git a/tests/unit/cli-auth-export-command.test.ts b/tests/unit/cli-auth-export-command.test.ts index 2b3cd5ef2d43..a23d9a8af90f 100644 --- a/tests/unit/cli-auth-export-command.test.ts +++ b/tests/unit/cli-auth-export-command.test.ts @@ -62,7 +62,7 @@ async function withAuthExportEnv( new Database(dbPath).close(); await fn(dataDir, dbPath); } finally { - fs.rmSync(dataDir, { recursive: true, force: true }); + fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; diff --git a/tests/unit/cli-backup-command.test.ts b/tests/unit/cli-backup-command.test.ts index 5859e5ab40ce..d2dd9cc5c504 100644 --- a/tests/unit/cli-backup-command.test.ts +++ b/tests/unit/cli-backup-command.test.ts @@ -31,7 +31,7 @@ async function withBackupEnv(fn: (dataDir: string) => Promise) { await fn(dataDir); } finally { console.log = originalLog; - fs.rmSync(dataDir, { recursive: true, force: true }); + fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; } diff --git a/tests/unit/cli-combo-create-models-10954.test.ts b/tests/unit/cli-combo-create-models-10954.test.ts index 52fabf8c1690..66f7346ad2f1 100644 --- a/tests/unit/cli-combo-create-models-10954.test.ts +++ b/tests/unit/cli-combo-create-models-10954.test.ts @@ -40,7 +40,7 @@ async function withComboEnv(fn: (dataDir: string) => Promise) { } finally { console.log = originalLog; globalThis.fetch = ORIGINAL_FETCH; - fs.rmSync(dataDir, { recursive: true, force: true }); + fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; @@ -225,7 +225,7 @@ test("combo create (HTTP) — POST /api/combos body carries the parsed models", } finally { console.log = originalLog; globalThis.fetch = ORIGINAL_FETCH; - fs.rmSync(dataDir, { recursive: true, force: true }); + fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; } diff --git a/tests/unit/cli-contexts.test.ts b/tests/unit/cli-contexts.test.ts index 2ed45bfeba59..46b778d7c22b 100644 --- a/tests/unit/cli-contexts.test.ts +++ b/tests/unit/cli-contexts.test.ts @@ -17,7 +17,7 @@ test.after(() => { if (origDataDir === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = origDataDir; try { - rmSync(tmpDir, { recursive: true, force: true }); + rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch {} }); diff --git a/tests/unit/cli-data-dir-env-loading.test.ts b/tests/unit/cli-data-dir-env-loading.test.ts index c3d2ae34236e..52c54f47bc79 100644 --- a/tests/unit/cli-data-dir-env-loading.test.ts +++ b/tests/unit/cli-data-dir-env-loading.test.ts @@ -89,7 +89,7 @@ test("CLI data-dir resolver preserves an existing legacy ~/.omniroute before XDG } ); } finally { - fs.rmSync(tmp, { recursive: true, force: true }); + fs.rmSync(tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -152,6 +152,6 @@ test("CLI startup loads later non-conflicting .env files without overriding earl assert.equal(current.OMNIROUTE_HTTP_TIMEOUT_MS, "1234"); assert.equal(current.PORT, "34567"); } finally { - fs.rmSync(tmp, { recursive: true, force: true }); + fs.rmSync(tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/cli-data-dir-env.test.ts b/tests/unit/cli-data-dir-env.test.ts index 798177db6609..e0555a7627d0 100644 --- a/tests/unit/cli-data-dir-env.test.ts +++ b/tests/unit/cli-data-dir-env.test.ts @@ -38,7 +38,7 @@ async function withTempEnv( for (const [key, value] of Object.entries(originalEnv)) { process.env[key] = value; } - fs.rmSync(root, { recursive: true, force: true }); + fs.rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } @@ -61,8 +61,5 @@ test("CLI env loader scans all env paths while preserving first value wins", () assert.match(loaderSource, /for \(const envPath of envPaths\)/); assert.match(loaderSource, /if \(process\.env\[key\] === undefined\)/); - assert.doesNotMatch( - loaderSource, - /Loaded env from \$\{envPath\}[\s\S]{0,80}\breturn;/ - ); + assert.doesNotMatch(loaderSource, /Loaded env from \$\{envPath\}[\s\S]{0,80}\breturn;/); }); diff --git a/tests/unit/cli-doctor-command.test.ts b/tests/unit/cli-doctor-command.test.ts index d23878b76b17..f6f19eec32e8 100644 --- a/tests/unit/cli-doctor-command.test.ts +++ b/tests/unit/cli-doctor-command.test.ts @@ -48,7 +48,7 @@ async function withDoctorEnv(fn: (dataDir: string) => Promise) { try { await fn(dataDir); } finally { - fs.rmSync(dataDir, { recursive: true, force: true }); + fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; diff --git a/tests/unit/cli-doctor-prebuilt-native-binary-10083.test.ts b/tests/unit/cli-doctor-prebuilt-native-binary-10083.test.ts index 2b42faba0287..ed9c95007946 100644 --- a/tests/unit/cli-doctor-prebuilt-native-binary-10083.test.ts +++ b/tests/unit/cli-doctor-prebuilt-native-binary-10083.test.ts @@ -31,8 +31,8 @@ async function withTempRoot(fn: (rootDir: string) => Promise) { try { await fn(rootDir); } finally { - fs.rmSync(dataDir, { recursive: true, force: true }); - fs.rmSync(rootDir, { recursive: true, force: true }); + fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.rmSync(rootDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; } diff --git a/tests/unit/cli-electron-to-cli-migration-server-env-7302.test.ts b/tests/unit/cli-electron-to-cli-migration-server-env-7302.test.ts index 5f48f1a7de70..bfc65abc557c 100644 --- a/tests/unit/cli-electron-to-cli-migration-server-env-7302.test.ts +++ b/tests/unit/cli-electron-to-cli-migration-server-env-7302.test.ts @@ -37,7 +37,7 @@ function runCli(dataDir: string): { code: number | null; stdout: string; stderr: }); return { code: res.status, stdout: res.stdout ?? "", stderr: res.stderr ?? "" }; } finally { - fs.rmSync(isolatedHome, { recursive: true, force: true }); + fs.rmSync(isolatedHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } @@ -72,7 +72,8 @@ test("#7302: CLI must recognize DATA_DIR/server.env (Electron's secrets file) wh envContent, new RegExp(`STORAGE_ENCRYPTION_KEY=${electronKey}`), "the Electron-persisted STORAGE_ENCRYPTION_KEY from server.env must be honored " + - "after migrating to the CLI install — got .env content: " + JSON.stringify(envContent) + "after migrating to the CLI install — got .env content: " + + JSON.stringify(envContent) ); assert.doesNotMatch( @@ -82,7 +83,7 @@ test("#7302: CLI must recognize DATA_DIR/server.env (Electron's secrets file) wh "Electron-persisted key in server.env" ); } finally { - fs.rmSync(dir, { recursive: true, force: true }); + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -109,6 +110,6 @@ test("#7302: an existing DATA_DIR/.env must still win over DATA_DIR/server.env w "server.env must not leak into an existing .env" ); } finally { - fs.rmSync(dir, { recursive: true, force: true }); + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/cli-env-collision.test.ts b/tests/unit/cli-env-collision.test.ts index cb11ac4474f0..4447ef4fb1a2 100644 --- a/tests/unit/cli-env-collision.test.ts +++ b/tests/unit/cli-env-collision.test.ts @@ -67,7 +67,7 @@ test("a key masked by an earlier .env is named, with both files and without its assert.ok(!stderr.includes("cwd.example"), "the ignored value must never be printed"); assert.ok(!stderr.includes("data.example"), "the winning value must never be printed"); } finally { - fs.rmSync(dirs.tmp, { recursive: true, force: true }); + fs.rmSync(dirs.tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -83,7 +83,7 @@ test("a key each file declares once says nothing", () => { const stderr = runCli(dirs).stderr ?? ""; assert.ok(!/OMNIROUTE_BASE_URL|PORT/.test(stderr), `nothing to report: ${stderr}`); } finally { - fs.rmSync(dirs.tmp, { recursive: true, force: true }); + fs.rmSync(dirs.tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -103,7 +103,7 @@ test("a key the environment already set is reported too — that is #6194", () = assert.ok(!stderr.includes("shell.example"), "the winning value must never be printed"); assert.ok(!stderr.includes("data.example"), "the ignored value must never be printed"); } finally { - fs.rmSync(dirs.tmp, { recursive: true, force: true }); + fs.rmSync(dirs.tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -125,6 +125,6 @@ test("an unreadable .env is reported instead of being swallowed", () => { `the unreadable file should be named: ${result.stderr}` ); } finally { - fs.rmSync(dirs.tmp, { recursive: true, force: true }); + fs.rmSync(dirs.tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/cli-expanded-commands.test.ts b/tests/unit/cli-expanded-commands.test.ts index e631a084c63d..54ca40568d2a 100644 --- a/tests/unit/cli-expanded-commands.test.ts +++ b/tests/unit/cli-expanded-commands.test.ts @@ -124,7 +124,7 @@ test("backup auto enable — nenhuma opção é sombreada pelo parent backup", a } finally { if (origDataDir === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = origDataDir; - rmSync(dataDir, { recursive: true, force: true }); + rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -189,7 +189,7 @@ test("backup — sem subcomando ainda cria um backup (uso legado documentado)", } finally { if (origDataDir === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = origDataDir; - rmSync(dataDir, { recursive: true, force: true }); + rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -345,11 +345,14 @@ test("test-provider --all-providers consumes the connections envelope", async () assert.ok(requests.some((url) => url.includes("/api/providers?limit=200"))); const parsed = JSON.parse(output.join("")); assert.deepEqual( - parsed.map(({ provider, model }: { provider: string; model: string }) => ({ provider, model })), + parsed.map(({ provider, model }: { provider: string; model: string }) => ({ + provider, + model, + })), [ { provider: "anthropic", model: "claude" }, { provider: "gemini", model: "gemini" }, - ], + ] ); assert.ok(parsed.every(({ success }: { success: boolean }) => success)); } finally { diff --git a/tests/unit/cli-helper/config-generator-codex.test.ts b/tests/unit/cli-helper/config-generator-codex.test.ts index 7d3843db5724..af798704f3d1 100644 --- a/tests/unit/cli-helper/config-generator-codex.test.ts +++ b/tests/unit/cli-helper/config-generator-codex.test.ts @@ -31,7 +31,7 @@ function tempCodexHome(): string { after(() => { for (const dir of tmpDirs) { try { - fs.rmSync(dir, { recursive: true, force: true }); + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // best-effort cleanup } diff --git a/tests/unit/cli-helper/tool-detector-opencode-jsonc-10227.test.ts b/tests/unit/cli-helper/tool-detector-opencode-jsonc-10227.test.ts index f2039e55ef35..4812f37c9d9b 100644 --- a/tests/unit/cli-helper/tool-detector-opencode-jsonc-10227.test.ts +++ b/tests/unit/cli-helper/tool-detector-opencode-jsonc-10227.test.ts @@ -36,6 +36,6 @@ test("detectTool reports an existing opencode.jsonc as the real config path (#10 } finally { if (previousXdg === undefined) delete process.env.XDG_CONFIG_HOME; else process.env.XDG_CONFIG_HOME = previousXdg; - fs.rmSync(xdgRoot, { recursive: true, force: true }); + fs.rmSync(xdgRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/cli-ipv4-first-dns-2699.test.ts b/tests/unit/cli-ipv4-first-dns-2699.test.ts index 74bd4f33f54b..3a0633d7b563 100644 --- a/tests/unit/cli-ipv4-first-dns-2699.test.ts +++ b/tests/unit/cli-ipv4-first-dns-2699.test.ts @@ -88,6 +88,6 @@ test("ServerSupervisor starts Node with IPv4-first DNS", async () => { else process.env.DATA_DIR = previousDataDir; if (previousNodeOptions === undefined) delete process.env.NODE_OPTIONS; else process.env.NODE_OPTIONS = previousNodeOptions; - rmSync(dataDir, { recursive: true, force: true }); + rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/cli-keys-command.test.ts b/tests/unit/cli-keys-command.test.ts index c0175bb3b3a7..a2f2e73c0a0b 100644 --- a/tests/unit/cli-keys-command.test.ts +++ b/tests/unit/cli-keys-command.test.ts @@ -55,7 +55,7 @@ async function withCliKeysEnv(fn: (dataDir: string, dbPath: string) => Promise { if (origOmniLang === undefined) delete process.env.OMNIROUTE_LANG; else process.env.OMNIROUTE_LANG = origOmniLang; try { - rmSync(tmpDir, { recursive: true, force: true }); + rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch {} }); diff --git a/tests/unit/cli-logs-route.test.ts b/tests/unit/cli-logs-route.test.ts index 8dcf83316e00..4dbba108c9de 100644 --- a/tests/unit/cli-logs-route.test.ts +++ b/tests/unit/cli-logs-route.test.ts @@ -43,7 +43,7 @@ test.before(async () => { test.after(async () => { await updateSettings({ requireLogin: true }); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); try { fs.unlinkSync(logPath); } catch { diff --git a/tests/unit/cli-npm-install-runtime-allow-scripts-10713.test.ts b/tests/unit/cli-npm-install-runtime-allow-scripts-10713.test.ts index eacb1e245abe..d07ee041d9bf 100644 --- a/tests/unit/cli-npm-install-runtime-allow-scripts-10713.test.ts +++ b/tests/unit/cli-npm-install-runtime-allow-scripts-10713.test.ts @@ -34,7 +34,7 @@ test("issue #10713: npmInstallRuntime requests --allow-scripts for its own fully process.env.PATH = originalPath; if (originalDataDir === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = originalDataDir; - rmSync(fakeBinDir, { recursive: true, force: true }); - rmSync(fakeDataDir, { recursive: true, force: true }); + rmSync(fakeBinDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + rmSync(fakeDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/cli-plugin-system.test.ts b/tests/unit/cli-plugin-system.test.ts index b74fdded2922..4be5c03f72bd 100644 --- a/tests/unit/cli-plugin-system.test.ts +++ b/tests/unit/cli-plugin-system.test.ts @@ -56,7 +56,7 @@ test("discoverPlugins descobre plugin com package.json válido", async () => { if (orig === undefined) delete process.env.OMNIROUTE_PLUGIN_PATH; else process.env.OMNIROUTE_PLUGIN_PATH = orig; try { - rmSync(pluginDir, { recursive: true }); + rmSync(pluginDir, { recursive: true, maxRetries: 5, retryDelay: 100 }); } catch {} } }); @@ -83,7 +83,7 @@ test("discoverPlugins ignora pacotes sem prefixo omniroute-cmd-", async () => { if (orig === undefined) delete process.env.OMNIROUTE_PLUGIN_PATH; else process.env.OMNIROUTE_PLUGIN_PATH = orig; try { - rmSync(pluginDir, { recursive: true }); + rmSync(pluginDir, { recursive: true, maxRetries: 5, retryDelay: 100 }); } catch {} } }); @@ -115,7 +115,7 @@ test("loadPlugins não quebra CLI quando plugin tem erro de load (try/catch)", a if (orig === undefined) delete process.env.OMNIROUTE_PLUGIN_PATH; else process.env.OMNIROUTE_PLUGIN_PATH = orig; try { - rmSync(pluginDir, { recursive: true }); + rmSync(pluginDir, { recursive: true, maxRetries: 5, retryDelay: 100 }); } catch {} } }); @@ -155,7 +155,7 @@ test("loadPlugins carrega plugin válido e chama register()", async () => { if (orig === undefined) delete process.env.OMNIROUTE_PLUGIN_PATH; else process.env.OMNIROUTE_PLUGIN_PATH = orig; try { - rmSync(pluginDir, { recursive: true }); + rmSync(pluginDir, { recursive: true, maxRetries: 5, retryDelay: 100 }); } catch {} } }); diff --git a/tests/unit/cli-provider-catalog-full-10080.test.ts b/tests/unit/cli-provider-catalog-full-10080.test.ts index f7a907c6904c..3d65de7c9542 100644 --- a/tests/unit/cli-provider-catalog-full-10080.test.ts +++ b/tests/unit/cli-provider-catalog-full-10080.test.ts @@ -128,7 +128,7 @@ test("falls back to COMMON_PROVIDERS when no catalog is present", () => { assert.equal(providers.length, COMMON_PROVIDERS.length); assert.equal(providers[0].id, "openai"); } finally { - fs.rmSync(emptyRoot, { recursive: true, force: true }); + fs.rmSync(emptyRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -147,6 +147,6 @@ test("an explicit catalogPath still overrides the directory walk", () => { ["only"] ); } finally { - fs.rmSync(dir, { recursive: true, force: true }); + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/cli-provider-test-routes-10570.test.ts b/tests/unit/cli-provider-test-routes-10570.test.ts index 36b9616ff140..56319745f65f 100644 --- a/tests/unit/cli-provider-test-routes-10570.test.ts +++ b/tests/unit/cli-provider-test-routes-10570.test.ts @@ -35,7 +35,7 @@ async function withCliEnv(fn: (dataDir: string) => Promise) { await fn(dataDir); } finally { globalThis.fetch = ORIGINAL_FETCH; - fs.rmSync(dataDir, { recursive: true, force: true }); + fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; if (ORIGINAL_API_KEY === undefined) delete process.env.OMNIROUTE_API_KEY; diff --git a/tests/unit/cli-providers-command.test.ts b/tests/unit/cli-providers-command.test.ts index 4a071f4e32f9..d7b8900ed50d 100644 --- a/tests/unit/cli-providers-command.test.ts +++ b/tests/unit/cli-providers-command.test.ts @@ -32,7 +32,7 @@ async function withProvidersEnv(fn: (dataDir: string) => Promise) { try { await fn(dataDir); } finally { - fs.rmSync(dataDir, { recursive: true, force: true }); + fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); globalThis.fetch = ORIGINAL_FETCH; if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; diff --git a/tests/unit/cli-providers-rotate.test.ts b/tests/unit/cli-providers-rotate.test.ts index 2d85ed4878b4..eb74d1e7ab99 100644 --- a/tests/unit/cli-providers-rotate.test.ts +++ b/tests/unit/cli-providers-rotate.test.ts @@ -30,7 +30,7 @@ async function withEnv(fn: (dataDir: string) => Promise) { try { await fn(dataDir); } finally { - fs.rmSync(dataDir, { recursive: true, force: true }); + fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); globalThis.fetch = ORIGINAL_FETCH; if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; diff --git a/tests/unit/cli-remote-mode.test.ts b/tests/unit/cli-remote-mode.test.ts index f8d060ff6d58..a1fafa6c932f 100644 --- a/tests/unit/cli-remote-mode.test.ts +++ b/tests/unit/cli-remote-mode.test.ts @@ -44,7 +44,7 @@ test.after(() => { if (origContext === undefined) delete process.env.OMNIROUTE_CONTEXT; else process.env.OMNIROUTE_CONTEXT = origContext; try { - rmSync(tmpDir, { recursive: true, force: true }); + rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch {} }); diff --git a/tests/unit/cli-repl.test.ts b/tests/unit/cli-repl.test.ts index 741f2c238e7a..1f94618f1734 100644 --- a/tests/unit/cli-repl.test.ts +++ b/tests/unit/cli-repl.test.ts @@ -113,7 +113,7 @@ test("saveSession e loadSession persistem e restauram sessão", async () => { } finally { process.env.DATA_DIR = origDataDir ?? ""; try { - rmSync(tmpDir, { recursive: true }); + rmSync(tmpDir, { recursive: true, maxRetries: 5, retryDelay: 100 }); } catch {} } }); @@ -129,7 +129,7 @@ test("loadSession lança erro se sessão não existe", async () => { } finally { process.env.DATA_DIR = origDataDir ?? ""; try { - rmSync(tmpDir, { recursive: true }); + rmSync(tmpDir, { recursive: true, maxRetries: 5, retryDelay: 100 }); } catch {} } }); @@ -154,7 +154,7 @@ test("listSessions retorna array (vazio ou com sessões)", async () => { } finally { process.env.DATA_DIR = origDataDir ?? ""; try { - rmSync(tmpDir, { recursive: true }); + rmSync(tmpDir, { recursive: true, maxRetries: 5, retryDelay: 100 }); } catch {} } }); @@ -172,7 +172,7 @@ test("autosave não lança erro em condições normais", async () => { } finally { process.env.DATA_DIR = origDataDir ?? ""; try { - rmSync(tmpDir, { recursive: true }); + rmSync(tmpDir, { recursive: true, maxRetries: 5, retryDelay: 100 }); } catch {} } }); diff --git a/tests/unit/cli-runtime-detection.test.ts b/tests/unit/cli-runtime-detection.test.ts index 9e9205a98e25..7a76f5110d11 100644 --- a/tests/unit/cli-runtime-detection.test.ts +++ b/tests/unit/cli-runtime-detection.test.ts @@ -101,7 +101,7 @@ describe("Size threshold — checkKnownPath", () => { }); after(() => { - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); it("should detect files >= 30 bytes via env var", async () => { @@ -164,7 +164,7 @@ describe("Healthcheck — checkRunnable", () => { }); after(() => { - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); it("should report runnable=true for a script that outputs version", async () => { diff --git a/tests/unit/cli-runtime-extended.test.ts b/tests/unit/cli-runtime-extended.test.ts index 8c2ef4484570..00f69eab910a 100644 --- a/tests/unit/cli-runtime-extended.test.ts +++ b/tests/unit/cli-runtime-extended.test.ts @@ -50,7 +50,7 @@ test.afterEach(() => { restoreEnv(); for (const dir of tempDirs) { - fs.rmSync(dir as any, { recursive: true, force: true }); + fs.rmSync(dir as any, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } tempDirs.clear(); }); diff --git a/tests/unit/cli-runtime-known-path-shortcircuit-7774.test.ts b/tests/unit/cli-runtime-known-path-shortcircuit-7774.test.ts index 6551b227f700..be95b0597826 100644 --- a/tests/unit/cli-runtime-known-path-shortcircuit-7774.test.ts +++ b/tests/unit/cli-runtime-known-path-shortcircuit-7774.test.ts @@ -25,9 +25,8 @@ delete process.env.CLI_CLAUDE_BIN; delete process.env.CLI_EXTRA_PATHS; process.env.npm_config_prefix = path.join(fakeHome, "npm-prefix-unused"); -const { getCliRuntimeStatus, getKnownToolPaths } = await import( - "../../src/shared/services/cliRuntime.ts" -); +const { getCliRuntimeStatus, getKnownToolPaths } = + await import("../../src/shared/services/cliRuntime.ts"); function makeExecutable(filePath: string, content: string) { fs.writeFileSync(filePath, content); @@ -55,8 +54,8 @@ describe("#7774 — known-path short-circuit hides a genuinely runnable Claude b if (value === undefined) delete (process.env as Record)[key]; else process.env[key] = value; } - fs.rmSync(fakeHome, { recursive: true, force: true }); - fs.rmSync(realBinDir, { recursive: true, force: true }); + fs.rmSync(fakeHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.rmSync(realBinDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); it("should still find and report Claude as installed+runnable via PATH fallback", async () => { diff --git a/tests/unit/cli-runtime.test.ts b/tests/unit/cli-runtime.test.ts index 293f6a393344..65cf129a9f6b 100644 --- a/tests/unit/cli-runtime.test.ts +++ b/tests/unit/cli-runtime.test.ts @@ -17,7 +17,7 @@ test.after(() => { if (origDataDir === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = origDataDir; try { - rmSync(tmpDir, { recursive: true, force: true }); + rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch {} }); @@ -99,7 +99,12 @@ test("isBetterSqliteBinaryValid rejeita binário com magic bytes válidos mas AB false, "binário com header válido mas ABI/conteúdo incompatível deve ser inválido" ); - rmSync(join(nm, "better-sqlite3"), { recursive: true, force: true }); + rmSync(join(nm, "better-sqlite3"), { + recursive: true, + force: true, + maxRetries: 5, + retryDelay: 100, + }); }); test("isBetterSqliteBinaryValid aceita um binário nativo real e carregável", async () => { @@ -125,7 +130,12 @@ test("isBetterSqliteBinaryValid aceita um binário nativo real e carregável", a copyFileSync(realBinary, binary); const result = isBetterSqliteBinaryValid(); assert.equal(result, true, "um binário real, compatível com o Node atual, deve ser válido"); - rmSync(join(nm, "better-sqlite3"), { recursive: true, force: true }); + rmSync(join(nm, "better-sqlite3"), { + recursive: true, + force: true, + maxRetries: 5, + retryDelay: 100, + }); }); test("commands/runtime.mjs pode ser importado sem erro", async () => { diff --git a/tests/unit/cli-serve-stop-command.test.ts b/tests/unit/cli-serve-stop-command.test.ts index efde50e1867a..5955ec80bd0c 100644 --- a/tests/unit/cli-serve-stop-command.test.ts +++ b/tests/unit/cli-serve-stop-command.test.ts @@ -26,7 +26,7 @@ async function withEnv(fn: (dataDir: string) => Promise) { } finally { console.log = originalLog; globalThis.fetch = ORIGINAL_FETCH; - fs.rmSync(dataDir, { recursive: true, force: true }); + fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; diff --git a/tests/unit/cli-setup-command.test.ts b/tests/unit/cli-setup-command.test.ts index 0f433a59c14b..5eb22d9d8b1d 100644 --- a/tests/unit/cli-setup-command.test.ts +++ b/tests/unit/cli-setup-command.test.ts @@ -32,7 +32,7 @@ async function withTempEnv(fn: (dataDir: string) => Promise) { try { await fn(dataDir); } finally { - fs.rmSync(dataDir, { recursive: true, force: true }); + fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; } else { diff --git a/tests/unit/cli-setup-opencode-nested-alias-7682.test.ts b/tests/unit/cli-setup-opencode-nested-alias-7682.test.ts index 97bfdc6c8f72..39b09562b11f 100644 --- a/tests/unit/cli-setup-opencode-nested-alias-7682.test.ts +++ b/tests/unit/cli-setup-opencode-nested-alias-7682.test.ts @@ -37,6 +37,6 @@ test("config-generator/opencode.ts imports cleanly with no tsconfig.json in scop `stdout: ${result.stdout}\nstderr: ${result.stderr}` ); } finally { - rmSync(stage, { recursive: true, force: true }); + rmSync(stage, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/cli-setup-opencode.test.ts b/tests/unit/cli-setup-opencode.test.ts index ac99ced134fe..e342087e5ff3 100644 --- a/tests/unit/cli-setup-opencode.test.ts +++ b/tests/unit/cli-setup-opencode.test.ts @@ -62,7 +62,7 @@ describe("omniroute setup opencode", () => { console.info = _console.info; console.warn = _console.warn; try { - fs.rmSync(FIXTURE_ROOT, { recursive: true, force: true }); + fs.rmSync(FIXTURE_ROOT, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // best-effort temp cleanup } @@ -146,7 +146,12 @@ describe("omniroute setup opencode", () => { }); it("fails with a clear error (exit 1) when the bundled plugin dist is missing", async () => { - fs.rmSync(path.join(FAKE_PLUGIN_DIR, "dist"), { recursive: true, force: true }); + fs.rmSync(path.join(FAKE_PLUGIN_DIR, "dist"), { + recursive: true, + force: true, + maxRetries: 5, + retryDelay: 100, + }); try { const r = await runSetupOpenCodeCommand({ configDir: CONFIG_DIR, diff --git a/tests/unit/cli-sqlite-construction-fallback-8826.test.ts b/tests/unit/cli-sqlite-construction-fallback-8826.test.ts index fc9783d85bfa..f604b2ba0171 100644 --- a/tests/unit/cli-sqlite-construction-fallback-8826.test.ts +++ b/tests/unit/cli-sqlite-construction-fallback-8826.test.ts @@ -25,8 +25,7 @@ Module._load = function patchedLoad(request, parent, isMain) { if (request === "better-sqlite3") { function FakeBetterSqlite() { throw new Error( - "Could not locate the bindings file. Tried:\n" + - " -> /fake/path/better_sqlite3.node" + "Could not locate the bindings file. Tried:\n" + " -> /fake/path/better_sqlite3.node" ); } return FakeBetterSqlite; @@ -40,7 +39,9 @@ const { openOmniRouteDb } = await import("../../bin/cli/sqlite.mjs"); test("#8826: openOmniRouteDb() falls back to node:sqlite when better-sqlite3 native binding is missing (construction-time failure)", async (t) => { const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-8826-")); t.after(() => { - try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {} + try { + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + } catch {} Module._load = originalLoad; }); diff --git a/tests/unit/cli-stop-supervisor-respawn-9455.test.ts b/tests/unit/cli-stop-supervisor-respawn-9455.test.ts index badebaa3f5ad..c580c1fffdaf 100644 --- a/tests/unit/cli-stop-supervisor-respawn-9455.test.ts +++ b/tests/unit/cli-stop-supervisor-respawn-9455.test.ts @@ -140,7 +140,7 @@ test("Defect 1b: pid.mjs SERVICES array must include supervisor so killAllSubpro assert.equal(ok, true, "writePidFile('supervisor', ...) must succeed"); assert.equal(readPidFile("supervisor"), 555555); } finally { - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; } diff --git a/tests/unit/cli-storage-key-bootstrap.test.ts b/tests/unit/cli-storage-key-bootstrap.test.ts index 3664ab5e8e6c..b6b9858ecc25 100644 --- a/tests/unit/cli-storage-key-bootstrap.test.ts +++ b/tests/unit/cli-storage-key-bootstrap.test.ts @@ -41,7 +41,7 @@ function runCli(dataDir: string): { code: number | null; stderr: string } { }); return { code: res.status, stderr: res.stderr ?? "" }; } finally { - fs.rmSync(isolatedHome, { recursive: true, force: true }); + fs.rmSync(isolatedHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } @@ -63,7 +63,7 @@ test("CLI generates STORAGE_ENCRYPTION_KEY into DATA_DIR on first run (#1622)", "key persisted into DATA_DIR/.env" ); } finally { - fs.rmSync(dir, { recursive: true, force: true }); + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -79,6 +79,6 @@ test("CLI refuses to auto-generate a key when a database already exists (#1622)" assert.equal(hasKey, false, "must NOT generate a key when a DB already exists"); assert.match(stderr, /already exists/i, "must warn that a database already exists"); } finally { - fs.rmSync(dir, { recursive: true, force: true }); + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/cli-tools-apply-container-422.test.ts b/tests/unit/cli-tools-apply-container-422.test.ts index b4fc5cf6bfb8..68213de0a742 100644 --- a/tests/unit/cli-tools-apply-container-422.test.ts +++ b/tests/unit/cli-tools-apply-container-422.test.ts @@ -38,7 +38,8 @@ test.after(async () => { } catch { // the DB was never opened } - for (const dir of tempDirs) fs.rmSync(dir, { recursive: true, force: true }); + for (const dir of tempDirs) + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function applyRequest(body: Record) { diff --git a/tests/unit/cli-tools-apply-opencode-jsonc.test.ts b/tests/unit/cli-tools-apply-opencode-jsonc.test.ts index 3abdb422b4ac..69dde22adb7d 100644 --- a/tests/unit/cli-tools-apply-opencode-jsonc.test.ts +++ b/tests/unit/cli-tools-apply-opencode-jsonc.test.ts @@ -79,14 +79,15 @@ test.afterEach(async () => { if (originalAllowContainerWrite === undefined) delete process.env.OMNIROUTE_ALLOW_CONTAINER_CONFIG_WRITE; else process.env.OMNIROUTE_ALLOW_CONTAINER_CONFIG_WRITE = originalAllowContainerWrite; - for (const root of testRoots) await fs.rm(root, { recursive: true, force: true }); + for (const root of testRoots) + await fs.rm(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); testRoots.clear(); }); test.after(async () => { if (originalDataDir === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = originalDataDir; - await fs.rm(databaseRoot, { recursive: true, force: true }); + await fs.rm(databaseRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("apply writes back to the selected opencode.jsonc and does not create opencode.json (#10227)", async () => { diff --git a/tests/unit/cli-tools-crush.test.ts b/tests/unit/cli-tools-crush.test.ts index a3fca0285131..4dbdbfde5e21 100644 --- a/tests/unit/cli-tools-crush.test.ts +++ b/tests/unit/cli-tools-crush.test.ts @@ -63,7 +63,7 @@ const { GET, POST, DELETE } = await import("../../src/app/api/cli-tools/crush-se async function resetStorage() { delete process.env.INITIAL_PASSWORD; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -151,7 +151,7 @@ test("crush-settings POST: writes crush.json with an openai-compat providers.omn } } finally { process.env.HOME = origHome; - fs.rmSync(tmpHome, { recursive: true, force: true }); + fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -196,7 +196,7 @@ test("crush-settings DELETE: removes only the omniroute provider entry", async ( } } finally { process.env.HOME = origHome; - fs.rmSync(tmpHome, { recursive: true, force: true }); + fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -226,7 +226,7 @@ test("crush-settings route.ts: does not call exec() or spawn() directly", () => test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); delete process.env.DATA_DIR; delete process.env.API_KEY_SECRET; delete process.env.JWT_SECRET; diff --git a/tests/unit/cli-tools-settings-jsonc.test.ts b/tests/unit/cli-tools-settings-jsonc.test.ts index c259397ec4e1..300d24140d0e 100644 --- a/tests/unit/cli-tools-settings-jsonc.test.ts +++ b/tests/unit/cli-tools-settings-jsonc.test.ts @@ -21,9 +21,8 @@ import { promises as fs } from "node:fs"; import os from "node:os"; import path from "node:path"; -const { parseJsoncOrNull, readJsoncConfig } = await import( - "../../src/app/api/cli-tools/_lib/jsoncConfig.ts" -); +const { parseJsoncOrNull, readJsoncConfig } = + await import("../../src/app/api/cli-tools/_lib/jsoncConfig.ts"); test("parseJsoncOrNull tolerates trailing commas in objects", () => { const jsonc = `{ @@ -65,7 +64,7 @@ test("readJsoncConfig parses a JSONC file with trailing commas (regression)", as assert.equal(parsed.apiKey, "sk-test"); assert.equal(parsed.model, "claude-sonnet-4-5"); } finally { - await fs.rm(dir, { recursive: true, force: true }); + await fs.rm(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -79,7 +78,7 @@ test("readJsoncConfig returns fallback on corrupted config instead of throwing", assert.equal(await readJsoncConfig(file), null); assert.deepEqual(await readJsoncConfig(file, {}), {}); } finally { - await fs.rm(dir, { recursive: true, force: true }); + await fs.rm(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -119,9 +118,6 @@ test("cli-tools settings routes use the JSONC-tolerant reader (source-guard)", a !/JSON\.parse\(\s*content\s*\)/.test(head), `${r}: read helper still calls raw JSON.parse(content) — port the JSONC fix` ); - assert.ok( - /readJsoncConfig\s*[<(]/.test(head), - `${r}: read helper must invoke readJsoncConfig` - ); + assert.ok(/readJsoncConfig\s*[<(]/.test(head), `${r}: read helper must invoke readJsoncConfig`); } }); diff --git a/tests/unit/cli-tray-systray2.test.ts b/tests/unit/cli-tray-systray2.test.ts index 33228db41ad3..9e0ecdea396b 100644 --- a/tests/unit/cli-tray-systray2.test.ts +++ b/tests/unit/cli-tray-systray2.test.ts @@ -48,7 +48,7 @@ test("chmodSystrayBinAt sets +x on the bundled tray binary when present", () => const mode = statSync(binPath).mode & 0o777; assert.ok((mode & 0o111) !== 0, `expected exec bits on bin, got mode=${mode.toString(8)}`); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -59,7 +59,7 @@ test("chmodSystrayBinAt is a no-op when the binary doesn't exist", () => { assert.equal(result.changed, false); assert.equal(result.reason, "missing"); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -70,6 +70,6 @@ test("chmodSystrayBinAt returns missing on win32 when binary is absent (#8609)", assert.equal(result.changed, false); assert.equal(result.reason, "missing"); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/cli-tray.test.ts b/tests/unit/cli-tray.test.ts index e67022bbd22a..63dc79a25c0f 100644 --- a/tests/unit/cli-tray.test.ts +++ b/tests/unit/cli-tray.test.ts @@ -37,7 +37,7 @@ test.after(() => { if (origPath === undefined) delete process.env.PATH; else process.env.PATH = origPath; try { - rmSync(tmpDir, { recursive: true, force: true }); + rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch {} }); diff --git a/tests/unit/cli-update-global-paths-3295.test.ts b/tests/unit/cli-update-global-paths-3295.test.ts index e23a2ef3ddda..5339b5278cea 100644 --- a/tests/unit/cli-update-global-paths-3295.test.ts +++ b/tests/unit/cli-update-global-paths-3295.test.ts @@ -25,7 +25,7 @@ test("getCurrentVersion resolves the real version from a foreign cwd (#3295)", a assert.equal(version, REAL_VERSION); } finally { process.chdir(originalCwd); - rmSync(foreignCwd, { recursive: true, force: true }); + rmSync(foreignCwd, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -51,15 +51,12 @@ test("createBackup resolves bin/ from a foreign cwd and copies cli/ recursively const cliBackup = path.join(backupDir, "cli"); assert.ok(existsSync(cliBackup), "cli/ directory copied"); assert.ok(statSync(cliBackup).isDirectory(), "cli/ backup is a directory"); - assert.ok( - existsSync(path.join(cliBackup, "commands")), - "cli/ contents copied recursively" - ); + assert.ok(existsSync(path.join(cliBackup, "commands")), "cli/ contents copied recursively"); } finally { process.chdir(originalCwd); if (originalHome === undefined) delete process.env.HOME; else process.env.HOME = originalHome; - rmSync(foreignCwd, { recursive: true, force: true }); - rmSync(fakeHome, { recursive: true, force: true }); + rmSync(foreignCwd, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + rmSync(fakeHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/cli-update-shadow-install-9475.test.ts b/tests/unit/cli-update-shadow-install-9475.test.ts index f05794f211d7..7715d9a3ff92 100644 --- a/tests/unit/cli-update-shadow-install-9475.test.ts +++ b/tests/unit/cli-update-shadow-install-9475.test.ts @@ -41,6 +41,6 @@ exit 0 console.log = origLog; if (origPath === undefined) delete process.env.PATH; else process.env.PATH = origPath; - rmSync(fakeBin, { recursive: true, force: true }); + rmSync(fakeBin, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/cli/alias-resolver-7791.test.ts b/tests/unit/cli/alias-resolver-7791.test.ts index d6037796f4a0..007fd4202821 100644 --- a/tests/unit/cli/alias-resolver-7791.test.ts +++ b/tests/unit/cli/alias-resolver-7791.test.ts @@ -203,7 +203,7 @@ describe("aliasResolver.registerAliasResolver", () => { const ok = await registerAliasResolver(tmp); assert.equal(ok, false, "must return false when there is no src/ dir"); } finally { - rmSync(tmp, { recursive: true, force: true }); + rmSync(tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -214,7 +214,7 @@ describe("aliasResolver.registerAliasResolver", () => { const ok = await registerAliasResolver(tmp); assert.equal(ok, true, "must register when src/ exists"); } finally { - rmSync(tmp, { recursive: true, force: true }); + rmSync(tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/cli/autostart-linux.test.ts b/tests/unit/cli/autostart-linux.test.ts index 100d13828e14..bf2dca7bf1ba 100644 --- a/tests/unit/cli/autostart-linux.test.ts +++ b/tests/unit/cli/autostart-linux.test.ts @@ -48,7 +48,7 @@ test.after(() => { if (origPath === undefined) delete process.env.PATH; else process.env.PATH = origPath; try { - rmSync(tmpDir, { recursive: true, force: true }); + rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch {} }); diff --git a/tests/unit/cli/autostart-windows.test.ts b/tests/unit/cli/autostart-windows.test.ts index 75cbd5af8fd9..439d00ebcb4e 100644 --- a/tests/unit/cli/autostart-windows.test.ts +++ b/tests/unit/cli/autostart-windows.test.ts @@ -17,7 +17,7 @@ test.after(() => { if (origAppData === undefined) delete process.env.APPDATA; else process.env.APPDATA = origAppData; try { - rmSync(tmpDir, { recursive: true, force: true }); + rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch {} }); diff --git a/tests/unit/cli/launch-codex-windows-spawn-args.test.ts b/tests/unit/cli/launch-codex-windows-spawn-args.test.ts index 1cb5d8eb8329..d33910a8458d 100644 --- a/tests/unit/cli/launch-codex-windows-spawn-args.test.ts +++ b/tests/unit/cli/launch-codex-windows-spawn-args.test.ts @@ -126,7 +126,7 @@ test( assert.deepEqual(received, args, "child argv must match what the caller passed"); } finally { - rmSync(dir, { recursive: true, force: true }); + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } ); diff --git a/tests/unit/cli/launch-windows-spawn-args.test.ts b/tests/unit/cli/launch-windows-spawn-args.test.ts index 52dd9aa3ab1a..65abfda8049f 100644 --- a/tests/unit/cli/launch-windows-spawn-args.test.ts +++ b/tests/unit/cli/launch-windows-spawn-args.test.ts @@ -126,7 +126,7 @@ test( assert.deepEqual(received, args, "child argv must match what the caller passed"); } finally { - rmSync(dir, { recursive: true, force: true }); + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } ); diff --git a/tests/unit/cli/run-execution.test.ts b/tests/unit/cli/run-execution.test.ts index 85b41f4ad321..7549b53ef26b 100644 --- a/tests/unit/cli/run-execution.test.ts +++ b/tests/unit/cli/run-execution.test.ts @@ -66,8 +66,8 @@ process.exit(7);` if (originalPath === undefined) delete process.env.PATH; else process.env.PATH = originalPath; delete process.env.CAPTURE_PATH; - await rm(fake.dir, { recursive: true, force: true }); - await rm(capture, { recursive: true, force: true }); + await rm(fake.dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + await rm(capture, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -117,8 +117,8 @@ fs.writeFileSync(process.env.CAPTURE_PATH, JSON.stringify({ if (originalPath === undefined) delete process.env.PATH; else process.env.PATH = originalPath; delete process.env.CAPTURE_PATH; - await rm(fake.dir, { recursive: true, force: true }); - await rm(capture, { recursive: true, force: true }); + await rm(fake.dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + await rm(capture, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -164,7 +164,7 @@ fs.writeFileSync(process.env.CAPTURE_PATH, JSON.stringify({ if (originalPath === undefined) delete process.env.PATH; else process.env.PATH = originalPath; delete process.env.CAPTURE_PATH; - await rm(fake.dir, { recursive: true, force: true }); - await rm(capture, { recursive: true, force: true }); + await rm(fake.dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + await rm(capture, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/cli/setup-claude.test.ts b/tests/unit/cli/setup-claude.test.ts index 256dcbd317a1..867e62a686d7 100644 --- a/tests/unit/cli/setup-claude.test.ts +++ b/tests/unit/cli/setup-claude.test.ts @@ -109,7 +109,7 @@ test("syncClaudeProfilesFromModels falls back to a generic profile for unmatched // No effort tier for the generic fallback — effortLevel must be omitted. assert.equal("effortLevel" in json, false); } finally { - await fs.rm(claudeHome, { recursive: true, force: true }); + await fs.rm(claudeHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -137,7 +137,7 @@ test("syncClaudeProfilesFromModels writes directory-per-profile settings + threa // The auth token must never be written to disk. assert.equal(JSON.stringify(json).includes("ANTHROPIC_AUTH_TOKEN"), false); } finally { - await fs.rm(claudeHome, { recursive: true, force: true }); + await fs.rm(claudeHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -166,7 +166,7 @@ test("syncClaudeProfilesFromModels dry-run writes nothing and reports via the in // …and writes nothing to disk. await assert.rejects(fs.stat(settingsPath), /ENOENT/); } finally { - await fs.rm(claudeHome, { recursive: true, force: true }); + await fs.rm(claudeHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/cli/setup-codex.test.ts b/tests/unit/cli/setup-codex.test.ts index 2280979c8b41..176dd922a15b 100644 --- a/tests/unit/cli/setup-codex.test.ts +++ b/tests/unit/cli/setup-codex.test.ts @@ -76,6 +76,6 @@ test("syncCodexProfilesFromModels writes compatible profiles and skips media", a /ENOENT/ ); } finally { - await fs.rm(codexHome, { recursive: true, force: true }); + await fs.rm(codexHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/cli/setup-qwen.test.ts b/tests/unit/cli/setup-qwen.test.ts index b6140c76d4b2..dc8d49427ec7 100644 --- a/tests/unit/cli/setup-qwen.test.ts +++ b/tests/unit/cli/setup-qwen.test.ts @@ -64,7 +64,7 @@ test("setup-qwen writes current V4 settings and only its dedicated env key", asy assert.match(env, /^OPENAI_API_KEY=keep-me$/m); assert.match(env, /^OMNIROUTE_API_KEY="sk-qwen-dedicated"$/m); } finally { - await fs.rm(tempDir, { recursive: true, force: true }); + await fs.rm(tempDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -85,6 +85,6 @@ test("setup-qwen does not overwrite an invalid settings file", async () => { assert.equal(code, 1); assert.equal(await fs.readFile(settingsPath, "utf8"), "{ invalid JSON"); } finally { - await fs.rm(tempDir, { recursive: true, force: true }); + await fs.rm(tempDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/cliRuntime-codex-shebang-8036.test.ts b/tests/unit/cliRuntime-codex-shebang-8036.test.ts index e31fd28321d2..96ce0b5ed5a5 100644 --- a/tests/unit/cliRuntime-codex-shebang-8036.test.ts +++ b/tests/unit/cliRuntime-codex-shebang-8036.test.ts @@ -69,5 +69,5 @@ test("#8036: codex is reported runnable even when the launcher PATH omits node's }); test.after(async () => { - await fsp.rm(sandboxHome, { recursive: true, force: true }); + await fsp.rm(sandboxHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); diff --git a/tests/unit/cliRuntime-symlink-escape-7753.test.ts b/tests/unit/cliRuntime-symlink-escape-7753.test.ts index a36ba60ef42e..64a88fb46a5c 100644 --- a/tests/unit/cliRuntime-symlink-escape-7753.test.ts +++ b/tests/unit/cliRuntime-symlink-escape-7753.test.ts @@ -27,9 +27,8 @@ fs.chmodSync(realBinaryPath, 0o755); const symlinkPath = path.join(localBinDir, "opencode"); fs.symlinkSync(realBinaryPath, symlinkPath); -const { getCliRuntimeStatus, checkKnownPath } = await import( - "../../src/shared/services/cliRuntime.ts" -); +const { getCliRuntimeStatus, checkKnownPath } = + await import("../../src/shared/services/cliRuntime.ts"); test("#7753: a CLI symlink located inside an expected parent dir is wrongly reported not-installed when its resolved target escapes EXPECTED_PARENT_PATHS", async () => { const status = await getCliRuntimeStatus("opencode"); @@ -51,10 +50,10 @@ test("#7753: a genuinely unsafe symlink whose ORIGINAL location is also untruste assert.equal(result.installed, false); assert.equal(result.reason, "symlink_escape"); - await fsp.rm(untrustedDir, { recursive: true, force: true }); + await fsp.rm(untrustedDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test.after(async () => { - await fsp.rm(sandboxHome, { recursive: true, force: true }); - await fsp.rm(outsideDir, { recursive: true, force: true }); + await fsp.rm(sandboxHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + await fsp.rm(outsideDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); diff --git a/tests/unit/client-identity-profiles.test.ts b/tests/unit/client-identity-profiles.test.ts index 7328111fe484..54e00736a518 100644 --- a/tests/unit/client-identity-profiles.test.ts +++ b/tests/unit/client-identity-profiles.test.ts @@ -24,7 +24,7 @@ const core = await import("../../src/lib/db/core.ts"); test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("getClientIdentityProfileHeaders: default profile adds no headers", () => { diff --git a/tests/unit/cliproxy-auth-import-1934.test.ts b/tests/unit/cliproxy-auth-import-1934.test.ts index 2f664c9330ff..8c5a01809de5 100644 --- a/tests/unit/cliproxy-auth-import-1934.test.ts +++ b/tests/unit/cliproxy-auth-import-1934.test.ts @@ -117,7 +117,7 @@ test("scanCliProxyAuthDir reads importable files and counts skips", async () => assert.equal(candidates[0].provider, "antigravity"); assert.equal(skipped, 2); // unknown type + broken json } finally { - await fs.rm(dir, { recursive: true, force: true }); + await fs.rm(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/cliproxyapi-dedicated-credential-7645.test.ts b/tests/unit/cliproxyapi-dedicated-credential-7645.test.ts index 9a3bd2563d2a..f53906759628 100644 --- a/tests/unit/cliproxyapi-dedicated-credential-7645.test.ts +++ b/tests/unit/cliproxyapi-dedicated-credential-7645.test.ts @@ -27,12 +27,10 @@ process.env.DATA_DIR = testDataDir; const coreDb = await import("../../src/lib/db/core.ts"); const settingsDb = await import("../../src/lib/db/settings.ts"); const upstreamProxyDb = await import("../../src/lib/db/upstreamProxy.ts"); -const { resolveExecutorWithProxy } = await import( - "../../open-sse/handlers/chatCore/executorProxy.ts" -); -const { clearUpstreamProxyConfigCache } = await import( - "../../open-sse/handlers/chatCore/comboContextCache.ts" -); +const { resolveExecutorWithProxy } = + await import("../../open-sse/handlers/chatCore/executorProxy.ts"); +const { clearUpstreamProxyConfigCache } = + await import("../../open-sse/handlers/chatCore/comboContextCache.ts"); const { updateSettingsSchema } = await import("../../src/shared/validation/settingsSchemas.ts"); const NATIVE_KEY = "sk-native-provider-key-cliproxyapi-must-not-see"; @@ -50,7 +48,8 @@ afterEach(async () => { after(() => { coreDb.resetDbInstance(); - if (fs.existsSync(testDataDir)) fs.rmSync(testDataDir, { recursive: true, force: true }); + if (fs.existsSync(testDataDir)) + fs.rmSync(testDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); type ExecuteInput = { @@ -68,9 +67,7 @@ type ExecutorLike = { execute: (input: ExecuteInput) => Promise }; * simulated native-provider network failure — driving the "fallback" retry * leg for real. */ -async function withCapturedCliproxyapiRequest( - fn: () => Promise -): Promise<{ +async function withCapturedCliproxyapiRequest(fn: () => Promise): Promise<{ headers: Record; body: Record; called: boolean; @@ -185,11 +182,9 @@ describe("#7645 — CLIProxyAPI fallback leg authenticates with the dedicated ke cliproxyapiModelMapping: { [sourceModel]: mappedModel }, }); - const executor = await resolveExecutorWithProxy( - "anthropic-7645-per-connection", - undefined, - { cliproxyapiMode: "claude-native" } - ); + const executor = await resolveExecutorWithProxy("anthropic-7645-per-connection", undefined, { + cliproxyapiMode: "claude-native", + }); const { headers, body, called } = await withCapturedCliproxyapiRequest(() => (executor as ExecutorLike).execute({ diff --git a/tests/unit/cliproxyapi-fallback-wiring.test.ts b/tests/unit/cliproxyapi-fallback-wiring.test.ts index 74ad11cbb542..f60764f15adb 100644 --- a/tests/unit/cliproxyapi-fallback-wiring.test.ts +++ b/tests/unit/cliproxyapi-fallback-wiring.test.ts @@ -30,10 +30,8 @@ const upstreamProxyDb = await import("../../src/lib/db/upstreamProxy.ts"); // Import the executor module to get the real exported functions. // This may be a cached import if cliproxyapi-executor.test.ts ran first — that // is intentional; we test the live module state, not a fresh copy. -const { - clearCliproxyapiUrlCache, - resolveCliproxyapiBaseUrl, -} = await import("../../open-sse/executors/cliproxyapi.ts"); +const { clearCliproxyapiUrlCache, resolveCliproxyapiBaseUrl } = + await import("../../open-sse/executors/cliproxyapi.ts"); // ─── Helpers ───────────────────────────────────────────────────────────────── @@ -53,14 +51,14 @@ before(async () => { afterEach(() => { // Reset DB singleton so each test starts from a clean schema state. coreDb.resetDbInstance(); - fs.rmSync(testDataDir, { recursive: true, force: true }); + fs.rmSync(testDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(testDataDir, { recursive: true }); }); after(() => { coreDb.resetDbInstance(); if (fs.existsSync(testDataDir)) { - fs.rmSync(testDataDir, { recursive: true, force: true }); + fs.rmSync(testDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -121,7 +119,10 @@ describe("CLIProxyAPI fallback wiring", () => { const url2 = await resolveCliproxyapiBaseUrl(); assert.ok(url1.endsWith(":8001"), `url1 should end with :8001, got: ${url1}`); - assert.ok(url2.endsWith(":8002"), `url2 should end with :8002 after cache clear, got: ${url2}`); + assert.ok( + url2.endsWith(":8002"), + `url2 should end with :8002 after cache clear, got: ${url2}` + ); assert.notEqual(url1, url2); }); }); diff --git a/tests/unit/cliproxyapi-model-mapping-dispatch.test.ts b/tests/unit/cliproxyapi-model-mapping-dispatch.test.ts index 195299911864..09f1b9a62b40 100644 --- a/tests/unit/cliproxyapi-model-mapping-dispatch.test.ts +++ b/tests/unit/cliproxyapi-model-mapping-dispatch.test.ts @@ -37,7 +37,8 @@ afterEach(() => { after(() => { coreDb.resetDbInstance(); - if (fs.existsSync(testDataDir)) fs.rmSync(testDataDir, { recursive: true, force: true }); + if (fs.existsSync(testDataDir)) + fs.rmSync(testDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); type ExecuteInput = { diff --git a/tests/unit/cloud-agent-credentials.test.ts b/tests/unit/cloud-agent-credentials.test.ts index 75f6c592ff5f..cb09b4093ab3 100644 --- a/tests/unit/cloud-agent-credentials.test.ts +++ b/tests/unit/cloud-agent-credentials.test.ts @@ -24,7 +24,7 @@ const creds = await import("../../src/lib/cloudAgent/credentials.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("migration 061 provisions cloud_agent_credentials (table exists after DB init)", () => { diff --git a/tests/unit/cloud-agent-tasks-route-auth.test.ts b/tests/unit/cloud-agent-tasks-route-auth.test.ts index c23fe9366b9e..fbfdfce1205c 100644 --- a/tests/unit/cloud-agent-tasks-route-auth.test.ts +++ b/tests/unit/cloud-agent-tasks-route-auth.test.ts @@ -21,7 +21,7 @@ type ErrorBody = { error: { message: string } }; async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -37,7 +37,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; diff --git a/tests/unit/cloud-sync.test.ts b/tests/unit/cloud-sync.test.ts index 5137382bcfc6..6123b8b678e0 100644 --- a/tests/unit/cloud-sync.test.ts +++ b/tests/unit/cloud-sync.test.ts @@ -37,7 +37,7 @@ async function loadCloudSync(label) { async function resetStorage() { apiKeysDb.resetApiKeyState(); coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); globalThis.fetch = ORIGINAL_FETCH; delete process.env.CLOUD_URL; @@ -53,7 +53,7 @@ test.beforeEach(async () => { test.after(() => { apiKeysDb.resetApiKeyState(); coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); globalThis.fetch = ORIGINAL_FETCH; if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; @@ -131,11 +131,7 @@ test("cloudSync returns a generic error when the API responds with a non-OK stat const originalConsoleLog = console.log; const logged = []; console.log = (...args) => - logged.push( - args - .map((x) => (typeof x === "object" ? JSON.stringify(x) : String(x))) - .join(" ") - ); + logged.push(args.map((x) => (typeof x === "object" ? JSON.stringify(x) : String(x))).join(" ")); globalThis.fetch = async () => new Response("upstream unavailable", { status: 503, diff --git a/tests/unit/cloud-write-auth.test.ts b/tests/unit/cloud-write-auth.test.ts index 7a04b1e40bad..68c1ce3ca92a 100644 --- a/tests/unit/cloud-write-auth.test.ts +++ b/tests/unit/cloud-write-auth.test.ts @@ -32,7 +32,7 @@ async function resetStorage() { process.env.API_KEY_SECRET = "cloud-write-auth-api-key-secret"; core.resetDbInstance(); localDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); await localDb.updateSettings({ requireLogin: true, password: "" }); } @@ -135,7 +135,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); localDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("PUT /api/cloud/credentials/update rejects valid API key without manage/admin scope and leaves credentials unchanged", async () => { diff --git a/tests/unit/cloudflare-models-uuid-4259.test.ts b/tests/unit/cloudflare-models-uuid-4259.test.ts index e14f4bc57b45..6026b4971b19 100644 --- a/tests/unit/cloudflare-models-uuid-4259.test.ts +++ b/tests/unit/cloudflare-models-uuid-4259.test.ts @@ -21,7 +21,7 @@ const originalFetch = globalThis.fetch; async function resetStorage() { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -51,7 +51,7 @@ test.beforeEach(async () => { test.after(async () => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#4259 cloudflare-ai discovery uses the model name (slug) as id, not the UUID", async () => { diff --git a/tests/unit/cloudflaredTunnel-extended.test.ts b/tests/unit/cloudflaredTunnel-extended.test.ts index c43c5cc5e6e7..394e9a6a0c22 100644 --- a/tests/unit/cloudflaredTunnel-extended.test.ts +++ b/tests/unit/cloudflaredTunnel-extended.test.ts @@ -101,7 +101,7 @@ test.afterEach(async () => { restoreEnv(); for (const dir of tempDirs) { - await fs.rm(dir as any, { recursive: true, force: true }); + await fs.rm(dir as any, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } tempDirs.clear(); }); diff --git a/tests/unit/cloudflaredTunnel.test.ts b/tests/unit/cloudflaredTunnel.test.ts index 72a0e526a688..02a860b8a966 100644 --- a/tests/unit/cloudflaredTunnel.test.ts +++ b/tests/unit/cloudflaredTunnel.test.ts @@ -400,6 +400,6 @@ test("getCloudflaredTunnelStatus resets stale runtime state from a previous serv process.env.CLOUDFLARED_BIN = originalBinary; } - await fs.rm(tempDir, { recursive: true, force: true }); + await fs.rm(tempDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/codex-account-cooldown-write.test.ts b/tests/unit/codex-account-cooldown-write.test.ts index c24347afb76d..d2a64419b0e2 100644 --- a/tests/unit/codex-account-cooldown-write.test.ts +++ b/tests/unit/codex-account-cooldown-write.test.ts @@ -15,7 +15,7 @@ const codexFailover = await import("../../open-sse/handlers/chatCore/codexFailov async function resetStorage(): Promise { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -65,7 +65,7 @@ test.beforeEach(resetStorage); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("persisting Codex and Spark child cooldowns retains sibling and unrelated state", async () => { diff --git a/tests/unit/codex-auth-import-userid-dedup-6301.test.ts b/tests/unit/codex-auth-import-userid-dedup-6301.test.ts index 9f5046ed4dd7..ab88408957c3 100644 --- a/tests/unit/codex-auth-import-userid-dedup-6301.test.ts +++ b/tests/unit/codex-auth-import-userid-dedup-6301.test.ts @@ -71,7 +71,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error) { @@ -92,7 +92,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("parseAndValidateCodexAuth extracts userId from chatgpt_user_id claim", () => { diff --git a/tests/unit/codex-catalog-revalidation-runtime.test.ts b/tests/unit/codex-catalog-revalidation-runtime.test.ts index c7d2d78665b7..fa4b542a6c71 100644 --- a/tests/unit/codex-catalog-revalidation-runtime.test.ts +++ b/tests/unit/codex-catalog-revalidation-runtime.test.ts @@ -25,7 +25,7 @@ const originalEnv = { async function resetStorage() { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -43,7 +43,7 @@ test.beforeEach(async () => { test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); for (const [key, value] of Object.entries(originalEnv)) { if (value === undefined) delete process.env[key]; else process.env[key] = value; diff --git a/tests/unit/codex-catalog-revalidation.test.ts b/tests/unit/codex-catalog-revalidation.test.ts index e70bdbc68657..c00de6ca98d5 100644 --- a/tests/unit/codex-catalog-revalidation.test.ts +++ b/tests/unit/codex-catalog-revalidation.test.ts @@ -84,7 +84,7 @@ test("resolveCodexCatalogAppVersion uses stable, source-qualified identities", ( ); assert.equal(resolveCodexCatalogAppVersion({}, { runtimeRoot, packageVersion: null }), null); } finally { - fs.rmSync(runtimeRoot, { recursive: true, force: true }); + fs.rmSync(runtimeRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/codex-connection-defaults.test.ts b/tests/unit/codex-connection-defaults.test.ts index a05abc4803db..dbfb7e4b2211 100644 --- a/tests/unit/codex-connection-defaults.test.ts +++ b/tests/unit/codex-connection-defaults.test.ts @@ -15,7 +15,7 @@ const { migrateCodexConnectionDefaultsFromLegacySettings } = async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -25,7 +25,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("migration backfills Codex request defaults, preserves existing providerSpecificData, and is idempotent", async () => { @@ -150,8 +150,7 @@ test("migration does not treat explicit default global tier as legacy fast", asy assert.equal(firstRun.legacyFastEnabled, false); const providerSpecificData = byId.get(created.id)?.providerSpecificData as - | { requestDefaults?: unknown } - | undefined; + { requestDefaults?: unknown } | undefined; assert.deepEqual(providerSpecificData?.requestDefaults, { reasoningEffort: "medium", }); diff --git a/tests/unit/codex-connection-edit-6562.test.ts b/tests/unit/codex-connection-edit-6562.test.ts index 7b444a95cc09..a1df77a5ac88 100644 --- a/tests/unit/codex-connection-edit-6562.test.ts +++ b/tests/unit/codex-connection-edit-6562.test.ts @@ -46,7 +46,7 @@ const providerByIdRoute = await import("../../src/app/api/providers/[id]/route.t function resetDb() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -56,7 +56,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); async function createCodexConnection( diff --git a/tests/unit/codex-fingerprint-seed-persistence.test.ts b/tests/unit/codex-fingerprint-seed-persistence.test.ts index d65b7197b4a7..82b88b17b9c0 100644 --- a/tests/unit/codex-fingerprint-seed-persistence.test.ts +++ b/tests/unit/codex-fingerprint-seed-persistence.test.ts @@ -14,13 +14,13 @@ const UUID_V4_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3} async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } beforeEach(resetStorage); after(() => { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); async function createCodexOAuthConnection(providerSpecificData?: Record) { diff --git a/tests/unit/codex-gpt55-effort-routing.test.ts b/tests/unit/codex-gpt55-effort-routing.test.ts index 17b0abdb40a7..2d1aeba82e0b 100644 --- a/tests/unit/codex-gpt55-effort-routing.test.ts +++ b/tests/unit/codex-gpt55-effort-routing.test.ts @@ -41,7 +41,7 @@ test.before(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ── Defect B: suffixed bare names infer codex, not openai ───────────────────── diff --git a/tests/unit/codex-gpt55-routing-5887.test.ts b/tests/unit/codex-gpt55-routing-5887.test.ts index 5e77fcdb4c1f..d710d919e086 100644 --- a/tests/unit/codex-gpt55-routing-5887.test.ts +++ b/tests/unit/codex-gpt55-routing-5887.test.ts @@ -31,7 +31,7 @@ let openaiConnectionId: number | string | undefined; test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // (a) Codex active, OpenAI NOT active → bare gpt-5.5 must infer codex. diff --git a/tests/unit/codex-import-refresh-validation-7522.test.ts b/tests/unit/codex-import-refresh-validation-7522.test.ts index cb22a60748f0..58abfb11d5da 100644 --- a/tests/unit/codex-import-refresh-validation-7522.test.ts +++ b/tests/unit/codex-import-refresh-validation-7522.test.ts @@ -31,7 +31,7 @@ test.before(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function jsonResponse(body: unknown, status = 200) { @@ -70,7 +70,10 @@ const BASE_RECORD = { test("import: rejects a record whose refresh_token is already invalidated upstream (#7522)", async () => { await withMockedFetch( (async () => - jsonResponse({ error: { code: "refresh_token_invalidated" } }, 401)) as unknown as typeof fetch, + jsonResponse( + { error: { code: "refresh_token_invalidated" } }, + 401 + )) as unknown as typeof fetch, async () => { const { status, body } = await postImport({ accounts: BASE_RECORD }); @@ -83,7 +86,11 @@ test("import: rejects a record whose refresh_token is already invalidated upstre const rows = await providersDb.getProviderConnections({ provider: "codex" }); const created = rows.find((r) => r.email === BASE_RECORD.email); - assert.equal(created, undefined, "no connection should be persisted for a dead refresh_token"); + assert.equal( + created, + undefined, + "no connection should be persisted for a dead refresh_token" + ); } ); }); @@ -160,7 +167,11 @@ test("import: a transient network error validating the refresh_token does not bl test("import: error responses never leak a stack trace", async () => { await withMockedFetch( - (async () => jsonResponse({ error: { code: "refresh_token_invalidated" } }, 401)) as unknown as typeof fetch, + (async () => + jsonResponse( + { error: { code: "refresh_token_invalidated" } }, + 401 + )) as unknown as typeof fetch, async () => { const { body } = await postImport({ accounts: { ...BASE_RECORD, email: "leak-check@example.com" }, diff --git a/tests/unit/codex-import-token-route.test.ts b/tests/unit/codex-import-token-route.test.ts index f9bbbc4ec2be..cafc20c897a8 100644 --- a/tests/unit/codex-import-token-route.test.ts +++ b/tests/unit/codex-import-token-route.test.ts @@ -39,7 +39,7 @@ test.before(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); async function postImportToken(body: unknown) { diff --git a/tests/unit/codex-models-catalog-refresh.test.ts b/tests/unit/codex-models-catalog-refresh.test.ts index 9de940db093d..87c3522c762c 100644 --- a/tests/unit/codex-models-catalog-refresh.test.ts +++ b/tests/unit/codex-models-catalog-refresh.test.ts @@ -43,7 +43,7 @@ type CatalogResponse = { async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -54,7 +54,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("codex client (originator: codex_exec) receives a top-level `models` array so the catalog refresh decodes", async () => { diff --git a/tests/unit/codex-oauth-refresh-persist-6352.test.ts b/tests/unit/codex-oauth-refresh-persist-6352.test.ts index 12f23cd67d4a..5473a242a841 100644 --- a/tests/unit/codex-oauth-refresh-persist-6352.test.ts +++ b/tests/unit/codex-oauth-refresh-persist-6352.test.ts @@ -51,7 +51,7 @@ const { OAUTH_ENDPOINTS } = await import("../../open-sse/config/constants.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -94,7 +94,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("checkAndRefreshToken reuses the stored Codex refresh_token, persists the new access_token, rotates the refresh_token, and clears stale auth-failure state (#6352)", async () => { diff --git a/tests/unit/codex-orphaned-tool-outputs-2928.test.ts b/tests/unit/codex-orphaned-tool-outputs-2928.test.ts index 1a96a1263b68..23ab9c00aac8 100644 --- a/tests/unit/codex-orphaned-tool-outputs-2928.test.ts +++ b/tests/unit/codex-orphaned-tool-outputs-2928.test.ts @@ -36,7 +36,7 @@ function toolOutputs(input: InputItem[]): InputItem[] { test.after(() => { core.resetDbInstance(); - rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("Codex strips function_call_output items without matching function calls", () => { diff --git a/tests/unit/codex-quota-selection-hydration.test.ts b/tests/unit/codex-quota-selection-hydration.test.ts index 124c863fcd11..f3d36ec19beb 100644 --- a/tests/unit/codex-quota-selection-hydration.test.ts +++ b/tests/unit/codex-quota-selection-hydration.test.ts @@ -21,7 +21,7 @@ function futureIso(ms = 60_000) { async function resetStorage() { core.resetDbInstance(); quotaCache.__clearForTests(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -31,7 +31,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("Codex selection ignores hydrated Spark-only exhaustion for normal Codex models", async () => { diff --git a/tests/unit/codex-reset-credits.test.ts b/tests/unit/codex-reset-credits.test.ts index e9466858d2a2..2a6ecccee751 100644 --- a/tests/unit/codex-reset-credits.test.ts +++ b/tests/unit/codex-reset-credits.test.ts @@ -17,7 +17,7 @@ type QuotaUsageRecord = Record; async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -42,7 +42,7 @@ test.beforeEach(async () => { test.after(async () => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("consumeCodexResetCredit fetches a credit id, posts it, then refreshes usage", async () => { diff --git a/tests/unit/codex-responses-passthrough-strip-3317.test.ts b/tests/unit/codex-responses-passthrough-strip-3317.test.ts index 9c755e6ff764..b925eb14d7da 100644 --- a/tests/unit/codex-responses-passthrough-strip-3317.test.ts +++ b/tests/unit/codex-responses-passthrough-strip-3317.test.ts @@ -72,7 +72,7 @@ test.after(() => { /* ignore */ } try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { /* ignore */ } diff --git a/tests/unit/codex-responses-ws-fingerprint.test.ts b/tests/unit/codex-responses-ws-fingerprint.test.ts index e65cdeefb26a..77a56c1f122c 100644 --- a/tests/unit/codex-responses-ws-fingerprint.test.ts +++ b/tests/unit/codex-responses-ws-fingerprint.test.ts @@ -15,14 +15,14 @@ const { POST } = await import("../../src/app/api/internal/codex-responses-ws/rou function resetDb() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.beforeEach(resetDb); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("Codex internal websocket bridge prepare preserves original OAuth identity in off mode", async () => { diff --git a/tests/unit/codex-same-account-transport-retry-9708.test.ts b/tests/unit/codex-same-account-transport-retry-9708.test.ts index e436df008062..cca34dd347f8 100644 --- a/tests/unit/codex-same-account-transport-retry-9708.test.ts +++ b/tests/unit/codex-same-account-transport-retry-9708.test.ts @@ -24,7 +24,7 @@ const auth = await import("../../src/sse/services/auth.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -55,7 +55,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#9708: 503 connection-reset and 507 buffer errors are retryable pre-output transport", () => { diff --git a/tests/unit/codex-session-affinity-reset-aware-5903.test.ts b/tests/unit/codex-session-affinity-reset-aware-5903.test.ts index 02f4ef6f1fb0..e3aed03da336 100644 --- a/tests/unit/codex-session-affinity-reset-aware-5903.test.ts +++ b/tests/unit/codex-session-affinity-reset-aware-5903.test.ts @@ -31,7 +31,7 @@ const auth = await import("../../src/sse/services/auth.ts"); async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -56,7 +56,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("codex session affinity wins over a per-request reset-aware forcedConnectionId (#5903)", async () => { @@ -73,7 +73,11 @@ test("codex session affinity wins over a per-request reset-aware forcedConnectio sessionKey: "session-S", forcedConnectionId: connectionA.id, }); - assert.equal(request1?.connectionId, connectionA.id, "request 1 should pin to the scored winner A"); + assert.equal( + request1?.connectionId, + connectionA.id, + "request 1 should pin to the scored winner A" + ); assert.equal( affinityDb.getSessionAccountAffinity("session-S", "codex", 60_000)?.connectionId, connectionA.id, @@ -105,7 +109,11 @@ test("codex session affinity wins over a per-request reset-aware forcedConnectio sessionKey: "session-S2", forcedConnectionId: connectionB.id, }); - assert.equal(request3?.connectionId, connectionB.id, "a new session must honor the fresh re-scored pick"); + assert.equal( + request3?.connectionId, + connectionB.id, + "a new session must honor the fresh re-scored pick" + ); assert.equal( affinityDb.getSessionAccountAffinity("session-S2", "codex", 60_000)?.connectionId, connectionB.id, diff --git a/tests/unit/codex-settings-wire-api-default.test.ts b/tests/unit/codex-settings-wire-api-default.test.ts index d8dae1b1b7a9..7b7d5fd32ddb 100644 --- a/tests/unit/codex-settings-wire-api-default.test.ts +++ b/tests/unit/codex-settings-wire-api-default.test.ts @@ -44,7 +44,7 @@ const post = async (body: Record) => test.after(async () => { os.homedir = originalHome; - await fs.rm(TEST_HOME, { recursive: true, force: true }); + await fs.rm(TEST_HOME, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (originalJwtSecret === undefined) delete process.env.JWT_SECRET; else process.env.JWT_SECRET = originalJwtSecret; if (originalWriteFlag === undefined) delete process.env.CLI_ALLOW_CONFIG_WRITES; @@ -78,7 +78,7 @@ test("POST resolves the Codex wire API before URL normalization and TOML generat for (const testCase of cases) { await t.test(testCase.name, async () => { - await fs.rm(TEST_HOME, { recursive: true, force: true }); + await fs.rm(TEST_HOME, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); const response = await post(testCase.body); assert.equal(response.status, 200); diff --git a/tests/unit/codex-stream-false.test.ts b/tests/unit/codex-stream-false.test.ts index 138272d8e725..7c111c7bf690 100644 --- a/tests/unit/codex-stream-false.test.ts +++ b/tests/unit/codex-stream-false.test.ts @@ -139,7 +139,7 @@ function buildResponsesNdjson(text = "Brasilia") { async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -207,7 +207,7 @@ test.beforeEach(async () => { test.after(async () => { globalThis.fetch = originalFetch; await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("CodexExecutor.transformRequest clones the request body before forcing stream=true", () => { diff --git a/tests/unit/codex-synced-bare-model-routing.test.ts b/tests/unit/codex-synced-bare-model-routing.test.ts index 765b5a1de301..685ce7f45618 100644 --- a/tests/unit/codex-synced-bare-model-routing.test.ts +++ b/tests/unit/codex-synced-bare-model-routing.test.ts @@ -46,13 +46,13 @@ async function seedSyncedModel(provider: TestProvider, modelId: string, isActive test.beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); }); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("bare GPT-5.6 model routes through Codex when it is the only active provider", async () => { diff --git a/tests/unit/codex-ws-policy-enforcement-6564.test.ts b/tests/unit/codex-ws-policy-enforcement-6564.test.ts index 7801e27a9f40..3cf85bc3e0f1 100644 --- a/tests/unit/codex-ws-policy-enforcement-6564.test.ts +++ b/tests/unit/codex-ws-policy-enforcement-6564.test.ts @@ -71,7 +71,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: unknown) { @@ -95,7 +95,7 @@ test.after(async () => { apiKeysDb.resetApiKeyState(); costRules.resetCostData(); coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); /** Builds a bridge POST request for the internal codex-responses-ws route's "prepare" action. */ diff --git a/tests/unit/colocate-optionals.test.ts b/tests/unit/colocate-optionals.test.ts index 8250c46317e8..7c8370be33a3 100644 --- a/tests/unit/colocate-optionals.test.ts +++ b/tests/unit/colocate-optionals.test.ts @@ -53,7 +53,12 @@ function buildRoot(rootDir: string): void { }, { "dist/index.js": "export const llmlingua = true;\n" } ); - mkPkg(rootNm, "es-toolkit", { main: "index.js" }, { "index.js": "export const esToolkit = true;\n" }); + mkPkg( + rootNm, + "es-toolkit", + { main: "index.js" }, + { "index.js": "export const esToolkit = true;\n" } + ); mkPkg( rootNm, "js-tiktoken", @@ -71,12 +76,7 @@ test("computeDependencyClosure walks deps transitively and skips peers (transfor buildRoot(root); const closure = computeDependencyClosure(join(root, "node_modules")); - for (const expected of [ - "@atjsh/llmlingua-2", - "js-tiktoken", - "es-toolkit", - "base64-js", - ]) { + for (const expected of ["@atjsh/llmlingua-2", "js-tiktoken", "es-toolkit", "base64-js"]) { assert.ok(closure.includes(expected), `closure should include ${expected}`); } // The peer (declared via peerDependencies, NOT dependencies) must NOT be pulled in. @@ -85,7 +85,7 @@ test("computeDependencyClosure walks deps transitively and skips peers (transfor "closure must NOT include the transformers peer" ); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -104,12 +104,7 @@ test("colocateLlmlinguaOptionals copies the closure into dist and never clobbers } // Full closure landed in dist/node_modules. - for (const name of [ - "@atjsh/llmlingua-2", - "es-toolkit", - "js-tiktoken", - "base64-js", - ]) { + for (const name of ["@atjsh/llmlingua-2", "es-toolkit", "js-tiktoken", "base64-js"]) { assert.ok(existsSync(join(distNm, name)), `${name} should be co-located into dist`); } // The package payload came along (not just the manifest). @@ -121,7 +116,7 @@ test("colocateLlmlinguaOptionals copies the closure into dist and never clobbers ); assert.equal(distTransformers.version, "4.2.0", "dist transformers must remain 4.2.0"); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -140,7 +135,7 @@ test("colocateLlmlinguaOptionals is idempotent (second run is a no-op)", () => { assert.equal(second.reason, "already co-located"); } } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -157,7 +152,7 @@ test("colocateLlmlinguaOptionals skips when SLM optionals are not installed", () assert.equal(result.reason, "SLM optionals not installed at root"); } } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -171,7 +166,7 @@ test("colocateLlmlinguaOptionals skips when there is no standalone dist bundle", assert.equal(result.reason, "no standalone dist/node_modules"); } } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -206,7 +201,7 @@ test("colocateLlmlinguaOptionals fills a Next-traced stub (package.json only, no "the real dist/index.js must be filled in, not left missing behind the stub" ); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/combo-account-allowlist-3266.test.ts b/tests/unit/combo-account-allowlist-3266.test.ts index 57411db268b1..eddde9809a79 100644 --- a/tests/unit/combo-account-allowlist-3266.test.ts +++ b/tests/unit/combo-account-allowlist-3266.test.ts @@ -42,7 +42,7 @@ function okResponse(content: string) { async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -63,7 +63,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ── 1. Schema parse ───────────────────────────────────────────────────────── diff --git a/tests/unit/combo-attempt-body-isolation-7847.test.ts b/tests/unit/combo-attempt-body-isolation-7847.test.ts index 9373c51e7c28..36cedb67b1b3 100644 --- a/tests/unit/combo-attempt-body-isolation-7847.test.ts +++ b/tests/unit/combo-attempt-body-isolation-7847.test.ts @@ -97,7 +97,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; }); diff --git a/tests/unit/combo-auto-candidate-expansion.test.ts b/tests/unit/combo-auto-candidate-expansion.test.ts index 4cf4be0c01b1..8ae2747c4fc3 100644 --- a/tests/unit/combo-auto-candidate-expansion.test.ts +++ b/tests/unit/combo-auto-candidate-expansion.test.ts @@ -20,7 +20,7 @@ const providerModels = await import("../../open-sse/config/providerModels.ts"); function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -28,7 +28,7 @@ test.beforeEach(() => resetStorage()); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; }); diff --git a/tests/unit/combo-auto-pool-visible-only.test.ts b/tests/unit/combo-auto-pool-visible-only.test.ts index e0748ea77ba6..002140163e71 100644 --- a/tests/unit/combo-auto-pool-visible-only.test.ts +++ b/tests/unit/combo-auto-pool-visible-only.test.ts @@ -24,7 +24,7 @@ const combo = await import("../../open-sse/services/combo.ts"); function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -32,7 +32,7 @@ test.beforeEach(() => resetStorage()); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; }); @@ -98,7 +98,9 @@ test("expandAutoComboCandidatePool excludes catalog-only models (openrouter/auto ); assert.ok( - expanded.some((t) => t.provider === "openrouter" && t.modelStr === "openrouter/liquid/lfm-2.5-2.6b:free"), + expanded.some( + (t) => t.provider === "openrouter" && t.modelStr === "openrouter/liquid/lfm-2.5-2.6b:free" + ), "a synced free model must be expanded into the pool" ); }); diff --git a/tests/unit/combo-bracket-names.test.ts b/tests/unit/combo-bracket-names.test.ts index 0f4d8af0de92..96ebcb9d2a88 100644 --- a/tests/unit/combo-bracket-names.test.ts +++ b/tests/unit/combo-bracket-names.test.ts @@ -14,7 +14,7 @@ const sseModelService = await import("../../src/sse/services/model.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -24,7 +24,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("combo schemas accept names with spaces and square brackets", () => { diff --git a/tests/unit/combo-builder-effort-variants-8072.test.ts b/tests/unit/combo-builder-effort-variants-8072.test.ts index 5a1ffe7eecef..da92cbea653d 100644 --- a/tests/unit/combo-builder-effort-variants-8072.test.ts +++ b/tests/unit/combo-builder-effort-variants-8072.test.ts @@ -34,7 +34,7 @@ const { getComboBuilderOptions } = await import("../../src/lib/combos/builderOpt test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#8072 buildModelOptions: synced - effort variants appear in the Combo Builder picker and inherit the base model's metadata", async () => { diff --git a/tests/unit/combo-builder-model-source-5477.test.ts b/tests/unit/combo-builder-model-source-5477.test.ts index 230e4fe48dfb..3ca86da56f97 100644 --- a/tests/unit/combo-builder-model-source-5477.test.ts +++ b/tests/unit/combo-builder-model-source-5477.test.ts @@ -22,7 +22,7 @@ const { getComboBuilderOptions } = await import("../../src/lib/combos/builderOpt test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#5477 buildModelOptions classifies custom-model source (manual -> custom, api-sync -> imported)", async () => { diff --git a/tests/unit/combo-builder-opencode-prefix.test.ts b/tests/unit/combo-builder-opencode-prefix.test.ts index 8ce510e8be97..24cfe8762d2e 100644 --- a/tests/unit/combo-builder-opencode-prefix.test.ts +++ b/tests/unit/combo-builder-opencode-prefix.test.ts @@ -28,7 +28,7 @@ const { parseModel } = await import("../../open-sse/services/model.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#2901 no-auth OpenCode combo models use the oc/ prefix (not opencode/)", async () => { diff --git a/tests/unit/combo-builder-options-route.test.ts b/tests/unit/combo-builder-options-route.test.ts index 8353b40de2a0..9f5181b8f6df 100644 --- a/tests/unit/combo-builder-options-route.test.ts +++ b/tests/unit/combo-builder-options-route.test.ts @@ -16,7 +16,7 @@ const route = await import("../../src/app/api/combos/builder/options/route.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -51,7 +51,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("combo builder options route aggregates providers, connections, models and combo refs", async () => { diff --git a/tests/unit/combo-cache-invalidation.test.ts b/tests/unit/combo-cache-invalidation.test.ts index 5291c5c76a32..ebff45d40aa3 100644 --- a/tests/unit/combo-cache-invalidation.test.ts +++ b/tests/unit/combo-cache-invalidation.test.ts @@ -36,7 +36,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: unknown) { @@ -58,7 +58,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // Mirror the cache-validity predicate used by the handler cache layers diff --git a/tests/unit/combo-context-generic-default-10734.test.ts b/tests/unit/combo-context-generic-default-10734.test.ts index 27749b9f9ad9..78d932d22eeb 100644 --- a/tests/unit/combo-context-generic-default-10734.test.ts +++ b/tests/unit/combo-context-generic-default-10734.test.ts @@ -19,7 +19,7 @@ const catalog = await import("../../src/app/api/v1/models/catalog.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#10734: resolveTokenLimit marks the generic 128k catch-all as specific:false", () => { diff --git a/tests/unit/combo-context-length.test.ts b/tests/unit/combo-context-length.test.ts index c416b4d8be38..fb900eaee3c8 100644 --- a/tests/unit/combo-context-length.test.ts +++ b/tests/unit/combo-context-length.test.ts @@ -17,7 +17,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: any) { @@ -38,7 +38,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ─── Zod Schema Validation (createComboSchema) ─── diff --git a/tests/unit/combo-context-overflow-compression-probe.test.ts b/tests/unit/combo-context-overflow-compression-probe.test.ts index aae7f3ab2ee3..c854339c69ca 100644 --- a/tests/unit/combo-context-overflow-compression-probe.test.ts +++ b/tests/unit/combo-context-overflow-compression-probe.test.ts @@ -43,7 +43,7 @@ test.after(() => { } else { process.env.DATA_DIR = ORIGINAL_DATA_DIR; } - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test.beforeEach(() => { diff --git a/tests/unit/combo-context-prefix-resolution.test.ts b/tests/unit/combo-context-prefix-resolution.test.ts index bd586f2efc2d..8a827bc6dee4 100644 --- a/tests/unit/combo-context-prefix-resolution.test.ts +++ b/tests/unit/combo-context-prefix-resolution.test.ts @@ -38,7 +38,7 @@ const { setModelContextOverride, removeModelContextOverride } = test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("computeComboContextLength resolves a registry-known, prefixed member (glm/glm-5.2) to its real context window", () => { diff --git a/tests/unit/combo-context-relay.test.ts b/tests/unit/combo-context-relay.test.ts index 5e83b460c322..e984d7a6963b 100644 --- a/tests/unit/combo-context-relay.test.ts +++ b/tests/unit/combo-context-relay.test.ts @@ -78,7 +78,7 @@ function buildQuotaResponse(usedPercent, resetAfterSeconds = 3600) { async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -116,7 +116,7 @@ test.after(async () => { clearSessions(); globalThis.fetch = originalFetch; await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("handleComboChat context-relay routes to the first available model", async () => { diff --git a/tests/unit/combo-context-window-filter.test.ts b/tests/unit/combo-context-window-filter.test.ts index 442caf85e015..a78c3d5ba546 100644 --- a/tests/unit/combo-context-window-filter.test.ts +++ b/tests/unit/combo-context-window-filter.test.ts @@ -27,7 +27,7 @@ test.after(() => { } else { process.env.DATA_DIR = ORIGINAL_DATA_DIR; } - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test.beforeEach(() => { diff --git a/tests/unit/combo-description-5005.test.ts b/tests/unit/combo-description-5005.test.ts index c77a6a9185ab..172f121b6d64 100644 --- a/tests/unit/combo-description-5005.test.ts +++ b/tests/unit/combo-description-5005.test.ts @@ -16,16 +16,15 @@ import path from "node:path"; const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-combo-desc-5005-")); process.env.DATA_DIR = TEST_DATA_DIR; -const { createComboSchema, updateComboSchema } = await import( - "../../src/shared/validation/schemas.ts" -); +const { createComboSchema, updateComboSchema } = + await import("../../src/shared/validation/schemas.ts"); const core = await import("../../src/lib/db/core.ts"); const combosDb = await import("../../src/lib/db/combos.ts"); async function resetStorage() { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -36,7 +35,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("createComboSchema preserves description instead of stripping it", () => { diff --git a/tests/unit/combo-dispatch-prelude.test.ts b/tests/unit/combo-dispatch-prelude.test.ts index 87d35ef25dea..68307b8da250 100644 --- a/tests/unit/combo-dispatch-prelude.test.ts +++ b/tests/unit/combo-dispatch-prelude.test.ts @@ -89,7 +89,7 @@ function setup(combo: ComboInput) { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; if (ORIGINAL_API_KEY_SECRET === undefined) delete process.env.API_KEY_SECRET; diff --git a/tests/unit/combo-empty-models.test.ts b/tests/unit/combo-empty-models.test.ts index 95f32813a34c..77785f9cb50c 100644 --- a/tests/unit/combo-empty-models.test.ts +++ b/tests/unit/combo-empty-models.test.ts @@ -15,7 +15,7 @@ const core = await import("../../src/lib/db/core.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("an update cannot remove every model from a combo", () => { diff --git a/tests/unit/combo-fallback-token-estimate-7847.test.ts b/tests/unit/combo-fallback-token-estimate-7847.test.ts index 1a8e57838c2b..b4d1aeb69af7 100644 --- a/tests/unit/combo-fallback-token-estimate-7847.test.ts +++ b/tests/unit/combo-fallback-token-estimate-7847.test.ts @@ -22,7 +22,7 @@ const core = await import("../../src/lib/db/core.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; }); diff --git a/tests/unit/combo-forecast.test.ts b/tests/unit/combo-forecast.test.ts index 46bbc75c2b04..2e7af7891f28 100644 --- a/tests/unit/combo-forecast.test.ts +++ b/tests/unit/combo-forecast.test.ts @@ -23,7 +23,7 @@ const { normalizeComboStep } = await import("../../src/lib/combos/steps.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -85,7 +85,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; diff --git a/tests/unit/combo-health-dashboard.test.ts b/tests/unit/combo-health-dashboard.test.ts index 52449883bf81..41edbf7f7d6c 100644 --- a/tests/unit/combo-health-dashboard.test.ts +++ b/tests/unit/combo-health-dashboard.test.ts @@ -26,7 +26,7 @@ const { normalizeComboStep } = await import("../../src/lib/combos/steps.ts"); async function resetStorage() { comboMetrics.resetAllComboMetrics(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -120,7 +120,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; diff --git a/tests/unit/combo-health-route.test.ts b/tests/unit/combo-health-route.test.ts index 9aae08757d45..489ef8c53468 100644 --- a/tests/unit/combo-health-route.test.ts +++ b/tests/unit/combo-health-route.test.ts @@ -18,7 +18,7 @@ const { normalizeComboStep } = await import("../../src/lib/combos/steps.ts"); async function resetStorage() { comboMetrics.resetAllComboMetrics(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -29,7 +29,7 @@ test.beforeEach(async () => { test.after(() => { comboMetrics.resetAllComboMetrics(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("combo health route exposes step-level target health for structured combos", async () => { diff --git a/tests/unit/combo-hidden-leaf-routing.test.ts b/tests/unit/combo-hidden-leaf-routing.test.ts index 3c340c5b2003..87c590ec5b33 100644 --- a/tests/unit/combo-hidden-leaf-routing.test.ts +++ b/tests/unit/combo-hidden-leaf-routing.test.ts @@ -21,13 +21,13 @@ function okResponse(): Response { test.beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); }); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("handleComboChat never routes hidden leaves in priority, weighted, or round-robin combos", async () => { diff --git a/tests/unit/combo-id-resolution-4446.test.ts b/tests/unit/combo-id-resolution-4446.test.ts index 6b69a49f8f02..e714dac404c3 100644 --- a/tests/unit/combo-id-resolution-4446.test.ts +++ b/tests/unit/combo-id-resolution-4446.test.ts @@ -21,7 +21,7 @@ const sseModelService = await import("../../src/sse/services/model.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -31,7 +31,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#4446 getComboForModel resolves a combo by a case-insensitive name (lowercased slug)", async () => { diff --git a/tests/unit/combo-lockout-quota-reset-6863.test.ts b/tests/unit/combo-lockout-quota-reset-6863.test.ts index 463cd361d285..35032f4907be 100644 --- a/tests/unit/combo-lockout-quota-reset-6863.test.ts +++ b/tests/unit/combo-lockout-quota-reset-6863.test.ts @@ -30,7 +30,7 @@ test.after(() => { clearAllModelLockouts(); try { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch {} }); diff --git a/tests/unit/combo-model-name-collision-8530.test.ts b/tests/unit/combo-model-name-collision-8530.test.ts index fcdf09a827d1..f9d8da17ab07 100644 --- a/tests/unit/combo-model-name-collision-8530.test.ts +++ b/tests/unit/combo-model-name-collision-8530.test.ts @@ -35,7 +35,7 @@ interface ComboResponseBody { async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -61,7 +61,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("POST /api/combos: name colliding with a real model id is created (#6940 pattern), with a warning", async () => { diff --git a/tests/unit/combo-patch-verb.test.ts b/tests/unit/combo-patch-verb.test.ts index c72d0e23d4dd..9f35123ad549 100644 --- a/tests/unit/combo-patch-verb.test.ts +++ b/tests/unit/combo-patch-verb.test.ts @@ -13,7 +13,7 @@ const comboRoute = await import("../../src/app/api/combos/[id]/route.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function patch(id: string, body: Record) { diff --git a/tests/unit/combo-prescreen.test.ts b/tests/unit/combo-prescreen.test.ts index 63d681b9c54d..7d687227a60d 100644 --- a/tests/unit/combo-prescreen.test.ts +++ b/tests/unit/combo-prescreen.test.ts @@ -14,7 +14,7 @@ const combosDb = await import("../../src/lib/db/combos.ts"); after(() => { dbCore.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; } else { diff --git a/tests/unit/combo-priority-quota-exhaustion-cutoff-5923.test.ts b/tests/unit/combo-priority-quota-exhaustion-cutoff-5923.test.ts index 252c9cef3b2a..8cb5f018d888 100644 --- a/tests/unit/combo-priority-quota-exhaustion-cutoff-5923.test.ts +++ b/tests/unit/combo-priority-quota-exhaustion-cutoff-5923.test.ts @@ -33,7 +33,7 @@ const { getCircuitBreaker } = await import("../../src/shared/utils/circuitBreake test.after(() => { dbCore.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function makeLog() { diff --git a/tests/unit/combo-quota-exhaustion-only-fallback.test.ts b/tests/unit/combo-quota-exhaustion-only-fallback.test.ts index cbb4f70c11f4..0db3589e9972 100644 --- a/tests/unit/combo-quota-exhaustion-only-fallback.test.ts +++ b/tests/unit/combo-quota-exhaustion-only-fallback.test.ts @@ -26,7 +26,7 @@ test.after(() => { resetDbInstance(); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function log() { diff --git a/tests/unit/combo-quota-share-cooldown-wait.test.ts b/tests/unit/combo-quota-share-cooldown-wait.test.ts index ba33def47022..a803245320c8 100644 --- a/tests/unit/combo-quota-share-cooldown-wait.test.ts +++ b/tests/unit/combo-quota-share-cooldown-wait.test.ts @@ -94,7 +94,7 @@ function comboOf(strategy: string) { async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -107,7 +107,7 @@ test.after(async () => { clearAllModelLockouts(); try { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { /* best effort */ } diff --git a/tests/unit/combo-quota-token-limit.test.ts b/tests/unit/combo-quota-token-limit.test.ts index 53ecdb91c120..e93f090606b5 100644 --- a/tests/unit/combo-quota-token-limit.test.ts +++ b/tests/unit/combo-quota-token-limit.test.ts @@ -23,7 +23,7 @@ test.after(() => { else process.env.DATA_DIR = ORIGINAL_DATA_DIR; if (ORIGINAL_QUOTA_ROUTING === undefined) delete process.env.OMNIROUTE_QUOTA_AWARE_ROUTING; else process.env.OMNIROUTE_QUOTA_AWARE_ROUTING = ORIGINAL_QUOTA_ROUTING; - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("round-robin quota reservation keeps the connection token limit", async () => { diff --git a/tests/unit/combo-resource-404-health.test.ts b/tests/unit/combo-resource-404-health.test.ts index a7d318a8d33d..fa5e71a15e4b 100644 --- a/tests/unit/combo-resource-404-health.test.ts +++ b/tests/unit/combo-resource-404-health.test.ts @@ -47,7 +47,7 @@ test.after(() => { clearAllModelLockouts(); clearCooldownState(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("combo resource 404 never records model lockout or provider cooldown", async () => { diff --git a/tests/unit/combo-roundrobin-compat-fallback-6238.test.ts b/tests/unit/combo-roundrobin-compat-fallback-6238.test.ts index 8ab28e108574..0aa1a0479936 100644 --- a/tests/unit/combo-roundrobin-compat-fallback-6238.test.ts +++ b/tests/unit/combo-roundrobin-compat-fallback-6238.test.ts @@ -80,7 +80,7 @@ test.after(() => { clearModelsDevCapabilities(); settingsDb.clearAllLKGP(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; } else { diff --git a/tests/unit/combo-routes-composite-tiers.test.ts b/tests/unit/combo-routes-composite-tiers.test.ts index bb1cbc0f93ab..4d2ba5f656d4 100644 --- a/tests/unit/combo-routes-composite-tiers.test.ts +++ b/tests/unit/combo-routes-composite-tiers.test.ts @@ -14,7 +14,7 @@ const comboRoute = await import("../../src/app/api/combos/[id]/route.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -77,7 +77,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("POST /api/combos persists names with spaces and square brackets", async () => { @@ -271,7 +271,6 @@ test("PUT /api/combos preserves legacy string combo refs during normalization", assert.equal(stored.models[0].comboName, "child-ref"); }); - test("POST /api/combos returns a structured 400 for invariant violations", async () => { const response = await createRoute.POST( makeCreateRequest({ diff --git a/tests/unit/combo-routing-engine.test.ts b/tests/unit/combo-routing-engine.test.ts index aa78ceeaf62e..41e4f43d352a 100644 --- a/tests/unit/combo-routing-engine.test.ts +++ b/tests/unit/combo-routing-engine.test.ts @@ -128,7 +128,7 @@ async function cleanupTestDataDir() { for (let attempt = 0; attempt < 5; attempt += 1) { try { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); return; } catch (error: any) { lastError = error; diff --git a/tests/unit/combo-rr-diagnostics-11462.test.ts b/tests/unit/combo-rr-diagnostics-11462.test.ts index ade088e3ce0a..d9bd60f64ad3 100644 --- a/tests/unit/combo-rr-diagnostics-11462.test.ts +++ b/tests/unit/combo-rr-diagnostics-11462.test.ts @@ -12,9 +12,8 @@ const { handleComboChat } = await import("../../open-sse/services/combo.ts"); const core = await import("../../src/lib/db/core.ts"); const { resetAllComboMetrics } = await import("../../open-sse/services/comboMetrics.ts"); const { resetAllCircuitBreakers } = await import("../../src/shared/utils/circuitBreaker.ts"); -const { resetAll: resetAllSemaphores } = await import( - "../../open-sse/services/rateLimitSemaphore.ts" -); +const { resetAll: resetAllSemaphores } = + await import("../../open-sse/services/rateLimitSemaphore.ts"); function createLog() { return { info: () => {}, warn: () => {}, error: () => {}, debug: () => {} }; @@ -36,7 +35,7 @@ test.after(() => { resetAllCircuitBreakers(); resetAllSemaphores(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; }); diff --git a/tests/unit/combo-rr-fallback-advance-948.test.ts b/tests/unit/combo-rr-fallback-advance-948.test.ts index 3ff204c8db0d..2bcb462021e0 100644 --- a/tests/unit/combo-rr-fallback-advance-948.test.ts +++ b/tests/unit/combo-rr-fallback-advance-948.test.ts @@ -39,9 +39,30 @@ function rrCombo(name: string) { // per-conversation pin; stickyLimit defaults to 1 (true round-robin). config: { maxRetries: 0, disableSessionStickiness: true }, models: [ - { kind: "model", provider: "codex", providerId: "codex", model: "m-a", connectionId: "conn-A", id: `${name}-0` }, - { kind: "model", provider: "codex", providerId: "codex", model: "m-b", connectionId: "conn-B", id: `${name}-1` }, - { kind: "model", provider: "glm-cn", providerId: "glm-cn", model: "m-c", connectionId: "conn-C", id: `${name}-2` }, + { + kind: "model", + provider: "codex", + providerId: "codex", + model: "m-a", + connectionId: "conn-A", + id: `${name}-0`, + }, + { + kind: "model", + provider: "codex", + providerId: "codex", + model: "m-b", + connectionId: "conn-B", + id: `${name}-1`, + }, + { + kind: "model", + provider: "glm-cn", + providerId: "glm-cn", + model: "m-c", + connectionId: "conn-C", + id: `${name}-2`, + }, ], }; } @@ -89,7 +110,7 @@ test.after(() => { } if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#948: two consecutive requests do not reuse the fallback-served model", async () => { diff --git a/tests/unit/combo-rr-session-stickiness-3825.test.ts b/tests/unit/combo-rr-session-stickiness-3825.test.ts index 9ef5ed2d2c59..c3bcb38f0843 100644 --- a/tests/unit/combo-rr-session-stickiness-3825.test.ts +++ b/tests/unit/combo-rr-session-stickiness-3825.test.ts @@ -39,14 +39,38 @@ function rrCombo(name: string) { strategy: "round-robin", config: { maxRetries: 0 }, models: [ - { kind: "model", provider: "codex", providerId: "codex", model: "m-a", connectionId: "conn-A", id: `${name}-0` }, - { kind: "model", provider: "codex", providerId: "codex", model: "m-b", connectionId: "conn-B", id: `${name}-1` }, - { kind: "model", provider: "glm-cn", providerId: "glm-cn", model: "m-c", connectionId: "conn-C", id: `${name}-2` }, + { + kind: "model", + provider: "codex", + providerId: "codex", + model: "m-a", + connectionId: "conn-A", + id: `${name}-0`, + }, + { + kind: "model", + provider: "codex", + providerId: "codex", + model: "m-b", + connectionId: "conn-B", + id: `${name}-1`, + }, + { + kind: "model", + provider: "glm-cn", + providerId: "glm-cn", + model: "m-c", + connectionId: "conn-C", + id: `${name}-2`, + }, ], }; } -async function dispatchConnection(combo: Record, firstMessage: string): Promise { +async function dispatchConnection( + combo: Record, + firstMessage: string +): Promise { let conn = "?"; await handleComboChat({ body: { model: combo.name, messages: [{ role: "user", content: firstMessage }], stream: false }, @@ -79,7 +103,7 @@ test.beforeEach(() => { test.after(() => { stick.__setStickinessHeadroomFetcherForTests(null); dbCore.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; }); @@ -104,7 +128,10 @@ test("round-robin: DISTINCT conversations still spread across connections on tur const combo = rrCombo("rr-spread"); const hist: Record = {}; for (let i = 0; i < 6; i++) { - const conn = await dispatchConnection(combo, `conversation number ${i} — distinct first message`); + const conn = await dispatchConnection( + combo, + `conversation number ${i} — distinct first message` + ); hist[conn] = (hist[conn] || 0) + 1; } // Round-robin distribution must be preserved across conversations: more than one diff --git a/tests/unit/combo-runtime-unit-concurrency.test.ts b/tests/unit/combo-runtime-unit-concurrency.test.ts index 8ccebe4603ea..ac2695c4a107 100644 --- a/tests/unit/combo-runtime-unit-concurrency.test.ts +++ b/tests/unit/combo-runtime-unit-concurrency.test.ts @@ -63,7 +63,7 @@ test.after(() => { resetDbInstance(); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("isRuntimeUnitAtConcurrencyCap returns true for a model unit at cap", async () => { diff --git a/tests/unit/combo-scope-proxy-dead-7149.test.ts b/tests/unit/combo-scope-proxy-dead-7149.test.ts index 8d301763e95f..06e4592a6f68 100644 --- a/tests/unit/combo-scope-proxy-dead-7149.test.ts +++ b/tests/unit/combo-scope-proxy-dead-7149.test.ts @@ -22,13 +22,13 @@ type ProxyResolutionLike = { async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#7149: a proxy assigned to a Combo via the dashboard (registry scope='combo') is honored when resolving the proxy for a request routed through that combo", async () => { diff --git a/tests/unit/combo-scoring-inspector.test.ts b/tests/unit/combo-scoring-inspector.test.ts index 12062cbbe5d3..a8d4f4c9a279 100644 --- a/tests/unit/combo-scoring-inspector.test.ts +++ b/tests/unit/combo-scoring-inspector.test.ts @@ -36,7 +36,7 @@ async function resetStorage() { clearAllModelLockouts(); resetAllCircuitBreakers(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -145,7 +145,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; diff --git a/tests/unit/combo-selected-connection-success.test.ts b/tests/unit/combo-selected-connection-success.test.ts index 2e986639c713..fc4a32a4311a 100644 --- a/tests/unit/combo-selected-connection-success.test.ts +++ b/tests/unit/combo-selected-connection-success.test.ts @@ -47,7 +47,7 @@ async function cleanupTestDataDir() { for (let attempt = 0; attempt < 5; attempt += 1) { try { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); return; } catch (error: any) { lastError = error; diff --git a/tests/unit/combo-sessionless-pin-3825.test.ts b/tests/unit/combo-sessionless-pin-3825.test.ts index d8bcd2207f74..e71e90c16cd7 100644 --- a/tests/unit/combo-sessionless-pin-3825.test.ts +++ b/tests/unit/combo-sessionless-pin-3825.test.ts @@ -56,7 +56,7 @@ async function cleanupTestDataDir() { for (let attempt = 0; attempt < 5; attempt += 1) { try { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); return; } catch (error: any) { lastError = error; diff --git a/tests/unit/combo-silent-stop-gaps.test.ts b/tests/unit/combo-silent-stop-gaps.test.ts index 3b101a20c01f..5ace242f14f5 100644 --- a/tests/unit/combo-silent-stop-gaps.test.ts +++ b/tests/unit/combo-silent-stop-gaps.test.ts @@ -90,7 +90,7 @@ test.after(async () => { process.env.DATA_DIR = ORIGINAL_DATA_DIR; } try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { /* best effort */ } diff --git a/tests/unit/combo-speed-telemetry-6875.test.ts b/tests/unit/combo-speed-telemetry-6875.test.ts index 6bef6edd58e0..83ad13bf676a 100644 --- a/tests/unit/combo-speed-telemetry-6875.test.ts +++ b/tests/unit/combo-speed-telemetry-6875.test.ts @@ -36,7 +36,7 @@ const core = await import("../../src/lib/db/core.ts"); after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // --------------------------------------------------------------------------- @@ -218,9 +218,7 @@ test("buildAutoCandidates: candidate carries avgTtftMs/avgE2ELatencyMs/avgTokens // slipped past the positive() guard). assert.ok(typeof candidate!.avgTtftMs === "number" && candidate!.avgTtftMs > 0); assert.ok(typeof candidate!.avgE2ELatencyMs === "number" && candidate!.avgE2ELatencyMs > 0); - assert.ok( - typeof candidate!.avgTokensPerSecond === "number" && candidate!.avgTokensPerSecond > 0 - ); + assert.ok(typeof candidate!.avgTokensPerSecond === "number" && candidate!.avgTokensPerSecond > 0); }); test("buildAutoCandidates: a provider/model with no historical signal omits the speed-telemetry fields", async () => { diff --git a/tests/unit/combo-stickiness-responses-input-7270.test.ts b/tests/unit/combo-stickiness-responses-input-7270.test.ts index 125b551b5b80..4b68ab83569e 100644 --- a/tests/unit/combo-stickiness-responses-input-7270.test.ts +++ b/tests/unit/combo-stickiness-responses-input-7270.test.ts @@ -39,9 +39,30 @@ function rrCombo(name: string) { strategy: "round-robin", config: { maxRetries: 0 }, models: [ - { kind: "model", provider: "codex", providerId: "codex", model: "m-a", connectionId: "conn-A", id: `${name}-0` }, - { kind: "model", provider: "codex", providerId: "codex", model: "m-b", connectionId: "conn-B", id: `${name}-1` }, - { kind: "model", provider: "glm-cn", providerId: "glm-cn", model: "m-c", connectionId: "conn-C", id: `${name}-2` }, + { + kind: "model", + provider: "codex", + providerId: "codex", + model: "m-a", + connectionId: "conn-A", + id: `${name}-0`, + }, + { + kind: "model", + provider: "codex", + providerId: "codex", + model: "m-b", + connectionId: "conn-B", + id: `${name}-1`, + }, + { + kind: "model", + provider: "glm-cn", + providerId: "glm-cn", + model: "m-c", + connectionId: "conn-C", + id: `${name}-2`, + }, ], }; } @@ -92,7 +113,7 @@ test.beforeEach(() => { test.after(() => { stick.__setStickinessHeadroomFetcherForTests(null); dbCore.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; }); diff --git a/tests/unit/combo-strategies.test.ts b/tests/unit/combo-strategies.test.ts index 127a20ab8086..dd0c7be36774 100644 --- a/tests/unit/combo-strategies.test.ts +++ b/tests/unit/combo-strategies.test.ts @@ -24,7 +24,7 @@ const { saveModelsDevCapabilities } = await import("../../src/lib/modelsDevSync. after(() => { dbCore.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; } else { diff --git a/tests/unit/combo-strategy-fallbacks.test.ts b/tests/unit/combo-strategy-fallbacks.test.ts index 88792e64ccdb..1cd34ef7b6a8 100644 --- a/tests/unit/combo-strategy-fallbacks.test.ts +++ b/tests/unit/combo-strategy-fallbacks.test.ts @@ -55,7 +55,7 @@ async function cleanupTestDataDir() { for (let attempt = 0; attempt < 5; attempt += 1) { try { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); return; } catch (error: any) { lastError = error; diff --git a/tests/unit/combo-strict-random-distribution-3959.test.ts b/tests/unit/combo-strict-random-distribution-3959.test.ts index 5209b6c49c26..d4ac6590f051 100644 --- a/tests/unit/combo-strict-random-distribution-3959.test.ts +++ b/tests/unit/combo-strict-random-distribution-3959.test.ts @@ -24,9 +24,8 @@ const { handleComboChat } = await import("../../open-sse/services/combo.ts"); const core = await import("../../src/lib/db/core.ts"); const { resetAllComboMetrics } = await import("../../open-sse/services/comboMetrics.ts"); const { resetAllCircuitBreakers } = await import("../../src/shared/utils/circuitBreaker.ts"); -const { resetAll: resetAllSemaphores } = await import( - "../../open-sse/services/rateLimitSemaphore.ts" -); +const { resetAll: resetAllSemaphores } = + await import("../../open-sse/services/rateLimitSemaphore.ts"); const { _resetAllDecks } = await import("../../src/shared/utils/shuffleDeck.ts"); function createLog() { @@ -61,7 +60,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#3959 strict-random spreads the fallback across healthy peers, not a fixed model", async () => { diff --git a/tests/unit/combo-system-prompt-templates-5501.test.ts b/tests/unit/combo-system-prompt-templates-5501.test.ts index e31e14fde33b..06ff50fafd83 100644 --- a/tests/unit/combo-system-prompt-templates-5501.test.ts +++ b/tests/unit/combo-system-prompt-templates-5501.test.ts @@ -66,14 +66,20 @@ function bodyWithSystem(content: string) { return { model: "openai/gpt-4o-mini", max_tokens: 100, - messages: [{ role: "system", content }, { role: "user", content: "hi" }], + messages: [ + { role: "system", content }, + { role: "user", content: "hi" }, + ], }; } test("messages format: expands all placeholders in messages[0] system content", () => { const body = { messages: [ - { role: "system", content: "M={{MODEL_ID}} P={{PROVIDER_ID}} A={{ACCOUNT}} F={{FINGERPRINT}}" }, + { + role: "system", + content: "M={{MODEL_ID}} P={{PROVIDER_ID}} A={{ACCOUNT}} F={{FINGERPRINT}}", + }, { role: "user", content: "hi" }, ], }; @@ -113,7 +119,10 @@ test("empty value expands to empty string", () => { test("no placeholders: body unchanged (deep equal)", () => { const body = { - messages: [{ role: "system", content: "plain" }, { role: "user", content: "hi" }], + messages: [ + { role: "system", content: "plain" }, + { role: "user", content: "hi" }, + ], }; const out = expandComboSystemPromptTemplates(body, CTX); assert.deepEqual(out, body); @@ -151,7 +160,11 @@ test("resolveTargetFingerprint: non-fp provider returns null", () => { test("resolveTargetFingerprint: pinned fingerprint wins", () => { assert.equal( - resolveTargetFingerprint({ provider: "opencode", pinnedFingerprint: "pin1", executionKey: "k@fp:abc" }), + resolveTargetFingerprint({ + provider: "opencode", + pinnedFingerprint: "pin1", + executionKey: "k@fp:abc", + }), "pin1" ); }); @@ -176,7 +189,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; }); @@ -267,4 +280,4 @@ test("round-robin gate: without combo system_message, client system content stay allCombos: null, }); assert.deepEqual(seen, ["keep {{MODEL_ID}} literal"]); -}); \ No newline at end of file +}); diff --git a/tests/unit/combo-target-resolution-split.test.ts b/tests/unit/combo-target-resolution-split.test.ts index 92d95f570a6e..9f80ff17fda7 100644 --- a/tests/unit/combo-target-resolution-split.test.ts +++ b/tests/unit/combo-target-resolution-split.test.ts @@ -30,7 +30,7 @@ test.after(() => { } else { process.env.DATA_DIR = ORIGINAL_DATA_DIR; } - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test.beforeEach(() => { diff --git a/tests/unit/combo-test-route.test.ts b/tests/unit/combo-test-route.test.ts index 022732e02c6a..b70f11fd94c5 100644 --- a/tests/unit/combo-test-route.test.ts +++ b/tests/unit/combo-test-route.test.ts @@ -19,7 +19,7 @@ const originalFetch = globalThis.fetch; async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -55,7 +55,7 @@ test.afterEach(() => { test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("combo test route validates request payloads and combo existence", async () => { diff --git a/tests/unit/combo-vision-aware-routing.test.ts b/tests/unit/combo-vision-aware-routing.test.ts index 8d5c186c3fc9..d77da16bb27a 100644 --- a/tests/unit/combo-vision-aware-routing.test.ts +++ b/tests/unit/combo-vision-aware-routing.test.ts @@ -42,7 +42,7 @@ const { deriveRequestCompatibilityRequirements, hasHardCapabilityFailure } = test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // --- Part A: capability resolution ----------------------------------------- diff --git a/tests/unit/combo/combo-failure-tracker-session-isolation.test.ts b/tests/unit/combo/combo-failure-tracker-session-isolation.test.ts index 7db23c6ce551..5db461967684 100644 --- a/tests/unit/combo/combo-failure-tracker-session-isolation.test.ts +++ b/tests/unit/combo/combo-failure-tracker-session-isolation.test.ts @@ -32,7 +32,7 @@ const failureTracker = await import("../../../open-sse/services/combo/failureTra test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("recordComboFailure clears only the failing session's pin, leaving other sessions on the same combo untouched", () => { diff --git a/tests/unit/combo/connection-aware-expansion.test.ts b/tests/unit/combo/connection-aware-expansion.test.ts index 6dbd2a3f4a6b..1c4a47bfb302 100644 --- a/tests/unit/combo/connection-aware-expansion.test.ts +++ b/tests/unit/combo/connection-aware-expansion.test.ts @@ -55,7 +55,7 @@ function makeTarget(overrides: Record = {}) { test.after(() => { coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // Gate: strategy + config resolution diff --git a/tests/unit/combo/image-combo.test.ts b/tests/unit/combo/image-combo.test.ts index d122dace1083..d455875b969d 100644 --- a/tests/unit/combo/image-combo.test.ts +++ b/tests/unit/combo/image-combo.test.ts @@ -62,7 +62,7 @@ async function cleanupTestDataDir() { for (let attempt = 0; attempt < 5; attempt += 1) { try { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); return; } catch (error: unknown) { lastError = error; @@ -220,7 +220,10 @@ test("non-combo bare model names pass through model resolution unchanged", async assert.equal(response.status, 400); const body = await response.json(); const bodyStr = JSON.stringify(body); - assert.ok(bodyStr.includes("not found") || bodyStr.includes("not a valid"), "Combo not found error"); + assert.ok( + bodyStr.includes("not found") || bodyStr.includes("not a valid"), + "Combo not found error" + ); }); test("provider/model format (with slash) is not treated as a combo name", async () => { @@ -279,4 +282,4 @@ test("all error responses from executeImageCombo sanitize stack traces", async ( `Scenario "${scenario.name}" does not leak stack traces` ); } -}); \ No newline at end of file +}); diff --git a/tests/unit/combo/reset-window-strategy-9330.test.ts b/tests/unit/combo/reset-window-strategy-9330.test.ts index 30bfcd43d094..648505656467 100644 --- a/tests/unit/combo/reset-window-strategy-9330.test.ts +++ b/tests/unit/combo/reset-window-strategy-9330.test.ts @@ -42,7 +42,7 @@ const { registerQuotaFetcher } = await import("../../../open-sse/services/quotaP after(() => { dbCore.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; } else { diff --git a/tests/unit/combo/speech-combo.test.ts b/tests/unit/combo/speech-combo.test.ts index 4bd415d8307d..897299092592 100644 --- a/tests/unit/combo/speech-combo.test.ts +++ b/tests/unit/combo/speech-combo.test.ts @@ -27,7 +27,7 @@ async function cleanupTestDataDir() { for (let attempt = 0; attempt < 5; attempt += 1) { try { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); return; } catch (error: unknown) { lastError = error; diff --git a/tests/unit/combo/strict-context-failopen-8786.test.ts b/tests/unit/combo/strict-context-failopen-8786.test.ts index 68a1e8c75cf0..29fdb48cc398 100644 --- a/tests/unit/combo/strict-context-failopen-8786.test.ts +++ b/tests/unit/combo/strict-context-failopen-8786.test.ts @@ -22,13 +22,12 @@ process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../../src/lib/db/core.ts"); const { getModelContextLimit } = await import("../../../src/lib/modelCapabilities.ts"); -const { applyContextRequirements } = await import( - "../../../open-sse/services/combo/contextRequirements.ts" -); +const { applyContextRequirements } = + await import("../../../open-sse/services/combo/contextRequirements.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function target(provider: string, modelStr: string) { diff --git a/tests/unit/combo/video-combo.test.ts b/tests/unit/combo/video-combo.test.ts index d429ada93092..bb280b161fa7 100644 --- a/tests/unit/combo/video-combo.test.ts +++ b/tests/unit/combo/video-combo.test.ts @@ -59,7 +59,7 @@ async function cleanupTestDataDir() { for (let attempt = 0; attempt < 5; attempt += 1) { try { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); return; } catch (error: unknown) { lastError = error; diff --git a/tests/unit/combos-duplicate-resolution-audit.test.ts b/tests/unit/combos-duplicate-resolution-audit.test.ts index ea304e8a8573..78d7bf21c6f7 100644 --- a/tests/unit/combos-duplicate-resolution-audit.test.ts +++ b/tests/unit/combos-duplicate-resolution-audit.test.ts @@ -22,7 +22,7 @@ const { resolveBuiltinAutoSpec } = test.after(() => { core.resetDbInstance(); try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch {} }); diff --git a/tests/unit/combos-duplicate-route.test.ts b/tests/unit/combos-duplicate-route.test.ts index 447c26583b68..5e957d8a9ed4 100644 --- a/tests/unit/combos-duplicate-route.test.ts +++ b/tests/unit/combos-duplicate-route.test.ts @@ -38,7 +38,7 @@ function makePostRequest(url: string, body: unknown, apiKey?: string): Request { test.after(() => { core.resetDbInstance(); try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // best-effort cleanup } diff --git a/tests/unit/combos-quota-protected.test.ts b/tests/unit/combos-quota-protected.test.ts index cf2c4ad6ceca..63de82ce00e8 100644 --- a/tests/unit/combos-quota-protected.test.ts +++ b/tests/unit/combos-quota-protected.test.ts @@ -13,7 +13,7 @@ const comboRoute = await import("../../src/app/api/combos/[id]/route.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -37,7 +37,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ---- quota-protected combos ---- diff --git a/tests/unit/command-code-auth-assist.test.ts b/tests/unit/command-code-auth-assist.test.ts index f8ca2c8a669e..0972d8a981be 100644 --- a/tests/unit/command-code-auth-assist.test.ts +++ b/tests/unit/command-code-auth-assist.test.ts @@ -19,7 +19,7 @@ const providersDb = await import("../../src/lib/db/providers.ts"); function resetDb() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -42,7 +42,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("Command Code auth assist start/callback/status/apply keeps state hash and key private", async () => { diff --git a/tests/unit/command-code-executor.test.ts b/tests/unit/command-code-executor.test.ts index 247aab2dea51..01ee533af312 100644 --- a/tests/unit/command-code-executor.test.ts +++ b/tests/unit/command-code-executor.test.ts @@ -72,7 +72,7 @@ test.afterEach(() => { test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("Command Code provider catalog has pinned models and alias lookup", () => { @@ -232,7 +232,9 @@ test("Command Code executor passes the upstream OpenAI SSE stream through untouc }); }; - const { response } = await (await getExecutor("command-code")).execute({ + const { response } = await ( + await getExecutor("command-code") + ).execute({ model: "gpt-5.4", stream: true, credentials: { apiKey: "cc_test_key" }, @@ -268,7 +270,9 @@ test("Command Code executor passes the upstream OpenAI JSON through untouched (n }); }; - const { response } = await (await getExecutor("command-code")).execute({ + const { response } = await ( + await getExecutor("command-code") + ).execute({ model: "gpt-5.4-mini", stream: false, credentials: { apiKey: "cc_test_key" }, @@ -280,8 +284,11 @@ test("Command Code executor passes the upstream OpenAI JSON through untouched (n }); test("Command Code executor surfaces upstream errors", async () => { - globalThis.fetch = async () => new Response("bad key", { status: 401, statusText: "Unauthorized" }); - const upstreamFailure = await (await getExecutor("command-code")).execute({ + globalThis.fetch = async () => + new Response("bad key", { status: 401, statusText: "Unauthorized" }); + const upstreamFailure = await ( + await getExecutor("command-code") + ).execute({ model: "gpt-5.4-mini", stream: false, credentials: { apiKey: "cc_test_key" }, @@ -352,7 +359,9 @@ test("Command Code stream preserves the upstream OpenAI usage chunk (passthrough globalThis.fetch = async () => new Response(sse, { status: 200, headers: { "Content-Type": "text/event-stream" } }); - const { response } = await (await getExecutor("command-code")).execute({ + const { response } = await ( + await getExecutor("command-code") + ).execute({ model: "gpt-5.4-mini", stream: true, credentials: { apiKey: "cc_test_key" }, @@ -409,7 +418,9 @@ test("Command Code executor falls back to /alpha/generate on 403 (e.g. Go plan w return new Response("Not found", { status: 404 }); }; - const { response, url, headers } = await (await getExecutor("command-code")).execute({ + const { response, url, headers } = await ( + await getExecutor("command-code") + ).execute({ model: "deepseek/deepseek-v4-flash", stream: true, credentials: { apiKey: "cc_go_plan_key" }, @@ -456,7 +467,9 @@ test("Command Code executor falls back to /alpha/generate on 403 (Go plan) for n return new Response("Not found", { status: 404 }); }; - const { response } = await (await getExecutor("command-code")).execute({ + const { response } = await ( + await getExecutor("command-code") + ).execute({ model: "gpt-5.4", stream: false, credentials: { apiKey: "cc_go_plan_key" }, @@ -484,7 +497,9 @@ test("Command Code executor surfaces fallback error when both /provider/v1 and / return new Response("error", { status: 500 }); }; - const result = await (await getExecutor("command-code")).execute({ + const result = await ( + await getExecutor("command-code") + ).execute({ model: "gpt-5.4", stream: false, credentials: { apiKey: "cc_key" }, diff --git a/tests/unit/command-code-user-array-5166.test.ts b/tests/unit/command-code-user-array-5166.test.ts index cb4cd5f6823a..fb6838867cca 100644 --- a/tests/unit/command-code-user-array-5166.test.ts +++ b/tests/unit/command-code-user-array-5166.test.ts @@ -30,7 +30,7 @@ function okResponse() { test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test.afterEach(() => { diff --git a/tests/unit/command-code-vision.test.ts b/tests/unit/command-code-vision.test.ts index 5af6176a4987..bf55bf0678fd 100644 --- a/tests/unit/command-code-vision.test.ts +++ b/tests/unit/command-code-vision.test.ts @@ -30,7 +30,7 @@ function okResponse() { test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test.afterEach(() => { diff --git a/tests/unit/compliance-audit-route.test.ts b/tests/unit/compliance-audit-route.test.ts index 51e4fc9c0914..4004ddd25bcf 100644 --- a/tests/unit/compliance-audit-route.test.ts +++ b/tests/unit/compliance-audit-route.test.ts @@ -13,7 +13,7 @@ const auditRoute = await import("../../src/app/api/compliance/audit-log/route.ts function resetDb() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -23,7 +23,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("compliance audit route keeps array payloads and exposes total count with structured filters", async () => { diff --git a/tests/unit/compliance-index.test.ts b/tests/unit/compliance-index.test.ts index f99a5a4b2633..7d9457248780 100644 --- a/tests/unit/compliance-index.test.ts +++ b/tests/unit/compliance-index.test.ts @@ -15,7 +15,7 @@ const compliance = await import("../../src/lib/compliance/index.ts"); function resetDb() { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -26,7 +26,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("compliance audit log initialization, writes and filtered reads work end to end", () => { diff --git a/tests/unit/compression-settings-cache.test.ts b/tests/unit/compression-settings-cache.test.ts index 729e4c8a253c..00b50ce90fbe 100644 --- a/tests/unit/compression-settings-cache.test.ts +++ b/tests/unit/compression-settings-cache.test.ts @@ -24,7 +24,7 @@ function cleanup() { delete process.env.DATA_DIR; } try { - fs.rmSync(tempDir, { recursive: true, force: true }); + fs.rmSync(tempDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch {} } @@ -81,11 +81,26 @@ test("getCompressionSettings returned config has expected shape", async () => { assert.ok(typeof config.defaultMode === "string", "defaultMode should be string"); assert.ok(typeof config.autoTriggerTokens === "number", "autoTriggerTokens should be number"); assert.ok(typeof config.cacheMinutes === "number", "cacheMinutes should be number"); - assert.ok(typeof config.preserveSystemPrompt === "boolean", "preserveSystemPrompt should be boolean"); - assert.ok(config.cavemanConfig && typeof config.cavemanConfig === "object", "cavemanConfig should be object"); - assert.ok(config.rtkConfig && typeof config.rtkConfig === "object", "rtkConfig should be object"); - assert.ok(config.languageConfig && typeof config.languageConfig === "object", "languageConfig should be object"); - assert.ok(config.aggressive && typeof config.aggressive === "object", "aggressive should be object"); + assert.ok( + typeof config.preserveSystemPrompt === "boolean", + "preserveSystemPrompt should be boolean" + ); + assert.ok( + config.cavemanConfig && typeof config.cavemanConfig === "object", + "cavemanConfig should be object" + ); + assert.ok( + config.rtkConfig && typeof config.rtkConfig === "object", + "rtkConfig should be object" + ); + assert.ok( + config.languageConfig && typeof config.languageConfig === "object", + "languageConfig should be object" + ); + assert.ok( + config.aggressive && typeof config.aggressive === "object", + "aggressive should be object" + ); assert.ok(config.ultra && typeof config.ultra === "object", "ultra should be object"); } finally { cleanup(); diff --git a/tests/unit/compression-tokens.test.ts b/tests/unit/compression-tokens.test.ts index a7a5a422b87e..743892b40f07 100644 --- a/tests/unit/compression-tokens.test.ts +++ b/tests/unit/compression-tokens.test.ts @@ -68,31 +68,19 @@ test("tokensCompressed round-trips through saveCallLog → getCallLogs", async ( limit: 10, }); - const logNull = logs.find( - (l: { id: string }) => l.id === "log-null" - ); - const logComp = logs.find( - (l: { id: string }) => l.id === "log-350" - ); + const logNull = logs.find((l: { id: string }) => l.id === "log-null"); + const logComp = logs.find((l: { id: string }) => l.id === "log-350"); // null when no compression - assert.equal( - logNull.tokens?.compressed, - null, - "uncompressed log should have null compressed" - ); + assert.equal(logNull.tokens?.compressed, null, "uncompressed log should have null compressed"); // Positive value when compressed - assert.equal( - logComp.tokens?.compressed, - 350, - "compressed log should store exact token delta" - ); + assert.equal(logComp.tokens?.compressed, 350, "compressed log should store exact token delta"); // Input tokens unaffected assert.equal(logComp.tokens?.in, 1000); assert.equal(logComp.tokens?.out, 500); } finally { - fs.rmSync(dir, { recursive: true, force: true }); + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/compression/active-combo-integration.test.ts b/tests/unit/compression/active-combo-integration.test.ts index 09e1c641cd80..4aa57c2eddca 100644 --- a/tests/unit/compression/active-combo-integration.test.ts +++ b/tests/unit/compression/active-combo-integration.test.ts @@ -11,12 +11,14 @@ process.env.DATA_DIR = TEST_DATA_DIR; const { getDbInstance, resetDbInstance } = await import("../../../src/lib/db/core.ts"); const combosDb = await import("../../../src/lib/db/compressionCombos.ts"); const { updateCompressionSettings } = await import("../../../src/lib/db/compression.ts"); -const { selectCompressionPlan } = await import("../../../open-sse/services/compression/strategySelector.ts"); -const { DEFAULT_COMPRESSION_CONFIG } = await import("../../../open-sse/services/compression/types.ts"); +const { selectCompressionPlan } = + await import("../../../open-sse/services/compression/strategySelector.ts"); +const { DEFAULT_COMPRESSION_CONFIG } = + await import("../../../open-sse/services/compression/types.ts"); after(() => { resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL; }); @@ -31,7 +33,9 @@ test("an active named combo's pipeline is what selectCompressionPlan resolves, f await updateCompressionSettings({ enabled: true, activeComboId: created.id }); // Mirror chatCore's load: build the combos map from the DB. - const combos = Object.fromEntries(combosDb.listCompressionCombos().map((c) => [c.id, c.pipeline])); + const combos = Object.fromEntries( + combosDb.listCompressionCombos().map((c) => [c.id, c.pipeline]) + ); const config = { ...DEFAULT_COMPRESSION_CONFIG, enabled: true, activeComboId: created.id }; const plan = selectCompressionPlan(config, null, 5000, undefined, undefined, combos); assert.equal(plan.mode, "stacked"); diff --git a/tests/unit/compression/adaptive-context-budget-config.test.ts b/tests/unit/compression/adaptive-context-budget-config.test.ts index 4dd4acb68c7a..7f92117d7a25 100644 --- a/tests/unit/compression/adaptive-context-budget-config.test.ts +++ b/tests/unit/compression/adaptive-context-budget-config.test.ts @@ -19,19 +19,16 @@ const ORIGINAL_DATA_DIR = process.env.DATA_DIR; process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../../src/lib/db/core.ts"); -const { getCompressionSettings, updateCompressionSettings } = await import( - "../../../src/lib/db/compression.ts" -); -const { compressionSettingsUpdateSchema } = await import( - "../../../src/shared/validation/compressionConfigSchemas.ts" -); -const { DEFAULT_CONTEXT_BUDGET } = await import( - "../../../open-sse/services/compression/adaptiveCompression/types.ts" -); +const { getCompressionSettings, updateCompressionSettings } = + await import("../../../src/lib/db/compression.ts"); +const { compressionSettingsUpdateSchema } = + await import("../../../src/shared/validation/compressionConfigSchemas.ts"); +const { DEFAULT_CONTEXT_BUDGET } = + await import("../../../open-sse/services/compression/adaptiveCompression/types.ts"); beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); }); @@ -41,7 +38,7 @@ afterEach(() => { after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; } else { @@ -71,7 +68,12 @@ describe("bug #7005: adaptive context-budget dial is configurable", () => { it("updateCompressionSettings() persists a partial contextBudget merge", async () => { await updateCompressionSettings({ - contextBudget: { ...DEFAULT_CONTEXT_BUDGET, mode: "floor", policy: "absolute", absoluteBudget: 8000 }, + contextBudget: { + ...DEFAULT_CONTEXT_BUDGET, + mode: "floor", + policy: "absolute", + absoluteBudget: 8000, + }, }); const settings = await getCompressionSettings(); assert.equal(settings.contextBudget?.mode, "floor"); diff --git a/tests/unit/compression/caveman-db.test.ts b/tests/unit/compression/caveman-db.test.ts index b1cd5f85926d..2257c9abb9ce 100644 --- a/tests/unit/compression/caveman-db.test.ts +++ b/tests/unit/compression/caveman-db.test.ts @@ -16,7 +16,7 @@ const { getCompressionSettings, updateCompressionSettings } = describe("compression DB module", () => { beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); }); @@ -26,7 +26,7 @@ describe("compression DB module", () => { after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; } else { diff --git a/tests/unit/compression/compareRoute.test.ts b/tests/unit/compression/compareRoute.test.ts index c3d75f7b0e07..ae6a268053d5 100644 --- a/tests/unit/compression/compareRoute.test.ts +++ b/tests/unit/compression/compareRoute.test.ts @@ -11,18 +11,27 @@ const core = await import("../../../src/lib/db/core.ts"); const route = await import("../../../src/app/api/compression/compare/route.ts"); function makeReq(body: unknown) { return new Request("http://localhost/api/compression/compare", { - method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body), + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify(body), }); } test.beforeEach(() => core.resetDbInstance()); -test.after(() => { core.resetDbInstance(); rmSync(TEST_DATA_DIR, { recursive: true, force: true }); }); +test.after(() => { + core.resetDbInstance(); + rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); test("ranks a high-savings engine above a no-op for repetitive tool output", async () => { - const text = ["$ npm install", + const text = [ + "$ npm install", "npm warn deprecated glob@7.2.3: no longer supported", "npm warn deprecated glob@7.2.3: no longer supported", "npm warn deprecated glob@7.2.3: no longer supported", - "added 1234 packages"].join("\n"); - const res = await route.POST(makeReq({ messages: [{ role: "user", content: text }], engineIds: ["rtk", "lite"] })); + "added 1234 packages", + ].join("\n"); + const res = await route.POST( + makeReq({ messages: [{ role: "user", content: text }], engineIds: ["rtk", "lite"] }) + ); assert.equal(res.status, 200); const body = await res.json(); assert.ok(Array.isArray(body.rows) && body.rows.length === 2); diff --git a/tests/unit/compression/compression-combos-db.test.ts b/tests/unit/compression/compression-combos-db.test.ts index 0b58800334fe..14cd61be9506 100644 --- a/tests/unit/compression/compression-combos-db.test.ts +++ b/tests/unit/compression/compression-combos-db.test.ts @@ -13,7 +13,7 @@ const combosDb = await import("../../../src/lib/db/compressionCombos.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -23,7 +23,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; } else { diff --git a/tests/unit/compression/compression-engines-map-migration.test.ts b/tests/unit/compression/compression-engines-map-migration.test.ts index d315c737e56f..d7ed99624baa 100644 --- a/tests/unit/compression/compression-engines-map-migration.test.ts +++ b/tests/unit/compression/compression-engines-map-migration.test.ts @@ -9,19 +9,18 @@ const ORIGINAL_DATA_DIR = process.env.DATA_DIR; process.env.DATA_DIR = TEST_DATA_DIR; const { getDbInstance, resetDbInstance } = await import("../../../src/lib/db/core.ts"); -const { getCompressionSettings, updateCompressionSettings } = await import( - "../../../src/lib/db/compression.ts" -); +const { getCompressionSettings, updateCompressionSettings } = + await import("../../../src/lib/db/compression.ts"); function freshDir() { resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } after(() => { resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; } else { diff --git a/tests/unit/compression/compression-preview-auth.test.ts b/tests/unit/compression/compression-preview-auth.test.ts index b5bfb1332efe..0e6ebc555354 100644 --- a/tests/unit/compression/compression-preview-auth.test.ts +++ b/tests/unit/compression/compression-preview-auth.test.ts @@ -24,7 +24,7 @@ type ErrorResponseBody = { async function resetAuthRequiredStorage(): Promise { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); await settingsDb.updateSettings({ requireLogin: true, @@ -44,7 +44,7 @@ test.after(() => { if (originalJwtSecret === undefined) delete process.env.JWT_SECRET; else process.env.JWT_SECRET = originalJwtSecret; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("compression preview requires management auth before reading preview input", async () => { diff --git a/tests/unit/compression/compressionAnalytics.test.ts b/tests/unit/compression/compressionAnalytics.test.ts index eaef7898df7e..7402059c825a 100644 --- a/tests/unit/compression/compressionAnalytics.test.ts +++ b/tests/unit/compression/compressionAnalytics.test.ts @@ -42,7 +42,7 @@ describe("compressionAnalytics", () => { after(() => { core.closeDbInstance(); - rmSync(tmpDir, { recursive: true, force: true }); + rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); it("empty table returns zeroed summary", () => { diff --git a/tests/unit/compression/db.test.ts b/tests/unit/compression/db.test.ts index 1861e71dcf25..957cdbf899f9 100644 --- a/tests/unit/compression/db.test.ts +++ b/tests/unit/compression/db.test.ts @@ -14,7 +14,7 @@ const { getCompressionSettings, updateCompressionSettings } = beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); }); @@ -24,7 +24,7 @@ afterEach(() => { after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; } else { diff --git a/tests/unit/compression/headroom-minrows-persist-8056.test.ts b/tests/unit/compression/headroom-minrows-persist-8056.test.ts index de52eb5b116b..717af1467368 100644 --- a/tests/unit/compression/headroom-minrows-persist-8056.test.ts +++ b/tests/unit/compression/headroom-minrows-persist-8056.test.ts @@ -32,7 +32,7 @@ const { getCompressionSettings, updateCompressionSettings } = beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); }); @@ -42,7 +42,7 @@ afterEach(() => { after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; } else { diff --git a/tests/unit/compression/llmlingua-model-store.test.ts b/tests/unit/compression/llmlingua-model-store.test.ts index 0459432b5bd9..18f820a5737f 100644 --- a/tests/unit/compression/llmlingua-model-store.test.ts +++ b/tests/unit/compression/llmlingua-model-store.test.ts @@ -87,7 +87,7 @@ describe("getLlmlinguaModelCacheDir", () => { } if (tmpDir) { try { - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { /* ignore cleanup errors */ } diff --git a/tests/unit/compression/llmlingua-worker-resolution.test.ts b/tests/unit/compression/llmlingua-worker-resolution.test.ts index cd7df8e9acfd..356ef718fd8e 100644 --- a/tests/unit/compression/llmlingua-worker-resolution.test.ts +++ b/tests/unit/compression/llmlingua-worker-resolution.test.ts @@ -67,7 +67,7 @@ test("firstAncestorWith walks up from anchors to find a marker", () => { assert.equal(found, path.join(tmp, "dist"), "must find the dist root by walking up"); assert.equal(firstAncestorWith([anchor], path.join("node_modules", "nope")), null); } finally { - fs.rmSync(tmp, { recursive: true, force: true }); + fs.rmSync(tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/compression/mcp-accessibility-config.test.ts b/tests/unit/compression/mcp-accessibility-config.test.ts index 96431eddfb52..da14e4d30df6 100644 --- a/tests/unit/compression/mcp-accessibility-config.test.ts +++ b/tests/unit/compression/mcp-accessibility-config.test.ts @@ -24,7 +24,7 @@ const route = await import("../../../src/app/api/settings/compression/mcp-access function resetDir() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -33,7 +33,7 @@ describe("mcpAccessibility config reachability", () => { afterEach(() => core.resetDbInstance()); after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; }); diff --git a/tests/unit/compression/omniglyph-profile-config.test.ts b/tests/unit/compression/omniglyph-profile-config.test.ts index d24f361baeab..4d16e4d3147a 100644 --- a/tests/unit/compression/omniglyph-profile-config.test.ts +++ b/tests/unit/compression/omniglyph-profile-config.test.ts @@ -21,13 +21,12 @@ const ORIGINAL_DATA_DIR = process.env.DATA_DIR; process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../../src/lib/db/core.ts"); -const { getCompressionSettings, updateCompressionSettings } = await import( - "../../../src/lib/db/compression.ts" -); +const { getCompressionSettings, updateCompressionSettings } = + await import("../../../src/lib/db/compression.ts"); beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); }); @@ -37,7 +36,7 @@ afterEach(() => { after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; }); diff --git a/tests/unit/compression/omniglyph-registries.test.ts b/tests/unit/compression/omniglyph-registries.test.ts index 895f1c54e2e4..5737c3c6fc8d 100644 --- a/tests/unit/compression/omniglyph-registries.test.ts +++ b/tests/unit/compression/omniglyph-registries.test.ts @@ -25,13 +25,13 @@ const { compressionConfigureInput } = await import("../../../open-sse/mcp-server beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); }); after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; } else { diff --git a/tests/unit/compression/preserve-system-prompt-mode-db.test.ts b/tests/unit/compression/preserve-system-prompt-mode-db.test.ts index ab3ae491f7fb..662524cbfc5c 100644 --- a/tests/unit/compression/preserve-system-prompt-mode-db.test.ts +++ b/tests/unit/compression/preserve-system-prompt-mode-db.test.ts @@ -38,7 +38,7 @@ test.after(async () => { /* core never loaded */ } try { - fs.rmSync(TEMP_DIR, { recursive: true, force: true }); + fs.rmSync(TEMP_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { /* best-effort */ } @@ -59,9 +59,8 @@ test("legacy preserveSystemPrompt=false (no mode row) derives whenNoCache", asyn ); // End-to-end: without a cacheable prefix, a legacy-off install must still compress the prompt. - const { resolveCacheAwareConfig } = await import( - "../../../open-sse/services/compression/cacheAwareConfig.ts" - ); + const { resolveCacheAwareConfig } = + await import("../../../open-sse/services/compression/cacheAwareConfig.ts"); assert.equal( resolveCacheAwareConfig(cfg).preserveSystemPrompt, false, diff --git a/tests/unit/compression/preview-fallback-reasons-6461.test.ts b/tests/unit/compression/preview-fallback-reasons-6461.test.ts index 4de93f128f12..4b15d63e6c4f 100644 --- a/tests/unit/compression/preview-fallback-reasons-6461.test.ts +++ b/tests/unit/compression/preview-fallback-reasons-6461.test.ts @@ -29,7 +29,7 @@ function makeReq(body: unknown) { test.beforeEach(() => core.resetDbInstance()); test.after(() => { core.resetDbInstance(); - rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#6461 preview exposes fallbackReasons and mirrors it into skippedReasons", async () => { diff --git a/tests/unit/compression/preview-outer-engine-token-reconcile-6488.test.ts b/tests/unit/compression/preview-outer-engine-token-reconcile-6488.test.ts index 0d7d01da9b30..2096bff502b5 100644 --- a/tests/unit/compression/preview-outer-engine-token-reconcile-6488.test.ts +++ b/tests/unit/compression/preview-outer-engine-token-reconcile-6488.test.ts @@ -22,7 +22,7 @@ function makeReq(body: unknown) { test.beforeEach(() => core.resetDbInstance()); test.after(() => { core.resetDbInstance(); - rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // Regression for #6488: outer originalTokens/compressedTokens (real tiktoken counter over diff --git a/tests/unit/compression/previewRouteBreakdown.test.ts b/tests/unit/compression/previewRouteBreakdown.test.ts index a4053bd73c4b..10de10f393fd 100644 --- a/tests/unit/compression/previewRouteBreakdown.test.ts +++ b/tests/unit/compression/previewRouteBreakdown.test.ts @@ -11,16 +11,23 @@ const core = await import("../../../src/lib/db/core.ts"); const route = await import("../../../src/app/api/compression/preview/route.ts"); function makeReq(body: unknown) { return new Request("http://localhost/api/compression/preview", { - method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body), + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify(body), }); } test.beforeEach(() => core.resetDbInstance()); -test.after(() => { core.resetDbInstance(); rmSync(TEST_DATA_DIR, { recursive: true, force: true }); }); +test.after(() => { + core.resetDbInstance(); + rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); test("response carries a non-empty engineBreakdown for a single engine", async () => { - const res = await route.POST(makeReq({ - messages: [{ role: "user", content: "$ git status\nOn branch main\nnothing to commit" }], - engineId: "rtk", - })); + const res = await route.POST( + makeReq({ + messages: [{ role: "user", content: "$ git status\nOn branch main\nnothing to commit" }], + engineId: "rtk", + }) + ); assert.equal(res.status, 200); const body = await res.json(); assert.ok(Array.isArray(body.engineBreakdown)); diff --git a/tests/unit/compression/previewRouteFidelity.test.ts b/tests/unit/compression/previewRouteFidelity.test.ts index b87f70b6b48c..8cc54f1f9f8e 100644 --- a/tests/unit/compression/previewRouteFidelity.test.ts +++ b/tests/unit/compression/previewRouteFidelity.test.ts @@ -11,26 +11,37 @@ const core = await import("../../../src/lib/db/core.ts"); const route = await import("../../../src/app/api/compression/preview/route.ts"); function makeReq(body: unknown) { return new Request("http://localhost/api/compression/preview", { - method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body), + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify(body), }); } test.beforeEach(() => core.resetDbInstance()); -test.after(() => { core.resetDbInstance(); rmSync(TEST_DATA_DIR, { recursive: true, force: true }); }); +test.after(() => { + core.resetDbInstance(); + rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); test("fidelityGate flag is accepted (200) and preview still works", async () => { - const res = await route.POST(makeReq({ - messages: [{ role: "user", content: "$ git status\nOn branch main" }], - engineId: "rtk", fidelityGate: { enabled: true }, - })); + const res = await route.POST( + makeReq({ + messages: [{ role: "user", content: "$ git status\nOn branch main" }], + engineId: "rtk", + fidelityGate: { enabled: true }, + }) + ); assert.equal(res.status, 200); const body = await res.json(); assert.ok(Array.isArray(body.engineBreakdown)); }); test("malformed fidelityGate is rejected (proves the field is in the schema, not stripped)", async () => { - const res = await route.POST(makeReq({ - messages: [{ role: "user", content: "x" }], - engineId: "rtk", fidelityGate: { enabled: "yes" }, // wrong type - })); + const res = await route.POST( + makeReq({ + messages: [{ role: "user", content: "x" }], + engineId: "rtk", + fidelityGate: { enabled: "yes" }, // wrong type + }) + ); assert.equal(res.status, 400); }); diff --git a/tests/unit/compression/previewRouteFuzzy.test.ts b/tests/unit/compression/previewRouteFuzzy.test.ts index 3b4b31159cdf..f08d2c99ba91 100644 --- a/tests/unit/compression/previewRouteFuzzy.test.ts +++ b/tests/unit/compression/previewRouteFuzzy.test.ts @@ -11,28 +11,42 @@ const core = await import("../../../src/lib/db/core.ts"); const route = await import("../../../src/app/api/compression/preview/route.ts"); function makeReq(body: unknown) { return new Request("http://localhost/api/compression/preview", { - method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body), + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify(body), }); } test.beforeEach(() => core.resetDbInstance()); -test.after(() => { core.resetDbInstance(); rmSync(TEST_DATA_DIR, { recursive: true, force: true }); }); +test.after(() => { + core.resetDbInstance(); + rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); const A = "alpha beta gamma delta epsilon zeta eta theta iota kappa lambda mu nu xi omicron pi rho"; test("fuzzyDedup flag drives the session-dedup lane to produce a CCR marker", async () => { - const res = await route.POST(makeReq({ - messages: [{ role: "user", content: A }, { role: "user", content: A + " sigma" }], - engineId: "session-dedup", - fuzzyDedup: { enabled: true }, - })); + const res = await route.POST( + makeReq({ + messages: [ + { role: "user", content: A }, + { role: "user", content: A + " sigma" }, + ], + engineId: "session-dedup", + fuzzyDedup: { enabled: true }, + }) + ); assert.equal(res.status, 200); const body = await res.json(); assert.match(body.compressed, /\[CCR retrieve hash=[0-9a-f]{24}/); }); test("malformed fuzzyDedup is rejected (field is in the schema, not stripped)", async () => { - const res = await route.POST(makeReq({ - messages: [{ role: "user", content: "x" }], engineId: "session-dedup", fuzzyDedup: { enabled: "yes" }, - })); + const res = await route.POST( + makeReq({ + messages: [{ role: "user", content: "x" }], + engineId: "session-dedup", + fuzzyDedup: { enabled: "yes" }, + }) + ); assert.equal(res.status, 400); }); diff --git a/tests/unit/compression/previewRouteIonizer.test.ts b/tests/unit/compression/previewRouteIonizer.test.ts index eebcb0989d6a..81e370275bbd 100644 --- a/tests/unit/compression/previewRouteIonizer.test.ts +++ b/tests/unit/compression/previewRouteIonizer.test.ts @@ -12,16 +12,26 @@ const core = await import("../../../src/lib/db/core.ts"); const route = await import("../../../src/app/api/compression/preview/route.ts"); function makeReq(body: unknown) { return new Request("http://localhost/api/compression/preview", { - method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body), + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify(body), }); } test.beforeEach(() => core.resetDbInstance()); -test.after(() => { core.resetDbInstance(); rmSync(TEST_DATA_DIR, { recursive: true, force: true }); }); +test.after(() => { + core.resetDbInstance(); + rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); test("the ionizer lane samples an oversized JSON array into a CCR marker", async () => { const big = JSON.stringify(Array.from({ length: 400 }, (_, i) => ({ i, v: `r${i}` }))); - const res = await route.POST(makeReq({ messages: [{ role: "user", content: big }], engineId: "ionizer" })); + const res = await route.POST( + makeReq({ messages: [{ role: "user", content: big }], engineId: "ionizer" }) + ); assert.equal(res.status, 200); const body = await res.json(); - assert.match(body.compressed, /\[ionizer: kept \d+\/400 rows; full → CCR retrieve hash=[0-9a-f]{24}/); + assert.match( + body.compressed, + /\[ionizer: kept \d+\/400 rows; full → CCR retrieve hash=[0-9a-f]{24}/ + ); }); diff --git a/tests/unit/compression/previewRoutePipeline.test.ts b/tests/unit/compression/previewRoutePipeline.test.ts index b90ddfcc73d5..e3f0f1bde7f5 100644 --- a/tests/unit/compression/previewRoutePipeline.test.ts +++ b/tests/unit/compression/previewRoutePipeline.test.ts @@ -11,14 +11,22 @@ const core = await import("../../../src/lib/db/core.ts"); const route = await import("../../../src/app/api/compression/preview/route.ts"); function makeReq(body: unknown) { return new Request("http://localhost/api/compression/preview", { - method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body), + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify(body), }); } test.beforeEach(() => core.resetDbInstance()); -test.after(() => { core.resetDbInstance(); rmSync(TEST_DATA_DIR, { recursive: true, force: true }); }); +test.after(() => { + core.resetDbInstance(); + rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); test("pipeline runs the engines in the GIVEN order (reversed vs default rtk→caveman)", async () => { - const text = "$ pytest\ntests/a.py ....\nbasically what I mean is that you should loop through them one by one"; - const res = await route.POST(makeReq({ messages: [{ role: "user", content: text }], pipeline: ["caveman", "rtk"] })); + const text = + "$ pytest\ntests/a.py ....\nbasically what I mean is that you should loop through them one by one"; + const res = await route.POST( + makeReq({ messages: [{ role: "user", content: text }], pipeline: ["caveman", "rtk"] }) + ); assert.equal(res.status, 200); const body = await res.json(); assert.equal(body.mode, "stacked"); @@ -28,14 +36,18 @@ test("pipeline runs the engines in the GIVEN order (reversed vs default rtk→ca }); test("pipeline accepts the 4 schema-restricted engines and does NOT fall back to the default rtk/caveman", async () => { - const res = await route.POST(makeReq({ - messages: [{ role: "user", content: "x".repeat(80) }], - pipeline: ["session-dedup", "headroom"], - })); + const res = await route.POST( + makeReq({ + messages: [{ role: "user", content: "x".repeat(80) }], + pipeline: ["session-dedup", "headroom"], + }) + ); assert.equal(res.status, 200); // would be 400 if it went through the strict config schema const body = await res.json(); // Discriminating: if `pipeline` were stripped, this would be the default rtk→caveman cascade. assert.ok( - body.engineBreakdown.every((e: { engine: string }) => e.engine !== "rtk" && e.engine !== "caveman") + body.engineBreakdown.every( + (e: { engine: string }) => e.engine !== "rtk" && e.engine !== "caveman" + ) ); }); diff --git a/tests/unit/compression/previewRouteTokens.test.ts b/tests/unit/compression/previewRouteTokens.test.ts index b0b36d313ba6..2f639da84f42 100644 --- a/tests/unit/compression/previewRouteTokens.test.ts +++ b/tests/unit/compression/previewRouteTokens.test.ts @@ -15,15 +15,22 @@ const { countTextTokens } = await import("../../../src/shared/utils/tiktokenCoun function makeReq(body: unknown) { return new Request("http://localhost/api/compression/preview", { - method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body), + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify(body), }); } test.beforeEach(() => core.resetDbInstance()); -test.after(() => { core.resetDbInstance(); rmSync(TEST_DATA_DIR, { recursive: true, force: true }); }); +test.after(() => { + core.resetDbInstance(); + rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); test("originalTokens equals countTextTokens, not the *1.33 estimate", async () => { const text = "the quick brown fox jumps over the lazy dog repeatedly and often"; - const res = await route.POST(makeReq({ messages: [{ role: "user", content: text }], mode: "off" })); + const res = await route.POST( + makeReq({ messages: [{ role: "user", content: text }], mode: "off" }) + ); assert.equal(res.status, 200); const body = await res.json(); assert.equal(body.originalTokens, countTextTokens(body.original)); diff --git a/tests/unit/compression/previewRouteToon.test.ts b/tests/unit/compression/previewRouteToon.test.ts index f10d9dcbd9b6..d912e77b084a 100644 --- a/tests/unit/compression/previewRouteToon.test.ts +++ b/tests/unit/compression/previewRouteToon.test.ts @@ -20,7 +20,7 @@ function makeReq(body: unknown) { test.beforeEach(() => core.resetDbInstance()); test.after(() => { core.resetDbInstance(); - rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("headroom engine carries encoderComparison with one array and a valid winner", async () => { diff --git a/tests/unit/compression/retrieveRoute.test.ts b/tests/unit/compression/retrieveRoute.test.ts index cbff7a447c2a..14f342086e71 100644 --- a/tests/unit/compression/retrieveRoute.test.ts +++ b/tests/unit/compression/retrieveRoute.test.ts @@ -11,11 +11,16 @@ const core = await import("../../../src/lib/db/core.ts"); const route = await import("../../../src/app/api/compression/retrieve/route.ts"); function makeReq(body: unknown) { return new Request("http://localhost/api/compression/retrieve", { - method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body), + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify(body), }); } test.beforeEach(() => core.resetDbInstance()); -test.after(() => { core.resetDbInstance(); rmSync(TEST_DATA_DIR, { recursive: true, force: true }); }); +test.after(() => { + core.resetDbInstance(); + rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); test("400 when hash is missing", async () => { const res = await route.POST(makeReq({})); assert.equal(res.status, 400); diff --git a/tests/unit/compression/retrieveRouteRanged.test.ts b/tests/unit/compression/retrieveRouteRanged.test.ts index a1dd2a941ee9..3f002e1872b7 100644 --- a/tests/unit/compression/retrieveRouteRanged.test.ts +++ b/tests/unit/compression/retrieveRouteRanged.test.ts @@ -24,7 +24,7 @@ test.beforeEach(() => { }); test.after(() => { core.resetDbInstance(); - rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("mode:head n:2 returns first 2 lines", async () => { diff --git a/tests/unit/compression/rtk-command-samples.test.ts b/tests/unit/compression/rtk-command-samples.test.ts index b352e38647e5..81fbfbd5eb89 100644 --- a/tests/unit/compression/rtk-command-samples.test.ts +++ b/tests/unit/compression/rtk-command-samples.test.ts @@ -35,7 +35,7 @@ beforeEach(() => { afterEach(() => { if (prevDataDir === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = prevDataDir; - fs.rmSync(tmp, { recursive: true, force: true }); + fs.rmSync(tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); describe("maybePersistRtkRawOutput — command sidecar", () => { diff --git a/tests/unit/compression/rtk-grouping-config.test.ts b/tests/unit/compression/rtk-grouping-config.test.ts index 6a65b1b6e366..8426af9e198f 100644 --- a/tests/unit/compression/rtk-grouping-config.test.ts +++ b/tests/unit/compression/rtk-grouping-config.test.ts @@ -16,14 +16,13 @@ const ORIGINAL_DATA_DIR = process.env.DATA_DIR; process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../../src/lib/db/core.ts"); -const { getCompressionSettings, updateCompressionSettings } = await import( - "../../../src/lib/db/compression.ts" -); +const { getCompressionSettings, updateCompressionSettings } = + await import("../../../src/lib/db/compression.ts"); describe("RTK grouping config persistence (R5)", () => { beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); }); @@ -33,7 +32,7 @@ describe("RTK grouping config persistence (R5)", () => { after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; }); diff --git a/tests/unit/compression/rtk-mcp-tools.test.ts b/tests/unit/compression/rtk-mcp-tools.test.ts index aefba54d0b21..b79af2190766 100644 --- a/tests/unit/compression/rtk-mcp-tools.test.ts +++ b/tests/unit/compression/rtk-mcp-tools.test.ts @@ -10,12 +10,10 @@ const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omni-rtk-mcp-")); process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../../src/lib/db/core.ts"); -const { handleRtkDiscover, handleRtkLearn } = await import( - "../../../open-sse/mcp-server/tools/compressionTools.ts" -); -const { maybePersistRtkRawOutput } = await import( - "../../../open-sse/services/compression/engines/rtk/rawOutput.ts" -); +const { handleRtkDiscover, handleRtkLearn } = + await import("../../../open-sse/mcp-server/tools/compressionTools.ts"); +const { maybePersistRtkRawOutput } = + await import("../../../open-sse/services/compression/engines/rtk/rawOutput.ts"); const { getRecentAuditEntries } = await import("../../../open-sse/mcp-server/audit.ts"); const NOISE = [ @@ -42,14 +40,14 @@ function seedSamples() { beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); core.getDbInstance(); // run migrations → mcp_tool_audit table exists }); after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); describe("RTK MCP tools (T07)", () => { @@ -66,7 +64,10 @@ describe("RTK MCP tools (T07)", () => { const result = await handleRtkLearn({ command: "gradle build", limit: 100 }); assert.equal(result.command, "gradle build"); assert.ok(result.sampleCount >= 1, "expected at least one matching sample"); - assert.ok(result.filter && typeof result.filter === "object", "expected a suggested filter draft"); + assert.ok( + result.filter && typeof result.filter === "object", + "expected a suggested filter draft" + ); }); it("returns an empty/baseline result with no samples (no throw)", async () => { diff --git a/tests/unit/compression/rtk-raw-output-route.test.ts b/tests/unit/compression/rtk-raw-output-route.test.ts index 0ab56c19280a..7eca439114ec 100644 --- a/tests/unit/compression/rtk-raw-output-route.test.ts +++ b/tests/unit/compression/rtk-raw-output-route.test.ts @@ -23,7 +23,7 @@ type ErrorResponseBody = { async function resetAuthRequiredStorage(): Promise { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); await settingsDb.updateSettings({ requireLogin: true, @@ -43,7 +43,7 @@ test.after(() => { if (originalJwtSecret === undefined) delete process.env.JWT_SECRET; else process.env.JWT_SECRET = originalJwtSecret; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("RTK raw-output route requires management auth before reading retained output", async () => { diff --git a/tests/unit/compression/rtk-renderers-config.test.ts b/tests/unit/compression/rtk-renderers-config.test.ts index 2be878169d7b..86812f82419a 100644 --- a/tests/unit/compression/rtk-renderers-config.test.ts +++ b/tests/unit/compression/rtk-renderers-config.test.ts @@ -22,7 +22,7 @@ describe("RTK renderer config persistence", () => { after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; }); diff --git a/tests/unit/compression/rtk-strip-comments.test.ts b/tests/unit/compression/rtk-strip-comments.test.ts index f9b70963abc9..39d31a7662b6 100644 --- a/tests/unit/compression/rtk-strip-comments.test.ts +++ b/tests/unit/compression/rtk-strip-comments.test.ts @@ -4,10 +4,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -import { - applyRtkCompression, - stripCode, -} from "../../../open-sse/services/compression/index.ts"; +import { applyRtkCompression, stripCode } from "../../../open-sse/services/compression/index.ts"; import { rtkConfigSchema } from "../../../src/shared/validation/compressionConfigSchemas.ts"; import { DEFAULT_RTK_CONFIG } from "../../../open-sse/services/compression/types.ts"; @@ -22,9 +19,8 @@ const ORIGINAL_DATA_DIR = process.env.DATA_DIR; process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../../src/lib/db/core.ts"); -const { getCompressionSettings, updateCompressionSettings } = await import( - "../../../src/lib/db/compression.ts" -); +const { getCompressionSettings, updateCompressionSettings } = + await import("../../../src/lib/db/compression.ts"); describe("RTK strip-code-comments — stripCode behavior", () => { it("removes line/block comments but keeps JSDoc when preserveDocstrings is on", () => { @@ -99,7 +95,7 @@ describe("RTK strip-code-comments — runtime reachability", () => { describe("RTK strip-code-comments — config persistence", () => { beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); }); @@ -109,7 +105,7 @@ describe("RTK strip-code-comments — config persistence", () => { after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; }); diff --git a/tests/unit/conductor-a2a-post.test.ts b/tests/unit/conductor-a2a-post.test.ts index 7cbae0e4d62e..801da19606a8 100644 --- a/tests/unit/conductor-a2a-post.test.ts +++ b/tests/unit/conductor-a2a-post.test.ts @@ -32,12 +32,18 @@ function delegationRequest(body: unknown, bearer?: string) { const VALID_BODY = { skill: "conductor-cli-claude", messages: [{ role: "user", content: "adicione um README com a seção Sobre" }], - metadata: { conductor: { repo: { url: "https://git.x/repo", base_ref: "dev" }, mode: "solo", model: "cc/claude-sonnet-5" } }, + metadata: { + conductor: { + repo: { url: "https://git.x/repo", base_ref: "dev" }, + mode: "solo", + model: "cc/claude-sonnet-5", + }, + }, }; test.beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); delete process.env.CONDUCTOR_HUB_URL; delete process.env.OMNIROUTE_API_KEY; @@ -45,7 +51,7 @@ test.beforeEach(() => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); delete process.env.CONDUCTOR_HUB_URL; delete process.env.OMNIROUTE_API_KEY; while (servers.length > 0) { @@ -91,7 +97,11 @@ test("delegação válida → 201 com o task_id do hub; requirements derivados d const out = await res.json(); assert.equal(out.conductor_task_id, "t_delegada"); assert.equal(out.state, "submitted"); - const sent = bodies[0] as { repo: { url: string; base_ref: string }; spec: { prompt: string }; requirements: { cli: string; model: string } }; + const sent = bodies[0] as { + repo: { url: string; base_ref: string }; + spec: { prompt: string }; + requirements: { cli: string; model: string }; + }; assert.equal(sent.repo.url, "https://git.x/repo"); assert.equal(sent.repo.base_ref, "dev"); assert.equal(sent.spec.prompt, "adicione um README com a seção Sobre"); diff --git a/tests/unit/conductor-ask-route.test.ts b/tests/unit/conductor-ask-route.test.ts index 9edfffbc8d4c..3fdadccde8c2 100644 --- a/tests/unit/conductor-ask-route.test.ts +++ b/tests/unit/conductor-ask-route.test.ts @@ -15,14 +15,14 @@ const servers: Server[] = []; test.beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); delete process.env.CONDUCTOR_SPOKESPERSON_URL; }); test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); delete process.env.CONDUCTOR_SPOKESPERSON_URL; while (servers.length > 0) { const s = servers.pop(); @@ -31,7 +31,10 @@ test.after(async () => { }); test("fonte: auth antes do proxy; token nunca manuseado na rota", () => { - const src = fs.readFileSync(path.join(process.cwd(), "src/app/api/conductor/ask/route.ts"), "utf8"); + const src = fs.readFileSync( + path.join(process.cwd(), "src/app/api/conductor/ask/route.ts"), + "utf8" + ); const authAt = src.indexOf("requireManagementAuth("); assert.ok(authAt > 0); assert.match(src, /if \(authError\) return authError;/); diff --git a/tests/unit/conductor-fleet-route.test.ts b/tests/unit/conductor-fleet-route.test.ts index 4fb4b88a2b51..adddca72970b 100644 --- a/tests/unit/conductor-fleet-route.test.ts +++ b/tests/unit/conductor-fleet-route.test.ts @@ -19,7 +19,9 @@ function fakeHub(routes: Record): Pro const server = createServer((req, res) => { const hit = Object.entries(routes).find(([p]) => (req.url ?? "").startsWith(p)); res.writeHead(hit ? hit[1].status : 404, { "content-type": "application/json" }); - res.end(JSON.stringify(hit ? hit[1].body : { error: "hub: segredo interno que NÃO pode vazar" })); + res.end( + JSON.stringify(hit ? hit[1].body : { error: "hub: segredo interno que NÃO pode vazar" }) + ); }); servers.push(server); return new Promise((resolve) => { @@ -32,7 +34,7 @@ function fakeHub(routes: Record): Pro test.beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); delete process.env.CONDUCTOR_HUB_URL; delete process.env.CONDUCTOR_HUB_TOKEN; @@ -40,7 +42,7 @@ test.beforeEach(() => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); delete process.env.CONDUCTOR_HUB_URL; while (servers.length > 0) { const s = servers.pop(); @@ -52,11 +54,26 @@ test("GET /api/conductor/fleet devolve snapshot whitelisted; sem hub → degrada process.env.CONDUCTOR_HUB_URL = await fakeHub({ "/v1/runners": { status: 200, - body: [{ id: "r_1", token: "VAZOU?", online: true, capabilities: { name: "devbox", clis: [{ profile: "claude" }] } }], + body: [ + { + id: "r_1", + token: "VAZOU?", + online: true, + capabilities: { name: "devbox", clis: [{ profile: "claude" }] }, + }, + ], }, "/v1/tasks": { status: 200, - body: [{ id: "t_1", status: "working", mode: "solo", repo: { url: "https://x/r" }, assigned_runner: "r_1" }], + body: [ + { + id: "t_1", + status: "working", + mode: "solo", + repo: { url: "https://x/r" }, + assigned_runner: "r_1", + }, + ], }, }); process.env.CONDUCTOR_HUB_TOKEN = "tok"; @@ -86,7 +103,10 @@ test("GET /api/conductor/tasks/[id] → 404 sanitizado quando o hub não conhece test("POST cancel repassa recusa do hub com status, sem corpo upstream", async () => { process.env.CONDUCTOR_HUB_URL = await fakeHub({ - "/v1/tasks/t_done/cancel": { status: 409, body: { error: "segredo interno que NÃO pode vazar" } }, + "/v1/tasks/t_done/cancel": { + status: 409, + body: { error: "segredo interno que NÃO pode vazar" }, + }, "/v1/tasks/t_ok/cancel": { status: 200, body: { ok: true } }, }); const denied = await cancelRoute.POST(new Request("http://localhost/x", { method: "POST" }), { diff --git a/tests/unit/config-audit-persistence.test.ts b/tests/unit/config-audit-persistence.test.ts index 4bad88bc5db4..12e5a1120b57 100644 --- a/tests/unit/config-audit-persistence.test.ts +++ b/tests/unit/config-audit-persistence.test.ts @@ -16,7 +16,7 @@ type CountRow = { c: number }; function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -59,11 +59,22 @@ test.after(() => { test("recordChange persists to SQLite, not memory", () => { const db = core.getDbInstance(); const tableRow = db - .prepare("SELECT count(*) as c FROM sqlite_master WHERE type='table' AND name='config_audit_log'") + .prepare( + "SELECT count(*) as c FROM sqlite_master WHERE type='table' AND name='config_audit_log'" + ) .get() as CountRow; assert.equal(tableRow.c, 1); - const e = audit.recordChange("update", "provider", "p1", "My Provider", { a: 1 }, { a: 2 }, "api", null); + const e = audit.recordChange( + "update", + "provider", + "p1", + "My Provider", + { a: 1 }, + { a: 2 }, + "api", + null + ); assert.equal(countRows(), 1); const { entries, total } = audit.getAuditLog({ target: "provider" }); @@ -74,7 +85,15 @@ test("recordChange persists to SQLite, not memory", () => { test("pagination + filters read from SQLite", () => { audit.recordChange("create", "combo", "c1", "C1", null, { models: ["m1"] }, "dashboard"); - audit.recordChange("update", "combo", "c1", "C1", { models: ["m1"] }, { models: ["m1", "m2"] }, "api"); + audit.recordChange( + "update", + "combo", + "c1", + "C1", + { models: ["m1"] }, + { models: ["m1", "m2"] }, + "api" + ); const { entries, total } = audit.getAuditLog({ target: "combo", limit: 1, offset: 0 }); assert.equal(total, 2); diff --git a/tests/unit/config-expiry-time-bomb.test.ts b/tests/unit/config-expiry-time-bomb.test.ts index 7740e09e5c5e..dbb837f2abdd 100644 --- a/tests/unit/config-expiry-time-bomb.test.ts +++ b/tests/unit/config-expiry-time-bomb.test.ts @@ -92,7 +92,7 @@ test("scanConfigExpiry: walks a config tree, skips node_modules and invalid JSON const found = scanConfigExpiry(dir).map((f) => `${f.file}:${f.keyPath}`); assert.deepEqual(found, ["a.json:validUntil", "sub/b.json:deep.expiresAt"]); } finally { - rmSync(dir, { recursive: true, force: true }); + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/config-hot-reload.test.ts b/tests/unit/config-hot-reload.test.ts index fabd87a500d6..195421ede1f4 100644 --- a/tests/unit/config-hot-reload.test.ts +++ b/tests/unit/config-hot-reload.test.ts @@ -45,7 +45,7 @@ async function resetStorage() { }); invalidateCacheControlSettingsCache(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } diff --git a/tests/unit/console-interceptor-message-fidelity.test.ts b/tests/unit/console-interceptor-message-fidelity.test.ts index ab1cd6f90edd..e3c8e2d2de11 100644 --- a/tests/unit/console-interceptor-message-fidelity.test.ts +++ b/tests/unit/console-interceptor-message-fidelity.test.ts @@ -64,7 +64,7 @@ test("the interceptor keeps the component and substitutes printf formats", () => assert.equal(plain, 'plain message {"a":1}'); } finally { __consoleInterceptorInternals.reset(); - fs.rmSync(LOG_DIR, { recursive: true, force: true }); + fs.rmSync(LOG_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -90,6 +90,6 @@ test("a first argument that coincidentally contains a printf token does not swal assert.ok(entry.includes(err.stack || ""), "Error stack was dropped"); } finally { __consoleInterceptorInternals.reset(); - fs.rmSync(LOG_DIR, { recursive: true, force: true }); + fs.rmSync(LOG_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/context-handoff.test.ts b/tests/unit/context-handoff.test.ts index a19b8734a801..8bbd7fac832b 100644 --- a/tests/unit/context-handoff.test.ts +++ b/tests/unit/context-handoff.test.ts @@ -13,7 +13,7 @@ const contextHandoff = await import("../../open-sse/services/contextHandoff.ts") async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -33,7 +33,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("buildHandoffSystemMessage and injectHandoffIntoBody preserve existing history", () => { diff --git a/tests/unit/context-manager.test.ts b/tests/unit/context-manager.test.ts index de95d5b6f2d3..17df8f9a255d 100644 --- a/tests/unit/context-manager.test.ts +++ b/tests/unit/context-manager.test.ts @@ -16,7 +16,7 @@ test.after(() => { core.resetDbInstance(); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ─── estimateTokens ───────────────────────────────────────────────────────── diff --git a/tests/unit/context-window-reconcile-persisted-overrides.test.ts b/tests/unit/context-window-reconcile-persisted-overrides.test.ts index 13285d398fd0..eeaf2e610c7a 100644 --- a/tests/unit/context-window-reconcile-persisted-overrides.test.ts +++ b/tests/unit/context-window-reconcile-persisted-overrides.test.ts @@ -16,13 +16,13 @@ const { runContextWindowReconcile } = await import("../../src/lib/contextWindowR test.beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); }); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("runContextWindowReconcile retains an auto override across repeated synced discovery", async () => { diff --git a/tests/unit/conversationTurnContent.test.ts b/tests/unit/conversationTurnContent.test.ts index adccdf3a8c95..8f941a061854 100644 --- a/tests/unit/conversationTurnContent.test.ts +++ b/tests/unit/conversationTurnContent.test.ts @@ -19,7 +19,7 @@ const { resolveTurnDisplayContent } = test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function insertCallLog(row: { id: string; correlationId: string; artifactRelPath: string | null }) { diff --git a/tests/unit/conversations-active-call-log-id.test.ts b/tests/unit/conversations-active-call-log-id.test.ts index 9e8473f5821f..843efe022b78 100644 --- a/tests/unit/conversations-active-call-log-id.test.ts +++ b/tests/unit/conversations-active-call-log-id.test.ts @@ -28,7 +28,7 @@ const route = await import("../../src/app/api/conversations/route.ts"); test.after(() => { core.resetDbInstance(); usageHistory.clearPendingRequests(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test.beforeEach(() => { diff --git a/tests/unit/cooldown-epoch-string-3954.test.ts b/tests/unit/cooldown-epoch-string-3954.test.ts index ddc3af23c7a5..7d8a19d27cf6 100644 --- a/tests/unit/cooldown-epoch-string-3954.test.ts +++ b/tests/unit/cooldown-epoch-string-3954.test.ts @@ -24,13 +24,12 @@ process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); const providersDb = await import("../../src/lib/db/providers.ts"); -const { isAccountUnavailable, getEarliestRateLimitedUntil, filterAvailableAccounts } = await import( - "../../open-sse/services/accountFallback.ts" -); +const { isAccountUnavailable, getEarliestRateLimitedUntil, filterAvailableAccounts } = + await import("../../open-sse/services/accountFallback.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); const HOUR = 3_600_000; diff --git a/tests/unit/correctness/goldenSnapshot.test.ts b/tests/unit/correctness/goldenSnapshot.test.ts index d7f804cea5ad..bc341cfe1078 100644 --- a/tests/unit/correctness/goldenSnapshot.test.ts +++ b/tests/unit/correctness/goldenSnapshot.test.ts @@ -23,7 +23,7 @@ test("goldenSnapshot writes on first run then matches", (t) => { assert.throws(() => goldenSnapshot("selftest/sample", { a: 1, b: 3 }, tmpDir)); // Cleanup - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("goldenSnapshot first-run (no UPDATE_GOLDEN) writes and passes", () => { @@ -36,6 +36,6 @@ test("goldenSnapshot first-run (no UPDATE_GOLDEN) writes and passes", () => { // File exists: different value should throw assert.throws(() => goldenSnapshot("test/value", { x: 99 }, td)); } finally { - fs.rmSync(td, { recursive: true, force: true }); + fs.rmSync(td, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/cursor-agent-availability-route-authenticated.test.ts b/tests/unit/cursor-agent-availability-route-authenticated.test.ts index 1959910495ad..3472e5b15b4a 100644 --- a/tests/unit/cursor-agent-availability-route-authenticated.test.ts +++ b/tests/unit/cursor-agent-availability-route-authenticated.test.ts @@ -22,7 +22,7 @@ const { GET } = await import("../../src/app/api/providers/cursor/agent-availabil test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); const FAKE_CURSOR_AGENT_SCRIPT = `#!/usr/bin/env node @@ -46,7 +46,7 @@ test.after(() => { process.env.HOME = originalHome; if (originalUserProfile !== undefined) process.env.USERPROFILE = originalUserProfile; else delete process.env.USERPROFILE; - fs.rmSync(tmpHome, { recursive: true, force: true }); + fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("returns {cursorAgentAvailable: true} and ONLY that field when cursor-agent is authenticated", async () => { diff --git a/tests/unit/cursor-agent-availability-route.test.ts b/tests/unit/cursor-agent-availability-route.test.ts index c395a228d997..6b9a5685f2cf 100644 --- a/tests/unit/cursor-agent-availability-route.test.ts +++ b/tests/unit/cursor-agent-availability-route.test.ts @@ -50,7 +50,7 @@ if (args[0] === "status") { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function writeFakeCursorAgentBinary(destPath: string): void { @@ -72,7 +72,7 @@ test.after(() => { if (originalUserProfile !== undefined) process.env.USERPROFILE = originalUserProfile; else delete process.env.USERPROFILE; delete process.env.FAKE_CURSOR_AGENT_STATUS_MODE; - fs.rmSync(tmpHome, { recursive: true, force: true }); + fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("returns {cursorAgentAvailable: false} and ONLY that field when cursor-agent is unauthenticated", async () => { diff --git a/tests/unit/cursor-agent-cli-version.test.ts b/tests/unit/cursor-agent-cli-version.test.ts index abd403175606..4ec68f69e49e 100644 --- a/tests/unit/cursor-agent-cli-version.test.ts +++ b/tests/unit/cursor-agent-cli-version.test.ts @@ -59,7 +59,7 @@ test("newestVersionInDir picks lexicographically newest matching child", () => { fs.mkdirSync(path.join(tmp, "3.9.0")); assert.equal(newestVersionInDir(tmp), "2026.07.08-0c04a8a"); } finally { - fs.rmSync(tmp, { recursive: true, force: true }); + fs.rmSync(tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -79,7 +79,7 @@ test("detectCursorAgentCliVersionFromFs uses shim realpath under versions/", assert.equal(detectCursorAgentCliVersionFromFs(home), id); }); } finally { - fs.rmSync(home, { recursive: true, force: true }); + fs.rmSync(home, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -93,8 +93,8 @@ test("detectCursorAgentCliVersionFromFs uses CURSOR_DATA_DIR versions when no sh assert.equal(detectCursorAgentCliVersionFromFs(home), id); }); } finally { - fs.rmSync(home, { recursive: true, force: true }); - fs.rmSync(data, { recursive: true, force: true }); + fs.rmSync(home, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.rmSync(data, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -123,7 +123,7 @@ test("getCursorAgentCliVersion ignores invalid env and uses pin when FS empty", ); } finally { resetCursorAgentCliVersionCache(); - fs.rmSync(home, { recursive: true, force: true }); + fs.rmSync(home, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -160,8 +160,8 @@ test("getCursorAgentCliVersion reads CURSOR_DATA_DIR via isolated HOME", () => { ); } finally { resetCursorAgentCliVersionCache(); - fs.rmSync(home, { recursive: true, force: true }); - fs.rmSync(data, { recursive: true, force: true }); + fs.rmSync(home, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.rmSync(data, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -209,8 +209,8 @@ test("disk cache hit serves immediately without blocking on network", async () = ); } finally { resetCursorAgentCliVersionTestHooks(); - fs.rmSync(cacheDir, { recursive: true, force: true }); - fs.rmSync(home, { recursive: true, force: true }); + fs.rmSync(cacheDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.rmSync(home, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -235,7 +235,7 @@ test("refreshCursorAgentCliVersionFromInstaller writes disk cache from HTML", as assert.equal(onDisk.version, scrapedId); } finally { resetCursorAgentCliVersionTestHooks(); - fs.rmSync(cacheDir, { recursive: true, force: true }); + fs.rmSync(cacheDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -264,7 +264,7 @@ test("invalid installer HTML falls through to pin", async () => { assert.equal(id, null); } finally { resetCursorAgentCliVersionTestHooks(); - fs.rmSync(cacheDir, { recursive: true, force: true }); - fs.rmSync(home, { recursive: true, force: true }); + fs.rmSync(cacheDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.rmSync(home, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/cursor-agent-models.test.ts b/tests/unit/cursor-agent-models.test.ts index 7b03ff95189a..1d72c79834fe 100644 --- a/tests/unit/cursor-agent-models.test.ts +++ b/tests/unit/cursor-agent-models.test.ts @@ -123,7 +123,7 @@ describe("resolveCursorAgentBinary", () => { if (ORIGINAL_USERPROFILE !== undefined) process.env.USERPROFILE = ORIGINAL_USERPROFILE; else delete process.env.USERPROFILE; process.env.PATH = ORIGINAL_PATH; - fs.rmSync(tmpHome, { recursive: true, force: true }); + fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); it("finds the HOME-relative fixed candidate (~/.local/bin/cursor-agent) with allowPathFallback:false", () => { @@ -152,7 +152,7 @@ describe("resolveCursorAgentBinary", () => { assert.equal(resolveCursorAgentBinary({ allowPathFallback: false }), fixedBinary); assert.equal(resolveCursorAgentBinary({ allowPathFallback: true }), fixedBinary); } finally { - fs.rmSync(pathDir, { recursive: true, force: true }); + fs.rmSync(pathDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -175,7 +175,7 @@ describe("resolveCursorAgentBinary", () => { assert.equal(resolveCursorAgentBinary({ allowPathFallback: true }), pathOnlyBinary); assert.equal(resolveCursorAgentBinary(), pathOnlyBinary); } finally { - fs.rmSync(pathDir, { recursive: true, force: true }); + fs.rmSync(pathDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -198,7 +198,7 @@ describe("resolveCursorAgentBinary", () => { try { assert.equal(resolveCursorAgentBinary({ allowPathFallback: false }), null); } finally { - fs.rmSync(pathDir, { recursive: true, force: true }); + fs.rmSync(pathDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } ); @@ -261,7 +261,7 @@ if (selfExitMs) { afterEach(() => { delete process.env.FAKE_BIN_SELF_EXIT_MS; - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); it("sends SIGKILL after the follow-up window when the process ignores SIGTERM", async () => { diff --git a/tests/unit/cursor-renewal.test.ts b/tests/unit/cursor-renewal.test.ts index fcb480bca134..242570b8eaf0 100644 --- a/tests/unit/cursor-renewal.test.ts +++ b/tests/unit/cursor-renewal.test.ts @@ -120,7 +120,7 @@ describe("runCursorAgentNudge", () => { afterEach(() => { clearFakeCursorAgentEnv(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); it('invokes the binary with exactly ["--list-models"] and never "login"', async () => { @@ -180,7 +180,7 @@ describe("checkCursorAgentAvailability", () => { if (ORIGINAL_USERPROFILE !== undefined) process.env.USERPROFILE = ORIGINAL_USERPROFILE; else delete process.env.USERPROFILE; clearFakeCursorAgentEnv(); - fs.rmSync(tmpHome, { recursive: true, force: true }); + fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); it("reports available:true when the resolved binary is authenticated", async () => { @@ -298,7 +298,7 @@ describe("getCachedCursorAgentAvailability (Task 5 Step 1 — 5-minute TTL wrapp if (ORIGINAL_USERPROFILE !== undefined) process.env.USERPROFILE = ORIGINAL_USERPROFILE; else delete process.env.USERPROFILE; clearFakeCursorAgentEnv(); - fs.rmSync(tmpHome, { recursive: true, force: true }); + fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // A single test, one continuous mocked timeline: getCachedCursorAgentAvailability()'s @@ -364,7 +364,7 @@ describe("renewCursorConnection", () => { if (ORIGINAL_USERPROFILE !== undefined) process.env.USERPROFILE = ORIGINAL_USERPROFILE; else delete process.env.USERPROFILE; clearFakeCursorAgentEnv(); - fs.rmSync(tmpHome, { recursive: true, force: true }); + fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); async function writeIdeToken(accessToken: string, machineId?: string): Promise { diff --git a/tests/unit/cursor-token-extractor.test.ts b/tests/unit/cursor-token-extractor.test.ts index 986d8dd0ad4f..70b60dc95c6a 100644 --- a/tests/unit/cursor-token-extractor.test.ts +++ b/tests/unit/cursor-token-extractor.test.ts @@ -236,7 +236,7 @@ describe("tryAgentAuth", () => { } else { delete process.env.USERPROFILE; } - fs.rmSync(tmpHome, { recursive: true, force: true }); + fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); it("finds a token in the primary auth.json candidate", async () => { @@ -336,7 +336,7 @@ describe("tryIdeAuth", () => { delete process.env.USERPROFILE; } if (tmpHome) { - fs.rmSync(tmpHome, { recursive: true, force: true }); + fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); tmpHome = undefined; } }); diff --git a/tests/unit/cursor-version-detector.test.mjs b/tests/unit/cursor-version-detector.test.mjs index 6fef869a9b53..fc70e70555ac 100644 --- a/tests/unit/cursor-version-detector.test.mjs +++ b/tests/unit/cursor-version-detector.test.mjs @@ -45,7 +45,7 @@ test("getCursorVersion reads version from state.vscdb", () => { } finally { if (origEnv === undefined) delete process.env.CURSOR_STATE_DB_PATH; else process.env.CURSOR_STATE_DB_PATH = origEnv; - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -74,7 +74,7 @@ test("getCursorVersion returns fallback when DB has no version key", () => { } finally { if (origEnv === undefined) delete process.env.CURSOR_STATE_DB_PATH; else process.env.CURSOR_STATE_DB_PATH = origEnv; - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -100,7 +100,7 @@ test("getCursorVersion caches the result across calls", () => { } finally { if (origEnv === undefined) delete process.env.CURSOR_STATE_DB_PATH; else process.env.CURSOR_STATE_DB_PATH = origEnv; - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -126,6 +126,6 @@ test("resetCursorVersionCache forces re-read from DB", () => { } finally { if (origEnv === undefined) delete process.env.CURSOR_STATE_DB_PATH; else process.env.CURSOR_STATE_DB_PATH = origEnv; - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/custom-headers-provider-nodes.test.ts b/tests/unit/custom-headers-provider-nodes.test.ts index 17646b37f95f..651ff02ff125 100644 --- a/tests/unit/custom-headers-provider-nodes.test.ts +++ b/tests/unit/custom-headers-provider-nodes.test.ts @@ -18,7 +18,7 @@ const { DefaultExecutor } = await import("../../open-sse/executors/default.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -44,7 +44,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("createProviderNodeSchema accepts valid customHeaders as record of strings", () => { diff --git a/tests/unit/custom-model-target-format.test.ts b/tests/unit/custom-model-target-format.test.ts index 062a97566d81..6382e367b2de 100644 --- a/tests/unit/custom-model-target-format.test.ts +++ b/tests/unit/custom-model-target-format.test.ts @@ -38,7 +38,7 @@ test.before(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#2905 addCustomModel persists targetFormat", async () => { diff --git a/tests/unit/dashscope-text-models-discovery.test.ts b/tests/unit/dashscope-text-models-discovery.test.ts index 5e62ce76137e..bef519cc29ab 100644 --- a/tests/unit/dashscope-text-models-discovery.test.ts +++ b/tests/unit/dashscope-text-models-discovery.test.ts @@ -73,7 +73,7 @@ const MIXED_DASHSCOPE_MODELS = [ async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -139,7 +139,7 @@ async function assertTextOnlyDiscovery({ test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("Qwen Cloud syncs only text models from the selected Beijing region", async () => { diff --git a/tests/unit/data-dir-writable-fallback.test.ts b/tests/unit/data-dir-writable-fallback.test.ts index 70421108f556..57cbf1def82b 100644 --- a/tests/unit/data-dir-writable-fallback.test.ts +++ b/tests/unit/data-dir-writable-fallback.test.ts @@ -15,9 +15,7 @@ import { const IS_ROOT = typeof process.getuid === "function" && process.getuid() === 0; const IS_WINDOWS = process.platform === "win32"; -async function withTempEnv( - fn: (paths: { root: string; home: string }) => void | Promise -) { +async function withTempEnv(fn: (paths: { root: string; home: string }) => void | Promise) { const originalEnv = { ...process.env }; const root = fs.mkdtempSync(path.join(os.tmpdir(), "omni-datadir-")); const home = path.join(root, "home"); @@ -44,7 +42,7 @@ async function withTempEnv( } catch { // ignore } - fs.rmSync(root, { recursive: true, force: true }); + fs.rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } @@ -60,26 +58,30 @@ test("resolveWritableDataDir returns the configured DATA_DIR when it is writable }); }); -test("resolveWritableDataDir falls back to the default dir when DATA_DIR is not writable (EACCES/EPERM)", { skip: IS_ROOT || IS_WINDOWS }, async () => { - await withTempEnv(({ root, home }) => { - // A read-only parent makes mkdir of the child fail with EACCES/EPERM. - const lockedParent = path.join(root, "locked"); - fs.mkdirSync(lockedParent, { recursive: true }); - fs.chmodSync(lockedParent, 0o555); - - const configured = path.join(lockedParent, "data"); - process.env.DATA_DIR = configured; - - const resolved = resolveWritableDataDir(); - const expectedFallback = getDefaultDataDir(); - - // It must NOT return the unwritable configured dir... - assert.notEqual(resolved, path.resolve(configured)); - // ...and instead fall back to the default user dir (~/.omniroute under HOME). - assert.equal(resolved, expectedFallback); - assert.ok(resolved.startsWith(path.resolve(home))); - }); -}); +test( + "resolveWritableDataDir falls back to the default dir when DATA_DIR is not writable (EACCES/EPERM)", + { skip: IS_ROOT || IS_WINDOWS }, + async () => { + await withTempEnv(({ root, home }) => { + // A read-only parent makes mkdir of the child fail with EACCES/EPERM. + const lockedParent = path.join(root, "locked"); + fs.mkdirSync(lockedParent, { recursive: true }); + fs.chmodSync(lockedParent, 0o555); + + const configured = path.join(lockedParent, "data"); + process.env.DATA_DIR = configured; + + const resolved = resolveWritableDataDir(); + const expectedFallback = getDefaultDataDir(); + + // It must NOT return the unwritable configured dir... + assert.notEqual(resolved, path.resolve(configured)); + // ...and instead fall back to the default user dir (~/.omniroute under HOME). + assert.equal(resolved, expectedFallback); + assert.ok(resolved.startsWith(path.resolve(home))); + }); + } +); test("resolveWritableDataDir returns the default dir (no probe) when DATA_DIR is unset", async () => { await withTempEnv(() => { @@ -105,9 +107,12 @@ test("resolveWritableDataDir rethrows non-permission errors", { skip: IS_WINDOWS const configured = path.join(fileParent, "data"); process.env.DATA_DIR = configured; - assert.throws(() => resolveWritableDataDir(), (err: NodeJS.ErrnoException) => { - return err.code !== "EACCES" && err.code !== "EPERM"; - }); + assert.throws( + () => resolveWritableDataDir(), + (err: NodeJS.ErrnoException) => { + return err.code !== "EACCES" && err.code !== "EPERM"; + } + ); }); }); diff --git a/tests/unit/database-settings-maintenance.test.ts b/tests/unit/database-settings-maintenance.test.ts index a6c3d69e8375..d5923d621ffb 100644 --- a/tests/unit/database-settings-maintenance.test.ts +++ b/tests/unit/database-settings-maintenance.test.ts @@ -30,7 +30,7 @@ type UsageSummaryRow = { function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } diff --git a/tests/unit/datadir-test-context-guard-10428.test.ts b/tests/unit/datadir-test-context-guard-10428.test.ts index e957a490fa19..93fad86c73bb 100644 --- a/tests/unit/datadir-test-context-guard-10428.test.ts +++ b/tests/unit/datadir-test-context-guard-10428.test.ts @@ -40,19 +40,19 @@ function withEnv(overrides: Record, run: () => void) } test("G1: a test context with no DATA_DIR never resolves to the operator's real data dir", () => { - withEnv({ DATA_DIR: undefined, NODE_ENV: "test", OMNIROUTE_ALLOW_DEFAULT_DATA_DIR: undefined }, () => { - const resolved = resolveWritableDataDir(); - assert.notEqual( - resolved, - getDefaultDataDir(), - "a test run must never be handed the operator's real DATA_DIR" - ); - assert.ok( - resolved.startsWith(os.tmpdir()), - `expected a throwaway temp dir, got ${resolved}` - ); - assert.ok(fs.existsSync(resolved), "the redirected dir must exist and be usable"); - }); + withEnv( + { DATA_DIR: undefined, NODE_ENV: "test", OMNIROUTE_ALLOW_DEFAULT_DATA_DIR: undefined }, + () => { + const resolved = resolveWritableDataDir(); + assert.notEqual( + resolved, + getDefaultDataDir(), + "a test run must never be handed the operator's real DATA_DIR" + ); + assert.ok(resolved.startsWith(os.tmpdir()), `expected a throwaway temp dir, got ${resolved}`); + assert.ok(fs.existsSync(resolved), "the redirected dir must exist and be usable"); + } + ); }); test("G2: an explicit DATA_DIR still wins inside a test context", () => { @@ -60,20 +60,17 @@ test("G2: an explicit DATA_DIR still wins inside a test context", () => { withEnv({ DATA_DIR: explicit, NODE_ENV: "test" }, () => { assert.equal(resolveWritableDataDir(), explicit); }); - fs.rmSync(explicit, { recursive: true, force: true }); + fs.rmSync(explicit, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("G3: the escape hatch restores the old behavior for deliberate runs", () => { - withEnv( - { DATA_DIR: undefined, NODE_ENV: "test", OMNIROUTE_ALLOW_DEFAULT_DATA_DIR: "1" }, - () => { - assert.equal( - resolveWritableDataDir(), - getDefaultDataDir(), - "an explicit opt-in must still reach the real dir, so the intent is recorded" - ); - } - ); + withEnv({ DATA_DIR: undefined, NODE_ENV: "test", OMNIROUTE_ALLOW_DEFAULT_DATA_DIR: "1" }, () => { + assert.equal( + resolveWritableDataDir(), + getDefaultDataDir(), + "an explicit opt-in must still reach the real dir, so the intent is recorded" + ); + }); }); test("G4: a normal server run (no test markers) is untouched", () => { diff --git a/tests/unit/db-adapters/driverFactory.test.ts b/tests/unit/db-adapters/driverFactory.test.ts index b73ea87a6f89..e4383ea9556a 100644 --- a/tests/unit/db-adapters/driverFactory.test.ts +++ b/tests/unit/db-adapters/driverFactory.test.ts @@ -7,7 +7,6 @@ import { createRequire } from "node:module"; import type * as NodePath from "node:path"; import { runtimeRequire } from "../../../src/lib/db/adapters/runtimeRequire.ts"; - const { createSyncDriverFactory, createBetterSqliteProbe, @@ -33,11 +32,10 @@ function forceNodeSqlite() { function createTempDatabasePath(t: TestContext) { const dir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-node-sqlite-")); const databasePath = path.join(dir, "database.sqlite"); - t.after(() => fs.rmSync(dir, { recursive: true, force: true })); + t.after(() => fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 })); return databasePath; } - describe("driverFactory", () => { test("runtimeRequire loads Node built-ins outside webpack", () => { const nodePath = runtimeRequire("node:path") as typeof NodePath; diff --git a/tests/unit/db-agent-bridge-bypass.test.ts b/tests/unit/db-agent-bridge-bypass.test.ts index a45e8f883159..b85cee532a01 100644 --- a/tests/unit/db-agent-bridge-bypass.test.ts +++ b/tests/unit/db-agent-bridge-bypass.test.ts @@ -4,9 +4,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -const TEST_DATA_DIR = fs.mkdtempSync( - path.join(os.tmpdir(), "omniroute-db-agent-bridge-bypass-") -); +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-db-agent-bridge-bypass-")); process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); @@ -18,7 +16,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: any) { @@ -39,7 +37,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); const DEFAULT_PATTERNS = [ diff --git a/tests/unit/db-agent-bridge-mappings.test.ts b/tests/unit/db-agent-bridge-mappings.test.ts index 5c03d24fa4b9..83ec3baf1c9e 100644 --- a/tests/unit/db-agent-bridge-mappings.test.ts +++ b/tests/unit/db-agent-bridge-mappings.test.ts @@ -4,9 +4,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -const TEST_DATA_DIR = fs.mkdtempSync( - path.join(os.tmpdir(), "omniroute-db-agent-bridge-mappings-") -); +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-db-agent-bridge-mappings-")); process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); @@ -18,7 +16,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: any) { @@ -39,7 +37,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("getMappingsForAgent returns empty array when no mappings exist", () => { diff --git a/tests/unit/db-agent-bridge-state.test.ts b/tests/unit/db-agent-bridge-state.test.ts index 919c17646bee..ec2d262e442f 100644 --- a/tests/unit/db-agent-bridge-state.test.ts +++ b/tests/unit/db-agent-bridge-state.test.ts @@ -16,7 +16,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: any) { @@ -37,7 +37,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("migration is idempotent — running getDbInstance twice does not throw", () => { diff --git a/tests/unit/db-apiKeys-crud.test.ts b/tests/unit/db-apiKeys-crud.test.ts index c7dc35e15aab..f1c4bfcbd877 100644 --- a/tests/unit/db-apiKeys-crud.test.ts +++ b/tests/unit/db-apiKeys-crud.test.ts @@ -31,7 +31,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch { @@ -65,10 +65,9 @@ test("createApiKey with scopes stores them", async () => { test("createApiKey rejects empty machineId", async () => { await resetStorage(); - await assert.rejects( - () => apiKeys.createApiKey("Bad Key", ""), - { message: /machineId is required/i } - ); + await assert.rejects(() => apiKeys.createApiKey("Bad Key", ""), { + message: /machineId is required/i, + }); }); // ──────────────── getApiKeys ──────────────── @@ -375,7 +374,10 @@ test("updateApiKeyPermissions clears accessSchedule with null", async () => { test("updateApiKeyPermissions sets rateLimits", async () => { await resetStorage(); const created = await apiKeys.createApiKey("Rate Limited", "ma-026"); - const limits = [{ limit: 100, window: 60 }, { limit: 1000, window: 3600 }]; + const limits = [ + { limit: 100, window: 60 }, + { limit: 1000, window: 3600 }, + ]; await apiKeys.updateApiKeyPermissions(created.id, { rateLimits: limits }); const loaded = await apiKeys.getApiKeyById(created.id); assert.deepEqual(loaded!.rateLimits, limits); diff --git a/tests/unit/db-backup-autobackup-setting-5871.test.ts b/tests/unit/db-backup-autobackup-setting-5871.test.ts index e9ed86365c1e..bb8704076c76 100644 --- a/tests/unit/db-backup-autobackup-setting-5871.test.ts +++ b/tests/unit/db-backup-autobackup-setting-5871.test.ts @@ -29,7 +29,7 @@ const databaseSettings = await import("../../src/lib/db/databaseSettings.ts"); function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -40,7 +40,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("fresh install (seeded default autoBackupEnabled=false) → auto backups disabled", () => { diff --git a/tests/unit/db-backup-extended.test.ts b/tests/unit/db-backup-extended.test.ts index d4f9f2646319..4ed9a08064e1 100644 --- a/tests/unit/db-backup-extended.test.ts +++ b/tests/unit/db-backup-extended.test.ts @@ -21,12 +21,12 @@ async function resetStorage() { const targetPath = path.join(TEST_DATA_DIR, entry); const stat = fs.lstatSync(targetPath); if (stat.isDirectory()) { - fs.rmSync(targetPath, { recursive: true, force: true }); + fs.rmSync(targetPath, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } else { await backupDb.unlinkFileWithRetry(targetPath, { maxAttempts: 20, baseDelayMs: 25 }); } } - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -78,7 +78,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("backupDbFile creates manual backups and listDbBackups returns metadata", async () => { @@ -98,7 +98,7 @@ test("backupDbFile creates manual backups and listDbBackups returns metadata", a }); test("listDbBackups returns an empty list when the backup directory is missing", async () => { - fs.rmSync(core.DB_BACKUPS_DIR, { recursive: true, force: true }); + fs.rmSync(core.DB_BACKUPS_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); const backups = await backupDb.listDbBackups(); assert.deepEqual(backups, []); }); diff --git a/tests/unit/db-backups-skills-3500.test.ts b/tests/unit/db-backups-skills-3500.test.ts index a2f0118ab2fc..bf0c0d50cfc3 100644 --- a/tests/unit/db-backups-skills-3500.test.ts +++ b/tests/unit/db-backups-skills-3500.test.ts @@ -161,9 +161,7 @@ test("exportAllSummaryRows — returns provider_connections rows (no credentials const { providers } = backupMod.exportAllSummaryRows(); - const found = (providers as Array<{ id: string; provider: string }>).find( - (p) => p.id === connId - ); + const found = (providers as Array<{ id: string; provider: string }>).find((p) => p.id === connId); assert.ok(found, "providers must include seeded row"); assert.equal(found?.provider, "openai"); // Sensitive credential columns must NOT be exported — the query only selects @@ -229,7 +227,7 @@ test.after(() => { /* best effort */ } try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { /* best effort */ } diff --git a/tests/unit/db-call-log-stats-3500.test.ts b/tests/unit/db-call-log-stats-3500.test.ts index 5216132d8bdc..b4d7b48ae06d 100644 --- a/tests/unit/db-call-log-stats-3500.test.ts +++ b/tests/unit/db-call-log-stats-3500.test.ts @@ -85,7 +85,7 @@ test.before(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // --------------------------------------------------------------------------- @@ -97,12 +97,16 @@ test("#3500 getProviderMetrics — aggregates totals and latency per provider", // provider_connections row — seed openai/anthropic connections so their // call_logs rows are not filtered out as ghost/deleted providers. const db0 = core.getDbInstance(); - db0.prepare( - `INSERT INTO provider_connections (id, provider, created_at, updated_at) VALUES (?, ?, ?, ?)` - ).run("conn-3500-openai", "openai", new Date().toISOString(), new Date().toISOString()); - db0.prepare( - `INSERT INTO provider_connections (id, provider, created_at, updated_at) VALUES (?, ?, ?, ?)` - ).run("conn-3500-anthropic", "anthropic", new Date().toISOString(), new Date().toISOString()); + db0 + .prepare( + `INSERT INTO provider_connections (id, provider, created_at, updated_at) VALUES (?, ?, ?, ?)` + ) + .run("conn-3500-openai", "openai", new Date().toISOString(), new Date().toISOString()); + db0 + .prepare( + `INSERT INTO provider_connections (id, provider, created_at, updated_at) VALUES (?, ?, ?, ?)` + ) + .run("conn-3500-anthropic", "anthropic", new Date().toISOString(), new Date().toISOString()); // Two openai rows: one success, one error with error_summary const ts1 = "2025-06-01T10:00:00.000Z"; @@ -121,11 +125,14 @@ test("#3500 getProviderMetrics — aggregates totals and latency per provider", // Provider '-' should be excluded insertCallLog({ provider: "-", status: 200 }); // Provider null should be excluded (insert directly to avoid type issue) - core.getDbInstance().prepare( - `INSERT INTO call_logs (id, timestamp, method, path, status, model, provider, duration, + core + .getDbInstance() + .prepare( + `INSERT INTO call_logs (id, timestamp, method, path, status, model, provider, duration, tokens_in, tokens_out, cache_source, detail_state, has_request_body, has_response_body, has_pipeline_details) VALUES (?, ?, 'POST', '/v1/test', 200, 'x', NULL, 100, 0, 0, 'upstream', 'none', 0, 0, 0)` - ).run(`log-3500-null-${++_idSeq}`, new Date().toISOString()); + ) + .run(`log-3500-null-${++_idSeq}`, new Date().toISOString()); const rows = mod.getProviderMetrics(); @@ -213,12 +220,36 @@ test("#3500 getSearchAggregateStats — correct totals, today, errors, avg, cach // Rows inserted after todayStart qualify as "today" const nowIso = new Date().toISOString(); // duration=0 → excluded from avg_duration; duration=3 → cached (>0 && <5) - insertCallLog({ provider: "brave", status: 200, duration: 100, request_type: "search", timestamp: nowIso }); - insertCallLog({ provider: "brave", status: 200, duration: 3, request_type: "search", timestamp: nowIso }); - insertCallLog({ provider: "brave", status: 500, duration: 80, request_type: "search", timestamp: nowIso }); + insertCallLog({ + provider: "brave", + status: 200, + duration: 100, + request_type: "search", + timestamp: nowIso, + }); + insertCallLog({ + provider: "brave", + status: 200, + duration: 3, + request_type: "search", + timestamp: nowIso, + }); + insertCallLog({ + provider: "brave", + status: 500, + duration: 80, + request_type: "search", + timestamp: nowIso, + }); // Old row (yesterday) — not in today count const yesterday = new Date(Date.now() - 86_400_000).toISOString(); - insertCallLog({ provider: "brave", status: 200, duration: 200, request_type: "search", timestamp: yesterday }); + insertCallLog({ + provider: "brave", + status: 200, + duration: 200, + request_type: "search", + timestamp: yesterday, + }); const result = mod.getSearchAggregateStats(todayIso); @@ -297,9 +328,7 @@ test("getProviderUsageSince — only counts rows inside the window", () => { insertCallLog({ provider: "usage-window", status: 200, timestamp: OUT_OF_WINDOW }); insertCallLog({ provider: "usage-window", status: 500, timestamp: OUT_OF_WINDOW }); - const row = mod - .getProviderUsageSince(USAGE_CUTOFF) - .find((r) => r.provider === "usage-window"); + const row = mod.getProviderUsageSince(USAGE_CUTOFF).find((r) => r.provider === "usage-window"); assert.ok(row, "provider must be present"); assert.equal(row.requests, 2, "rows before the cutoff must not be counted"); assert.equal(row.successes, 2); @@ -314,9 +343,7 @@ test("getProviderUsageSince — 2xx/3xx count as success, 4xx/5xx do not", () => insertCallLog({ provider: "usage-status", status, timestamp: IN_WINDOW }); } - const row = mod - .getProviderUsageSince(USAGE_CUTOFF) - .find((r) => r.provider === "usage-status"); + const row = mod.getProviderUsageSince(USAGE_CUTOFF).find((r) => r.provider === "usage-status"); assert.ok(row); assert.equal(row.requests, 8); assert.equal(row.successes, 4, "same success rule as getProviderMetrics"); @@ -349,9 +376,7 @@ test("getProviderUsageSince — latency and lastRequestAt are bounded by the win timestamp: OUT_OF_WINDOW, }); - const row = mod - .getProviderUsageSince(USAGE_CUTOFF) - .find((r) => r.provider === "usage-latency"); + const row = mod.getProviderUsageSince(USAGE_CUTOFF).find((r) => r.provider === "usage-latency"); assert.ok(row); assert.equal(row.avgLatencyMs, 100, "the out-of-window 900ms row must not weigh in"); assert.equal(row.lastRequestAt, IN_WINDOW); @@ -362,6 +387,12 @@ test("getProviderUsageSince — providers '-' and NULL are excluded", () => { insertCallLog({ provider: "-", status: 200, timestamp: IN_WINDOW }); const rows = mod.getProviderUsageSince(USAGE_CUTOFF); - assert.equal(rows.find((r) => r.provider === "-"), undefined); - assert.equal(rows.find((r) => r.provider === null), undefined); + assert.equal( + rows.find((r) => r.provider === "-"), + undefined + ); + assert.equal( + rows.find((r) => r.provider === null), + undefined + ); }); diff --git a/tests/unit/db-ccr-migration-renumber-134.test.ts b/tests/unit/db-ccr-migration-renumber-134.test.ts index 2be3b40c98a1..edbdd23f6eb5 100644 --- a/tests/unit/db-ccr-migration-renumber-134.test.ts +++ b/tests/unit/db-ccr-migration-renumber-134.test.ts @@ -49,7 +49,7 @@ function createLegacyDb(appliedName: string) { } test.after(() => { - fs.rmSync(migrationsDir, { recursive: true, force: true }); + fs.rmSync(migrationsDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (originalMigrationsDir === undefined) delete process.env.OMNIROUTE_MIGRATIONS_DIR; else process.env.OMNIROUTE_MIGRATIONS_DIR = originalMigrationsDir; }); diff --git a/tests/unit/db-cleanup-xp-audit-log.test.ts b/tests/unit/db-cleanup-xp-audit-log.test.ts index 630acaf17c0f..7fe70478456e 100644 --- a/tests/unit/db-cleanup-xp-audit-log.test.ts +++ b/tests/unit/db-cleanup-xp-audit-log.test.ts @@ -19,7 +19,7 @@ type CountRow = { function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } diff --git a/tests/unit/db-combos-crud.test.ts b/tests/unit/db-combos-crud.test.ts index 986ab7d76e51..f610197b443f 100644 --- a/tests/unit/db-combos-crud.test.ts +++ b/tests/unit/db-combos-crud.test.ts @@ -16,7 +16,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: any) { @@ -37,7 +37,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("createCombo stores default strategy and supports lookup by id and name", async () => { diff --git a/tests/unit/db-command-code-auth.test.ts b/tests/unit/db-command-code-auth.test.ts index 266aa7c333d3..6b67dd28fd8b 100644 --- a/tests/unit/db-command-code-auth.test.ts +++ b/tests/unit/db-command-code-auth.test.ts @@ -12,7 +12,7 @@ const commandCodeAuthDb = await import("../../src/lib/db/commandCodeAuth.ts"); async function resetStorage() { coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -22,7 +22,7 @@ test.beforeEach(async () => { test.after(() => { coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("status lookup expires stale pending command-code auth sessions", () => { diff --git a/tests/unit/db-core-extended.test.ts b/tests/unit/db-core-extended.test.ts index b38b567d06b6..95516bcfe78b 100644 --- a/tests/unit/db-core-extended.test.ts +++ b/tests/unit/db-core-extended.test.ts @@ -24,7 +24,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch { diff --git a/tests/unit/db-core-init.test.ts b/tests/unit/db-core-init.test.ts index e93736e2aff9..52105627257f 100644 --- a/tests/unit/db-core-init.test.ts +++ b/tests/unit/db-core-init.test.ts @@ -50,7 +50,7 @@ function makeTempDir(prefix) { } function removePath(targetPath) { - fs.rmSync(targetPath, { recursive: true, force: true }); + fs.rmSync(targetPath, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } async function importFresh(modulePath) { @@ -540,35 +540,39 @@ test("build phase returns the no-op stub without creating sqlite files", serial, } }); -test("invalid DATA_DIR (a file where a dir is expected) surfaces as a startup failure", serial, async () => { - const sandboxDir = makeTempDir("omniroute-db-bad-path-"); - const fileAsDir = path.join(sandboxDir, "not-a-directory"); - fs.writeFileSync(fileAsDir, "blocked"); +test( + "invalid DATA_DIR (a file where a dir is expected) surfaces as a startup failure", + serial, + async () => { + const sandboxDir = makeTempDir("omniroute-db-bad-path-"); + const fileAsDir = path.join(sandboxDir, "not-a-directory"); + fs.writeFileSync(fileAsDir, "blocked"); - try { - // Since #4767, db/core.ts resolves a writable data dir at module load via - // resolveWritableDataDir() → mkdirSync(recursive). Pointing DATA_DIR at a - // regular file is a non-permission misconfiguration (EEXIST/ENOTDIR), which - // resolveWritableDataDir rethrows by design (only EACCES/EPERM fall back), so - // the failure now surfaces at import time, not lazily from getDbInstance(). - let caught: unknown; - await withEnv({ DATA_DIR: fileAsDir }, () => importFresh("src/lib/db/core.ts")).then( - () => { - throw new Error("expected importing db/core with an invalid DATA_DIR to reject"); - }, - (err) => { - caught = err; - } - ); - assert.ok(caught instanceof Error, "an invalid DATA_DIR must surface as a thrown Error"); - assert.match( - String((caught as Error).message), - /unable to open database file|ENOTDIR|EEXIST|not a directory|file already exists/i - ); - } finally { - removePath(sandboxDir); + try { + // Since #4767, db/core.ts resolves a writable data dir at module load via + // resolveWritableDataDir() → mkdirSync(recursive). Pointing DATA_DIR at a + // regular file is a non-permission misconfiguration (EEXIST/ENOTDIR), which + // resolveWritableDataDir rethrows by design (only EACCES/EPERM fall back), so + // the failure now surfaces at import time, not lazily from getDbInstance(). + let caught: unknown; + await withEnv({ DATA_DIR: fileAsDir }, () => importFresh("src/lib/db/core.ts")).then( + () => { + throw new Error("expected importing db/core with an invalid DATA_DIR to reject"); + }, + (err) => { + caught = err; + } + ); + assert.ok(caught instanceof Error, "an invalid DATA_DIR must surface as a thrown Error"); + assert.match( + String((caught as Error).message), + /unable to open database file|ENOTDIR|EEXIST|not a directory|file already exists/i + ); + } finally { + removePath(sandboxDir); + } } -}); +); test( "legacy empty schema databases are renamed before a fresh sqlite database is created", diff --git a/tests/unit/db-core-migration.test.ts b/tests/unit/db-core-migration.test.ts index 806921487728..61545c072ce2 100644 --- a/tests/unit/db-core-migration.test.ts +++ b/tests/unit/db-core-migration.test.ts @@ -13,7 +13,7 @@ const core = await import("../../src/lib/db/core.ts"); test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("Test 1: migrateFromJson handles empty db.json and renames it", () => { diff --git a/tests/unit/db-core.test.ts b/tests/unit/db-core.test.ts index b198a780a7d2..5d7027dcb300 100644 --- a/tests/unit/db-core.test.ts +++ b/tests/unit/db-core.test.ts @@ -12,7 +12,7 @@ function makeTempDir(prefix: string): string { } function removePath(targetPath: string) { - fs.rmSync(targetPath, { recursive: true, force: true }); + fs.rmSync(targetPath, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } const originalEnv = { diff --git a/tests/unit/db-detailed-logs.test.ts b/tests/unit/db-detailed-logs.test.ts index 4469c7baf71f..0eef9b566b62 100644 --- a/tests/unit/db-detailed-logs.test.ts +++ b/tests/unit/db-detailed-logs.test.ts @@ -29,7 +29,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: any) { @@ -52,7 +52,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_PII_ENABLED === undefined) { delete process.env.PII_RESPONSE_SANITIZATION; diff --git a/tests/unit/db-domainState-crud.test.ts b/tests/unit/db-domainState-crud.test.ts index c02be9060c05..eecc41142aa5 100644 --- a/tests/unit/db-domainState-crud.test.ts +++ b/tests/unit/db-domainState-crud.test.ts @@ -15,7 +15,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch { @@ -166,7 +166,15 @@ test("saveBudgetResetLog and loadBudgetResetLogs", async () => { test("deleteBudget removes budget and reset logs", async () => { await resetStorage(); ds.saveBudget("del-key", { dailyLimitUsd: 10 }); - ds.saveBudgetResetLog({ apiKeyId: "del-key", resetInterval: "daily", previousSpend: 3, resetAt: 1, nextResetAt: 2, periodStart: 0, periodEnd: 1 }); + ds.saveBudgetResetLog({ + apiKeyId: "del-key", + resetInterval: "daily", + previousSpend: 3, + resetAt: 1, + nextResetAt: 2, + periodStart: 0, + periodEnd: 1, + }); ds.deleteBudget("del-key"); assert.equal(ds.loadBudget("del-key"), null); assert.deepEqual(ds.loadBudgetResetLogs("del-key"), []); diff --git a/tests/unit/db-fresh-setup-9934.test.ts b/tests/unit/db-fresh-setup-9934.test.ts index 6faafa3f8ac7..2330eedb6ef6 100644 --- a/tests/unit/db-fresh-setup-9934.test.ts +++ b/tests/unit/db-fresh-setup-9934.test.ts @@ -132,9 +132,9 @@ test( }, "first serve must not abort on a fresh setup DB that only has the 001 seed (#9934)"); // Prove the fresh DB actually got migrated past 001 to the latest version. - const maxRow = db.prepare( - "SELECT MAX(CAST(version AS INTEGER)) AS maxV FROM _omniroute_migrations" - ).get(); + const maxRow = db + .prepare("SELECT MAX(CAST(version AS INTEGER)) AS maxV FROM _omniroute_migrations") + .get(); assert.ok( (maxRow?.maxV ?? 0) > 1, `expected migrations beyond 001 to run, got max=${maxRow?.maxV}` @@ -142,7 +142,7 @@ test( } finally { if (originalDataDir === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = originalDataDir; - fs.rmSync(dataDir, { recursive: true, force: true }); + fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } ); diff --git a/tests/unit/db-gamification-federation-3500.test.ts b/tests/unit/db-gamification-federation-3500.test.ts index 5fd1876fec70..f36e5daee255 100644 --- a/tests/unit/db-gamification-federation-3500.test.ts +++ b/tests/unit/db-gamification-federation-3500.test.ts @@ -28,11 +28,23 @@ function seedServers() { db.prepare( `INSERT OR REPLACE INTO community_servers (id, name, url, api_key_hash, status) VALUES (?, ?, ?, ?, ?)` - ).run("srv-connected", "Connected Server", "https://connected.example", "hash-connected", "connected"); + ).run( + "srv-connected", + "Connected Server", + "https://connected.example", + "hash-connected", + "connected" + ); db.prepare( `INSERT OR REPLACE INTO community_servers (id, name, url, api_key_hash, status) VALUES (?, ?, ?, ?, ?)` - ).run("srv-disconnected", "Disconnected Server", "https://disconnected.example", "hash-disconnected", "disconnected"); + ).run( + "srv-disconnected", + "Disconnected Server", + "https://disconnected.example", + "hash-disconnected", + "disconnected" + ); } test.after(async () => { @@ -42,7 +54,12 @@ test.after(async () => { const tryRm = (attempts: number) => { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { + recursive: true, + force: true, + maxRetries: 5, + retryDelay: 100, + }); } resolve(); } catch (err: any) { @@ -73,9 +90,5 @@ test("getConnectedServerByKeyHash returns undefined for an unknown hash", () => test("getConnectedServerByKeyHash returns undefined for a disconnected server (status filter)", () => { seedServers(); const result = gamifDb.getConnectedServerByKeyHash("hash-disconnected"); - assert.equal( - result, - undefined, - "should not return a server whose status is not 'connected'" - ); + assert.equal(result, undefined, "should not return a server whose status is not 'connected'"); }); diff --git a/tests/unit/db-health-check.test.ts b/tests/unit/db-health-check.test.ts index 52f0f0a7a608..4486b7fc6068 100644 --- a/tests/unit/db-health-check.test.ts +++ b/tests/unit/db-health-check.test.ts @@ -16,7 +16,7 @@ const healthCheckDb = await import("../../src/lib/db/healthCheck.ts"); async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -27,7 +27,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function insertBrokenRows(db) { diff --git a/tests/unit/db-health-driver.test.ts b/tests/unit/db-health-driver.test.ts index efbc6b08f62a..ef8a652eda72 100644 --- a/tests/unit/db-health-driver.test.ts +++ b/tests/unit/db-health-driver.test.ts @@ -14,7 +14,7 @@ const driverFactory = await import("../../src/lib/db/adapters/driverFactory.ts") test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ─── describeDbDriver: pure decision ─────────────────── diff --git a/tests/unit/db-health-route.test.ts b/tests/unit/db-health-route.test.ts index 8b36871b88a2..415fffc9cb71 100644 --- a/tests/unit/db-health-route.test.ts +++ b/tests/unit/db-health-route.test.ts @@ -19,7 +19,7 @@ const TEST_INITIAL_PASSWORD = "db-health-route-password"; async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); process.env.JWT_SECRET = TEST_JWT_SECRET; process.env.INITIAL_PASSWORD = TEST_INITIAL_PASSWORD; @@ -58,7 +58,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_JWT_SECRET === undefined) delete process.env.JWT_SECRET; else process.env.JWT_SECRET = ORIGINAL_JWT_SECRET; if (ORIGINAL_INITIAL_PASSWORD === undefined) delete process.env.INITIAL_PASSWORD; diff --git a/tests/unit/db-inspector-custom-hosts.test.ts b/tests/unit/db-inspector-custom-hosts.test.ts index 39ad298ae876..4496f2d51bf8 100644 --- a/tests/unit/db-inspector-custom-hosts.test.ts +++ b/tests/unit/db-inspector-custom-hosts.test.ts @@ -18,7 +18,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: any) { @@ -39,7 +39,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("listCustomHosts returns empty array initially", () => { diff --git a/tests/unit/db-inspector-sessions.test.ts b/tests/unit/db-inspector-sessions.test.ts index 801309d7d15d..164e82223ed5 100644 --- a/tests/unit/db-inspector-sessions.test.ts +++ b/tests/unit/db-inspector-sessions.test.ts @@ -4,9 +4,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -const TEST_DATA_DIR = fs.mkdtempSync( - path.join(os.tmpdir(), "omniroute-db-inspector-sessions-") -); +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-db-inspector-sessions-")); process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); @@ -18,7 +16,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: any) { @@ -39,7 +37,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("createSession returns a uuid and started_at timestamp", () => { diff --git a/tests/unit/db-install-upgrade-schema-parity.test.ts b/tests/unit/db-install-upgrade-schema-parity.test.ts index 9ce6c408c481..6ed39eee5e7f 100644 --- a/tests/unit/db-install-upgrade-schema-parity.test.ts +++ b/tests/unit/db-install-upgrade-schema-parity.test.ts @@ -49,7 +49,7 @@ test.after(() => { } catch { /* best effort */ } - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function migrationFiles(): Array<{ version: string; name: string }> { diff --git a/tests/unit/db-job-registry-migration-renumber-139.test.ts b/tests/unit/db-job-registry-migration-renumber-139.test.ts index 11f430dc0fba..09788ae2cc0a 100644 --- a/tests/unit/db-job-registry-migration-renumber-139.test.ts +++ b/tests/unit/db-job-registry-migration-renumber-139.test.ts @@ -35,7 +35,7 @@ fs.writeFileSync( const { runMigrations } = await import("../../src/lib/db/migrationRunner.ts"); test.after(() => { - fs.rmSync(migrationsDir, { recursive: true, force: true }); + fs.rmSync(migrationsDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (originalMigrationsDir === undefined) delete process.env.OMNIROUTE_MIGRATIONS_DIR; else process.env.OMNIROUTE_MIGRATIONS_DIR = originalMigrationsDir; }); diff --git a/tests/unit/db-logs-cache-3500.test.ts b/tests/unit/db-logs-cache-3500.test.ts index 914a3f9a6be1..b4e020f9a224 100644 --- a/tests/unit/db-logs-cache-3500.test.ts +++ b/tests/unit/db-logs-cache-3500.test.ts @@ -69,7 +69,7 @@ test.before(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // =========================================================================== diff --git a/tests/unit/db-migration-runner-extra-dirs.test.ts b/tests/unit/db-migration-runner-extra-dirs.test.ts index 845b06366cfa..494122e7a7ae 100644 --- a/tests/unit/db-migration-runner-extra-dirs.test.ts +++ b/tests/unit/db-migration-runner-extra-dirs.test.ts @@ -85,7 +85,7 @@ const { runMigrations } = await import("../../src/lib/db/migrationRunner.ts"); process.on("exit", () => { for (const dir of tempDirs) { try { - fs.rmSync(dir, { recursive: true, force: true }); + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { /* ignore */ } @@ -258,7 +258,7 @@ test("diretório core ausente não impede as migrations dos extras", async () => name: f, body: fs.readFileSync(path.join(CORE_DIR, f), "utf-8"), })); - fs.rmSync(CORE_DIR, { recursive: true, force: true }); + fs.rmSync(CORE_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); try { const r = runWithExtras(`ee=${eeDir}`); assert.ok(r.tables.includes("ee_solo"), `tabelas: ${r.tables.join(", ")}`); diff --git a/tests/unit/db-model-aliases-cascade.test.ts b/tests/unit/db-model-aliases-cascade.test.ts index 2d1a76ce307d..e75f2fc14463 100644 --- a/tests/unit/db-model-aliases-cascade.test.ts +++ b/tests/unit/db-model-aliases-cascade.test.ts @@ -16,7 +16,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: any) { @@ -37,7 +37,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("deleteModelAliasesForProvider removes only the target provider's aliases", async () => { diff --git a/tests/unit/db-model-context-overrides.test.ts b/tests/unit/db-model-context-overrides.test.ts index 5584ac3bc9d4..e13a791b2654 100644 --- a/tests/unit/db-model-context-overrides.test.ts +++ b/tests/unit/db-model-context-overrides.test.ts @@ -14,7 +14,7 @@ const mco = await import("../../src/lib/db/modelContextOverrides.ts"); function resetStorage() { coreDb.resetDbInstance(); - fs.rmSync(moduleDataDir, { recursive: true, force: true }); + fs.rmSync(moduleDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(moduleDataDir, { recursive: true }); } @@ -26,7 +26,7 @@ beforeEach(() => { after(() => { coreDb.resetDbInstance(); - fs.rmSync(moduleDataDir, { recursive: true, force: true }); + fs.rmSync(moduleDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); describe("modelContextOverrides", () => { @@ -47,7 +47,10 @@ describe("modelContextOverrides", () => { it("upserts on the same (provider, model) key and records the source", () => { mco.setModelContextOverride("anthropic", "claude-sonnet-4-5", 200000, "auto:discovery"); - assert.equal(mco.getModelContextOverrideRecord("anthropic", "claude-sonnet-4-5")?.source, "auto:discovery"); + assert.equal( + mco.getModelContextOverrideRecord("anthropic", "claude-sonnet-4-5")?.source, + "auto:discovery" + ); // Re-set as manual overwrites the same row. mco.setModelContextOverride("anthropic", "claude-sonnet-4-5", 1000000, "manual"); const rec = mco.getModelContextOverrideRecord("anthropic", "claude-sonnet-4-5"); @@ -82,9 +85,9 @@ describe("modelContextOverrides", () => { mco.setModelContextOverride("anthropic", "claude-sonnet-4-5", 200000, "auto:discovery"); const all = mco.listModelContextOverrides(); assert.equal(all.length, 2); - assert.deepEqual( - all.map((o) => `${o.provider}/${o.modelId}`).sort(), - ["anthropic/claude-sonnet-4-5", "openai/gpt-5"] - ); + assert.deepEqual(all.map((o) => `${o.provider}/${o.modelId}`).sort(), [ + "anthropic/claude-sonnet-4-5", + "openai/gpt-5", + ]); }); }); diff --git a/tests/unit/db-models-crud.test.ts b/tests/unit/db-models-crud.test.ts index bd21ad6acad1..31c0d3025381 100644 --- a/tests/unit/db-models-crud.test.ts +++ b/tests/unit/db-models-crud.test.ts @@ -16,7 +16,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: any) { @@ -37,7 +37,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("model aliases can be created, listed and deleted", async () => { diff --git a/tests/unit/db-models-extended.test.ts b/tests/unit/db-models-extended.test.ts index 201d9d88a3ff..8971da4e2ace 100644 --- a/tests/unit/db-models-extended.test.ts +++ b/tests/unit/db-models-extended.test.ts @@ -25,7 +25,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch { diff --git a/tests/unit/db-playground-presets.test.ts b/tests/unit/db-playground-presets.test.ts index f3907481e8ee..77b8497466b7 100644 --- a/tests/unit/db-playground-presets.test.ts +++ b/tests/unit/db-playground-presets.test.ts @@ -18,7 +18,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: any) { @@ -39,7 +39,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ─── Migration idempotency ─────────────────────────────────────────────────── diff --git a/tests/unit/db-pre-migration-backup-retention-10421.test.ts b/tests/unit/db-pre-migration-backup-retention-10421.test.ts index bc99aa39fb9c..798bb3cb66df 100644 --- a/tests/unit/db-pre-migration-backup-retention-10421.test.ts +++ b/tests/unit/db-pre-migration-backup-retention-10421.test.ts @@ -211,7 +211,7 @@ test( ); } finally { db.close(); - fs.rmSync(dataDir, { recursive: true, force: true }); + fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } ); @@ -263,6 +263,6 @@ test("#10421 the newest pre-migration backup survives pruning", serial, async () assert.equal(fresh.length, 1, `expected the run's own backup to survive, got ${fresh.length}`); } finally { db.close(); - fs.rmSync(dataDir, { recursive: true, force: true }); + fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/db-provider-cookie-dedup-3368.test.ts b/tests/unit/db-provider-cookie-dedup-3368.test.ts index 9d5916a4ff11..f62278b31753 100644 --- a/tests/unit/db-provider-cookie-dedup-3368.test.ts +++ b/tests/unit/db-provider-cookie-dedup-3368.test.ts @@ -19,7 +19,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: unknown) { @@ -40,7 +40,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#3368 cookie dedup: re-importing the same cookie under a different name updates, not duplicates", async () => { diff --git a/tests/unit/db-provider-daily-usage-4009.test.ts b/tests/unit/db-provider-daily-usage-4009.test.ts index 9c19f747a347..3dcf30c2f563 100644 --- a/tests/unit/db-provider-daily-usage-4009.test.ts +++ b/tests/unit/db-provider-daily-usage-4009.test.ts @@ -59,7 +59,7 @@ test.before(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#4009 getProviderDailyUsageRows is exported as a function", () => { diff --git a/tests/unit/db-provider-limits.test.ts b/tests/unit/db-provider-limits.test.ts index e5104e5e0848..40c6804ad814 100644 --- a/tests/unit/db-provider-limits.test.ts +++ b/tests/unit/db-provider-limits.test.ts @@ -12,7 +12,7 @@ const providerLimitsDb = await import("../../src/lib/db/providerLimits.ts"); async function resetStorage() { coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -22,7 +22,7 @@ test.beforeEach(async () => { test.after(() => { coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("providerLimits cache returns empty defaults before any writes", () => { diff --git a/tests/unit/db-provider-plans.test.ts b/tests/unit/db-provider-plans.test.ts index ab9c25edaecb..a9a35c285d43 100644 --- a/tests/unit/db-provider-plans.test.ts +++ b/tests/unit/db-provider-plans.test.ts @@ -26,7 +26,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (err: any) { @@ -46,7 +46,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // --------------------------------------------------------------------------- @@ -54,12 +54,7 @@ test.after(async () => { // --------------------------------------------------------------------------- test("upsertPlan creates a plan row", () => { - plansDb.upsertPlan( - "conn-1", - "codex", - [{ unit: "percent", window: "5h", limit: 100 }], - "auto" - ); + plansDb.upsertPlan("conn-1", "codex", [{ unit: "percent", window: "5h", limit: 100 }], "auto"); const all = plansDb.listPlans(); assert.equal(all.length, 1); @@ -204,7 +199,12 @@ test("upserting one plan does not affect other connection plans", () => { ); // Update conn-x - plansDb.upsertPlan("conn-x", "openai", [{ unit: "usd", window: "monthly", limit: 100 }], "manual"); + plansDb.upsertPlan( + "conn-x", + "openai", + [{ unit: "usd", window: "monthly", limit: 100 }], + "manual" + ); const planY = plansDb.getPlan("conn-y"); assert.ok(planY, "conn-y should still exist"); diff --git a/tests/unit/db-provider-stats.test.ts b/tests/unit/db-provider-stats.test.ts index 398351084ab4..0d4f5c09d718 100644 --- a/tests/unit/db-provider-stats.test.ts +++ b/tests/unit/db-provider-stats.test.ts @@ -72,7 +72,7 @@ test.before(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#3175 getProviderCallStats aggregates totals, success and latency per provider", () => { diff --git a/tests/unit/db-providers-access-token-1290.test.ts b/tests/unit/db-providers-access-token-1290.test.ts index 85d94ec50f09..45c1600e4d90 100644 --- a/tests/unit/db-providers-access-token-1290.test.ts +++ b/tests/unit/db-providers-access-token-1290.test.ts @@ -20,7 +20,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: unknown) { @@ -41,7 +41,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("createProviderConnection: authType access_token never dedups — same email creates a new row each time", async () => { diff --git a/tests/unit/db-providers-cross-idp-dedup-2244.test.ts b/tests/unit/db-providers-cross-idp-dedup-2244.test.ts index fd74044cc4e1..47c05102a2d9 100644 --- a/tests/unit/db-providers-cross-idp-dedup-2244.test.ts +++ b/tests/unit/db-providers-cross-idp-dedup-2244.test.ts @@ -24,7 +24,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: unknown) { @@ -45,7 +45,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#2244 cross-IdP dedup: same email + same username updates the existing connection", async () => { @@ -92,7 +92,9 @@ test("#2244 cross-IdP dedup: same email + DIFFERENT username creates a separate "two different IdP identities sharing an email must NOT be collapsed into one connection" ); const usernames = conns - .map((c) => (c as { providerSpecificData?: { username?: string } }).providerSpecificData?.username) + .map( + (c) => (c as { providerSpecificData?: { username?: string } }).providerSpecificData?.username + ) .sort(); assert.deepEqual(usernames, ["alice-google", "alice-huggingface"]); }); diff --git a/tests/unit/db-providers-crud.test.ts b/tests/unit/db-providers-crud.test.ts index a731780597e7..eb545bbfce7d 100644 --- a/tests/unit/db-providers-crud.test.ts +++ b/tests/unit/db-providers-crud.test.ts @@ -16,7 +16,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: any) { @@ -37,7 +37,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("createProviderConnection assigns provider-scoped priorities and supports filtered reads", async () => { @@ -418,11 +418,12 @@ test("getProviderConnections supports authType filter and column projection", as assert.equal(activeOAuth.length, 1); // Column projection: only requested columns returned - const projected = await providersDb.getProviderConnections({ authType: "oauth" }, undefined, undefined, [ - "id", - "provider", - "name", - ]); + const projected = await providersDb.getProviderConnections( + { authType: "oauth" }, + undefined, + undefined, + ["id", "provider", "name"] + ); assert.equal(projected.length, 1); const keys = Object.keys(projected[0]); // id, provider, name each appear in camelCase @@ -456,7 +457,11 @@ test("getProviderConnections rejects column names outside the real provider_conn // A mix of valid + invalid columns must still reject (fail-closed, not a // silent partial projection). await assert.rejects( - () => providersDb.getProviderConnections({}, undefined, undefined, ["id", "provider; DROP TABLE provider_connections; --"]), + () => + providersDb.getProviderConnections({}, undefined, undefined, [ + "id", + "provider; DROP TABLE provider_connections; --", + ]), /invalid column/i ); @@ -472,10 +477,12 @@ test("getProviderConnections rejects column names outside the real provider_conn isActive: true, group: "team-a", }); - const withGroup = await providersDb.getProviderConnections({ authType: "oauth" }, undefined, undefined, [ - "id", - "group", - ]); + const withGroup = await providersDb.getProviderConnections( + { authType: "oauth" }, + undefined, + undefined, + ["id", "group"] + ); assert.equal(withGroup.length, 1); assert.equal(withGroup[0].group, "team-a"); }); diff --git a/tests/unit/db-proxies-crud.test.ts b/tests/unit/db-proxies-crud.test.ts index 1cd35d35c966..4a69ebc71874 100644 --- a/tests/unit/db-proxies-crud.test.ts +++ b/tests/unit/db-proxies-crud.test.ts @@ -17,7 +17,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: any) { @@ -38,7 +38,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("proxy CRUD redacts secrets by default and preserves stored credentials when omitted", async () => { diff --git a/tests/unit/db-quota-consumption.test.ts b/tests/unit/db-quota-consumption.test.ts index 3a166291f43f..b3daccbfdeff 100644 --- a/tests/unit/db-quota-consumption.test.ts +++ b/tests/unit/db-quota-consumption.test.ts @@ -24,7 +24,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (err: any) { @@ -44,7 +44,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // --------------------------------------------------------------------------- @@ -128,7 +128,7 @@ test("getPair returns curr and prev buckets", () => { const now = Date.now(); consumptionDb.incrementBucket(key, dim, 100, 70, now); // current bucket - consumptionDb.incrementBucket(key, dim, 99, 30, now); // previous bucket + consumptionDb.incrementBucket(key, dim, 99, 30, now); // previous bucket const { curr, prev } = consumptionDb.getPair(key, dim, 100); assert.equal(curr, 70); @@ -157,8 +157,8 @@ test("gcOlderThan deletes only rows with updated_at strictly less than threshold // Insert 3 rows with different timestamps consumptionDb.incrementBucket("key-gc1", "pool-gc:tokens:daily", 1, 1, now - 100); // older → deleted - consumptionDb.incrementBucket("key-gc2", "pool-gc:tokens:daily", 2, 1, now - 1); // older → deleted - consumptionDb.incrementBucket("key-gc3", "pool-gc:tokens:daily", 3, 1, now); // at threshold → kept + consumptionDb.incrementBucket("key-gc2", "pool-gc:tokens:daily", 2, 1, now - 1); // older → deleted + consumptionDb.incrementBucket("key-gc3", "pool-gc:tokens:daily", 3, 1, now); // at threshold → kept consumptionDb.incrementBucket("key-gc4", "pool-gc:tokens:daily", 4, 1, now + 100); // newer → kept const deleted = consumptionDb.gcOlderThan(threshold); diff --git a/tests/unit/db-quota-migrations-idempotency.test.ts b/tests/unit/db-quota-migrations-idempotency.test.ts index 969335fc2fcc..d10fba4d93bd 100644 --- a/tests/unit/db-quota-migrations-idempotency.test.ts +++ b/tests/unit/db-quota-migrations-idempotency.test.ts @@ -24,7 +24,9 @@ const core = await import("../../src/lib/db/core.ts"); function getDb() { return core.getDbInstance() as unknown as { - prepare: (sql: string) => { + prepare: ( + sql: string + ) => { all: (...params: unknown[]) => TRow[]; get: (...params: unknown[]) => TRow | undefined; run: (...params: unknown[]) => { changes: number }; @@ -35,9 +37,7 @@ function getDb() { function listSqliteMaster(type: "table" | "index"): string[] { const db = getDb(); const rows = db - .prepare<{ name: string }>( - `SELECT name FROM sqlite_master WHERE type = ? ORDER BY name` - ) + .prepare<{ name: string }>(`SELECT name FROM sqlite_master WHERE type = ? ORDER BY name`) .all(type); return rows.map((r) => r.name); } @@ -53,7 +53,7 @@ const EXPECTED_INDEXES = [ test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("migrations 073-075 create all expected tables and indexes on first init", () => { @@ -64,7 +64,10 @@ test("migrations 073-075 create all expected tables and indexes on first init", const indexes = listSqliteMaster("index"); for (const table of EXPECTED_TABLES) { - assert.ok(tables.includes(table), `Expected table '${table}' to exist. Found: ${tables.join(", ")}`); + assert.ok( + tables.includes(table), + `Expected table '${table}' to exist. Found: ${tables.join(", ")}` + ); } for (const idx of EXPECTED_INDEXES) { diff --git a/tests/unit/db-quota-pools.test.ts b/tests/unit/db-quota-pools.test.ts index ecf04494a187..c73fbf9ad457 100644 --- a/tests/unit/db-quota-pools.test.ts +++ b/tests/unit/db-quota-pools.test.ts @@ -27,7 +27,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (err: any) { @@ -47,7 +47,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // --------------------------------------------------------------------------- diff --git a/tests/unit/db-quota-snapshots.test.ts b/tests/unit/db-quota-snapshots.test.ts index cce181254a82..9a138649809a 100644 --- a/tests/unit/db-quota-snapshots.test.ts +++ b/tests/unit/db-quota-snapshots.test.ts @@ -12,7 +12,7 @@ const quotaSnapshotsDb = await import("../../src/lib/db/quotaSnapshots.ts"); async function resetStorage() { coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -22,7 +22,7 @@ test.beforeEach(async () => { test.after(() => { coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("quotaSnapshots save and query rows with provider and connection filters", () => { diff --git a/tests/unit/db-read-cache.test.ts b/tests/unit/db-read-cache.test.ts index 55181dba2e87..f1e58b299033 100644 --- a/tests/unit/db-read-cache.test.ts +++ b/tests/unit/db-read-cache.test.ts @@ -23,7 +23,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: any) { @@ -44,7 +44,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("getCachedSettings returns cached data until TTL expires or cache is invalidated", async () => { diff --git a/tests/unit/db-recovery.test.ts b/tests/unit/db-recovery.test.ts index 0eaa3156d9e6..5a2aad049fa7 100644 --- a/tests/unit/db-recovery.test.ts +++ b/tests/unit/db-recovery.test.ts @@ -12,7 +12,7 @@ async function withRecoveryEnv(fn: (dataDir: string) => Promise) { try { await fn(dataDir); } finally { - fs.rmSync(dataDir, { recursive: true, force: true }); + fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; } diff --git a/tests/unit/db-registered-keys.test.ts b/tests/unit/db-registered-keys.test.ts index 6173cc08d490..4dee75800b33 100644 --- a/tests/unit/db-registered-keys.test.ts +++ b/tests/unit/db-registered-keys.test.ts @@ -12,7 +12,7 @@ const registeredKeysDb = await import("../../src/lib/db/registeredKeys.ts"); async function resetStorage() { coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -22,7 +22,7 @@ test.beforeEach(async () => { test.after(() => { coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("registered keys issue, validate, consume budget and revoke correctly", () => { diff --git a/tests/unit/db-registeredKeys-crud.test.ts b/tests/unit/db-registeredKeys-crud.test.ts index 84755179823a..7ef8c0a1928f 100644 --- a/tests/unit/db-registeredKeys-crud.test.ts +++ b/tests/unit/db-registeredKeys-crud.test.ts @@ -15,7 +15,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch { diff --git a/tests/unit/db-reset-module-state.test.ts b/tests/unit/db-reset-module-state.test.ts index f4fdd63a128d..2ab944dac04b 100644 --- a/tests/unit/db-reset-module-state.test.ts +++ b/tests/unit/db-reset-module-state.test.ts @@ -22,7 +22,7 @@ const { isValidApiKey } = await import("../../src/sse/services/auth.ts"); async function recreateDataDirFromScratch(): Promise { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); // Primeiro acesso recria o DB do zero (migrations + colunas-fallback). await settingsDb.updateSettings({ requireLogin: true, setupComplete: true }); @@ -40,7 +40,7 @@ test("api-key validation survives a second resetDbInstance with a recreated DB ( test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (originalDataDir === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = originalDataDir; }); diff --git a/tests/unit/db-secrets.test.ts b/tests/unit/db-secrets.test.ts index 0d085c21760a..033491b27574 100644 --- a/tests/unit/db-secrets.test.ts +++ b/tests/unit/db-secrets.test.ts @@ -16,7 +16,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: any) { @@ -37,7 +37,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("getPersistedSecret returns null for missing keys", () => { diff --git a/tests/unit/db-settings-crud.test.ts b/tests/unit/db-settings-crud.test.ts index bc5d06aa4e90..2b53e043ae30 100644 --- a/tests/unit/db-settings-crud.test.ts +++ b/tests/unit/db-settings-crud.test.ts @@ -21,7 +21,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: any) { @@ -43,7 +43,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_INITIAL_PASSWORD === undefined) { delete process.env.INITIAL_PASSWORD; diff --git a/tests/unit/db-settings-debug-mode-default-10312.test.ts b/tests/unit/db-settings-debug-mode-default-10312.test.ts index 327b4ce74dd9..e4a22b6417ba 100644 --- a/tests/unit/db-settings-debug-mode-default-10312.test.ts +++ b/tests/unit/db-settings-debug-mode-default-10312.test.ts @@ -21,7 +21,7 @@ async function resetStorage() { delete (globalThis as { __omnirouteDb?: unknown }).__omnirouteDb; core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); core.getDbInstance(); diff --git a/tests/unit/db-settings-extended.test.ts b/tests/unit/db-settings-extended.test.ts index 6bb476a573c9..cf368108718a 100644 --- a/tests/unit/db-settings-extended.test.ts +++ b/tests/unit/db-settings-extended.test.ts @@ -25,7 +25,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch { diff --git a/tests/unit/db-sqljs-atomic-persist.test.ts b/tests/unit/db-sqljs-atomic-persist.test.ts index bb61f2a5c3a8..de2b9f607cd3 100644 --- a/tests/unit/db-sqljs-atomic-persist.test.ts +++ b/tests/unit/db-sqljs-atomic-persist.test.ts @@ -96,7 +96,7 @@ test( } finally { if (readerFd !== null) fs.closeSync(readerFd); if (adapter?.open) adapter.close(); - fs.rmSync(dataDir, { recursive: true, force: true }); + fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } ); @@ -111,6 +111,6 @@ test("sql.js persist() is a no-op for :memory: databases (no temp file, no throw assert.deepEqual(fs.readdirSync(dataDir), [], "an in-memory database wrote to disk"); } finally { if (adapter?.open) adapter.close(); - fs.rmSync(dataDir, { recursive: true, force: true }); + fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/db-sqljs-close-poison-7494.test.ts b/tests/unit/db-sqljs-close-poison-7494.test.ts index cd819c8785cd..1da7b006aebc 100644 --- a/tests/unit/db-sqljs-close-poison-7494.test.ts +++ b/tests/unit/db-sqljs-close-poison-7494.test.ts @@ -22,9 +22,8 @@ test( const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-7494-mech-")); const sqliteFile = path.join(dataDir, "storage.sqlite"); try { - const { preInitSqlJs, getSqlJsAdapter } = await import( - "../../src/lib/db/adapters/driverFactory" - ); + const { preInitSqlJs, getSqlJsAdapter } = + await import("../../src/lib/db/adapters/driverFactory"); const boot = await preInitSqlJs(sqliteFile); boot.exec("CREATE TABLE t (id INTEGER)"); @@ -32,9 +31,7 @@ test( const probe = getSqlJsAdapter(sqliteFile); probe! - .prepare( - "SELECT name FROM sqlite_master WHERE type='table' AND name='schema_migrations'" - ) + .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='schema_migrations'") .get(); probe!.close(); @@ -46,7 +43,7 @@ test( "sanity: confirms the underlying sql.js singleton mechanism this bug exploits" ); } finally { - fs.rmSync(dataDir, { recursive: true, force: true }); + fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } ); @@ -59,9 +56,8 @@ test( const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-7494-guard-")); const sqliteFile = path.join(dataDir, "storage.sqlite"); try { - const { preInitSqlJs, getSqlJsAdapter } = await import( - "../../src/lib/db/adapters/driverFactory" - ); + const { preInitSqlJs, getSqlJsAdapter } = + await import("../../src/lib/db/adapters/driverFactory"); const { closeProbeIfSafe } = await import("../../src/lib/db/core"); const boot = await preInitSqlJs(sqliteFile); @@ -72,9 +68,7 @@ test( // the guarded helper instead of a raw .close() call. const probe = getSqlJsAdapter(sqliteFile); probe! - .prepare( - "SELECT name FROM sqlite_master WHERE type='table' AND name='schema_migrations'" - ) + .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='schema_migrations'") .get(); closeProbeIfSafe(probe); @@ -94,7 +88,7 @@ test( // already-deleted path in the background. await new Promise((resolve) => setTimeout(resolve, 200)); } finally { - fs.rmSync(dataDir, { recursive: true, force: true }); + fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } ); @@ -117,7 +111,7 @@ test( assert.equal(probe!.open, false, "closeProbeIfSafe() must still close non-sql.js adapters"); } finally { - fs.rmSync(dataDir, { recursive: true, force: true }); + fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } ); diff --git a/tests/unit/db-sqljs-preinit-ordering-gap-7288.test.ts b/tests/unit/db-sqljs-preinit-ordering-gap-7288.test.ts index c68cebea3d3d..912702df138e 100644 --- a/tests/unit/db-sqljs-preinit-ordering-gap-7288.test.ts +++ b/tests/unit/db-sqljs-preinit-ordering-gap-7288.test.ts @@ -61,7 +61,7 @@ test.after(() => { } if (prevDataDir === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = prevDataDir; - if (dataDir) fs.rmSync(dataDir, { recursive: true, force: true }); + if (dataDir) fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("src/lib/db/core.ts has no top-level await (breaks esbuild's CJS require() bundling — #7288 hotfix)", () => { @@ -183,9 +183,8 @@ test( const dir2 = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-7288-happy-")); const file2 = path.join(dir2, "storage.sqlite"); try { - const { tryOpenSync, getSqlJsAdapter } = await import( - "../../src/lib/db/adapters/driverFactory" - ); + const { tryOpenSync, getSqlJsAdapter } = + await import("../../src/lib/db/adapters/driverFactory"); const { default: Database } = await import("better-sqlite3"); const seed = new Database(file2); seed.exec("CREATE TABLE t (id INTEGER)"); @@ -204,7 +203,7 @@ test( "otherwise every boot would pay the WASM-load cost even on the happy path" ); } finally { - fs.rmSync(dir2, { recursive: true, force: true }); + fs.rmSync(dir2, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } ); diff --git a/tests/unit/db-synced-model-catalog-invalidation-8728.test.ts b/tests/unit/db-synced-model-catalog-invalidation-8728.test.ts index 7145857980ab..38940659e7ea 100644 --- a/tests/unit/db-synced-model-catalog-invalidation-8728.test.ts +++ b/tests/unit/db-synced-model-catalog-invalidation-8728.test.ts @@ -14,7 +14,7 @@ const readCache = await import("../../src/lib/db/readCache.ts"); function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -45,7 +45,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("replace invalidates only for canonical persisted changes", async () => { diff --git a/tests/unit/db-upstreamProxy.test.ts b/tests/unit/db-upstreamProxy.test.ts index 7948e62eff25..3b2b66447801 100644 --- a/tests/unit/db-upstreamProxy.test.ts +++ b/tests/unit/db-upstreamProxy.test.ts @@ -53,12 +53,13 @@ afterEach(() => { }); after(() => { - if (fs.existsSync(fileTmpDir)) fs.rmSync(fileTmpDir, { recursive: true, force: true }); + if (fs.existsSync(fileTmpDir)) + fs.rmSync(fileTmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); async function resetModuleStorage() { coreDb.resetDbInstance(); - fs.rmSync(moduleDataDir, { recursive: true, force: true }); + fs.rmSync(moduleDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(moduleDataDir, { recursive: true }); } @@ -367,7 +368,7 @@ describe("db/upstreamProxy (module coverage)", () => { after(async () => { coreDb.resetDbInstance(); - fs.rmSync(moduleDataDir, { recursive: true, force: true }); + fs.rmSync(moduleDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); it("validates proxy URLs and blocks unsupported or private destinations", async () => { diff --git a/tests/unit/db-usage-analytics-3500.test.ts b/tests/unit/db-usage-analytics-3500.test.ts index e9d49c432683..4bf2c7b409f2 100644 --- a/tests/unit/db-usage-analytics-3500.test.ts +++ b/tests/unit/db-usage-analytics-3500.test.ts @@ -89,7 +89,7 @@ test.before(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // --------------------------------------------------------------------------- diff --git a/tests/unit/db-versionManager.test.ts b/tests/unit/db-versionManager.test.ts index 34ff05a3a2f8..2020fc8e7ff7 100644 --- a/tests/unit/db-versionManager.test.ts +++ b/tests/unit/db-versionManager.test.ts @@ -64,12 +64,13 @@ afterEach(() => { }); after(() => { - if (fs.existsSync(fileTmpDir)) fs.rmSync(fileTmpDir, { recursive: true, force: true }); + if (fs.existsSync(fileTmpDir)) + fs.rmSync(fileTmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); async function resetModuleStorage() { coreDb.resetDbInstance(); - fs.rmSync(moduleDataDir, { recursive: true, force: true }); + fs.rmSync(moduleDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(moduleDataDir, { recursive: true }); } @@ -395,7 +396,7 @@ describe("db/versionManager (module coverage)", () => { after(async () => { coreDb.resetDbInstance(); - fs.rmSync(moduleDataDir, { recursive: true, force: true }); + fs.rmSync(moduleDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); it("round-trips inserts, updates and status listings through the production module", async () => { diff --git a/tests/unit/db-webhooks.test.ts b/tests/unit/db-webhooks.test.ts index 78e83b0fc7df..5e8ace2df780 100644 --- a/tests/unit/db-webhooks.test.ts +++ b/tests/unit/db-webhooks.test.ts @@ -12,7 +12,7 @@ const webhooksDb = await import("../../src/lib/db/webhooks.ts"); async function resetStorage() { coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -22,7 +22,7 @@ test.beforeEach(async () => { test.after(() => { coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("webhooks create, update, query enabled hooks and delete records", () => { diff --git a/tests/unit/db/api-keys.test.ts b/tests/unit/db/api-keys.test.ts index 3c01698b5671..56f6145981b4 100644 --- a/tests/unit/db/api-keys.test.ts +++ b/tests/unit/db/api-keys.test.ts @@ -27,7 +27,7 @@ const MACHINE_ID = "machine1234567890"; async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -38,7 +38,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ───────────────────────────────────────────────────────────────────────────── diff --git a/tests/unit/db/connectionRuntimeState.test.ts b/tests/unit/db/connectionRuntimeState.test.ts index 1a90d0527ac3..897e0a0b2c92 100644 --- a/tests/unit/db/connectionRuntimeState.test.ts +++ b/tests/unit/db/connectionRuntimeState.test.ts @@ -28,7 +28,7 @@ const crs = await import("../../../src/lib/db/connectionRuntimeState.ts"); async function resetDb() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -52,7 +52,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("get: returns null for unknown connection", async () => { diff --git a/tests/unit/db/context-editing-telemetry-record.test.ts b/tests/unit/db/context-editing-telemetry-record.test.ts index 1b5c9ad3eb8e..8242818b6beb 100644 --- a/tests/unit/db/context-editing-telemetry-record.test.ts +++ b/tests/unit/db/context-editing-telemetry-record.test.ts @@ -29,7 +29,7 @@ const { function resetDb(): void { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -39,7 +39,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (originalDataDir === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = originalDataDir; }); diff --git a/tests/unit/db/default-combo-toggle.test.ts b/tests/unit/db/default-combo-toggle.test.ts index 62434b9cd6c5..8e49b23b727e 100644 --- a/tests/unit/db/default-combo-toggle.test.ts +++ b/tests/unit/db/default-combo-toggle.test.ts @@ -27,7 +27,7 @@ const { getDefaultCompressionCombo, setEngineInDefaultCombo, getCompressionCombo function resetDb(): void { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -39,7 +39,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (originalDataDir === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = originalDataDir; }); diff --git a/tests/unit/db/discovery-results.test.ts b/tests/unit/db/discovery-results.test.ts index afc4106ee66f..6ad482c0e961 100644 --- a/tests/unit/db/discovery-results.test.ts +++ b/tests/unit/db/discovery-results.test.ts @@ -21,7 +21,8 @@ before(async () => { after(() => { core.resetDbInstance(); - if (tmpDataDir) rmSync(tmpDataDir, { recursive: true, force: true }); + if (tmpDataDir) + rmSync(tmpDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); describe("discoveryResults DB module", () => { diff --git a/tests/unit/db/jobRegistryDb.test.ts b/tests/unit/db/jobRegistryDb.test.ts index 08aa22a9c7b9..c701f08a75ac 100644 --- a/tests/unit/db/jobRegistryDb.test.ts +++ b/tests/unit/db/jobRegistryDb.test.ts @@ -28,7 +28,7 @@ const db = await import("../../../src/lib/db/jobRegistryDb.ts"); function resetDb() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -38,7 +38,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("seed: migration registers 3 built-in jobs", () => { diff --git a/tests/unit/db/migration-071.test.ts b/tests/unit/db/migration-071.test.ts index 1522e37abcf9..6df839940b4c 100644 --- a/tests/unit/db/migration-071.test.ts +++ b/tests/unit/db/migration-071.test.ts @@ -25,7 +25,7 @@ const versionManager = await import("../../../src/lib/db/versionManager.ts"); async function resetDb() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -35,7 +35,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("migration 071 — adds 3 new columns to version_manager", async () => { diff --git a/tests/unit/db/migration-163.test.ts b/tests/unit/db/migration-163.test.ts index dcad8bd95947..ea1b6f7f39c9 100644 --- a/tests/unit/db/migration-163.test.ts +++ b/tests/unit/db/migration-163.test.ts @@ -24,7 +24,7 @@ const radarDb = await import("../../../src/lib/db/radar.ts"); function resetDb() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -34,7 +34,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("migration 163 — radar_feed_cache carries generated_at exactly once", () => { diff --git a/tests/unit/db/omp.test.ts b/tests/unit/db/omp.test.ts index d06e014b90a5..ae5c13329f7a 100644 --- a/tests/unit/db/omp.test.ts +++ b/tests/unit/db/omp.test.ts @@ -25,11 +25,8 @@ import os from "node:os"; import path from "node:path"; import Database from "better-sqlite3"; -const { - getOmpCredentials, - saveOmpCredentials, - deleteOmpCredentials, -} = await import("../../../src/lib/db/omp.ts"); +const { getOmpCredentials, saveOmpCredentials, deleteOmpCredentials } = + await import("../../../src/lib/db/omp.ts"); const PROVIDER_ID = "omniroute"; @@ -67,7 +64,7 @@ beforeEach(() => { afterEach(() => { process.env.HOME = origHome; - fs.rmSync(tmpHome, { recursive: true, force: true }); + fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); describe("db/omp.ts — getOmpCredentials", () => { diff --git a/tests/unit/db/per-engine-analytics.test.ts b/tests/unit/db/per-engine-analytics.test.ts index 018e14e1f291..58af68687e90 100644 --- a/tests/unit/db/per-engine-analytics.test.ts +++ b/tests/unit/db/per-engine-analytics.test.ts @@ -27,7 +27,7 @@ const { insertCompressionAnalyticsRow, getPerEngineAnalytics } = function resetDb(): void { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -39,7 +39,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (originalDataDir === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = originalDataDir; }); diff --git a/tests/unit/db/per-engine-breakdown-analytics.test.ts b/tests/unit/db/per-engine-breakdown-analytics.test.ts index e5fec7147e83..107d4768d0fb 100644 --- a/tests/unit/db/per-engine-breakdown-analytics.test.ts +++ b/tests/unit/db/per-engine-breakdown-analytics.test.ts @@ -27,7 +27,7 @@ const { insertCompressionAnalyticsRow, insertCompressionEngineBreakdown, getPerE function resetDb(): void { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -37,7 +37,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (originalDataDir === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = originalDataDir; }); diff --git a/tests/unit/db/quota-pools.test.ts b/tests/unit/db/quota-pools.test.ts index d81884074095..5245e3910f98 100644 --- a/tests/unit/db/quota-pools.test.ts +++ b/tests/unit/db/quota-pools.test.ts @@ -21,7 +21,7 @@ const { getDbInstance } = await import("../../../src/lib/db/core.ts"); function resetDb() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -44,7 +44,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ───────────────────────────────────────────────────────────────────────────── diff --git a/tests/unit/db/repositories/sqliteComboRepositories.test.ts b/tests/unit/db/repositories/sqliteComboRepositories.test.ts index a9d91812340b..94c41620e1d4 100644 --- a/tests/unit/db/repositories/sqliteComboRepositories.test.ts +++ b/tests/unit/db/repositories/sqliteComboRepositories.test.ts @@ -18,7 +18,7 @@ const combosDb = await import("../../../../src/lib/db/combos.ts"); async function resetStorage(): Promise { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -42,5 +42,5 @@ test("legacy combo count facade remains synchronous", async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); diff --git a/tests/unit/db/serviceModels.test.ts b/tests/unit/db/serviceModels.test.ts index 0e36f2164a2e..4d745d97fb0e 100644 --- a/tests/unit/db/serviceModels.test.ts +++ b/tests/unit/db/serviceModels.test.ts @@ -21,7 +21,7 @@ const { getServiceModels, saveServiceModels, markAllUnavailable } = function resetDb() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -31,7 +31,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("getServiceModels — returns [] when no row exists", () => { diff --git a/tests/unit/db/vacuum-scheduler.test.ts b/tests/unit/db/vacuum-scheduler.test.ts index 0f979c24958b..43fb6e9bb0aa 100644 --- a/tests/unit/db/vacuum-scheduler.test.ts +++ b/tests/unit/db/vacuum-scheduler.test.ts @@ -54,7 +54,7 @@ test.beforeEach(() => { test.after(() => { scheduler.__resetForTests(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (originalDataDir === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = originalDataDir; }); diff --git a/tests/unit/db/weak-rng-fixes.test.ts b/tests/unit/db/weak-rng-fixes.test.ts index 3d23d5d20c40..9f50fe15a44d 100644 --- a/tests/unit/db/weak-rng-fixes.test.ts +++ b/tests/unit/db/weak-rng-fixes.test.ts @@ -9,7 +9,7 @@ process.env.DATA_DIR = TEST_DATA_DIR; test.after(() => { try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch {} }); diff --git a/tests/unit/deepseek-thinking-efforts.test.ts b/tests/unit/deepseek-thinking-efforts.test.ts index a47dc88c7600..899402eab87b 100644 --- a/tests/unit/deepseek-thinking-efforts.test.ts +++ b/tests/unit/deepseek-thinking-efforts.test.ts @@ -19,14 +19,14 @@ const { sanitizeReasoningEffortForProvider } = await import("../../open-sse/exec test.beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); v1ModelsCatalog.__resetCatalogBuilderRunsForTest(); }); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("DeepSeek registries declare none/low/high/max on both V4 models", () => { diff --git a/tests/unit/delete-provider-connection-clears-combo-pins-8887.test.ts b/tests/unit/delete-provider-connection-clears-combo-pins-8887.test.ts index b38a81c8d04c..f97fb9115c91 100644 --- a/tests/unit/delete-provider-connection-clears-combo-pins-8887.test.ts +++ b/tests/unit/delete-provider-connection-clears-combo-pins-8887.test.ts @@ -22,6 +22,9 @@ async function resetStorage(): Promise { fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, + + maxRetries: 5, + retryDelay: 100, }); break; } catch (error: unknown) { @@ -84,6 +87,9 @@ test.after(() => { fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, + + maxRetries: 5, + retryDelay: 100, }); }); diff --git a/tests/unit/delete-provider-connection-invalidates-lkgp-8887.test.ts b/tests/unit/delete-provider-connection-invalidates-lkgp-8887.test.ts index 83226544fe09..04d4f52434ad 100644 --- a/tests/unit/delete-provider-connection-invalidates-lkgp-8887.test.ts +++ b/tests/unit/delete-provider-connection-invalidates-lkgp-8887.test.ts @@ -22,7 +22,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); break; } catch (error: unknown) { const code = @@ -60,7 +60,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#8887: single delete removes only the matching LKGP pin", async () => { diff --git a/tests/unit/delete-provider-connection-purges-key-health-7740.test.ts b/tests/unit/delete-provider-connection-purges-key-health-7740.test.ts index 0d9cc7bc3a73..f508cb51a4af 100644 --- a/tests/unit/delete-provider-connection-purges-key-health-7740.test.ts +++ b/tests/unit/delete-provider-connection-purges-key-health-7740.test.ts @@ -16,7 +16,8 @@ async function resetStorage() { core.resetDbInstance(); for (let attempt = 0; attempt < 10; attempt++) { try { - if (fs.existsSync(TEST_DATA_DIR)) fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + if (fs.existsSync(TEST_DATA_DIR)) + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); break; } catch (error: unknown) { const code = (error as { code?: string } | undefined)?.code; @@ -33,7 +34,7 @@ test.beforeEach(async () => { }); test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#7740: orphaned provider connection (id removed from catalog) keeps surfacing apiKeyHealth and 404s on click, but deleting purges in-memory key-health", async () => { diff --git a/tests/unit/devin-bridge-network-guard.test.ts b/tests/unit/devin-bridge-network-guard.test.ts index 2114311637cc..cd974f6a3d35 100644 --- a/tests/unit/devin-bridge-network-guard.test.ts +++ b/tests/unit/devin-bridge-network-guard.test.ts @@ -125,7 +125,7 @@ test("HTTP proxy overwrites Host and strips proxy and hop-by-hop credentials", a } finally { await close(proxy); await close(upstream); - fs.rmSync(tmp, { recursive: true, force: true }); + fs.rmSync(tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -163,7 +163,7 @@ test("CONNECT rejects mismatched SNI before opening an upstream socket", async ( assert.match(fs.readFileSync(logPath, "utf8"), /"reason":"sni_mismatch"/); } finally { await close(proxy); - fs.rmSync(tmp, { recursive: true, force: true }); + fs.rmSync(tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -212,6 +212,6 @@ test("CONNECT forwards only after matching SNI is validated", async () => { } finally { await close(proxy); await close(upstream); - fs.rmSync(tmp, { recursive: true, force: true }); + fs.rmSync(tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/dgrid-provider.test.ts b/tests/unit/dgrid-provider.test.ts index 4542afad330c..77fc9c77c390 100644 --- a/tests/unit/dgrid-provider.test.ts +++ b/tests/unit/dgrid-provider.test.ts @@ -71,13 +71,13 @@ const modelsRoute = await import("../../src/app/api/providers/[id]/models/route. async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); interface ModelsBody { diff --git a/tests/unit/dns-config-generic.test.ts b/tests/unit/dns-config-generic.test.ts index a9441895aeff..5f1d85921e28 100644 --- a/tests/unit/dns-config-generic.test.ts +++ b/tests/unit/dns-config-generic.test.ts @@ -272,7 +272,7 @@ test("addDNSEntries: generates both IPv4 and IPv6 lines per host", () => { // --------------------------------------------------------------------------- test.after(() => { try { - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // best effort } diff --git a/tests/unit/docker-llmlingua-optionals-9166.test.ts b/tests/unit/docker-llmlingua-optionals-9166.test.ts index ac57d0d362c6..1765cbd413a4 100644 --- a/tests/unit/docker-llmlingua-optionals-9166.test.ts +++ b/tests/unit/docker-llmlingua-optionals-9166.test.ts @@ -1,13 +1,6 @@ import test from "node:test"; import assert from "node:assert/strict"; -import { - existsSync, - mkdtempSync, - mkdirSync, - readFileSync, - rmSync, - writeFileSync, -} from "node:fs"; +import { existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { tmpdir } from "node:os"; @@ -44,10 +37,7 @@ function mkPkg( } } -function buildLlmlinguaRoot( - rootDir: string, - transformersVersion = "4.2.0" -): void { +function buildLlmlinguaRoot(rootDir: string, transformersVersion = "4.2.0"): void { const rootNm = join(rootDir, "node_modules"); mkPkg( @@ -97,18 +87,13 @@ function createStandalone(rootDir: string): { recursive: true, }); - writeFileSync( - join(standaloneDir, "package.json"), - JSON.stringify({ name: "standalone-test" }) - ); + writeFileSync(join(standaloneDir, "package.json"), JSON.stringify({ name: "standalone-test" })); return { distDir, standaloneDir }; } test("#9166 standalone assembly includes the complete LLMLingua runtime closure", () => { - const root = mkdtempSync( - join(tmpdir(), "omniroute-docker-llmlingua-9166-") - ); + const root = mkdtempSync(join(tmpdir(), "omniroute-docker-llmlingua-9166-")); try { buildLlmlinguaRoot(root); @@ -128,47 +113,30 @@ test("#9166 standalone assembly includes the complete LLMLingua runtime closure" "onnxruntime-node", ]) { assert.ok( - existsSync( - join(standaloneDir, "node_modules", packageName, "package.json") - ), + existsSync(join(standaloneDir, "node_modules", packageName, "package.json")), `${packageName} must be present in the standalone runtime` ); } assert.ok( - existsSync( - join( - standaloneDir, - "node_modules", - "@atjsh", - "llmlingua-2", - "dist", - "index.js" - ) - ), + existsSync(join(standaloneDir, "node_modules", "@atjsh", "llmlingua-2", "dist", "index.js")), "the complete LLMLingua package payload must be copied" ); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); test("#9166 standalone assembly never overwrites an already pinned transformers instance", () => { - const root = mkdtempSync( - join(tmpdir(), "omniroute-docker-llmlingua-pinned-9166-") - ); + const root = mkdtempSync(join(tmpdir(), "omniroute-docker-llmlingua-pinned-9166-")); try { buildLlmlinguaRoot(root, "5.0.0"); const { distDir, standaloneDir } = createStandalone(root); - mkPkg( - join(standaloneDir, "node_modules"), - "@huggingface/transformers", - { - version: "4.2.0", - } - ); + mkPkg(join(standaloneDir, "node_modules"), "@huggingface/transformers", { + version: "4.2.0", + }); assembleStandalone({ distDir, @@ -179,13 +147,7 @@ test("#9166 standalone assembly never overwrites an already pinned transformers const targetManifest = JSON.parse( readFileSync( - join( - standaloneDir, - "node_modules", - "@huggingface", - "transformers", - "package.json" - ), + join(standaloneDir, "node_modules", "@huggingface", "transformers", "package.json"), "utf8" ) ); @@ -197,25 +159,16 @@ test("#9166 standalone assembly never overwrites an already pinned transformers ); assert.ok( - existsSync( - join( - standaloneDir, - "node_modules", - "onnxruntime-node", - "package.json" - ) - ), + existsSync(join(standaloneDir, "node_modules", "onnxruntime-node", "package.json")), "missing dependencies from the transformers closure must still be copied" ); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); test("#9166 co-location completes a partially traced package (package.json without its main)", () => { - const root = mkdtempSync( - join(tmpdir(), "omniroute-docker-llmlingua-partial-9166-") - ); + const root = mkdtempSync(join(tmpdir(), "omniroute-docker-llmlingua-partial-9166-")); try { buildLlmlinguaRoot(root); @@ -238,27 +191,16 @@ test("#9166 co-location completes a partially traced package (package.json witho }); assert.ok( - existsSync( - join( - standaloneDir, - "node_modules", - "@atjsh", - "llmlingua-2", - "dist", - "index.js" - ) - ), + existsSync(join(standaloneDir, "node_modules", "@atjsh", "llmlingua-2", "dist", "index.js")), "a partially traced package must be completed, not skipped as already present" ); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); test("#9166 co-location is not skipped when every closure dir exists but one is partial", () => { - const root = mkdtempSync( - join(tmpdir(), "omniroute-docker-llmlingua-partial-all-9166-") - ); + const root = mkdtempSync(join(tmpdir(), "omniroute-docker-llmlingua-partial-all-9166-")); try { buildLlmlinguaRoot(root); @@ -275,9 +217,14 @@ test("#9166 co-location is not skipped when every closure dir exists but one is "@huggingface/transformers", "onnxruntime-node", ]) { - mkPkg(standaloneNm, packageName, { main: "index.js" }, { - "index.js": "export {};\n", - }); + mkPkg( + standaloneNm, + packageName, + { main: "index.js" }, + { + "index.js": "export {};\n", + } + ); } mkPkg(standaloneNm, "@atjsh/llmlingua-2", { main: "dist/index.js" }); @@ -289,28 +236,16 @@ test("#9166 co-location is not skipped when every closure dir exists but one is }); assert.ok( - existsSync( - join( - standaloneDir, - "node_modules", - "@atjsh", - "llmlingua-2", - "dist", - "index.js" - ) - ), + existsSync(join(standaloneDir, "node_modules", "@atjsh", "llmlingua-2", "dist", "index.js")), "the closure-wide early-exit must not fire while any member is partial" ); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); test("#9166 Docker explicitly installs and validates LLMLingua optionals", () => { - const dockerfile = readFileSync( - new URL("../../Dockerfile", import.meta.url), - "utf8" - ); + const dockerfile = readFileSync(new URL("../../Dockerfile", import.meta.url), "utf8"); const builderStart = dockerfile.indexOf("FROM base AS builder"); const runnerStart = dockerfile.indexOf("FROM base AS runner-base"); diff --git a/tests/unit/docs-validate-svg.test.ts b/tests/unit/docs-validate-svg.test.ts index a53ff75afd31..89b73e073d0c 100644 --- a/tests/unit/docs-validate-svg.test.ts +++ b/tests/unit/docs-validate-svg.test.ts @@ -27,7 +27,7 @@ test("SVG validator ignores Mermaid data-id attributes when checking duplicate I assert.match(result.stdout, /PASS/); assert.doesNotMatch(`${result.stdout}${result.stderr}`, /WARN/); } finally { - rmSync(fixtureDir, { recursive: true, force: true }); + rmSync(fixtureDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -46,7 +46,7 @@ test("SVG validator rejects duplicate XML id attributes", () => { assert.equal(result.status, 1, `${result.stdout}${result.stderr}`); assert.match(result.stderr, /duplicate IDs: edge-a/); } finally { - rmSync(fixtureDir, { recursive: true, force: true }); + rmSync(fixtureDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -101,6 +101,6 @@ test("SVG validator adds explicit accessible naming when requested for a generat assert.equal([...updated.matchAll(/id="auto-combo-title"/g)].length, 1); assert.equal([...updated.matchAll(/id="auto-combo-desc"/g)].length, 1); } finally { - rmSync(fixtureDir, { recursive: true, force: true }); + rmSync(fixtureDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/domain-branch-hardening.test.ts b/tests/unit/domain-branch-hardening.test.ts index bf2319d44002..d5cee8dbef49 100644 --- a/tests/unit/domain-branch-hardening.test.ts +++ b/tests/unit/domain-branch-hardening.test.ts @@ -34,7 +34,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: any) { @@ -63,7 +63,7 @@ test.after(async () => { fallbackPolicy.resetAllFallbacks(); providerExpiration.resetExpirations(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("resolveComboModel covers empty combos, priority, round-robin, random, least-used and default fallback", () => { diff --git a/tests/unit/domain-cost-rules.test.ts b/tests/unit/domain-cost-rules.test.ts index 6cab0a8015df..94fe9b7baeb3 100644 --- a/tests/unit/domain-cost-rules.test.ts +++ b/tests/unit/domain-cost-rules.test.ts @@ -18,7 +18,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: any) { @@ -40,7 +40,7 @@ test.beforeEach(async () => { test.after(async () => { costRules.resetCostData(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("setBudget normalizes defaults and getBudget returns the stored config", () => { diff --git a/tests/unit/domain-fallback-policy.test.ts b/tests/unit/domain-fallback-policy.test.ts index 28730aebc644..d69556f693c4 100644 --- a/tests/unit/domain-fallback-policy.test.ts +++ b/tests/unit/domain-fallback-policy.test.ts @@ -17,7 +17,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: any) { @@ -39,7 +39,7 @@ test.beforeEach(async () => { test.after(async () => { fallbackPolicy.resetAllFallbacks(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("registerFallback sorts by priority and defaults missing flags to enabled", () => { diff --git a/tests/unit/domain-lockout-policy.test.ts b/tests/unit/domain-lockout-policy.test.ts index 758742dc87fa..bf5d492342cc 100644 --- a/tests/unit/domain-lockout-policy.test.ts +++ b/tests/unit/domain-lockout-policy.test.ts @@ -16,7 +16,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: any) { @@ -41,7 +41,7 @@ test.beforeEach(async () => { test.after(async () => { Date.now = originalDateNow; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("checkLockout starts unlocked and locks after reaching the configured threshold", () => { diff --git a/tests/unit/domain-persistence.test.ts b/tests/unit/domain-persistence.test.ts index 95ef343ad3ac..66c9f79e3d92 100644 --- a/tests/unit/domain-persistence.test.ts +++ b/tests/unit/domain-persistence.test.ts @@ -45,7 +45,8 @@ afterEach(async () => { after(() => { process.env.DATA_DIR = originalDataDir; - if (fs.existsSync(fileTmpDir)) fs.rmSync(fileTmpDir, { recursive: true, force: true }); + if (fs.existsSync(fileTmpDir)) + fs.rmSync(fileTmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ─── Fallback Policy Tests ──────────────────────── diff --git a/tests/unit/duckduckgo-vqd-429-misclassification-6996.test.ts b/tests/unit/duckduckgo-vqd-429-misclassification-6996.test.ts index 8b00ac5dc3c6..90c174b8fbc2 100644 --- a/tests/unit/duckduckgo-vqd-429-misclassification-6996.test.ts +++ b/tests/unit/duckduckgo-vqd-429-misclassification-6996.test.ts @@ -7,9 +7,8 @@ import path from "node:path"; const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-6996-")); process.env.DATA_DIR = TEST_DATA_DIR; -const { DuckDuckGoWebExecutor, STATUS_URL } = await import( - "../../open-sse/executors/duckduckgo-web.ts" -); +const { DuckDuckGoWebExecutor, STATUS_URL } = + await import("../../open-sse/executors/duckduckgo-web.ts"); const { resetDbInstance } = await import("../../src/lib/db/core.ts"); const executeInputBase = { model: "gpt-4o-mini", @@ -32,7 +31,7 @@ describe("#6996 DuckDuckGo VQD 429 misclassification", () => { after(() => { globalThis.fetch = originalFetch; resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); it("propagates upstream 429 instead of masking it as a generic 503", async () => { @@ -57,9 +56,7 @@ describe("#6996 DuckDuckGo VQD 429 misclassification", () => { const response = await executor.execute(executeInputBase); const httpResponse = - response instanceof Response - ? response - : (response as { response: Response }).response; + response instanceof Response ? response : (response as { response: Response }).response; const bodyText = await httpResponse.text(); assert.equal( @@ -85,9 +82,7 @@ describe("#6996 DuckDuckGo VQD 429 misclassification", () => { const response = await executor.execute(executeInputBase); const httpResponse = - response instanceof Response - ? response - : (response as { response: Response }).response; + response instanceof Response ? response : (response as { response: Response }).response; const bodyText = await httpResponse.text(); assert.equal( diff --git a/tests/unit/effort-thinking-standardization-6241.test.ts b/tests/unit/effort-thinking-standardization-6241.test.ts index 406cb823775d..95d906df536c 100644 --- a/tests/unit/effort-thinking-standardization-6241.test.ts +++ b/tests/unit/effort-thinking-standardization-6241.test.ts @@ -19,7 +19,7 @@ const registry = await import("../../src/lib/modelMetadataRegistry.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -29,7 +29,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ── Schema ───────────────────────────────────────────────────────────── diff --git a/tests/unit/effort-tiers-loop-catalog-e2e.test.ts b/tests/unit/effort-tiers-loop-catalog-e2e.test.ts index 4e6b5c266303..a3818d8ca941 100644 --- a/tests/unit/effort-tiers-loop-catalog-e2e.test.ts +++ b/tests/unit/effort-tiers-loop-catalog-e2e.test.ts @@ -23,7 +23,7 @@ const { recordLearnedReasoningEffort, __test_resetLearnedReasoningEffortCaps } = async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -46,7 +46,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("learned set flows end-to-end into /v1/models capabilities and variant entries", async () => { diff --git a/tests/unit/egress-ip-lock-10880.test.ts b/tests/unit/egress-ip-lock-10880.test.ts index c4b67cdb0da8..34b17dedf4c1 100644 --- a/tests/unit/egress-ip-lock-10880.test.ts +++ b/tests/unit/egress-ip-lock-10880.test.ts @@ -55,7 +55,7 @@ let seedSeq = 0; async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -97,7 +97,7 @@ function seedProxyLog(connectionId: string, egressIp: string, provider: string = test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("siblings sharing the egress IP are cooled down with the same cooldown", async () => { diff --git a/tests/unit/electron-main.test.ts b/tests/unit/electron-main.test.ts index b8da073bdd87..a378a3ba71c7 100644 --- a/tests/unit/electron-main.test.ts +++ b/tests/unit/electron-main.test.ts @@ -492,7 +492,7 @@ describe("Electron SQLite credential inspection", () => { fn(dbPath, db); } finally { db.close(); - rmSync(dir, { recursive: true, force: true }); + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } diff --git a/tests/unit/electron-packaging.test.ts b/tests/unit/electron-packaging.test.ts index e08a2f9ee985..cbddd60babd8 100644 --- a/tests/unit/electron-packaging.test.ts +++ b/tests/unit/electron-packaging.test.ts @@ -115,7 +115,7 @@ test("electron docs manifest prunes authoring payloads without removing runtime removedPaths: [], }); } finally { - rmSync(bundleRoot, { recursive: true, force: true }); + rmSync(bundleRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/electron-remote-server.test.ts b/tests/unit/electron-remote-server.test.ts index 9595a0d6fe63..a3ed546cd962 100644 --- a/tests/unit/electron-remote-server.test.ts +++ b/tests/unit/electron-remote-server.test.ts @@ -33,7 +33,7 @@ function withTempDir(fn: (dir: string) => void) { try { fn(dir); } finally { - rmSync(dir, { recursive: true, force: true }); + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } diff --git a/tests/unit/electron-smoke-script.test.ts b/tests/unit/electron-smoke-script.test.ts index 874f87e28191..5c1ad11dc391 100644 --- a/tests/unit/electron-smoke-script.test.ts +++ b/tests/unit/electron-smoke-script.test.ts @@ -65,7 +65,7 @@ test("electron smoke pre-creates the USERPROFILE-derived Roaming userData tree o assert.ok(fs.existsSync(viaAppData), `expected pre-created APPDATA dir: ${viaAppData}`); } } finally { - fs.rmSync(dataDir, { recursive: true, force: true }); + fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -85,7 +85,7 @@ test("electron smoke tarPack handles absolute Windows-style tarball paths", () = assert.ok(fs.existsSync(tarballPath), "tarball should exist after tarPack"); assert.ok(fs.statSync(tarballPath).size > 0, "tarball should not be empty"); } finally { - fs.rmSync(staging, { recursive: true, force: true }); + fs.rmSync(staging, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/electron-sqlite-prebuild.test.ts b/tests/unit/electron-sqlite-prebuild.test.ts index 9c344f7db643..8f9550c5b3b7 100644 --- a/tests/unit/electron-sqlite-prebuild.test.ts +++ b/tests/unit/electron-sqlite-prebuild.test.ts @@ -81,6 +81,6 @@ test("prebuild verification fails fast when the selected binary is missing", () fs.writeFileSync(expected, "napi"); assert.equal(assertSqlitePrebuildExists?.(moduleDir, "darwin", "arm64"), expected); } finally { - fs.rmSync(moduleDir, { recursive: true, force: true }); + fs.rmSync(moduleDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/elevenlabs-native-routes.test.ts b/tests/unit/elevenlabs-native-routes.test.ts index ded3468f3102..c3eb26718e7a 100644 --- a/tests/unit/elevenlabs-native-routes.test.ts +++ b/tests/unit/elevenlabs-native-routes.test.ts @@ -6,18 +6,13 @@ import path from "node:path"; const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-elevenlabs-native-")); process.env.DATA_DIR = TEST_DATA_DIR; -process.env.API_KEY_SECRET = - process.env.API_KEY_SECRET || "elevenlabs-native-route-test-secret"; +process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "elevenlabs-native-route-test-secret"; const core = await import("../../src/lib/db/core.ts"); const readCache = await import("../../src/lib/db/readCache.ts"); const voicesRoute = await import("../../src/app/api/v1/voices/route.ts"); -const speechRoute = await import( - "../../src/app/api/v1/text-to-speech/[voiceId]/route.ts" -); -const transcriptionRoute = await import( - "../../src/app/api/v1/speech-to-text/route.ts" -); +const speechRoute = await import("../../src/app/api/v1/text-to-speech/[voiceId]/route.ts"); +const transcriptionRoute = await import("../../src/app/api/v1/speech-to-text/route.ts"); const originalFetch = globalThis.fetch; const API_KEY = "test-elevenlabs-key"; @@ -35,9 +30,10 @@ function seedCredential() { } function clearCredentials() { - core.getDbInstance().prepare("DELETE FROM provider_connections WHERE provider = ?").run( - "elevenlabs" - ); + core + .getDbInstance() + .prepare("DELETE FROM provider_connections WHERE provider = ?") + .run("elevenlabs"); readCache.invalidateDbCache("connections"); } @@ -53,7 +49,7 @@ test.afterEach(() => { test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("GET /v1/voices forwards query and stored xi-api-key", async () => { @@ -89,14 +85,11 @@ test("POST /v1/text-to-speech/[voiceId] forwards JSON and binary response", asyn }) as typeof fetch; const response = await speechRoute.POST( - new Request( - "http://localhost/v1/text-to-speech/voice_123?output_format=mp3_44100_128", - { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: payload, - } - ), + new Request("http://localhost/v1/text-to-speech/voice_123?output_format=mp3_44100_128", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: payload, + }), { params: Promise.resolve({ voiceId: "voice_123" }) } ); assert.equal(response.status, 200); diff --git a/tests/unit/embedding-account-cooldown-10347.test.ts b/tests/unit/embedding-account-cooldown-10347.test.ts index a9c1d8b5ce86..9410c1577db1 100644 --- a/tests/unit/embedding-account-cooldown-10347.test.ts +++ b/tests/unit/embedding-account-cooldown-10347.test.ts @@ -14,7 +14,7 @@ const auth = await import("../../src/sse/services/auth.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -31,7 +31,7 @@ async function seedConnection(provider: string): Promise { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#10347: markAccountUnavailable triggers cooldown on embedding 402", async () => { diff --git a/tests/unit/embedding-cooldown-integration-10347.test.ts b/tests/unit/embedding-cooldown-integration-10347.test.ts index f7bff8ea7a9b..15fdbe768f37 100644 --- a/tests/unit/embedding-cooldown-integration-10347.test.ts +++ b/tests/unit/embedding-cooldown-integration-10347.test.ts @@ -21,7 +21,7 @@ const auth = await import("../../src/sse/services/auth.ts"); function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -42,7 +42,7 @@ async function seedConnection( test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("createEmbeddingResponse marks connection on upstream 402", async () => { diff --git a/tests/unit/embeddings-cost-telemetry-headers.test.ts b/tests/unit/embeddings-cost-telemetry-headers.test.ts index 30320a8f2a6d..1c78001ef342 100644 --- a/tests/unit/embeddings-cost-telemetry-headers.test.ts +++ b/tests/unit/embeddings-cost-telemetry-headers.test.ts @@ -15,7 +15,7 @@ const { OMNIROUTE_RESPONSE_HEADERS } = await import("../../src/shared/constants/ test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("createEmbeddingResponse emits X-OmniRoute-* cost telemetry headers on success", async () => { diff --git a/tests/unit/embeddings-lan-noauth-6925.test.ts b/tests/unit/embeddings-lan-noauth-6925.test.ts index e2e07e5c0d26..2b91a43c5dff 100644 --- a/tests/unit/embeddings-lan-noauth-6925.test.ts +++ b/tests/unit/embeddings-lan-noauth-6925.test.ts @@ -14,7 +14,7 @@ const { createEmbeddingResponse } = await import("../../src/lib/embeddings/servi test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // #6925: a keyless LAN OpenAI-compatible embeddings provider (e.g. Ollama at diff --git a/tests/unit/embeddings-proxy-forwarding.test.ts b/tests/unit/embeddings-proxy-forwarding.test.ts index 2d93d066bf75..a48cdbf65c64 100644 --- a/tests/unit/embeddings-proxy-forwarding.test.ts +++ b/tests/unit/embeddings-proxy-forwarding.test.ts @@ -17,10 +17,13 @@ const { resolveProxyForRequest } = await import("../../open-sse/utils/proxyFetch test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); -async function withHttpServer(handler: http.RequestListener, fn: (baseUrl: string) => Promise) { +async function withHttpServer( + handler: http.RequestListener, + fn: (baseUrl: string) => Promise +) { const server = http.createServer(handler); await new Promise((resolve, reject) => { server.once("error", reject); diff --git a/tests/unit/embeddings-route-apikeymeta-6929.test.ts b/tests/unit/embeddings-route-apikeymeta-6929.test.ts index 8e241d731cba..d3e686297967 100644 --- a/tests/unit/embeddings-route-apikeymeta-6929.test.ts +++ b/tests/unit/embeddings-route-apikeymeta-6929.test.ts @@ -44,7 +44,7 @@ const PLAYGROUND_KEY_ID_HEADER = "x-omniroute-playground-key-id"; test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); async function sessionCookie(): Promise { diff --git a/tests/unit/emergency-fallback-service.test.ts b/tests/unit/emergency-fallback-service.test.ts index b229fb70122c..65b2d2458afd 100644 --- a/tests/unit/emergency-fallback-service.test.ts +++ b/tests/unit/emergency-fallback-service.test.ts @@ -29,7 +29,7 @@ function restoreEnv(name: string, value: string | undefined) { function resetTestState() { core.resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(tmpDir, { recursive: true }); delete process.env.OMNIROUTE_EMERGENCY_FALLBACK; resetEmergencyFallbackEnvCache(); @@ -46,7 +46,7 @@ test.afterEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); restoreEnv("DATA_DIR", previousDataDir); restoreEnv("DISABLE_SQLITE_AUTO_BACKUP", previousDisableSqliteAutoBackup); }); diff --git a/tests/unit/empty-choices-no-inject.test.ts b/tests/unit/empty-choices-no-inject.test.ts index a252e0c29495..3de851b328b4 100644 --- a/tests/unit/empty-choices-no-inject.test.ts +++ b/tests/unit/empty-choices-no-inject.test.ts @@ -28,7 +28,7 @@ async function readTransformed(chunks: string[], options: Record { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/endpoint-restrictions-policy.test.ts b/tests/unit/endpoint-restrictions-policy.test.ts index 5c5bba8ec51a..24ac4812e47c 100644 --- a/tests/unit/endpoint-restrictions-policy.test.ts +++ b/tests/unit/endpoint-restrictions-policy.test.ts @@ -30,7 +30,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: any) { @@ -79,7 +79,7 @@ test.after(async () => { apiKeysDb.resetApiKeyState(); costRules.resetCostData(); coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ─── Policy tests ───────────────────────────────────────────────────────── @@ -137,10 +137,7 @@ test("chat-only key blocks /v1/embeddings", async () => { assert.ok(result.rejection, "Should reject the request"); assert.equal(result.rejection.status, 403); const msg = await readErrorMessage(result.rejection); - assert.ok( - msg.includes("embeddings"), - `Error message should mention 'embeddings', got: ${msg}` - ); + assert.ok(msg.includes("embeddings"), `Error message should mention 'embeddings', got: ${msg}`); }); test("search-only key blocks /v1/images/generations", async () => { diff --git a/tests/unit/error-message-sanitization.test.ts b/tests/unit/error-message-sanitization.test.ts index 4c726771643d..f33a74a59104 100644 --- a/tests/unit/error-message-sanitization.test.ts +++ b/tests/unit/error-message-sanitization.test.ts @@ -32,7 +32,7 @@ function makeRequest(url: string, options: { method?: string; body?: unknown } = async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -42,7 +42,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); async function createCombo(name: string, model: string) { diff --git a/tests/unit/evals-history.test.ts b/tests/unit/evals-history.test.ts index c4ebe9104a74..033c04cd98bd 100644 --- a/tests/unit/evals-history.test.ts +++ b/tests/unit/evals-history.test.ts @@ -12,7 +12,7 @@ const evalsDb = await import("../../src/lib/db/evals.ts"); function resetDb() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -22,7 +22,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("eval run history persists target metadata and newest-first ordering", () => { diff --git a/tests/unit/evals-route.test.ts b/tests/unit/evals-route.test.ts index 4893d3697129..c9e5b83e8c13 100644 --- a/tests/unit/evals-route.test.ts +++ b/tests/unit/evals-route.test.ts @@ -25,7 +25,7 @@ const evalSuiteByIdRoute = await import("../../src/app/api/evals/suites/[suiteId function resetDb() { core.resetDbInstance(); localDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -36,7 +36,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); localDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("evals GET returns suites, target options, api key metadata, and persisted history", async () => { diff --git a/tests/unit/exclusive-connection-leases.test.ts b/tests/unit/exclusive-connection-leases.test.ts index fcfceb71c796..b2ae14c28f2f 100644 --- a/tests/unit/exclusive-connection-leases.test.ts +++ b/tests/unit/exclusive-connection-leases.test.ts @@ -20,7 +20,7 @@ function at(seconds: number): string { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("hashes canonical owners and never persists the raw owner", () => { @@ -463,6 +463,6 @@ test("cross-process contenders never both acquire the same connection", async () assert.equal(results.filter((result) => result.kind === "ACQUIRED").length, 1); assert.equal(results.filter((result) => result.kind === "CONNECTION_BUSY").length, 1); } finally { - fs.rmSync(raceDir, { recursive: true, force: true }); + fs.rmSync(raceDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/exclusive-lease-api-key-policy.test.ts b/tests/unit/exclusive-lease-api-key-policy.test.ts index dfd6d4dc4b64..d51f4d152759 100644 --- a/tests/unit/exclusive-lease-api-key-policy.test.ts +++ b/tests/unit/exclusive-lease-api-key-policy.test.ts @@ -17,7 +17,7 @@ const CONNECTION = "00000000-0000-4000-8000-000000000001"; async function resetStorage(): Promise { core.resetDbInstance(); apiKeys.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -25,7 +25,7 @@ test.beforeEach(resetStorage); test.after(() => { core.resetDbInstance(); apiKeys.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("managed API key create requires and atomically stores an explicit allowlist", async () => { diff --git a/tests/unit/exclusive-lease-auxiliary-isolation.test.ts b/tests/unit/exclusive-lease-auxiliary-isolation.test.ts index da6174648c75..199c2aba7c0e 100644 --- a/tests/unit/exclusive-lease-auxiliary-isolation.test.ts +++ b/tests/unit/exclusive-lease-auxiliary-isolation.test.ts @@ -49,7 +49,7 @@ async function markLeaseOnly(connectionId: string): Promise { async function resetStorage(): Promise { core.resetDbInstance(); apiKeys.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); externalCalls = 0; } @@ -59,7 +59,7 @@ test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); apiKeys.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("translator send excludes a FREE lease-only connection before provider fetch", async () => { diff --git a/tests/unit/exclusive-lease-connection-test-isolation.test.ts b/tests/unit/exclusive-lease-connection-test-isolation.test.ts index 34e565836bbc..957f678dee05 100644 --- a/tests/unit/exclusive-lease-connection-test-isolation.test.ts +++ b/tests/unit/exclusive-lease-connection-test-isolation.test.ts @@ -28,7 +28,7 @@ const OWNER = "vlo_TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT"; test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("connection verification skips an ACTIVE exclusive lease before any probe or mutation", async () => { diff --git a/tests/unit/exclusive-lease-managed-set.test.ts b/tests/unit/exclusive-lease-managed-set.test.ts index 86e44ffb589b..e5fdcc73f45b 100644 --- a/tests/unit/exclusive-lease-managed-set.test.ts +++ b/tests/unit/exclusive-lease-managed-set.test.ts @@ -44,7 +44,7 @@ function insertKey(input: { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("derives the overlapping managed set from active scoped key allowlists", async () => { diff --git a/tests/unit/exclusive-session-observability.test.ts b/tests/unit/exclusive-session-observability.test.ts index d5fd11a4a892..161665ba410b 100644 --- a/tests/unit/exclusive-session-observability.test.ts +++ b/tests/unit/exclusive-session-observability.test.ts @@ -42,7 +42,7 @@ test.afterEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("projects idle and active leases, distinct connections, legacy rows, and de-duplication", () => { diff --git a/tests/unit/execute-chat-resource-pressure-breaker.test.ts b/tests/unit/execute-chat-resource-pressure-breaker.test.ts index 4bf1f54fe7ba..790f309eebf2 100644 --- a/tests/unit/execute-chat-resource-pressure-breaker.test.ts +++ b/tests/unit/execute-chat-resource-pressure-breaker.test.ts @@ -25,7 +25,7 @@ const MiB = 1024 ** 2; async function resetStorage() { resetAllCircuitBreakers(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); // Restore a non-shedding resource pressure runtime between tests. reloadResourcePressureRuntime({ @@ -53,7 +53,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("executeChatWithBreaker returns typed pressure 503 before normal, bypass, and shadow breaker paths", async () => { diff --git a/tests/unit/execute-web-search-fallback-11524.test.ts b/tests/unit/execute-web-search-fallback-11524.test.ts index 1daf06a94b84..517868f4878f 100644 --- a/tests/unit/execute-web-search-fallback-11524.test.ts +++ b/tests/unit/execute-web-search-fallback-11524.test.ts @@ -15,7 +15,7 @@ const { executeWebSearch } = await import("../../src/lib/search/executeWebSearch async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -44,7 +44,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // Regression test for #11524 — executeWebSearch must prefer a credentialed diff --git a/tests/unit/executor-devin-cli-acp-protocol-8406.test.ts b/tests/unit/executor-devin-cli-acp-protocol-8406.test.ts index ae6a3a707250..c00107277323 100644 --- a/tests/unit/executor-devin-cli-acp-protocol-8406.test.ts +++ b/tests/unit/executor-devin-cli-acp-protocol-8406.test.ts @@ -115,6 +115,6 @@ rl.on('line', (line) => { } else { delete process.env.CLI_DEVIN_BIN; } - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/executor-devin-cli-agentic-acp.test.ts b/tests/unit/executor-devin-cli-agentic-acp.test.ts index 5538d3a86e11..330098390408 100644 --- a/tests/unit/executor-devin-cli-agentic-acp.test.ts +++ b/tests/unit/executor-devin-cli-agentic-acp.test.ts @@ -272,7 +272,7 @@ test("DevinCliAgenticExecutor returns Anthropic tool_use JSON and sends ACP fram } finally { if (oldBin === undefined) delete process.env.CLI_DEVIN_AGENTIC_BIN; else process.env.CLI_DEVIN_AGENTIC_BIN = oldBin; - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -303,7 +303,7 @@ test("no-tools summarizer does not depend on mutable ACP permission modes", asyn const body = JSON.parse(await result.response.text()); assert.equal(body.content[0].text, "unsafe"); } finally { - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -323,7 +323,7 @@ test("ACP client fails closed when session/new omits the session id", async () = const body = JSON.parse(await result.response.text()); assert.equal(body.error.code, "missing_session_id"); } finally { - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -351,7 +351,7 @@ test("DevinCliAgenticExecutor returns Anthropic SSE for streaming Claude clients } finally { if (oldBin === undefined) delete process.env.CLI_DEVIN_AGENTIC_BIN; else process.env.CLI_DEVIN_AGENTIC_BIN = oldBin; - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -382,7 +382,7 @@ test("ACP client handles fragmented frames, multiple chunks, and stderr", async const body = JSON.parse(await result.response.text()); assert.equal(body.content[0].text, "Hello"); } finally { - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -408,7 +408,7 @@ test("ACP client fails closed when Devin attempts an internal tool call", async const body = JSON.parse(await result.response.text()); assert.equal(body.error.code, "devin_internal_tool_execution"); } finally { - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -439,7 +439,7 @@ test("ACP client fails closed on protocol errors and early exit", async () => { const body = JSON.parse(await result.response.text()); assert.equal(body.error.code, scenario.code, scenario.name); } finally { - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } }); @@ -463,7 +463,7 @@ test("ACP client times out, cancels, and terminates a stuck process", async () = } finally { if (oldTimeout === undefined) delete process.env.DEVIN_AGENTIC_ACP_TIMEOUT_MS; else process.env.DEVIN_AGENTIC_ACP_TIMEOUT_MS = oldTimeout; - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -517,7 +517,7 @@ rl.on("line", (line) => { } finally { if (oldBin === undefined) delete process.env.CLI_DEVIN_AGENTIC_BIN; else process.env.CLI_DEVIN_AGENTIC_BIN = oldBin; - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -572,6 +572,6 @@ rl.on("line", (line) => { } finally { if (oldBin === undefined) delete process.env.CLI_DEVIN_AGENTIC_BIN; else process.env.CLI_DEVIN_AGENTIC_BIN = oldBin; - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/executor-map-golden.test.ts b/tests/unit/executor-map-golden.test.ts index 569351d74c35..b3533e267b72 100644 --- a/tests/unit/executor-map-golden.test.ts +++ b/tests/unit/executor-map-golden.test.ts @@ -18,15 +18,14 @@ const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-executor- process.env.DATA_DIR = TEST_DATA_DIR; // Dynamic imports AFTER DATA_DIR is set so db/core.ts picks up the temp path. -const { getExecutor, hasSpecializedExecutor, DefaultExecutor } = await import( - "../../open-sse/executors/index.ts" -); +const { getExecutor, hasSpecializedExecutor, DefaultExecutor } = + await import("../../open-sse/executors/index.ts"); const { PROVIDERS } = await import("../../open-sse/config/constants.ts"); const { SEARCH_PROVIDERS } = await import("../../open-sse/config/searchRegistry.ts"); const { goldenSnapshot } = await import("../helpers/goldenSnapshot.ts"); test.after(() => { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // The specialized keys are not exported; enumerate them through the public @@ -68,8 +67,7 @@ function describeExecutor(instance: unknown): { return { className: inst.constructor.name, provider: typeof inst.provider === "string" ? inst.provider : null, - configSource: - cfg == null ? null : (providerConfigKeyByRef.get(cfg) ?? ""), + configSource: cfg == null ? null : (providerConfigKeyByRef.get(cfg) ?? ""), }; } diff --git a/tests/unit/executor-registry.test.ts b/tests/unit/executor-registry.test.ts index b1c82c80991c..efb4f8d20a71 100644 --- a/tests/unit/executor-registry.test.ts +++ b/tests/unit/executor-registry.test.ts @@ -13,12 +13,11 @@ process.env.DATA_DIR = TEST_DATA_DIR; const { registerExecutor, getRegisteredExecutor, hasRegisteredExecutor, listExecutorAliases } = await import("../../open-sse/executors/registry.ts"); -const { getExecutor, hasSpecializedExecutor, BaseExecutor, DefaultExecutor } = await import( - "../../open-sse/executors/index.ts" -); +const { getExecutor, hasSpecializedExecutor, BaseExecutor, DefaultExecutor } = + await import("../../open-sse/executors/index.ts"); test.after(() => { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("built-ins are registered at module load and resolve through the registry", async () => { diff --git a/tests/unit/feature-flags-settings.test.ts b/tests/unit/feature-flags-settings.test.ts index d53d9c199d48..708ebed493d5 100644 --- a/tests/unit/feature-flags-settings.test.ts +++ b/tests/unit/feature-flags-settings.test.ts @@ -230,7 +230,7 @@ describe("featureFlagDefinitions", () => { describe("featureFlags DB module", () => { function resetDb() { core.resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(tmpDir, { recursive: true }); } @@ -240,7 +240,7 @@ describe("featureFlags DB module", () => { after(() => { core.resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); it("getFeatureFlagOverrides returns empty object when no overrides", () => { @@ -289,7 +289,7 @@ describe("featureFlags DB module", () => { describe("resolveFeatureFlag", () => { function resetDb() { core.resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(tmpDir, { recursive: true }); } @@ -300,7 +300,7 @@ describe("resolveFeatureFlag", () => { after(() => { core.resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); delete process.env["REQUIRE_API_KEY"]; }); @@ -397,7 +397,7 @@ describe("resolveFeatureFlag", () => { console.error = () => {}; try { core.resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(tmpDir, { recursive: true }); const blockerPath = path.join(tmpDir, "storage.sqlite"); fs.mkdirSync(blockerPath, { recursive: true }); @@ -405,7 +405,7 @@ describe("resolveFeatureFlag", () => { } finally { console.error = originalError; core.resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(tmpDir, { recursive: true }); } }); @@ -460,7 +460,7 @@ describe("resolveFeatureFlag", () => { console.error = () => {}; try { core.resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(tmpDir, { recursive: true }); const blockerPath = path.join(tmpDir, "storage.sqlite"); fs.mkdirSync(blockerPath, { recursive: true }); @@ -468,7 +468,7 @@ describe("resolveFeatureFlag", () => { } finally { console.error = originalError; core.resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(tmpDir, { recursive: true }); } }); diff --git a/tests/unit/feature-triage/integration.test.mjs b/tests/unit/feature-triage/integration.test.mjs index b56b2c564e7c..bff7678cda5d 100644 --- a/tests/unit/feature-triage/integration.test.mjs +++ b/tests/unit/feature-triage/integration.test.mjs @@ -155,6 +155,6 @@ describe("feature-triage integration", () => { assert.equal(out.counts.skip_has_pr, 1); assert.equal(out.buckets.already_delivered[0].version, "v3.7.2"); - rmSync(tmp, { recursive: true, force: true }); + rmSync(tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); }); diff --git a/tests/unit/felo-web-runtime-block.test.ts b/tests/unit/felo-web-runtime-block.test.ts index 82eea583f2e5..f161bd2996b4 100644 --- a/tests/unit/felo-web-runtime-block.test.ts +++ b/tests/unit/felo-web-runtime-block.test.ts @@ -35,7 +35,7 @@ const RETIRED_PROVIDER_VARIANTS = [ async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); core.getDbInstance(); modelAliasResolver.invalidateAliasCache(); @@ -54,7 +54,7 @@ test.afterEach(async () => { test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function isRetiredError(error: unknown): boolean { diff --git a/tests/unit/file-deletion.test.ts b/tests/unit/file-deletion.test.ts index 057d02f7b5be..e74f480ef16c 100644 --- a/tests/unit/file-deletion.test.ts +++ b/tests/unit/file-deletion.test.ts @@ -14,7 +14,7 @@ const { getDbInstance, resetDbInstance } = await import("@/lib/db/core"); after(() => { resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; } else { diff --git a/tests/unit/fix-bare-model-precedence.test.ts b/tests/unit/fix-bare-model-precedence.test.ts index 72107710d732..c3029be74226 100644 --- a/tests/unit/fix-bare-model-precedence.test.ts +++ b/tests/unit/fix-bare-model-precedence.test.ts @@ -9,9 +9,8 @@ process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); const providersDb = await import("../../src/lib/db/providers.ts"); -const { CODEX_NATIVE_UNPREFIXED_MODELS, getModelInfoCore } = await import( - "../../open-sse/services/model.ts" -); +const { CODEX_NATIVE_UNPREFIXED_MODELS, getModelInfoCore } = + await import("../../open-sse/services/model.ts"); // #FIX: bare Codex-default model ids must route to the `codex` provider // (chatgpt.com OAuth) when no provider prefix is supplied, even when other @@ -37,7 +36,7 @@ async function seedActiveCodexConnection() { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("CODEX_NATIVE_UNPREFIXED_MODELS includes gpt-5.6-sol tier set", () => { diff --git a/tests/unit/fix-bare-routing-fallback.test.ts b/tests/unit/fix-bare-routing-fallback.test.ts index 6a7f693c9581..46a68ee52838 100644 --- a/tests/unit/fix-bare-routing-fallback.test.ts +++ b/tests/unit/fix-bare-routing-fallback.test.ts @@ -36,7 +36,7 @@ test.before(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("bare gpt-5.6-sol routes to codex (precedence via CODEX_NATIVE_UNPREFIXED_MODELS)", async () => { @@ -85,4 +85,4 @@ test("bare claude-opus-5 never resolves to kiro (synced-catalog validation)", as test("bare claude-opus-4-8 also never resolves to kiro (same fix must apply to all shared models)", async () => { const info = await getModelInfoCore("claude-opus-4-8", null); assert.notEqual(info.provider, "kiro"); -}); \ No newline at end of file +}); diff --git a/tests/unit/fix-tls-client-node-binary-7802.test.ts b/tests/unit/fix-tls-client-node-binary-7802.test.ts index c4c120994989..80d735d1cba0 100644 --- a/tests/unit/fix-tls-client-node-binary-7802.test.ts +++ b/tests/unit/fix-tls-client-node-binary-7802.test.ts @@ -22,7 +22,7 @@ test("no-ops when node_modules/tls-client-node is absent (module not installed)" await fixTlsClientNodeBinary({ rootDir, log }); assert.deepEqual(logs, []); } finally { - rmSync(rootDir, { recursive: true, force: true }); + rmSync(rootDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -43,7 +43,7 @@ test("copies an already-populated root bin/ into the standalone dist bundle (#78 assert.ok(existsSync(distBin), "dist bin/ should have been created"); assert.deepEqual(readdirSync(distBin), ["tls-client-linux-ubuntu-amd64-1.0.0.so"]); } finally { - rmSync(rootDir, { recursive: true, force: true }); + rmSync(rootDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -79,7 +79,7 @@ test("retries the download when root bin/ is empty, and stops once a file appear "expected a success log once the retry recovered" ); } finally { - rmSync(rootDir, { recursive: true, force: true }); + rmSync(rootDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -98,9 +98,7 @@ test("warns without throwing when every retry leaves bin/ empty (still rate-limi console.warn = (m: string) => warnings.push(m); try { const { log } = collectLogs(); - await assert.doesNotReject( - fixTlsClientNodeBinary({ rootDir, log, retryDelaysMs: [1, 1] }) - ); + await assert.doesNotReject(fixTlsClientNodeBinary({ rootDir, log, retryDelaysMs: [1, 1] })); } finally { console.warn = originalWarn; } @@ -110,6 +108,6 @@ test("warns without throwing when every retry leaves bin/ empty (still rate-limi "expected a clear warning pointing at the manual fix, not a silent no-op" ); } finally { - rmSync(rootDir, { recursive: true, force: true }); + rmSync(rootDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/fixes-p1.test.ts b/tests/unit/fixes-p1.test.ts index 186cdd7eacb1..93d76898d20c 100644 --- a/tests/unit/fixes-p1.test.ts +++ b/tests/unit/fixes-p1.test.ts @@ -57,7 +57,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (err: any) { @@ -73,7 +73,7 @@ async function resetStorage() { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("token refresh dedupe key avoids collision for same-prefix tokens", async () => { diff --git a/tests/unit/free-provider-rankings-custom-models-6368.test.ts b/tests/unit/free-provider-rankings-custom-models-6368.test.ts index 98880f654a51..25cb491b957d 100644 --- a/tests/unit/free-provider-rankings-custom-models-6368.test.ts +++ b/tests/unit/free-provider-rankings-custom-models-6368.test.ts @@ -39,7 +39,7 @@ const CUSTOM_MODEL_ID = "claude-fable-5-6368"; test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("mergeProviderModels: additively includes custom models, de-duping by id", () => { diff --git a/tests/unit/free-provider-rankings-usage-route.test.ts b/tests/unit/free-provider-rankings-usage-route.test.ts index e493ddca074f..314d72b8d6b1 100644 --- a/tests/unit/free-provider-rankings-usage-route.test.ts +++ b/tests/unit/free-provider-rankings-usage-route.test.ts @@ -29,7 +29,7 @@ test.after(() => { core.resetDbInstance(); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("route: an unknown usageRange is rejected with 400, not coerced", async () => { diff --git a/tests/unit/free-proxies-add-to-pool.test.ts b/tests/unit/free-proxies-add-to-pool.test.ts index 3767762c5a20..c16ea70e3a01 100644 --- a/tests/unit/free-proxies-add-to-pool.test.ts +++ b/tests/unit/free-proxies-add-to-pool.test.ts @@ -21,7 +21,7 @@ const bulkAddRoute = async function reset() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -45,7 +45,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; } else { diff --git a/tests/unit/free-proxies-db.test.ts b/tests/unit/free-proxies-db.test.ts index 369f5fbb3435..d09ddcd34562 100644 --- a/tests/unit/free-proxies-db.test.ts +++ b/tests/unit/free-proxies-db.test.ts @@ -12,13 +12,13 @@ const freeProxiesDb = await import("../../src/lib/db/freeProxies.ts"); async function reset() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("upsertFreeProxy creates a new record", async () => { diff --git a/tests/unit/free-proxies-list-search.test.ts b/tests/unit/free-proxies-list-search.test.ts index 57dea9618b5c..e34fa0f37a07 100644 --- a/tests/unit/free-proxies-list-search.test.ts +++ b/tests/unit/free-proxies-list-search.test.ts @@ -15,13 +15,13 @@ const freeProxiesDb = await import("../../src/lib/db/freeProxies.ts"); async function reset() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function make(host: string, quality: number, latency: number): FreeProxyItem { diff --git a/tests/unit/free-proxy-auto-sync-scheduler.test.ts b/tests/unit/free-proxy-auto-sync-scheduler.test.ts index c07652be658a..8bdd01bee8b1 100644 --- a/tests/unit/free-proxy-auto-sync-scheduler.test.ts +++ b/tests/unit/free-proxy-auto-sync-scheduler.test.ts @@ -45,7 +45,7 @@ function reset() { restoreEnv(); process.env.FREE_PROXY_AUTO_SYNC_ENABLED = "false"; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -57,7 +57,7 @@ test.after(() => { scheduler.stopFreeProxyAutoSync(); scheduler._setSyncCycleRunnerForTests(null); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); restoreEnv(); }); @@ -171,7 +171,10 @@ test("cycle delegates to the shared sync-cycle runner (same path as the manual r let called = false; scheduler._setSyncCycleRunnerForTests(async () => { called = true; - return { results: { "1proxy": { fetched: 1, added: 1, updated: 0, errors: [] } }, lastSyncAt: "x" }; + return { + results: { "1proxy": { fetched: 1, added: 1, updated: 0, errors: [] } }, + lastSyncAt: "x", + }; }); await scheduler.forceFreeProxySyncCycle(); diff --git a/tests/unit/free-proxy-providers.test.ts b/tests/unit/free-proxy-providers.test.ts index 1faa53b9cf7a..34de40dea082 100644 --- a/tests/unit/free-proxy-providers.test.ts +++ b/tests/unit/free-proxy-providers.test.ts @@ -18,13 +18,13 @@ const { getProvider, getEnabledProviders, getAllProviders } = async function reset() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ── Registry ───────────────────────────────────────────────────────────────── @@ -259,7 +259,10 @@ test("IplocateProvider.sync parses the plain-text ip:port lists (.txt, not .json seenUrls.length > 0 && seenUrls.every((u) => u.endsWith(".txt")), `expected .txt URLs, got: ${seenUrls.join(", ")}` ); - assert.ok(result.fetched > 0, `expected proxies parsed from the txt list, got ${result.fetched}`); + assert.ok( + result.fetched > 0, + `expected proxies parsed from the txt list, got ${result.fetched}` + ); const items = await p.list({ limit: 50 }); assert.ok( items.some((i) => i.host === "103.173.141.10" && i.port === 8080), diff --git a/tests/unit/free-proxy-sync-cycle.test.ts b/tests/unit/free-proxy-sync-cycle.test.ts index fd24efb43239..35541ffe1fe2 100644 --- a/tests/unit/free-proxy-sync-cycle.test.ts +++ b/tests/unit/free-proxy-sync-cycle.test.ts @@ -25,7 +25,7 @@ const { runFreeProxySyncCycle } = await import("../../src/lib/freeProxyProviders function reset() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -35,7 +35,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function makeProvider( diff --git a/tests/unit/free-tier-summary-radar-overlay.test.ts b/tests/unit/free-tier-summary-radar-overlay.test.ts index 08d490c345aa..d97ab156ffca 100644 --- a/tests/unit/free-tier-summary-radar-overlay.test.ts +++ b/tests/unit/free-tier-summary-radar-overlay.test.ts @@ -113,7 +113,8 @@ function feedPayload(tier: "community" | "live") { function resetState() { core.resetDbInstance(); try { - if (fs.existsSync(TEST_DATA_DIR)) fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + if (fs.existsSync(TEST_DATA_DIR)) + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // ignore } diff --git a/tests/unit/fusion-vision-panel-3378.test.ts b/tests/unit/fusion-vision-panel-3378.test.ts index 572891d252bf..7a9059dc5979 100644 --- a/tests/unit/fusion-vision-panel-3378.test.ts +++ b/tests/unit/fusion-vision-panel-3378.test.ts @@ -23,14 +23,12 @@ process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "fusion-vision-3378-test-secret"; const { handleComboChat } = await import("../../open-sse/services/combo.ts"); -const { saveModelsDevCapabilities, clearModelsDevCapabilities } = await import( - "../../src/lib/modelsDevSync.ts" -); +const { saveModelsDevCapabilities, clearModelsDevCapabilities } = + await import("../../src/lib/modelsDevSync.ts"); const { resetAllComboMetrics } = await import("../../open-sse/services/comboMetrics.ts"); const { resetAllCircuitBreakers } = await import("../../src/shared/utils/circuitBreaker.ts"); -const { resetAll: resetAllSemaphores } = await import( - "../../open-sse/services/rateLimitSemaphore.ts" -); +const { resetAll: resetAllSemaphores } = + await import("../../open-sse/services/rateLimitSemaphore.ts"); const core = await import("../../src/lib/db/core.ts"); function createLog() { @@ -92,7 +90,7 @@ test.after(() => { resetAllSemaphores(); clearModelsDevCapabilities(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; } else { diff --git a/tests/unit/g13-combo-chatcore-golden.test.ts b/tests/unit/g13-combo-chatcore-golden.test.ts index 0cd7a4cd1893..8b484b562b7d 100644 --- a/tests/unit/g13-combo-chatcore-golden.test.ts +++ b/tests/unit/g13-combo-chatcore-golden.test.ts @@ -392,6 +392,6 @@ test("G13 golden detects a public behavior mutation", () => { } finally { if (previousUpdateGolden === undefined) delete process.env.UPDATE_GOLDEN; else process.env.UPDATE_GOLDEN = previousUpdateGolden; - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/gamification/aggregate-profile-3484.test.ts b/tests/unit/gamification/aggregate-profile-3484.test.ts index 1d1fc0e7ffc7..8af17bc0d164 100644 --- a/tests/unit/gamification/aggregate-profile-3484.test.ts +++ b/tests/unit/gamification/aggregate-profile-3484.test.ts @@ -16,7 +16,8 @@ if (!process.env.API_KEY_SECRET) { const { getDbInstance, resetDbInstance } = await import("../../../src/lib/db/core.ts"); const gami = await import("../../../src/lib/db/gamification.ts"); -const { seedBuiltinBadges, BUILTIN_BADGES } = await import("../../../src/lib/gamification/badges.ts"); +const { seedBuiltinBadges, BUILTIN_BADGES } = + await import("../../../src/lib/gamification/badges.ts"); test.after(() => { try { @@ -29,7 +30,7 @@ test.after(() => { } catch { /* ignore */ } - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#3484 getAggregateXp on an empty ledger → zero XP, level 1, no throw", () => { diff --git a/tests/unit/github-copilot-retired-models.test.ts b/tests/unit/github-copilot-retired-models.test.ts index 6ce66556c07e..fd8d2df10a41 100644 --- a/tests/unit/github-copilot-retired-models.test.ts +++ b/tests/unit/github-copilot-retired-models.test.ts @@ -17,7 +17,7 @@ before(() => { after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("GitHub Copilot sync rejects retired Gemini models", async () => { diff --git a/tests/unit/glm-provider-model-import-route.test.ts b/tests/unit/glm-provider-model-import-route.test.ts index b86c115e70b6..9f47f555c7ef 100644 --- a/tests/unit/glm-provider-model-import-route.test.ts +++ b/tests/unit/glm-provider-model-import-route.test.ts @@ -19,13 +19,13 @@ const modelsRoute = await import("../../src/app/api/providers/[id]/models/route. async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("GLM import uses international coding endpoint when apiRegion is international", async () => { diff --git a/tests/unit/gpt-max-input-tokens-6191.test.ts b/tests/unit/gpt-max-input-tokens-6191.test.ts index df2a90fd6077..3d8b8fb1512b 100644 --- a/tests/unit/gpt-max-input-tokens-6191.test.ts +++ b/tests/unit/gpt-max-input-tokens-6191.test.ts @@ -18,7 +18,7 @@ const modelCapabilities = await import("../../src/lib/modelCapabilities.ts"); function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -28,7 +28,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("codex gpt-5.5 reports max_input_tokens smaller than its context window (#6191)", () => { diff --git a/tests/unit/grok-cli-device-route.test.ts b/tests/unit/grok-cli-device-route.test.ts index fc926a946c37..1453f348d408 100644 --- a/tests/unit/grok-cli-device-route.test.ts +++ b/tests/unit/grok-cli-device-route.test.ts @@ -23,7 +23,7 @@ test.afterEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("grok-cli poll does not require a PKCE code verifier", async () => { diff --git a/tests/unit/grok-cli-provider-limits-ui.test.ts b/tests/unit/grok-cli-provider-limits-ui.test.ts index bee641c2bdaf..c3f3ca4c9da5 100644 --- a/tests/unit/grok-cli-provider-limits-ui.test.ts +++ b/tests/unit/grok-cli-provider-limits-ui.test.ts @@ -30,7 +30,7 @@ const baseBilling = { }; test.after(() => { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("Grok Build product aliases normalize to one stable row and preserve collisions", () => { diff --git a/tests/unit/grok-cli-provider-limits.test.ts b/tests/unit/grok-cli-provider-limits.test.ts index e3fb1689ff0d..081d403f5fbc 100644 --- a/tests/unit/grok-cli-provider-limits.test.ts +++ b/tests/unit/grok-cli-provider-limits.test.ts @@ -135,7 +135,7 @@ test.afterEach(() => { test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("grok-cli fetches the fixed read-only surfaces with the full Grok client profile", async () => { diff --git a/tests/unit/guardrails-api-3496.test.ts b/tests/unit/guardrails-api-3496.test.ts index 0a845effaefd..6da986b86df3 100644 --- a/tests/unit/guardrails-api-3496.test.ts +++ b/tests/unit/guardrails-api-3496.test.ts @@ -34,7 +34,7 @@ test.after(() => { } catch { /* ignore */ } - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#3496 GET /api/guardrails lists the registered guardrails with status", async () => { @@ -119,8 +119,6 @@ test("#3496 check-docs-symbols no longer freezes guardrails/shadow + API_REFEREN const src = fs.readFileSync(path.join(process.cwd(), apiRefRel), "utf8"); const docPathsByFile = [{ file: apiRefRel, paths: extractDocApiPaths(src) }]; const misses = findStaleDocApiRefs(docPathsByFile, routeFiles, KNOWN_STALE_DOC_REFS); - const ghosts = misses.filter( - (m) => m.includes("/api/guardrails") || m.includes("/api/shadow") - ); + const ghosts = misses.filter((m) => m.includes("/api/guardrails") || m.includes("/api/shadow")); assert.deepEqual(ghosts, [], `stale guardrails/shadow refs remain: ${ghosts.join("; ")}`); }); diff --git a/tests/unit/guardrails/videoBridgeFu07StructuralSampling.test.ts b/tests/unit/guardrails/videoBridgeFu07StructuralSampling.test.ts index 5c5695f01833..477c00c58cfc 100644 --- a/tests/unit/guardrails/videoBridgeFu07StructuralSampling.test.ts +++ b/tests/unit/guardrails/videoBridgeFu07StructuralSampling.test.ts @@ -432,7 +432,7 @@ test("real FFmpeg evidence distinguishes a frozen dark segment from dense motion assert.equal(decision.timestamps.filter((timestamp) => timestamp < 6).length, 1); assert.equal(decision.timestamps.filter((timestamp) => timestamp > 6).length, 3); } finally { - await rm(directory, { force: true, recursive: true }); + await rm(directory, { force: true, recursive: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -481,6 +481,6 @@ test("real FFmpeg abort stops preanalysis, skips frame extraction, and cleans th assert.notEqual(privateInputPath, ""); await assert.rejects(() => access(privateInputPath)); } finally { - await rm(directory, { force: true, recursive: true }); + await rm(directory, { force: true, recursive: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/guardrails/videoBridgeRuntime.test.ts b/tests/unit/guardrails/videoBridgeRuntime.test.ts index 01f56f381d8d..9b5402bfc44b 100644 --- a/tests/unit/guardrails/videoBridgeRuntime.test.ts +++ b/tests/unit/guardrails/videoBridgeRuntime.test.ts @@ -431,7 +431,7 @@ test("checks individual and aggregate frame byte caps before returning broker ou 6 ); } finally { - await rm(directory, { recursive: true, force: true }); + await rm(directory, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/guardrails/vision-bridge-callmodel.test.ts b/tests/unit/guardrails/vision-bridge-callmodel.test.ts index 40a0e77699a8..f3041c00c225 100644 --- a/tests/unit/guardrails/vision-bridge-callmodel.test.ts +++ b/tests/unit/guardrails/vision-bridge-callmodel.test.ts @@ -14,16 +14,12 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -const TEST_DATA_DIR = fs.mkdtempSync( - path.join(os.tmpdir(), "omniroute-vision-bridge-") -); +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-vision-bridge-")); process.env.DATA_DIR = TEST_DATA_DIR; // Prevent vision bridge from routing through a real API process.env.VISION_BRIDGE_ENABLED = "false"; -const { callVisionModel } = await import( - "../../../src/lib/guardrails/visionBridgeHelpers.ts" -); +const { callVisionModel } = await import("../../../src/lib/guardrails/visionBridgeHelpers.ts"); const { createProviderConnection } = await import("../../../src/lib/db/providers.ts"); // PR #8433 taught getFallbackModels() to exclude any candidate without a @@ -45,7 +41,7 @@ const originalFetch = globalThis.fetch; test.after(() => { globalThis.fetch = originalFetch; - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test.afterEach(() => { @@ -53,7 +49,8 @@ test.afterEach(() => { }); // Helper: build a minimal OpenAI-compat image data URI -const TINY_PNG = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="; +const TINY_PNG = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="; test("callVisionModel falls through to next model when primary fails", async () => { let fetchCallCount = 0; @@ -91,16 +88,8 @@ test("callVisionModel falls through to next model when primary fails", async () { fixedModel: "openai/gpt-4o-mini", maxFallbackAttempts: 2 } ); - assert.equal( - fetchCallCount, - 2, - "must have attempted exactly 2 models (primary + 1 fallback)" - ); - assert.equal( - result, - FALLBACK_TEXT, - "must return the fallback model's response" - ); + assert.equal(fetchCallCount, 2, "must have attempted exactly 2 models (primary + 1 fallback)"); + assert.equal(result, FALLBACK_TEXT, "must return the fallback model's response"); }); test("callVisionModel throws when ALL models fail", async () => { diff --git a/tests/unit/guardrails/vision-bridge-credentials-alias-mismatch-10702.test.ts b/tests/unit/guardrails/vision-bridge-credentials-alias-mismatch-10702.test.ts index 942de0347446..217eb8c989d7 100644 --- a/tests/unit/guardrails/vision-bridge-credentials-alias-mismatch-10702.test.ts +++ b/tests/unit/guardrails/vision-bridge-credentials-alias-mismatch-10702.test.ts @@ -10,13 +10,12 @@ process.env.DISABLE_SQLITE_AUTO_BACKUP = "true"; const core = await import("../../../src/lib/db/core.ts"); const providersDb = await import("../../../src/lib/db/providers.ts"); -const { hasUsableCredentialsForModel } = await import( - "../../../src/lib/guardrails/visionBridgeCredentials.ts" -); +const { hasUsableCredentialsForModel } = + await import("../../../src/lib/guardrails/visionBridgeCredentials.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("issue #10702: hasUsableCredentialsForModel resolves alias-prefixed model to the raw provider id (command-code / alias cmd)", async () => { diff --git a/tests/unit/guardrails/visionBridge-combo-reroute.test.ts b/tests/unit/guardrails/visionBridge-combo-reroute.test.ts index 7757d028b697..a2e4241bb34a 100644 --- a/tests/unit/guardrails/visionBridge-combo-reroute.test.ts +++ b/tests/unit/guardrails/visionBridge-combo-reroute.test.ts @@ -32,7 +32,7 @@ const mappingsDb = await import("../../../src/lib/db/modelComboMappings.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -42,7 +42,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); async function createCombo(name, models, overrides = {}) { diff --git a/tests/unit/guardrails/visionBridgeCredentials.test.ts b/tests/unit/guardrails/visionBridgeCredentials.test.ts index 255c36603cac..39e4b2474c50 100644 --- a/tests/unit/guardrails/visionBridgeCredentials.test.ts +++ b/tests/unit/guardrails/visionBridgeCredentials.test.ts @@ -40,13 +40,13 @@ const { hasUsableCredentialsForModel, hasTerminalConnectionStatus } = async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ── alias → canonical id resolution (#10702) ──────────────────────────────── diff --git a/tests/unit/guide-settings-route.test.ts b/tests/unit/guide-settings-route.test.ts index 0580a7c9c1a6..8fcb89615f15 100644 --- a/tests/unit/guide-settings-route.test.ts +++ b/tests/unit/guide-settings-route.test.ts @@ -55,7 +55,9 @@ test.beforeEach(async () => { }); test.afterEach(async () => { - await fs.rm(DUMMY_HOME, { recursive: true, force: true }).catch(() => {}); + await fs + .rm(DUMMY_HOME, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }) + .catch(() => {}); if (originalXDG === undefined) delete process.env.XDG_CONFIG_HOME; else process.env.XDG_CONFIG_HOME = originalXDG; if (originalAppData === undefined) delete process.env.APPDATA; diff --git a/tests/unit/headroom-codex-quota-snapshot-6379.test.ts b/tests/unit/headroom-codex-quota-snapshot-6379.test.ts index 6472496b158c..85ac9fa922e0 100644 --- a/tests/unit/headroom-codex-quota-snapshot-6379.test.ts +++ b/tests/unit/headroom-codex-quota-snapshot-6379.test.ts @@ -44,14 +44,12 @@ const providersDb = await import("../../src/lib/db/providers.ts"); // the DB instead of short-circuiting to []. const codexFetcher = await import("../../open-sse/services/codexQuotaFetcher.ts"); codexFetcher.registerCodexQuotaFetcher(); -const { orderTargetsByHeadroom } = await import( - "../../open-sse/services/combo/quotaStrategies.ts" -); +const { orderTargetsByHeadroom } = await import("../../open-sse/services/combo/quotaStrategies.ts"); const { _clearSaturationCache } = await import("../../src/lib/quota/saturationSignals.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); _clearSaturationCache(); } @@ -62,7 +60,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); const silentLog = { warn: () => {} }; @@ -111,8 +109,7 @@ test("orderTargetsByHeadroom (codex): ranks the account with more free quota fir globalThis.fetch = (async (_url: string, init?: RequestInit) => { const headers = init?.headers as Record | undefined; const auth = headers?.["Authorization"] ?? ""; - const body = - auth === "Bearer tok-busy" ? usageResponse(90, 10) : usageResponse(5, 5); + const body = auth === "Bearer tok-busy" ? usageResponse(90, 10) : usageResponse(5, 5); return new Response(JSON.stringify(body), { status: 200 }); }) as typeof fetch; diff --git a/tests/unit/health-ping-route.test.ts b/tests/unit/health-ping-route.test.ts index d32b990c0242..6cc09fcd539d 100644 --- a/tests/unit/health-ping-route.test.ts +++ b/tests/unit/health-ping-route.test.ts @@ -17,7 +17,7 @@ const routeModule = await import("../../src/app/api/health/ping/route.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("GET /api/health/ping returns 200 with status ok and ISO timestamp", async () => { diff --git a/tests/unit/helpers/decollidedMigrationsDir.ts b/tests/unit/helpers/decollidedMigrationsDir.ts index 00d0f42d8f40..9aa11cabc8d3 100644 --- a/tests/unit/helpers/decollidedMigrationsDir.ts +++ b/tests/unit/helpers/decollidedMigrationsDir.ts @@ -71,7 +71,7 @@ export function useDecollidedMigrationsDir(): void { process.on("exit", () => { try { - fs.rmSync(tmp, { recursive: true, force: true }); + fs.rmSync(tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // Best-effort cleanup — the OS reaps its temp dir eventually. } diff --git a/tests/unit/hermes-agent-settings-route-keyid-10711.test.ts b/tests/unit/hermes-agent-settings-route-keyid-10711.test.ts index 6651b8bdc40c..206fbde2450a 100644 --- a/tests/unit/hermes-agent-settings-route-keyid-10711.test.ts +++ b/tests/unit/hermes-agent-settings-route-keyid-10711.test.ts @@ -40,7 +40,7 @@ const route = await import("../../src/app/api/cli-tools/hermes-agent-settings/ro test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); async function authCookie(): Promise { @@ -53,7 +53,10 @@ async function authCookie(): Promise { } test("#10711: POST hermes-agent-settings resolves keyId server-side instead of writing the placeholder", async () => { - const created = await apiKeysDb.createApiKey("hermes-agent-10711-key", "hermes-agent-10711-machine"); + const created = await apiKeysDb.createApiKey( + "hermes-agent-10711-key", + "hermes-agent-10711-machine" + ); const realKey = created.key; assert.ok(realKey && realKey.length > 0, "createApiKey must return the real plaintext key"); diff --git a/tests/unit/hidden-models-leak-v1-models-11300.test.ts b/tests/unit/hidden-models-leak-v1-models-11300.test.ts index c937d2464e3e..1de38b5b6f11 100644 --- a/tests/unit/hidden-models-leak-v1-models-11300.test.ts +++ b/tests/unit/hidden-models-leak-v1-models-11300.test.ts @@ -36,7 +36,7 @@ const v1ModelsCatalog = await import("../../src/app/api/v1/models/catalog.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); v1ModelsCatalog.__resetCatalogBuilderRunsForTest(); } @@ -47,7 +47,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); async function fetchCatalogIds(): Promise { @@ -93,7 +93,7 @@ test("#11300 A: hiding a static model under its ALIAS (cc) excludes it under bot ); }); -test("#11300 B: hiding a codex-native unprefixed model under \"openai\" excludes the bare model id", async () => { +test('#11300 B: hiding a codex-native unprefixed model under "openai" excludes the bare model id', async () => { await providersDb.createProviderConnection({ provider: "codex", authType: "oauth", @@ -151,9 +151,11 @@ test("#11300 C: hiding a compatible-node synced model under its configured PREFI }); const modelId = "deepseek-v4-flash-0731"; - await modelsDb.replaceSyncedAvailableModelsForConnection(NODE_ID, (connection as { id: string }).id, [ - { id: modelId, name: "DeepSeek V4 Flash", source: "imported", supportedEndpoints: ["chat"] }, - ]); + await modelsDb.replaceSyncedAvailableModelsForConnection( + NODE_ID, + (connection as { id: string }).id, + [{ id: modelId, name: "DeepSeek V4 Flash", source: "imported", supportedEndpoints: ["chat"] }] + ); let ids = await fetchCatalogIds(); assert.ok( diff --git a/tests/unit/image-compat-node-alias-shadow.test.ts b/tests/unit/image-compat-node-alias-shadow.test.ts index 06d4d1db081e..3ccb76da4332 100644 --- a/tests/unit/image-compat-node-alias-shadow.test.ts +++ b/tests/unit/image-compat-node-alias-shadow.test.ts @@ -18,9 +18,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -const TEST_DATA_DIR = fs.mkdtempSync( - path.join(os.tmpdir(), "omniroute-image-compat-shadow-") -); +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-image-compat-shadow-")); process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); @@ -50,7 +48,7 @@ test.before(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("compatible node with prefix=cf must NOT shadow the built-in cloudflare-ai alias", async () => { diff --git a/tests/unit/image-edits-multipart-3273.test.ts b/tests/unit/image-edits-multipart-3273.test.ts index d7e3e390ddb8..b0afd24e3337 100644 --- a/tests/unit/image-edits-multipart-3273.test.ts +++ b/tests/unit/image-edits-multipart-3273.test.ts @@ -40,7 +40,10 @@ test("#3273 /v1/images/edits forwards model as real multipart (undici-patched fe await handleOpenAIImageEdit({ model: "gpt-image-2", provider: "customopenai", - credentials: { apiKey: "sk-test", providerSpecificData: { baseUrl: `http://127.0.0.1:${port}` } }, + credentials: { + apiKey: "sk-test", + providerSpecificData: { baseUrl: `http://127.0.0.1:${port}` }, + }, prompt: "make it blue", imageBytes: Buffer.from([0x89, 0x50, 0x4e, 0x47]), imageMime: "image/png", @@ -68,7 +71,12 @@ test.after(() => { /* ignore */ } try { - fs.rmSync(process.env.DATA_DIR as string, { recursive: true, force: true }); + fs.rmSync(process.env.DATA_DIR as string, { + recursive: true, + force: true, + maxRetries: 5, + retryDelay: 100, + }); } catch { /* ignore */ } diff --git a/tests/unit/image-generation-route-auth.test.ts b/tests/unit/image-generation-route-auth.test.ts index e6f25631a71a..03a29128327d 100644 --- a/tests/unit/image-generation-route-auth.test.ts +++ b/tests/unit/image-generation-route-auth.test.ts @@ -30,7 +30,7 @@ async function resetStorage() { globalThis.fetch = originalFetch; apiKeysDb.resetApiKeyState(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); v1ModelsCatalog.__resetCatalogBuilderRunsForTest(); } @@ -71,7 +71,7 @@ test.after(() => { globalThis.fetch = originalFetch; apiKeysDb.resetApiKeyState(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("v1 image generation POST requires an API key when REQUIRE_API_KEY is enabled", async () => { diff --git a/tests/unit/image-generation-route.test.ts b/tests/unit/image-generation-route.test.ts index f08014bfee2d..a9526585d580 100644 --- a/tests/unit/image-generation-route.test.ts +++ b/tests/unit/image-generation-route.test.ts @@ -74,7 +74,7 @@ async function resetStorage() { globalThis.fetch = originalFetch; apiKeysDb.resetApiKeyState(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); // #6303 moved this route onto the shared unified catalog (getUnifiedModelsResponse), // which #6408 wrapped in a 1.5s TTL response cache keyed only by (prefix, isCodex @@ -122,7 +122,7 @@ test.after(() => { globalThis.fetch = originalFetch; apiKeysDb.resetApiKeyState(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("image routes expose CORS preflight handlers", async () => { diff --git a/tests/unit/image-model-not-in-chat-catalog-6457.test.ts b/tests/unit/image-model-not-in-chat-catalog-6457.test.ts index 84187fe2f726..0e02a4e6154c 100644 --- a/tests/unit/image-model-not-in-chat-catalog-6457.test.ts +++ b/tests/unit/image-model-not-in-chat-catalog-6457.test.ts @@ -33,7 +33,7 @@ const v1ModelsCatalog = await import("../../src/app/api/v1/models/catalog.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -44,7 +44,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); async function seedProviderConnection(provider: string) { diff --git a/tests/unit/image-routes-combo-edits-3214-3215.test.ts b/tests/unit/image-routes-combo-edits-3214-3215.test.ts index 3976716d4e0d..c9559b09818b 100644 --- a/tests/unit/image-routes-combo-edits-3214-3215.test.ts +++ b/tests/unit/image-routes-combo-edits-3214-3215.test.ts @@ -35,7 +35,7 @@ const { createCombo } = await import("../../src/lib/db/combos.ts"); test.after(() => { resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; }); diff --git a/tests/unit/inspector-agent-bridge-hook.test.ts b/tests/unit/inspector-agent-bridge-hook.test.ts index a1a0c75b69f2..6249f0600073 100644 --- a/tests/unit/inspector-agent-bridge-hook.test.ts +++ b/tests/unit/inspector-agent-bridge-hook.test.ts @@ -18,16 +18,13 @@ const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-ab-hook-" process.env.DATA_DIR = TEST_DATA_DIR; const { resetDbInstance, getDbInstance } = await import("../../src/lib/db/core.ts"); -const { addCustomHost, toggleCustomHost } = await import( - "../../src/lib/db/inspectorCustomHosts.ts" -); -const { recordRequestStart } = await import( - "../../src/mitm/inspector/agentBridgeHook.ts" -); +const { addCustomHost, toggleCustomHost } = + await import("../../src/lib/db/inspectorCustomHosts.ts"); +const { recordRequestStart } = await import("../../src/mitm/inspector/agentBridgeHook.ts"); async function resetStorage() { resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); getDbInstance(); } @@ -46,7 +43,7 @@ test.beforeEach(async () => { test.after(() => { resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("recordRequestStart: custom-host entry → source=custom-host, agent=undefined", async () => { diff --git a/tests/unit/instrumentation-warm-catalog-cache.test.ts b/tests/unit/instrumentation-warm-catalog-cache.test.ts index 84fa161e787b..56861834def0 100644 --- a/tests/unit/instrumentation-warm-catalog-cache.test.ts +++ b/tests/unit/instrumentation-warm-catalog-cache.test.ts @@ -47,7 +47,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error) { @@ -64,7 +64,7 @@ async function resetStorage() { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); const REAL_FETCH = globalThis.fetch; diff --git a/tests/unit/intercept-fetch-resolver.test.ts b/tests/unit/intercept-fetch-resolver.test.ts index ff109d89e26e..4b2d50e0518c 100644 --- a/tests/unit/intercept-fetch-resolver.test.ts +++ b/tests/unit/intercept-fetch-resolver.test.ts @@ -9,16 +9,15 @@ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-test-intercept-f process.env.DATA_DIR = tmpDir; const core = await import("../../src/lib/db/core.ts"); -const { setInterceptionRules, resolveInterceptFetch } = await import( - "../../src/lib/db/interceptionRules.ts" -); +const { setInterceptionRules, resolveInterceptFetch } = + await import("../../src/lib/db/interceptionRules.ts"); // #7339 — resolveInterceptFetch, a structural twin of resolveInterceptSearch // (tests/unit/interception-rules.test.ts), covering Phase 3 of #3384. describe("db/interceptionRules — resolveInterceptFetch precedence (#7339)", () => { function resetDb() { core.resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(tmpDir, { recursive: true }); } @@ -28,7 +27,7 @@ describe("db/interceptionRules — resolveInterceptFetch precedence (#7339)", () after(() => { core.resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); it("returns undefined when no provider/model rule exists", () => { diff --git a/tests/unit/interception-rules.test.ts b/tests/unit/interception-rules.test.ts index bad1e0a28137..1653ebbe1813 100644 --- a/tests/unit/interception-rules.test.ts +++ b/tests/unit/interception-rules.test.ts @@ -20,7 +20,7 @@ const { describe("db/interceptionRules — per-model interception rules (#3384)", () => { function resetDb() { core.resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(tmpDir, { recursive: true }); } @@ -30,7 +30,7 @@ describe("db/interceptionRules — per-model interception rules (#3384)", () => after(() => { core.resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); it("returns null for an unconfigured provider", () => { diff --git a/tests/unit/internal-service-auth.test.ts b/tests/unit/internal-service-auth.test.ts index 3ff053ab010a..3bc5497c5be6 100644 --- a/tests/unit/internal-service-auth.test.ts +++ b/tests/unit/internal-service-auth.test.ts @@ -61,6 +61,6 @@ test("internal service token file is read without exposing it to process env", ( [INTERNAL_SERVICE_AUTH_HEADER]: "file-backed-token-0123456789", }); } finally { - fs.rmSync(directory, { recursive: true, force: true }); + fs.rmSync(directory, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/ip-filter-persistence-6131.test.ts b/tests/unit/ip-filter-persistence-6131.test.ts index 279193627ed2..b7f195f64802 100644 --- a/tests/unit/ip-filter-persistence-6131.test.ts +++ b/tests/unit/ip-filter-persistence-6131.test.ts @@ -17,13 +17,13 @@ const ipFilter = await import("../../open-sse/services/ipFilter.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test.beforeEach(() => { // Fresh DB per test + fresh in-memory module state. core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); ipFilter.resetIPFilter(); }); diff --git a/tests/unit/ip-filter.test.ts b/tests/unit/ip-filter.test.ts index 4559bc6812e8..77bad1ea6420 100644 --- a/tests/unit/ip-filter.test.ts +++ b/tests/unit/ip-filter.test.ts @@ -27,12 +27,12 @@ const { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test.beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); resetIPFilter(); }); diff --git a/tests/unit/issue-6343-v0-web-alias-collision.test.ts b/tests/unit/issue-6343-v0-web-alias-collision.test.ts index 15e672e3b291..75116efb3dfb 100644 --- a/tests/unit/issue-6343-v0-web-alias-collision.test.ts +++ b/tests/unit/issue-6343-v0-web-alias-collision.test.ts @@ -26,7 +26,7 @@ describe("#6343: v0-vercel-web credential detection (alias collision)", () => { } catch { // best-effort cleanup } - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); it("v0-vercel and v0-vercel-web no longer share an alias", async () => { diff --git a/tests/unit/issue-6686-quota-preflight-coverage.test.ts b/tests/unit/issue-6686-quota-preflight-coverage.test.ts index 488ee55f58af..0b8d147d3c31 100644 --- a/tests/unit/issue-6686-quota-preflight-coverage.test.ts +++ b/tests/unit/issue-6686-quota-preflight-coverage.test.ts @@ -86,7 +86,7 @@ test("#6686: getProviderCredentialsWithQuotaPreflight (now used by every credent core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); try { @@ -135,6 +135,6 @@ test("#6686: getProviderCredentialsWithQuotaPreflight (now used by every credent } finally { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/issue-agent-route-execution.test.ts b/tests/unit/issue-agent-route-execution.test.ts index 9f0c7e0b8674..1d90b6d9ab40 100644 --- a/tests/unit/issue-agent-route-execution.test.ts +++ b/tests/unit/issue-agent-route-execution.test.ts @@ -17,7 +17,7 @@ const originalFetch = globalThis.fetch; async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } diff --git a/tests/unit/json-migration-combos.test.ts b/tests/unit/json-migration-combos.test.ts index 978f9109aeab..4ccdacb34670 100644 --- a/tests/unit/json-migration-combos.test.ts +++ b/tests/unit/json-migration-combos.test.ts @@ -23,7 +23,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; }); @@ -144,7 +144,6 @@ test("runJsonMigration normalizes legacy combo strategy names at the import boun assert.equal(byId.get("combo-unknown").strategy, "priority"); }); - test("runJsonMigration rejects invalid combo invariants atomically", () => { const db = core.getDbInstance(); diff --git a/tests/unit/key-health-402-disable-5239.test.ts b/tests/unit/key-health-402-disable-5239.test.ts index 5b3a5286e156..40fa7e0240e8 100644 --- a/tests/unit/key-health-402-disable-5239.test.ts +++ b/tests/unit/key-health-402-disable-5239.test.ts @@ -22,16 +22,13 @@ const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-5239-")); process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); -const { recordKeyHealthStatus } = await import( - "../../open-sse/handlers/chatCore/keyHealth.ts" -); -const { getValidApiKey, getAllKeyHealth, resetKeyStatus } = await import( - "../../open-sse/services/apiKeyRotator.ts" -); +const { recordKeyHealthStatus } = await import("../../open-sse/handlers/chatCore/keyHealth.ts"); +const { getValidApiKey, getAllKeyHealth, resetKeyStatus } = + await import("../../open-sse/services/apiKeyRotator.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // Two keys live on ONE connection as API Key Round-Robin (extraApiKeys[]). diff --git a/tests/unit/kimi-coding-billing.test.ts b/tests/unit/kimi-coding-billing.test.ts index 026922887869..6720b1436dd0 100644 --- a/tests/unit/kimi-coding-billing.test.ts +++ b/tests/unit/kimi-coding-billing.test.ts @@ -83,7 +83,7 @@ test.afterEach(() => { test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("kimi-coding exposes the official boosterWallet Extra Usage contract", async () => { diff --git a/tests/unit/kimi-quota-reset-recovery.test.ts b/tests/unit/kimi-quota-reset-recovery.test.ts index 8771cba1870d..5129037c2a20 100644 --- a/tests/unit/kimi-quota-reset-recovery.test.ts +++ b/tests/unit/kimi-quota-reset-recovery.test.ts @@ -17,7 +17,7 @@ const quotaCache = await import("../../src/domain/quotaCache.ts"); test.after(() => { quotaCache.__clearForTests(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("Kimi billing-cycle quota errors remain active and recover at the cached reset", async () => { diff --git a/tests/unit/kimi-web-models-discovery.test.ts b/tests/unit/kimi-web-models-discovery.test.ts index b565698418bb..dd0ae0cdc961 100644 --- a/tests/unit/kimi-web-models-discovery.test.ts +++ b/tests/unit/kimi-web-models-discovery.test.ts @@ -13,13 +13,13 @@ const modelsRoute = await import("../../src/app/api/providers/[id]/models/route. async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("kimi-web uses the curated registry catalog without remote discovery", async () => { diff --git a/tests/unit/kiro-auto-import-idc-2059.test.ts b/tests/unit/kiro-auto-import-idc-2059.test.ts index 331637ffb531..e215036ea5fe 100644 --- a/tests/unit/kiro-auto-import-idc-2059.test.ts +++ b/tests/unit/kiro-auto-import-idc-2059.test.ts @@ -57,7 +57,7 @@ let tmpHome: string; test.beforeEach(() => { tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-kiro-idc-2059-")); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); process.env.HOME = tmpHome; delete process.env.APPDATA; @@ -73,12 +73,12 @@ test.afterEach(() => { delete process.env.APPDATA; } globalThis.fetch = ORIGINAL_FETCH; - if (tmpHome) fs.rmSync(tmpHome, { recursive: true, force: true }); + if (tmpHome) fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ── Helpers ────────────────────────────────────────────────────────────────── diff --git a/tests/unit/kiro-auto-import-name-dedup-3615.test.ts b/tests/unit/kiro-auto-import-name-dedup-3615.test.ts index 748312a31615..d8dac813474d 100644 --- a/tests/unit/kiro-auto-import-name-dedup-3615.test.ts +++ b/tests/unit/kiro-auto-import-name-dedup-3615.test.ts @@ -24,7 +24,7 @@ process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "test-api-key-secret- test.after(() => { try { - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // best effort } diff --git a/tests/unit/kiro-builder-id-import-3333.test.ts b/tests/unit/kiro-builder-id-import-3333.test.ts index f9ba09772f84..106d460b83dc 100644 --- a/tests/unit/kiro-builder-id-import-3333.test.ts +++ b/tests/unit/kiro-builder-id-import-3333.test.ts @@ -35,7 +35,7 @@ test.beforeEach(() => { test.afterEach(() => { process.env.HOME = ORIGINAL_HOME; globalThis.fetch = ORIGINAL_FETCH; - if (tmpHome) fs.rmSync(tmpHome, { recursive: true, force: true }); + if (tmpHome) fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("validateImportToken uses cached Builder ID client creds + OIDC refresh path", async () => { diff --git a/tests/unit/kiro-import-error-3589.test.ts b/tests/unit/kiro-import-error-3589.test.ts index 4c789cae325d..948b92c6afab 100644 --- a/tests/unit/kiro-import-error-3589.test.ts +++ b/tests/unit/kiro-import-error-3589.test.ts @@ -23,7 +23,7 @@ const { buildKiroImportError } = await import("../../src/app/api/oauth/kiro/impo test.after(() => { try { - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // best effort } diff --git a/tests/unit/kiro-second-oauth-connection-10815.test.ts b/tests/unit/kiro-second-oauth-connection-10815.test.ts index 4b2640d99b4d..8128e9fa395c 100644 --- a/tests/unit/kiro-second-oauth-connection-10815.test.ts +++ b/tests/unit/kiro-second-oauth-connection-10815.test.ts @@ -12,7 +12,7 @@ const providersDb = await import("../../src/lib/db/providers.ts"); test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("createProviderConnection keeps two Kiro oauth connections with the same email but different profileArn separate (#10815)", async () => { diff --git a/tests/unit/kiro-sso-cache-direct-clientid-1253.test.ts b/tests/unit/kiro-sso-cache-direct-clientid-1253.test.ts index 0afef3661481..0f561417f600 100644 --- a/tests/unit/kiro-sso-cache-direct-clientid-1253.test.ts +++ b/tests/unit/kiro-sso-cache-direct-clientid-1253.test.ts @@ -53,7 +53,7 @@ let tmpHome: string; test.beforeEach(() => { tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-kiro-1253-")); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); process.env.HOME = tmpHome; delete process.env.APPDATA; @@ -68,12 +68,12 @@ test.afterEach(() => { delete process.env.APPDATA; } globalThis.fetch = ORIGINAL_FETCH; - if (tmpHome) fs.rmSync(tmpHome, { recursive: true, force: true }); + if (tmpHome) fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function cacheDirFor(home: string) { @@ -142,7 +142,11 @@ test("auto-import: resolves clientId/clientSecret from a direct `clientId` field assert.equal(parsed.clientId, "correct-client-id"); assert.equal(parsed.clientSecret, "correct-secret"); return new Response( - JSON.stringify({ accessToken: "access-refreshed", refreshToken: "aorAAAAAGrefreshed", expiresIn: 3600 }), + JSON.stringify({ + accessToken: "access-refreshed", + refreshToken: "aorAAAAAGrefreshed", + expiresIn: 3600, + }), { status: 200, headers: { "Content-Type": "application/json" } } ); } @@ -185,7 +189,11 @@ test("KiroService.validateImportToken: prefers the client registration matching fetchedBodies.push(parsed); if (parsed.clientId === "correct-client-id" && parsed.clientSecret === "correct-secret") { return new Response( - JSON.stringify({ accessToken: "ok-access", refreshToken: "aorAAAAAGok", expiresIn: 3600 }), + JSON.stringify({ + accessToken: "ok-access", + refreshToken: "aorAAAAAGok", + expiresIn: 3600, + }), { status: 200, headers: { "Content-Type": "application/json" } } ); } diff --git a/tests/unit/kiro-windows-auto-import-3363.test.ts b/tests/unit/kiro-windows-auto-import-3363.test.ts index cc1ebd4d8a88..6bd0d90a2e47 100644 --- a/tests/unit/kiro-windows-auto-import-3363.test.ts +++ b/tests/unit/kiro-windows-auto-import-3363.test.ts @@ -44,7 +44,7 @@ test.beforeEach(() => { tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-kiro-3363-")); // Reset DB instance so each test gets a clean settings DB (no requireLogin). core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); // Override HOME so homedir() returns a temp dir where no kiro-cli DB exists. process.env.HOME = tmpHome; @@ -69,12 +69,12 @@ test.afterEach(() => { delete process.env.APPDATA; } globalThis.fetch = ORIGINAL_FETCH; - if (tmpHome) fs.rmSync(tmpHome, { recursive: true, force: true }); + if (tmpHome) fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // Helper to call the GET handler and parse the JSON body. @@ -112,9 +112,7 @@ test("triedPaths does NOT include any Windows path when process.env.APPDATA is n const paths = body.triedPaths as string[]; // No path should reference "kiro/storage.db" (the Windows IDE storage path). - const hasWindowsPath = paths.some( - (p) => p.includes("storage.db") && p.includes("kiro") - ); + const hasWindowsPath = paths.some((p) => p.includes("storage.db") && p.includes("kiro")); assert.equal( hasWindowsPath, false, @@ -161,10 +159,7 @@ test("GET extracts refresh_token from a Windows storage.db with ItemTable schema expires_at: new Date(Date.now() + 3600 * 1000).toISOString(), region: "us-east-1", }); - db.prepare("INSERT INTO ItemTable (key, value) VALUES (?, ?)").run( - "kiro:auth:token", - tokenValue - ); + db.prepare("INSERT INTO ItemTable (key, value) VALUES (?, ?)").run("kiro:auth:token", tokenValue); db.close(); // Point APPDATA at tmpHome so tryKiroCliSqlite() resolves @@ -198,11 +193,7 @@ test("GET extracts refresh_token from a Windows storage.db with ItemTable schema const { status, body } = await callGet(); - assert.equal( - status, - 200, - `expected HTTP 200, got ${status}: ${JSON.stringify(body)}` - ); + assert.equal(status, 200, `expected HTTP 200, got ${status}: ${JSON.stringify(body)}`); assert.equal( body.found, true, diff --git a/tests/unit/latency-stats-ttft-6875.test.ts b/tests/unit/latency-stats-ttft-6875.test.ts index 7a05d029ef1a..332dea2ed3ae 100644 --- a/tests/unit/latency-stats-ttft-6875.test.ts +++ b/tests/unit/latency-stats-ttft-6875.test.ts @@ -20,7 +20,7 @@ const clearPendingRequests = usageHistory.clearPendingRequests; async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); clearPendingRequests(); } @@ -32,7 +32,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("getModelLatencyStats aggregates avgTtftMs/avgE2ELatencyMs/avgTokensPerSecond over successful rows", async () => { diff --git a/tests/unit/least-used-rotation-10945.test.ts b/tests/unit/least-used-rotation-10945.test.ts index 6218697ce1b2..38730dec5558 100644 --- a/tests/unit/least-used-rotation-10945.test.ts +++ b/tests/unit/least-used-rotation-10945.test.ts @@ -27,13 +27,13 @@ const auth = await import("../../src/sse/services/auth.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); /** Three active apikey connections, distinct priorities, all last_used_at NULL. */ diff --git a/tests/unit/lib/consoleInterceptor-epipe.test.ts b/tests/unit/lib/consoleInterceptor-epipe.test.ts index 4a968e3bb3f5..664426afbbf5 100644 --- a/tests/unit/lib/consoleInterceptor-epipe.test.ts +++ b/tests/unit/lib/consoleInterceptor-epipe.test.ts @@ -155,7 +155,7 @@ test("a non-EPIPE stream error is still fatal: it must be re-raised (#8181)", as env: { ...process.env, DISABLE_SQLITE_AUTO_BACKUP: "true" }, }); - rmSync(childDir, { recursive: true, force: true }); + rmSync(childDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); assert.notEqual( result.status, @@ -197,7 +197,7 @@ test("the stdio guard is installed even when file logging is disabled (#8181)", env: { ...process.env, DISABLE_SQLITE_AUTO_BACKUP: "true", APP_LOG_TO_FILE: "false" }, }); - rmSync(childDir, { recursive: true, force: true }); + rmSync(childDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); assert.equal( result.status, @@ -259,5 +259,6 @@ test.after(() => { if (prevLogFilePath === undefined) delete process.env.APP_LOG_FILE_PATH; else process.env.APP_LOG_FILE_PATH = prevLogFilePath; - if (existsSync(dir)) rmSync(dir, { recursive: true, force: true }); + if (existsSync(dir)) + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); diff --git a/tests/unit/lib/consoleInterceptor-writes.test.ts b/tests/unit/lib/consoleInterceptor-writes.test.ts index 8a16c244d723..2cba3d559101 100644 --- a/tests/unit/lib/consoleInterceptor-writes.test.ts +++ b/tests/unit/lib/consoleInterceptor-writes.test.ts @@ -56,7 +56,7 @@ function runChild(body: string[]): ChildResult { .map((l) => JSON.parse(l) as Record) : []; - rmSync(dir, { recursive: true, force: true }); + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); return { status: result.status, stderr: String(result.stderr), lines }; } @@ -132,7 +132,7 @@ test("a log directory removed at runtime is recreated and logging recovers (#818 `const { dirname } = await import("node:path");`, `M.initConsoleInterceptor();`, `console.error("before removal");`, - `rmSync(dirname(LOG_FILE), { recursive: true, force: true });`, + `rmSync(dirname(LOG_FILE), { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });`, `if (existsSync(LOG_FILE)) { process.exit(3); }`, `console.error("after removal");`, `setTimeout(() => process.exit(0), 200);`, @@ -154,7 +154,7 @@ test("the log-unavailable notice is emitted at most once, to the real stderr", ( `M.initConsoleInterceptor();`, // Make the directory unrecreatable so the retry fails and the notice path is exercised. `const parent = dirname(dirname(LOG_FILE));`, - `rmSync(dirname(LOG_FILE), { recursive: true, force: true });`, + `rmSync(dirname(LOG_FILE), { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });`, `chmodSync(parent, 0o500);`, `for (let i = 0; i < 5; i++) console.error("unwritable " + i);`, `chmodSync(parent, 0o700);`, diff --git a/tests/unit/lib/jobRegistry/registry.test.ts b/tests/unit/lib/jobRegistry/registry.test.ts index 64ce9d205067..9be264d5b640 100644 --- a/tests/unit/lib/jobRegistry/registry.test.ts +++ b/tests/unit/lib/jobRegistry/registry.test.ts @@ -56,7 +56,7 @@ function resetAll() { } __resetJobRegistry(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -67,7 +67,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("register + start (interval) fires handler immediately", async () => { diff --git a/tests/unit/lib/jobs/backupScheduleJob.test.ts b/tests/unit/lib/jobs/backupScheduleJob.test.ts index b2d2266181b4..e581057b99a5 100644 --- a/tests/unit/lib/jobs/backupScheduleJob.test.ts +++ b/tests/unit/lib/jobs/backupScheduleJob.test.ts @@ -13,7 +13,7 @@ async function withTmpDataDir(fn: (dataDir: string) => Promise) { } finally { if (orig === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = orig; - rmSync(dataDir, { recursive: true, force: true }); + rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } diff --git a/tests/unit/lib/managementCliToken.test.ts b/tests/unit/lib/managementCliToken.test.ts index 638c4c3fd7fb..d2a87a526249 100644 --- a/tests/unit/lib/managementCliToken.test.ts +++ b/tests/unit/lib/managementCliToken.test.ts @@ -28,7 +28,7 @@ const { CLI_TOKEN_HEADER } = await import("../../../src/server/authz/headers.ts" test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (originalDataDir === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = originalDataDir; }); diff --git a/tests/unit/lib/quota-reset-events.test.ts b/tests/unit/lib/quota-reset-events.test.ts index 155d515b50e2..ee1ddb888aae 100644 --- a/tests/unit/lib/quota-reset-events.test.ts +++ b/tests/unit/lib/quota-reset-events.test.ts @@ -25,7 +25,7 @@ const OBSERVED = "2026-01-15T00:05:00.000Z"; test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("records a weekly window transition and getWindowStart returns the prior window start", () => { diff --git a/tests/unit/lib/warmupScheduler/circuitBreakerFactory.test.ts b/tests/unit/lib/warmupScheduler/circuitBreakerFactory.test.ts index c7808fd572fd..81c71ea387db 100644 --- a/tests/unit/lib/warmupScheduler/circuitBreakerFactory.test.ts +++ b/tests/unit/lib/warmupScheduler/circuitBreakerFactory.test.ts @@ -21,7 +21,7 @@ const core = await import("../../../../src/lib/db/core.ts"); async function resetDb() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -32,7 +32,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("REDIS_URL unset → SqliteCircuitBreakerStore", async () => { diff --git a/tests/unit/lib/warmupScheduler/circuitBreakerFactoryConcurrency.test.ts b/tests/unit/lib/warmupScheduler/circuitBreakerFactoryConcurrency.test.ts index 0ab9c8b80866..faca11da13df 100644 --- a/tests/unit/lib/warmupScheduler/circuitBreakerFactoryConcurrency.test.ts +++ b/tests/unit/lib/warmupScheduler/circuitBreakerFactoryConcurrency.test.ts @@ -24,7 +24,7 @@ const core = await import("../../../../src/lib/db/core.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); /** diff --git a/tests/unit/lib/warmupScheduler/circuitBreakerFactoryRelease.test.ts b/tests/unit/lib/warmupScheduler/circuitBreakerFactoryRelease.test.ts index 97ec79116b52..3ff651f4f4fe 100644 --- a/tests/unit/lib/warmupScheduler/circuitBreakerFactoryRelease.test.ts +++ b/tests/unit/lib/warmupScheduler/circuitBreakerFactoryRelease.test.ts @@ -24,7 +24,7 @@ const core = await import("../../../../src/lib/db/core.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); /** diff --git a/tests/unit/lib/warmupScheduler/sqliteCircuitBreakerStore.test.ts b/tests/unit/lib/warmupScheduler/sqliteCircuitBreakerStore.test.ts index da4dae94b5ba..161a02393a0c 100644 --- a/tests/unit/lib/warmupScheduler/sqliteCircuitBreakerStore.test.ts +++ b/tests/unit/lib/warmupScheduler/sqliteCircuitBreakerStore.test.ts @@ -23,7 +23,7 @@ const store = new SqliteCircuitBreakerStore(); async function resetDb() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -46,7 +46,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("recordResult(success): clears streak and records tokens", async () => { diff --git a/tests/unit/limiter-lifecycle.test.ts b/tests/unit/limiter-lifecycle.test.ts index c70981d90cda..bc4f61271539 100644 --- a/tests/unit/limiter-lifecycle.test.ts +++ b/tests/unit/limiter-lifecycle.test.ts @@ -50,7 +50,7 @@ await flushBackgroundWork(); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -67,7 +67,7 @@ test.after(async () => { await rateLimitManager.__resetRateLimitManagerForTests(); await flushBackgroundWork(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); /** diff --git a/tests/unit/live-model-catalog-reconciliation-8926.test.ts b/tests/unit/live-model-catalog-reconciliation-8926.test.ts index 2796071e43a3..833c2d855136 100644 --- a/tests/unit/live-model-catalog-reconciliation-8926.test.ts +++ b/tests/unit/live-model-catalog-reconciliation-8926.test.ts @@ -58,7 +58,7 @@ function seedActiveLiveCatalog() { test.beforeEach(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); assert.ok( @@ -71,7 +71,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#8926: bare inference excludes a stale static model absent from the active live catalog", async () => { diff --git a/tests/unit/live-ws-public-url.test.ts b/tests/unit/live-ws-public-url.test.ts index deff8cb92693..16fd8f7bb6b8 100644 --- a/tests/unit/live-ws-public-url.test.ts +++ b/tests/unit/live-ws-public-url.test.ts @@ -20,7 +20,7 @@ const wsRoute = await import("../../src/app/api/v1/ws/route.ts"); function resetStorage() { apiKeysDb.resetApiKeyState(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -37,7 +37,7 @@ test.beforeEach(async () => { test.after(() => { apiKeysDb.resetApiKeyState(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; diff --git a/tests/unit/llamacpp-model-delete.test.ts b/tests/unit/llamacpp-model-delete.test.ts index 9379cfee8f25..29c8180fc30c 100644 --- a/tests/unit/llamacpp-model-delete.test.ts +++ b/tests/unit/llamacpp-model-delete.test.ts @@ -12,7 +12,7 @@ const modelsDb = await import("../../src/lib/db/models.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -22,7 +22,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("removeSyncedAvailableModel deletes a single model from syncedAvailableModels", async () => { diff --git a/tests/unit/llm7-byteplus-models-fetch-3976.test.ts b/tests/unit/llm7-byteplus-models-fetch-3976.test.ts index 022319179964..d154ff066d3a 100644 --- a/tests/unit/llm7-byteplus-models-fetch-3976.test.ts +++ b/tests/unit/llm7-byteplus-models-fetch-3976.test.ts @@ -27,13 +27,13 @@ const modelsRoute = await import("../../src/app/api/providers/[id]/models/route. async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); interface ModelsBody { diff --git a/tests/unit/lmstudio-connection-baseurl-11233.test.ts b/tests/unit/lmstudio-connection-baseurl-11233.test.ts index 78af8690e4fd..1d8ad7afdd0a 100644 --- a/tests/unit/lmstudio-connection-baseurl-11233.test.ts +++ b/tests/unit/lmstudio-connection-baseurl-11233.test.ts @@ -15,7 +15,7 @@ const { createEmbeddingResponse } = await import("../../src/lib/embeddings/servi test.after(() => { core.resetDbInstance(); - rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // Issue #11233: the dashboard stores LM Studio connections under the provider diff --git a/tests/unit/local-corpus-index.test.ts b/tests/unit/local-corpus-index.test.ts index fc26e9802bac..12851efeb508 100644 --- a/tests/unit/local-corpus-index.test.ts +++ b/tests/unit/local-corpus-index.test.ts @@ -21,7 +21,7 @@ async function withCorpus( try { await run(root, index); } finally { - await fs.rm(root, { recursive: true, force: true }); + await fs.rm(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } diff --git a/tests/unit/local-corpus-lru-cache.test.ts b/tests/unit/local-corpus-lru-cache.test.ts index 1fc959835834..68d0c4f26a0d 100644 --- a/tests/unit/local-corpus-lru-cache.test.ts +++ b/tests/unit/local-corpus-lru-cache.test.ts @@ -25,13 +25,10 @@ test("dynamic root path traversal outside bounding box throws error", async () = const outsideFolder = fs.mkdtempSync(path.join(os.tmpdir(), "omni-corpus-outside-")); - assert.throws( - () => getConfiguredLocalCorpusStatus(outsideFolder), - /Path traversal forbidden/ - ); + assert.throws(() => getConfiguredLocalCorpusStatus(outsideFolder), /Path traversal forbidden/); - fs.rmSync(tmpBase, { recursive: true, force: true }); - fs.rmSync(outsideFolder, { recursive: true, force: true }); + fs.rmSync(tmpBase, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.rmSync(outsideFolder, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("path traversal check rejects sibling directory with matching string prefix", async () => { @@ -41,13 +38,10 @@ test("path traversal check rejects sibling directory with matching string prefix setLocalCorpusRoot(tmpBase); - assert.throws( - () => getConfiguredLocalCorpusStatus(siblingFolder), - /Path traversal forbidden/ - ); + assert.throws(() => getConfiguredLocalCorpusStatus(siblingFolder), /Path traversal forbidden/); - fs.rmSync(tmpBase, { recursive: true, force: true }); - fs.rmSync(siblingFolder, { recursive: true, force: true }); + fs.rmSync(tmpBase, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.rmSync(siblingFolder, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("search and read configured local corpus support dynamic root within bounds", async () => { @@ -70,7 +64,7 @@ test("search and read configured local corpus support dynamic root within bounds }); assert.ok(readResult.content.includes("searchable")); - fs.rmSync(tmpBase, { recursive: true, force: true }); + fs.rmSync(tmpBase, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("LRU cache respects access order and OMNIROUTE_CORPUS_CACHE_SIZE", async () => { @@ -101,5 +95,5 @@ test("LRU cache respects access order and OMNIROUTE_CORPUS_CACHE_SIZE", async () assert.equal(idx1.indexedBytes, idx1Again.indexedBytes); delete process.env.OMNIROUTE_CORPUS_CACHE_SIZE; - fs.rmSync(tmpRoot, { recursive: true, force: true }); + fs.rmSync(tmpRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); diff --git a/tests/unit/local-rerank-logging.test.ts b/tests/unit/local-rerank-logging.test.ts index 3c6ec6279458..820a1f7694ee 100644 --- a/tests/unit/local-rerank-logging.test.ts +++ b/tests/unit/local-rerank-logging.test.ts @@ -35,7 +35,7 @@ test.describe("Local rerank provider logging and fallback", () => { globalThis.fetch = originalFetch; core.resetDbInstance(); try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // ignore } diff --git a/tests/unit/log-export-routes.test.mjs b/tests/unit/log-export-routes.test.mjs index 3ff34a36acc0..12bdecbcf391 100644 --- a/tests/unit/log-export-routes.test.mjs +++ b/tests/unit/log-export-routes.test.mjs @@ -17,7 +17,7 @@ const exportAllRoute = await import("../../src/app/api/db-backups/exportAll/rout async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); await settingsDb.updateSettings({ requireLogin: false }); } @@ -28,7 +28,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("GET /api/logs/export returns explicit detailed payloads from artifact storage", async () => { diff --git a/tests/unit/log-retention.test.ts b/tests/unit/log-retention.test.ts index f9d83d4c8da0..57a2617f5866 100644 --- a/tests/unit/log-retention.test.ts +++ b/tests/unit/log-retention.test.ts @@ -16,7 +16,7 @@ const compliance = await import("../../src/lib/compliance/index.ts"); function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -158,7 +158,11 @@ test("cleanupExpiredLogs honors the dashboard usageHistory retention when env is // 30 days < the configured 90-day dashboard retention → must be kept. // With the old env-default (7d) behavior this row would be deleted. - assert.equal(result.deletedUsage, 0, "30-day usage_history must survive a 90-day dashboard retention"); + assert.equal( + result.deletedUsage, + 0, + "30-day usage_history must survive a 90-day dashboard retention" + ); assert.equal((db.prepare("SELECT COUNT(*) AS cnt FROM usage_history").get() as any).cnt, 1); } finally { if (savedCall !== undefined) process.env.CALL_LOG_RETENTION_DAYS = savedCall; diff --git a/tests/unit/logger-write-after-datadir-removed-6360.test.ts b/tests/unit/logger-write-after-datadir-removed-6360.test.ts index 26c6c2858cc0..ea9a8bea42cc 100644 --- a/tests/unit/logger-write-after-datadir-removed-6360.test.ts +++ b/tests/unit/logger-write-after-datadir-removed-6360.test.ts @@ -70,7 +70,7 @@ test("logger must not crash the process when its worker transport reports a writ // Simulate the teardown every test file already does: rip out DATA_DIR // while the logger's worker-thread transport is still alive. - rmSync(dir, { recursive: true, force: true }); + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); assert.equal(existsSync(dir), false, "sanity: DATA_DIR must actually be gone"); // Simulate the worker thread reporting the resulting write failure back to @@ -104,6 +104,6 @@ test("logger must not crash the process when its worker transport reports a writ test.after(async () => { await flushLogger(); if (existsSync(dir)) { - rmSync(dir, { recursive: true, force: true }); + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/login-bootstrap-route.test.ts b/tests/unit/login-bootstrap-route.test.ts index c51bd9870a46..8df1f6ccaeff 100644 --- a/tests/unit/login-bootstrap-route.test.ts +++ b/tests/unit/login-bootstrap-route.test.ts @@ -22,7 +22,7 @@ type BootstrapResponse = { async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -38,7 +38,7 @@ test.afterEach(() => { test.after(() => { delete process.env.INITIAL_PASSWORD; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); const originalHash = bcrypt.hash; diff --git a/tests/unit/managed-available-models.test.ts b/tests/unit/managed-available-models.test.ts index 2635867568a2..db79cbcab7e7 100644 --- a/tests/unit/managed-available-models.test.ts +++ b/tests/unit/managed-available-models.test.ts @@ -21,7 +21,7 @@ const { getModelsByProviderId } = await import("../../src/shared/constants/model async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -31,7 +31,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("CC compatible fallback models mirror the OAuth Claude Code registry list", () => { diff --git a/tests/unit/managed-model-import.test.ts b/tests/unit/managed-model-import.test.ts index e6e3bc04b7ad..5e75583c71f5 100644 --- a/tests/unit/managed-model-import.test.ts +++ b/tests/unit/managed-model-import.test.ts @@ -16,7 +16,7 @@ const { mergeProviderModelListing } = async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -26,7 +26,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("sync mode builds aliases from provider-level synced available models", async () => { diff --git a/tests/unit/management-password-insecure-default.test.ts b/tests/unit/management-password-insecure-default.test.ts index 752c15e30cf0..3abc287cdcdd 100644 --- a/tests/unit/management-password-insecure-default.test.ts +++ b/tests/unit/management-password-insecure-default.test.ts @@ -23,13 +23,13 @@ function makeLogger() { test.afterEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); }); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("warns when bootstrapping the management password with the CHANGEME default (Seg2)", async () => { @@ -60,5 +60,9 @@ test("does not warn when bootstrapping with a strong password", async () => { }); assert.equal(managementPassword.isBcryptHash(result.hash), true); - assert.equal(logger.warnings.length, 0, "did not expect any security warning for a strong password"); + assert.equal( + logger.warnings.length, + 0, + "did not expect any security warning for a strong password" + ); }); diff --git a/tests/unit/management-password.test.ts b/tests/unit/management-password.test.ts index eb5af4034d0f..1f12a3bd030f 100644 --- a/tests/unit/management-password.test.ts +++ b/tests/unit/management-password.test.ts @@ -26,7 +26,7 @@ const managementPassword = await import("../../src/lib/auth/managementPassword.t async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); delete process.env.INITIAL_PASSWORD; } @@ -70,7 +70,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_INITIAL_PASSWORD === undefined) { delete process.env.INITIAL_PASSWORD; } else { diff --git a/tests/unit/mark-account-unavailable-numeric-epoch-guard.test.ts b/tests/unit/mark-account-unavailable-numeric-epoch-guard.test.ts index 130481f5adee..ed964ea459bf 100644 --- a/tests/unit/mark-account-unavailable-numeric-epoch-guard.test.ts +++ b/tests/unit/mark-account-unavailable-numeric-epoch-guard.test.ts @@ -31,7 +31,7 @@ const { markAccountUnavailable } = await import("../../src/sse/services/auth.ts" test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); const HOUR = 3_600_000; diff --git a/tests/unit/masked-200-exhaustion-fallback-6427.test.ts b/tests/unit/masked-200-exhaustion-fallback-6427.test.ts index 531c3401a3d1..d6cb0056f459 100644 --- a/tests/unit/masked-200-exhaustion-fallback-6427.test.ts +++ b/tests/unit/masked-200-exhaustion-fallback-6427.test.ts @@ -29,7 +29,8 @@ const core = await import("../../src/lib/db/core.ts"); const settingsDb = await import("../../src/lib/db/settings.ts"); const { resetAllComboMetrics } = await import("../../open-sse/services/comboMetrics.ts"); const { resetAllCircuitBreakers } = await import("../../src/shared/utils/circuitBreaker.ts"); -const { resetAll: resetAllSemaphores } = await import("../../open-sse/services/rateLimitSemaphore.ts"); +const { resetAll: resetAllSemaphores } = + await import("../../open-sse/services/rateLimitSemaphore.ts"); const { _resetAllDecks } = await import("../../src/shared/utils/shuffleDeck.ts"); const { clearSessions } = await import("../../open-sse/services/sessionManager.ts"); @@ -56,7 +57,7 @@ async function cleanupTestDataDir() { for (let attempt = 0; attempt < 5; attempt += 1) { try { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); return; } catch (error: unknown) { lastError = error; @@ -115,7 +116,9 @@ test("#6427 priority combo falls back when the first target's 200 body carries a error: { message: "Insufficient credits balance", type: "insufficient_quota" }, }); } - return jsonResponse({ choices: [{ message: { role: "assistant", content: "real answer" } }] }); + return jsonResponse({ + choices: [{ message: { role: "assistant", content: "real answer" } }], + }); }, isModelAvailable: async () => true, log: createLog(), @@ -130,7 +133,11 @@ test("#6427 priority combo falls back when the first target's 200 body carries a "combo must fail over past the masked-200 target instead of returning it" ); const bodyText = await result.clone().text(); - assert.match(bodyText, /real answer/, "the returned body must be the fallback target's real answer"); + assert.match( + bodyText, + /real answer/, + "the returned body must be the fallback target's real answer" + ); }); test("#6427 priority combo falls back when the first target's 200 body carries a known exhaustion phrase (no structured error)", async () => { @@ -154,7 +161,9 @@ test("#6427 priority combo falls back when the first target's 200 body carries a message: "Quota exceeded for this account", }); } - return jsonResponse({ choices: [{ message: { role: "assistant", content: "real answer" } }] }); + return jsonResponse({ + choices: [{ message: { role: "assistant", content: "real answer" } }], + }); }, isModelAvailable: async () => true, log: createLog(), diff --git a/tests/unit/materialize-bundled-symlinks.test.ts b/tests/unit/materialize-bundled-symlinks.test.ts index a83dc980bdce..d7d11440de89 100644 --- a/tests/unit/materialize-bundled-symlinks.test.ts +++ b/tests/unit/materialize-bundled-symlinks.test.ts @@ -44,7 +44,7 @@ test("materializeBundledSymlinks dereferences a live symlink into a real directo assert.equal(lstatSync(target).isDirectory(), true); assert.equal(JSON.parse(readFileSync(join(target, "package.json"), "utf8")).marker, "real-ws"); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -69,7 +69,7 @@ test("materializeBundledSymlinks relinks a dangling hashed symlink to its siblin assert.equal(lstatSync(target).isSymbolicLink(), false); assert.equal(JSON.parse(readFileSync(join(target, "package.json"), "utf8")).marker, "real-bsq"); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -89,7 +89,7 @@ test("materializeBundledSymlinks drops a dangling link with no resolvable siblin assert.equal(summary.removed, 1); assert.equal(existsSync(join(nm, "mystery-deadbeefcafe0001")), false); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -115,7 +115,7 @@ test("materializeBundledSymlinks handles scoped-package symlinks", () => { assert.equal(lstatSync(target).isSymbolicLink(), false); assert.equal(JSON.parse(readFileSync(join(target, "package.json"), "utf8")).marker, "real-hf"); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -133,7 +133,7 @@ test("materializeBundledSymlinks leaves real directories untouched and no-ops on const missing = materializeBundledSymlinks(join(root, "does-not-exist")); assert.deepEqual(missing, { materialized: 0, relinked: 0, removed: 0 }); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -154,7 +154,7 @@ test("syncRebuiltNativeModuleIntoHashedEntries overwrites a hashed entry with th "electron-abi-rebuilt" ); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -174,7 +174,7 @@ test("syncRebuiltNativeModuleIntoHashedEntries overwrites a plain-named entry to "electron-abi-rebuilt" ); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -195,7 +195,7 @@ test("syncRebuiltNativeModuleIntoHashedEntries no-ops when root module or nested ); assert.deepEqual(missingNm, { synced: 0 }); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -220,6 +220,6 @@ test("syncRebuiltNativeModuleIntoHashedEntries leaves unrelated entries untouche "unrelated-package" ); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/mcp-connect-scope.test.ts b/tests/unit/mcp-connect-scope.test.ts index 35734a27ffef..371d686f7084 100644 --- a/tests/unit/mcp-connect-scope.test.ts +++ b/tests/unit/mcp-connect-scope.test.ts @@ -19,17 +19,13 @@ const core = await import("../../src/lib/db/core.ts"); const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); const settingsDb = await import("../../src/lib/db/settings.ts"); const { managementPolicy } = await import("../../src/server/authz/policies/management.ts"); -const { - isLocalOnlyPath, - isLocalOnlyBypassableByManageScope, -} = await import("../../src/server/authz/routeGuard.ts"); -const { MCP_CONNECT_SCOPE, hasMcpConnectOrManageScope } = await import( - "../../src/shared/constants/managementScopes.ts" -); +const { isLocalOnlyPath, isLocalOnlyBypassableByManageScope } = + await import("../../src/server/authz/routeGuard.ts"); +const { MCP_CONNECT_SCOPE, hasMcpConnectOrManageScope } = + await import("../../src/shared/constants/managementScopes.ts"); const { resolveMcpCallerAuthInfo } = await import("../../open-sse/mcp-server/httpAuthContext.ts"); -const { resolveCallerScopeContext, evaluateToolScopes } = await import( - "../../open-sse/mcp-server/scopeEnforcement.ts" -); +const { resolveCallerScopeContext, evaluateToolScopes } = + await import("../../open-sse/mcp-server/scopeEnforcement.ts"); const ORIGINAL_JWT = process.env.JWT_SECRET; const ORIGINAL_INITIAL = process.env.INITIAL_PASSWORD; @@ -37,7 +33,7 @@ const ORIGINAL_INITIAL = process.env.INITIAL_PASSWORD; function reset() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); delete process.env.JWT_SECRET; delete process.env.INITIAL_PASSWORD; @@ -49,7 +45,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_JWT === undefined) delete process.env.JWT_SECRET; else process.env.JWT_SECRET = ORIGINAL_JWT; if (ORIGINAL_INITIAL === undefined) delete process.env.INITIAL_PASSWORD; @@ -193,20 +189,14 @@ test("per-key authInfo.scopes takes precedence over the env fallback once resolv assert.equal(scopeContext.source, "authInfo"); assert.deepEqual(scopeContext.scopes, ["read:health"]); - const allowedCheck = evaluateToolScopes( - "irrelevant-tool-name", - scopeContext.scopes, - true, - ["read:health"] - ); + const allowedCheck = evaluateToolScopes("irrelevant-tool-name", scopeContext.scopes, true, [ + "read:health", + ]); assert.equal(allowedCheck.allowed, true); - const deniedCheck = evaluateToolScopes( - "irrelevant-tool-name", - scopeContext.scopes, - true, - ["write:combos"] - ); + const deniedCheck = evaluateToolScopes("irrelevant-tool-name", scopeContext.scopes, true, [ + "write:combos", + ]); assert.equal(deniedCheck.allowed, false, "per-key scopes must gate, not the wider env fallback"); }); diff --git a/tests/unit/mcp-memory-tools-strategy.test.ts b/tests/unit/mcp-memory-tools-strategy.test.ts index ba740797caa8..f89dcfcbf9f8 100644 --- a/tests/unit/mcp-memory-tools-strategy.test.ts +++ b/tests/unit/mcp-memory-tools-strategy.test.ts @@ -29,7 +29,7 @@ const core = await import("../../src/lib/db/core.ts"); function cleanup() { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -37,16 +37,15 @@ function cleanup() { test.afterEach(() => cleanup()); test.after(() => { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); // ── A: toMemoryRetrievalConfig: "hybrid" → retrievalStrategy="hybrid" ───────── test("toMemoryRetrievalConfig: strategy=hybrid → retrievalStrategy=hybrid", async () => { - const { toMemoryRetrievalConfig, DEFAULT_MEMORY_SETTINGS } = await import( - "../../src/lib/memory/settings.ts" - ); + const { toMemoryRetrievalConfig, DEFAULT_MEMORY_SETTINGS } = + await import("../../src/lib/memory/settings.ts"); const settings = { ...DEFAULT_MEMORY_SETTINGS, strategy: "hybrid" as const }; const config = toMemoryRetrievalConfig(settings); assert.equal( @@ -59,9 +58,8 @@ test("toMemoryRetrievalConfig: strategy=hybrid → retrievalStrategy=hybrid", as // ── B: toMemoryRetrievalConfig: "semantic" → retrievalStrategy="semantic" ───── test("toMemoryRetrievalConfig: strategy=semantic → retrievalStrategy=semantic", async () => { - const { toMemoryRetrievalConfig, DEFAULT_MEMORY_SETTINGS } = await import( - "../../src/lib/memory/settings.ts" - ); + const { toMemoryRetrievalConfig, DEFAULT_MEMORY_SETTINGS } = + await import("../../src/lib/memory/settings.ts"); const settings = { ...DEFAULT_MEMORY_SETTINGS, strategy: "semantic" as const }; const config = toMemoryRetrievalConfig(settings); assert.equal( @@ -74,9 +72,8 @@ test("toMemoryRetrievalConfig: strategy=semantic → retrievalStrategy=semantic" // ── C: toMemoryRetrievalConfig: "recent" → retrievalStrategy="exact" ────────── test("toMemoryRetrievalConfig: strategy=recent → retrievalStrategy=exact (mapped)", async () => { - const { toMemoryRetrievalConfig, DEFAULT_MEMORY_SETTINGS } = await import( - "../../src/lib/memory/settings.ts" - ); + const { toMemoryRetrievalConfig, DEFAULT_MEMORY_SETTINGS } = + await import("../../src/lib/memory/settings.ts"); const settings = { ...DEFAULT_MEMORY_SETTINGS, strategy: "recent" as const }; const config = toMemoryRetrievalConfig(settings); assert.equal( @@ -105,9 +102,7 @@ test("omniroute_memory_search: strategy=hybrid in DB → handler returns success const { invalidateMemorySettingsCache } = await import("../../src/lib/memory/settings.ts"); invalidateMemorySettingsCache(); - const { memoryTools } = await import( - "../../open-sse/mcp-server/tools/memoryTools.ts" - ); + const { memoryTools } = await import("../../open-sse/mcp-server/tools/memoryTools.ts"); const handler = memoryTools.omniroute_memory_search.handler; const result = await handler({ apiKeyId: "api-mcp-h", query: "Paris" }); @@ -135,9 +130,7 @@ test("omniroute_memory_search: strategy=recent in DB → handler maps to exact, const { invalidateMemorySettingsCache } = await import("../../src/lib/memory/settings.ts"); invalidateMemorySettingsCache(); - const { memoryTools } = await import( - "../../open-sse/mcp-server/tools/memoryTools.ts" - ); + const { memoryTools } = await import("../../open-sse/mcp-server/tools/memoryTools.ts"); const handler = memoryTools.omniroute_memory_search.handler; const result = await handler({ apiKeyId: "api-mcp-r" }); @@ -150,9 +143,8 @@ test("omniroute_memory_search: strategy=recent in DB → handler maps to exact, // toMemoryRetrievalConfig used on DEFAULT maps to retrievalStrategy="hybrid" ── test("toMemoryRetrievalConfig: DEFAULT_MEMORY_SETTINGS maps to retrievalStrategy=hybrid", async () => { - const { toMemoryRetrievalConfig, DEFAULT_MEMORY_SETTINGS } = await import( - "../../src/lib/memory/settings.ts" - ); + const { toMemoryRetrievalConfig, DEFAULT_MEMORY_SETTINGS } = + await import("../../src/lib/memory/settings.ts"); // Verify the default strategy is "hybrid" so fallback in handler resolves to hybrid assert.equal( DEFAULT_MEMORY_SETTINGS.strategy, @@ -174,9 +166,8 @@ test("omniroute_memory_search: hardcoded fallback config has retrievalStrategy=e // We verify this by examining the fallback object directly from the source logic: // When memorySettings is null, the handler uses retrievalStrategy: "exact" as const. // We test this via toMemoryRetrievalConfig with a minimal disabled-settings object. - const { toMemoryRetrievalConfig, DEFAULT_MEMORY_SETTINGS } = await import( - "../../src/lib/memory/settings.ts" - ); + const { toMemoryRetrievalConfig, DEFAULT_MEMORY_SETTINGS } = + await import("../../src/lib/memory/settings.ts"); // Simulate the catch path: strategy "recent" maps to "exact" (same as hardcoded fallback) const disabledSettings = { ...DEFAULT_MEMORY_SETTINGS, strategy: "recent" as const }; diff --git a/tests/unit/mcp-route-scope-carveout.test.ts b/tests/unit/mcp-route-scope-carveout.test.ts index d64dbc7922ec..f35d7f8d409e 100644 --- a/tests/unit/mcp-route-scope-carveout.test.ts +++ b/tests/unit/mcp-route-scope-carveout.test.ts @@ -30,7 +30,7 @@ const ORIGINAL_INITIAL = process.env.INITIAL_PASSWORD; function reset() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); delete process.env.JWT_SECRET; delete process.env.INITIAL_PASSWORD; @@ -42,7 +42,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_JWT === undefined) delete process.env.JWT_SECRET; else process.env.JWT_SECRET = ORIGINAL_JWT; if (ORIGINAL_INITIAL === undefined) delete process.env.INITIAL_PASSWORD; diff --git a/tests/unit/mcp/bundle-no-sync-esm-await.test.ts b/tests/unit/mcp/bundle-no-sync-esm-await.test.ts index 7884def6a004..d6182fa07826 100644 --- a/tests/unit/mcp/bundle-no-sync-esm-await.test.ts +++ b/tests/unit/mcp/bundle-no-sync-esm-await.test.ts @@ -155,7 +155,7 @@ test("MCP bundle never emits await inside a synchronous __esm initializer", () = runEsbuild(bundleArgs, ROOT); assertNoSyncEsmAwait(outputFile); } finally { - rmSync(outputDir, { recursive: true, force: true }); + rmSync(outputDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -186,6 +186,6 @@ test("esbuild propagates async initialization through wrapped import cycles", () ); assertNoSyncEsmAwait(outputFile); } finally { - rmSync(fixtureDir, { recursive: true, force: true }); + rmSync(fixtureDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/media-cost-headers-handlers.test.ts b/tests/unit/media-cost-headers-handlers.test.ts index 341c2871c709..4e8d54822b04 100644 --- a/tests/unit/media-cost-headers-handlers.test.ts +++ b/tests/unit/media-cost-headers-handlers.test.ts @@ -31,7 +31,7 @@ test.afterEach(() => { test.after(() => { restoreGlobals(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("v1 video generation failure preserves provider status and error payload", async () => { diff --git a/tests/unit/media-cost-headers.test.ts b/tests/unit/media-cost-headers.test.ts index 41ad5542fb9c..d0f47e8b0006 100644 --- a/tests/unit/media-cost-headers.test.ts +++ b/tests/unit/media-cost-headers.test.ts @@ -36,7 +36,7 @@ test.afterEach(() => { test.after(() => { restoreGlobals(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // Shared assertions: every successful media Response must carry the @@ -142,7 +142,9 @@ test("v1 music generation success Response carries cost telemetry headers", asyn return new Response( JSON.stringify({ "music-cost-1": { - outputs: { 7: { audio: [{ filename: "track.wav", subfolder: "out", type: "output" }] } }, + outputs: { + 7: { audio: [{ filename: "track.wav", subfolder: "out", type: "output" }] }, + }, }, }), { status: 200, headers: { "content-type": "application/json" } } diff --git a/tests/unit/memory-engine-status.test.ts b/tests/unit/memory-engine-status.test.ts index a3921b74a9f1..936a901e8640 100644 --- a/tests/unit/memory-engine-status.test.ts +++ b/tests/unit/memory-engine-status.test.ts @@ -33,7 +33,7 @@ const { MemoryEngineStatusSchema } = await import("../../src/shared/schemas/memo function cleanup() { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -41,7 +41,7 @@ function cleanup() { test.afterEach(() => cleanup()); test.after(() => { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -153,11 +153,7 @@ test("engineStatus(): detail strings are English, not mixed Portuguese (#5596)", "degradado", "selecionado", ]; - for (const reason of [ - status.embedding.reason, - status.vectorStore.reason, - status.rerank.reason, - ]) { + for (const reason of [status.embedding.reason, status.vectorStore.reason, status.rerank.reason]) { for (const w of ptWords) { assert.ok(!reason.includes(w), `reason "${reason}" still contains Portuguese "${w}"`); } diff --git a/tests/unit/memory-needs-reindex.test.ts b/tests/unit/memory-needs-reindex.test.ts index 8818358a4837..0793676155d5 100644 --- a/tests/unit/memory-needs-reindex.test.ts +++ b/tests/unit/memory-needs-reindex.test.ts @@ -27,10 +27,12 @@ function insertTestMemory( content: string, key: string ): void { - db.prepare(` + db.prepare( + ` INSERT INTO memories (id, api_key_id, type, key, content, created_at, updated_at) VALUES (?, ?, ?, ?, ?, datetime('now'), datetime('now')) - `).run(id, "test-api-key", "factual", key, content); + ` + ).run(id, "test-api-key", "factual", key, content); } async function resetStorage() { @@ -39,7 +41,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: unknown) { @@ -61,7 +63,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ──────────────── markMemoryNeedsReindex ──────────────── diff --git a/tests/unit/memory-reindex-batch.test.ts b/tests/unit/memory-reindex-batch.test.ts index 26d99247d37a..82e96824d01a 100644 --- a/tests/unit/memory-reindex-batch.test.ts +++ b/tests/unit/memory-reindex-batch.test.ts @@ -36,7 +36,7 @@ const { runReindexBatch, getReindexPending } = await import("../../src/lib/memor function cleanup() { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -44,7 +44,7 @@ function cleanup() { test.afterEach(() => cleanup()); test.after(() => { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/memory-retrieval-hybrid.test.ts b/tests/unit/memory-retrieval-hybrid.test.ts index 0c0daa119e4f..5614cef31231 100644 --- a/tests/unit/memory-retrieval-hybrid.test.ts +++ b/tests/unit/memory-retrieval-hybrid.test.ts @@ -28,7 +28,7 @@ async function removeTestDataDir() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } return; } catch (error: unknown) { diff --git a/tests/unit/memory-retrieval-rerank.test.ts b/tests/unit/memory-retrieval-rerank.test.ts index 22c4e4121b71..0e9b588a1edd 100644 --- a/tests/unit/memory-retrieval-rerank.test.ts +++ b/tests/unit/memory-retrieval-rerank.test.ts @@ -32,7 +32,7 @@ const core = await import("../../src/lib/db/core.ts"); function cleanup() { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -40,7 +40,7 @@ function cleanup() { test.afterEach(() => cleanup()); test.after(() => { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -144,7 +144,12 @@ test("retrieveMemories: large result set is token-budget capped before any reran const db = core.getDbInstance(); // Insert 20 memories for (let i = 1; i <= 20; i++) { - insertMemory(db, `large-${i}`, "api-large", `Content number ${i} with enough words to use tokens.`); + insertMemory( + db, + `large-${i}`, + "api-large", + `Content number ${i} with enough words to use tokens.` + ); } const { retrieveMemories } = await import("../../src/lib/memory/retrieval.ts"); diff --git a/tests/unit/memory-retrieval-semantic.test.ts b/tests/unit/memory-retrieval-semantic.test.ts index 806c2ee397f1..b5b0eae4bb6d 100644 --- a/tests/unit/memory-retrieval-semantic.test.ts +++ b/tests/unit/memory-retrieval-semantic.test.ts @@ -30,7 +30,7 @@ const core = await import("../../src/lib/db/core.ts"); function cleanup() { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -38,7 +38,7 @@ function cleanup() { test.afterEach(() => cleanup()); test.after(() => { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -116,9 +116,30 @@ test("retrieveMemories: strategy=exact returns memories chronologically", async // Use recent dates (within last 30 days) so retention filter does not remove them const now = Date.now(); const base = new Date(now - 3 * 24 * 60 * 60 * 1000); // 3 days ago - insertMemory(db, "e1", "api-exact", "First memory", "first", new Date(base.getTime() + 3000).toISOString()); - insertMemory(db, "e2", "api-exact", "Second memory", "second", new Date(base.getTime() + 2000).toISOString()); - insertMemory(db, "e3", "api-exact", "Third memory", "third", new Date(base.getTime() + 1000).toISOString()); + insertMemory( + db, + "e1", + "api-exact", + "First memory", + "first", + new Date(base.getTime() + 3000).toISOString() + ); + insertMemory( + db, + "e2", + "api-exact", + "Second memory", + "second", + new Date(base.getTime() + 2000).toISOString() + ); + insertMemory( + db, + "e3", + "api-exact", + "Third memory", + "third", + new Date(base.getTime() + 1000).toISOString() + ); const { retrieveMemories } = await import("../../src/lib/memory/retrieval.ts"); @@ -181,7 +202,10 @@ test("retrieveMemories: returns only memories for the given apiKeyId", async () const { retrieveMemories } = await import("../../src/lib/memory/retrieval.ts"); - const result = await retrieveMemories("api-key1", { retrievalStrategy: "exact", maxTokens: 2000 }); + const result = await retrieveMemories("api-key1", { + retrievalStrategy: "exact", + maxTokens: 2000, + }); for (const m of result) { assert.equal(m.apiKeyId, "api-key1", "should only return memories for api-key1"); } diff --git a/tests/unit/memory-retrieve-preview.test.ts b/tests/unit/memory-retrieve-preview.test.ts index e4de63cb727e..14fd82a2cebf 100644 --- a/tests/unit/memory-retrieve-preview.test.ts +++ b/tests/unit/memory-retrieve-preview.test.ts @@ -28,7 +28,7 @@ const core = await import("../../src/lib/db/core.ts"); function cleanup() { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -36,7 +36,7 @@ function cleanup() { test.afterEach(() => cleanup()); test.after(() => { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -126,8 +126,7 @@ test("retrievePreview: semantic strategy with no vec store → fallbackReason is // No embedding source configured → fallback assert.ok( - bundle.resolution.fallbackReason !== null || - bundle.resolution.strategyUsed !== "semantic", + bundle.resolution.fallbackReason !== null || bundle.resolution.strategyUsed !== "semantic", "semantic preview with no vec store should indicate fallback" ); assert.ok(Array.isArray(bundle.items), "items must be array even in fallback"); diff --git a/tests/unit/memory-route.test.ts b/tests/unit/memory-route.test.ts index 7ee36213944b..bcc462a94c65 100644 --- a/tests/unit/memory-route.test.ts +++ b/tests/unit/memory-route.test.ts @@ -14,7 +14,7 @@ const { MemoryType } = await import("../../src/lib/memory/types.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -54,7 +54,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("GET /api/memory filters by q and returns matching stats", async () => { diff --git a/tests/unit/memory-store-sync.test.ts b/tests/unit/memory-store-sync.test.ts index f0d8955a69a3..5a358b191a3d 100644 --- a/tests/unit/memory-store-sync.test.ts +++ b/tests/unit/memory-store-sync.test.ts @@ -43,7 +43,7 @@ const memoryVec = await import("../../src/lib/db/memoryVec.ts"); function cleanup() { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -54,7 +54,7 @@ test.afterEach(() => { test.after(() => { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -87,8 +87,7 @@ test("createMemory() inserts row and returns valid Memory object", async () => { // Verify row exists in DB const db = core.getDbInstance(); const row = db.prepare("SELECT * FROM memories WHERE id = ?").get(created.id) as - | { id: string; content: string } - | undefined; + { id: string; content: string } | undefined; assert.ok(row, "row should exist in DB after createMemory"); assert.equal(row.content, "content for create test"); }); @@ -121,7 +120,9 @@ test("createMemory() UPSERT: same apiKeyId+key updates existing row", async () = // Verify only one row in DB for this key const db = core.getDbInstance(); const count = ( - db.prepare("SELECT COUNT(*) as cnt FROM memories WHERE api_key_id = ? AND key = ?").get("key-b", "upsert:test") as { + db + .prepare("SELECT COUNT(*) as cnt FROM memories WHERE api_key_id = ? AND key = ?") + .get("key-b", "upsert:test") as { cnt: number; } ).cnt; @@ -180,8 +181,7 @@ test("updateMemory() with content change returns true and updates the row", asyn // Verify the DB was updated const db = core.getDbInstance(); const row = db.prepare("SELECT content FROM memories WHERE id = ?").get(created.id) as - | { content: string } - | undefined; + { content: string } | undefined; assert.equal(row?.content, "new content changed", "content should be updated in DB"); }); @@ -208,11 +208,7 @@ test("updateMemory() metadata-only change does NOT mark needs_reindex (content u const pending = memoryVec.getMemoryReindexQueue(100); const inQueue = pending.some((item) => item.id === created.id); - assert.equal( - inQueue, - false, - "metadata-only update should NOT schedule vector re-gen" - ); + assert.equal(inQueue, false, "metadata-only update should NOT schedule vector re-gen"); }); test("getMemoryTokensUsed() returns 0 for empty DB", () => { diff --git a/tests/unit/memory-store.test.ts b/tests/unit/memory-store.test.ts index 1699955136c9..b48eb7b3ad95 100644 --- a/tests/unit/memory-store.test.ts +++ b/tests/unit/memory-store.test.ts @@ -17,7 +17,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: any) { @@ -76,7 +76,7 @@ test.afterEach(async () => { test.after(async () => { await drainSetImmediate(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("memory store CRUD round-trip persists to the memories table and invalidates cache on update/delete", async () => { diff --git a/tests/unit/memory-summarization-older-than.test.ts b/tests/unit/memory-summarization-older-than.test.ts index 03c618a4f4f3..6611ca3991da 100644 --- a/tests/unit/memory-summarization-older-than.test.ts +++ b/tests/unit/memory-summarization-older-than.test.ts @@ -29,7 +29,7 @@ const { summarizeMemoriesOlderThan } = await import("../../src/lib/memory/summar function cleanup() { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -53,7 +53,7 @@ test.afterEach(async () => { }); test.after(() => { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -175,7 +175,11 @@ test("summarizeMemoriesOlderThan: totalTokens equals sum of candidates' content (sum, m) => sum + Math.ceil(m.content.length / 4), 0 ); - assert.equal(result.totalTokens, expectedTokens, "totalTokens must equal sum of candidate tokens"); + assert.equal( + result.totalTokens, + expectedTokens, + "totalTokens must equal sum of candidate tokens" + ); }); test("summarizeMemoriesOlderThan: apiKeyId=undefined scopes to ALL memories", async () => { diff --git a/tests/unit/memory-summarization.test.ts b/tests/unit/memory-summarization.test.ts index 381e1a22bb1d..d8cdc629235e 100644 --- a/tests/unit/memory-summarization.test.ts +++ b/tests/unit/memory-summarization.test.ts @@ -16,7 +16,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: any) { @@ -62,7 +62,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("summarizeMemories returns zeroed metrics for empty conversations", async () => { diff --git a/tests/unit/memory-tools.test.ts b/tests/unit/memory-tools.test.ts index 4e021834ce66..aadbf6b43318 100644 --- a/tests/unit/memory-tools.test.ts +++ b/tests/unit/memory-tools.test.ts @@ -16,7 +16,7 @@ const { invalidateMemorySettingsCache } = await import("../../src/lib/memory/set function resetStorage() { core.resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(tmpDir, { recursive: true }); core.getDbInstance(); } @@ -34,7 +34,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); process.env.DATA_DIR = originalDataDir; - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("memory add stores entries with default session and metadata", async () => { diff --git a/tests/unit/memory-vec-meta.test.ts b/tests/unit/memory-vec-meta.test.ts index 49db9ace0030..3f55166a9686 100644 --- a/tests/unit/memory-vec-meta.test.ts +++ b/tests/unit/memory-vec-meta.test.ts @@ -24,7 +24,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: unknown) { @@ -46,7 +46,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ──────────────── getMemoryVecMeta initial state ──────────────── @@ -92,7 +92,7 @@ test("setMemoryVecMeta persists activeDim and embeddingSignature", () => { assert.equal(meta.activeDim, 1536); assert.equal(meta.embeddingSignature, "remote:openai/text-embedding-3-small:1536"); assert.equal(meta.lastResetAt, null); // not set - assert.equal(meta.vecLoaded, false); // not set + assert.equal(meta.vecLoaded, false); // not set }); test("setMemoryVecMeta persists vecLoaded = true", () => { @@ -119,7 +119,11 @@ test("setMemoryVecMeta updates only the provided fields (partial update)", () => const meta = memoryVec.getMemoryVecMeta(); assert.equal(meta.activeDim, 1536, "activeDim should be updated"); - assert.equal(meta.embeddingSignature, "static:potion-base-8M:768", "embeddingSignature should be preserved"); + assert.equal( + meta.embeddingSignature, + "static:potion-base-8M:768", + "embeddingSignature should be preserved" + ); assert.equal(meta.vecLoaded, true, "vecLoaded should be preserved"); }); diff --git a/tests/unit/memory-vectorstore-crud.test.ts b/tests/unit/memory-vectorstore-crud.test.ts index a1d489e18687..1a3932e38105 100644 --- a/tests/unit/memory-vectorstore-crud.test.ts +++ b/tests/unit/memory-vectorstore-crud.test.ts @@ -49,7 +49,7 @@ function cleanup() { _resetVectorStoreSingleton(); core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -61,7 +61,7 @@ test.afterEach(() => { test.after(() => { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -84,11 +84,11 @@ function insertMemory( db: ReturnType, id: string, apiKeyId: string, - content: string, + content: string ) { db.prepare( `INSERT INTO memories (id, api_key_id, type, key, content, created_at) - VALUES (?, ?, 'factual', ?, ?, datetime('now'))`, + VALUES (?, ?, 'factual', ?, ?, datetime('now'))` ).run(id, apiKeyId, `key-${id}`, content); } @@ -137,7 +137,7 @@ test("upsertVector: throws when memoryId does not exist in memories table", asyn await assert.rejects( () => store.upsertVector("nonexistent-id", makeVec(1.0, 0.0, 0.0, 0.0)), /memory not found/i, - "should throw when memoryId not found", + "should throw when memoryId not found" ); }); @@ -176,7 +176,7 @@ test("searchVector: returns topK=2 results ordered by distance ASC", async (t) = if (hits.length >= 2) { assert.ok( hits[0].distance <= hits[1].distance, - "results must be ordered by distance ASC (smaller = more similar)", + "results must be ordered by distance ASC (smaller = more similar)" ); } @@ -263,6 +263,6 @@ test("deleteVector: no-op when memoryId does not exist (no throw)", async (t) => // Should not throw. await assert.doesNotReject( () => store.deleteVector("nonexistent-id"), - "deleteVector for non-existent id must be a no-op (not throw)", + "deleteVector for non-existent id must be a no-op (not throw)" ); }); diff --git a/tests/unit/memory-vectorstore-ensure-ready.test.ts b/tests/unit/memory-vectorstore-ensure-ready.test.ts index 36052ead834e..9208589b6604 100644 --- a/tests/unit/memory-vectorstore-ensure-ready.test.ts +++ b/tests/unit/memory-vectorstore-ensure-ready.test.ts @@ -42,7 +42,7 @@ function cleanup() { _resetVectorStoreSingleton(); core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -54,7 +54,7 @@ test.afterEach(() => { test.after(() => { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -126,7 +126,7 @@ test("ensureReady: signature change triggers reset + marks memories needs_reinde for (let i = 0; i < 3; i++) { db.prepare( `INSERT INTO memories (id, api_key_id, type, key, content, created_at) - VALUES (?, 'key1', 'factual', ?, ?, datetime('now'))`, + VALUES (?, 'key1', 'factual', ?, ?, datetime('now'))` ).run(`mem-${i}`, `key-${i}`, `content-${i}`); } @@ -154,7 +154,11 @@ test("ensureReady: signature change triggers reset + marks memories needs_reinde const needsRows = db .prepare("SELECT COUNT(*) AS cnt FROM memories WHERE needs_reindex = 1") .get() as { cnt: number }; - assert.equal(needsRows.cnt, 3, "all memories should be marked needs_reindex=1 after signature change"); + assert.equal( + needsRows.cnt, + 3, + "all memories should be marked needs_reindex=1 after signature change" + ); }); test("ensureReady: returns {ready: false} when dimensions are null (no probe done yet)", async (t) => { @@ -176,7 +180,7 @@ test("ensureReady: returns {ready: false} when dimensions are null (no probe don // Either ready (if signature already matches a loaded table) or not ready. assert.ok( typeof result.ready === "boolean", - "ensureReady must return {ready: boolean, reason: string}", + "ensureReady must return {ready: boolean, reason: string}" ); assert.ok(typeof result.reason === "string"); }); diff --git a/tests/unit/memory-vectorstore-int8-quant.test.ts b/tests/unit/memory-vectorstore-int8-quant.test.ts index e301298649e8..0b9d6d914c04 100644 --- a/tests/unit/memory-vectorstore-int8-quant.test.ts +++ b/tests/unit/memory-vectorstore-int8-quant.test.ts @@ -70,7 +70,8 @@ function exactNearestIds(query: number[], k: number): string[] { function cleanup() { _resetVectorStoreSingleton(); core.resetDbInstance(); - if (fs.existsSync(TEST_DATA_DIR)) fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + if (fs.existsSync(TEST_DATA_DIR)) + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -81,7 +82,8 @@ test.afterEach(() => { test.after(() => { core.resetDbInstance(); - if (fs.existsSync(TEST_DATA_DIR)) fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + if (fs.existsSync(TEST_DATA_DIR)) + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function getStoreOrSkip(t: { skip: (msg: string) => void }): ReturnType { @@ -97,7 +99,7 @@ function getStoreOrSkip(t: { skip: (msg: string) => void }): ReturnType, id: string) { db.prepare( `INSERT INTO memories (id, api_key_id, type, key, content, created_at) - VALUES (?, 'key1', 'factual', ?, ?, datetime('now'))`, + VALUES (?, 'key1', 'factual', ?, ?, datetime('now'))` ).run(id, `key-${id}`, `content-${id}`); } @@ -119,7 +121,7 @@ test("int8 mode: ensureReady stores an ':int8' signature", async (t) => { const stats = await store.stats(); assert.ok( stats.signature?.endsWith(":int8"), - `signature must carry the int8 marker, got ${stats.signature}`, + `signature must carry the int8 marker, got ${stats.signature}` ); }); @@ -139,9 +141,12 @@ test("int8 recall: nearest-neighbor matches exact float32 NN on the fixture", as assert.equal(hits[0].memoryId, exact[0], `top-1 must match exact NN (${exact[0]})`); const overlap = hits.slice(0, 3).filter((h) => exact.includes(h.memoryId)).length; - assert.ok(overlap >= 2, `top-3 overlap must be >= 2/3 (got ${overlap}; int8=${hits - .map((h) => h.memoryId) - .join(",")} exact=${exact.join(",")})`); + assert.ok( + overlap >= 2, + `top-3 overlap must be >= 2/3 (got ${overlap}; int8=${hits + .map((h) => h.memoryId) + .join(",")} exact=${exact.join(",")})` + ); }); test("switching none → int8 is a signature change that triggers reindex", async (t) => { diff --git a/tests/unit/memory-vectorstore-load.test.ts b/tests/unit/memory-vectorstore-load.test.ts index 7aec414cd988..8b2ed83fef50 100644 --- a/tests/unit/memory-vectorstore-load.test.ts +++ b/tests/unit/memory-vectorstore-load.test.ts @@ -31,7 +31,7 @@ function cleanup() { _resetVectorStoreSingleton(); core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -43,7 +43,7 @@ test.afterEach(() => { test.after(() => { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -83,7 +83,7 @@ test("getVectorStore() returns null or a VectorStore instance (never throws)", ( assert.equal(threw, false, "getVectorStore() must never throw — must return null on failure"); assert.ok( result === null || (typeof result === "object" && result !== null), - `getVectorStore() must return object or null, got ${typeof result}`, + `getVectorStore() must return object or null, got ${typeof result}` ); }); @@ -109,7 +109,7 @@ test("getVectorStore() result has all required VectorStore methods when not null for (const method of requiredMethods) { assert.ok( typeof (store as Record)[method] === "function", - `VectorStore must have method ${method}`, + `VectorStore must have method ${method}` ); } }); diff --git a/tests/unit/memory-vectorstore-rrf.test.ts b/tests/unit/memory-vectorstore-rrf.test.ts index 4261ca6df25b..88a3dacd4b59 100644 --- a/tests/unit/memory-vectorstore-rrf.test.ts +++ b/tests/unit/memory-vectorstore-rrf.test.ts @@ -50,7 +50,7 @@ function cleanup() { _resetVectorStoreSingleton(); core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -62,7 +62,7 @@ test.afterEach(() => { test.after(() => { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -84,20 +84,19 @@ function insertMemoryWithFts( db: ReturnType, id: string, apiKeyId: string, - content: string, + content: string ) { // Insert into memories — the trigger memory_fts_ai fires automatically if the DB has it. // In a fresh test DB the trigger exists (created by migration 023). db.prepare( `INSERT INTO memories (id, api_key_id, type, key, content, created_at) - VALUES (?, ?, 'factual', ?, ?, datetime('now'))`, + VALUES (?, ?, 'factual', ?, ?, datetime('now'))` ).run(id, apiKeyId, `key-${id}`, content); // The migration 023 trigger inserts into memory_fts using memory_id (= rowid). // If the trigger didn't fire (e.g. test DB without triggers), manually sync FTS. try { const row = db.prepare("SELECT rowid, memory_id FROM memories WHERE id = ?").get(id) as - | { rowid: number; memory_id: number | null } - | undefined; + { rowid: number; memory_id: number | null } | undefined; if (row) { const ftsRowid = row.memory_id ?? row.rowid; const ftsCount = db @@ -107,7 +106,7 @@ function insertMemoryWithFts( db.prepare("INSERT INTO memory_fts(rowid, content, key) VALUES(?, ?, ?)").run( ftsRowid, content, - `key-${id}`, + `key-${id}` ); } } @@ -153,7 +152,7 @@ test("searchHybrid: results ordered DESC by rrfScore", async (t) => { for (let i = 0; i < hits.length - 1; i++) { assert.ok( hits[i].rrfScore >= hits[i + 1].rrfScore, - `results must be ordered DESC by rrfScore: ${hits[i].rrfScore} >= ${hits[i + 1].rrfScore}`, + `results must be ordered DESC by rrfScore: ${hits[i].rrfScore} >= ${hits[i + 1].rrfScore}` ); } }); @@ -183,7 +182,7 @@ test("searchHybrid: doc in both FTS and vec → highest rrfScore (sum of both co const minRrf = 1 / (RRF_K + 1); assert.ok( bothHit.rrfScore >= minRrf, - `mem-both rrfScore ${bothHit.rrfScore} should be >= ${minRrf}`, + `mem-both rrfScore ${bothHit.rrfScore} should be >= ${minRrf}` ); } }); @@ -210,7 +209,7 @@ test("searchHybrid: FTS-only hit has vecRank=null", async (t) => { // Score should be approximately the FTS contribution. assert.ok( Math.abs(ftsOnlyHit.rrfScore - expectedContrib) < 0.01, - `FTS-only rrfScore ${ftsOnlyHit.rrfScore} should ≈ ${expectedContrib}`, + `FTS-only rrfScore ${ftsOnlyHit.rrfScore} should ≈ ${expectedContrib}` ); } } @@ -236,7 +235,7 @@ test("searchHybrid: apiKeyId filters both vec and FTS results", async (t) => { // At least one of each should appear (FTS and/or vec). assert.ok( allIds.includes("mem-key1") || allIds.includes("mem-key2"), - "without filter should include at least one hit", + "without filter should include at least one hit" ); // With filter for key1 only. diff --git a/tests/unit/memory-vectorstore-stats.test.ts b/tests/unit/memory-vectorstore-stats.test.ts index 5491fdde0386..038bab8653fd 100644 --- a/tests/unit/memory-vectorstore-stats.test.ts +++ b/tests/unit/memory-vectorstore-stats.test.ts @@ -43,13 +43,10 @@ function makeVec(...values: number[]): Float32Array { return new Float32Array(values); } -function insertMemory( - db: ReturnType, - id: string, -) { +function insertMemory(db: ReturnType, id: string) { db.prepare( `INSERT INTO memories (id, api_key_id, type, key, content, created_at) - VALUES (?, 'key1', 'factual', ?, ?, datetime('now'))`, + VALUES (?, 'key1', 'factual', ?, ?, datetime('now'))` ).run(id, `key-${id}`, `content-${id}`); } @@ -58,7 +55,7 @@ function cleanup() { _resetVectorStoreSingleton(); core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -70,7 +67,7 @@ test.afterEach(() => { test.after(() => { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/memory-vectorstore-upsert-self-heal.test.ts b/tests/unit/memory-vectorstore-upsert-self-heal.test.ts index de8657ff607b..5180c6e95441 100644 --- a/tests/unit/memory-vectorstore-upsert-self-heal.test.ts +++ b/tests/unit/memory-vectorstore-upsert-self-heal.test.ts @@ -59,7 +59,7 @@ function cleanup() { _resetVectorStoreSingleton(); core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -71,7 +71,7 @@ test.afterEach(() => { test.after(() => { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -89,11 +89,11 @@ function insertMemory( db: ReturnType, id: string, apiKeyId: string, - content: string, + content: string ) { db.prepare( `INSERT INTO memories (id, api_key_id, type, key, content, created_at) - VALUES (?, ?, 'factual', ?, ?, datetime('now'))`, + VALUES (?, ?, 'factual', ?, ?, datetime('now'))` ).run(id, apiKeyId, `key-${id}`, content); } @@ -116,7 +116,7 @@ test("upsertVector: self-heals when vec_memories is missing after ensureReady al assert.equal( db.prepare("SELECT name FROM sqlite_master WHERE name = 'vec_memories'").get(), undefined, - "table must actually be gone for this test to be meaningful", + "table must actually be gone for this test to be meaningful" ); // Must NOT throw "no such table: vec_memories" — must self-heal and succeed. @@ -139,7 +139,7 @@ test("deleteVector: self-heals when vec_memories is missing (no throw)", async ( await assert.doesNotReject( () => store.deleteVector("mem-a"), - "deleteVector must self-heal from a missing table, not throw", + "deleteVector must self-heal from a missing table, not throw" ); }); @@ -154,6 +154,6 @@ test("upsertVector: still throws a genuine unrelated error unchanged (no over-br await assert.rejects( () => store.upsertVector("nonexistent-id", makeVec(1.0, 0.0, 0.0, 0.0)), /memory not found/i, - "unrelated errors must not be swallowed by the self-heal retry", + "unrelated errors must not be swallowed by the self-heal retry" ); }); diff --git a/tests/unit/memory/typed-decay.test.ts b/tests/unit/memory/typed-decay.test.ts index adab78196097..0af1349e5979 100644 --- a/tests/unit/memory/typed-decay.test.ts +++ b/tests/unit/memory/typed-decay.test.ts @@ -14,9 +14,7 @@ before(() => { process.env.DATA_DIR = dataDir; }); -const { - MemoryType, -} = await import("../../../src/lib/memory/types.ts"); +const { MemoryType } = await import("../../../src/lib/memory/types.ts"); const { resolveTypedDecayConfig, isTypeImmune, @@ -27,9 +25,8 @@ const { DEFAULT_TTL_DAYS_BY_TYPE, DEFAULT_ACCESS_IMMUNITY_THRESHOLD, } = await import("../../../src/lib/memory/typedDecay.ts"); -const { createMemory, getMemory, recordMemoryAccess, listMemoriesForDecay } = await import( - "../../../src/lib/memory/store.ts" -); +const { createMemory, getMemory, recordMemoryAccess, listMemoriesForDecay } = + await import("../../../src/lib/memory/store.ts"); const { resetDbInstance, getDbInstance } = await import("../../../src/lib/db/core.ts"); const DAY_MS = 24 * 60 * 60 * 1000; @@ -51,7 +48,7 @@ after(() => { } catch { /* ignore */ } - if (dataDir) rmSync(dataDir, { recursive: true, force: true }); + if (dataDir) rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); describe("typedDecay — pure predicates", () => { @@ -113,7 +110,10 @@ describe("typedDecay — env config", () => { const cfg = resolveTypedDecayConfig({} as NodeJS.ProcessEnv); assert.equal(cfg.enabled, false); assert.equal(cfg.accessImmunityThreshold, DEFAULT_ACCESS_IMMUNITY_THRESHOLD); - assert.equal(cfg.ttlDaysByType[MemoryType.EPISODIC], DEFAULT_TTL_DAYS_BY_TYPE[MemoryType.EPISODIC]); + assert.equal( + cfg.ttlDaysByType[MemoryType.EPISODIC], + DEFAULT_TTL_DAYS_BY_TYPE[MemoryType.EPISODIC] + ); }); it("MEMORY_TYPED_DECAY_EPISODIC_DAYS=0 makes episodic immune too", () => { diff --git a/tests/unit/merge-train-plan.test.ts b/tests/unit/merge-train-plan.test.ts index f3b795452180..cb5355831363 100644 --- a/tests/unit/merge-train-plan.test.ts +++ b/tests/unit/merge-train-plan.test.ts @@ -121,7 +121,7 @@ test("--plan shell-quotes a hostile base before the gate command is evaluated", await assert.rejects(access(marker), { code: "ENOENT" }); } } finally { - await rm(tempDir, { recursive: true, force: true }); + await rm(tempDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/messages-count-tokens-route.test.ts b/tests/unit/messages-count-tokens-route.test.ts index be78194349aa..56b31464cefb 100644 --- a/tests/unit/messages-count-tokens-route.test.ts +++ b/tests/unit/messages-count-tokens-route.test.ts @@ -34,7 +34,7 @@ type CountTokensErrorResponse = { async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -57,7 +57,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("messages/count_tokens uses real provider count when Claude-compatible upstream supports it", async () => { diff --git a/tests/unit/microsoft-designer-web-image-handler-block.test.ts b/tests/unit/microsoft-designer-web-image-handler-block.test.ts index 39fb45d6dc6f..66ff9734c26a 100644 --- a/tests/unit/microsoft-designer-web-image-handler-block.test.ts +++ b/tests/unit/microsoft-designer-web-image-handler-block.test.ts @@ -11,7 +11,7 @@ process.env.DISABLE_SQLITE_AUTO_BACKUP = "true"; const { handleImageGeneration } = await import("../../open-sse/handlers/imageGeneration.ts"); test.after(() => { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("image handler blocks exact retired providers before any upstream fetch", async () => { diff --git a/tests/unit/microsoft-designer-web-model-routing.test.ts b/tests/unit/microsoft-designer-web-model-routing.test.ts index 5abaf9a7f734..56c4e1f1b849 100644 --- a/tests/unit/microsoft-designer-web-model-routing.test.ts +++ b/tests/unit/microsoft-designer-web-model-routing.test.ts @@ -21,7 +21,7 @@ const { createProviderNodeSchema, updateProviderNodeSchema } = async function resetStorage(): Promise { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -48,7 +48,7 @@ function assertRetiredError(error: unknown): boolean { test.beforeEach(resetStorage); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("retired Designer IDs remain reserved after leaving the live provider registry", () => { diff --git a/tests/unit/microsoft-designer-web-runtime-block.test.ts b/tests/unit/microsoft-designer-web-runtime-block.test.ts index c71662e548a0..3c6480eebf36 100644 --- a/tests/unit/microsoft-designer-web-runtime-block.test.ts +++ b/tests/unit/microsoft-designer-web-runtime-block.test.ts @@ -18,14 +18,14 @@ const API_KEY_ID = "designer-retirement-managed-key"; async function resetStorage(): Promise { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.beforeEach(resetStorage); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("creating a retired Microsoft Designer connection reports its persisted tombstone", async () => { diff --git a/tests/unit/migration-135-numbering-collision.test.ts b/tests/unit/migration-135-numbering-collision.test.ts index 1a6474f45072..d8fd6b0e6f46 100644 --- a/tests/unit/migration-135-numbering-collision.test.ts +++ b/tests/unit/migration-135-numbering-collision.test.ts @@ -34,7 +34,7 @@ before(async () => { after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (originalDataDir === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = originalDataDir; }); diff --git a/tests/unit/migration-159-remove-mimocode-provider.test.ts b/tests/unit/migration-159-remove-mimocode-provider.test.ts index ce62675fde3f..970c70ef18a5 100644 --- a/tests/unit/migration-159-remove-mimocode-provider.test.ts +++ b/tests/unit/migration-159-remove-mimocode-provider.test.ts @@ -11,7 +11,7 @@ const core = await import("../../src/lib/db/core.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("migration 159 removes stale MiMoCode provider state and is idempotent", () => { diff --git a/tests/unit/migration-165-retire-felo-web.test.ts b/tests/unit/migration-165-retire-felo-web.test.ts index 94fe43f9286e..ca0c6e6f6ec5 100644 --- a/tests/unit/migration-165-retire-felo-web.test.ts +++ b/tests/unit/migration-165-retire-felo-web.test.ts @@ -35,7 +35,7 @@ type LeaseState = { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("migration 165 retires every Felo id fail-closed and preserves audit history", async () => { diff --git a/tests/unit/migration-166-retire-gpl-derived-providers.test.ts b/tests/unit/migration-166-retire-gpl-derived-providers.test.ts index 88d23ad7da37..679d187e5900 100644 --- a/tests/unit/migration-166-retire-gpl-derived-providers.test.ts +++ b/tests/unit/migration-166-retire-gpl-derived-providers.test.ts @@ -14,7 +14,7 @@ const RETIRED_PROVIDER_IDS = ["raycast", "rc", "hailuo-web"] as const; test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("migration 166 disables GPL-derived connections fail-closed and preserves audit history", async () => { diff --git a/tests/unit/migration-167-retire-qwen-web.test.ts b/tests/unit/migration-167-retire-qwen-web.test.ts index 4007f9de8069..c8f3cd6fccd3 100644 --- a/tests/unit/migration-167-retire-qwen-web.test.ts +++ b/tests/unit/migration-167-retire-qwen-web.test.ts @@ -67,7 +67,7 @@ type LeaseState = { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("migration 167 retires every Qwen Web id fail-closed and preserves audit history", async () => { diff --git a/tests/unit/migration-168-retire-chatgpt-web.test.ts b/tests/unit/migration-168-retire-chatgpt-web.test.ts index cb61510363bc..cc229eb48c5e 100644 --- a/tests/unit/migration-168-retire-chatgpt-web.test.ts +++ b/tests/unit/migration-168-retire-chatgpt-web.test.ts @@ -35,7 +35,7 @@ type LeaseState = { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("migration 168 retires every common ChatGPT Web id fail-closed and preserves audit history", async () => { diff --git a/tests/unit/minimax-m3-maxtokens.test.ts b/tests/unit/minimax-m3-maxtokens.test.ts index 93a63d9b40c8..f0b5c68a170e 100644 --- a/tests/unit/minimax-m3-maxtokens.test.ts +++ b/tests/unit/minimax-m3-maxtokens.test.ts @@ -31,17 +31,14 @@ const { getModelSpec } = await import("../../src/shared/constants/modelSpecs.ts" test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); const DEFAULT_CAP = 8192; test("#3141 MiniMax-M3 max_tokens is not capped to the 8192 default", () => { const cap = modelCapabilities.capMaxOutputTokens({ provider: "minimax", model: "MiniMax-M3" }); - assert.ok( - cap > DEFAULT_CAP, - `expected MiniMax-M3 maxOutputTokens > ${DEFAULT_CAP}, got ${cap}` - ); + assert.ok(cap > DEFAULT_CAP, `expected MiniMax-M3 maxOutputTokens > ${DEFAULT_CAP}, got ${cap}`); }); test("#3141 MiniMaxAI/MiniMax-M3 (prefixed id) resolves above the 8192 default", () => { diff --git a/tests/unit/mitm-cert-install-mode-9442.test.ts b/tests/unit/mitm-cert-install-mode-9442.test.ts index 4128719c5cde..dd415ce67f9b 100644 --- a/tests/unit/mitm-cert-install-mode-9442.test.ts +++ b/tests/unit/mitm-cert-install-mode-9442.test.ts @@ -80,7 +80,7 @@ test.after(() => { else process.env.OMNIROUTE_NO_SUDO = originalNoSudo; if (originalSkipSystemTrust === undefined) delete process.env.OMNIROUTE_SKIP_SYSTEM_TRUST; else process.env.OMNIROUTE_SKIP_SYSTEM_TRUST = originalSkipSystemTrust; - fs.rmSync(tmpRoot, { recursive: true, force: true }); + fs.rmSync(tmpRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function resetCaptured(): void { diff --git a/tests/unit/mitm-cert-migration-6684.test.ts b/tests/unit/mitm-cert-migration-6684.test.ts index 111eef536911..415b86884ab7 100644 --- a/tests/unit/mitm-cert-migration-6684.test.ts +++ b/tests/unit/mitm-cert-migration-6684.test.ts @@ -26,7 +26,7 @@ test("decideCertMigration: existing legacy leaf, no CA pair, flag off → stay o touch(path.join(certDir, "server.key")); assert.equal(decideCertMigration(certDir, false), "use-legacy-leaf"); } finally { - fs.rmSync(certDir, { recursive: true, force: true }); + fs.rmSync(certDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -35,7 +35,7 @@ test("decideCertMigration: no legacy leaf and no CA pair (fresh install) → use try { assert.equal(decideCertMigration(certDir, false), "use-root-ca"); } finally { - fs.rmSync(certDir, { recursive: true, force: true }); + fs.rmSync(certDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -46,7 +46,7 @@ test("decideCertMigration: legacy leaf present but explicit opt-in flag on → u touch(path.join(certDir, "server.key")); assert.equal(decideCertMigration(certDir, true), "use-root-ca"); } finally { - fs.rmSync(certDir, { recursive: true, force: true }); + fs.rmSync(certDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -59,7 +59,7 @@ test("decideCertMigration: CA pair already persisted → use root CA even withou touch(path.join(certDir, "ca.key")); assert.equal(decideCertMigration(certDir, false), "use-root-ca"); } finally { - fs.rmSync(certDir, { recursive: true, force: true }); + fs.rmSync(certDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -69,6 +69,6 @@ test("decideCertMigration: partial legacy pair (only server.crt) is treated as n touch(path.join(certDir, "server.crt")); assert.equal(decideCertMigration(certDir, false), "use-root-ca"); } finally { - fs.rmSync(certDir, { recursive: true, force: true }); + fs.rmSync(certDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/mitm-hosts-cleanup-on-exit.test.ts b/tests/unit/mitm-hosts-cleanup-on-exit.test.ts index f39ac8857486..988c9810b243 100644 --- a/tests/unit/mitm-hosts-cleanup-on-exit.test.ts +++ b/tests/unit/mitm-hosts-cleanup-on-exit.test.ts @@ -30,7 +30,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: unknown) { @@ -52,7 +52,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("handleExitCleanup: with a cached sudo password, best-effort reverts managed /etc/hosts entries", async () => { @@ -99,7 +99,11 @@ test("handleExitCleanup: with a cached sudo password, best-effort reverts manage test("handleExitCleanup: with NO cached password, falls back to orphaned-state flag and skips DNS removal", async () => { manager.clearCachedPassword(); - assert.equal(manager.getCachedPassword(), null, "precondition: no password cached in this session"); + assert.equal( + manager.getCachedPassword(), + null, + "precondition: no password cached in this session" + ); let removeDNSEntryCalled = false; let removeDNSEntriesCalled = false; diff --git a/tests/unit/mitm-manager-bypass-json.test.ts b/tests/unit/mitm-manager-bypass-json.test.ts index 47dca8a683de..9be2e2973677 100644 --- a/tests/unit/mitm-manager-bypass-json.test.ts +++ b/tests/unit/mitm-manager-bypass-json.test.ts @@ -13,9 +13,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -const TEST_DATA_DIR = fs.mkdtempSync( - path.join(os.tmpdir(), "omniroute-mitm-bypass-json-") -); +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-mitm-bypass-json-")); process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); @@ -27,7 +25,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: unknown) { @@ -48,7 +46,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("writeBypassJson — creates mitm/ dir and writes JSON file", () => { @@ -74,20 +72,12 @@ test("writeBypassJson — pulls from DB when no patterns argument passed", () => manager.writeBypassJson(); const file = path.join(TEST_DATA_DIR, "mitm", "bypass.json"); const payload = JSON.parse(fs.readFileSync(file, "utf-8")); - assert.deepEqual( - payload.patterns.sort(), - ["*.from-db.example.com", "literal.com"].sort() - ); + assert.deepEqual(payload.patterns.sort(), ["*.from-db.example.com", "literal.com"].sort()); }); test("writeBypassJson — does NOT write default patterns (those live in server.cjs)", () => { // Seed defaults via the DB module — these should NOT appear in the JSON. - bypassDb.seedDefaultBypassPatterns([ - "*.bank.test", - "*.gov.test", - "okta.com", - "auth0.com", - ]); + bypassDb.seedDefaultBypassPatterns(["*.bank.test", "*.gov.test", "okta.com", "auth0.com"]); manager.writeBypassJson(); const file = path.join(TEST_DATA_DIR, "mitm", "bypass.json"); const payload = JSON.parse(fs.readFileSync(file, "utf-8")); diff --git a/tests/unit/mitm-manager-cleanup-symmetry.test.ts b/tests/unit/mitm-manager-cleanup-symmetry.test.ts index 1175da20c454..87423d3221f3 100644 --- a/tests/unit/mitm-manager-cleanup-symmetry.test.ts +++ b/tests/unit/mitm-manager-cleanup-symmetry.test.ts @@ -13,9 +13,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -const TEST_DATA_DIR = fs.mkdtempSync( - path.join(os.tmpdir(), "omniroute-mitm-cleanup-symmetry-") -); +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-mitm-cleanup-symmetry-")); process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); @@ -28,7 +26,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: unknown) { @@ -49,7 +47,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("collectManagedHosts includes every host of every agent target", () => { @@ -66,11 +64,7 @@ test("collectManagedHosts includes every host of every agent target", () => { test("collectManagedHosts returns a de-duplicated list", () => { const list = manager.collectManagedHosts(); - assert.equal( - list.length, - new Set(list).size, - "collectManagedHosts must not return duplicates" - ); + assert.equal(list.length, new Set(list).size, "collectManagedHosts must not return duplicates"); }); test("collectManagedHosts includes custom hosts persisted in the DB", () => { diff --git a/tests/unit/mitm-manager-repair.test.ts b/tests/unit/mitm-manager-repair.test.ts index 4a72a7f1f44c..a4cd075b6cf8 100644 --- a/tests/unit/mitm-manager-repair.test.ts +++ b/tests/unit/mitm-manager-repair.test.ts @@ -14,9 +14,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -const TEST_DATA_DIR = fs.mkdtempSync( - path.join(os.tmpdir(), "omniroute-mitm-repair-") -); +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-mitm-repair-")); process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); @@ -27,7 +25,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: unknown) { @@ -48,16 +46,13 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("buildRepairPlan enumerates DNS hosts and the CA + proxy teardown steps", () => { const plan = manager.buildRepairPlan(); assert.ok(Array.isArray(plan.dnsHostsToRemove), "plan.dnsHostsToRemove must be an array"); - assert.ok( - plan.dnsHostsToRemove.length > 0, - "must remove at least the agent target hosts" - ); + assert.ok(plan.dnsHostsToRemove.length > 0, "must remove at least the agent target hosts"); assert.equal(plan.removeCert, true, "repair must include CA removal"); assert.equal(plan.revertSystemProxy, true, "repair must attempt system-proxy revert"); }); diff --git a/tests/unit/mitm-privileged-steps-sudo-gate.test.ts b/tests/unit/mitm-privileged-steps-sudo-gate.test.ts index 30701c7ad3b7..7c1f5c07bbf1 100644 --- a/tests/unit/mitm-privileged-steps-sudo-gate.test.ts +++ b/tests/unit/mitm-privileged-steps-sudo-gate.test.ts @@ -7,10 +7,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { EventEmitter } from "node:events"; -import { - canRunPrivilegedMitmSteps, - isMitmSudoPasswordRequired, -} from "../../src/mitm/sudoGate.ts"; +import { canRunPrivilegedMitmSteps, isMitmSudoPasswordRequired } from "../../src/mitm/sudoGate.ts"; const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-mitm-sudo-gate-")); process.env.DATA_DIR = TEST_DATA_DIR; @@ -20,7 +17,7 @@ const manager = await import("../../src/mitm/manager.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("canRunPrivilegedMitmSteps is false when isMitmSudoPasswordRequired is true", () => { @@ -53,7 +50,10 @@ test("stopMitm skips DNS teardown without sudo password but still kills server ( return true; }; - manager.__setServerProcessForTest(fakeProc as unknown as import("child_process").ChildProcess, 4242); + manager.__setServerProcessForTest( + fakeProc as unknown as import("child_process").ChildProcess, + 4242 + ); await manager.stopMitm("", { removeDNSEntry: async () => { @@ -70,5 +70,8 @@ test("stopMitm skips DNS teardown without sudo password but still kills server ( 0, "must not invoke DNS teardown with empty sudo password" ); - assert.ok(events.some((event) => event.startsWith("kill:")), "server process must still be stopped"); + assert.ok( + events.some((event) => event.startsWith("kill:")), + "server process must still be stopped" + ); }); diff --git a/tests/unit/mitm-root-ca-persistence-6684.test.ts b/tests/unit/mitm-root-ca-persistence-6684.test.ts index 63d08a91c302..470ac1dd74b6 100644 --- a/tests/unit/mitm-root-ca-persistence-6684.test.ts +++ b/tests/unit/mitm-root-ca-persistence-6684.test.ts @@ -25,7 +25,7 @@ test("loadOrCreateMitmCa: first call with an empty dir generates and persists a assert.equal(fs.existsSync(path.join(certDir, "ca.key")), true); assert.equal(fs.existsSync(path.join(certDir, "ca.crt")), true); } finally { - fs.rmSync(certDir, { recursive: true, force: true }); + fs.rmSync(certDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -37,20 +37,24 @@ test("loadOrCreateMitmCa: a second call loads the same CA instead of regeneratin assert.equal(second.key, first.key); assert.equal(second.cert, first.cert); } finally { - fs.rmSync(certDir, { recursive: true, force: true }); + fs.rmSync(certDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); -test("loadOrCreateMitmCa: the written CA private key file mode is 0o600", { skip: process.platform === "win32" }, async () => { - const certDir = tmpCertDir(); - try { - const ca = await loadOrCreateMitmCa(certDir); - const mode = fs.statSync(ca.keyPath).mode & 0o777; - assert.equal(mode, 0o600); - } finally { - fs.rmSync(certDir, { recursive: true, force: true }); +test( + "loadOrCreateMitmCa: the written CA private key file mode is 0o600", + { skip: process.platform === "win32" }, + async () => { + const certDir = tmpCertDir(); + try { + const ca = await loadOrCreateMitmCa(certDir); + const mode = fs.statSync(ca.keyPath).mode & 0o777; + assert.equal(mode, 0o600); + } finally { + fs.rmSync(certDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + } } -}); +); test("loadOrCreateMitmCa: the CA cert carries CA basicConstraints (matches generateMitmCa)", async () => { const certDir = tmpCertDir(); @@ -60,6 +64,6 @@ test("loadOrCreateMitmCa: the CA cert carries CA basicConstraints (matches gener const cert = new X509Certificate(ca.cert); assert.equal(cert.ca, true); } finally { - fs.rmSync(certDir, { recursive: true, force: true }); + fs.rmSync(certDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/mitm-start-guard.test.ts b/tests/unit/mitm-start-guard.test.ts index 91eb57233621..b298ac51f1a1 100644 --- a/tests/unit/mitm-start-guard.test.ts +++ b/tests/unit/mitm-start-guard.test.ts @@ -44,7 +44,7 @@ const manager = await import("../../src/mitm/manager.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // Belt-and-braces: never leave the module-level lock held across tests. diff --git a/tests/unit/mitm-stop-dns-before-kill-1809.test.ts b/tests/unit/mitm-stop-dns-before-kill-1809.test.ts index 3dd92dcaad0a..66de0779fb93 100644 --- a/tests/unit/mitm-stop-dns-before-kill-1809.test.ts +++ b/tests/unit/mitm-stop-dns-before-kill-1809.test.ts @@ -32,7 +32,7 @@ const manager = await import("../../src/mitm/manager.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("stopMitm removes DNS entries before killing the MITM server process (#1809)", async () => { @@ -50,7 +50,10 @@ test("stopMitm removes DNS entries before killing the MITM server process (#1809 return true; }; - manager.__setServerProcessForTest(fakeProc as unknown as import("child_process").ChildProcess, 4242); + manager.__setServerProcessForTest( + fakeProc as unknown as import("child_process").ChildProcess, + 4242 + ); const removeDNSEntry = async () => { events.push("removeDNSEntry"); @@ -67,9 +70,7 @@ test("stopMitm removes DNS entries before killing the MITM server process (#1809 }); const firstKillIndex = events.findIndex((e) => e.startsWith("kill:")); - const firstDnsIndex = events.findIndex( - (e) => e === "removeDNSEntry" || e === "removeDNSEntries" - ); + const firstDnsIndex = events.findIndex((e) => e === "removeDNSEntry" || e === "removeDNSEntries"); assert.ok(firstKillIndex !== -1, "server process kill was never invoked"); assert.ok(firstDnsIndex !== -1, "DNS removal was never invoked"); diff --git a/tests/unit/mitm-upstream-ca-wiring.test.ts b/tests/unit/mitm-upstream-ca-wiring.test.ts index a98c3688a694..3a5fadeec679 100644 --- a/tests/unit/mitm-upstream-ca-wiring.test.ts +++ b/tests/unit/mitm-upstream-ca-wiring.test.ts @@ -25,9 +25,7 @@ import os from "node:os"; import path from "node:path"; // ── test isolation: dedicated DATA_DIR ──────────────────────────────────────── -const TEST_DATA_DIR = fs.mkdtempSync( - path.join(os.tmpdir(), "omniroute-mitm-upstream-ca-wiring-") -); +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-mitm-upstream-ca-wiring-")); process.env.DATA_DIR = TEST_DATA_DIR; // Ensure the mitm subdir exists for CA path file writes. @@ -55,7 +53,11 @@ function readStoredCaPath(): string | null { } function clearStoredCaPath(): void { - try { fs.unlinkSync(CA_PATH_FILE); } catch { /* ignore */ } + try { + fs.unlinkSync(CA_PATH_FILE); + } catch { + /* ignore */ + } } // ── path-selection logic tests ──────────────────────────────────────────────── @@ -113,11 +115,15 @@ test("startMitm CA wiring — configureUpstreamCa called with bad path does not // The function throws — startMitm wraps this in try/catch, so boot continues. assert.ok(threw, "configureUpstreamCa should throw for non-existent path"); assert.ok(!caughtMsg.includes("\n at "), "error message must not include stack trace lines"); - assert.ok(caughtMsg.includes("AGENTBRIDGE_UPSTREAM_CA_CERT"), "error message should include env var label"); + assert.ok( + caughtMsg.includes("AGENTBRIDGE_UPSTREAM_CA_CERT"), + "error message should include env var label" + ); }); test("startMitm CA wiring — configureUpstreamCa no-op for undefined path", async () => { - const { configureUpstreamCa: configureUpstreamCaNoop } = await import("../../src/mitm/upstreamTrust.ts"); + const { configureUpstreamCa: configureUpstreamCaNoop } = + await import("../../src/mitm/upstreamTrust.ts"); // undefined / empty should never load undici — safe to call in tests. assert.doesNotThrow(() => configureUpstreamCaNoop(undefined)); assert.doesNotThrow(() => configureUpstreamCaNoop("")); @@ -126,9 +132,7 @@ test("startMitm CA wiring — configureUpstreamCa no-op for undefined path", asy // ── POST route wiring tests ─────────────────────────────────────────────────── test("POST upstream-ca route — returns 400 when file does not exist", async () => { - const { POST } = await import( - "../../src/app/api/tools/agent-bridge/upstream-ca/route.ts" - ); + const { POST } = await import("../../src/app/api/tools/agent-bridge/upstream-ca/route.ts"); const badPath = "/definitely/does/not/exist/ca.pem"; const req = new Request("http://localhost/api/tools/agent-bridge/upstream-ca", { @@ -159,9 +163,7 @@ test("POST upstream-ca route — persists path to upstream-ca.path file on valid // was attempted, by checking the CA_PATH_FILE exists after the response. clearStoredCaPath(); - const { POST } = await import( - "../../src/app/api/tools/agent-bridge/upstream-ca/route.ts" - ); + const { POST } = await import("../../src/app/api/tools/agent-bridge/upstream-ca/route.ts"); const req = new Request("http://localhost/api/tools/agent-bridge/upstream-ca", { method: "POST", @@ -172,19 +174,17 @@ test("POST upstream-ca route — persists path to upstream-ca.path file on valid const res = await POST(req); // Either 200 (undici loaded ok) or 400 (undici fails in this test env). + assert.ok(res.status === 200 || res.status === 400, `expected 200 or 400 but got ${res.status}`); + // The file should have been written (persistence step happened). assert.ok( - res.status === 200 || res.status === 400, - `expected 200 or 400 but got ${res.status}` + fs.existsSync(CA_PATH_FILE), + "upstream-ca.path should be written before configureUpstreamCa" ); - // The file should have been written (persistence step happened). - assert.ok(fs.existsSync(CA_PATH_FILE), "upstream-ca.path should be written before configureUpstreamCa"); assert.equal(fs.readFileSync(CA_PATH_FILE, "utf8").trim(), REAL_PEM); }); test("POST upstream-ca route — error response does not leak stack trace when configureUpstreamCa throws", async () => { - const { POST } = await import( - "../../src/app/api/tools/agent-bridge/upstream-ca/route.ts" - ); + const { POST } = await import("../../src/app/api/tools/agent-bridge/upstream-ca/route.ts"); const badPath = "/nonexistent/for/configureUpstreamCa/ca.pem"; const req = new Request("http://localhost/api/tools/agent-bridge/upstream-ca", { @@ -205,7 +205,7 @@ test("POST upstream-ca route — error response does not leak stack trace when c test.after(() => { try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // ignore } diff --git a/tests/unit/modality-bridge-video-runtime-route.test.ts b/tests/unit/modality-bridge-video-runtime-route.test.ts index b210aa7ed598..9077086517d6 100644 --- a/tests/unit/modality-bridge-video-runtime-route.test.ts +++ b/tests/unit/modality-bridge-video-runtime-route.test.ts @@ -25,7 +25,7 @@ async function withLocality(request: Request, locality: "loopback" | "lan"): Pro test.beforeEach(async () => { core.resetDbInstance(); - fs.rmSync(dataDirectory, { force: true, recursive: true }); + fs.rmSync(dataDirectory, { force: true, recursive: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(dataDirectory, { recursive: true }); process.env.INITIAL_PASSWORD = "video-runtime-test-password"; await settings.updateSettings({ requireLogin: true, password: "" }); @@ -33,7 +33,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(dataDirectory, { force: true, recursive: true }); + fs.rmSync(dataDirectory, { force: true, recursive: true, maxRetries: 5, retryDelay: 100 }); if (originalDataDirectory === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = originalDataDirectory; if (originalInitialPassword === undefined) delete process.env.INITIAL_PASSWORD; diff --git a/tests/unit/model-alias-route.test.ts b/tests/unit/model-alias-route.test.ts index 5517defc440d..6bf42b4db322 100644 --- a/tests/unit/model-alias-route.test.ts +++ b/tests/unit/model-alias-route.test.ts @@ -20,7 +20,7 @@ const v1Catalog = await import("../../src/app/api/v1/models/catalog.ts"); async function resetStorage() { delete process.env.INITIAL_PASSWORD; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -30,7 +30,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("model alias route resolves a stored alias and emits diagnostics headers", async () => { diff --git a/tests/unit/model-alias-seed-fallback.test.ts b/tests/unit/model-alias-seed-fallback.test.ts index 25f9513fb90b..7838049f7a82 100644 --- a/tests/unit/model-alias-seed-fallback.test.ts +++ b/tests/unit/model-alias-seed-fallback.test.ts @@ -22,7 +22,7 @@ async function withEmptyAliasDb(fn: () => Promise) { resetDbInstance?.(); await fn(); } finally { - fs.rmSync(dataDir, { recursive: true, force: true }); + fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); const { resetDbInstance } = await import("../../src/lib/db/core"); resetDbInstance?.(); if (prevDataDir === undefined) delete process.env.DATA_DIR; diff --git a/tests/unit/model-alias-seed.test.ts b/tests/unit/model-alias-seed.test.ts index 3a56098defc7..e7b77f4f0df3 100644 --- a/tests/unit/model-alias-seed.test.ts +++ b/tests/unit/model-alias-seed.test.ts @@ -15,7 +15,7 @@ const { DEFAULT_MODEL_ALIAS_SEED, seedDefaultModelAliases } = async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -25,7 +25,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("default model alias seed writes missing aliases and is idempotent", async () => { diff --git a/tests/unit/model-aliases-settings-route-selfheal.test.ts b/tests/unit/model-aliases-settings-route-selfheal.test.ts index 30167fdce099..2655a43ecd8c 100644 --- a/tests/unit/model-aliases-settings-route-selfheal.test.ts +++ b/tests/unit/model-aliases-settings-route-selfheal.test.ts @@ -29,7 +29,7 @@ const route = await import("../../src/app/api/settings/model-aliases/route.ts"); async function resetStorage() { delete process.env.INITIAL_PASSWORD; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -39,7 +39,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("GET /api/settings/model-aliases hydrates custom aliases from DB when in-memory state is empty", async () => { diff --git a/tests/unit/model-capabilities-kimi-k3-vision-8250.test.ts b/tests/unit/model-capabilities-kimi-k3-vision-8250.test.ts index 4e4175c47615..95bff213efc3 100644 --- a/tests/unit/model-capabilities-kimi-k3-vision-8250.test.ts +++ b/tests/unit/model-capabilities-kimi-k3-vision-8250.test.ts @@ -53,7 +53,7 @@ function buildCapability(overrides = {}) { function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -113,7 +113,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#8250 kimi-coding-apikey/k3: attachment=false + image modalities → vision=true and fields agree", () => { diff --git a/tests/unit/model-capabilities-mistral-vision-sync-4073.test.ts b/tests/unit/model-capabilities-mistral-vision-sync-4073.test.ts index 2cdc3092aab8..cf4e3130dd68 100644 --- a/tests/unit/model-capabilities-mistral-vision-sync-4073.test.ts +++ b/tests/unit/model-capabilities-mistral-vision-sync-4073.test.ts @@ -57,7 +57,7 @@ function buildCapability(overrides = {}) { function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -98,7 +98,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#4073 mistral/pixtral-12b-latest resolves vision via the synced `-latest` alias (not the heuristic)", () => { @@ -106,7 +106,11 @@ test("#4073 mistral/pixtral-12b-latest resolves vision via the synced `-latest` const latest = modelCapabilities.getResolvedModelCapabilities("mistral/pixtral-12b-latest"); // attachment === true can ONLY come from the synced row keyed `pixtral-12b`. - assert.equal(latest.attachment, true, "synced attachment must resolve via the stripped `-latest` alias"); + assert.equal( + latest.attachment, + true, + "synced attachment must resolve via the stripped `-latest` alias" + ); assert.equal(latest.supportsVision, true); }); @@ -142,7 +146,9 @@ test("#4073 the `-latest` strip never fabricates a match for an unknown id", () // No synced row for `unknown-text-model` (stripped) nor its `-latest` form, and // the heuristic doesn't recognise it → attachment null, vision null. The strip // must not invent a capability out of nothing. - const unknown = modelCapabilities.getResolvedModelCapabilities("mistral/unknown-text-model-latest"); + const unknown = modelCapabilities.getResolvedModelCapabilities( + "mistral/unknown-text-model-latest" + ); assert.equal(unknown.attachment, null); assert.equal(unknown.supportsVision, null); }); diff --git a/tests/unit/model-capabilities-path-shaped-vision-8032.test.ts b/tests/unit/model-capabilities-path-shaped-vision-8032.test.ts index ae53ebef86cb..730e03c3f415 100644 --- a/tests/unit/model-capabilities-path-shaped-vision-8032.test.ts +++ b/tests/unit/model-capabilities-path-shaped-vision-8032.test.ts @@ -43,7 +43,7 @@ function buildCapability(overrides: Record = {}) { function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -53,7 +53,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#8032 cp/cline-pass/kimi-k3: attachment=false empty modalities → vision via leaf/registry", () => { @@ -83,9 +83,7 @@ test("#8032 leaf fallback is vision-only: aihorde/deepseek/deepseek-v4-flash kee // Regression guard from PR review (#8495 / #8212): shared getStaticSpec leaf // lookup previously promoted this live-discovered AI Horde id to the real // DeepSeek V4 Flash supportsTools:true spec. Leaf lookup must stay vision-only. - const caps = modelCapabilities.getResolvedModelCapabilities( - "aihorde/deepseek/deepseek-v4-flash" - ); + const caps = modelCapabilities.getResolvedModelCapabilities("aihorde/deepseek/deepseek-v4-flash"); assert.equal(caps.toolCalling, false); assert.equal(caps.supportsTools, false); assert.equal( diff --git a/tests/unit/model-capabilities-registry.test.ts b/tests/unit/model-capabilities-registry.test.ts index 29054c2f6023..7bb82681db95 100644 --- a/tests/unit/model-capabilities-registry.test.ts +++ b/tests/unit/model-capabilities-registry.test.ts @@ -37,7 +37,7 @@ function buildCapability(overrides = {}) { function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -47,7 +47,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("canonical model capability resolver lets exact synced metadata override global specs", () => { diff --git a/tests/unit/model-capability-overrides.test.ts b/tests/unit/model-capability-overrides.test.ts index 5053ed7520b8..d81a5594ed19 100644 --- a/tests/unit/model-capability-overrides.test.ts +++ b/tests/unit/model-capability-overrides.test.ts @@ -15,14 +15,14 @@ const route = await import("../../src/app/api/model-capability-overrides/route.t beforeEach(() => { coreDb.resetDbInstance(); - fs.rmSync(moduleDataDir, { recursive: true, force: true }); + fs.rmSync(moduleDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(moduleDataDir, { recursive: true }); coreDb.getDbInstance(); }); after(() => { coreDb.resetDbInstance(); - fs.rmSync(moduleDataDir, { recursive: true, force: true }); + fs.rmSync(moduleDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function patchOverride(key: string, value: unknown) { @@ -195,10 +195,7 @@ describe("model capability overrides", () => { it("stores exact reasoning_efforts through the API and preserves native max/ultra", async () => { const before = caps.getResolvedModelCapabilities("codex/gpt-5.6"); - const accepted = await patchOverride( - "reasoning_efforts", - "​ low\r\n, medium, max‍, ultra⁠" - ); + const accepted = await patchOverride("reasoning_efforts", "​ low\r\n, medium, max‍, ultra⁠"); assert.equal(accepted.status, 200); const payload = (await accepted.json()) as { diff --git a/tests/unit/model-capability-resolution-snapshot-9199.test.ts b/tests/unit/model-capability-resolution-snapshot-9199.test.ts index 2e63420b4f12..90088a5d9747 100644 --- a/tests/unit/model-capability-resolution-snapshot-9199.test.ts +++ b/tests/unit/model-capability-resolution-snapshot-9199.test.ts @@ -38,7 +38,7 @@ test.after(() => { } for (const [key, value] of originalContextLengthEnv) process.env[key] = value; try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // best-effort cleanup } @@ -46,7 +46,7 @@ test.after(() => { function seedFixture() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); core.getDbInstance(); @@ -134,7 +134,11 @@ function seedFixture() { assert.equal(aliasCanonical.model, "claude-opus-4-5-20251101"); assert.notEqual(aliasCanonical.model, "claude-4.5-opus"); assert.equal( - capabilityOverrides.setModelCapabilityOverride("github/claude-4.5-opus", "max_output_tokens", 77777), + capabilityOverrides.setModelCapabilityOverride( + "github/claude-4.5-opus", + "max_output_tokens", + 77777 + ), true ); assert.equal( @@ -223,7 +227,7 @@ function assertOrdinarySnapshotParity( test("#9199 bulk capability rows treat prototype-shaped keys as data", () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); core.getDbInstance(); diff --git a/tests/unit/model-catalog-cache-swr-8728.test.ts b/tests/unit/model-catalog-cache-swr-8728.test.ts index 5e5265b804fa..464986347f67 100644 --- a/tests/unit/model-catalog-cache-swr-8728.test.ts +++ b/tests/unit/model-catalog-cache-swr-8728.test.ts @@ -53,7 +53,7 @@ test.beforeEach(() => { }); test.after(() => { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("the SWR window is a bounded constant, not an unbounded accessor", () => { diff --git a/tests/unit/model-catalog-policy-invalidation-8728.test.ts b/tests/unit/model-catalog-policy-invalidation-8728.test.ts index e23b672e97fb..144f350e2e13 100644 --- a/tests/unit/model-catalog-policy-invalidation-8728.test.ts +++ b/tests/unit/model-catalog-policy-invalidation-8728.test.ts @@ -5,7 +5,7 @@ import path from "node:path"; import test from "node:test"; const TEST_DATA_DIR = fs.mkdtempSync( - path.join(os.tmpdir(), "omniroute-model-catalog-policy-8728-"), + path.join(os.tmpdir(), "omniroute-model-catalog-policy-8728-") ); process.env.DATA_DIR = TEST_DATA_DIR; process.env.DISABLE_SQLITE_AUTO_BACKUP = "true"; @@ -26,7 +26,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: unknown) { @@ -53,7 +53,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); apiKeys.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("updateApiKeyPermissions increments only on catalog-affecting fields", async () => { @@ -108,7 +108,7 @@ test("isModelAllowedForKey cache recomputes after custom model visibility change "Catalog cache repro", "manual", "chat-completions", - ["chat"], + ["chat"] ); assert.equal(await apiKeys.isModelAllowedForKey(key.key, modelId), true); diff --git a/tests/unit/model-catalog-runtime-invalidation.test.ts b/tests/unit/model-catalog-runtime-invalidation.test.ts index c37b9831c48f..4c3abe20990c 100644 --- a/tests/unit/model-catalog-runtime-invalidation.test.ts +++ b/tests/unit/model-catalog-runtime-invalidation.test.ts @@ -25,7 +25,7 @@ const auth = await import("../../src/sse/services/auth.ts"); async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); v1ModelsCatalog.__resetCatalogBuilderRunsForTest(); } @@ -77,7 +77,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("session-affinity bookkeeping preserves the published model catalog", async () => { diff --git a/tests/unit/model-catalog-source-invalidation-8728.test.ts b/tests/unit/model-catalog-source-invalidation-8728.test.ts index 50dfa86efff8..17f73ba27d3f 100644 --- a/tests/unit/model-catalog-source-invalidation-8728.test.ts +++ b/tests/unit/model-catalog-source-invalidation-8728.test.ts @@ -5,7 +5,7 @@ import path from "node:path"; import test from "node:test"; const TEST_DATA_DIR = fs.mkdtempSync( - path.join(os.tmpdir(), "omniroute-model-catalog-sources-8728-"), + path.join(os.tmpdir(), "omniroute-model-catalog-sources-8728-") ); process.env.DATA_DIR = TEST_DATA_DIR; process.env.DISABLE_SQLITE_AUTO_BACKUP = "true"; @@ -22,7 +22,7 @@ const openRouterCatalog = await import("../../src/lib/catalog/openrouterCatalog. function resetStorage() { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -57,7 +57,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); restoreRealFetch(); }); @@ -195,7 +195,7 @@ test("refreshOpenRouterCatalog invalidates only on success", async () => { assert.equal( catalogVersion(), beforeGet, - "ordinary get should not invalidate the model-catalog cache", + "ordinary get should not invalidate the model-catalog cache" ); const beforeRefreshSuccess = catalogVersion(); diff --git a/tests/unit/model-combo-mappings-db.test.ts b/tests/unit/model-combo-mappings-db.test.ts index 9470884eb42d..2ce0b1bb9017 100644 --- a/tests/unit/model-combo-mappings-db.test.ts +++ b/tests/unit/model-combo-mappings-db.test.ts @@ -13,7 +13,7 @@ const mappingsDb = await import("../../src/lib/db/modelComboMappings.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -23,7 +23,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); async function createCombo(name, model, overrides = {}) { diff --git a/tests/unit/model-connid-prefix-normalization-6772.test.ts b/tests/unit/model-connid-prefix-normalization-6772.test.ts index c7b6fca7b43e..88088e9519cb 100644 --- a/tests/unit/model-connid-prefix-normalization-6772.test.ts +++ b/tests/unit/model-connid-prefix-normalization-6772.test.ts @@ -50,7 +50,7 @@ test.before(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#6772 baseline: bare alias form `custpfx6772/vova/gpt-5.5` resolves to the raw model id", async () => { diff --git a/tests/unit/model-context-override-readpath.test.ts b/tests/unit/model-context-override-readpath.test.ts index 9cd6a6ea3ebc..b22e2790063e 100644 --- a/tests/unit/model-context-override-readpath.test.ts +++ b/tests/unit/model-context-override-readpath.test.ts @@ -15,14 +15,14 @@ const caps = await import("../../src/lib/modelCapabilities.ts"); beforeEach(() => { coreDb.resetDbInstance(); - fs.rmSync(moduleDataDir, { recursive: true, force: true }); + fs.rmSync(moduleDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(moduleDataDir, { recursive: true }); coreDb.getDbInstance(); }); after(() => { coreDb.resetDbInstance(); - fs.rmSync(moduleDataDir, { recursive: true, force: true }); + fs.rmSync(moduleDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); describe("getModelContextLimit override precedence (5004)", () => { diff --git a/tests/unit/model-cooldowns-route-auth.test.ts b/tests/unit/model-cooldowns-route-auth.test.ts index 77ebf02ea4a7..c648c0346551 100644 --- a/tests/unit/model-cooldowns-route-auth.test.ts +++ b/tests/unit/model-cooldowns-route-auth.test.ts @@ -23,7 +23,7 @@ const { clearModelLock, lockModel } = accountFallback; async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -41,7 +41,7 @@ test.beforeEach(async () => { test.after(async () => { clearModelLock("cooldown-auth-provider", "cooldown-auth-conn", "cooldown-auth-model"); await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; diff --git a/tests/unit/model-intelligence-db.test.ts b/tests/unit/model-intelligence-db.test.ts index ba15f9f805c5..71fc90cee374 100644 --- a/tests/unit/model-intelligence-db.test.ts +++ b/tests/unit/model-intelligence-db.test.ts @@ -11,9 +11,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -const TEST_DATA_DIR = fs.mkdtempSync( - path.join(os.tmpdir(), "omniroute-mi-test-"), -); +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-mi-test-")); process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); @@ -23,9 +21,11 @@ function resetStorage(): void { core.resetDbInstance(); try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } - } catch { /* EBUSY — ignore */ } + } catch { + /* EBUSY — ignore */ + } fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -38,13 +38,13 @@ function insertEntry( eloRaw?: number | null; confidence?: string | null; expiresAt?: string | null; - } = {}, + } = {} ): void { const db = core.getDbInstance(); db.prepare( `INSERT OR REPLACE INTO model_intelligence (model, source, category, score, elo_raw, confidence, synced_at, expires_at) - VALUES (?, ?, ?, ?, ?, ?, datetime('now'), ?)`, + VALUES (?, ?, ?, ?, ?, ?, datetime('now'), ?)` ).run( model, source, @@ -52,14 +52,16 @@ function insertEntry( score, opts.eloRaw ?? null, opts.confidence ?? null, - opts.expiresAt ?? null, + opts.expiresAt ?? null ); } // ─── Tests ─────────────────────────────────────────────── describe("upsertModelIntelligence", () => { - beforeEach(() => { resetStorage(); }); + beforeEach(() => { + resetStorage(); + }); it("inserts a new entry", () => { mi.upsertModelIntelligence({ @@ -90,7 +92,7 @@ describe("upsertModelIntelligence", () => { model: "gpt-4o", source: "arena_elo", category: "coding", - score: 0.90, + score: 0.9, eloRaw: 1400, confidence: "high", expiresAt: null, @@ -98,13 +100,15 @@ describe("upsertModelIntelligence", () => { const entry = mi.getModelIntelligenceBySource("gpt-4o", "arena_elo", "coding"); assert.ok(entry); - assert.strictEqual(entry.score, 0.90); + assert.strictEqual(entry.score, 0.9); assert.strictEqual(entry.eloRaw, 1400); }); }); describe("getModelIntelligence", () => { - beforeEach(() => { resetStorage(); }); + beforeEach(() => { + resetStorage(); + }); it("returns user_override when all three sources exist (highest priority)", () => { insertEntry("claude-sonnet", "models_dev_tier", "coding", 0.75); @@ -119,7 +123,7 @@ describe("getModelIntelligence", () => { it("returns arena_elo when no user_override exists", () => { insertEntry("gpt-4o", "arena_elo", "coding", 0.87); - insertEntry("gpt-4o", "models_dev_tier", "coding", 0.70); + insertEntry("gpt-4o", "models_dev_tier", "coding", 0.7); const entry = mi.getModelIntelligence("gpt-4o", "coding"); assert.ok(entry); @@ -145,7 +149,7 @@ describe("getModelIntelligence", () => { insertEntry("gemini-pro", "arena_elo", "coding", 0.82, { expiresAt: "2000-01-01T00:00:00Z", }); - insertEntry("gemini-pro", "models_dev_tier", "coding", 0.70); + insertEntry("gemini-pro", "models_dev_tier", "coding", 0.7); const entry = mi.getModelIntelligence("gemini-pro", "coding"); assert.ok(entry); @@ -153,7 +157,7 @@ describe("getModelIntelligence", () => { }); it("returns null when all entries for a model+category are expired", () => { - insertEntry("expired-model", "arena_elo", "coding", 0.80, { + insertEntry("expired-model", "arena_elo", "coding", 0.8, { expiresAt: "2000-01-01T00:00:00Z", }); @@ -162,7 +166,7 @@ describe("getModelIntelligence", () => { }); it("model names require exact match (case-sensitive in DB)", () => { - insertEntry("Claude-Sonnet", "arena_elo", "coding", 0.90); + insertEntry("Claude-Sonnet", "arena_elo", "coding", 0.9); const exact = mi.getModelIntelligence("Claude-Sonnet", "coding"); assert.ok(exact); @@ -173,7 +177,9 @@ describe("getModelIntelligence", () => { }); describe("getModelIntelligenceBySource", () => { - beforeEach(() => { resetStorage(); }); + beforeEach(() => { + resetStorage(); + }); it("returns a specific source entry", () => { insertEntry("claude-sonnet", "arena_elo", "coding", 0.88); @@ -199,7 +205,9 @@ describe("getModelIntelligenceBySource", () => { }); describe("deleteModelIntelligence", () => { - beforeEach(() => { resetStorage(); }); + beforeEach(() => { + resetStorage(); + }); it("deletes an entry and returns true", () => { insertEntry("gpt-4o", "arena_elo", "coding", 0.87); @@ -218,7 +226,9 @@ describe("deleteModelIntelligence", () => { }); describe("deleteExpiredIntelligence", () => { - beforeEach(() => { resetStorage(); }); + beforeEach(() => { + resetStorage(); + }); it("deletes only expired entries leaving valid ones", () => { insertEntry("old-model", "arena_elo", "coding", 0.7, { @@ -263,7 +273,9 @@ describe("deleteExpiredIntelligence", () => { }); describe("listModelIntelligence", () => { - beforeEach(() => { resetStorage(); }); + beforeEach(() => { + resetStorage(); + }); it("lists all entries when no filters provided", () => { insertEntry("model-a", "arena_elo", "coding", 0.8); @@ -312,13 +324,39 @@ describe("listModelIntelligence", () => { }); describe("bulkUpsertModelIntelligence", () => { - beforeEach(() => { resetStorage(); }); + beforeEach(() => { + resetStorage(); + }); it("bulk inserts multiple entries", () => { const count = mi.bulkUpsertModelIntelligence([ - { model: "model-a", source: "arena_elo", category: "coding", score: 0.80, eloRaw: 1300, confidence: "high", expiresAt: "2099-12-31T23:59:59Z" }, - { model: "model-b", source: "arena_elo", category: "coding", score: 0.70, eloRaw: 1200, confidence: "medium", expiresAt: "2099-12-31T23:59:59Z" }, - { model: "model-c", source: "arena_elo", category: "review", score: 0.85, eloRaw: 1350, confidence: "high", expiresAt: "2099-12-31T23:59:59Z" }, + { + model: "model-a", + source: "arena_elo", + category: "coding", + score: 0.8, + eloRaw: 1300, + confidence: "high", + expiresAt: "2099-12-31T23:59:59Z", + }, + { + model: "model-b", + source: "arena_elo", + category: "coding", + score: 0.7, + eloRaw: 1200, + confidence: "medium", + expiresAt: "2099-12-31T23:59:59Z", + }, + { + model: "model-c", + source: "arena_elo", + category: "review", + score: 0.85, + eloRaw: 1350, + confidence: "high", + expiresAt: "2099-12-31T23:59:59Z", + }, ]); assert.strictEqual(count, 3); @@ -332,21 +370,31 @@ describe("bulkUpsertModelIntelligence", () => { }); it("replaces existing entries on conflict (INSERT OR REPLACE)", () => { - insertEntry("model-a", "arena_elo", "coding", 0.70); + insertEntry("model-a", "arena_elo", "coding", 0.7); mi.bulkUpsertModelIntelligence([ - { model: "model-a", source: "arena_elo", category: "coding", score: 0.90, eloRaw: 1450, confidence: "high", expiresAt: null }, + { + model: "model-a", + source: "arena_elo", + category: "coding", + score: 0.9, + eloRaw: 1450, + confidence: "high", + expiresAt: null, + }, ]); const entry = mi.getModelIntelligenceBySource("model-a", "arena_elo", "coding"); assert.ok(entry); - assert.strictEqual(entry.score, 0.90); + assert.strictEqual(entry.score, 0.9); assert.strictEqual(entry.eloRaw, 1450); }); }); describe("getResolvedTaskFitness", () => { - beforeEach(() => { resetStorage(); }); + beforeEach(() => { + resetStorage(); + }); it("returns user_override score when all sources exist", () => { insertEntry("claude-sonnet", "models_dev_tier", "coding", 0.75); @@ -359,7 +407,7 @@ describe("getResolvedTaskFitness", () => { it("returns arena_elo score when no user_override exists", () => { insertEntry("gpt-4o", "arena_elo", "coding", 0.87); - insertEntry("gpt-4o", "models_dev_tier", "coding", 0.70); + insertEntry("gpt-4o", "models_dev_tier", "coding", 0.7); const score = mi.getResolvedTaskFitness("gpt-4o", "coding"); assert.strictEqual(score, 0.87); @@ -381,15 +429,17 @@ describe("getResolvedTaskFitness", () => { insertEntry("gemini-pro", "arena_elo", "coding", 0.82, { expiresAt: "2000-01-01T00:00:00Z", }); - insertEntry("gemini-pro", "models_dev_tier", "coding", 0.70); + insertEntry("gemini-pro", "models_dev_tier", "coding", 0.7); const score = mi.getResolvedTaskFitness("gemini-pro", "coding"); - assert.strictEqual(score, 0.70); + assert.strictEqual(score, 0.7); }); }); describe("edge cases", () => { - beforeEach(() => { resetStorage(); }); + beforeEach(() => { + resetStorage(); + }); it("score values are stored and retrieved with float precision", () => { mi.upsertModelIntelligence({ diff --git a/tests/unit/model-latency-stats-route.test.ts b/tests/unit/model-latency-stats-route.test.ts index 15cbc0650af6..6d444ca7ac73 100644 --- a/tests/unit/model-latency-stats-route.test.ts +++ b/tests/unit/model-latency-stats-route.test.ts @@ -20,7 +20,7 @@ const route = await import("../../src/app/api/usage/model-latency-stats/route.ts async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -55,7 +55,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; diff --git a/tests/unit/model-lifecycle-integration.test.ts b/tests/unit/model-lifecycle-integration.test.ts index dd2426d3b174..d62923d90ee2 100644 --- a/tests/unit/model-lifecycle-integration.test.ts +++ b/tests/unit/model-lifecycle-integration.test.ts @@ -22,7 +22,7 @@ async function resetStorage() { globalThis.fetch = originalFetch; core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); v1ModelsCatalog.__resetCatalogBuilderRunsForTest(); } @@ -47,7 +47,7 @@ test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("chatCore rejects a shutdown OpenAI model before an upstream request", async () => { diff --git a/tests/unit/model-lockout-max-cooldown.test.ts b/tests/unit/model-lockout-max-cooldown.test.ts index a5912985f6e7..064bfa703495 100644 --- a/tests/unit/model-lockout-max-cooldown.test.ts +++ b/tests/unit/model-lockout-max-cooldown.test.ts @@ -48,7 +48,7 @@ async function seedConnection(provider: string, overrides: any = {}): Promise { clearAllModelLockouts(); try { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch {} }); diff --git a/tests/unit/model-metadata-registry.test.ts b/tests/unit/model-metadata-registry.test.ts index a85673b62585..707202f8ddd0 100644 --- a/tests/unit/model-metadata-registry.test.ts +++ b/tests/unit/model-metadata-registry.test.ts @@ -14,7 +14,7 @@ const registry = await import("../../src/lib/modelMetadataRegistry.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -24,7 +24,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("canonical model metadata merges static and synced capabilities into one record", async () => { diff --git a/tests/unit/model-output-cap-synced-fallthrough-6714.test.ts b/tests/unit/model-output-cap-synced-fallthrough-6714.test.ts index 378a9914c568..8a4d600409a6 100644 --- a/tests/unit/model-output-cap-synced-fallthrough-6714.test.ts +++ b/tests/unit/model-output-cap-synced-fallthrough-6714.test.ts @@ -78,7 +78,7 @@ function buildCapability(overrides: Record = {}) { function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); // The synced-capabilities module keeps an in-memory cache across DB resets // (`cachedCapabilitiesLoadedAll`) — clear it too so each test starts from a @@ -92,7 +92,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#6714 synced row present but limit_output missing falls through to the registry output cap", () => { diff --git a/tests/unit/model-overrides-provider-prefix-9557.test.ts b/tests/unit/model-overrides-provider-prefix-9557.test.ts index a78bb6b7ad5c..e6f2b506ffe7 100644 --- a/tests/unit/model-overrides-provider-prefix-9557.test.ts +++ b/tests/unit/model-overrides-provider-prefix-9557.test.ts @@ -22,14 +22,14 @@ const sseModel = await import("../../src/sse/services/model.ts"); beforeEach(() => { coreDb.resetDbInstance(); - fs.rmSync(moduleDataDir, { recursive: true, force: true }); + fs.rmSync(moduleDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(moduleDataDir, { recursive: true }); coreDb.getDbInstance(); }); after(() => { coreDb.resetDbInstance(); - fs.rmSync(moduleDataDir, { recursive: true, force: true }); + fs.rmSync(moduleDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); const NODE_ID = "openai-compatible-chat-02669115-2545-4896-b003-cb4dac09d441"; diff --git a/tests/unit/model-resolver.test.ts b/tests/unit/model-resolver.test.ts index d7d0d0e739d7..a5b0be4f58cb 100644 --- a/tests/unit/model-resolver.test.ts +++ b/tests/unit/model-resolver.test.ts @@ -38,7 +38,7 @@ test.after(async () => { const { invalidateDbCache } = await import("../../src/lib/db/readCache.ts"); core.resetDbInstance(); invalidateDbCache(); - fs.rmSync(modelResolverDataDir, { recursive: true, force: true }); + fs.rmSync(modelResolverDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (previousDataDir === undefined) { delete process.env.DATA_DIR; } else { @@ -185,8 +185,7 @@ test( ); test("getModelInfoCore routes unprefixed Claude models to Claude Code from settings toggle", async () => { - const previousEnvFlag = - process.env.OMNIROUTE_PREFER_CLAUDE_CODE_FOR_UNPREFIXED_CLAUDE_MODELS; + const previousEnvFlag = process.env.OMNIROUTE_PREFER_CLAUDE_CODE_FOR_UNPREFIXED_CLAUDE_MODELS; delete process.env.OMNIROUTE_PREFER_CLAUDE_CODE_FOR_UNPREFIXED_CLAUDE_MODELS; try { @@ -216,8 +215,7 @@ test("getModelInfoCore routes unprefixed Claude models to Claude Code from setti }); test("getModelInfoCore lets settings toggle disable Claude Code preference", async () => { - const previousEnvFlag = - process.env.OMNIROUTE_PREFER_CLAUDE_CODE_FOR_UNPREFIXED_CLAUDE_MODELS; + const previousEnvFlag = process.env.OMNIROUTE_PREFER_CLAUDE_CODE_FOR_UNPREFIXED_CLAUDE_MODELS; process.env.OMNIROUTE_PREFER_CLAUDE_CODE_FOR_UNPREFIXED_CLAUDE_MODELS = "true"; try { diff --git a/tests/unit/model-sync-custom-preservation.test.ts b/tests/unit/model-sync-custom-preservation.test.ts index c8f8fc7e6762..759c32961434 100644 --- a/tests/unit/model-sync-custom-preservation.test.ts +++ b/tests/unit/model-sync-custom-preservation.test.ts @@ -21,7 +21,7 @@ const originalFetch = globalThis.fetch; test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("model sync preserves response-only custom models during discovery", async () => { diff --git a/tests/unit/model-sync-route.test.ts b/tests/unit/model-sync-route.test.ts index ece60f730d31..73f199a1ec4b 100644 --- a/tests/unit/model-sync-route.test.ts +++ b/tests/unit/model-sync-route.test.ts @@ -34,7 +34,7 @@ async function resetStorage() { modelSyncRoute.__resetLoopbackReadinessForTests(); core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -42,7 +42,7 @@ test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); async function enableAuth() { diff --git a/tests/unit/model-sync-scheduler.test.ts b/tests/unit/model-sync-scheduler.test.ts index 449c91475bf8..c8a6e2bfd477 100644 --- a/tests/unit/model-sync-scheduler.test.ts +++ b/tests/unit/model-sync-scheduler.test.ts @@ -18,7 +18,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: any) { @@ -107,7 +107,7 @@ test.beforeEach(async () => { test.after(async () => { coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("modelSyncScheduler: internal auth headers validate only for scheduler requests", async () => { diff --git a/tests/unit/model-test-route.test.ts b/tests/unit/model-test-route.test.ts index 1c8efe137c55..5e1c1174e56b 100644 --- a/tests/unit/model-test-route.test.ts +++ b/tests/unit/model-test-route.test.ts @@ -24,7 +24,7 @@ async function resetStorage() { core.resetDbInstance(); delete process.env.INITIAL_PASSWORD; delete process.env.REQUIRE_API_KEY; - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -51,7 +51,7 @@ test.afterEach(() => { test.after(async () => { globalThis.fetch = originalFetch; await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; diff --git a/tests/unit/model-token-limit-catalog.test.ts b/tests/unit/model-token-limit-catalog.test.ts index b456ce42add0..7d36565f0b3b 100644 --- a/tests/unit/model-token-limit-catalog.test.ts +++ b/tests/unit/model-token-limit-catalog.test.ts @@ -20,14 +20,14 @@ const LIMITS = { context: 372000, input: 353400, output: 128000 }; test.beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); catalog.__resetCatalogBuilderRunsForTest(); }); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); async function getModel(target = TARGET) { diff --git a/tests/unit/models-catalog-auto-combos-4164.test.ts b/tests/unit/models-catalog-auto-combos-4164.test.ts index b83fc174a38a..440878c3704c 100644 --- a/tests/unit/models-catalog-auto-combos-4164.test.ts +++ b/tests/unit/models-catalog-auto-combos-4164.test.ts @@ -25,7 +25,7 @@ const builtinCatalog = await import("../../open-sse/services/autoCombo/builtinCa function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -35,7 +35,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#4164 /v1/models advertises every built-in auto/* combo", async () => { @@ -113,7 +113,11 @@ test("#4189 every auto/* entry exposes token limits + baseline capabilities", as `${entry.id} must expose a numeric context_length` ); assert.ok((entry.context_length ?? 0) > 0, `${entry.id} context_length must be positive`); - assert.equal(typeof entry.max_input_tokens, "number", `${entry.id} must expose max_input_tokens`); + assert.equal( + typeof entry.max_input_tokens, + "number", + `${entry.id} must expose max_input_tokens` + ); assert.equal( typeof entry.max_output_tokens, "number", diff --git a/tests/unit/models-catalog-block-auto-5192.test.ts b/tests/unit/models-catalog-block-auto-5192.test.ts index 44de506b6538..256e4930d9b4 100644 --- a/tests/unit/models-catalog-block-auto-5192.test.ts +++ b/tests/unit/models-catalog-block-auto-5192.test.ts @@ -28,7 +28,7 @@ type ModelsResponseBody = { data: Array<{ id: string }> }; async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -47,7 +47,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#5192 baseline: built-in auto/* combos are listed when Auto is not blocked", async () => { diff --git a/tests/unit/models-catalog-combo-metadata.test.ts b/tests/unit/models-catalog-combo-metadata.test.ts index 1ab884f3d33f..c64b46bd1a34 100644 --- a/tests/unit/models-catalog-combo-metadata.test.ts +++ b/tests/unit/models-catalog-combo-metadata.test.ts @@ -19,7 +19,7 @@ const catalog = await import("../../src/app/api/v1/models/catalog.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("single-target combo preserves its direct model metadata", async () => { diff --git a/tests/unit/models-catalog-custom-node-prefix.test.ts b/tests/unit/models-catalog-custom-node-prefix.test.ts index c3684ecab755..8fe177ed599f 100644 --- a/tests/unit/models-catalog-custom-node-prefix.test.ts +++ b/tests/unit/models-catalog-custom-node-prefix.test.ts @@ -24,7 +24,7 @@ const EXPECTED_IDS = [ async function resetStorage(): Promise { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); modelsCatalog.__resetCatalogBuilderRunsForTest(); } @@ -93,7 +93,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("alias mode exposes every custom node model under its configured prefix", async () => { diff --git a/tests/unit/models-catalog-envkey-6406.test.ts b/tests/unit/models-catalog-envkey-6406.test.ts index 824fd8a08144..5127c9d5012e 100644 --- a/tests/unit/models-catalog-envkey-6406.test.ts +++ b/tests/unit/models-catalog-envkey-6406.test.ts @@ -29,7 +29,7 @@ interface ModelsCatalogResponseBody { async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -52,7 +52,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); delete process.env.OMNIROUTE_API_KEY; }); diff --git a/tests/unit/models-catalog-functional-gateway-permissions.test.ts b/tests/unit/models-catalog-functional-gateway-permissions.test.ts index ccdcfb19036c..f7f0aad41c23 100644 --- a/tests/unit/models-catalog-functional-gateway-permissions.test.ts +++ b/tests/unit/models-catalog-functional-gateway-permissions.test.ts @@ -20,7 +20,7 @@ const v1ModelsCatalog = await import("../../src/app/api/v1/models/catalog.ts"); async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); v1ModelsCatalog.__resetCatalogBuilderRunsForTest(); } @@ -65,7 +65,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("v1 models catalog requires independent permission for functional gateway mirrors", async () => { diff --git a/tests/unit/models-catalog-hidden-combo-leaves.test.ts b/tests/unit/models-catalog-hidden-combo-leaves.test.ts index 670ef653b484..44e7e264d07d 100644 --- a/tests/unit/models-catalog-hidden-combo-leaves.test.ts +++ b/tests/unit/models-catalog-hidden-combo-leaves.test.ts @@ -55,7 +55,7 @@ async function getCatalogData(): Promise { test.beforeEach(() => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); v1ModelsCatalog.__resetCatalogBuilderRunsForTest(); }); @@ -64,7 +64,7 @@ test.after(() => { modelsDevSync.saveModelsDevCapabilities({}); core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("v1 models catalog keeps partially hidden combos and derives metadata from visible targets", async () => { diff --git a/tests/unit/models-catalog-hide-paid.test.ts b/tests/unit/models-catalog-hide-paid.test.ts index 4f2f8db67472..8df5176bb039 100644 --- a/tests/unit/models-catalog-hide-paid.test.ts +++ b/tests/unit/models-catalog-hide-paid.test.ts @@ -31,7 +31,7 @@ async function fetchCatalog(): Promise> { test.after(() => { core.resetDbInstance(); try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { /* best-effort */ } diff --git a/tests/unit/models-catalog-low-noise-flag.test.ts b/tests/unit/models-catalog-low-noise-flag.test.ts index f1df7c57028a..d8297ebd6b46 100644 --- a/tests/unit/models-catalog-low-noise-flag.test.ts +++ b/tests/unit/models-catalog-low-noise-flag.test.ts @@ -22,7 +22,7 @@ type ModelsResponseBody = { async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -52,7 +52,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("MODELS_CATALOG_PREFIX_MODE=alias suppresses canonical provider-id prefixes", async () => { diff --git a/tests/unit/models-catalog-route.test.ts b/tests/unit/models-catalog-route.test.ts index 7c2ed395d9c8..e6216899f094 100644 --- a/tests/unit/models-catalog-route.test.ts +++ b/tests/unit/models-catalog-route.test.ts @@ -21,7 +21,7 @@ const v1ModelsCatalog = await import("../../src/app/api/v1/models/catalog.ts"); async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); // #6408 added a 1.5s TTL response cache to getUnifiedModelsResponse keyed only by // (prefix, isCodex client, apiKey) — NOT by DB/settings state. Without clearing it @@ -73,7 +73,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("v1 models catalog requires auth when the route is protected and login is enabled", async () => { diff --git a/tests/unit/models-catalog-static-synced-suppression.test.ts b/tests/unit/models-catalog-static-synced-suppression.test.ts index 288e7c2e3c51..52dc64564e82 100644 --- a/tests/unit/models-catalog-static-synced-suppression.test.ts +++ b/tests/unit/models-catalog-static-synced-suppression.test.ts @@ -28,7 +28,7 @@ function getStaticModel(provider: string) { async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(path.join(TEST_DATA_DIR, "logs/application"), { recursive: true }); catalog.__resetCatalogBuilderRunsForTest(); } @@ -58,7 +58,7 @@ test.beforeEach(resetStorage); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("active authoritative live catalog suppresses stale static registry models", async () => { @@ -109,9 +109,11 @@ test("partial discovery provider preserves uncovered static models when synced", const uncoveredStaticModel = "deepseek/deepseek-v4-flash"; const coveredSyncedModel = "claude-opus-4-7"; - await modelsDb.replaceSyncedAvailableModelsForConnection("command-code", connection.id as string, [ - { id: coveredSyncedModel, name: "Claude Opus 4.7", source: "imported" }, - ]); + await modelsDb.replaceSyncedAvailableModelsForConnection( + "command-code", + connection.id as string, + [{ id: coveredSyncedModel, name: "Claude Opus 4.7", source: "imported" }] + ); const ids = await getCatalogIds(); diff --git a/tests/unit/models-db-isfree.test.ts b/tests/unit/models-db-isfree.test.ts index 7e1234d09731..df2e23485d96 100644 --- a/tests/unit/models-db-isfree.test.ts +++ b/tests/unit/models-db-isfree.test.ts @@ -1,6 +1,11 @@ import { describe, it, beforeEach, afterEach } from "node:test"; import assert from "node:assert/strict"; -import { addCustomModel, replaceCustomModels, updateCustomModel, getCustomModels } from "../../src/lib/db/models.ts"; +import { + addCustomModel, + replaceCustomModels, + updateCustomModel, + getCustomModels, +} from "../../src/lib/db/models.ts"; import { resetDbInstance } from "../../src/lib/db/core.ts"; import { rmSync, mkdtempSync } from "node:fs"; import { tmpdir } from "node:os"; @@ -20,22 +25,60 @@ describe("custom isFree tri-state (DB)", () => { resetDbInstance(); if (prevDataDir === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = prevDataDir; - try { rmSync(dir, { recursive: true, force: true }); } catch {} + try { + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + } catch {} }); it("addCustomModel round-trip isFree:true → kept, isFree absent → not set", async () => { - await addCustomModel("p", "m1", "M1", "manual", "chat-completions", ["chat"], undefined, {}, undefined, undefined, true); + await addCustomModel( + "p", + "m1", + "M1", + "manual", + "chat-completions", + ["chat"], + undefined, + {}, + undefined, + undefined, + true + ); const rows: any = await getCustomModels("p"); const r = rows.find((x: any) => x.id === "m1"); assert.equal(r.isFree, true); - await addCustomModel("p", "m2", "M2", "manual", "chat-completions", ["chat"], undefined, {}, undefined, undefined, undefined); + await addCustomModel( + "p", + "m2", + "M2", + "manual", + "chat-completions", + ["chat"], + undefined, + {}, + undefined, + undefined, + undefined + ); const rows2: any = await getCustomModels("p"); const r2 = rows2.find((x: any) => x.id === "m2"); assert.equal(r2.isFree, undefined); }); it("updateCustomModel isFree:null → delete key (tri-state clear)", async () => { - await addCustomModel("p", "m", "M", "manual", "chat-completions", ["chat"], undefined, {}, undefined, undefined, true); + await addCustomModel( + "p", + "m", + "M", + "manual", + "chat-completions", + ["chat"], + undefined, + {}, + undefined, + undefined, + true + ); await updateCustomModel("p", "m", { isFree: null } as any); const rows: any = await getCustomModels("p"); const r = rows.find((x: any) => x.id === "m"); @@ -43,7 +86,19 @@ describe("custom isFree tri-state (DB)", () => { }); it("updateCustomModel isFree:true → set, then false-effective via tri-state (Boolean) ", async () => { - await addCustomModel("p", "m", "M", "manual", "chat-completions", ["chat"], undefined, {}, undefined, undefined, undefined); + await addCustomModel( + "p", + "m", + "M", + "manual", + "chat-completions", + ["chat"], + undefined, + {}, + undefined, + undefined, + undefined + ); await updateCustomModel("p", "m", { isFree: true } as any); let rows: any = await getCustomModels("p"); assert.equal(rows.find((x: any) => x.id === "m").isFree, true); @@ -54,17 +109,60 @@ describe("custom isFree tri-state (DB)", () => { }); it("replaceCustomModels preserves isFree (new wins else prev)", async () => { - await addCustomModel("p", "keep", "K", "manual", "chat-completions", ["chat"], undefined, {}, undefined, undefined, true); - await addCustomModel("p", "override", "O", "manual", "chat-completions", ["chat"], undefined, {}, undefined, undefined, undefined); + await addCustomModel( + "p", + "keep", + "K", + "manual", + "chat-completions", + ["chat"], + undefined, + {}, + undefined, + undefined, + true + ); + await addCustomModel( + "p", + "override", + "O", + "manual", + "chat-completions", + ["chat"], + undefined, + {}, + undefined, + undefined, + undefined + ); // replace with new truth for override, omit for keep (prev should win) - await replaceCustomModels("p", [{ id: "keep", name: "keep" }, { id: "override", name: "override", isFree: true } as any]); + await replaceCustomModels("p", [ + { id: "keep", name: "keep" }, + { id: "override", name: "override", isFree: true } as any, + ]); const rows: any = await getCustomModels("p"); - assert.equal(rows.find((x: any) => x.id === "keep").isFree, true, "prev isFree preserved when new omits"); + assert.equal( + rows.find((x: any) => x.id === "keep").isFree, + true, + "prev isFree preserved when new omits" + ); assert.equal(rows.find((x: any) => x.id === "override").isFree, true, "new isFree wins"); }); it("allowEmpty:false intact (no destructive clear)", async () => { - await addCustomModel("p", "m", "M", "manual", "chat-completions", ["chat"], undefined, {}, undefined, undefined, true); + await addCustomModel( + "p", + "m", + "M", + "manual", + "chat-completions", + ["chat"], + undefined, + {}, + undefined, + undefined, + true + ); const before: any = await getCustomModels("p"); const after: any = await replaceCustomModels("p", [], { allowEmpty: false }); assert.equal(after.length, before.length); diff --git a/tests/unit/models-dev-pricing-caching-9300.test.ts b/tests/unit/models-dev-pricing-caching-9300.test.ts index a1d62025e36f..9c22b2722f61 100644 --- a/tests/unit/models-dev-pricing-caching-9300.test.ts +++ b/tests/unit/models-dev-pricing-caching-9300.test.ts @@ -47,7 +47,9 @@ describe("getModelsDevPricing caching (#9300)", () => { modelsDev = await importFresh("9300-cache"); // Seed pricing data into DB - modelsDev.saveModelsDevPricing(PRICING_DATA as Record>>); + modelsDev.saveModelsDevPricing( + PRICING_DATA as Record>> + ); // Reset cache to ensure a clean read from DB // (saveModelsDevPricing clears the cache, so next get will load from DB) @@ -57,7 +59,7 @@ describe("getModelsDevPricing caching (#9300)", () => { // Clean up DB handles dbCore.resetDbInstance(); try { - fs.rmSync(testDataDir, { recursive: true, force: true }); + fs.rmSync(testDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // ignore } @@ -110,4 +112,4 @@ describe("getModelsDevPricing caching (#9300)", () => { // After clear, pricing should be empty assert.deepEqual(afterClear, {}, "pricing should be empty after clear"); }); -}); \ No newline at end of file +}); diff --git a/tests/unit/models-test-error-shape.test.ts b/tests/unit/models-test-error-shape.test.ts index 54f98ed4936a..cbe01c214344 100644 --- a/tests/unit/models-test-error-shape.test.ts +++ b/tests/unit/models-test-error-shape.test.ts @@ -26,7 +26,7 @@ test.before(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function post(body: unknown, rawText?: string) { diff --git a/tests/unit/modelsDevSync-extended.test.ts b/tests/unit/modelsDevSync-extended.test.ts index 3d0f26266eee..4a6edc3dc05e 100644 --- a/tests/unit/modelsDevSync-extended.test.ts +++ b/tests/unit/modelsDevSync-extended.test.ts @@ -101,7 +101,7 @@ function restoreEnv() { function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -142,7 +142,7 @@ test.afterEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test.describe("modelsDevSync-extended", { concurrency: 1 }, async () => { diff --git a/tests/unit/monitoring-health-public-view.test.ts b/tests/unit/monitoring-health-public-view.test.ts index 5b7249066d32..20d386b29630 100644 --- a/tests/unit/monitoring-health-public-view.test.ts +++ b/tests/unit/monitoring-health-public-view.test.ts @@ -20,7 +20,7 @@ const route = await import("../../src/app/api/monitoring/health/route.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("anonymous health GET is reduced to liveness only (GHSA-mvf8)", async () => { diff --git a/tests/unit/native-binary-compat.test.ts b/tests/unit/native-binary-compat.test.ts index 120c5e7853b3..a8867d88884a 100644 --- a/tests/unit/native-binary-compat.test.ts +++ b/tests/unit/native-binary-compat.test.ts @@ -70,7 +70,7 @@ describe("isNativeBinaryCompatible", () => { try { callback(file); } finally { - rmSync(dir, { recursive: true, force: true }); + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } diff --git a/tests/unit/noauth-autocombo-allowlist.test.ts b/tests/unit/noauth-autocombo-allowlist.test.ts index 228193c8af46..70789f588797 100644 --- a/tests/unit/noauth-autocombo-allowlist.test.ts +++ b/tests/unit/noauth-autocombo-allowlist.test.ts @@ -27,7 +27,7 @@ const virtualFactory = await import("../../open-sse/services/autoCombo/virtualFa async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -37,7 +37,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; diff --git a/tests/unit/noauth-autocombo-exclude-7622.test.ts b/tests/unit/noauth-autocombo-exclude-7622.test.ts index 0d8362cd8c13..f784f21394a6 100644 --- a/tests/unit/noauth-autocombo-exclude-7622.test.ts +++ b/tests/unit/noauth-autocombo-exclude-7622.test.ts @@ -23,7 +23,7 @@ const virtualFactory = await import("../../open-sse/services/autoCombo/virtualFa async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -33,7 +33,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; diff --git a/tests/unit/noauth-autocombo-hidden-7620.test.ts b/tests/unit/noauth-autocombo-hidden-7620.test.ts index 630b7fe564c0..6ec9dea14920 100644 --- a/tests/unit/noauth-autocombo-hidden-7620.test.ts +++ b/tests/unit/noauth-autocombo-hidden-7620.test.ts @@ -25,7 +25,7 @@ const virtualFactory = await import("../../open-sse/services/autoCombo/virtualFa async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -35,7 +35,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; } else { diff --git a/tests/unit/noauth-autocombo-lockout-7623.test.ts b/tests/unit/noauth-autocombo-lockout-7623.test.ts index dea6d7b23113..49c6fd0259b9 100644 --- a/tests/unit/noauth-autocombo-lockout-7623.test.ts +++ b/tests/unit/noauth-autocombo-lockout-7623.test.ts @@ -21,7 +21,7 @@ const accountFallback = await import("../../open-sse/services/accountFallback.ts async function resetStorage() { core.resetDbInstance(); accountFallback.clearAllModelLockouts(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -31,7 +31,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; diff --git a/tests/unit/noauth-imported-models-3200.test.ts b/tests/unit/noauth-imported-models-3200.test.ts index cf23a57f8da7..9f6a24d1b4f7 100644 --- a/tests/unit/noauth-imported-models-3200.test.ts +++ b/tests/unit/noauth-imported-models-3200.test.ts @@ -28,7 +28,7 @@ const v1ModelsCatalog = await import("../../src/app/api/v1/models/catalog.ts"); async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -39,7 +39,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#3200 imported model on a noAuth provider (theoldllm) appears in /api/v1/models", async () => { diff --git a/tests/unit/notion-web-models-discovery.test.ts b/tests/unit/notion-web-models-discovery.test.ts index 9c3b1c3d63c8..e6594fbf0546 100644 --- a/tests/unit/notion-web-models-discovery.test.ts +++ b/tests/unit/notion-web-models-discovery.test.ts @@ -14,13 +14,13 @@ const modelsRoute = await import("../../src/app/api/providers/[id]/models/route. async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); const SAMPLE_RESPONSE = { diff --git a/tests/unit/nvidia-410-model-scope.test.ts b/tests/unit/nvidia-410-model-scope.test.ts index 13fbaa6ef67e..0d72fe13454a 100644 --- a/tests/unit/nvidia-410-model-scope.test.ts +++ b/tests/unit/nvidia-410-model-scope.test.ts @@ -28,7 +28,7 @@ const GONE_BODY = JSON.stringify({ async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -50,7 +50,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("NVIDIA 410 Gone stays model-scoped and leaves the connection usable", async () => { diff --git a/tests/unit/oauth-400-recovery.test.ts b/tests/unit/oauth-400-recovery.test.ts index 66d40db46c35..fa041ea56dc6 100644 --- a/tests/unit/oauth-400-recovery.test.ts +++ b/tests/unit/oauth-400-recovery.test.ts @@ -231,7 +231,7 @@ test("isReactive400Recoverable fixtures compile with the real helper signature", }); test.after(() => { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("isTokenExpired treats a corrupt expiresAt string as expired (refreshable)", () => { diff --git a/tests/unit/oauth-connection-persistence-codex-dedup.test.ts b/tests/unit/oauth-connection-persistence-codex-dedup.test.ts index 30d5a29adf8c..30fe9ed7fd58 100644 --- a/tests/unit/oauth-connection-persistence-codex-dedup.test.ts +++ b/tests/unit/oauth-connection-persistence-codex-dedup.test.ts @@ -16,7 +16,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: unknown) { @@ -36,7 +36,7 @@ test.beforeEach(async () => { }); test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("persistOAuthConnection must not merge two distinct Codex accounts that share an email but have different chatgptUserId and no workspaceId", async () => { @@ -90,9 +90,17 @@ test("persistOAuthConnection still merges a re-login for the SAME Codex chatgptU providerSpecificData: { chatgptUserId: "user-solo" }, }); - assert.equal(second.id, first.id, "re-authenticating the same Codex user must update the same row"); + assert.equal( + second.id, + first.id, + "re-authenticating the same Codex user must update the same row" + ); const rows = await providersDb.getProviderConnections({ provider: "codex" }); - assert.equal(rows.length, 1, "no duplicate connection should be created for the same chatgptUserId"); + assert.equal( + rows.length, + 1, + "no duplicate connection should be created for the same chatgptUserId" + ); assert.equal(rows[0]?.accessToken, "token-second", "the row must reflect the latest tokens"); }); diff --git a/tests/unit/oauth-connection-tokenexpiresat-5326.test.ts b/tests/unit/oauth-connection-tokenexpiresat-5326.test.ts index 950750622180..a018739b8d8e 100644 --- a/tests/unit/oauth-connection-tokenexpiresat-5326.test.ts +++ b/tests/unit/oauth-connection-tokenexpiresat-5326.test.ts @@ -15,7 +15,7 @@ const { buildOAuthConnectionCreatePayload } = test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // Regression for #5326: a freshly created OAuth connection (e.g. antigravity) used diff --git a/tests/unit/oauth-device-code-region-ssrf.test.ts b/tests/unit/oauth-device-code-region-ssrf.test.ts index bc57659f7998..c5f7f1c5e123 100644 --- a/tests/unit/oauth-device-code-region-ssrf.test.ts +++ b/tests/unit/oauth-device-code-region-ssrf.test.ts @@ -21,7 +21,7 @@ const route = await import("../../src/app/api/oauth/[provider]/[action]/route.ts test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); async function deviceCode(provider: string, region: string) { diff --git a/tests/unit/oauth-grok-cli-browser.test.ts b/tests/unit/oauth-grok-cli-browser.test.ts index 3e23d57a06c5..9d21a1ad8fef 100644 --- a/tests/unit/oauth-grok-cli-browser.test.ts +++ b/tests/unit/oauth-grok-cli-browser.test.ts @@ -19,9 +19,8 @@ const settingsDb = await import("../../src/lib/db/settings.ts"); const route = await import("../../src/app/api/oauth/[provider]/[action]/route.ts"); const { generateAuthData } = await import("../../src/lib/oauth/providers.ts"); const { grokCli } = await import("../../src/lib/oauth/providers/grok-cli.ts"); -const { GROK_BUILD_OAUTH_CONFIG, XAI_OAUTH_CONFIG } = await import( - "../../src/lib/oauth/constants/oauth.ts" -); +const { GROK_BUILD_OAUTH_CONFIG, XAI_OAUTH_CONFIG } = + await import("../../src/lib/oauth/constants/oauth.ts"); const originalFetch = globalThis.fetch; @@ -32,7 +31,7 @@ test.before(async () => { test.after(async () => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test.afterEach(() => { @@ -98,7 +97,11 @@ test("grok-cli exchangeToken POSTs grant_type=authorization_code with the PKCE v assert.equal(body.get("code"), "auth-code"); assert.equal(body.get("redirect_uri"), "http://127.0.0.1:56122/callback"); assert.equal(body.get("code_verifier"), "verifier"); - return Response.json({ access_token: "gb-access", refresh_token: "gb-refresh", expires_in: 3600 }); + return Response.json({ + access_token: "gb-access", + refresh_token: "gb-refresh", + expires_in: 3600, + }); }; const tokens = await grokCli.exchangeToken( @@ -188,7 +191,8 @@ test("POST /api/oauth/grok-cli/exchange requires a codeVerifier (PKCE branch rea }); test("POST /api/oauth/grok-cli/exchange failure returns a sanitized 500 (Hard Rule #12)", async () => { - globalThis.fetch = async () => new Response("upstream secret leak: token=abc123", { status: 500 }); + globalThis.fetch = async () => + new Response("upstream secret leak: token=abc123", { status: 500 }); const res = await postRoute("grok-cli", "exchange", { code: "auth-code", diff --git a/tests/unit/oauth-import-manage-scope.test.ts b/tests/unit/oauth-import-manage-scope.test.ts index 070515c02a9f..dd1ef84317b1 100644 --- a/tests/unit/oauth-import-manage-scope.test.ts +++ b/tests/unit/oauth-import-manage-scope.test.ts @@ -30,7 +30,7 @@ test.before(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); delete process.env.JWT_SECRET; delete process.env.INITIAL_PASSWORD; }); @@ -60,7 +60,11 @@ test("codex/import-token: non-manage key → 403, no key → 401, manage key pas const nonManage = await apiKeysDb.createApiKey("client", "machine-client", []); const manage = await apiKeysDb.createApiKey("admin", "machine-admin", ["manage"]); - assert.equal((await post(codexImportToken, nonManage.key)).status, 403, "non-manage key rejected"); + assert.equal( + (await post(codexImportToken, nonManage.key)).status, + 403, + "non-manage key rejected" + ); assert.equal((await post(codexImportToken)).status, 401, "no credential rejected"); const withManage = await post(codexImportToken, manage.key); diff --git a/tests/unit/oauth-keychain-import-only-6041.test.ts b/tests/unit/oauth-keychain-import-only-6041.test.ts index d2363ea2251a..f5a676c00292 100644 --- a/tests/unit/oauth-keychain-import-only-6041.test.ts +++ b/tests/unit/oauth-keychain-import-only-6041.test.ts @@ -28,7 +28,7 @@ test.before(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function get(provider: string, action: string) { @@ -42,7 +42,11 @@ test("#6041 GET /oauth/zed/authorize returns a graceful 400, not a 500 'Unknown const body = await res.json(); assert.ok(body.error, "error message present"); assert.match(body.error, /Import/i, "must point the user at the Import flow"); - assert.doesNotMatch(body.error, /Unknown provider/i, "must not leak the raw 'Unknown provider' error"); + assert.doesNotMatch( + body.error, + /Unknown provider/i, + "must not leak the raw 'Unknown provider' error" + ); // Never leak a stack trace (ERROR_SANITIZATION). assert.doesNotMatch(body.error, /at \//, "must not leak a stack trace"); }); diff --git a/tests/unit/oauth-paste-credentials-route.test.ts b/tests/unit/oauth-paste-credentials-route.test.ts index 43793bf08dc9..653b03ac2064 100644 --- a/tests/unit/oauth-paste-credentials-route.test.ts +++ b/tests/unit/oauth-paste-credentials-route.test.ts @@ -33,7 +33,7 @@ test.before(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); async function postPaste(provider: string, body: unknown) { diff --git a/tests/unit/oauth-refresh-connection-dedup-8059.test.ts b/tests/unit/oauth-refresh-connection-dedup-8059.test.ts index 5724dadcb091..d0dee409e005 100644 --- a/tests/unit/oauth-refresh-connection-dedup-8059.test.ts +++ b/tests/unit/oauth-refresh-connection-dedup-8059.test.ts @@ -26,14 +26,14 @@ const { persistOAuthConnection, findExistingOAuthConnectionMatch } = async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.beforeEach(resetStorage); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); type ProviderConnection = Awaited>[number]; diff --git a/tests/unit/obsidian-config.test.ts b/tests/unit/obsidian-config.test.ts index 71239c0c7afb..2d617cef2ce7 100644 --- a/tests/unit/obsidian-config.test.ts +++ b/tests/unit/obsidian-config.test.ts @@ -8,12 +8,18 @@ const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omni-obsidian-confi process.env.DATA_DIR = TEST_DATA_DIR; const coreDb = await import("../../src/lib/db/core.ts"); -const { getApiKeyContextSource, setApiKeyContextSource, deleteApiKeyContextSource, listApiKeyContextSources } = await import("../../src/lib/db/apiKeyContextSources.ts"); -const { getObsidianConfigForApiKey, setObsidianToken, setObsidianBaseUrl } = await import("../../src/lib/db/obsidian.ts"); +const { + getApiKeyContextSource, + setApiKeyContextSource, + deleteApiKeyContextSource, + listApiKeyContextSources, +} = await import("../../src/lib/db/apiKeyContextSources.ts"); +const { getObsidianConfigForApiKey, setObsidianToken, setObsidianBaseUrl } = + await import("../../src/lib/db/obsidian.ts"); async function resetStorage() { coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -30,7 +36,7 @@ test.beforeEach(async () => { test.after(() => { coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("apiKeyContextSources: returns null for unknown apiKeyId", () => { @@ -88,7 +94,7 @@ test("apiKeyContextSources: list returns all sources for a key", () => { setApiKeyContextSource("key-5", "notion", { token: "not", enabled: true }); const results = listApiKeyContextSources("key-5"); assert.equal(results.length, 2); - const types = results.map(r => r.sourceType).sort(); + const types = results.map((r) => r.sourceType).sort(); assert.deepEqual(types, ["notion", "obsidian"]); }); diff --git a/tests/unit/obsidian-webdav-route.test.ts b/tests/unit/obsidian-webdav-route.test.ts index cfb93634923e..a92d06d332e8 100644 --- a/tests/unit/obsidian-webdav-route.test.ts +++ b/tests/unit/obsidian-webdav-route.test.ts @@ -37,7 +37,7 @@ const obsidianDb = await import("../../src/lib/db/obsidian.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -53,7 +53,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; @@ -103,8 +103,14 @@ test("POST with a valid temp dir → returns { username, password }, GET shows e assert.equal(res.status, 200); const body = (await res.json()) as Record; - assert.ok(typeof body.username === "string" && (body.username as string).length > 0, "username non-empty"); - assert.ok(typeof body.password === "string" && (body.password as string).length > 0, "password non-empty"); + assert.ok( + typeof body.username === "string" && (body.username as string).length > 0, + "username non-empty" + ); + assert.ok( + typeof body.password === "string" && (body.password as string).length > 0, + "password non-empty" + ); assert.ok(typeof body.vaultPath === "string", "vaultPath returned"); // GET should now reflect enabled state @@ -113,13 +119,15 @@ test("POST with a valid temp dir → returns { username, password }, GET shows e assert.equal(getRes.status, 200); const getBody = (await getRes.json()) as Record; assert.equal(getBody.webdavEnabled, true); - assert.ok(typeof getBody.webdavUsername === "string" && (getBody.webdavUsername as string).length > 0); + assert.ok( + typeof getBody.webdavUsername === "string" && (getBody.webdavUsername as string).length > 0 + ); // Anonymous GET (this request carries no management credential): the plaintext // password is masked (GHSA-62vw), but the set/unset flag still reflects state. assert.equal(getBody.webdavPassword, null); assert.equal(getBody.webdavPasswordSet, true); } finally { - fs.rmSync(vaultDir, { recursive: true, force: true }); + fs.rmSync(vaultDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -137,11 +145,15 @@ test("GET masks the WebDAV password for anonymous callers but reveals it to a ma assert.equal(enableRes.status, 200); // Anonymous (open-mode) caller: password masked, flag still set. - const anonBody = (await (await route.GET( - makeRequest("http://localhost/api/settings/obsidian/webdav") - )).json()) as Record; + const anonBody = (await ( + await route.GET(makeRequest("http://localhost/api/settings/obsidian/webdav")) + ).json()) as Record; assert.equal(anonBody.webdavEnabled, true); - assert.equal(anonBody.webdavPassword, null, "anonymous caller must not receive the plaintext password"); + assert.equal( + anonBody.webdavPassword, + null, + "anonymous caller must not receive the plaintext password" + ); assert.equal(anonBody.webdavPasswordSet, true); // Genuine management session: the operator's reveal-password view still works. @@ -155,7 +167,7 @@ test("GET masks the WebDAV password for anonymous callers but reveals it to a ma "a management session must still receive the plaintext password" ); } finally { - fs.rmSync(vaultDir, { recursive: true, force: true }); + fs.rmSync(vaultDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -170,7 +182,8 @@ test("POST with a non-existent path → 400, body does NOT contain a stack trace assert.equal(res.status, 400); const body = (await res.json()) as Record; - const errorMsg = (body.error as Record | undefined)?.message as string | undefined; + const errorMsg = (body.error as Record | undefined)?.message as + string | undefined; // Must not leak stack trace assert.ok( !errorMsg || !errorMsg.includes("at /"), @@ -217,7 +230,7 @@ test("DELETE after enable → webdavEnabled:false, creds cleared in GET", async assert.equal(getBody.webdavUsername, null); assert.equal(getBody.webdavPassword, null); } finally { - fs.rmSync(vaultDir, { recursive: true, force: true }); + fs.rmSync(vaultDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -243,7 +256,7 @@ test("GET when disabled does not leak password even if stale data exists", async assert.equal(getBody.webdavEnabled, false); assert.equal(getBody.webdavPassword, null); } finally { - fs.rmSync(vaultDir, { recursive: true, force: true }); + fs.rmSync(vaultDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -272,7 +285,7 @@ test("Unauthenticated POST → 401 when auth is required", async () => { const res = await route.POST(req); assert.equal(res.status, 401); } finally { - fs.rmSync(vaultDir, { recursive: true, force: true }); + fs.rmSync(vaultDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -336,5 +349,9 @@ test("encryption graceful fallback: plaintext stored without key reads back corr // Must read back the same value const retrieved = obsidianDb.getWebdavPassword(); - assert.equal(retrieved, plaintext, "Plaintext value must read back unchanged when no encryption key"); + assert.equal( + retrieved, + plaintext, + "Plaintext value must read back unchanged when no encryption key" + ); }); diff --git a/tests/unit/oidc-callback.test.ts b/tests/unit/oidc-callback.test.ts index 5d41701e42e4..0659edf58c31 100644 --- a/tests/unit/oidc-callback.test.ts +++ b/tests/unit/oidc-callback.test.ts @@ -32,7 +32,7 @@ let capturedCookies: Record = {}; async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); capturedCookies = {}; } @@ -61,7 +61,7 @@ test.afterEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); delete process.env.JWT_SECRET; }); diff --git a/tests/unit/oidc-login-state.test.ts b/tests/unit/oidc-login-state.test.ts index ea9c4e56fba0..341ba53932c0 100644 --- a/tests/unit/oidc-login-state.test.ts +++ b/tests/unit/oidc-login-state.test.ts @@ -20,7 +20,7 @@ const loginRoute = await import("../../src/app/api/auth/oidc/login/route.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -30,7 +30,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); delete process.env.JWT_SECRET; }); diff --git a/tests/unit/ollama-404-model-lockout-11071.test.ts b/tests/unit/ollama-404-model-lockout-11071.test.ts index f58c29c07b97..fbcdb32e4673 100644 --- a/tests/unit/ollama-404-model-lockout-11071.test.ts +++ b/tests/unit/ollama-404-model-lockout-11071.test.ts @@ -10,17 +10,18 @@ process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); const providersDb = await import("../../src/lib/db/providers.ts"); const auth = await import("../../src/sse/services/auth.ts"); -const { hasPerModelQuota, isModelLocked } = await import("../../open-sse/services/accountFallback.ts"); +const { hasPerModelQuota, isModelLocked } = + await import("../../open-sse/services/accountFallback.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("hasPerModelQuota returns true for ollama-local and ollama providers", () => { @@ -53,7 +54,11 @@ test("markAccountUnavailable locks only the missing model on a 404 from ollama-l // The connection in DB must remain active / not marked unavailable for sibling models const connInDb = await providersDb.getProviderConnectionById(connection.id); - assert.notEqual(connInDb?.testStatus, "unavailable", "connection should not be marked unavailable connection-wide on a 404 model-not-found error"); + assert.notEqual( + connInDb?.testStatus, + "unavailable", + "connection should not be marked unavailable connection-wide on a 404 model-not-found error" + ); // getProviderCredentials must still serve sibling models const selectedForSibling = await auth.getProviderCredentials( @@ -62,5 +67,8 @@ test("markAccountUnavailable locks only the missing model on a 404 from ollama-l null, "model-a" ); - assert.ok(selectedForSibling && !("allExpired" in selectedForSibling), "sibling model-a must still be selected on the same connection"); + assert.ok( + selectedForSibling && !("allExpired" in selectedForSibling), + "sibling model-a must still be selected on the same connection" + ); }); diff --git a/tests/unit/ollama-local-capabilities-routing.test.ts b/tests/unit/ollama-local-capabilities-routing.test.ts index 0ce75a298355..25c1f6190692 100644 --- a/tests/unit/ollama-local-capabilities-routing.test.ts +++ b/tests/unit/ollama-local-capabilities-routing.test.ts @@ -23,7 +23,7 @@ const originalFetch = globalThis.fetch; function resetStorage() { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -45,7 +45,7 @@ test.beforeEach(resetStorage); test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("Ollama discovery maps /api/show capabilities into connection-scoped model metadata", async () => { diff --git a/tests/unit/ollama-local-embedding-2824.test.ts b/tests/unit/ollama-local-embedding-2824.test.ts index 2cb4c18e2f80..400f30fa8e10 100644 --- a/tests/unit/ollama-local-embedding-2824.test.ts +++ b/tests/unit/ollama-local-embedding-2824.test.ts @@ -16,7 +16,7 @@ const { createEmbeddingResponse } = await import("../../src/lib/embeddings/servi test.after(() => { core.resetDbInstance(); - rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("ollama-local exposes a static no-auth embedding registry entry", () => { diff --git a/tests/unit/openai-style-providers-4239-4155-3841.test.ts b/tests/unit/openai-style-providers-4239-4155-3841.test.ts index 4783262345f8..f5e744c96ee4 100644 --- a/tests/unit/openai-style-providers-4239-4155-3841.test.ts +++ b/tests/unit/openai-style-providers-4239-4155-3841.test.ts @@ -51,13 +51,13 @@ const modelsRoute = await import("../../src/app/api/providers/[id]/models/route. function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); interface ProviderSpec { diff --git a/tests/unit/openapi-try-route.test.ts b/tests/unit/openapi-try-route.test.ts index 4ab2edab5af8..bb29d2e81529 100644 --- a/tests/unit/openapi-try-route.test.ts +++ b/tests/unit/openapi-try-route.test.ts @@ -21,7 +21,7 @@ const originalFetch = globalThis.fetch; async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -57,7 +57,7 @@ test.afterEach(() => { test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; diff --git a/tests/unit/opencode-free-tier-routing-shortcircuit-10571.test.ts b/tests/unit/opencode-free-tier-routing-shortcircuit-10571.test.ts index 76bfd17922b3..75a17b160315 100644 --- a/tests/unit/opencode-free-tier-routing-shortcircuit-10571.test.ts +++ b/tests/unit/opencode-free-tier-routing-shortcircuit-10571.test.ts @@ -39,7 +39,7 @@ const { getModelInfoCore } = await import("../../open-sse/services/model.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("bare big-pickle routes to an opencode-family provider when an opencode connection is active", async () => { diff --git a/tests/unit/opencode-noauth-models-route.test.ts b/tests/unit/opencode-noauth-models-route.test.ts index 6965c485ea87..2b4abeaee82b 100644 --- a/tests/unit/opencode-noauth-models-route.test.ts +++ b/tests/unit/opencode-noauth-models-route.test.ts @@ -13,7 +13,7 @@ const modelsRoute = await import("../../src/app/api/providers/[id]/models/route. test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // #3047 — OpenCode Free (no-auth) has no connection row, so the diff --git a/tests/unit/opencode-zen-alias-combo-e2e.test.ts b/tests/unit/opencode-zen-alias-combo-e2e.test.ts index 40f4cb70657b..f9adbf73a2ac 100644 --- a/tests/unit/opencode-zen-alias-combo-e2e.test.ts +++ b/tests/unit/opencode-zen-alias-combo-e2e.test.ts @@ -120,7 +120,7 @@ before(async () => { after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ─── Tests ────────────────────────────────────────────────────────────── diff --git a/tests/unit/openrouter-embeddings-catalog-6976.test.ts b/tests/unit/openrouter-embeddings-catalog-6976.test.ts index 3e2f27b3940a..e722e5c09675 100644 --- a/tests/unit/openrouter-embeddings-catalog-6976.test.ts +++ b/tests/unit/openrouter-embeddings-catalog-6976.test.ts @@ -22,7 +22,7 @@ type ModelsResponseBody = { source: string; models: DiscoveredModel[] }; async function resetStorage() { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -53,7 +53,7 @@ test.beforeEach(async () => { test.after(async () => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("embeddingRegistry curated openrouter catalog carries the refreshed lineup with dimensions (#6976)", () => { @@ -75,10 +75,7 @@ test("embeddingRegistry curated openrouter catalog carries the refreshed lineup const dim = config!.models.find((m) => m.id === expected)?.dimensions; assert.equal(typeof dim, "number", `${expected} must carry a dimensions value`); } - assert.equal( - config!.models.find((m) => m.id === "google/gemini-embedding-2")?.dimensions, - 3072 - ); + assert.equal(config!.models.find((m) => m.id === "google/gemini-embedding-2")?.dimensions, 3072); assert.equal( config!.models.find((m) => m.id === "google/gemini-embedding-2-preview")?.dimensions, 3072 diff --git a/tests/unit/openrouter-free-model-credits-exhausted.test.ts b/tests/unit/openrouter-free-model-credits-exhausted.test.ts index 129d2fac6d94..6a5664659407 100644 --- a/tests/unit/openrouter-free-model-credits-exhausted.test.ts +++ b/tests/unit/openrouter-free-model-credits-exhausted.test.ts @@ -29,13 +29,13 @@ const auth = await import("../../src/sse/services/auth.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("getProviderCredentials still serves a :free OpenRouter model after the connection is credits_exhausted", async () => { diff --git a/tests/unit/openrouter-provider-stats.test.ts b/tests/unit/openrouter-provider-stats.test.ts index f2d0d3ec4f46..4e0550a841d5 100644 --- a/tests/unit/openrouter-provider-stats.test.ts +++ b/tests/unit/openrouter-provider-stats.test.ts @@ -139,7 +139,7 @@ describe("getOpenRouterProviderStats / refreshOpenRouterProviderStats (cache + T afterEach(() => { restoreFetch(); - fs.rmSync(tempDir, { recursive: true, force: true }); + fs.rmSync(tempDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (originalDataDir === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = originalDataDir; if (originalTtl === undefined) delete process.env.OPENROUTER_PROVIDER_STATS_TTL_MS; diff --git a/tests/unit/openrouter-vision-sync-4264.test.ts b/tests/unit/openrouter-vision-sync-4264.test.ts index ac00c42e686f..3b0117a1aa89 100644 --- a/tests/unit/openrouter-vision-sync-4264.test.ts +++ b/tests/unit/openrouter-vision-sync-4264.test.ts @@ -24,7 +24,7 @@ const v1ModelsCatalog = await import("../../src/app/api/v1/models/catalog.ts"); async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -35,7 +35,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#4264 normalizeDiscoveredModels captures vision from OpenRouter architecture", () => { @@ -107,9 +107,7 @@ test("#4264 synced OpenRouter vision model surfaces capabilities.vision in /v1/m assert.equal(response.status, 200); const body = (await response.json()) as any; - const visionModel = body.data.find((m: any) => - String(m.id).endsWith("nex-agi/nex-n2-pro:free") - ); + const visionModel = body.data.find((m: any) => String(m.id).endsWith("nex-agi/nex-n2-pro:free")); assert.ok(visionModel, `expected the synced vision model in the catalog`); // RED before the fix: synced models carried no capabilities at all. assert.equal(visionModel.capabilities?.vision, true); diff --git a/tests/unit/ops-scripts.test.ts b/tests/unit/ops-scripts.test.ts index 2b37aae0cdf4..8bfb6d3e1c5f 100644 --- a/tests/unit/ops-scripts.test.ts +++ b/tests/unit/ops-scripts.test.ts @@ -136,7 +136,7 @@ describe("ops runbook scripts (bin/*.sh)", () => { ); db.close(); } finally { - fs.rmSync(dataDir, { recursive: true, force: true }); + fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -187,7 +187,7 @@ describe("ops runbook scripts (bin/*.sh)", () => { ); db.close(); } finally { - fs.rmSync(dataDir, { recursive: true, force: true }); + fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } ); diff --git a/tests/unit/optional-packs.test.ts b/tests/unit/optional-packs.test.ts index 5ff6eb84f66a..47947c8eab0e 100644 --- a/tests/unit/optional-packs.test.ts +++ b/tests/unit/optional-packs.test.ts @@ -33,7 +33,7 @@ test("packs dirs derive from DATA_DIR override without touching the real home", packNodeModulesDir("browser-runtime", dataDir), path.join(dataDir, "packs", "browser-runtime", "node_modules") ); - fs.rmSync(dataDir, { recursive: true, force: true }); + fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("installedPackNodePaths lists only packs with an existing node_modules dir, in manifest order", () => { @@ -51,7 +51,7 @@ test("installedPackNodePaths lists only packs with an existing node_modules dir, path.join(dataDir, "packs", "ml-runtime", "node_modules"), path.join(dataDir, "packs", "browser-runtime", "node_modules"), ]); - fs.rmSync(dataDir, { recursive: true, force: true }); + fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("packMemberInstalled probes installed pack trees with optional node_modules prefix", () => { @@ -83,7 +83,7 @@ test("packMemberInstalled probes installed pack trees with optional node_modules packMemberInstalled("@atjsh/llmlingua-2/package.json", path.join(dataDir, "absent")), false ); - fs.rmSync(dataDir, { recursive: true, force: true }); + fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("manifest and runtime pack lists stay in sync", async () => { diff --git a/tests/unit/paid-model-target-routes-6540.test.ts b/tests/unit/paid-model-target-routes-6540.test.ts index 63e216e84a06..5e6181f629a5 100644 --- a/tests/unit/paid-model-target-routes-6540.test.ts +++ b/tests/unit/paid-model-target-routes-6540.test.ts @@ -12,9 +12,8 @@ const core = await import("../../src/lib/db/core.ts"); const settingsDb = await import("../../src/lib/db/settings.ts"); const settingsRoute = await import("../../src/app/api/settings/route.ts"); const comboDefaultsRoute = await import("../../src/app/api/settings/combo-defaults/route.ts"); -const backgroundDegradationRoute = await import( - "../../src/app/api/settings/background-degradation/route.ts" -); +const backgroundDegradationRoute = + await import("../../src/app/api/settings/background-degradation/route.ts"); // A provider present in the free-model catalog (so providerHasFreeModels is // true) but a model id that is NOT one of its documented free models. @@ -26,7 +25,7 @@ const UNKNOWN_TARGET = "my-combo-alias"; async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -36,7 +35,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ── PATCH /api/settings — webSearchRouteModel ────────────────────────────── diff --git a/tests/unit/param-filters-db.test.ts b/tests/unit/param-filters-db.test.ts index 2b6f83b77f63..71a82aa7623e 100644 --- a/tests/unit/param-filters-db.test.ts +++ b/tests/unit/param-filters-db.test.ts @@ -24,7 +24,7 @@ const { stripUnsupportedParams } = await import("../../open-sse/translator/param test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // --------------------------------------------------------------------------- diff --git a/tests/unit/payload-rules-restart-persistence.test.ts b/tests/unit/payload-rules-restart-persistence.test.ts index e08bee0870e2..b57f0256f21d 100644 --- a/tests/unit/payload-rules-restart-persistence.test.ts +++ b/tests/unit/payload-rules-restart-persistence.test.ts @@ -27,7 +27,7 @@ const payloadRulesService = await import("../../open-sse/services/payloadRules.t test.after(() => { payloadRulesService.resetPayloadRulesConfigForTests(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#2986 payload rules survive a restart (DB fallback when override is cleared)", async () => { diff --git a/tests/unit/payload-rules-route.test.ts b/tests/unit/payload-rules-route.test.ts index 9db6cf01171f..22b2b445158b 100644 --- a/tests/unit/payload-rules-route.test.ts +++ b/tests/unit/payload-rules-route.test.ts @@ -21,7 +21,7 @@ async function resetStorage() { core.resetDbInstance(); payloadRulesService.resetPayloadRulesConfigForTests(); delete process.env.INITIAL_PASSWORD; - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -36,7 +36,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; diff --git a/tests/unit/payload-rules.test.ts b/tests/unit/payload-rules.test.ts index 1e059d4402ab..df9d40908b72 100644 --- a/tests/unit/payload-rules.test.ts +++ b/tests/unit/payload-rules.test.ts @@ -138,5 +138,5 @@ test("payload rules load from JSON file and reload changed content", async () => assert.equal(second.defaultRaw.length, 1); assert.deepEqual(second.defaultRaw[0].params.response_format, { type: "json_object" }); - fs.rmSync(tempDir, { recursive: true, force: true }); + fs.rmSync(tempDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); diff --git a/tests/unit/perf-waterfall-elimination.test.ts b/tests/unit/perf-waterfall-elimination.test.ts index 6e235ad24565..eeafce4ceef8 100644 --- a/tests/unit/perf-waterfall-elimination.test.ts +++ b/tests/unit/perf-waterfall-elimination.test.ts @@ -48,9 +48,7 @@ function allPromiseAllBodies(src: string): string[] { test("A1: home page fetches settings + machineId concurrently (#11396)", () => { const src = readSource("src/app/(dashboard)/home/page.tsx"); - const pair = src.match( - /const \[settings, machineId\] = await Promise\.all\(\[([\s\S]*?)\]\);/s - ); + const pair = src.match(/const \[settings, machineId\] = await Promise\.all\(\[([\s\S]*?)\]\);/s); assert.ok(pair, "expected `[settings, machineId] = await Promise.all([...])`"); assert.match(pair![1], /\bgetSettings\(\)/); assert.match(pair![1], /\bgetMachineId\(\)/); @@ -77,10 +75,7 @@ test("F1: cache route GET batches its four async reads (#11396)", () => { assert.match(body, /getCacheTrend\(trendHours\)/); // settings-load failure must degrade to {} *inside* the batch, not reject // the whole Promise.all and 500 the stats endpoint - assert.match( - body, - /getCachedSettings\(\)\.catch\(\s*\(\s*\)\s*=>\s*\(\s*\{\}\s*\)\s*\)/ - ); + assert.match(body, /getCachedSettings\(\)\.catch\(\s*\(\s*\)\s*=>\s*\(\s*\{\}\s*\)\s*\)/); // no serial waterfall remains for the same reads assert.doesNotMatch(src, /await getIdempotencyStats\(\)\s*;/); @@ -102,14 +97,14 @@ test.before(async () => { test.beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); }); test.after(() => { core?.resetDbInstance(); if (TEST_DATA_DIR) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } delete process.env.DATA_DIR; delete process.env.DISABLE_SQLITE_AUTO_BACKUP; @@ -147,11 +142,62 @@ test("F1: cache GET returns correct shapes + trend window after batching (#11396 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` ); // cache hit (tokens_cache_read > 0) - insert.run("test-provider", "test-model", "conn-1", "key-1", "k", 1000, 100, 900, 0, 0, "ok", 1, 123, 45, null, iso(now - 3_600_000)); + insert.run( + "test-provider", + "test-model", + "conn-1", + "key-1", + "k", + 1000, + 100, + 900, + 0, + 0, + "ok", + 1, + 123, + 45, + null, + iso(now - 3_600_000) + ); // cache creation (tokens_cache_creation > 0) - insert.run("test-provider", "test-model", "conn-2", "key-2", "k", 2000, 200, 0, 1500, 0, "ok", 1, 200, 50, null, iso(now - 7_200_000)); + insert.run( + "test-provider", + "test-model", + "conn-2", + "key-2", + "k", + 2000, + 200, + 0, + 1500, + 0, + "ok", + 1, + 200, + 50, + null, + iso(now - 7_200_000) + ); // plain request — must not pollute cache metrics - insert.run("test-provider", "test-model", "conn-3", "key-3", "k", 500, 50, 0, 0, 0, "ok", 1, 90, 30, null, iso(now - 300_000)); + insert.run( + "test-provider", + "test-model", + "conn-3", + "key-3", + "k", + 500, + 50, + 0, + 0, + 0, + "ok", + 1, + 90, + 30, + null, + iso(now - 300_000) + ); const req = new Request("http://localhost/api/cache?trendHours=48", { method: "GET", @@ -174,7 +220,10 @@ test("F1: cache GET returns correct shapes + trend window after batching (#11396 assert.ok(body.idempotency && typeof body.idempotency === "object"); // trend honors the requested window and carries the seeded rows assert.ok(Array.isArray(body.trend)); - assert.equal(body.trend.reduce((s: number, p: { requests: number }) => s + p.requests, 0), 3); + assert.equal( + body.trend.reduce((s: number, p: { requests: number }) => s + p.requests, 0), + 3 + ); // config reads settings through the batched getCachedSettings(.catch → {}) assert.equal(body.config.semanticCacheEnabled, true); @@ -222,7 +271,11 @@ test("N2: provider deletion cleanup helpers run in parallel (#11396)", () => { const src = readSource("src/lib/db/providers/deletion.ts"); const batches = allPromiseAllBodies(src); - assert.equal(batches.length, 3, "expected 3 Promise.all batches in deletion.ts (one per delete function)"); + assert.equal( + batches.length, + 3, + "expected 3 Promise.all batches in deletion.ts (one per delete function)" + ); for (const batch of batches) { assert.match(batch, /_cleanupDeletedComboConnectionRefs\(/); assert.match(batch, /_cleanupDeletedLKGPConnectionRefs\(/); @@ -239,4 +292,4 @@ test("N2: provider deletion cleanup helpers run in parallel (#11396)", () => { // no serial awaits left behind assert.doesNotMatch(src, /await _cleanupDeletedComboConnectionRefs\(/); assert.doesNotMatch(src, /await _cleanupDeletedLKGPConnectionRefs\(/); -}); \ No newline at end of file +}); diff --git a/tests/unit/persist-429-cooldown-account-fallback.test.ts b/tests/unit/persist-429-cooldown-account-fallback.test.ts index e5fa987a2483..82eeab204197 100644 --- a/tests/unit/persist-429-cooldown-account-fallback.test.ts +++ b/tests/unit/persist-429-cooldown-account-fallback.test.ts @@ -1,215 +1,204 @@ -/** - * TDD regression tests for the per-connection 429 cascade DB persistence. - * - * Bug: before this fix, `applyErrorState` (open-sse/services/accountFallback.ts) - * marked a connection rate-limited IN-MEMORY ONLY — the cooldown was forgotten - * when the request ended and `isConnectionRateLimited` (the DB-backed read - * helper) always returned false for non-Antigravity providers. Result: cascade - * failures against a multi-key OpenCode-Go setup retried the same exhausted key - * on the next request and the user saw no "kill for X days" behavior. - * - * After the fix: - * 1. `applyErrorState` with a non-zero cooldown also writes - * `provider_connections.rate_limited_until` via - * `setConnectionRateLimitUntil` (best-effort, never crashes the request). - * 2. `resetAccountState` with a DB id clears that column. - * 3. The localDb re-exports `markConnectionRateLimitedUntil` and - * `clearConnectionRateLimit` for direct use by other consumers - * (e.g. provider-specific executors). - * - * These tests mirror the harness from `antigravity-429-quota-cooldown.test.ts` - * so they share the same DATA_DIR sandbox and DB reset pattern. - */ - -import { test } from "node:test"; -import assert from "node:assert/strict"; -import fs from "node:fs"; -import os from "node:os"; -import path from "node:path"; - -const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-fb-cascade-")); -process.env.DATA_DIR = TEST_DATA_DIR; - -const core = await import("../../src/lib/db/core.ts"); -const providersDb = await import("../../src/lib/db/providers.ts"); - -import { - applyErrorState, - resetAccountState, -} from "../../open-sse/services/accountFallback.ts"; -import { - markConnectionRateLimitedUntil, - clearConnectionRateLimit, -} from "../../src/lib/localDb.ts"; - -test.after(() => { - core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); -}); - -// ── Helpers ──────────────────────────────────────────────────────────────── - -async function makeConnection(provider: string, name: string): Promise { - const conn = await providersDb.createProviderConnection({ - provider, - authType: "api_key", - name, - }); - return (conn as any).id as string; -} - -// ── applyErrorState persistence (Bug Fix A) ──────────────────────────────── - -test("applyErrorState: 429 cascade persists cooldown via setConnectionRateLimitUntil", async () => { - const connId = await makeConnection("opencode-go", "OC-GO Cascade Test"); - - assert.equal( - providersDb.isConnectionRateLimited(connId), - false, - "should start as not rate-limited", - ); - - const before = Date.now(); - applyErrorState( - { id: connId, backoffLevel: 0, status: "active" }, - 429, - "Monthly usage limit reached. Resets in 13 days.", - "opencode-go", - ); - - assert.equal( - providersDb.isConnectionRateLimited(connId), - true, - "should be rate-limited in the DB after applyErrorState with 429", - ); - - const limited = providersDb.getRateLimitedConnections("opencode-go"); - assert.ok( - limited.some((c: any) => c.id === connId), - "should appear in getRateLimitedConnections list for the provider", - ); - - // Sanity: the persisted timestamp is in the future (within reason). - const row = limited.find((c: any) => c.id === connId) as any; - if (row?.rate_limited_until) { - const ts = Number(row.rate_limited_until); - assert.ok( - ts > before, - `cooldown timestamp ${ts} must be > request start ${before}`, - ); - } -}); - -test("applyErrorState: non-429 / non-rateLimit errors do NOT persist a cooldown", async () => { - const connId = await makeConnection("opencode-go", "OC-GO Non-429"); - - // 400 with no rate-limit signals should NOT set a DB cooldown. - applyErrorState( - { id: connId, backoffLevel: 0, status: "active" }, - 400, - "Invalid request body", - "opencode-go", - ); - - assert.equal( - providersDb.isConnectionRateLimited(connId), - false, - "non-rate-limit error should not persist a cooldown", - ); -}); - -test("applyErrorState: account with no `id` does not crash and does not persist", async () => { - // No id field → DB write is skipped. - const result = applyErrorState( - { backoffLevel: 0, status: "active" } as any, - 429, - "rate limit exceeded", - "opencode-go", - ); - - assert.ok(result, "should return a new state object"); - assert.equal((result as any).status, "error"); - assert.ok((result as any).rateLimitedUntil, "in-memory rateLimitedUntil should be set"); -}); - -// ── resetAccountState persistence (Bug Fix A) ────────────────────────────── - -test("resetAccountState clears the persisted cooldown after a success", async () => { - const connId = await makeConnection("opencode-go", "OC-GO Reset Test"); - - // Force the connection into a cooled state. - providersDb.setConnectionRateLimitUntil(connId, Date.now() + 60_000); - assert.equal( - providersDb.isConnectionRateLimited(connId), - true, - "precondition: should be rate-limited after explicit set", - ); - - resetAccountState({ id: connId, backoffLevel: 1, status: "error" }); - - assert.equal( - providersDb.isConnectionRateLimited(connId), - false, - "resetAccountState should clear the persisted cooldown", - ); -}); - -// ── localDb re-exports (Bug Fix F) ────────────────────────────────────────── - -test("localDb.markConnectionRateLimitedUntil: writes cooldown; never throws on bad id", () => { - const connId = "non-existent-id-xxxxx"; - // Must not throw even though the id doesn't exist — DB write failure - // inside the wrapper must never crash the request path. - assert.doesNotThrow(() => - markConnectionRateLimitedUntil(connId, 5_000), - ); -}); - -test("localDb.clearConnectionRateLimit: does not throw on bad id", () => { - const connId = "non-existent-id-xxxxx"; - assert.doesNotThrow(() => clearConnectionRateLimit(connId)); -}); - -test("localDb.markConnectionRateLimitedUntil + clearConnectionRateLimit round-trip", async () => { - const connId = await makeConnection("opencode-go", "OC-GO RoundTrip"); - - markConnectionRateLimitedUntil(connId, 60_000); - assert.equal( - providersDb.isConnectionRateLimited(connId), - true, - "after markConnectionRateLimitedUntil the connection should be limited", - ); - - clearConnectionRateLimit(connId); - assert.equal( - providersDb.isConnectionRateLimited(connId), - false, - "after clearConnectionRateLimit the connection should not be limited", - ); -}); - -// ── Multi-account scenario (the user's exact bug) ─────────────────────────── - -test("multi-key scenario: cooling one OpenCode-Go key does NOT poison other keys", async () => { - const connA = await makeConnection("opencode-go", "OC-GO Key A"); - const connB = await makeConnection("opencode-go", "OC-GO Key B"); - - // Account A hits the monthly quota envelope. - applyErrorState( - { id: connA, backoffLevel: 0, status: "active" }, - 429, - "Monthly usage limit reached. Resets in 13 days.", - "opencode-go", - ); - - assert.equal( - providersDb.isConnectionRateLimited(connA), - true, - "key A should be rate-limited after monthly envelope", - ); - assert.equal( - providersDb.isConnectionRateLimited(connB), - false, - "key B should remain available — scope is per-connection, not per-provider", - ); -}); \ No newline at end of file +/** + * TDD regression tests for the per-connection 429 cascade DB persistence. + * + * Bug: before this fix, `applyErrorState` (open-sse/services/accountFallback.ts) + * marked a connection rate-limited IN-MEMORY ONLY — the cooldown was forgotten + * when the request ended and `isConnectionRateLimited` (the DB-backed read + * helper) always returned false for non-Antigravity providers. Result: cascade + * failures against a multi-key OpenCode-Go setup retried the same exhausted key + * on the next request and the user saw no "kill for X days" behavior. + * + * After the fix: + * 1. `applyErrorState` with a non-zero cooldown also writes + * `provider_connections.rate_limited_until` via + * `setConnectionRateLimitUntil` (best-effort, never crashes the request). + * 2. `resetAccountState` with a DB id clears that column. + * 3. The localDb re-exports `markConnectionRateLimitedUntil` and + * `clearConnectionRateLimit` for direct use by other consumers + * (e.g. provider-specific executors). + * + * These tests mirror the harness from `antigravity-429-quota-cooldown.test.ts` + * so they share the same DATA_DIR sandbox and DB reset pattern. + */ + +import { test } from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-fb-cascade-")); +process.env.DATA_DIR = TEST_DATA_DIR; + +const core = await import("../../src/lib/db/core.ts"); +const providersDb = await import("../../src/lib/db/providers.ts"); + +import { applyErrorState, resetAccountState } from "../../open-sse/services/accountFallback.ts"; +import { markConnectionRateLimitedUntil, clearConnectionRateLimit } from "../../src/lib/localDb.ts"; + +test.after(() => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +// ── Helpers ──────────────────────────────────────────────────────────────── + +async function makeConnection(provider: string, name: string): Promise { + const conn = await providersDb.createProviderConnection({ + provider, + authType: "api_key", + name, + }); + return (conn as any).id as string; +} + +// ── applyErrorState persistence (Bug Fix A) ──────────────────────────────── + +test("applyErrorState: 429 cascade persists cooldown via setConnectionRateLimitUntil", async () => { + const connId = await makeConnection("opencode-go", "OC-GO Cascade Test"); + + assert.equal( + providersDb.isConnectionRateLimited(connId), + false, + "should start as not rate-limited" + ); + + const before = Date.now(); + applyErrorState( + { id: connId, backoffLevel: 0, status: "active" }, + 429, + "Monthly usage limit reached. Resets in 13 days.", + "opencode-go" + ); + + assert.equal( + providersDb.isConnectionRateLimited(connId), + true, + "should be rate-limited in the DB after applyErrorState with 429" + ); + + const limited = providersDb.getRateLimitedConnections("opencode-go"); + assert.ok( + limited.some((c: any) => c.id === connId), + "should appear in getRateLimitedConnections list for the provider" + ); + + // Sanity: the persisted timestamp is in the future (within reason). + const row = limited.find((c: any) => c.id === connId) as any; + if (row?.rate_limited_until) { + const ts = Number(row.rate_limited_until); + assert.ok(ts > before, `cooldown timestamp ${ts} must be > request start ${before}`); + } +}); + +test("applyErrorState: non-429 / non-rateLimit errors do NOT persist a cooldown", async () => { + const connId = await makeConnection("opencode-go", "OC-GO Non-429"); + + // 400 with no rate-limit signals should NOT set a DB cooldown. + applyErrorState( + { id: connId, backoffLevel: 0, status: "active" }, + 400, + "Invalid request body", + "opencode-go" + ); + + assert.equal( + providersDb.isConnectionRateLimited(connId), + false, + "non-rate-limit error should not persist a cooldown" + ); +}); + +test("applyErrorState: account with no `id` does not crash and does not persist", async () => { + // No id field → DB write is skipped. + const result = applyErrorState( + { backoffLevel: 0, status: "active" } as any, + 429, + "rate limit exceeded", + "opencode-go" + ); + + assert.ok(result, "should return a new state object"); + assert.equal((result as any).status, "error"); + assert.ok((result as any).rateLimitedUntil, "in-memory rateLimitedUntil should be set"); +}); + +// ── resetAccountState persistence (Bug Fix A) ────────────────────────────── + +test("resetAccountState clears the persisted cooldown after a success", async () => { + const connId = await makeConnection("opencode-go", "OC-GO Reset Test"); + + // Force the connection into a cooled state. + providersDb.setConnectionRateLimitUntil(connId, Date.now() + 60_000); + assert.equal( + providersDb.isConnectionRateLimited(connId), + true, + "precondition: should be rate-limited after explicit set" + ); + + resetAccountState({ id: connId, backoffLevel: 1, status: "error" }); + + assert.equal( + providersDb.isConnectionRateLimited(connId), + false, + "resetAccountState should clear the persisted cooldown" + ); +}); + +// ── localDb re-exports (Bug Fix F) ────────────────────────────────────────── + +test("localDb.markConnectionRateLimitedUntil: writes cooldown; never throws on bad id", () => { + const connId = "non-existent-id-xxxxx"; + // Must not throw even though the id doesn't exist — DB write failure + // inside the wrapper must never crash the request path. + assert.doesNotThrow(() => markConnectionRateLimitedUntil(connId, 5_000)); +}); + +test("localDb.clearConnectionRateLimit: does not throw on bad id", () => { + const connId = "non-existent-id-xxxxx"; + assert.doesNotThrow(() => clearConnectionRateLimit(connId)); +}); + +test("localDb.markConnectionRateLimitedUntil + clearConnectionRateLimit round-trip", async () => { + const connId = await makeConnection("opencode-go", "OC-GO RoundTrip"); + + markConnectionRateLimitedUntil(connId, 60_000); + assert.equal( + providersDb.isConnectionRateLimited(connId), + true, + "after markConnectionRateLimitedUntil the connection should be limited" + ); + + clearConnectionRateLimit(connId); + assert.equal( + providersDb.isConnectionRateLimited(connId), + false, + "after clearConnectionRateLimit the connection should not be limited" + ); +}); + +// ── Multi-account scenario (the user's exact bug) ─────────────────────────── + +test("multi-key scenario: cooling one OpenCode-Go key does NOT poison other keys", async () => { + const connA = await makeConnection("opencode-go", "OC-GO Key A"); + const connB = await makeConnection("opencode-go", "OC-GO Key B"); + + // Account A hits the monthly quota envelope. + applyErrorState( + { id: connA, backoffLevel: 0, status: "active" }, + 429, + "Monthly usage limit reached. Resets in 13 days.", + "opencode-go" + ); + + assert.equal( + providersDb.isConnectionRateLimited(connA), + true, + "key A should be rate-limited after monthly envelope" + ); + assert.equal( + providersDb.isConnectionRateLimited(connB), + false, + "key B should remain available — scope is per-connection, not per-provider" + ); +}); diff --git a/tests/unit/pick-internal-api-key-6372.test.ts b/tests/unit/pick-internal-api-key-6372.test.ts index 483235feecf5..569f7879e669 100644 --- a/tests/unit/pick-internal-api-key-6372.test.ts +++ b/tests/unit/pick-internal-api-key-6372.test.ts @@ -19,14 +19,14 @@ const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); function reset() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.beforeEach(() => reset()); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#6372: returns null when there are no keys", async () => { diff --git a/tests/unit/piiReproduction.test.ts b/tests/unit/piiReproduction.test.ts index a1c984b91137..7c1d56bb7618 100644 --- a/tests/unit/piiReproduction.test.ts +++ b/tests/unit/piiReproduction.test.ts @@ -15,11 +15,11 @@ import { sanitizePII } from "../../src/lib/piiSanitizer"; test("PII Reproduction Tests", async (t) => { // Setup overrides for tests const originalEnv = process.env; - process.env = { + process.env = { ...originalEnv, PII_RESPONSE_SANITIZATION: "true", PII_RESPONSE_SANITIZATION_MODE: "redact", - PII_TEST_BYPASS_MIN_WINDOW: "true" + PII_TEST_BYPASS_MIN_WINDOW: "true", }; await t.test("THEORY-001: Infinite Streaming Buffer Accumulation", async () => { @@ -30,25 +30,32 @@ test("PII Reproduction Tests", async (t) => { // Collect all output via pipeTo (non-blocking, handles lifecycle properly) const chunks: Uint8Array[] = []; const collector = new WritableStream({ - write(chunk) { chunks.push(chunk); } + write(chunk) { + chunks.push(chunk); + }, }); const pipePromise = transform.readable.pipeTo(collector); // Write 50 alphanumeric characters starting with "sk-" const piiText = "sk-123456789012345678901234567890123456789012345678"; // 51 chars - await writer.write(encoder.encode(`data: ${JSON.stringify({ choices: [{ delta: { content: piiText } }] })}\n`)); + await writer.write( + encoder.encode(`data: ${JSON.stringify({ choices: [{ delta: { content: piiText } }] })}\n`) + ); // Wait a bit — if the buffer is withheld (W=10, PII window), nothing should be emitted yet await new Promise((r) => setTimeout(r, 150)); - const preCloseOutput = chunks.map(c => new TextDecoder().decode(c)).join(""); - assert.ok(!preCloseOutput.includes("[API_KEY_REDACTED]"), "Nothing should be emitted before close because buffer is indefinitely withheld"); + const preCloseOutput = chunks.map((c) => new TextDecoder().decode(c)).join(""); + assert.ok( + !preCloseOutput.includes("[API_KEY_REDACTED]"), + "Nothing should be emitted before close because buffer is indefinitely withheld" + ); // Close the writer — this triggers flush which emits the redacted output await writer.close(); await pipePromise; - const decoded = chunks.map(c => new TextDecoder().decode(c)).join(""); + const decoded = chunks.map((c) => new TextDecoder().decode(c)).join(""); assert.ok(decoded.includes("[API_KEY_REDACTED]"), "Flushed output should be redacted"); }); @@ -61,8 +68,16 @@ test("PII Reproduction Tests", async (t) => { const resultSoftHyphen = sanitizePII(keyWithSoftHyphen); // Sanitizer now correctly catches unicode-obfuscated keys - assert.strictEqual(resultWordJoiner.text, "[API_KEY_REDACTED]", "API Key with Word Joiner is now correctly redacted"); - assert.strictEqual(resultSoftHyphen.text, "[API_KEY_REDACTED]", "API Key with Soft Hyphen is now correctly redacted"); + assert.strictEqual( + resultWordJoiner.text, + "[API_KEY_REDACTED]", + "API Key with Word Joiner is now correctly redacted" + ); + assert.strictEqual( + resultSoftHyphen.text, + "[API_KEY_REDACTED]", + "API Key with Soft Hyphen is now correctly redacted" + ); // 2. IPv6 lookbehind/lookahead issues // xyz::1 (preceded by non-hex alphabetic characters) should NOT be redacted @@ -71,11 +86,19 @@ test("PII Reproduction Tests", async (t) => { // abc::1 (preceded by valid hex characters) is a valid compressed IPv6 address and should be redacted const resultIpv6ValidCompressed = sanitizePII("abc::1"); - assert.strictEqual(resultIpv6ValidCompressed.text, "[IP_REDACTED]", "abc::1 should be redacted as a valid compressed IP"); + assert.strictEqual( + resultIpv6ValidCompressed.text, + "[IP_REDACTED]", + "abc::1 should be redacted as a valid compressed IP" + ); // Invalid IPv6 followed by letters should NOT be redacted const resultIpv6Lookahead = sanitizePII("2001:db8:3333:4444:5555:6666:7777:8888abcd"); - assert.strictEqual(resultIpv6Lookahead.text, "2001:db8:3333:4444:5555:6666:7777:8888abcd", "Invalid IPv6 with trailing characters should not be redacted"); + assert.strictEqual( + resultIpv6Lookahead.text, + "2001:db8:3333:4444:5555:6666:7777:8888abcd", + "Invalid IPv6 with trailing characters should not be redacted" + ); // Valid IPv6 is correctly redacted const resultIpv6Valid = sanitizePII("2001:db8:3333:4444:5555:6666:7777:8888"); @@ -86,7 +109,11 @@ test("PII Reproduction Tests", async (t) => { // 16-digit database ID/Snowflake ID — no longer falsely flagged as credit card const snowflakeId = "1234567890123456"; const resultCc = sanitizePII(snowflakeId); - assert.strictEqual(resultCc.text, snowflakeId, "16-digit numeric identifier should not be redacted as Credit Card"); + assert.strictEqual( + resultCc.text, + snowflakeId, + "16-digit numeric identifier should not be redacted as Credit Card" + ); // 11-digit database ID — now caught as phone number by sanitizer const dbId11 = "12345678901"; @@ -101,25 +128,39 @@ test("PII Reproduction Tests", async (t) => { const transformA = createPiiSseTransform({ windowSize: 10 }); const writerA = transformA.writable.getWriter(); const chunksA: Uint8Array[] = []; - const collectorA = new WritableStream({ write(chunk) { chunksA.push(chunk); } }); + const collectorA = new WritableStream({ + write(chunk) { + chunksA.push(chunk); + }, + }); const pipeA = transformA.readable.pipeTo(collectorA); await writerA.write(encoder.encode("data: Hello world\n")); await writerA.close(); await pipeA; - const outputA = chunksA.map(c => new TextDecoder().decode(c)).join(""); + const outputA = chunksA.map((c) => new TextDecoder().decode(c)).join(""); // Bug (fixed by #3021): raw-text SSE was being wrapped in an OpenAI JSON envelope on flush. // After the fix, raw text passes through as raw text — the envelope must NOT appear. - assert.ok(!outputA.includes('{"choices":'), "Scenario A: raw text must NOT be wrapped in a JSON choices envelope"); + assert.ok( + !outputA.includes('{"choices":'), + "Scenario A: raw text must NOT be wrapped in a JSON choices envelope" + ); // The content must still be present in the output (not silently dropped) - assert.ok(outputA.includes("Hello world") || outputA.length > "data: \n".length, "Scenario A: raw text content must not be silently dropped"); + assert.ok( + outputA.includes("Hello world") || outputA.length > "data: \n".length, + "Scenario A: raw text content must not be silently dropped" + ); // Scenario B: Non-standard JSON stream — use pipeTo const transformB = createPiiSseTransform({ windowSize: 10 }); const writerB = transformB.writable.getWriter(); const chunksB: Uint8Array[] = []; - const collectorB = new WritableStream({ write(chunk) { chunksB.push(chunk); } }); + const collectorB = new WritableStream({ + write(chunk) { + chunksB.push(chunk); + }, + }); const pipeB = transformB.readable.pipeTo(collectorB); await writerB.write(encoder.encode('data: {"msg": "Hello world"}\n')); @@ -127,15 +168,18 @@ test("PII Reproduction Tests", async (t) => { await writerB.close(); await pipeB; - const outputB = chunksB.map(c => new TextDecoder().decode(c)).join(""); + const outputB = chunksB.map((c) => new TextDecoder().decode(c)).join(""); // Bug (fixed by #3021): buffered content was permanently lost when the stop signal had no string fields. // After the fix, the content is emitted (possibly split across chunks due to the PII window). // Verify the content is present — "H" from first window emit + "ello world" from flush. - assert.ok(outputB.includes('"H"') && outputB.includes("ello world"), "Scenario B: buffered content must not be lost — expect window-split output containing both parts"); + assert.ok( + outputB.includes('"H"') && outputB.includes("ello world"), + "Scenario B: buffered content must not be lost — expect window-split output containing both parts" + ); }); }); test.after(() => { resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); diff --git a/tests/unit/piiSanitizer.test.ts b/tests/unit/piiSanitizer.test.ts index 8a035b358d68..042ad5c45aee 100644 --- a/tests/unit/piiSanitizer.test.ts +++ b/tests/unit/piiSanitizer.test.ts @@ -96,7 +96,7 @@ test("sanitizePII checks resolveFeatureFlag, not process.env", async (t) => { test.after(async () => { const coreDb = await import("@/lib/db/core"); coreDb.resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("getMode returns redact for invalid flag values", async () => { diff --git a/tests/unit/piiSanitizerIpv6.test.ts b/tests/unit/piiSanitizerIpv6.test.ts index 03fdb4c35391..272319c9da1a 100644 --- a/tests/unit/piiSanitizerIpv6.test.ts +++ b/tests/unit/piiSanitizerIpv6.test.ts @@ -129,7 +129,11 @@ test("IPv6 followed by colon-hex suffix is NOT redacted (lookahead guard)", () = // being carved out of a longer colon-separated sequence. const text = "1:2:3:4:5:6:7:8:extra"; const result = sanitizePII(text); - assert.strictEqual(result.text, text, "8-segment prefix of a longer colon sequence should not be redacted"); + assert.strictEqual( + result.text, + text, + "8-segment prefix of a longer colon sequence should not be redacted" + ); }); test("IPv6 xyz::1 (non-hex prefix) is NOT redacted", () => { @@ -141,7 +145,10 @@ test("IPv6 xyz::1 (non-hex prefix) is NOT redacted", () => { test("IPv6 abc::1 (valid hex prefix) IS redacted", () => { // a, b, c are valid hex digits, so abc::1 is a valid compressed IPv6 address. const result = sanitizePII("abc::1"); - assert.ok(result.text.includes("[IP_REDACTED]"), "abc::1 should be redacted as valid compressed IPv6"); + assert.ok( + result.text.includes("[IP_REDACTED]"), + "abc::1 should be redacted as valid compressed IPv6" + ); }); test("IPv6 full 8-segment with trailing alphanumeric is NOT redacted", () => { @@ -149,7 +156,11 @@ test("IPv6 full 8-segment with trailing alphanumeric is NOT redacted", () => { // a letter/digit (8888abcd). const text = "2001:db8:3333:4444:5555:6666:7777:8888abcd"; const result = sanitizePII(text); - assert.strictEqual(result.text, text, "8-segment address with trailing alnum should not be redacted"); + assert.strictEqual( + result.text, + text, + "8-segment address with trailing alnum should not be redacted" + ); }); test("multiple IPv6 addresses in the same string are all redacted", () => { @@ -174,9 +185,11 @@ test("IPv6 address inside SSE JSON content is redacted end-to-end", async () => const encoder = new TextEncoder(); const writePromise = (async () => { - await writer.write(encoder.encode( - `data: {"choices":[{"delta":{"content":"server is at 2001:db8:3333:4444:5555:6666:7777:8888"}}]}\n\n` - )); + await writer.write( + encoder.encode( + `data: {"choices":[{"delta":{"content":"server is at 2001:db8:3333:4444:5555:6666:7777:8888"}}]}\n\n` + ) + ); await writer.write(encoder.encode(`data: [DONE]\n\n`)); await writer.close(); })(); @@ -190,13 +203,17 @@ test("IPv6 address inside SSE JSON content is redacted end-to-end", async () => await writePromise; const output = chunks.join(""); - assert.ok(!output.includes("2001:db8:3333:4444:5555:6666:7777:8888"), - "full IPv6 address in SSE stream should be redacted"); - assert.ok(output.includes("[IP_REDACTED]"), - "redaction marker should appear in SSE stream output"); + assert.ok( + !output.includes("2001:db8:3333:4444:5555:6666:7777:8888"), + "full IPv6 address in SSE stream should be redacted" + ); + assert.ok( + output.includes("[IP_REDACTED]"), + "redaction marker should appear in SSE stream output" + ); }); test.after(() => { resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); -}); \ No newline at end of file + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); diff --git a/tests/unit/playground-key-policy-3503.test.ts b/tests/unit/playground-key-policy-3503.test.ts index fd6a87850c39..67263d8444b1 100644 --- a/tests/unit/playground-key-policy-3503.test.ts +++ b/tests/unit/playground-key-policy-3503.test.ts @@ -48,14 +48,18 @@ function req(headers: Record) { } test.after(() => { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#3503 — authenticated session + key-id header resolves the key secret server-side", async () => { const out = await resolvePlaygroundTestKey( req({ [PLAYGROUND_KEY_ID_HEADER]: KEY_ID, cookie: await sessionCookie() }) ); - assert.equal(out, KEY_SECRET, "an authenticated session should resolve the selected key's secret by id"); + assert.equal( + out, + KEY_SECRET, + "an authenticated session should resolve the selected key's secret by id" + ); }); test("#3503 — SECURITY: the key-id header is IGNORED without an authenticated session", async () => { diff --git a/tests/unit/playground-simulate-route-persisted-combo.test.ts b/tests/unit/playground-simulate-route-persisted-combo.test.ts index 311ee55f9f72..02e2c61f121d 100644 --- a/tests/unit/playground-simulate-route-persisted-combo.test.ts +++ b/tests/unit/playground-simulate-route-persisted-combo.test.ts @@ -16,7 +16,7 @@ let persistedComboId: string; test.beforeEach(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); await providersDb.createProviderConnection({ provider: "cc", @@ -42,7 +42,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function request(body: unknown): Request { @@ -76,9 +76,7 @@ test("simulates persisted combo model steps in order", async () => { // #11822 follow-up: combo-ref steps now get a specific warning naming the // referenced combo instead of folding into the generic "unsupported step" // count (that count is reserved for genuinely unrecognized step shapes). - assert.ok( - body.warnings.some((warning: string) => warning.includes('combo "nested combo"')) - ); + assert.ok(body.warnings.some((warning: string) => warning.includes('combo "nested combo"'))); assert.ok(body.warnings.every((warning: string) => !warning.includes("not configured"))); }); @@ -104,7 +102,9 @@ test("surfaces a provider-wildcard step as an unresolved target with a specific ] ); assert.ok( - body.warnings.some((warning: string) => warning.includes("groq/llama-*") && warning.includes("wildcard")) + body.warnings.some( + (warning: string) => warning.includes("groq/llama-*") && warning.includes("wildcard") + ) ); }); diff --git a/tests/unit/plugins-config-route.test.ts b/tests/unit/plugins-config-route.test.ts index 0b90bb65b334..428b47c47dfc 100644 --- a/tests/unit/plugins-config-route.test.ts +++ b/tests/unit/plugins-config-route.test.ts @@ -46,13 +46,19 @@ function validateConfig( return { valid: false, error: `Config key '${key}' must be a ${def.type}` }; } if (def.enum && !(def.enum as unknown[]).includes(val)) { - return { valid: false, error: `Config key '${key}' must be one of: ${(def.enum as string[]).join(", ")}` }; + return { + valid: false, + error: `Config key '${key}' must be one of: ${(def.enum as string[]).join(", ")}`, + }; } if (def.min !== undefined) { const limit = def.min; const size = typeof val === "string" ? val.length : typeof val === "number" ? val : undefined; if (size !== undefined && size < limit) { - return { valid: false, error: `Config key '${key}' must be at least ${limit}${typeof val === "string" ? " characters" : ""}` }; + return { + valid: false, + error: `Config key '${key}' must be at least ${limit}${typeof val === "string" ? " characters" : ""}`, + }; } } if (def.max !== undefined && typeof val === "number" && val > def.max) { @@ -66,13 +72,15 @@ function validateConfig( test.beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); }); test.after(() => { core.resetDbInstance(); - try { fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); } catch {} + try { + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + } catch {} }); // ── Test schema ── @@ -126,7 +134,9 @@ test("PUT: updates config via updatePluginConfig", () => { configSchema: testSchema, }); - const success = dbPlugins.updatePluginConfig("config-put-test", { apiUrl: "https://new.api.com" }); + const success = dbPlugins.updatePluginConfig("config-put-test", { + apiUrl: "https://new.api.com", + }); assert.ok(success); const plugin = dbPlugins.getPluginByName("config-put-test"); diff --git a/tests/unit/plugins-dev-mode.test.ts b/tests/unit/plugins-dev-mode.test.ts index 2795930c3a48..dffb7b960cad 100644 --- a/tests/unit/plugins-dev-mode.test.ts +++ b/tests/unit/plugins-dev-mode.test.ts @@ -10,13 +10,17 @@ describe("devMode", () => { afterEach(() => { stopDevMode(); - try { rmSync(testDir, { recursive: true, force: true }); } catch {} + try { + rmSync(testDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + } catch {} }); it("startDevMode creates watcher without throwing", () => { mkdirSync(testDir, { recursive: true }); let reloadCalled = false; - startDevMode(testDir, async () => { reloadCalled = true; }); + startDevMode(testDir, async () => { + reloadCalled = true; + }); // Watcher is active — no crash assert.ok(true); }); diff --git a/tests/unit/plugins-doctor.test.ts b/tests/unit/plugins-doctor.test.ts index 3f2dd5d4c9d8..1c3b2e0e0357 100644 --- a/tests/unit/plugins-doctor.test.ts +++ b/tests/unit/plugins-doctor.test.ts @@ -21,13 +21,20 @@ describe("runPluginDoctor", () => { }); afterEach(() => { - try { rmSync(testDir, { recursive: true, force: true }); } catch {} + try { + rmSync(testDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + } catch {} }); it("healthy plugin with valid manifest and entry point", async () => { - writeFileSync(join(pluginDir, "plugin.json"), JSON.stringify({ - name: "test-plugin", version: "1.0.0", main: "index.js", - })); + writeFileSync( + join(pluginDir, "plugin.json"), + JSON.stringify({ + name: "test-plugin", + version: "1.0.0", + main: "index.js", + }) + ); writeFileSync(join(pluginDir, "index.js"), "export default {}"); const result = await runPluginDoctor(pluginDir, "test-plugin"); // Plugin not in DB → db_status_correct is "warn" → overall "degraded" @@ -48,17 +55,27 @@ describe("runPluginDoctor", () => { }); it("reports missing entry point", async () => { - writeFileSync(join(pluginDir, "plugin.json"), JSON.stringify({ - name: "no-entry", version: "1.0.0", main: "index.js", - })); + writeFileSync( + join(pluginDir, "plugin.json"), + JSON.stringify({ + name: "no-entry", + version: "1.0.0", + main: "index.js", + }) + ); const result = await runPluginDoctor(pluginDir, "no-entry"); assert.ok(result.checks.some((c) => c.name === "entry_point_exists" && c.status === "fail")); }); it("degraded when only warnings", async () => { - writeFileSync(join(pluginDir, "plugin.json"), JSON.stringify({ - name: "warn-plugin", version: "1.0.0", main: "index.ts", - })); + writeFileSync( + join(pluginDir, "plugin.json"), + JSON.stringify({ + name: "warn-plugin", + version: "1.0.0", + main: "index.ts", + }) + ); writeFileSync(join(pluginDir, "index.ts"), "export default {}"); const result = await runPluginDoctor(pluginDir, "warn-plugin"); // .ts extension should produce a warn on can_spawn diff --git a/tests/unit/plugins-edge-cases.test.ts b/tests/unit/plugins-edge-cases.test.ts index a76383c3ab70..6074e23156e4 100644 --- a/tests/unit/plugins-edge-cases.test.ts +++ b/tests/unit/plugins-edge-cases.test.ts @@ -15,20 +15,16 @@ const core = await import("../../src/lib/db/core.ts"); const dbPlugins = await import("../../src/lib/db/plugins.ts"); const { scanPluginDir } = await import("../../src/lib/plugins/scanner.ts"); const { pluginManager } = await import("../../src/lib/plugins/manager.ts"); -const { - registerHook, - unregisterHooks, - emitHook, - emitHookBlocking, - resetHooks, - getHooks, -} = await import("../../src/lib/plugins/hooks.ts"); +const { registerHook, unregisterHooks, emitHook, emitHookBlocking, resetHooks, getHooks } = + await import("../../src/lib/plugins/hooks.ts"); const activeSourceDirs: string[] = []; function cleanupSourceDirs() { for (const dir of activeSourceDirs) { - try { fs.rmSync(dir, { recursive: true, force: true }); } catch {} + try { + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + } catch {} } activeSourceDirs.length = 0; } @@ -65,10 +61,14 @@ function writeTestPlugin(opts: { let indexJs = opts.indexJs; if (!indexJs) { const handlers: string[] = []; - if (opts.onRequest) handlers.push(`onRequest: function(ctx) { ctx.metadata = ctx.metadata || {}; ctx.metadata.hookCalled = true; }`); + if (opts.onRequest) + handlers.push( + `onRequest: function(ctx) { ctx.metadata = ctx.metadata || {}; ctx.metadata.hookCalled = true; }` + ); if (opts.onResponse) handlers.push(`onResponse: function(ctx, resp) { return resp; }`); if (opts.onError) handlers.push(`onError: function(ctx, err) {}`); - indexJs = handlers.length > 0 ? `module.exports = { ${handlers.join(", ")} };` : `module.exports = {};`; + indexJs = + handlers.length > 0 ? `module.exports = { ${handlers.join(", ")} };` : `module.exports = {};`; } fs.writeFileSync(path.join(pluginDir, "index.js"), indexJs); @@ -94,7 +94,7 @@ test.beforeEach(() => { // Production DB may not have the plugins table — ignore; fresh DB created below. } resetHooks(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); cleanupSourceDirs(); }); @@ -102,7 +102,9 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); cleanupSourceDirs(); - try { fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); } catch {} + try { + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + } catch {} }); // ══════════════════════════════════════════ @@ -141,7 +143,9 @@ test("scanner: invalid JSON manifest reports error", async () => { const result = await scanPluginDir(badDir); assert.equal(result.plugins.length, 0); assert.equal(result.errors.length, 1); - assert.ok(result.errors[0].error.includes("invalid manifest") || result.errors[0].error.includes("JSON")); + assert.ok( + result.errors[0].error.includes("invalid manifest") || result.errors[0].error.includes("JSON") + ); }); test("scanner: missing required fields reports error", async () => { @@ -192,7 +196,9 @@ test("manager: install with null bytes in path throws", async () => { () => pluginManager.install("/tmp/test\0malicious"), (err: Error) => { assert.ok( - err.message.includes("Invalid") || err.message.includes("null") || err.message.includes("No valid plugin found"), + err.message.includes("Invalid") || + err.message.includes("null") || + err.message.includes("No valid plugin found"), `Unexpected error: ${err.message}` ); return true; @@ -204,10 +210,7 @@ test("manager: double install same plugin throws", async () => { const { sourceDir, name } = writeTestPlugin({ name: "double-install" }); await pluginManager.install(sourceDir); - await assert.rejects( - () => pluginManager.install(sourceDir), - /already installed/ - ); + await assert.rejects(() => pluginManager.install(sourceDir), /already installed/); await pluginManager.uninstall(name); }); @@ -256,7 +259,10 @@ test("manager: activate registers hooks from manifest", async () => { assert.ok(getHooks("onRequest").find((r) => r.pluginName === name)); assert.ok(getHooks("onResponse").find((r) => r.pluginName === name)); - assert.equal(getHooks("onError").find((r) => r.pluginName === name), undefined); + assert.equal( + getHooks("onError").find((r) => r.pluginName === name), + undefined + ); await pluginManager.uninstall(name); }); @@ -278,9 +284,18 @@ test("manager: deactivate unregisters all hooks", async () => { await pluginManager.deactivate(name); - assert.equal(getHooks("onRequest").find((r) => r.pluginName === name), undefined); - assert.equal(getHooks("onResponse").find((r) => r.pluginName === name), undefined); - assert.equal(getHooks("onError").find((r) => r.pluginName === name), undefined); + assert.equal( + getHooks("onRequest").find((r) => r.pluginName === name), + undefined + ); + assert.equal( + getHooks("onResponse").find((r) => r.pluginName === name), + undefined + ); + assert.equal( + getHooks("onError").find((r) => r.pluginName === name), + undefined + ); await pluginManager.uninstall(name); }); @@ -297,9 +312,30 @@ test("hooks: emitHookBlocking with no handlers returns empty body", async () => test("hooks: multiple plugins on same event fire in priority order", async () => { const order: string[] = []; - registerHook("onRequest", "low", () => { order.push("low"); }, 200); - registerHook("onRequest", "high", () => { order.push("high"); }, 10); - registerHook("onRequest", "mid", () => { order.push("mid"); }, 100); + registerHook( + "onRequest", + "low", + () => { + order.push("low"); + }, + 200 + ); + registerHook( + "onRequest", + "high", + () => { + order.push("high"); + }, + 10 + ); + registerHook( + "onRequest", + "mid", + () => { + order.push("mid"); + }, + 100 + ); await emitHookBlocking("onRequest", { body: {}, metadata: {} }); assert.deepEqual(order, ["high", "mid", "low"]); @@ -312,7 +348,9 @@ test("hooks: handler that returns undefined does not modify payload", async () = }); test("hooks: handler error in emitHookBlocking stops chain", async () => { - registerHook("onRequest", "bad", () => { throw new Error("handler error"); }); + registerHook("onRequest", "bad", () => { + throw new Error("handler error"); + }); registerHook("onRequest", "good", () => ({ metadata: { from: "good" } })); // emitHookBlocking should handle the error gracefully @@ -383,8 +421,22 @@ test("db: updatePluginConfig replaces existing config", () => { }); test("db: listPlugins with no status returns all", () => { - dbPlugins.insertPlugin({ id: "p1", name: "alpha", version: "1.0.0", main: "index.js", pluginDir: "/tmp/a", manifest: {} }); - dbPlugins.insertPlugin({ id: "p2", name: "beta", version: "1.0.0", main: "index.js", pluginDir: "/tmp/b", manifest: {} }); + dbPlugins.insertPlugin({ + id: "p1", + name: "alpha", + version: "1.0.0", + main: "index.js", + pluginDir: "/tmp/a", + manifest: {}, + }); + dbPlugins.insertPlugin({ + id: "p2", + name: "beta", + version: "1.0.0", + main: "index.js", + pluginDir: "/tmp/b", + manifest: {}, + }); const all = dbPlugins.listPlugins(); assert.equal(all.length, 2); @@ -394,8 +446,22 @@ test("db: listPlugins with no status returns all", () => { }); test("db: listPlugins with status filters correctly", () => { - dbPlugins.insertPlugin({ id: "f1", name: "installed-filter", version: "1.0.0", main: "index.js", pluginDir: "/tmp/f1", manifest: {} }); - dbPlugins.insertPlugin({ id: "f2", name: "active-filter", version: "1.0.0", main: "index.js", pluginDir: "/tmp/f2", manifest: {} }); + dbPlugins.insertPlugin({ + id: "f1", + name: "installed-filter", + version: "1.0.0", + main: "index.js", + pluginDir: "/tmp/f1", + manifest: {}, + }); + dbPlugins.insertPlugin({ + id: "f2", + name: "active-filter", + version: "1.0.0", + main: "index.js", + pluginDir: "/tmp/f2", + manifest: {}, + }); dbPlugins.updatePluginStatus("active-filter", "active"); const installed = dbPlugins.listPlugins("installed"); @@ -408,14 +474,28 @@ test("db: listPlugins with status filters correctly", () => { }); test("db: pluginExists returns true/false correctly", () => { - dbPlugins.insertPlugin({ id: "exists-test", name: "exists-test", version: "1.0.0", main: "index.js", pluginDir: "/tmp/e", manifest: {} }); + dbPlugins.insertPlugin({ + id: "exists-test", + name: "exists-test", + version: "1.0.0", + main: "index.js", + pluginDir: "/tmp/e", + manifest: {}, + }); assert.equal(dbPlugins.pluginExists("exists-test"), true); assert.equal(dbPlugins.pluginExists("nope"), false); }); test("db: deletePlugin returns true when plugin exists, false when not", () => { - dbPlugins.insertPlugin({ id: "del-test", name: "del-test", version: "1.0.0", main: "index.js", pluginDir: "/tmp/d", manifest: {} }); + dbPlugins.insertPlugin({ + id: "del-test", + name: "del-test", + version: "1.0.0", + main: "index.js", + pluginDir: "/tmp/d", + manifest: {}, + }); assert.equal(dbPlugins.deletePlugin("del-test"), true); assert.equal(dbPlugins.deletePlugin("del-test"), false); diff --git a/tests/unit/plugins-fs-safety.test.ts b/tests/unit/plugins-fs-safety.test.ts index 2e2d695fb026..f574f1597151 100644 --- a/tests/unit/plugins-fs-safety.test.ts +++ b/tests/unit/plugins-fs-safety.test.ts @@ -34,10 +34,7 @@ const managerSource = readFileSync( pathResolve(process.cwd(), "src/lib/plugins/manager.ts"), "utf-8" ); -const loaderSource = readFileSync( - pathResolve(process.cwd(), "src/lib/plugins/loader.ts"), - "utf-8" -); +const loaderSource = readFileSync(pathResolve(process.cwd(), "src/lib/plugins/loader.ts"), "utf-8"); // ── Fixture helpers ─────────────────────────────────────────────────────────── @@ -74,7 +71,8 @@ function writePluginWithMain(opts: { ); // Write the main file only for safe relative paths - const shouldWrite = opts.writeMainFile !== false && !opts.main.startsWith("..") && !path.isAbsolute(opts.main); + const shouldWrite = + opts.writeMainFile !== false && !opts.main.startsWith("..") && !path.isAbsolute(opts.main); if (shouldWrite) { const mainAbs = path.join(sourceDir, opts.main); fs.mkdirSync(path.dirname(mainAbs), { recursive: true }); @@ -110,14 +108,19 @@ function cleanInstalledPluginDirs() { // Remove final dir and any staging remnants const base = path.join(DEFAULT_PLUGIN_DIR, name); try { - fs.rmSync(base, { recursive: true, force: true }); + fs.rmSync(base, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch {} // Also clean any .staging-* leftovers if (fs.existsSync(DEFAULT_PLUGIN_DIR)) { for (const entry of fs.readdirSync(DEFAULT_PLUGIN_DIR)) { if (entry.startsWith(`${name}.staging-`)) { try { - fs.rmSync(path.join(DEFAULT_PLUGIN_DIR, entry), { recursive: true, force: true }); + fs.rmSync(path.join(DEFAULT_PLUGIN_DIR, entry), { + recursive: true, + force: true, + maxRetries: 5, + retryDelay: 100, + }); } catch {} } } @@ -128,7 +131,7 @@ function cleanInstalledPluginDirs() { function cleanSourceDirs() { for (const d of activeDirs) { try { - fs.rmSync(d, { recursive: true, force: true }); + fs.rmSync(d, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch {} } activeDirs.length = 0; @@ -137,7 +140,7 @@ function cleanSourceDirs() { test.beforeEach(() => { core.resetDbInstance(); hooks.resetHooks(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); cleanSourceDirs(); cleanInstalledPluginDirs(); @@ -148,7 +151,7 @@ test.after(() => { cleanSourceDirs(); cleanInstalledPluginDirs(); try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch {} }); @@ -306,7 +309,8 @@ test("source: assertWithinPluginDir is called before rm in uninstall", () => { // Get the slice from uninstall through the next method const afterUninstall = managerSource.slice(uninstallIdx); const nextMethodIdx = afterUninstall.indexOf("\n async ", 10); - const uninstallBody = nextMethodIdx !== -1 ? afterUninstall.slice(0, nextMethodIdx) : afterUninstall; + const uninstallBody = + nextMethodIdx !== -1 ? afterUninstall.slice(0, nextMethodIdx) : afterUninstall; const guardIdx = uninstallBody.indexOf("assertWithinPluginDir"); const rmIdx = uninstallBody.indexOf("await rm("); @@ -342,7 +346,9 @@ test("source: assertWithinPluginDir throws for path outside pluginDir", () => { // resolve("/tmp/evil") is not fine when root is "/plugins". // Since we can't easily import the unexported helper, verify it uses resolve + sep. assert.ok( - managerSource.includes('resolve(pluginRoot)') || managerSource.includes('resolve(this_pluginDir)') || managerSource.includes('resolve('), + managerSource.includes("resolve(pluginRoot)") || + managerSource.includes("resolve(this_pluginDir)") || + managerSource.includes("resolve("), "assertWithinPluginDir must call resolve()" ); assert.ok( diff --git a/tests/unit/plugins-loader.test.ts b/tests/unit/plugins-loader.test.ts index 90b5c6ed480f..c7211c69ac0d 100644 --- a/tests/unit/plugins-loader.test.ts +++ b/tests/unit/plugins-loader.test.ts @@ -88,7 +88,7 @@ test( t.after(async () => { loaded?.cleanup(); - await rm(pluginDir, { recursive: true, force: true }); + await rm(pluginDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); await writeFile( @@ -148,7 +148,7 @@ test( t.after(async () => { loaded?.cleanup(); - await rm(pluginDir, { recursive: true, force: true }); + await rm(pluginDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); await writeFile( @@ -224,8 +224,8 @@ test( if (value === undefined) delete process.env[key]; else process.env[key] = value; } - await rm(pluginDir, { recursive: true, force: true }); - await rm(hostScriptDir, { recursive: true, force: true }); + await rm(pluginDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + await rm(hostScriptDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); await writeFile(entryPoint, "export async function onRequest() { return {}; }\n", "utf-8"); diff --git a/tests/unit/plugins-logger.test.ts b/tests/unit/plugins-logger.test.ts index da3b96d2a90e..84c040e18ca0 100644 --- a/tests/unit/plugins-logger.test.ts +++ b/tests/unit/plugins-logger.test.ts @@ -9,7 +9,9 @@ describe("PluginLogger", () => { const testDir = join(tmpdir(), `plugin-logger-test-${Date.now()}`); afterEach(() => { - try { rmSync(testDir, { recursive: true, force: true }); } catch {} + try { + rmSync(testDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + } catch {} }); it("creates log file and writes JSON entries", () => { diff --git a/tests/unit/plugins-manager-lifecycle.test.ts b/tests/unit/plugins-manager-lifecycle.test.ts index be2139cf00aa..02d34697562e 100644 --- a/tests/unit/plugins-manager-lifecycle.test.ts +++ b/tests/unit/plugins-manager-lifecycle.test.ts @@ -58,7 +58,12 @@ describe("pluginManager lifecycle", () => { assert.ok(dbRow); assert.equal(dbRow!.status, "installed"); } finally { - rmSync(dir.split("/").slice(0, -1).join("/"), { recursive: true, force: true }); + rmSync(dir.split("/").slice(0, -1).join("/"), { + recursive: true, + force: true, + maxRetries: 5, + retryDelay: 100, + }); } }); @@ -81,7 +86,12 @@ describe("pluginManager lifecycle", () => { // the plugin's child process — without it the child outlives the test and its // IPC channel keeps this process's event loop alive after the suite finishes. await mod.pluginManager.deactivate("activate-test").catch(() => {}); - rmSync(dir.split("/").slice(0, -1).join("/"), { recursive: true, force: true }); + rmSync(dir.split("/").slice(0, -1).join("/"), { + recursive: true, + force: true, + maxRetries: 5, + retryDelay: 100, + }); } }); @@ -95,7 +105,12 @@ describe("pluginManager lifecycle", () => { const dbRow = db.getPluginByName("deactivate-test"); assert.equal(dbRow!.status, "inactive"); } finally { - rmSync(dir.split("/").slice(0, -1).join("/"), { recursive: true, force: true }); + rmSync(dir.split("/").slice(0, -1).join("/"), { + recursive: true, + force: true, + maxRetries: 5, + retryDelay: 100, + }); } }); @@ -114,7 +129,12 @@ describe("pluginManager lifecycle", () => { const dbRow = db.getPluginByName("uninstall-test"); assert.equal(dbRow, null); } finally { - rmSync(dir.split("/").slice(0, -1).join("/"), { recursive: true, force: true }); + rmSync(dir.split("/").slice(0, -1).join("/"), { + recursive: true, + force: true, + maxRetries: 5, + retryDelay: 100, + }); } }); }); diff --git a/tests/unit/plugins-manager-restart-reload-7806.test.ts b/tests/unit/plugins-manager-restart-reload-7806.test.ts index 31e4c18b8037..93de2df07e82 100644 --- a/tests/unit/plugins-manager-restart-reload-7806.test.ts +++ b/tests/unit/plugins-manager-restart-reload-7806.test.ts @@ -103,7 +103,12 @@ describe("pluginManager reload after restart (#7806)", () => { // Deactivate to kill the reloaded child process — otherwise it dangles and // keeps the test runner's event loop alive after the suite finishes. await mod.pluginManager.deactivate(name).catch(() => {}); - rmSync(dir.split("/").slice(0, -1).join("/"), { recursive: true, force: true }); + rmSync(dir.split("/").slice(0, -1).join("/"), { + recursive: true, + force: true, + maxRetries: 5, + retryDelay: 100, + }); } }); @@ -130,7 +135,12 @@ describe("pluginManager reload after restart (#7806)", () => { // Deactivate to kill the reloaded child process — otherwise it dangles and // keeps the test runner's event loop alive after the suite finishes. await mod.pluginManager.deactivate(name).catch(() => {}); - rmSync(dir.split("/").slice(0, -1).join("/"), { recursive: true, force: true }); + rmSync(dir.split("/").slice(0, -1).join("/"), { + recursive: true, + force: true, + maxRetries: 5, + retryDelay: 100, + }); } }); }); diff --git a/tests/unit/plugins-metrics.test.ts b/tests/unit/plugins-metrics.test.ts index a9780c90018b..f5c183d6fa3d 100644 --- a/tests/unit/plugins-metrics.test.ts +++ b/tests/unit/plugins-metrics.test.ts @@ -18,11 +18,14 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - try { fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); } catch {} + try { + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + } catch {} }); test("recordPluginMetric stores call count", async () => { - const { recordPluginMetric, getPluginMetrics } = await import("../../src/lib/db/pluginMetrics.ts"); + const { recordPluginMetric, getPluginMetrics } = + await import("../../src/lib/db/pluginMetrics.ts"); recordPluginMetric("test-plugin", "onRequest", 5.2, false); recordPluginMetric("test-plugin", "onRequest", 3.1, false); @@ -34,7 +37,8 @@ test("recordPluginMetric stores call count", async () => { }); test("recordPluginMetric tracks errors", async () => { - const { recordPluginMetric, getPluginMetrics } = await import("../../src/lib/db/pluginMetrics.ts"); + const { recordPluginMetric, getPluginMetrics } = + await import("../../src/lib/db/pluginMetrics.ts"); recordPluginMetric("err-plugin", "onRequest", 1.0, true); const metrics = getPluginMetrics("err-plugin"); @@ -44,7 +48,8 @@ test("recordPluginMetric tracks errors", async () => { }); test("recordPluginMetric tracks latency", async () => { - const { recordPluginMetric, getPluginMetrics } = await import("../../src/lib/db/pluginMetrics.ts"); + const { recordPluginMetric, getPluginMetrics } = + await import("../../src/lib/db/pluginMetrics.ts"); recordPluginMetric("latency-plugin", "onRequest", 42.5, false); const metrics = getPluginMetrics("latency-plugin"); @@ -54,7 +59,8 @@ test("recordPluginMetric tracks latency", async () => { }); test("getPluginMetrics returns all plugins when no filter", async () => { - const { recordPluginMetric, getPluginMetrics } = await import("../../src/lib/db/pluginMetrics.ts"); + const { recordPluginMetric, getPluginMetrics } = + await import("../../src/lib/db/pluginMetrics.ts"); recordPluginMetric("p1", "onRequest", 1, false); recordPluginMetric("p2", "onResponse", 2, false); @@ -63,7 +69,8 @@ test("getPluginMetrics returns all plugins when no filter", async () => { }); test("clearPluginMetrics removes metrics", async () => { - const { recordPluginMetric, clearPluginMetrics, getPluginMetrics } = await import("../../src/lib/db/pluginMetrics.ts"); + const { recordPluginMetric, clearPluginMetrics, getPluginMetrics } = + await import("../../src/lib/db/pluginMetrics.ts"); recordPluginMetric("clear-test", "onRequest", 1, false); clearPluginMetrics("clear-test"); diff --git a/tests/unit/plugins-scanner.test.ts b/tests/unit/plugins-scanner.test.ts index eab34574f94c..e2636a128d90 100644 --- a/tests/unit/plugins-scanner.test.ts +++ b/tests/unit/plugins-scanner.test.ts @@ -36,7 +36,7 @@ describe("plugin scanner", () => { assert.ok(result.plugins[0].manifest); assert.ok(result.plugins[0].pluginDir); } finally { - rmSync(tmp, { recursive: true, force: true }); + rmSync(tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -48,7 +48,7 @@ describe("plugin scanner", () => { const result = await mod.scanPluginDir(tmp); assert.equal(result.plugins.length, 0); } finally { - rmSync(tmp, { recursive: true, force: true }); + rmSync(tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -59,7 +59,7 @@ describe("plugin scanner", () => { const result = await mod.scanPluginDir(tmp); assert.equal(result.plugins.length, 0); } finally { - rmSync(tmp, { recursive: true, force: true }); + rmSync(tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -76,7 +76,7 @@ describe("plugin scanner", () => { const result = await mod.scanPluginDir(tmp); assert.equal(result.plugins.length, 2); } finally { - rmSync(tmp, { recursive: true, force: true }); + rmSync(tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); }); diff --git a/tests/unit/plugins-signing.test.ts b/tests/unit/plugins-signing.test.ts index d24b5a57fc73..48b4124bfeac 100644 --- a/tests/unit/plugins-signing.test.ts +++ b/tests/unit/plugins-signing.test.ts @@ -29,7 +29,9 @@ function writePlugin(dir: string, name: string, source: string, integrity?: stri const activeDirs: string[] = []; function cleanupDirs() { for (const d of activeDirs) { - try { fs.rmSync(d, { recursive: true, force: true }); } catch {} + try { + fs.rmSync(d, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + } catch {} } activeDirs.length = 0; } @@ -44,7 +46,9 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); cleanupDirs(); - try { fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); } catch {} + try { + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + } catch {} }); test("computeIntegrity returns correct format", async () => { diff --git a/tests/unit/plugins-tools.test.ts b/tests/unit/plugins-tools.test.ts index 8b6a37203b21..4ac81cae7f33 100644 --- a/tests/unit/plugins-tools.test.ts +++ b/tests/unit/plugins-tools.test.ts @@ -45,9 +45,11 @@ function writeTestPlugin(opts?: { name?: string; onRequest?: boolean }) { }, }; fs.writeFileSync(path.join(pluginDir, "plugin.json"), JSON.stringify(manifest, null, 2)); - fs.writeFileSync(path.join(pluginDir, "index.js"), onRequest - ? `module.exports.onRequest = function(ctx) { ctx.metadata = ctx.metadata || {}; ctx.metadata.hookCalled = true; };` - : `module.exports = {};` + fs.writeFileSync( + path.join(pluginDir, "index.js"), + onRequest + ? `module.exports.onRequest = function(ctx) { ctx.metadata = ctx.metadata || {}; ctx.metadata.hookCalled = true; };` + : `module.exports = {};` ); return { sourceDir, pluginDir, name }; } @@ -56,7 +58,9 @@ const activeSourceDirs: string[] = []; function cleanupSourceDirs() { for (const dir of activeSourceDirs) { - try { fs.rmSync(dir, { recursive: true, force: true }); } catch {} + try { + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + } catch {} } activeSourceDirs.length = 0; } @@ -66,7 +70,7 @@ function cleanupSourceDirs() { test.beforeEach(() => { core.resetDbInstance(); hooks.resetHooks(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); cleanupSourceDirs(); }); @@ -74,7 +78,9 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); cleanupSourceDirs(); - try { fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); } catch {} + try { + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + } catch {} }); // ── plugin_list ── @@ -309,7 +315,10 @@ test("plugin_configure: accepts valid config matching schema", async () => { await pluginManager.install(sourceDir); const tool = getTool("plugin_configure"); - const result = await tool.handler({ name, config: { apiUrl: "https://ok.example.com", maxRetries: 5 } }); + const result = await tool.handler({ + name, + config: { apiUrl: "https://ok.example.com", maxRetries: 5 }, + }); assert.equal(result.success, true, "should succeed for valid config"); assert.equal(result.config.apiUrl, "https://ok.example.com"); @@ -325,14 +334,17 @@ test("plugin_configure: allows any config when plugin has no configSchema", asyn const pluginDir = sourceDir + "/" + name; const fs = await import("node:fs"); const path = await import("node:path"); - fs.writeFileSync(path.join(pluginDir, "plugin.json"), JSON.stringify({ - name, - version: "1.0.0", - main: "index.js", - hooks: { onRequest: false, onResponse: false, onError: false }, - requires: { permissions: [] }, - // no configSchema - })); + fs.writeFileSync( + path.join(pluginDir, "plugin.json"), + JSON.stringify({ + name, + version: "1.0.0", + main: "index.js", + hooks: { onRequest: false, onResponse: false, onError: false }, + requires: { permissions: [] }, + // no configSchema + }) + ); const { pluginManager } = await import("../../src/lib/plugins/manager.ts"); await pluginManager.install(sourceDir); diff --git a/tests/unit/plugins-upgrade.test.ts b/tests/unit/plugins-upgrade.test.ts index 51a2f8b7c28b..95b66b13864a 100644 --- a/tests/unit/plugins-upgrade.test.ts +++ b/tests/unit/plugins-upgrade.test.ts @@ -20,16 +20,19 @@ function writePlugin(version: string, name = "upgrade-test") { const pluginDir = path.join(sourceDir, name); fs.mkdirSync(pluginDir, { recursive: true }); - fs.writeFileSync(path.join(pluginDir, "plugin.json"), JSON.stringify({ - name, - version, - description: `Plugin v${version}`, - author: "test", - main: "index.js", - hooks: { onRequest: true, onResponse: false, onError: false }, - enabledByDefault: false, - requires: { permissions: [] }, - })); + fs.writeFileSync( + path.join(pluginDir, "plugin.json"), + JSON.stringify({ + name, + version, + description: `Plugin v${version}`, + author: "test", + main: "index.js", + hooks: { onRequest: true, onResponse: false, onError: false }, + enabledByDefault: false, + requires: { permissions: [] }, + }) + ); fs.writeFileSync( path.join(pluginDir, "index.js"), @@ -44,19 +47,22 @@ function writePluginWithConfig(version: string, name = "upgrade-config-test") { const pluginDir = path.join(sourceDir, name); fs.mkdirSync(pluginDir, { recursive: true }); - fs.writeFileSync(path.join(pluginDir, "plugin.json"), JSON.stringify({ - name, - version, - description: `Plugin v${version}`, - author: "test", - main: "index.js", - hooks: { onRequest: true, onResponse: false, onError: false }, - enabledByDefault: false, - requires: { permissions: [] }, - configSchema: { - apiKey: { type: "string", description: "API key" }, - }, - })); + fs.writeFileSync( + path.join(pluginDir, "plugin.json"), + JSON.stringify({ + name, + version, + description: `Plugin v${version}`, + author: "test", + main: "index.js", + hooks: { onRequest: true, onResponse: false, onError: false }, + enabledByDefault: false, + requires: { permissions: [] }, + configSchema: { + apiKey: { type: "string", description: "API key" }, + }, + }) + ); fs.writeFileSync( path.join(pluginDir, "index.js"), @@ -69,7 +75,9 @@ function writePluginWithConfig(version: string, name = "upgrade-config-test") { const activeDirs: string[] = []; function cleanupDirs() { for (const dir of activeDirs) { - try { fs.rmSync(dir, { recursive: true, force: true }); } catch {} + try { + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + } catch {} } activeDirs.length = 0; } @@ -84,7 +92,9 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); cleanupDirs(); - try { fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); } catch {} + try { + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + } catch {} }); // ── Tests ── @@ -231,7 +241,11 @@ test("compareSemver: pre-release suffix strips cleanly (no NaN)", () => { assert.ok(compareSemver("1.0.1", "1.0.0-beta") > 0, "1.0.1 > 1.0.0-beta (treated as 1.0.0)"); assert.ok(compareSemver("1.0.0-beta", "0.9.0") > 0, "1.0.0-beta > 0.9.0"); // Both pre-release: treated as equal numeric parts - assert.equal(compareSemver("1.0.0-beta", "1.0.0-rc.1"), 0, "1.0.0-beta == 1.0.0-rc.1 (both strip to 1.0.0)"); + assert.equal( + compareSemver("1.0.0-beta", "1.0.0-rc.1"), + 0, + "1.0.0-beta == 1.0.0-rc.1 (both strip to 1.0.0)" + ); }); test("compareSemver: NaN segments coerce to 0, result is not NaN", () => { diff --git a/tests/unit/plugins-welcome-banner-e2e.test.ts b/tests/unit/plugins-welcome-banner-e2e.test.ts index 59295b07ad41..e03efd157604 100644 --- a/tests/unit/plugins-welcome-banner-e2e.test.ts +++ b/tests/unit/plugins-welcome-banner-e2e.test.ts @@ -455,7 +455,7 @@ test("full lifecycle: install → activate → hook fires → deactivate → uni test("cleanup fixture directory", () => { if (existsSync(FIXTURE_DIR)) { - rmSync(FIXTURE_DIR, { recursive: true, force: true }); + rmSync(FIXTURE_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } assert.ok(!existsSync(FIXTURE_DIR)); }); diff --git a/tests/unit/poe-api-executor-regression.test.ts b/tests/unit/poe-api-executor-regression.test.ts index 85cda3985011..22aba3238a60 100644 --- a/tests/unit/poe-api-executor-regression.test.ts +++ b/tests/unit/poe-api-executor-regression.test.ts @@ -34,7 +34,7 @@ test.after(() => { } catch { // ignore } - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); const CHAT_URL = "https://api.poe.com/v1/chat/completions"; diff --git a/tests/unit/poe-provider-models-baseurl.test.ts b/tests/unit/poe-provider-models-baseurl.test.ts index 7043e4ae4463..9d81cce5a30b 100644 --- a/tests/unit/poe-provider-models-baseurl.test.ts +++ b/tests/unit/poe-provider-models-baseurl.test.ts @@ -24,7 +24,7 @@ const originalFetch = globalThis.fetch; async function resetStorage() { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -35,7 +35,7 @@ test.beforeEach(async () => { test.after(async () => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("provider models route resolves the built-in Poe registry base URL instead of failing with 'No base URL configured for provider' (#8082)", async () => { diff --git a/tests/unit/policy-engine.test.ts b/tests/unit/policy-engine.test.ts index 79e12befc34d..ad98f1286742 100644 --- a/tests/unit/policy-engine.test.ts +++ b/tests/unit/policy-engine.test.ts @@ -18,7 +18,7 @@ beforeEach(() => { afterEach(() => { delete process.env.DATA_DIR; - if (tmpDir) fs.rmSync(tmpDir, { recursive: true, force: true }); + if (tmpDir) fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); describe("policyEngine", async () => { diff --git a/tests/unit/postinstall-support.test.ts b/tests/unit/postinstall-support.test.ts index c007b35ae0ac..7784f2b66a5f 100644 --- a/tests/unit/postinstall-support.test.ts +++ b/tests/unit/postinstall-support.test.ts @@ -13,7 +13,7 @@ test("hasStandaloneAppBundle returns false for source checkout without standalon mkdirSync(join(root, "src", "app"), { recursive: true }); assert.equal(hasStandaloneAppBundle(root), false); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -25,7 +25,7 @@ test("hasStandaloneAppBundle returns true for published standalone app bundle", writeFileSync(join(root, "app", "server.js"), "export {};\n"); assert.equal(hasStandaloneAppBundle(root), true); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/pricing-route-sources.test.ts b/tests/unit/pricing-route-sources.test.ts index 1398a10ef5f8..348bd63c6271 100644 --- a/tests/unit/pricing-route-sources.test.ts +++ b/tests/unit/pricing-route-sources.test.ts @@ -18,7 +18,7 @@ const pricingRoute = await import("../../src/app/api/pricing/route.ts"); function resetDb() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -28,7 +28,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("pricing GET keeps legacy payload by default and exposes source metadata on demand", async () => { diff --git a/tests/unit/pricing-sync-cross-instance.test.ts b/tests/unit/pricing-sync-cross-instance.test.ts index 1e123835c693..3b7b1d95cd2e 100644 --- a/tests/unit/pricing-sync-cross-instance.test.ts +++ b/tests/unit/pricing-sync-cross-instance.test.ts @@ -41,7 +41,7 @@ function buildLiteLLMFixture() { test.after(async () => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("manual sync history remains visible without advertising a disabled future sync", async () => { diff --git a/tests/unit/pricing-sync-extended.test.ts b/tests/unit/pricing-sync-extended.test.ts index ae85d28f036b..0f669e653335 100644 --- a/tests/unit/pricing-sync-extended.test.ts +++ b/tests/unit/pricing-sync-extended.test.ts @@ -33,7 +33,7 @@ function buildLiteLLMFixture() { async function resetStorage() { pricingSync.stopPeriodicSync(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -49,7 +49,7 @@ test.after(async () => { globalThis.fetch = originalFetch; console.warn = originalWarn; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("fetchLiteLLMPricing parses JSON and rejects invalid payloads", async () => { diff --git a/tests/unit/probe-6835-cyclebreaker.test.ts b/tests/unit/probe-6835-cyclebreaker.test.ts index fda653124d37..a93d1e8ae01c 100644 --- a/tests/unit/probe-6835-cyclebreaker.test.ts +++ b/tests/unit/probe-6835-cyclebreaker.test.ts @@ -24,5 +24,5 @@ test("getDbInstance() caps the probe-failed/restore cycle at 3 attempts (#6835)" const abortIndex = errors.findIndex((e) => e.includes("Aborting startup")); assert.notEqual(abortIndex, -1, "Expected the cap to trip; got: " + errors.join(" | ")); assert.ok(abortIndex <= 4, "Expected cap by call #4; took until #" + abortIndex); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); diff --git a/tests/unit/probe-6835-oom-uncapped.test.ts b/tests/unit/probe-6835-oom-uncapped.test.ts index 9b91101646ba..27215a307fc1 100644 --- a/tests/unit/probe-6835-oom-uncapped.test.ts +++ b/tests/unit/probe-6835-oom-uncapped.test.ts @@ -52,7 +52,8 @@ test("getDbInstance() eventually caps a persistently-OOMing sql.js probe (#6835) "Expected getDbInstance() to eventually give up with a terminal " + "'Aborting startup'-style diagnostic after repeated OOM probe failures, the same way it " + "already does for generic corruption (#6632). Instead every call re-threw an identical, " + - "uncapped OOM error:\n" + errors.map((e, i) => ` [${i}] ${e}`).join("\n") + "uncapped OOM error:\n" + + errors.map((e, i) => ` [${i}] ${e}`).join("\n") ); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); diff --git a/tests/unit/probe-9541-repro.test.ts b/tests/unit/probe-9541-repro.test.ts index 1faab9984428..1a1caa76a684 100644 --- a/tests/unit/probe-9541-repro.test.ts +++ b/tests/unit/probe-9541-repro.test.ts @@ -107,7 +107,7 @@ test("BUG-CONFIRMED (regression guard): probe failure renames DB and loses persi ); } finally { try { - fs.rmSync(dir, { recursive: true, force: true }); + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { /* ok */ } diff --git a/tests/unit/probe-autodisable-isolation.test.ts b/tests/unit/probe-autodisable-isolation.test.ts index 5cb4479b67d8..d2e3e2da9120 100644 --- a/tests/unit/probe-autodisable-isolation.test.ts +++ b/tests/unit/probe-autodisable-isolation.test.ts @@ -16,7 +16,7 @@ const { runAsProbe } = await import("../../src/shared/utils/probeOrigin.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function readIsActive(connId: string): unknown { diff --git a/tests/unit/probe-gate-autodisable.test.ts b/tests/unit/probe-gate-autodisable.test.ts index 7be8fe685a08..4eb8b4c106f2 100644 --- a/tests/unit/probe-gate-autodisable.test.ts +++ b/tests/unit/probe-gate-autodisable.test.ts @@ -28,7 +28,7 @@ test.beforeEach(() => { test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function readConnectionRow(connId: string) { diff --git a/tests/unit/probe-policy.test.ts b/tests/unit/probe-policy.test.ts index bed1fc6f550a..12403a603437 100644 --- a/tests/unit/probe-policy.test.ts +++ b/tests/unit/probe-policy.test.ts @@ -15,7 +15,7 @@ const { runAsProbe, shouldIsolateProbeFailures, isProbeContext } = test.after(() => { delete process.env.PROBE_CAN_DISABLE; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("outside a probe context the decision is always false (real path)", async () => { diff --git a/tests/unit/probe-production-path.test.ts b/tests/unit/probe-production-path.test.ts index 3b099b99dcf0..cc61c597d74b 100644 --- a/tests/unit/probe-production-path.test.ts +++ b/tests/unit/probe-production-path.test.ts @@ -14,7 +14,7 @@ const { markAccountUnavailable } = await import("../../src/sse/services/auth.ts" test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function readConnectionRow(connId: string) { diff --git a/tests/unit/probe-testall-isolation.test.ts b/tests/unit/probe-testall-isolation.test.ts index 40016fec8238..201fafc9098a 100644 --- a/tests/unit/probe-testall-isolation.test.ts +++ b/tests/unit/probe-testall-isolation.test.ts @@ -29,7 +29,7 @@ test.beforeEach(() => { test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function readConnectionRow(connId: string) { diff --git a/tests/unit/prompt-injection-guard-db-flag.test.ts b/tests/unit/prompt-injection-guard-db-flag.test.ts index a50b011a6461..951a3f55d65b 100644 --- a/tests/unit/prompt-injection-guard-db-flag.test.ts +++ b/tests/unit/prompt-injection-guard-db-flag.test.ts @@ -22,7 +22,7 @@ const ATTACK = { describe("prompt injection guard — DB feature flag override (INJECTION_GUARD_MODE)", () => { function resetDb() { core.resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(tmpDir, { recursive: true }); } @@ -36,7 +36,7 @@ describe("prompt injection guard — DB feature flag override (INJECTION_GUARD_M after(() => { core.resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); delete process.env.INPUT_SANITIZER_ENABLED; delete process.env.INPUT_SANITIZER_MODE; delete process.env.INJECTION_GUARD_MODE; diff --git a/tests/unit/prompt-required-routes.test.ts b/tests/unit/prompt-required-routes.test.ts index b1faa3ae0174..562d70326f80 100644 --- a/tests/unit/prompt-required-routes.test.ts +++ b/tests/unit/prompt-required-routes.test.ts @@ -15,7 +15,7 @@ type ErrorResponseBody = { error: { message: string } }; test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("v1 video generation POST rejects requests without a prompt", async () => { diff --git a/tests/unit/provider-connection-apikey-dedup.test.ts b/tests/unit/provider-connection-apikey-dedup.test.ts index b0e05a0d5d1a..0ef654c553cd 100644 --- a/tests/unit/provider-connection-apikey-dedup.test.ts +++ b/tests/unit/provider-connection-apikey-dedup.test.ts @@ -13,7 +13,7 @@ const providersDb = await import("../../src/lib/db/providers.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -21,7 +21,7 @@ test.beforeEach(resetStorage); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); async function apiKeyConnections(provider: string) { diff --git a/tests/unit/provider-connection-healthcheck-interval-zero.test.ts b/tests/unit/provider-connection-healthcheck-interval-zero.test.ts index 0633f7e7d14d..3ffdfb9816ac 100644 --- a/tests/unit/provider-connection-healthcheck-interval-zero.test.ts +++ b/tests/unit/provider-connection-healthcheck-interval-zero.test.ts @@ -6,9 +6,7 @@ import path from "node:path"; process.env.NODE_ENV = "test"; -const TEST_DATA_DIR = fs.mkdtempSync( - path.join(os.tmpdir(), "omniroute-hci-zero-") -); +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-hci-zero-")); process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); @@ -19,7 +17,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: any) { @@ -35,7 +33,7 @@ async function resetStorage() { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // Regression: the `_insertConnectionRow` and `_updateConnectionRow` bind helpers @@ -117,4 +115,4 @@ test("updateProviderConnection still persists a nonzero healthCheckInterval", as const stored = await providersDb.getProviderConnectionById((connection as any).id); assert.equal(stored?.healthCheckInterval, 60); -}); \ No newline at end of file +}); diff --git a/tests/unit/provider-connection-test-key-health.test.ts b/tests/unit/provider-connection-test-key-health.test.ts index 170b72f2bcb4..c75e9bab3fbe 100644 --- a/tests/unit/provider-connection-test-key-health.test.ts +++ b/tests/unit/provider-connection-test-key-health.test.ts @@ -39,7 +39,7 @@ const WARNING_HEALTH: StoredKeyHealth = { async function resetStorage(): Promise { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -83,7 +83,7 @@ test.afterEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("authoritative validation clears only the exact primary credential during a quota cooldown", async () => { diff --git a/tests/unit/provider-connections-pagination-2998.test.ts b/tests/unit/provider-connections-pagination-2998.test.ts index ed2025b153c4..3810cfff5bf0 100644 --- a/tests/unit/provider-connections-pagination-2998.test.ts +++ b/tests/unit/provider-connections-pagination-2998.test.ts @@ -17,7 +17,7 @@ const providersRoute = await import("../../src/app/api/providers/route.ts"); function resetDb() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -34,7 +34,7 @@ test.beforeEach(resetDb); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("GET /api/providers filters and counts before applying limit/offset", async () => { diff --git a/tests/unit/provider-connections-quota-threshold.test.ts b/tests/unit/provider-connections-quota-threshold.test.ts index e7812557e994..16e98173cd22 100644 --- a/tests/unit/provider-connections-quota-threshold.test.ts +++ b/tests/unit/provider-connections-quota-threshold.test.ts @@ -17,7 +17,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: any) { @@ -38,7 +38,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("createProviderConnection persists quotaWindowThresholds map", async () => { diff --git a/tests/unit/provider-health-matrix.test.ts b/tests/unit/provider-health-matrix.test.ts index 7e4edd12abae..d3509935dac5 100644 --- a/tests/unit/provider-health-matrix.test.ts +++ b/tests/unit/provider-health-matrix.test.ts @@ -27,7 +27,7 @@ const CANONICAL_ALIAS_PROVIDER = "nous-research"; async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); for (const lockout of accountFallback.getAllModelLockouts()) { if (lockout.provider === PROVIDER) { @@ -55,7 +55,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; diff --git a/tests/unit/provider-limits-local-apikey-sync-spacing.test.ts b/tests/unit/provider-limits-local-apikey-sync-spacing.test.ts index ac0a285133ab..c3bc8e9efa8a 100644 --- a/tests/unit/provider-limits-local-apikey-sync-spacing.test.ts +++ b/tests/unit/provider-limits-local-apikey-sync-spacing.test.ts @@ -27,7 +27,7 @@ const originalFetch = globalThis.fetch; test.beforeEach(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); delete process.env.PROVIDER_LIMITS_SYNC_SPACING_MS; }); @@ -35,7 +35,7 @@ test.beforeEach(() => { test.after(() => { globalThis.fetch = originalFetch; delete process.env.PROVIDER_LIMITS_SYNC_SPACING_MS; - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); async function createGlmApiKeyConnection(i: number) { diff --git a/tests/unit/provider-limits-oauth-sequential-sync.test.ts b/tests/unit/provider-limits-oauth-sequential-sync.test.ts index d1c99ca088a0..075b22650130 100644 --- a/tests/unit/provider-limits-oauth-sequential-sync.test.ts +++ b/tests/unit/provider-limits-oauth-sequential-sync.test.ts @@ -29,13 +29,13 @@ const originalFetch = globalThis.fetch; test.beforeEach(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); }); test.after(() => { globalThis.fetch = originalFetch; - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); async function createClaudeOAuth(i: number) { diff --git a/tests/unit/provider-limits-proxy-fail-closed.test.ts b/tests/unit/provider-limits-proxy-fail-closed.test.ts index f32cc36f1eea..eb3a923fd92c 100644 --- a/tests/unit/provider-limits-proxy-fail-closed.test.ts +++ b/tests/unit/provider-limits-proxy-fail-closed.test.ts @@ -17,7 +17,7 @@ const originalFetch = globalThis.fetch; async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -84,7 +84,7 @@ test.beforeEach(async () => { test.after(async () => { globalThis.fetch = originalFetch; await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("Claude provider limits fail closed when an account proxy is unreachable", async () => { diff --git a/tests/unit/provider-limits-recovery.test.ts b/tests/unit/provider-limits-recovery.test.ts index b3aedca49478..809051c7564e 100644 --- a/tests/unit/provider-limits-recovery.test.ts +++ b/tests/unit/provider-limits-recovery.test.ts @@ -17,7 +17,7 @@ const originalFetch = globalThis.fetch; async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -79,7 +79,7 @@ test.beforeEach(async () => { test.after(async () => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("successful GLM quota refresh clears transient rate-limit state", async () => { diff --git a/tests/unit/provider-limits-rotating-expired-guard.test.ts b/tests/unit/provider-limits-rotating-expired-guard.test.ts index daca704d088e..74ad93f0806d 100644 --- a/tests/unit/provider-limits-rotating-expired-guard.test.ts +++ b/tests/unit/provider-limits-rotating-expired-guard.test.ts @@ -13,7 +13,7 @@ const { quotaPathShouldMarkExpired, shouldAttemptRotatingRefresh } = await import("../../src/lib/usage/providerLimits.ts"); test.after(() => { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // Regression: the quota sync reuses a rotating provider's (possibly expired) diff --git a/tests/unit/provider-limits-sanitize-scope-3821.test.ts b/tests/unit/provider-limits-sanitize-scope-3821.test.ts index 60c29f0098d6..5b802ff38c2e 100644 --- a/tests/unit/provider-limits-sanitize-scope-3821.test.ts +++ b/tests/unit/provider-limits-sanitize-scope-3821.test.ts @@ -35,13 +35,13 @@ const providerLimits = await import("../../src/lib/usage/providerLimits.ts"); test.beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); }); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function cacheEntry(quotas: Record) { diff --git a/tests/unit/provider-limits-sync-scheduler-public-surface.test.ts b/tests/unit/provider-limits-sync-scheduler-public-surface.test.ts index 64800fbfeebe..aa10617c6224 100644 --- a/tests/unit/provider-limits-sync-scheduler-public-surface.test.ts +++ b/tests/unit/provider-limits-sync-scheduler-public-surface.test.ts @@ -14,7 +14,7 @@ const core = await import("../../src/lib/db/core.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("provider limits sync scheduler public surface excludes unused stop helper", () => { diff --git a/tests/unit/provider-login-timeout-validation.test.ts b/tests/unit/provider-login-timeout-validation.test.ts index 36b33300e3fc..f8236177aa1a 100644 --- a/tests/unit/provider-login-timeout-validation.test.ts +++ b/tests/unit/provider-login-timeout-validation.test.ts @@ -48,7 +48,7 @@ const { inAppLoginService } = await import("../../open-sse/services/inAppLoginSe test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; }); diff --git a/tests/unit/provider-metrics-deleted-provider.test.ts b/tests/unit/provider-metrics-deleted-provider.test.ts index 2cfbbbbf7a98..bce87d87613b 100644 --- a/tests/unit/provider-metrics-deleted-provider.test.ts +++ b/tests/unit/provider-metrics-deleted-provider.test.ts @@ -4,9 +4,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -const TEST_DATA_DIR = fs.mkdtempSync( - path.join(os.tmpdir(), "omniroute-provider-metrics-deleted-") -); +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-provider-metrics-deleted-")); const ORIGINAL_DATA_DIR = process.env.DATA_DIR; process.env.DATA_DIR = TEST_DATA_DIR; @@ -20,7 +18,7 @@ type ProviderMetricsResponse = { function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -29,7 +27,7 @@ test.beforeEach(() => { }); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; }); @@ -47,7 +45,14 @@ test("#10714: a provider deleted from provider_connections must NOT keep showing // Deleted provider: historical call_logs rows exist, but no provider_connections row. db.prepare( `INSERT INTO call_logs (id, timestamp, provider, status, duration, error_summary) VALUES (?, ?, ?, ?, ?, ?)` - ).run("g4f-pollinations-error", "2026-08-19T11:00:00.000Z", "g4f-pollinations", 402, 50, "payment required"); + ).run( + "g4f-pollinations-error", + "2026-08-19T11:00:00.000Z", + "g4f-pollinations", + 402, + 50, + "payment required" + ); const response = await providerMetricsRoute.GET(); const body = (await response.json()) as ProviderMetricsResponse; diff --git a/tests/unit/provider-metrics-route.test.ts b/tests/unit/provider-metrics-route.test.ts index 2d0b2d254391..d2bf2d40bd57 100644 --- a/tests/unit/provider-metrics-route.test.ts +++ b/tests/unit/provider-metrics-route.test.ts @@ -33,7 +33,7 @@ type ProviderMetricsResponse = { function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -43,7 +43,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; diff --git a/tests/unit/provider-models-context-window-override-4125.test.ts b/tests/unit/provider-models-context-window-override-4125.test.ts index f7488cd04eb9..0e093ab48fac 100644 --- a/tests/unit/provider-models-context-window-override-4125.test.ts +++ b/tests/unit/provider-models-context-window-override-4125.test.ts @@ -32,7 +32,7 @@ const providerModelsRoute = await import("../../src/app/api/provider-models/rout async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -42,7 +42,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function buildRequest(method: string, body: unknown) { @@ -103,7 +103,11 @@ test("GET surfaces contextWindowOverride on the custom model row", async () => { new Request("http://localhost/api/provider-models?provider=openai-compatible-demo") ); const body = (await getRes.json()) as { - models: Array<{ id?: string; contextWindowOverride?: number; contextWindowOverrideSource?: string }>; + models: Array<{ + id?: string; + contextWindowOverride?: number; + contextWindowOverrideSource?: string; + }>; }; const row = body.models.find((m) => m.id === "m1"); diff --git a/tests/unit/provider-models-custom-merge-6247.test.ts b/tests/unit/provider-models-custom-merge-6247.test.ts index c6341ba8fbbb..eb5cf88b1ca2 100644 --- a/tests/unit/provider-models-custom-merge-6247.test.ts +++ b/tests/unit/provider-models-custom-merge-6247.test.ts @@ -30,7 +30,7 @@ const originalFetch = globalThis.fetch; async function resetStorage() { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -71,7 +71,7 @@ test.beforeEach(async () => { test.after(async () => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("per-connection models route includes user-added custom models on the local_catalog path (#6247)", async () => { diff --git a/tests/unit/provider-models-management-route.test.ts b/tests/unit/provider-models-management-route.test.ts index 3fee3b71325f..a3039c042612 100644 --- a/tests/unit/provider-models-management-route.test.ts +++ b/tests/unit/provider-models-management-route.test.ts @@ -15,7 +15,7 @@ const providerModelsRoute = await import("../../src/app/api/provider-models/rout async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -47,7 +47,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("provider-models GET returns an empty hiddenModelsByProvider map with no hidden models", async () => { diff --git a/tests/unit/provider-models-route-codex.test.ts b/tests/unit/provider-models-route-codex.test.ts index 0d4587218d10..5a0ea5e792ef 100644 --- a/tests/unit/provider-models-route-codex.test.ts +++ b/tests/unit/provider-models-route-codex.test.ts @@ -47,7 +47,7 @@ async function resetStorage() { globalThis.fetch = originalFetch; codexDiscovery.clearCodexGithubCatalogCacheForTests(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -79,7 +79,7 @@ test.after(async () => { globalThis.fetch = originalFetch; codexDiscovery.clearCodexGithubCatalogCacheForTests(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("provider models route merges live Codex models with the local catalog then filters denylist", async () => { diff --git a/tests/unit/provider-models-route-lan-guard.test.ts b/tests/unit/provider-models-route-lan-guard.test.ts index 92330febdd8e..48c7bb718b43 100644 --- a/tests/unit/provider-models-route-lan-guard.test.ts +++ b/tests/unit/provider-models-route-lan-guard.test.ts @@ -38,7 +38,7 @@ async function resetStorage() { process.env.OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS = originalAllowLocalProviderUrls; } core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -69,7 +69,7 @@ test.beforeEach(async () => { test.after(async () => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#6939: getProviderOutboundGuard() and getProviderValidationGuard() agree for LAN hosts under the default local-first setting", () => { diff --git a/tests/unit/provider-models-route.test.ts b/tests/unit/provider-models-route.test.ts index 237e3bc3b156..a8d6d2dd80af 100644 --- a/tests/unit/provider-models-route.test.ts +++ b/tests/unit/provider-models-route.test.ts @@ -26,7 +26,7 @@ async function resetStorage() { } antigravityVersion.clearAntigravityVersionCaches(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -58,7 +58,7 @@ test.beforeEach(async () => { test.after(async () => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("provider models route returns a static local catalog for non-LLM search/agent providers (#5569/#5571/#5573/#5575)", async () => { diff --git a/tests/unit/provider-models-token-limits.test.ts b/tests/unit/provider-models-token-limits.test.ts index aada0b2f8722..13902a16f144 100644 --- a/tests/unit/provider-models-token-limits.test.ts +++ b/tests/unit/provider-models-token-limits.test.ts @@ -15,7 +15,7 @@ const providerModelsRoute = await import("../../src/app/api/provider-models/rout async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -33,7 +33,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // #1294: POST /api/provider-models must persist max_input_tokens / max_output_tokens diff --git a/tests/unit/provider-models-v1-route.test.ts b/tests/unit/provider-models-v1-route.test.ts index c20eda3544be..af92b95b4f05 100644 --- a/tests/unit/provider-models-v1-route.test.ts +++ b/tests/unit/provider-models-v1-route.test.ts @@ -16,9 +16,7 @@ process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); const serviceModelsDb = await import("../../src/lib/db/serviceModels.ts"); -const routeModule = await import( - "../../src/app/api/v1/providers/[provider]/models/route.ts" -); +const routeModule = await import("../../src/app/api/v1/providers/[provider]/models/route.ts"); function makeRequest(provider: string) { return new Request(`http://localhost/api/v1/providers/${encodeURIComponent(provider)}/models`); @@ -36,7 +34,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("GET /v1/providers/:provider/models returns 400 for completely unknown provider", async () => { diff --git a/tests/unit/provider-models-vision-override-1904.test.ts b/tests/unit/provider-models-vision-override-1904.test.ts index 508c6a139963..b3e7bed0e3d4 100644 --- a/tests/unit/provider-models-vision-override-1904.test.ts +++ b/tests/unit/provider-models-vision-override-1904.test.ts @@ -31,7 +31,7 @@ const catalogVision = await import("../../src/app/api/v1/models/catalogVision.ts async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -41,7 +41,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function buildRequest(method: string, body: unknown) { diff --git a/tests/unit/provider-node-icon-url.test.ts b/tests/unit/provider-node-icon-url.test.ts index be4b14f0db7a..40ad7dad1a68 100644 --- a/tests/unit/provider-node-icon-url.test.ts +++ b/tests/unit/provider-node-icon-url.test.ts @@ -19,7 +19,7 @@ const { createProviderNodeSchema, updateProviderNodeSchema } = async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -45,7 +45,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("createProviderNodeSchema accepts a valid iconUrl", () => { diff --git a/tests/unit/provider-node-reserved-prefix.test.ts b/tests/unit/provider-node-reserved-prefix.test.ts index d2fc26610117..7b13218cc4c8 100644 --- a/tests/unit/provider-node-reserved-prefix.test.ts +++ b/tests/unit/provider-node-reserved-prefix.test.ts @@ -41,7 +41,7 @@ const { isCommonChatGptWebRetiredProviderId } = async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -86,7 +86,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ──── Shared module ──── diff --git a/tests/unit/provider-nodes-route.test.ts b/tests/unit/provider-nodes-route.test.ts index eb21fa057801..681304538fd8 100644 --- a/tests/unit/provider-nodes-route.test.ts +++ b/tests/unit/provider-nodes-route.test.ts @@ -30,7 +30,7 @@ async function resetStorage() { process.env.OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS = originalAllowLocalProviderUrls; } core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -48,7 +48,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("provider nodes route lists stored nodes and exposes the CC feature flag", async () => { diff --git a/tests/unit/provider-nodes-validate-modelid.test.ts b/tests/unit/provider-nodes-validate-modelid.test.ts index f0e51a6d0e7e..5c644b542604 100644 --- a/tests/unit/provider-nodes-validate-modelid.test.ts +++ b/tests/unit/provider-nodes-validate-modelid.test.ts @@ -17,7 +17,7 @@ const originalFetch = globalThis.fetch; async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -29,7 +29,7 @@ test.afterEach(async () => { test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); type FetchCall = { url: string; init: any }; diff --git a/tests/unit/provider-nodes-vibeproxy-preset.test.ts b/tests/unit/provider-nodes-vibeproxy-preset.test.ts index 59830e8650ff..869f7b9afe92 100644 --- a/tests/unit/provider-nodes-vibeproxy-preset.test.ts +++ b/tests/unit/provider-nodes-vibeproxy-preset.test.ts @@ -36,7 +36,7 @@ async function resetStorage() { delete process.env.OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS; delete process.env.OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -54,7 +54,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("vibeproxy-openai preset creates a node with defaulted name/prefix/apiType", async () => { diff --git a/tests/unit/provider-patch-ratelimit-protection-11278.test.ts b/tests/unit/provider-patch-ratelimit-protection-11278.test.ts index b41f067d251e..d3f5e03fb669 100644 --- a/tests/unit/provider-patch-ratelimit-protection-11278.test.ts +++ b/tests/unit/provider-patch-ratelimit-protection-11278.test.ts @@ -38,7 +38,7 @@ const rateLimitManager = await import("../../open-sse/services/rateLimitManager. function resetDb() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -48,7 +48,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); async function createConnection(rateLimitProtection: boolean) { diff --git a/tests/unit/provider-probe-target.test.ts b/tests/unit/provider-probe-target.test.ts index 2fff1cafcc73..c1995c1fae06 100644 --- a/tests/unit/provider-probe-target.test.ts +++ b/tests/unit/provider-probe-target.test.ts @@ -26,7 +26,7 @@ const probeTarget = await import("../../src/lib/proxyHealth/providerProbeTarget. test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function seedProxy(name: string) { diff --git a/tests/unit/provider-request-failure-pipeline.test.ts b/tests/unit/provider-request-failure-pipeline.test.ts index 2b6684f452ce..d27e2f361b00 100644 --- a/tests/unit/provider-request-failure-pipeline.test.ts +++ b/tests/unit/provider-request-failure-pipeline.test.ts @@ -63,7 +63,7 @@ async function resetStorage() { // under load this cache is evicted at unpredictable times, so tests that rely // on the stale cache flake. Make the reset honest and deterministic here. invalidateDbCache("settings"); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -88,7 +88,7 @@ test.after(async () => { clearPendingRequests(); resetAccountSemaphores(); await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("network failure persisted call log includes providerRequest in pipeline payloads", async () => { diff --git a/tests/unit/provider-scoped-models-route.test.ts b/tests/unit/provider-scoped-models-route.test.ts index 4d41d2cce51d..cc90dc3a3929 100644 --- a/tests/unit/provider-scoped-models-route.test.ts +++ b/tests/unit/provider-scoped-models-route.test.ts @@ -36,7 +36,7 @@ type ProviderModelsResponse = { async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -59,7 +59,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("provider models route returns only selected provider models with unprefixed ids", async () => { diff --git a/tests/unit/provider-sweep-live-discovery.test.ts b/tests/unit/provider-sweep-live-discovery.test.ts index 2431179fa7a4..fe5905cb8bde 100644 --- a/tests/unit/provider-sweep-live-discovery.test.ts +++ b/tests/unit/provider-sweep-live-discovery.test.ts @@ -31,13 +31,13 @@ const modelsRoute = await import("../../src/app/api/providers/[id]/models/route. async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); interface ModelsBody { @@ -113,7 +113,11 @@ for (const { provider, liveUrl, source = "api" } of LIVE_CASES) { const body = (await response.json()) as ModelsBody; assert.equal(body.provider, provider); assert.ok(fetched, `should have probed ${liveUrl}`); - assert.equal(body.source, source, "should serve the live upstream catalog, not local_catalog"); + assert.equal( + body.source, + source, + "should serve the live upstream catalog, not local_catalog" + ); const ids = body.models.map((m) => m.id); assert.ok( ids.includes(`${provider}-live-a`) && ids.includes(`${provider}-live-b`), diff --git a/tests/unit/provider-translate-path-golden.test.ts b/tests/unit/provider-translate-path-golden.test.ts index 7cffac3d66f8..36e81677d170 100644 --- a/tests/unit/provider-translate-path-golden.test.ts +++ b/tests/unit/provider-translate-path-golden.test.ts @@ -171,6 +171,6 @@ test("GOLDEN guard catches translate-path drift", () => { assert.throws(() => goldenSnapshot("provider/translate-path", mutated, tmpDir)); } finally { delete process.env.UPDATE_GOLDEN; - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/provider-validation-unsupported-neutral.test.ts b/tests/unit/provider-validation-unsupported-neutral.test.ts index d26433dfb0bd..2a02d0f17faa 100644 --- a/tests/unit/provider-validation-unsupported-neutral.test.ts +++ b/tests/unit/provider-validation-unsupported-neutral.test.ts @@ -53,6 +53,9 @@ test.after(() => { fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, + + maxRetries: 5, + retryDelay: 100, }); }); diff --git a/tests/unit/provider-window-costs.test.ts b/tests/unit/provider-window-costs.test.ts index 63ac2e23ec8a..46f42f5e7cd5 100644 --- a/tests/unit/provider-window-costs.test.ts +++ b/tests/unit/provider-window-costs.test.ts @@ -22,7 +22,7 @@ async function resetStorage() { core.resetDbInstance(); apiKeys.resetApiKeyState(); costRules.resetCostData(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -34,7 +34,7 @@ test.after(() => { core.resetDbInstance(); apiKeys.resetApiKeyState(); costRules.resetCostData(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("Codex provider window costs use the weekly reset window and API key USD limit", async () => { diff --git a/tests/unit/providers-batch-update.test.ts b/tests/unit/providers-batch-update.test.ts index a45e3cd40528..a05ed83006e9 100644 --- a/tests/unit/providers-batch-update.test.ts +++ b/tests/unit/providers-batch-update.test.ts @@ -9,9 +9,8 @@ process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); const providersDb = await import("../../src/lib/db/providers.ts"); -const { batchUpdateProviderConnectionsSchema, providersBatchTestSchema } = await import( - "../../src/shared/validation/schemas.ts" -); +const { batchUpdateProviderConnectionsSchema, providersBatchTestSchema } = + await import("../../src/shared/validation/schemas.ts"); type Connection = Awaited>; @@ -23,7 +22,7 @@ function getConnectionId(connection: Connection): string { async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -45,7 +44,7 @@ beforeEach(async () => { after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); describe("batchUpdateProviderConnectionsSchema", () => { diff --git a/tests/unit/providers-route-codex-account-pool.test.ts b/tests/unit/providers-route-codex-account-pool.test.ts index c5f5ad7e07ef..4ffa109ac5d4 100644 --- a/tests/unit/providers-route-codex-account-pool.test.ts +++ b/tests/unit/providers-route-codex-account-pool.test.ts @@ -19,7 +19,7 @@ const providersRoute = await import("../../src/app/api/providers/route.ts"); test.after(() => { quotaCache.__clearForTests(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("GET keeps one parent row and projects raw Codex state without exposing credentials", async () => { diff --git a/tests/unit/providers-route-managed-catalog.test.ts b/tests/unit/providers-route-managed-catalog.test.ts index 8e380dae8429..02e8012d203b 100644 --- a/tests/unit/providers-route-managed-catalog.test.ts +++ b/tests/unit/providers-route-managed-catalog.test.ts @@ -16,7 +16,7 @@ const modelsDb = await import("../../src/lib/db/models.ts"); function resetDb() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -26,7 +26,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("providers route accepts managed local, audio, web-cookie and search providers", async () => { diff --git a/tests/unit/providers-route-model-autofetch-optin.test.ts b/tests/unit/providers-route-model-autofetch-optin.test.ts index 70cf67d78fed..166e7d4fdad2 100644 --- a/tests/unit/providers-route-model-autofetch-optin.test.ts +++ b/tests/unit/providers-route-model-autofetch-optin.test.ts @@ -58,7 +58,7 @@ async function createConnection(options: CreateOptions = {}): Promise test.beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); modelSyncUrls.length = 0; }); @@ -66,7 +66,7 @@ test.beforeEach(() => { test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("POST /api/providers does not sync models when autoFetchModels is omitted", async () => { diff --git a/tests/unit/providers-validate-route.test.ts b/tests/unit/providers-validate-route.test.ts index ba319577810e..83b3a7bead20 100644 --- a/tests/unit/providers-validate-route.test.ts +++ b/tests/unit/providers-validate-route.test.ts @@ -16,13 +16,13 @@ const validateRoute = await import("../../src/app/api/providers/validate/route.t async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (originalAllowPrivateProviderUrls === undefined) { delete process.env.OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS; } else { diff --git a/tests/unit/proxy-10348-log-redaction.test.ts b/tests/unit/proxy-10348-log-redaction.test.ts index 615cbc3adf10..907bed0a4dfd 100644 --- a/tests/unit/proxy-10348-log-redaction.test.ts +++ b/tests/unit/proxy-10348-log-redaction.test.ts @@ -16,7 +16,7 @@ const proxyLogger = await import("../../src/lib/proxyLogger.ts"); function resetStorage() { proxyLogger.clearProxyLogs(); core.closeDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -57,4 +57,4 @@ test("[10348] default ProxyEgress console line redacts client IP, egress IP, and !line!.includes("aabbccdd"), "account prefix must be redacted from the console line by default" ); -}); \ No newline at end of file +}); diff --git a/tests/unit/proxy-assigned-unavailable-6246.test.ts b/tests/unit/proxy-assigned-unavailable-6246.test.ts index 396949108e03..0f829b25e85a 100644 --- a/tests/unit/proxy-assigned-unavailable-6246.test.ts +++ b/tests/unit/proxy-assigned-unavailable-6246.test.ts @@ -28,7 +28,7 @@ const providersDb = await import("../../src/lib/db/providers.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -51,7 +51,7 @@ async function makeConnection(): Promise { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("BLOCKS: an account proxy assigned but marked inactive (the IP-leak case)", async () => { diff --git a/tests/unit/proxy-autoselect-optin-3332.test.ts b/tests/unit/proxy-autoselect-optin-3332.test.ts index 35f58c77feac..0f1a8f5a5715 100644 --- a/tests/unit/proxy-autoselect-optin-3332.test.ts +++ b/tests/unit/proxy-autoselect-optin-3332.test.ts @@ -8,9 +8,8 @@ const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-proxy-333 process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); -const { FEATURE_FLAG_DEFINITIONS } = await import( - "../../src/shared/constants/featureFlagDefinitions.ts" -); +const { FEATURE_FLAG_DEFINITIONS } = + await import("../../src/shared/constants/featureFlagDefinitions.ts"); const { isFeatureFlagEnabled } = await import("../../src/shared/utils/featureFlags.ts"); const { selectWorkingProxyFallback } = await import("../../open-sse/utils/proxyFallback.ts"); @@ -48,7 +47,7 @@ test.after(() => { /* ignore */ } try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { /* ignore */ } diff --git a/tests/unit/proxy-batch-routes-5918.test.ts b/tests/unit/proxy-batch-routes-5918.test.ts index b5ca6ae98ff1..a2b64b39c7fd 100644 --- a/tests/unit/proxy-batch-routes-5918.test.ts +++ b/tests/unit/proxy-batch-routes-5918.test.ts @@ -20,12 +20,10 @@ delete process.env.INITIAL_PASSWORD; // auth not required in this test env const core = await import("../../src/lib/db/core.ts"); const proxiesDb = await import("../../src/lib/db/proxies.ts"); -const { POST: batchDeletePost } = await import( - "../../src/app/api/settings/proxies/batch-delete/route.ts" -); -const { POST: autoTestPost } = await import( - "../../src/app/api/settings/proxies/auto-test/route.ts" -); +const { POST: batchDeletePost } = + await import("../../src/app/api/settings/proxies/batch-delete/route.ts"); +const { POST: autoTestPost } = + await import("../../src/app/api/settings/proxies/auto-test/route.ts"); function jsonRequest(body: unknown): Request { return new Request("http://localhost/api/settings/proxies/batch-delete", { @@ -38,13 +36,13 @@ function jsonRequest(body: unknown): Request { async function resetStorage() { delete process.env.INITIAL_PASSWORD; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("batch-delete removes multiple existing proxies in one request", async () => { diff --git a/tests/unit/proxy-bulk-import-dedup-7594.test.ts b/tests/unit/proxy-bulk-import-dedup-7594.test.ts index bec2a9660fa1..0a7bb0c91fc1 100644 --- a/tests/unit/proxy-bulk-import-dedup-7594.test.ts +++ b/tests/unit/proxy-bulk-import-dedup-7594.test.ts @@ -22,7 +22,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error) { @@ -44,7 +44,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("upsertProxy creates distinct entries for same host:port with different credentials (#7594)", async () => { diff --git a/tests/unit/proxy-egress-route-summary.test.ts b/tests/unit/proxy-egress-route-summary.test.ts index 19cbd0be6c09..c96862fd6e3f 100644 --- a/tests/unit/proxy-egress-route-summary.test.ts +++ b/tests/unit/proxy-egress-route-summary.test.ts @@ -24,7 +24,7 @@ const route = await import("../../src/app/api/settings/proxies/egress/route.ts") function resetStorage() { core.resetDbInstance(); proxyLogger.clearProxyLogs(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -41,23 +41,39 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("GET /api/settings/proxies/egress adds an anonymous summary to the existing payload", async () => { const bearer = await setupAuth(); // Seed two codex accounts on one egress IP (persisted proxy_logs). - proxyLogger.logProxyEvent({ status: "success", provider: "codex", targetUrl: "codex/gpt-5.5", egressIp: "100.115.194.84", account: "acc-a", connectionId: "conn-a" }); - proxyLogger.logProxyEvent({ status: "success", provider: "codex", targetUrl: "codex/gpt-5.5", egressIp: "100.115.194.84", account: "acc-b", connectionId: "conn-b" }); + proxyLogger.logProxyEvent({ + status: "success", + provider: "codex", + targetUrl: "codex/gpt-5.5", + egressIp: "100.115.194.84", + account: "acc-a", + connectionId: "conn-a", + }); + proxyLogger.logProxyEvent({ + status: "success", + provider: "codex", + targetUrl: "codex/gpt-5.5", + egressIp: "100.115.194.84", + account: "acc-b", + connectionId: "conn-b", + }); // logProxyEvent only enqueues for the 1s/100-entry background batch; the route // below reads persisted proxy_logs synchronously, so flush before asserting or // the rows are not yet on disk (timing-flaky otherwise). proxyLogger.flushProxyLogsSync(); - const response = await route.GET(new Request("https://example.com/api/settings/proxies/egress", { - headers: { authorization: `Bearer ${bearer}` }, - })); + const response = await route.GET( + new Request("https://example.com/api/settings/proxies/egress", { + headers: { authorization: `Bearer ${bearer}` }, + }) + ); assert.equal(response.status, 200); const body = await response.json(); diff --git a/tests/unit/proxy-egress-validate-pool-default.test.ts b/tests/unit/proxy-egress-validate-pool-default.test.ts index 423406e48ac6..3d34b159a278 100644 --- a/tests/unit/proxy-egress-validate-pool-default.test.ts +++ b/tests/unit/proxy-egress-validate-pool-default.test.ts @@ -20,16 +20,16 @@ const core = await import("../../src/lib/db/core.ts"); const proxiesDb = await import("../../src/lib/db/proxies.ts"); const egress = await import("../../src/lib/proxyEgress.ts"); const { validateProxyPool, _setEgressProbeForTests, clearEgressCache } = egress as unknown as { - validateProxyPool: (deps?: unknown) => Promise< - Array<{ proxyId: string; alive: boolean; newStatus: string }> - >; + validateProxyPool: ( + deps?: unknown + ) => Promise>; _setEgressProbeForTests: (fn: unknown) => void; clearEgressCache: () => void; }; async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -41,7 +41,7 @@ test.beforeEach(async () => { test.after(() => { _setEgressProbeForTests(null); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("validateProxyPool() with no injected deps does not crash on the real listProxies() {items,total} shape", async () => { diff --git a/tests/unit/proxy-fallback-candidates-listproxies-shape.test.ts b/tests/unit/proxy-fallback-candidates-listproxies-shape.test.ts index 7194a69f1b94..2014da7361a3 100644 --- a/tests/unit/proxy-fallback-candidates-listproxies-shape.test.ts +++ b/tests/unit/proxy-fallback-candidates-listproxies-shape.test.ts @@ -23,7 +23,7 @@ const { getProxyCandidates } = await import("../../open-sse/utils/proxyFallback. test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("getProxyCandidates() surfaces user-configured proxies against the real listProxies() {items,total} shape", async () => { diff --git a/tests/unit/proxy-fallback-ssrf.test.ts b/tests/unit/proxy-fallback-ssrf.test.ts index cc90a0fef98e..49c904efc6ec 100644 --- a/tests/unit/proxy-fallback-ssrf.test.ts +++ b/tests/unit/proxy-fallback-ssrf.test.ts @@ -13,7 +13,7 @@ const { isRetryableProxyTarget } = await import("../../src/lib/providers/validat const { isPrivateHost } = await import("../../src/shared/network/outboundUrlGuard.ts"); test.after(() => { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); /** @@ -62,7 +62,11 @@ test("isRetryableProxyTarget rejects every private / link-local / metadata host" test("isRetryableProxyTarget allows public provider targets", () => { for (const url of PUBLIC_TARGETS) { - assert.equal(isRetryableProxyTarget(url), true, `${url} should be a valid proxy-fallback target`); + assert.equal( + isRetryableProxyTarget(url), + true, + `${url} should be a valid proxy-fallback target` + ); } }); diff --git a/tests/unit/proxy-health-6246.test.ts b/tests/unit/proxy-health-6246.test.ts index 72668761bf89..ce0160483d54 100644 --- a/tests/unit/proxy-health-6246.test.ts +++ b/tests/unit/proxy-health-6246.test.ts @@ -27,27 +27,24 @@ delete process.env.PROXY_HEALTH_AUTO_DEACTIVATE; const core = await import("../../src/lib/db/core.ts"); const proxiesDb = await import("../../src/lib/db/proxies.ts"); -const { resolveHealthCheckStatusWrite, isProxyHealthAutoDeactivateEnabled } = await import( - "../../src/lib/proxyHealth/statusPolicy.ts" -); -const { POST: autoTestPost } = await import( - "../../src/app/api/settings/proxies/auto-test/route.ts" -); -const { POST: batchActivatePost } = await import( - "../../src/app/api/settings/proxies/batch-activate/route.ts" -); +const { resolveHealthCheckStatusWrite, isProxyHealthAutoDeactivateEnabled } = + await import("../../src/lib/proxyHealth/statusPolicy.ts"); +const { POST: autoTestPost } = + await import("../../src/app/api/settings/proxies/auto-test/route.ts"); +const { POST: batchActivatePost } = + await import("../../src/app/api/settings/proxies/batch-activate/route.ts"); function resetStorage() { delete process.env.INITIAL_PASSWORD; delete process.env.PROXY_HEALTH_AUTO_DEACTIVATE; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ── status-write policy ─────────────────────────────────────────────────────── @@ -146,7 +143,10 @@ test("batch-activate can bulk-disable with status=inactive", async () => { }); const res = await batchActivatePost(req); assert.equal(res.status, 200); - assert.equal((await proxiesDb.getProxyById(a!.id, { includeSecrets: false }))?.status, "inactive"); + assert.equal( + (await proxiesDb.getProxyById(a!.id, { includeSecrets: false }))?.status, + "inactive" + ); }); test("batch-activate rejects an empty ids array with 400", async () => { diff --git a/tests/unit/proxy-health-egress-line.test.ts b/tests/unit/proxy-health-egress-line.test.ts index bf75be512214..91707aaa6c62 100644 --- a/tests/unit/proxy-health-egress-line.test.ts +++ b/tests/unit/proxy-health-egress-line.test.ts @@ -18,28 +18,27 @@ delete process.env.PROXY_LOG_INCLUDE_IPS; const core = await import("../../src/lib/db/core.ts"); const proxyLogger = await import("../../src/lib/proxyLogger.ts"); const proxiesDb = await import("../../src/lib/db/proxies.ts"); -const { forceProxyHealthSweep, formatEgressSharingSummaryLine } = await import( - "../../src/lib/proxyHealth/scheduler.ts" -) as unknown as { - forceProxyHealthSweep: () => Promise; - formatEgressSharingSummaryLine: ( - summary: EgressSharingSummary, - warnings: Array<{ egressIp: string; rotationGroup: string; connections: string[] }>, - includeDetails: boolean - ) => string; -}; +const { forceProxyHealthSweep, formatEgressSharingSummaryLine } = + (await import("../../src/lib/proxyHealth/scheduler.ts")) as unknown as { + forceProxyHealthSweep: () => Promise; + formatEgressSharingSummaryLine: ( + summary: EgressSharingSummary, + warnings: Array<{ egressIp: string; rotationGroup: string; connections: string[] }>, + includeDetails: boolean + ) => string; + }; import type { EgressSharingSummary } from "../../src/lib/proxyEgress.ts"; function resetStorage() { core.resetDbInstance(); proxyLogger.clearProxyLogs(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); delete process.env.PROXY_LOG_INCLUDE_IPS; }); @@ -48,13 +47,20 @@ test("formatEgressSharingSummaryLine is anonymous by default and raw when opted windowStart: "2026-08-20T00:00:00.000Z", windowEnd: "2026-08-21T00:00:00.000Z", distinctEgressIps: 1, - sharingByRotationGroup: [{ rotationGroup: "openai-auth0", sharedIps: 1, maxAccountsSharingOneIp: 2 }], + sharingByRotationGroup: [ + { rotationGroup: "openai-auth0", sharedIps: 1, maxAccountsSharingOneIp: 2 }, + ], maxAccountsSharingOneIp: 2, }; - const warnings = [{ egressIp: "100.115.194.84", rotationGroup: "openai-auth0", connections: ["a", "b"] }]; + const warnings = [ + { egressIp: "100.115.194.84", rotationGroup: "openai-auth0", connections: ["a", "b"] }, + ]; const anonymous = formatEgressSharingSummaryLine(summary, warnings, false); - assert.equal(anonymous, "[ProxyHealth] egress: 1 rotation group(s) share an egress IP (max 2 accounts)"); + assert.equal( + anonymous, + "[ProxyHealth] egress: 1 rotation group(s) share an egress IP (max 2 accounts)" + ); assert.ok(!anonymous.includes("100.115.194.84"), "no IP without opt-in"); const raw = formatEgressSharingSummaryLine(summary, warnings, true); @@ -72,13 +78,29 @@ test("forceProxyHealthSweep logs the anonymous egress line when accounts share a }); // Two codex accounts on one egress IP, persisted (the sweep reads the DB). - proxyLogger.logProxyEvent({ status: "success", provider: "codex", targetUrl: "codex/gpt-5.5", egressIp: "100.115.194.84", account: "acc-a", connectionId: "conn-a" }); - proxyLogger.logProxyEvent({ status: "success", provider: "codex", targetUrl: "codex/gpt-5.5", egressIp: "100.115.194.84", account: "acc-b", connectionId: "conn-b" }); + proxyLogger.logProxyEvent({ + status: "success", + provider: "codex", + targetUrl: "codex/gpt-5.5", + egressIp: "100.115.194.84", + account: "acc-a", + connectionId: "conn-a", + }); + proxyLogger.logProxyEvent({ + status: "success", + provider: "codex", + targetUrl: "codex/gpt-5.5", + egressIp: "100.115.194.84", + account: "acc-b", + connectionId: "conn-b", + }); proxyLogger.flushProxyLogsSync(); // persist the enqueued batch before the sweep reads the DB const logs: string[] = []; const originalLog = console.log; - console.log = (...args: unknown[]) => { logs.push(args.join(" ")); }; + console.log = (...args: unknown[]) => { + logs.push(args.join(" ")); + }; try { await forceProxyHealthSweep(); } finally { @@ -102,13 +124,29 @@ test("forceProxyHealthSweep logs raw details only with PROXY_LOG_INCLUDE_IPS=tru host: "127.0.0.1", port: 1, }); - proxyLogger.logProxyEvent({ status: "success", provider: "codex", targetUrl: "codex/gpt-5.5", egressIp: "100.115.194.84", account: "acc-a", connectionId: "conn-a" }); - proxyLogger.logProxyEvent({ status: "success", provider: "codex", targetUrl: "codex/gpt-5.5", egressIp: "100.115.194.84", account: "acc-b", connectionId: "conn-b" }); + proxyLogger.logProxyEvent({ + status: "success", + provider: "codex", + targetUrl: "codex/gpt-5.5", + egressIp: "100.115.194.84", + account: "acc-a", + connectionId: "conn-a", + }); + proxyLogger.logProxyEvent({ + status: "success", + provider: "codex", + targetUrl: "codex/gpt-5.5", + egressIp: "100.115.194.84", + account: "acc-b", + connectionId: "conn-b", + }); proxyLogger.flushProxyLogsSync(); // persist the enqueued batch before the sweep reads the DB const logs: string[] = []; const originalLog = console.log; - console.log = (...args: unknown[]) => { logs.push(args.join(" ")); }; + console.log = (...args: unknown[]) => { + logs.push(args.join(" ")); + }; try { await forceProxyHealthSweep(); } finally { diff --git a/tests/unit/proxy-health-scheduler-listproxies-shape.test.ts b/tests/unit/proxy-health-scheduler-listproxies-shape.test.ts index 5b9057cca17b..a0c59962716c 100644 --- a/tests/unit/proxy-health-scheduler-listproxies-shape.test.ts +++ b/tests/unit/proxy-health-scheduler-listproxies-shape.test.ts @@ -31,13 +31,13 @@ const { forceProxyHealthSweep } = await import("../../src/lib/proxyHealth/schedu function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("forceProxyHealthSweep() actually probes seeded proxies against the real listProxies() {items,total} shape", async () => { diff --git a/tests/unit/proxy-logger-client-ip.test.ts b/tests/unit/proxy-logger-client-ip.test.ts index 6c7829a3e0c4..2301ceb945f2 100644 --- a/tests/unit/proxy-logger-client-ip.test.ts +++ b/tests/unit/proxy-logger-client-ip.test.ts @@ -13,7 +13,7 @@ const proxyLogger = await import("../../src/lib/proxyLogger.ts"); function resetStorage() { proxyLogger.clearProxyLogs(); core.closeDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } diff --git a/tests/unit/proxy-logs-egress-ip.test.ts b/tests/unit/proxy-logs-egress-ip.test.ts index e3ca2a633176..0ff3c8156542 100644 --- a/tests/unit/proxy-logs-egress-ip.test.ts +++ b/tests/unit/proxy-logs-egress-ip.test.ts @@ -20,7 +20,7 @@ const proxyLogger = await import("../../src/lib/proxyLogger.ts"); function resetStorage() { proxyLogger.clearProxyLogs(); core.closeDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -30,7 +30,7 @@ test.beforeEach(() => { test.after(() => { core.closeDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("fresh install exposes egress_ip and the reconciler is idempotent", async () => { diff --git a/tests/unit/proxy-logs-egress-lookup-10880.test.ts b/tests/unit/proxy-logs-egress-lookup-10880.test.ts index 91f9b99a13f0..9742b0640e37 100644 --- a/tests/unit/proxy-logs-egress-lookup-10880.test.ts +++ b/tests/unit/proxy-logs-egress-lookup-10880.test.ts @@ -17,14 +17,14 @@ const { getRecentEgressIpForConnection } = await import("../../src/lib/db/proxyL function resetStorage() { proxyLogger.clearProxyLogs(); core.closeDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.beforeEach(() => resetStorage()); test.after(() => { core.closeDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("returns the LAST known egress IP of the connection in the window", () => { @@ -43,7 +43,10 @@ test("returns the LAST known egress IP of the connection in the window", () => { connectionId: "conn-a", }); proxyLogger.flushProxyLogsSync(); // persist the enqueued batch before the DB-backed lookup - const got = getRecentEgressIpForConnection("conn-a", new Date(Date.now() - 24 * 3600_000).toISOString()); + const got = getRecentEgressIpForConnection( + "conn-a", + new Date(Date.now() - 24 * 3600_000).toISOString() + ); assert.deepEqual(got, { egressIp: "203.0.113.9", at: got!.at }); }); @@ -55,11 +58,20 @@ test("ignores rows with NULL egress_ip (never probed)", () => { egressIp: null, connectionId: "conn-b", }); - assert.equal(getRecentEgressIpForConnection("conn-b", new Date(Date.now() - 24 * 3600_000).toISOString()), null); + assert.equal( + getRecentEgressIpForConnection("conn-b", new Date(Date.now() - 24 * 3600_000).toISOString()), + null + ); }); test("returns null when the connection has no row in the window", () => { - assert.equal(getRecentEgressIpForConnection("ghost-conn", new Date(Date.now() - 24 * 3600_000).toISOString()), null); + assert.equal( + getRecentEgressIpForConnection( + "ghost-conn", + new Date(Date.now() - 24 * 3600_000).toISOString() + ), + null + ); }); test("does not return rows outside the since window", () => { @@ -70,5 +82,8 @@ test("does not return rows outside the since window", () => { `INSERT INTO proxy_logs (id, timestamp, status, provider, target_url, egress_ip, connection_id) VALUES (?, ?, 'success', 'opencode', 'https://api.opencode.ai/chat', '203.0.113.1', 'conn-c')` ).run(randomUUID(), new Date(Date.now() - 48 * 3600_000).toISOString()); - assert.equal(getRecentEgressIpForConnection("conn-c", new Date(Date.now() - 24 * 3600_000).toISOString()), null); + assert.equal( + getRecentEgressIpForConnection("conn-c", new Date(Date.now() - 24 * 3600_000).toISOString()), + null + ); }); diff --git a/tests/unit/proxy-logs-route.test.ts b/tests/unit/proxy-logs-route.test.ts index 1589e640b15c..663015faf4d7 100644 --- a/tests/unit/proxy-logs-route.test.ts +++ b/tests/unit/proxy-logs-route.test.ts @@ -13,14 +13,14 @@ const proxyLogsRoute = await import("../../src/app/api/usage/proxy-logs/route.ts test.beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); proxyLogger.clearProxyLogs(); }); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("proxy logger public surface excludes removed stats helper", () => { diff --git a/tests/unit/proxy-management-v1-route.test.ts b/tests/unit/proxy-management-v1-route.test.ts index 9dfdd43aa39c..8dca4bc168e1 100644 --- a/tests/unit/proxy-management-v1-route.test.ts +++ b/tests/unit/proxy-management-v1-route.test.ts @@ -39,7 +39,7 @@ async function withEnv(name, value, fn) { async function resetStorage() { delete process.env.INITIAL_PASSWORD; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -65,7 +65,7 @@ async function withPrepareFailure(match, message, fn) { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("v1 management proxies supports create/list/pagination", async () => { diff --git a/tests/unit/proxy-noauth-provider-6272.test.ts b/tests/unit/proxy-noauth-provider-6272.test.ts index 610caa421462..ec64dd731cf4 100644 --- a/tests/unit/proxy-noauth-provider-6272.test.ts +++ b/tests/unit/proxy-noauth-provider-6272.test.ts @@ -15,7 +15,7 @@ const { safeResolveProxy } = await import("../../src/sse/handlers/chatHelpers.ts test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_INITIAL_PASSWORD === undefined) delete process.env.INITIAL_PASSWORD; else process.env.INITIAL_PASSWORD = ORIGINAL_INITIAL_PASSWORD; }); diff --git a/tests/unit/proxy-pool-rotation-6365.test.ts b/tests/unit/proxy-pool-rotation-6365.test.ts index 02c21457a880..59f98787d3f9 100644 --- a/tests/unit/proxy-pool-rotation-6365.test.ts +++ b/tests/unit/proxy-pool-rotation-6365.test.ts @@ -31,7 +31,7 @@ const providersDb = await import("../../src/lib/db/providers.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -60,7 +60,7 @@ async function makeConnection(): Promise { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("round-robin (default for >1) cycles through the whole pool across calls", async () => { @@ -176,7 +176,10 @@ test("random strategy always returns a member of the alive set", async () => { // The random strategy uses crypto.randomInt (not Math.random — CodeQL js/insecure-randomness). // Over 30 picks from a 3-member alive pool it must vary, not stick on one member // (P(all 30 identical) ≈ (1/3)^29 ≈ 0). Guards that randomInt selection is uniform-ish. - assert.ok(seen.size >= 2, `random strategy must vary its pick (saw only: ${[...seen].join(", ")})`); + assert.ok( + seen.size >= 2, + `random strategy must vary its pick (saw only: ${[...seen].join(", ")})` + ); }); test("setScopeRotationStrategy round-trips via getScopeRotationStrategy", async () => { diff --git a/tests/unit/proxy-pool-route-6365.test.ts b/tests/unit/proxy-pool-route-6365.test.ts index 0cb2041e0e6b..594e383693b9 100644 --- a/tests/unit/proxy-pool-route-6365.test.ts +++ b/tests/unit/proxy-pool-route-6365.test.ts @@ -22,9 +22,8 @@ delete process.env.INITIAL_PASSWORD; // auth not required in this test env const core = await import("../../src/lib/db/core.ts"); const proxiesDb = await import("../../src/lib/db/proxies.ts"); -const { GET, PUT, DELETE, PATCH } = await import( - "../../src/app/api/settings/proxies/pool/route.ts" -); +const { GET, PUT, DELETE, PATCH } = + await import("../../src/app/api/settings/proxies/pool/route.ts"); function jsonRequest(method: string, body: unknown): Request { return new Request("http://localhost/api/settings/proxies/pool", { @@ -44,7 +43,7 @@ function getRequest(query: Record): Request { async function resetStorage() { delete process.env.INITIAL_PASSWORD; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -64,7 +63,7 @@ async function makeProxy() { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("add → list → remove round-trips a scope pool", async () => { diff --git a/tests/unit/proxy-pool-sync-4878.test.ts b/tests/unit/proxy-pool-sync-4878.test.ts index 787a2a0b5036..c0b522bf6770 100644 --- a/tests/unit/proxy-pool-sync-4878.test.ts +++ b/tests/unit/proxy-pool-sync-4878.test.ts @@ -13,15 +13,14 @@ delete process.env.OMNIROUTE_API_KEY; const core = await import("../../src/lib/db/core.ts"); const freeProxiesDb = await import("../../src/lib/db/freeProxies.ts"); -const addToPoolRoute = await import( - "../../src/app/api/settings/free-proxies/[id]/add-to-pool/route.ts" -); +const addToPoolRoute = + await import("../../src/app/api/settings/free-proxies/[id]/add-to-pool/route.ts"); const syncRoute = await import("../../src/app/api/settings/free-proxies/sync/route.ts"); const rateLimiter = await import("../../src/shared/utils/rateLimiter.ts"); function reset() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -36,7 +35,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; } else { diff --git a/tests/unit/proxy-registry-route-handlers.test.ts b/tests/unit/proxy-registry-route-handlers.test.ts index b1b66b9c10d5..72f7d274a3c9 100644 --- a/tests/unit/proxy-registry-route-handlers.test.ts +++ b/tests/unit/proxy-registry-route-handlers.test.ts @@ -17,20 +17,19 @@ process.env.API_KEY_SECRET = "test-secret"; const core = await import("../../src/lib/db/core.ts"); const proxiesDb = await import("../../src/lib/db/proxies.ts"); -const { resolveProxyLookupResponse } = await import( - "../../src/lib/api/proxyRegistryRouteHandlers.ts" -); +const { resolveProxyLookupResponse } = + await import("../../src/lib/api/proxyRegistryRouteHandlers.ts"); async function resetStorage() { delete process.env.INITIAL_PASSWORD; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("resolveProxyLookupResponse returns null for the list path (no id)", async () => { diff --git a/tests/unit/proxy-registry.test.ts b/tests/unit/proxy-registry.test.ts index ced37967388f..97f2f76c1a4f 100644 --- a/tests/unit/proxy-registry.test.ts +++ b/tests/unit/proxy-registry.test.ts @@ -14,21 +14,20 @@ const proxiesDb = await import("../../src/lib/db/proxies.ts"); const settingsDb = await import("../../src/lib/db/settings.ts"); const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); const proxiesRoute = await import("../../src/app/api/settings/proxies/route.ts"); -const { createProxyRegistrySchema, updateProxyRegistrySchema } = await import( - "../../src/shared/validation/schemas.ts" -); +const { createProxyRegistrySchema, updateProxyRegistrySchema } = + await import("../../src/shared/validation/schemas.ts"); async function resetStorage() { delete process.env.INITIAL_PASSWORD; core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("proxy registry blocks delete when proxy is still assigned", async () => { diff --git a/tests/unit/proxy-resolution-status-filter.test.ts b/tests/unit/proxy-resolution-status-filter.test.ts index c2286be251ef..1675777f0e40 100644 --- a/tests/unit/proxy-resolution-status-filter.test.ts +++ b/tests/unit/proxy-resolution-status-filter.test.ts @@ -24,13 +24,13 @@ const proxiesDb = await import("../../src/lib/db/proxies.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("resolution SKIPS an account proxy marked inactive", async () => { diff --git a/tests/unit/proxy-rotation-latency.test.ts b/tests/unit/proxy-rotation-latency.test.ts index e37be50496e9..298d5370de2f 100644 --- a/tests/unit/proxy-rotation-latency.test.ts +++ b/tests/unit/proxy-rotation-latency.test.ts @@ -17,7 +17,7 @@ const proxiesDb = await import("../../src/lib/db/proxies.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -48,7 +48,7 @@ function insertLog( test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("latency strategy chooses proxy with lowest average latency within the window", async () => { diff --git a/tests/unit/proxy-subscriptions-route-validation.test.ts b/tests/unit/proxy-subscriptions-route-validation.test.ts index 4b302c7b413b..8baaf1fb7b1c 100644 --- a/tests/unit/proxy-subscriptions-route-validation.test.ts +++ b/tests/unit/proxy-subscriptions-route-validation.test.ts @@ -35,12 +35,9 @@ process.env.API_KEY_SECRET = process.env.API_KEY_SECRET ?? "proxy-sub-route-test delete process.env.INITIAL_PASSWORD; // ensure auth is NOT required const core = await import("../../src/lib/db/core.ts"); -const collectionRoute = await import( - "../../src/app/api/v1/management/proxy-subscriptions/route.ts" -); -const itemRoute = await import( - "../../src/app/api/v1/management/proxy-subscriptions/[id]/route.ts" -); +const collectionRoute = + await import("../../src/app/api/v1/management/proxy-subscriptions/route.ts"); +const itemRoute = await import("../../src/app/api/v1/management/proxy-subscriptions/[id]/route.ts"); function jsonRequest(url: string, body: unknown, method = "POST"): Request { return new Request(url, { @@ -70,7 +67,7 @@ async function createValidSubscription(name: string) { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; @@ -129,7 +126,10 @@ test("POST proxy-subscriptions — valid-JSON non-object (string) body returns 4 // `typeof body !== "object"` guard), so it falls through to the missing-name // check instead — see the array-body test below for that path. A primitive // (string/number/boolean) is the one JSON shape that actually trips this guard. - const req = jsonRequest("http://localhost/api/v1/management/proxy-subscriptions", "just-a-string"); + const req = jsonRequest( + "http://localhost/api/v1/management/proxy-subscriptions", + "just-a-string" + ); const res = await collectionRoute.POST(req); assert.equal(res.status, 400); @@ -326,7 +326,11 @@ test("PATCH proxy-subscriptions/:id — a JSON array body is an 'object' in JS, ); const res = await itemRoute.PATCH(req, { params: Promise.resolve({ id: fixture.id }) }); - assert.equal(res.status, 200, "matches the original inline parser: no typed field matches, no error"); + assert.equal( + res.status, + 200, + "matches the original inline parser: no typed field matches, no error" + ); const body = (await res.json()) as { name?: string }; assert.equal(body.name, "patch-arraybody", "name is unchanged — the array had no usable fields"); }); diff --git a/tests/unit/proxySubscription.service.test.ts b/tests/unit/proxySubscription.service.test.ts index 442788eb795e..264790933607 100644 --- a/tests/unit/proxySubscription.service.test.ts +++ b/tests/unit/proxySubscription.service.test.ts @@ -14,7 +14,7 @@ const sub = await import("../../src/lib/proxySubscription/index.ts"); function reset() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -47,7 +47,7 @@ function insertSubscription( test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("global subscription binds its pool to the global scope and is resolvable", async () => { @@ -171,7 +171,9 @@ test("deleteSubscription unbinds and removes its proxy rows", async () => { assert.equal(rows.length, 0, "subscription proxy rows should be removed"); const assignments = db - .prepare("SELECT 1 FROM proxy_assignments a JOIN proxy_registry p ON p.id=a.proxy_id WHERE p.source='subscription' LIMIT 1") + .prepare( + "SELECT 1 FROM proxy_assignments a JOIN proxy_registry p ON p.id=a.proxy_id WHERE p.source='subscription' LIMIT 1" + ) .get(); assert.equal(assignments, undefined, "no subscription proxy should remain assigned"); diff --git a/tests/unit/puter-provider-removed.test.ts b/tests/unit/puter-provider-removed.test.ts index 86dd593c92c2..abe80ff1db40 100644 --- a/tests/unit/puter-provider-removed.test.ts +++ b/tests/unit/puter-provider-removed.test.ts @@ -21,7 +21,7 @@ const core = await import("../../src/lib/db/core.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("puter provider is removed from the chat registry (id and alias)", () => { diff --git a/tests/unit/qiniu-provider.test.ts b/tests/unit/qiniu-provider.test.ts index f98f38623ec6..438c1b93a480 100644 --- a/tests/unit/qiniu-provider.test.ts +++ b/tests/unit/qiniu-provider.test.ts @@ -61,13 +61,13 @@ const modelsRoute = await import("../../src/app/api/providers/[id]/models/route. async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); interface ModelsBody { diff --git a/tests/unit/qoder-cli.test.ts b/tests/unit/qoder-cli.test.ts index 12fbdce91800..c94f0f641e27 100644 --- a/tests/unit/qoder-cli.test.ts +++ b/tests/unit/qoder-cli.test.ts @@ -38,7 +38,7 @@ function withStubQoderCli(fn: () => void | Promise) { const restore = () => { if (prevBin === undefined) delete process.env.CLI_QODER_BIN; else process.env.CLI_QODER_BIN = prevBin; - fs.rmSync(dir, { recursive: true, force: true }); + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }; return Promise.resolve().then(fn).finally(restore); } @@ -489,7 +489,7 @@ test("runQoderCli survives qodercli exiting before it reads a large stdin (async } finally { if (prevBin === undefined) delete process.env.CLI_QODER_BIN; else process.env.CLI_QODER_BIN = prevBin; - fs.rmSync(dir, { recursive: true, force: true }); + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -516,7 +516,7 @@ test("runQoderCli preserves multi-byte UTF-8 output (Chinese) via stream setEnco } finally { if (prevBin === undefined) delete process.env.CLI_QODER_BIN; else process.env.CLI_QODER_BIN = prevBin; - fs.rmSync(dir, { recursive: true, force: true }); + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -581,6 +581,6 @@ test("runQoderCli resolves the request against live --list-models and passes the qoderCli.__clearQoderModelNamesCache(); if (prevBin === undefined) delete process.env.CLI_QODER_BIN; else process.env.CLI_QODER_BIN = prevBin; - fs.rmSync(dir, { recursive: true, force: true }); + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/qoder-executor.test.ts b/tests/unit/qoder-executor.test.ts index 9d0fd974cd69..f3bcabdd58a3 100644 --- a/tests/unit/qoder-executor.test.ts +++ b/tests/unit/qoder-executor.test.ts @@ -49,7 +49,7 @@ function withStubQoderCli(fn: () => void | Promise) { const restore = () => { if (prevBin === undefined) delete process.env.CLI_QODER_BIN; else process.env.CLI_QODER_BIN = prevBin; - fs.rmSync(dir, { recursive: true, force: true }); + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }; return Promise.resolve().then(fn).finally(restore); } @@ -425,7 +425,7 @@ test("QoderExecutor: surfaces qodercli stderr when is_error=true with empty resu } finally { if (prevBin === undefined) delete process.env.CLI_QODER_BIN; else process.env.CLI_QODER_BIN = prevBin; - fs.rmSync(dir, { recursive: true, force: true }); + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/qoder-jobtoken-exchange-4683.test.ts b/tests/unit/qoder-jobtoken-exchange-4683.test.ts index b6468e16cfe0..3ea87f6a6e4f 100644 --- a/tests/unit/qoder-jobtoken-exchange-4683.test.ts +++ b/tests/unit/qoder-jobtoken-exchange-4683.test.ts @@ -198,7 +198,7 @@ test("validateQoderCliPat validates via qodercli and makes no Cosy/jobToken HTTP globalThis.fetch = originalFetch; if (prevBin === undefined) delete process.env.CLI_QODER_BIN; else process.env.CLI_QODER_BIN = prevBin; - fs.rmSync(dir, { recursive: true, force: true }); + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); __clearQoderJobTokenCache(); } }); diff --git a/tests/unit/quota-cache-hydrate-5015.test.ts b/tests/unit/quota-cache-hydrate-5015.test.ts index e692ceb71168..c6ad4114cd22 100644 --- a/tests/unit/quota-cache-hydrate-5015.test.ts +++ b/tests/unit/quota-cache-hydrate-5015.test.ts @@ -26,7 +26,7 @@ const quotaCache = await import("../../src/domain/quotaCache.ts"); test.after(() => { coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#5015 isAccountQuotaExhausted hydrates exhausted state from a persisted snapshot", () => { diff --git a/tests/unit/quota-cache-is-exhausted-per-window-5923.test.ts b/tests/unit/quota-cache-is-exhausted-per-window-5923.test.ts index e1f1c0cb9b5b..4b52d4d2355d 100644 --- a/tests/unit/quota-cache-is-exhausted-per-window-5923.test.ts +++ b/tests/unit/quota-cache-is-exhausted-per-window-5923.test.ts @@ -27,7 +27,7 @@ const quotaCache = await import("../../src/domain/quotaCache.ts"); test.after(() => { coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#5923 setQuotaCache writes is_exhausted per-window, not the connection-wide AND aggregate", () => { diff --git a/tests/unit/quota-combo-balancing.test.ts b/tests/unit/quota-combo-balancing.test.ts index f5cd33149974..f45e12a3f405 100644 --- a/tests/unit/quota-combo-balancing.test.ts +++ b/tests/unit/quota-combo-balancing.test.ts @@ -85,7 +85,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // best-effort cleanup } diff --git a/tests/unit/quota-combo-cli-providers.test.ts b/tests/unit/quota-combo-cli-providers.test.ts index a7dd90b5e52c..604dc1487ec4 100644 --- a/tests/unit/quota-combo-cli-providers.test.ts +++ b/tests/unit/quota-combo-cli-providers.test.ts @@ -29,7 +29,8 @@ const { REGISTRY } = await import("../../open-sse/config/providerRegistry.ts"); async function resetStorage() { core.resetDbInstance(); - if (fs.existsSync(TEST_DATA_DIR)) fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + if (fs.existsSync(TEST_DATA_DIR)) + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -39,7 +40,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); const CLI_PROVIDER = "codex"; // absent from PROVIDER_MODELS, present in REGISTRY diff --git a/tests/unit/quota-combo-groups.test.ts b/tests/unit/quota-combo-groups.test.ts index f182fdeb0a6d..7a271598fb86 100644 --- a/tests/unit/quota-combo-groups.test.ts +++ b/tests/unit/quota-combo-groups.test.ts @@ -18,9 +18,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -const TEST_DATA_DIR = fs.mkdtempSync( - path.join(os.tmpdir(), "omniroute-quota-combo-groups-") -); +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-quota-combo-groups-")); process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); @@ -29,9 +27,8 @@ const providersDb = await import("../../src/lib/db/providers.ts"); const combosDb = await import("../../src/lib/db/combos.ts"); const { createGroup } = await import("../../src/lib/db/quotaGroups.ts"); const { syncQuotaCombos } = await import("../../src/lib/quota/quotaCombos.ts"); -const { isQuotaModelName, parseQuotaModelName, quotaGroupSlug } = await import( - "../../src/lib/quota/quotaModelNaming.ts" -); +const { isQuotaModelName, parseQuotaModelName, quotaGroupSlug } = + await import("../../src/lib/quota/quotaModelNaming.ts"); // --------------------------------------------------------------------------- // Lifecycle @@ -42,7 +39,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: unknown) { @@ -63,7 +60,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // --------------------------------------------------------------------------- @@ -139,7 +136,10 @@ test("G1: two pools in same group → combos named qtSd//provider/model ( const p = parseQuotaModelName(c.name); return p?.groupSlug === groupSlug && p?.provider === "openrouter"; }); - assert.ok(orCombos.length > 0, `Expected openrouter combos under qtSd/${groupSlug}/openrouter/...`); + assert.ok( + orCombos.length > 0, + `Expected openrouter combos under qtSd/${groupSlug}/openrouter/...` + ); // Combos for baidu must exist under the group slug const baiduCombos = allCombos.filter((c) => { @@ -287,7 +287,14 @@ test("G4: stale same-group same-provider combo is pruned on re-sync", async () = const staleComboName = `qtSd/${groupSlug}/openrouter/fake-stale-model`; await combosDb.createCombo({ name: staleComboName, - models: [{ kind: "model", model: "openrouter/fake-stale-model", providerId: "openrouter", weight: 100 }], + models: [ + { + kind: "model", + model: "openrouter/fake-stale-model", + providerId: "openrouter", + weight: 100, + }, + ], strategy: "priority", isHidden: true, }); diff --git a/tests/unit/quota-combos-sync.test.ts b/tests/unit/quota-combos-sync.test.ts index a0a1b5b50261..1e6b0e54b92a 100644 --- a/tests/unit/quota-combos-sync.test.ts +++ b/tests/unit/quota-combos-sync.test.ts @@ -22,12 +22,10 @@ const core = await import("../../src/lib/db/core.ts"); const poolsDb = await import("../../src/lib/db/quotaPools.ts"); const providersDb = await import("../../src/lib/db/providers.ts"); const combosDb = await import("../../src/lib/db/combos.ts"); -const { syncQuotaCombos, removeQuotaCombosForPool } = await import( - "../../src/lib/quota/quotaCombos.ts" -); -const { quotaModelName, isQuotaModelName, parseQuotaModelName, quotaPoolSlug } = await import( - "../../src/lib/quota/quotaModelNaming.ts" -); +const { syncQuotaCombos, removeQuotaCombosForPool } = + await import("../../src/lib/quota/quotaCombos.ts"); +const { quotaModelName, isQuotaModelName, parseQuotaModelName, quotaPoolSlug } = + await import("../../src/lib/quota/quotaModelNaming.ts"); const { PROVIDER_MODELS } = await import("../../open-sse/config/providerModels.ts"); // --------------------------------------------------------------------------- @@ -39,7 +37,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: unknown) { @@ -60,7 +58,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // --------------------------------------------------------------------------- @@ -312,7 +310,11 @@ test("syncQuotaCombos: does not affect quota combos for a different provider in }); assert.equal(remainingForA.length, 0, "PoolAlpha (glm) combos should all be removed"); - assert.equal(remainingForB.length, forB.length, "PoolBeta (openrouter) combos should be untouched"); + assert.equal( + remainingForB.length, + forB.length, + "PoolBeta (openrouter) combos should be untouched" + ); }); test("syncQuotaCombos: unknown pool id — no throw, prunes nothing (no combos exist)", async () => { diff --git a/tests/unit/quota-epsilon-unconfigured-allow.test.ts b/tests/unit/quota-epsilon-unconfigured-allow.test.ts index 1d93c6053efd..ba1a8ae80c98 100644 --- a/tests/unit/quota-epsilon-unconfigured-allow.test.ts +++ b/tests/unit/quota-epsilon-unconfigured-allow.test.ts @@ -40,7 +40,7 @@ test.after(() => { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { /* ignore */ } diff --git a/tests/unit/quota-exclusive-catalog-4806.test.ts b/tests/unit/quota-exclusive-catalog-4806.test.ts index 75cd30bcece2..a55fc34146dc 100644 --- a/tests/unit/quota-exclusive-catalog-4806.test.ts +++ b/tests/unit/quota-exclusive-catalog-4806.test.ts @@ -47,7 +47,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: unknown) { @@ -69,7 +69,7 @@ test.beforeEach(async () => { test.after(async () => { apiKeysDb.resetApiKeyState(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#4806 quota-exclusive key lists its qtSd/* virtual models in GET /v1/models", async () => { diff --git a/tests/unit/quota-exclusive-catalog-short-circuit.test.ts b/tests/unit/quota-exclusive-catalog-short-circuit.test.ts index a20cb9ee6062..db0c37d8a49a 100644 --- a/tests/unit/quota-exclusive-catalog-short-circuit.test.ts +++ b/tests/unit/quota-exclusive-catalog-short-circuit.test.ts @@ -66,7 +66,7 @@ test.after(async () => { apiKeysDb.resetApiKeyState(); core.resetDbInstance(); try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { /* ignore */ } diff --git a/tests/unit/quota-exclusivity-reconcile.test.ts b/tests/unit/quota-exclusivity-reconcile.test.ts index 89ef35e2b397..53932921d18d 100644 --- a/tests/unit/quota-exclusivity-reconcile.test.ts +++ b/tests/unit/quota-exclusivity-reconcile.test.ts @@ -17,19 +17,14 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -const TEST_DATA_DIR = fs.mkdtempSync( - path.join(os.tmpdir(), "omniroute-quota-exclusivity-"), -); +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-quota-exclusivity-")); process.env.DATA_DIR = TEST_DATA_DIR; -process.env.API_KEY_SECRET = - process.env.API_KEY_SECRET || "exclusivity-reconcile-test-secret"; +process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "exclusivity-reconcile-test-secret"; const core = await import("../../src/lib/db/core.ts"); const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); const poolsDb = await import("../../src/lib/db/quotaPools.ts"); -const { reconcilePoolExclusivity } = await import( - "../../src/lib/quota/quotaKey.ts" -); +const { reconcilePoolExclusivity } = await import("../../src/lib/quota/quotaKey.ts"); // --------------------------------------------------------------------------- // Test lifecycle helpers @@ -42,7 +37,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: unknown) { @@ -74,7 +69,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // --------------------------------------------------------------------------- @@ -147,7 +142,7 @@ test("reconcilePoolExclusivity: idempotent — calling twice with same args does assert.equal( quotasAfterFirst.filter((q) => q === pool.id).length, 1, - "poolId should appear exactly once", + "poolId should appear exactly once" ); // Second call — idempotent @@ -158,12 +153,12 @@ test("reconcilePoolExclusivity: idempotent — calling twice with same args does assert.deepEqual( quotasAfterSecond, quotasAfterFirst, - "allowedQuotas should be unchanged after second call", + "allowedQuotas should be unchanged after second call" ); assert.equal( quotasAfterSecond.filter((q) => q === pool.id).length, 1, - "poolId should still appear exactly once", + "poolId should still appear exactly once" ); }); @@ -194,7 +189,7 @@ test("reconcilePoolExclusivity: missing/unknown keyId is skipped defensively (no // ghost-key does not exist in the DB; must not throw await assert.doesNotReject( () => reconcilePoolExclusivity(pool.id, [], ["ghost-key-id-that-does-not-exist"], true), - "should not throw for unknown key IDs", + "should not throw for unknown key IDs" ); }); diff --git a/tests/unit/quota-exhaustion-cutoff-opencode.test.ts b/tests/unit/quota-exhaustion-cutoff-opencode.test.ts index d212fc9394dd..c8aaa04d1209 100644 --- a/tests/unit/quota-exhaustion-cutoff-opencode.test.ts +++ b/tests/unit/quota-exhaustion-cutoff-opencode.test.ts @@ -54,15 +54,12 @@ const quotaSnapshotsDb = await import("../../src/lib/db/quotaSnapshots.ts"); const quotaCache = await import("../../src/domain/quotaCache.ts"); const providersDb = await import("../../src/lib/db/providers.ts"); const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); -const { fetchOpencodeQuota, invalidateOpencodeQuotaCache } = await import( - "../../open-sse/services/opencodeQuotaFetcher.ts" -); -const { evaluateQuotaCutoff, registerQuotaFetcher } = await import( - "../../open-sse/services/quotaPreflight.ts" -); -const { buildAutoQuotaThresholds } = await import( - "../../open-sse/services/combo/quotaExhaustionCutoff.ts" -); +const { fetchOpencodeQuota, invalidateOpencodeQuotaCache } = + await import("../../open-sse/services/opencodeQuotaFetcher.ts"); +const { evaluateQuotaCutoff, registerQuotaFetcher } = + await import("../../open-sse/services/quotaPreflight.ts"); +const { buildAutoQuotaThresholds } = + await import("../../open-sse/services/combo/quotaExhaustionCutoff.ts"); const { resolveResilienceSettings } = await import("../../src/lib/resilience/settings.ts"); const auth = await import("../../src/sse/services/auth.ts"); @@ -112,7 +109,7 @@ test.after(() => { globalThis.fetch = originalFetch; coreDb.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test.afterEach(() => { @@ -137,7 +134,10 @@ test("#11234 dashboard snapshots feed the quota cutoff when the live endpoint ha const quota = await fetchOpencodeQuota(connectionId, dashboardConfiguredConnection("sk-test")); - assert.ok(quota, "fetcher must synthesize quota from dashboard snapshots when the live endpoint 404s"); + assert.ok( + quota, + "fetcher must synthesize quota from dashboard snapshots when the live endpoint 404s" + ); assert.equal(fetchCalls, 1, "snapshot bridge must be read-only — no re-scrape on the hot path"); // Key mapping: weekly → window_weekly (0% remaining = 100% used), @@ -148,10 +148,7 @@ test("#11234 dashboard snapshots feed the quota cutoff when the live endpoint ha `window_5h percentUsed should be ~0.2, got ${quota.windows?.[WINDOW_5H]?.percentUsed}` ); - const decision = evaluateQuotaCutoff( - quota, - buildAutoQuotaThresholds(PROVIDER, undefined, null) - ); + const decision = evaluateQuotaCutoff(quota, buildAutoQuotaThresholds(PROVIDER, undefined, null)); assert.equal(decision.proceed, false, "weekly at 0% remaining must block the connection"); assert.equal(decision.reason, "quota_exhausted"); @@ -177,10 +174,7 @@ test("#11234 a snapshot whose reset already passed must not count as exhausted", "an expired weekly window must be dropped from the synthesized quota" ); - const decision = evaluateQuotaCutoff( - quota, - buildAutoQuotaThresholds(PROVIDER, undefined, null) - ); + const decision = evaluateQuotaCutoff(quota, buildAutoQuotaThresholds(PROVIDER, undefined, null)); assert.equal(decision.proceed, true, "an expired weekly window must not block the connection"); invalidateOpencodeQuotaCache(connectionId); diff --git a/tests/unit/quota-group-allocations.test.ts b/tests/unit/quota-group-allocations.test.ts index c8c47d6783ad..70e2cf907550 100644 --- a/tests/unit/quota-group-allocations.test.ts +++ b/tests/unit/quota-group-allocations.test.ts @@ -31,13 +31,10 @@ import os from "node:os"; import path from "node:path"; // ── DB / store harness ──────────────────────────────────────────────────────── -const TEST_DATA_DIR = fs.mkdtempSync( - path.join(os.tmpdir(), "omniroute-quota-group-alloc-"), -); +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-quota-group-alloc-")); process.env.DATA_DIR = TEST_DATA_DIR; // Ensure a deterministic secret for apiKey tests (check 5). -process.env.API_KEY_SECRET = - process.env.API_KEY_SECRET || "group-alloc-test-secret-32ch-xxxx"; +process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "group-alloc-test-secret-32ch-xxxx"; const core = await import("../../src/lib/db/core.ts"); const poolsDb = await import("../../src/lib/db/quotaPools.ts"); @@ -46,9 +43,8 @@ const providersDb = await import("../../src/lib/db/providers.ts"); const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); const { enforceQuotaShare } = await import("../../src/lib/quota/enforce.ts"); const { resolveQuotaKeyScope } = await import("../../src/lib/quota/quotaKey.ts"); -const { isQuotaModelName, parseQuotaModelName, quotaModelName, quotaGroupSlug } = await import( - "../../src/lib/quota/quotaModelNaming.ts" -); +const { isQuotaModelName, parseQuotaModelName, quotaModelName, quotaGroupSlug } = + await import("../../src/lib/quota/quotaModelNaming.ts"); // --------------------------------------------------------------------------- // Lifecycle @@ -64,7 +60,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: unknown) { @@ -89,7 +85,7 @@ test.after(async () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any (apiKeysDb as any).resetApiKeyState(); } - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // --------------------------------------------------------------------------- @@ -190,8 +186,16 @@ test("upsertAllocations: single-pool group — only that pool is written", async const groupOther = groupsDb.createGroup("GroupOther3"); const connO = await mkConn("baidu", "conn-alloc-o3"); - const poolZ = poolsDb.createPool({ connectionId: connZ, name: "Pool Z3", groupId: groupSingle.id }); - const poolO = poolsDb.createPool({ connectionId: connO, name: "Pool O3", groupId: groupOther.id }); + const poolZ = poolsDb.createPool({ + connectionId: connZ, + name: "Pool Z3", + groupId: groupSingle.id, + }); + const poolO = poolsDb.createPool({ + connectionId: connO, + name: "Pool O3", + groupId: groupOther.id, + }); poolsDb.upsertAllocations(poolZ.id, [{ apiKeyId: "k3", weight: 100, policy: "hard" }]); @@ -199,7 +203,11 @@ test("upsertAllocations: single-pool group — only that pool is written", async assert.equal(getAllocs(poolZ.id).length, 1, "poolZ should have 1 allocation"); // poolO (different group) should have NO rows - assert.equal(getAllocs(poolO.id).length, 0, "poolO (different group) must not receive propagated rows"); + assert.equal( + getAllocs(poolO.id).length, + 0, + "poolO (different group) must not receive propagated rows" + ); }); // --------------------------------------------------------------------------- @@ -212,8 +220,16 @@ test("enforceQuotaShare: key k1 allocated via pool A is enforced when calling po const connA = await mkConn("openrouter", "conn-enforce-a4"); const connB = await mkConn("baidu", "conn-enforce-b4"); - const poolA = poolsDb.createPool({ connectionId: connA, name: "Pool EnforceA4", groupId: groupG.id }); - const poolB = poolsDb.createPool({ connectionId: connB, name: "Pool EnforceB4", groupId: groupG.id }); + const poolA = poolsDb.createPool({ + connectionId: connA, + name: "Pool EnforceA4", + groupId: groupG.id, + }); + const poolB = poolsDb.createPool({ + connectionId: connB, + name: "Pool EnforceB4", + groupId: groupG.id, + }); // Allocate k1 via pool A — propagation should write to pool B as well poolsDb.upsertAllocations(poolA.id, [{ apiKeyId: "k1", weight: 50, policy: "hard" }]); @@ -244,7 +260,11 @@ test("enforceQuotaShare: key k1 allocated via pool A is enforced when calling po // rows, and the pool-connection-match loop would find no pool for connB → allow (fail-open). // Both paths return allow here, but the key difference is the allocation row IS present // in pool B (asserted above) — the enforce path will find it and proceed to plan check. - assert.equal(result.kind, "allow", "enforceQuotaShare should allow (no plan dims for test provider)"); + assert.equal( + result.kind, + "allow", + "enforceQuotaShare should allow (no plan dims for test provider)" + ); }); // --------------------------------------------------------------------------- @@ -259,13 +279,25 @@ test("apiKeyPolicy groupSlug check: key in group G allowed for B's qtSd model, d const connA = await mkConn("openrouter", "conn-policy-a5"); const connB = await mkConn("baidu", "conn-policy-b5"); - const poolA = poolsDb.createPool({ connectionId: connA, name: "Pool PolicyA5", groupId: groupG.id }); - const poolB = poolsDb.createPool({ connectionId: connB, name: "Pool PolicyB5", groupId: groupG.id }); + const poolA = poolsDb.createPool({ + connectionId: connA, + name: "Pool PolicyA5", + groupId: groupG.id, + }); + const poolB = poolsDb.createPool({ + connectionId: connB, + name: "Pool PolicyB5", + groupId: groupG.id, + }); // Also create a different group with its own pool const groupH = groupsDb.createGroup("GroupPolicyH5"); const connH = await mkConn("openrouter", "conn-policy-h5"); - const poolH = poolsDb.createPool({ connectionId: connH, name: "Pool PolicyH5", groupId: groupH.id }); + const poolH = poolsDb.createPool({ + connectionId: connH, + name: "Pool PolicyH5", + groupId: groupH.id, + }); // Key is allocated to pool A only (allowedQuotas=[poolA.id]) poolsDb.upsertAllocations(poolA.id, [{ apiKeyId: "k5", weight: 50, policy: "hard" }]); @@ -273,8 +305,8 @@ test("apiKeyPolicy groupSlug check: key in group G allowed for B's qtSd model, d // Resolve the key's scope const scope = await resolveQuotaKeyScope([poolA.id]); - const gSlug = quotaGroupSlug(groupG.name); // "grouppolicy5" - const hSlug = quotaGroupSlug(groupH.name); // "grouppolicyh5" + const gSlug = quotaGroupSlug(groupG.name); // "grouppolicy5" + const hSlug = quotaGroupSlug(groupH.name); // "grouppolicyh5" // Pool B's qtSd model (belongs to group G) const modelB = quotaModelName(groupG.name, "baidu", "ernie-4.5"); @@ -334,7 +366,11 @@ test("upsertAllocations: propagates to all 3 pools in the same group", async () { apiKeyId: "k6b", weight: 60, policy: "soft" }, ]); - for (const [label, pid] of [["A", poolA.id], ["B", poolB.id], ["C", poolC.id]] as [string, string][]) { + for (const [label, pid] of [ + ["A", poolA.id], + ["B", poolB.id], + ["C", poolC.id], + ] as [string, string][]) { const allocs = getAllocs(pid); assert.equal(allocs.length, 2, `pool ${label} should have 2 allocations`); const k6a = allocs.find((a) => a.apiKeyId === "k6a"); diff --git a/tests/unit/quota-group-scope.test.ts b/tests/unit/quota-group-scope.test.ts index f5e27630bd23..814c29e68d55 100644 --- a/tests/unit/quota-group-scope.test.ts +++ b/tests/unit/quota-group-scope.test.ts @@ -43,7 +43,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: unknown) { @@ -64,7 +64,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // --------------------------------------------------------------------------- @@ -100,7 +100,10 @@ test("resolveQuotaKeyScope: key in pool A sees ALL connections/providers of grou // Must include both connections assert.ok(scope.connectionIds.includes(idA), "should include pool A connection"); - assert.ok(scope.connectionIds.includes(idB), "should include pool B connection (group expansion)"); + assert.ok( + scope.connectionIds.includes(idB), + "should include pool B connection (group expansion)" + ); assert.equal(scope.connectionIds.length, 2, "exactly 2 connections"); // Must include both providers @@ -207,7 +210,10 @@ test("filterModelsToQuotaPools: keeps both providers' qtSd//... models fr assert.equal(result.length, 2, "should return both providers' models for the group"); assert.ok(result.some((m) => m.id === `qtSd/${groupSlug}/openrouter/gpt-5.5`)); assert.ok(result.some((m) => m.id === `qtSd/${groupSlug}/baidu/ernie-4.5`)); - assert.ok(!result.some((m) => m.id === `qtSd/otherg/openrouter/gpt-5.5`), "other group filtered out"); + assert.ok( + !result.some((m) => m.id === `qtSd/otherg/openrouter/gpt-5.5`), + "other group filtered out" + ); assert.ok(!result.some((m) => m.id === "gpt-5.5"), "non-quota model filtered out"); }); @@ -240,7 +246,11 @@ test("resolveQuotaKeyScope: orphan pool in group that also has a valid pool — apiKey: "sk-partial-valid", }); const idValid = (connValid as Record).id as string; - const validPool = poolsDb.createPool({ connectionId: idValid, name: "Valid Pool G", groupId: groupG.id }); + const validPool = poolsDb.createPool({ + connectionId: idValid, + name: "Valid Pool G", + groupId: groupG.id, + }); // One orphan pool in the same group const orphanPool = poolsDb.createPool({ @@ -254,7 +264,10 @@ test("resolveQuotaKeyScope: orphan pool in group that also has a valid pool — // The group has a valid connection (the validPool's connection) so group slug should be included const expectedSlug = quotaGroupSlug(groupG.name); - assert.ok(scope.poolSlugs.includes(expectedSlug), "group slug should be included since group has valid connection"); + assert.ok( + scope.poolSlugs.includes(expectedSlug), + "group slug should be included since group has valid connection" + ); assert.ok(scope.connectionIds.includes(idValid), "should include the valid pool's connection"); assert.ok(scope.providers.includes("openrouter"), "should include openrouter from valid pool"); diff --git a/tests/unit/quota-groups-crud.test.ts b/tests/unit/quota-groups-crud.test.ts index 6bbaf313a406..057b39de40a9 100644 --- a/tests/unit/quota-groups-crud.test.ts +++ b/tests/unit/quota-groups-crud.test.ts @@ -31,7 +31,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (err: any) { @@ -51,7 +51,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ── B2.1: createGroup / getGroup / getGroupName ─────────────────────────────── diff --git a/tests/unit/quota-groups-migration.test.ts b/tests/unit/quota-groups-migration.test.ts index 88594c287aca..bfaa80e6a997 100644 --- a/tests/unit/quota-groups-migration.test.ts +++ b/tests/unit/quota-groups-migration.test.ts @@ -30,7 +30,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (err: any) { @@ -50,13 +50,15 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // Helper to get a raw DB handle for inspection / seeding. function getDb() { return core.getDbInstance() as unknown as { - prepare: (sql: string) => { + prepare: ( + sql: string + ) => { all: (...params: unknown[]) => TRow[]; get: (...params: unknown[]) => TRow | undefined; run: (...params: unknown[]) => { changes: number }; @@ -72,10 +74,7 @@ test("migration 087 file exists", () => { }); test("migration 087 contains quota_groups CREATE TABLE", () => { - const sql = fs.readFileSync( - path.resolve("src/lib/db/migrations/088_quota_groups.sql"), - "utf8" - ); + const sql = fs.readFileSync(path.resolve("src/lib/db/migrations/088_quota_groups.sql"), "utf8"); assert.ok(sql.includes("quota_groups"), "migration SQL should reference quota_groups"); assert.ok( sql.includes("CREATE TABLE IF NOT EXISTS quota_groups"), @@ -84,14 +83,8 @@ test("migration 087 contains quota_groups CREATE TABLE", () => { }); test("migration 087 seeds group-demo", () => { - const sql = fs.readFileSync( - path.resolve("src/lib/db/migrations/088_quota_groups.sql"), - "utf8" - ); - assert.ok( - sql.includes("group-demo"), - "migration SQL should insert the 'group-demo' seed row" - ); + const sql = fs.readFileSync(path.resolve("src/lib/db/migrations/088_quota_groups.sql"), "utf8"); + assert.ok(sql.includes("group-demo"), "migration SQL should insert the 'group-demo' seed row"); assert.ok( sql.includes("INSERT OR IGNORE INTO quota_groups"), "migration SQL should use INSERT OR IGNORE for idempotency" @@ -99,10 +92,7 @@ test("migration 087 seeds group-demo", () => { }); test("migration 087 adds group_id column to quota_pools", () => { - const sql = fs.readFileSync( - path.resolve("src/lib/db/migrations/088_quota_groups.sql"), - "utf8" - ); + const sql = fs.readFileSync(path.resolve("src/lib/db/migrations/088_quota_groups.sql"), "utf8"); assert.ok( sql.includes("ALTER TABLE quota_pools ADD COLUMN group_id"), "migration SQL should ALTER TABLE quota_pools to add group_id" @@ -110,10 +100,7 @@ test("migration 087 adds group_id column to quota_pools", () => { }); test("migration 087 contains backfill UPDATE for existing pools", () => { - const sql = fs.readFileSync( - path.resolve("src/lib/db/migrations/088_quota_groups.sql"), - "utf8" - ); + const sql = fs.readFileSync(path.resolve("src/lib/db/migrations/088_quota_groups.sql"), "utf8"); assert.ok( sql.includes("UPDATE quota_pools SET group_id = 'group-demo'"), "migration SQL should backfill existing pools to group-demo" diff --git a/tests/unit/quota-key-resolve.test.ts b/tests/unit/quota-key-resolve.test.ts index 9c6630fd27a0..707f89ec0849 100644 --- a/tests/unit/quota-key-resolve.test.ts +++ b/tests/unit/quota-key-resolve.test.ts @@ -32,7 +32,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: unknown) { @@ -53,7 +53,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // --------------------------------------------------------------------------- diff --git a/tests/unit/quota-multiprovider.test.ts b/tests/unit/quota-multiprovider.test.ts index d15b0e6a5b83..1b15546a1c0e 100644 --- a/tests/unit/quota-multiprovider.test.ts +++ b/tests/unit/quota-multiprovider.test.ts @@ -113,7 +113,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // best-effort cleanup } diff --git a/tests/unit/quota-per-key-model-hotpath.test.ts b/tests/unit/quota-per-key-model-hotpath.test.ts index ca211750f57d..506ab9472e6f 100644 --- a/tests/unit/quota-per-key-model-hotpath.test.ts +++ b/tests/unit/quota-per-key-model-hotpath.test.ts @@ -37,9 +37,8 @@ const { createPool, upsertAllocations } = await import("../../src/lib/db/quotaPo const { setModelCap } = await import("../../src/lib/db/quotaModelCaps.ts"); const { enforceQuotaShare } = await import("../../src/lib/quota/enforce.ts"); const { resetQuotaStoreSingleton } = await import("../../src/lib/quota/storeFactory.ts"); -const { scheduleQuotaShareConsumption } = await import( - "../../open-sse/handlers/chatCore/quotaShareConsumption.ts" -); +const { scheduleQuotaShareConsumption } = + await import("../../open-sse/handlers/chatCore/quotaShareConsumption.ts"); // ── Fixtures ────────────────────────────────────────────────────────────────── const CONN_ID = "conn-model-cap-hotpath"; @@ -55,7 +54,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (err: unknown) { @@ -76,7 +75,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function makePool() { @@ -145,7 +144,13 @@ async function enforceUntil( // --------------------------------------------------------------------------- test("hot-path: model cap blocks after N consumptions driven through scheduleQuotaShareConsumption", async () => { const pool = makePool(); - setModelCap({ poolId: pool.id, apiKeyId: KEY_A, model: MODEL_M, capValue: CAP_N, capUnit: "requests" }); + setModelCap({ + poolId: pool.id, + apiKeyId: KEY_A, + model: MODEL_M, + capValue: CAP_N, + capUnit: "requests", + }); // Drive CAP_N consumptions through the REAL non-streaming hot-path hook. await consumeViaHotPath(MODEL_M, CAP_N); @@ -164,7 +169,7 @@ test("hot-path: model cap blocks after N consumptions driven through scheduleQuo assert.equal(blocked.kind, "block", "model M must be blocked after N hot-path consumptions"); assert.ok( "reason" in blocked && blocked.reason.includes("model-cap"), - `reason must mention model-cap; got: ${"reason" in blocked ? blocked.reason : "(no reason)"}`, + `reason must mention model-cap; got: ${"reason" in blocked ? blocked.reason : "(no reason)"}` ); // A different model in the SAME pool (no cap) must still be allowed. @@ -184,7 +189,13 @@ test("hot-path: model cap blocks after N consumptions driven through scheduleQuo // --------------------------------------------------------------------------- test("hot-path: enforce WITHOUT model never triggers model-cap block (fail-safe)", async () => { const pool = makePool(); - setModelCap({ poolId: pool.id, apiKeyId: KEY_A, model: MODEL_M, capValue: 1, capUnit: "requests" }); + setModelCap({ + poolId: pool.id, + apiKeyId: KEY_A, + model: MODEL_M, + capValue: 1, + capUnit: "requests", + }); // Consume via hot path WITH model so the bucket fills. await consumeViaHotPath(MODEL_M, 2); diff --git a/tests/unit/quota-per-key-model.test.ts b/tests/unit/quota-per-key-model.test.ts index 0ce1970382ed..6158a20d4b72 100644 --- a/tests/unit/quota-per-key-model.test.ts +++ b/tests/unit/quota-per-key-model.test.ts @@ -39,7 +39,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (err: unknown) { @@ -69,7 +69,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ── Helper: create pool with KEY_A allocation ───────────────────────────── @@ -84,7 +84,13 @@ function makePool() { // --------------------------------------------------------------------------- test("per-(key,model) cap — keyA blocked on model M after N requests", async () => { const pool = makePool(); - setModelCap({ poolId: pool.id, apiKeyId: KEY_A, model: MODEL_M, capValue: CAP_N, capUnit: "requests" }); + setModelCap({ + poolId: pool.id, + apiKeyId: KEY_A, + model: MODEL_M, + capValue: CAP_N, + capUnit: "requests", + }); // Simulate CAP_N prior consumptions for (let i = 0; i < CAP_N; i++) { @@ -108,7 +114,7 @@ test("per-(key,model) cap — keyA blocked on model M after N requests", async ( assert.equal(result.kind, "block", "must block when model cap is reached"); assert.ok( "reason" in result && result.reason.includes("model-cap"), - `reason must mention model-cap; got: ${"reason" in result ? result.reason : "(no reason)"}`, + `reason must mention model-cap; got: ${"reason" in result ? result.reason : "(no reason)"}` ); assert.equal("httpStatus" in result && result.httpStatus, 429, "must return 429"); }); @@ -118,7 +124,13 @@ test("per-(key,model) cap — keyA blocked on model M after N requests", async ( // --------------------------------------------------------------------------- test("per-(key,model) cap — keyA blocked on M, still allowed on M2 same pool", async () => { const pool = makePool(); - setModelCap({ poolId: pool.id, apiKeyId: KEY_A, model: MODEL_M, capValue: 1, capUnit: "requests" }); + setModelCap({ + poolId: pool.id, + apiKeyId: KEY_A, + model: MODEL_M, + capValue: 1, + capUnit: "requests", + }); // Consume the single request cap on model M await recordConsumption({ @@ -140,7 +152,7 @@ test("per-(key,model) cap — keyA blocked on M, still allowed on M2 same pool", assert.equal(resultM.kind, "block", "model M should be blocked"); assert.ok( "reason" in resultM && resultM.reason.includes("model-cap"), - `reason must mention model-cap; got: ${"reason" in resultM ? resultM.reason : "(no reason)"}`, + `reason must mention model-cap; got: ${"reason" in resultM ? resultM.reason : "(no reason)"}` ); // Model M2 (no cap configured) must still be allowed @@ -182,7 +194,8 @@ test("per-(key,model) cap — EPSILON cap value → ignored, request allowed", a // Insert a placeholder cap directly (Number.EPSILON > 0 passes DB CHECK constraint // but enforce.ts skips it: !(capValue > Number.EPSILON) → true for EPSILON). - core.getDbInstance() + core + .getDbInstance() .prepare( `INSERT INTO quota_allocation_model_caps (pool_id, api_key_id, model, cap_value, cap_unit) VALUES (?, ?, ?, ?, ?)` diff --git a/tests/unit/quota-phase2.test.ts b/tests/unit/quota-phase2.test.ts index 64e0600e2ac7..0da572c9f463 100644 --- a/tests/unit/quota-phase2.test.ts +++ b/tests/unit/quota-phase2.test.ts @@ -8,21 +8,18 @@ const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omni-quota-phase2-" process.env.DATA_DIR = TEST_DATA_DIR; const coreDb = await import("../../src/lib/db/core.ts"); -const { parseProviderQuotaHeaders, applyQuotaHeadersToState } = await import( - "../../src/lib/quota/quotaAdapters" -); +const { parseProviderQuotaHeaders, applyQuotaHeadersToState } = + await import("../../src/lib/quota/quotaAdapters"); const { getQuotaAnalyticsSummary } = await import("../../src/lib/quota/quotaAnalytics"); -const { getActiveQuotaResetItems, resetExpiredQuotaWindows } = await import( - "../../src/lib/quota/quotaResetTimers" -); -const { recordProviderQuotaUsage, getProviderQuota } = await import( - "../../src/lib/quota/providerQuotaState" -); +const { getActiveQuotaResetItems, resetExpiredQuotaWindows } = + await import("../../src/lib/quota/quotaResetTimers"); +const { recordProviderQuotaUsage, getProviderQuota } = + await import("../../src/lib/quota/providerQuotaState"); const { getDbInstance } = coreDb; async function resetStorage() { coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -32,7 +29,7 @@ test.beforeEach(async () => { test.after(() => { coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("parseProviderQuotaHeaders: parses OpenAI rate limit headers", () => { @@ -96,15 +93,7 @@ test("quotaResetTimers: tracks active reset items and purges expired windows", ( `INSERT OR REPLACE INTO provider_quota_state (connection_id, model, tokens_used, token_limit, window_start, window_reset, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)` - ).run( - connId, - model, - 5000, - 5000, - now - 10_000, - now - 1_000, - new Date().toISOString() - ); + ).run(connId, model, 5000, 5000, now - 10_000, now - 1_000, new Date().toISOString()); const expiredCount = resetExpiredQuotaWindows(); assert.ok(expiredCount >= 1); diff --git a/tests/unit/quota-plan-resolver.test.ts b/tests/unit/quota-plan-resolver.test.ts index 72401f2022b3..fad765f066a5 100644 --- a/tests/unit/quota-plan-resolver.test.ts +++ b/tests/unit/quota-plan-resolver.test.ts @@ -26,7 +26,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (err: unknown) { @@ -47,7 +47,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ─── Scenario 1 ───────────────────────────────────────────────────────────── @@ -55,9 +55,12 @@ test("planResolver: DB plan present → returns DB plan (source=manual)", async const { resolvePlan } = await import("../../src/lib/quota/planResolver.ts"); // Seed a DB override - providerPlansDb.upsertPlan("conn-123", "openai", [ - { unit: "tokens", window: "hourly", limit: 10_000 }, - ], "manual"); + providerPlansDb.upsertPlan( + "conn-123", + "openai", + [{ unit: "tokens", window: "hourly", limit: 10_000 }], + "manual" + ); const plan = resolvePlan("conn-123", "openai"); assert.equal(plan.source, "manual"); @@ -95,9 +98,12 @@ test("planResolver: DB plan overrides catalog for same provider", async () => { const { resolvePlan } = await import("../../src/lib/quota/planResolver.ts"); // codex is in catalog, but we add a DB override - providerPlansDb.upsertPlan("conn-codex-override", "codex", [ - { unit: "requests", window: "daily", limit: 999 }, - ], "manual"); + providerPlansDb.upsertPlan( + "conn-codex-override", + "codex", + [{ unit: "requests", window: "daily", limit: 999 }], + "manual" + ); const plan = resolvePlan("conn-codex-override", "codex"); assert.equal(plan.source, "manual"); diff --git a/tests/unit/quota-pool-connections.test.ts b/tests/unit/quota-pool-connections.test.ts index 7f063f112fc8..c51600186573 100644 --- a/tests/unit/quota-pool-connections.test.ts +++ b/tests/unit/quota-pool-connections.test.ts @@ -31,7 +31,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (err: any) { @@ -51,7 +51,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ── D1.1: Migration file ──────────────────────────────────────────────────── diff --git a/tests/unit/quota-pool-delete-prune.test.ts b/tests/unit/quota-pool-delete-prune.test.ts index ed08223b0dd9..8156206f77b4 100644 --- a/tests/unit/quota-pool-delete-prune.test.ts +++ b/tests/unit/quota-pool-delete-prune.test.ts @@ -35,7 +35,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (err: any) { @@ -55,7 +55,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ── Helper: get allowed_quotas for a key by id from DB ─────────────────────── diff --git a/tests/unit/quota-pool-single-provider.test.ts b/tests/unit/quota-pool-single-provider.test.ts index a2ce296220c9..d654558d08c0 100644 --- a/tests/unit/quota-pool-single-provider.test.ts +++ b/tests/unit/quota-pool-single-provider.test.ts @@ -29,7 +29,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (err: any) { @@ -49,7 +49,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ── T3.1: createPool with mixed providers → throws ────────────────────────── diff --git a/tests/unit/quota-pool-update-full.test.ts b/tests/unit/quota-pool-update-full.test.ts index 1497b144b6d9..908ee3b0eaaf 100644 --- a/tests/unit/quota-pool-update-full.test.ts +++ b/tests/unit/quota-pool-update-full.test.ts @@ -24,20 +24,18 @@ const poolsDb = await import("../../src/lib/db/quotaPools.ts"); const providersDb = await import("../../src/lib/db/providers.ts"); const combosDb = await import("../../src/lib/db/combos.ts"); const { createGroup } = await import("../../src/lib/db/quotaGroups.ts"); -const { syncQuotaCombos, removeQuotaCombosForPool } = await import( - "../../src/lib/quota/quotaCombos.ts" -); +const { syncQuotaCombos, removeQuotaCombosForPool } = + await import("../../src/lib/quota/quotaCombos.ts"); const { PoolUpdateSchema } = await import("../../src/shared/schemas/quota.ts"); -const { isQuotaModelName, parseQuotaModelName, quotaGroupSlug } = await import( - "../../src/lib/quota/quotaModelNaming.ts" -); +const { isQuotaModelName, parseQuotaModelName, quotaGroupSlug } = + await import("../../src/lib/quota/quotaModelNaming.ts"); async function resetStorage() { core.resetDbInstance(); for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: unknown) { @@ -58,7 +56,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ── Helper: list only quota-named combos ─────────────────────────────────── diff --git a/tests/unit/quota-redis-store.test.ts b/tests/unit/quota-redis-store.test.ts index 5a5ae936dc88..551074c6a913 100644 --- a/tests/unit/quota-redis-store.test.ts +++ b/tests/unit/quota-redis-store.test.ts @@ -34,7 +34,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (err: unknown) { @@ -56,7 +56,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/quota-scheduler.test.ts b/tests/unit/quota-scheduler.test.ts index d105945a0a8f..91cd5756803f 100644 --- a/tests/unit/quota-scheduler.test.ts +++ b/tests/unit/quota-scheduler.test.ts @@ -14,7 +14,7 @@ const { clearProviderQuota, getProviderQuota, recordProviderQuotaUsage } = async function resetStorage() { coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -24,7 +24,7 @@ test.beforeEach(async () => { test.after(() => { coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); const CONN = "test-conn-quota"; diff --git a/tests/unit/quota-sharing-fixes.test.ts b/tests/unit/quota-sharing-fixes.test.ts index 83743fea8369..73278130d958 100644 --- a/tests/unit/quota-sharing-fixes.test.ts +++ b/tests/unit/quota-sharing-fixes.test.ts @@ -26,7 +26,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (err: unknown) { @@ -48,7 +48,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -146,9 +146,8 @@ test("upsertAllocations: preserves non-zero weights", async () => { // ─── Fix 4: storeRateLimitHeaders + Anthropic saturation ───────────────────── test("storeRateLimitHeaders: stores headers and getSaturation reads them", async () => { - const { storeRateLimitHeaders, _clearSaturationCache } = await import( - "../../src/lib/quota/saturationSignals.ts" - ); + const { storeRateLimitHeaders, _clearSaturationCache } = + await import("../../src/lib/quota/saturationSignals.ts"); _clearSaturationCache(); @@ -170,9 +169,8 @@ test("storeRateLimitHeaders: stores headers and getSaturation reads them", async }); test("storeRateLimitHeaders: ignores non-Anthropic headers gracefully", async () => { - const { storeRateLimitHeaders, _clearSaturationCache, getSaturation } = await import( - "../../src/lib/quota/saturationSignals.ts" - ); + const { storeRateLimitHeaders, _clearSaturationCache, getSaturation } = + await import("../../src/lib/quota/saturationSignals.ts"); _clearSaturationCache(); @@ -199,9 +197,7 @@ test("poolUsageWithDimensions: returns non-null burn rate for token dimensions", const pool = poolsDb.createPool({ connectionId: "conn-burn-rate", name: "Burn Rate Pool", - allocations: [ - { apiKeyId: "key-br-1", weight: 100, policy: "hard" }, - ], + allocations: [{ apiKeyId: "key-br-1", weight: 100, policy: "hard" }], }); const dim = { poolId: pool.id, unit: "tokens" as const, window: "hourly" as const }; @@ -229,9 +225,7 @@ test("poolUsageWithDimensions: no burn rate when consumedTotal is 0", async () = const pool = poolsDb.createPool({ connectionId: "conn-no-burn", name: "No Burn Pool", - allocations: [ - { apiKeyId: "key-nb-1", weight: 100, policy: "hard" }, - ], + allocations: [{ apiKeyId: "key-nb-1", weight: 100, policy: "hard" }], }); const snapshot = await store.poolUsageWithDimensions(pool.id, [ @@ -248,7 +242,11 @@ test("QuotaStore interface: poolUsageWithDimensions is on the interface", async // We verify at runtime that both implementations have it. const { SqliteQuotaStore } = await import("../../src/lib/quota/sqliteQuotaStore.ts"); const sqlite = new SqliteQuotaStore(); - assert.equal(typeof sqlite.poolUsageWithDimensions, "function", "SqliteQuotaStore must have poolUsageWithDimensions"); + assert.equal( + typeof sqlite.poolUsageWithDimensions, + "function", + "SqliteQuotaStore must have poolUsageWithDimensions" + ); // Redis store (just check the prototype) const { RedisQuotaStore } = await import("../../src/lib/quota/redisQuotaStore.ts"); diff --git a/tests/unit/quota-sqlite-store.test.ts b/tests/unit/quota-sqlite-store.test.ts index 3b4ecda92628..d1ea93989129 100644 --- a/tests/unit/quota-sqlite-store.test.ts +++ b/tests/unit/quota-sqlite-store.test.ts @@ -28,7 +28,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (err: unknown) { @@ -50,7 +50,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/quota-store-factory.test.ts b/tests/unit/quota-store-factory.test.ts index 111960c10038..a82efdcff0a4 100644 --- a/tests/unit/quota-store-factory.test.ts +++ b/tests/unit/quota-store-factory.test.ts @@ -25,7 +25,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (err: unknown) { @@ -56,7 +56,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } // Restore env if (origDriver !== undefined) process.env.QUOTA_STORE_DRIVER = origDriver; @@ -68,7 +68,8 @@ test.after(async () => { // ─── Default driver ────────────────────────────────────────────────────────── test("storeFactory: default driver is sqlite", async () => { - const { getQuotaStore, resetQuotaStoreSingleton } = await import("../../src/lib/quota/storeFactory.ts"); + const { getQuotaStore, resetQuotaStoreSingleton } = + await import("../../src/lib/quota/storeFactory.ts"); resetQuotaStoreSingleton(); const store = await getQuotaStore(); @@ -83,7 +84,8 @@ test("storeFactory: default driver is sqlite", async () => { // ─── Singleton behaviour ───────────────────────────────────────────────────── test("storeFactory: multiple calls return same singleton", async () => { - const { getQuotaStore, resetQuotaStoreSingleton } = await import("../../src/lib/quota/storeFactory.ts"); + const { getQuotaStore, resetQuotaStoreSingleton } = + await import("../../src/lib/quota/storeFactory.ts"); resetQuotaStoreSingleton(); const store1 = await getQuotaStore(); @@ -92,7 +94,8 @@ test("storeFactory: multiple calls return same singleton", async () => { }); test("storeFactory: resetQuotaStoreSingleton() creates new instance on next call", async () => { - const { getQuotaStore, resetQuotaStoreSingleton } = await import("../../src/lib/quota/storeFactory.ts"); + const { getQuotaStore, resetQuotaStoreSingleton } = + await import("../../src/lib/quota/storeFactory.ts"); resetQuotaStoreSingleton(); const store1 = await getQuotaStore(); @@ -107,7 +110,8 @@ test("storeFactory: resetQuotaStoreSingleton() creates new instance on next call // ─── Redis driver + no URL → fallback sqlite ───────────────────────────────── test("storeFactory: QUOTA_STORE_DRIVER=redis without URL → fallback to sqlite", async () => { - const { getQuotaStore, resetQuotaStoreSingleton } = await import("../../src/lib/quota/storeFactory.ts"); + const { getQuotaStore, resetQuotaStoreSingleton } = + await import("../../src/lib/quota/storeFactory.ts"); resetQuotaStoreSingleton(); process.env.QUOTA_STORE_DRIVER = "redis"; @@ -122,7 +126,8 @@ test("storeFactory: QUOTA_STORE_DRIVER=redis without URL → fallback to sqlite" // ─── Unknown driver → fallback sqlite ──────────────────────────────────────── test("storeFactory: unknown driver value → falls back to sqlite silently", async () => { - const { getQuotaStore, resetQuotaStoreSingleton } = await import("../../src/lib/quota/storeFactory.ts"); + const { getQuotaStore, resetQuotaStoreSingleton } = + await import("../../src/lib/quota/storeFactory.ts"); resetQuotaStoreSingleton(); (process.env as Record).QUOTA_STORE_DRIVER = "memcached"; @@ -135,7 +140,8 @@ test("storeFactory: unknown driver value → falls back to sqlite silently", asy // ─── Redis driver + invalid URL (ioredis not installed) → fallback ──────────── test("storeFactory: QUOTA_STORE_DRIVER=redis with invalid URL → fallback or throws gracefully", async () => { - const { getQuotaStore, resetQuotaStoreSingleton } = await import("../../src/lib/quota/storeFactory.ts"); + const { getQuotaStore, resetQuotaStoreSingleton } = + await import("../../src/lib/quota/storeFactory.ts"); resetQuotaStoreSingleton(); process.env.QUOTA_STORE_DRIVER = "redis"; diff --git a/tests/unit/quota-store-pool-total.test.ts b/tests/unit/quota-store-pool-total.test.ts index a6d44d461180..206cd20fdb54 100644 --- a/tests/unit/quota-store-pool-total.test.ts +++ b/tests/unit/quota-store-pool-total.test.ts @@ -23,7 +23,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (err: unknown) { @@ -45,7 +45,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/qwen-settings-route.test.ts b/tests/unit/qwen-settings-route.test.ts index 0b1f2ab6195d..60f998e92d0f 100644 --- a/tests/unit/qwen-settings-route.test.ts +++ b/tests/unit/qwen-settings-route.test.ts @@ -38,13 +38,13 @@ const request = async (method: string, body?: unknown): Promise => }); test.beforeEach(async () => { - await fs.rm(TEST_HOME, { recursive: true, force: true }); + await fs.rm(TEST_HOME, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); await fs.mkdir(path.dirname(SETTINGS_PATH), { recursive: true }); }); test.after(async () => { os.homedir = originalHome; - await fs.rm(TEST_HOME, { recursive: true, force: true }); + await fs.rm(TEST_HOME, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (originalJwtSecret === undefined) delete process.env.JWT_SECRET; else process.env.JWT_SECRET = originalJwtSecret; if (originalWriteFlag === undefined) delete process.env.CLI_ALLOW_CONFIG_WRITES; diff --git a/tests/unit/qwen-web-runtime-block.test.ts b/tests/unit/qwen-web-runtime-block.test.ts index 71630ede727d..8cb2e3cc4525 100644 --- a/tests/unit/qwen-web-runtime-block.test.ts +++ b/tests/unit/qwen-web-runtime-block.test.ts @@ -32,7 +32,7 @@ const RETIRED_PROVIDER_VARIANTS = [ async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); core.getDbInstance(); } @@ -50,7 +50,7 @@ test.afterEach(async () => { test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function isRetiredError(error: unknown): boolean { diff --git a/tests/unit/radar-api-routes.test.ts b/tests/unit/radar-api-routes.test.ts index ae95272befaf..92200d9a776e 100644 --- a/tests/unit/radar-api-routes.test.ts +++ b/tests/unit/radar-api-routes.test.ts @@ -85,7 +85,7 @@ function resetStorage() { core.resetDbInstance(); try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } catch { // ignore @@ -563,7 +563,7 @@ test.after(() => { delete process.env.JWT_SECRET; delete process.env.INITIAL_PASSWORD; try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // ignore } diff --git a/tests/unit/radar-db.test.ts b/tests/unit/radar-db.test.ts index 1aaaaa645508..d28608a52606 100644 --- a/tests/unit/radar-db.test.ts +++ b/tests/unit/radar-db.test.ts @@ -29,7 +29,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: unknown) { @@ -51,7 +51,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); delete process.env.STORAGE_ENCRYPTION_KEY; }); diff --git a/tests/unit/radar-export.test.mjs b/tests/unit/radar-export.test.mjs index 55e1d424aa19..f8f74f179947 100644 --- a/tests/unit/radar-export.test.mjs +++ b/tests/unit/radar-export.test.mjs @@ -35,7 +35,7 @@ function runExport(extraEnv = {}) { }, }); const parsed = JSON.parse(fs.readFileSync(outPath, "utf8")); - fs.rmSync(outDir, { recursive: true, force: true }); + fs.rmSync(outDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); return parsed; } @@ -64,7 +64,10 @@ test("radar export provenance never fabricates unknown fields", () => { assert.equal(p.sourceRef, null); assert.equal(p.runUrl, null); // sourceCommit: SHA de 40 hex (via git no checkout) ou null se indisponível. - assert.ok(p.sourceCommit === null || /^[0-9a-f]{40}$/.test(p.sourceCommit), "sourceCommit sha|null"); + assert.ok( + p.sourceCommit === null || /^[0-9a-f]{40}$/.test(p.sourceCommit), + "sourceCommit sha|null" + ); }); test("radar export provenance reflects the GitHub Actions environment when present", () => { diff --git a/tests/unit/radar-feed-cache-generated-at.test.ts b/tests/unit/radar-feed-cache-generated-at.test.ts index d364c69cc505..df29fc0e7b26 100644 --- a/tests/unit/radar-feed-cache-generated-at.test.ts +++ b/tests/unit/radar-feed-cache-generated-at.test.ts @@ -209,7 +209,7 @@ test.after(() => { delete process.env.RADAR_ENABLED; delete process.env.INITIAL_PASSWORD; try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // ignore } diff --git a/tests/unit/radar-inertia.test.ts b/tests/unit/radar-inertia.test.ts index b48b7bbb0844..7071bef06223 100644 --- a/tests/unit/radar-inertia.test.ts +++ b/tests/unit/radar-inertia.test.ts @@ -45,19 +45,17 @@ delete process.env.RADAR_ENABLED; const core = await import("../../src/lib/db/core.ts"); const { clearAllFeatureFlagOverrides } = await import("../../src/lib/db/featureFlags.ts"); -const { isFeatureFlagEnabled, resolveAllFeatureFlags } = await import( - "../../src/shared/utils/featureFlags.ts" -); -const { FREE_MODEL_BUDGETS, computeFreeModelTotals } = await import( - "../../open-sse/config/freeModelCatalog.ts" -); +const { isFeatureFlagEnabled, resolveAllFeatureFlags } = + await import("../../src/shared/utils/featureFlags.ts"); +const { FREE_MODEL_BUDGETS, computeFreeModelTotals } = + await import("../../open-sse/config/freeModelCatalog.ts"); const { getRadarCatalog, baselineToMergedEntries } = await import("../../src/lib/radar/index.ts"); function resetState() { core.resetDbInstance(); try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } catch { // ignore @@ -94,7 +92,7 @@ test("Radar inertia — flag off means zero behavioral delta", async (t) => { assert.equal(syncRes.status, 404, "POST /api/radar/sync must 404 when disabled"); const settingsRes = await settingsPost( - mockPostRequest("http://localhost:20128/api/radar/settings", { optIn: true }), + mockPostRequest("http://localhost:20128/api/radar/settings", { optIn: true }) ); assert.equal(settingsRes.status, 404, "POST /api/radar/settings must 404 when disabled"); @@ -106,12 +104,12 @@ test("Radar inertia — flag off means zero behavioral delta", async (t) => { mockPostRequest("http://localhost:20128/api/radar/settings", { optIn: true, supporterKey: "omr_abcdef01234567890abcdef01234567890abcdef", - }), + }) ); assert.equal( settingsWithKeyRes.status, 404, - "POST /api/radar/settings with optIn+supporterKey together must also 404 when disabled", + "POST /api/radar/settings with optIn+supporterKey together must also 404 when disabled" ); }); @@ -121,7 +119,7 @@ test("Radar inertia — flag off means zero behavioral delta", async (t) => { assert.equal( isFeatureFlagEnabled("RADAR_ENABLED"), false, - "RADAR_ENABLED must resolve to disabled by default", + "RADAR_ENABLED must resolve to disabled by default" ); const resolved = resolveAllFeatureFlags().find((f) => f.key === "RADAR_ENABLED"); @@ -129,12 +127,12 @@ test("Radar inertia — flag off means zero behavioral delta", async (t) => { assert.equal( resolved!.effectiveValue, "false", - "RADAR_ENABLED effective value must be 'false' with no override present", + "RADAR_ENABLED effective value must be 'false' with no override present" ); assert.equal( resolved!.source, "default", - "RADAR_ENABLED must resolve from the definition default, not a DB/env override", + "RADAR_ENABLED must resolve from the definition default, not a DB/env override" ); }); @@ -151,12 +149,16 @@ test("Radar inertia — flag off means zero behavioral delta", async (t) => { }, }); - assert.equal(cacheReadCount, 0, "getRadarCatalog() must short-circuit before reading the cache"); + assert.equal( + cacheReadCount, + 0, + "getRadarCatalog() must short-circuit before reading the cache" + ); assert.equal(result.meta, null, "meta must be null — no feed is active"); assert.equal( result.entries.length, FREE_MODEL_BUDGETS.length, - "entry count must match the baseline catalog exactly", + "entry count must match the baseline catalog exactly" ); const baselineKeys = new Set(FREE_MODEL_BUDGETS.map((m) => `${m.provider}:${m.modelId}`)); @@ -164,18 +166,26 @@ test("Radar inertia — flag off means zero behavioral delta", async (t) => { assert.deepEqual( resultKeys, baselineKeys, - "entries must be exactly the baseline provider:modelId set — no additions, no removals", + "entries must be exactly the baseline provider:modelId set — no additions, no removals" ); for (const entry of result.entries) { - assert.equal(entry.origin, "baseline", `entry ${entry.provider}:${entry.modelId} must be origin:baseline`); - assert.equal(entry.disabledBy, undefined, "no entry should carry Radar disabledBy provenance"); + assert.equal( + entry.origin, + "baseline", + `entry ${entry.provider}:${entry.modelId} must be origin:baseline` + ); + assert.equal( + entry.disabledBy, + undefined, + "no entry should carry Radar disabledBy provenance" + ); } // Cross-check against the explicit baseline converter too — same shape. const converted = baselineToMergedEntries(FREE_MODEL_BUDGETS); assert.equal(converted.length, result.entries.length); - }, + } ); await t.test( @@ -227,8 +237,12 @@ test("Radar inertia — flag off means zero behavioral delta", async (t) => { // Re-running getRadarCatalog() (flag off) must not perturb the totals either. getRadarCatalog(); const totalsAfter = computeFreeModelTotals(); - assert.deepEqual(totalsAfter, totals, "computeFreeModelTotals() must be idempotent across a getRadarCatalog() call"); - }, + assert.deepEqual( + totalsAfter, + totals, + "computeFreeModelTotals() must be idempotent across a getRadarCatalog() call" + ); + } ); }); @@ -236,7 +250,7 @@ test.after(() => { core.resetDbInstance(); delete process.env.RADAR_ENABLED; try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // ignore } diff --git a/tests/unit/radar-intel-db.test.ts b/tests/unit/radar-intel-db.test.ts index d5f80496af9f..15fb923dd28b 100644 --- a/tests/unit/radar-intel-db.test.ts +++ b/tests/unit/radar-intel-db.test.ts @@ -13,14 +13,14 @@ const radar = await import("../../src/lib/db/radar.ts"); function resetStorage(): void { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.beforeEach(resetStorage); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("Intel migration provides a byte-preserving single-row cache", () => { diff --git a/tests/unit/radar-intel-routes.test.ts b/tests/unit/radar-intel-routes.test.ts index 023056e11e75..8a96cacc20c8 100644 --- a/tests/unit/radar-intel-routes.test.ts +++ b/tests/unit/radar-intel-routes.test.ts @@ -30,13 +30,13 @@ function request(pathname: string, method: "GET" | "POST", headers: Record { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); delete process.env.RADAR_ENABLED; }); diff --git a/tests/unit/radar-local-state-db.test.ts b/tests/unit/radar-local-state-db.test.ts index c6e2074d78f3..a59677513d7c 100644 --- a/tests/unit/radar-local-state-db.test.ts +++ b/tests/unit/radar-local-state-db.test.ts @@ -27,7 +27,7 @@ const { getRadarCatalog } = await import("../../src/lib/radar/index.ts"); async function resetStorage(): Promise { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -35,7 +35,7 @@ test.beforeEach(resetStorage); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); delete process.env.DATA_DIR; delete process.env.RADAR_ENABLED; }); diff --git a/tests/unit/radar-local-state-route.test.ts b/tests/unit/radar-local-state-route.test.ts index 08970609bbd6..a9de6f107f68 100644 --- a/tests/unit/radar-local-state-route.test.ts +++ b/tests/unit/radar-local-state-route.test.ts @@ -34,7 +34,7 @@ function request(method: string, body?: unknown, headers: Record async function resetStorage(): Promise { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -42,7 +42,7 @@ test.beforeEach(resetStorage); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); delete process.env.DATA_DIR; delete process.env.RADAR_ENABLED; delete process.env.JWT_SECRET; diff --git a/tests/unit/radar-offers-db.test.ts b/tests/unit/radar-offers-db.test.ts index fa93225ffa2e..b4ee10eb4ace 100644 --- a/tests/unit/radar-offers-db.test.ts +++ b/tests/unit/radar-offers-db.test.ts @@ -13,7 +13,7 @@ const radar = await import("../../src/lib/db/radar.ts"); function resetStorage(): void { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -21,7 +21,7 @@ test.beforeEach(resetStorage); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); delete process.env.STORAGE_ENCRYPTION_KEY; }); diff --git a/tests/unit/radar-offers-routes.test.ts b/tests/unit/radar-offers-routes.test.ts index 1ff65c20fff4..fcfcd3dc0091 100644 --- a/tests/unit/radar-offers-routes.test.ts +++ b/tests/unit/radar-offers-routes.test.ts @@ -25,7 +25,7 @@ async function authHeaders(): Promise> { function resetStorage(): void { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -44,7 +44,7 @@ function request( test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); delete process.env.RADAR_ENABLED; delete process.env.STORAGE_ENCRYPTION_KEY; }); diff --git a/tests/unit/radar-referrals-route.test.ts b/tests/unit/radar-referrals-route.test.ts index a8c9b7157761..6f110906477a 100644 --- a/tests/unit/radar-referrals-route.test.ts +++ b/tests/unit/radar-referrals-route.test.ts @@ -59,7 +59,7 @@ async function authHeaders(): Promise> { function mockGetRequest( url = "http://localhost:20128/api/radar/referrals", - headers: Record = {}, + headers: Record = {} ): Request { return new Request(url, { method: "GET", headers }); } @@ -68,7 +68,7 @@ function resetStorage() { core.resetDbInstance(); try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } catch { // ignore @@ -217,7 +217,7 @@ test("GET /api/radar/referrals: stale cached referrals feed still served (sync-o test("GET /api/radar/referrals: never proxies the private feed server (route source has no upstream fetch)", async () => { const routeSrc = fs.readFileSync( path.resolve(process.cwd(), "src/app/api/radar/referrals/route.ts"), - "utf-8", + "utf-8" ); assert.ok(!/fetch\(/.test(routeSrc), "referrals route must never call fetch() upstream"); }); diff --git a/tests/unit/radar-supporter-gamification.test.ts b/tests/unit/radar-supporter-gamification.test.ts index a3b1d4b10019..a926cd064bf8 100644 --- a/tests/unit/radar-supporter-gamification.test.ts +++ b/tests/unit/radar-supporter-gamification.test.ts @@ -13,7 +13,7 @@ const { emitGamificationEvent } = await import("../../src/lib/gamification/event test.after(() => { resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("Radar supporter has a dedicated badge and zero-XP idempotent action", async () => { diff --git a/tests/unit/rate-limit-execution-timeout-message-4165.test.ts b/tests/unit/rate-limit-execution-timeout-message-4165.test.ts index 3cae26ebbd02..894cd7a1be6f 100644 --- a/tests/unit/rate-limit-execution-timeout-message-4165.test.ts +++ b/tests/unit/rate-limit-execution-timeout-message-4165.test.ts @@ -39,7 +39,7 @@ test.afterEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // Drive a real Bottleneck execution expiration with a function that outlives it. diff --git a/tests/unit/rate-limit-local-error-classification.test.ts b/tests/unit/rate-limit-local-error-classification.test.ts index f7c140a38025..ac34cb308d70 100644 --- a/tests/unit/rate-limit-local-error-classification.test.ts +++ b/tests/unit/rate-limit-local-error-classification.test.ts @@ -97,7 +97,7 @@ test.afterEach(() => { providerCooldown.clearCooldownState(); rateLimitSemaphore.resetAll(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); }); @@ -107,7 +107,7 @@ test.after(() => { providerCooldown.clearCooldownState(); rateLimitSemaphore.resetAll(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("execution-timeout classification requires trusted provenance; queue codes classify by string (#9164/#9342)", () => { diff --git a/tests/unit/rate-limit-manager.test.ts b/tests/unit/rate-limit-manager.test.ts index b1d411e62a7e..da6d29089dc2 100644 --- a/tests/unit/rate-limit-manager.test.ts +++ b/tests/unit/rate-limit-manager.test.ts @@ -91,7 +91,7 @@ async function flushBackgroundWork() { async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -108,7 +108,7 @@ test.after(async () => { await rateLimitManager.__resetRateLimitManagerForTests(); await flushBackgroundWork(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("rate limit manager bypasses disabled connections and exposes inactive status", async () => { diff --git a/tests/unit/rate-limit-queue-timeout-lockout.test.ts b/tests/unit/rate-limit-queue-timeout-lockout.test.ts index 0fb874cc7f5f..8ebf77ff2467 100644 --- a/tests/unit/rate-limit-queue-timeout-lockout.test.ts +++ b/tests/unit/rate-limit-queue-timeout-lockout.test.ts @@ -56,7 +56,7 @@ function errorResponseWithConnectionId(status: number, connectionId: string) { test.afterEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("RATE_LIMIT_QUEUE_TIMEOUT lockout behaves correctly depending on connection ID header", async () => { diff --git a/tests/unit/ratelimit-admission-control-6593.test.ts b/tests/unit/ratelimit-admission-control-6593.test.ts index 04f3dd9fe1b3..deeb46174522 100644 --- a/tests/unit/ratelimit-admission-control-6593.test.ts +++ b/tests/unit/ratelimit-admission-control-6593.test.ts @@ -54,7 +54,7 @@ test.afterEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // --- Pure unit tests for the extracted admission check ------------------- diff --git a/tests/unit/reasoning-probe-truncated-response-10281.test.ts b/tests/unit/reasoning-probe-truncated-response-10281.test.ts index b69a452defa9..286c7caea140 100644 --- a/tests/unit/reasoning-probe-truncated-response-10281.test.ts +++ b/tests/unit/reasoning-probe-truncated-response-10281.test.ts @@ -69,7 +69,7 @@ test.before(() => { test.after(() => { clearModelsDevCapabilities(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#10281 isTinyBudgetReasoningProbe detects tiny explicit budgets on reasoning models", () => { diff --git a/tests/unit/reasoning-routing-api.test.ts b/tests/unit/reasoning-routing-api.test.ts index 8a32c62a32f5..ef47ed55f2db 100644 --- a/tests/unit/reasoning-routing-api.test.ts +++ b/tests/unit/reasoning-routing-api.test.ts @@ -37,7 +37,7 @@ type SimulationResponse = { async function resetStorage() { apiKeysDb.resetApiKeyState(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); rulesDb.invalidateReasoningRoutingRuleCache(); } @@ -72,7 +72,7 @@ test.beforeEach(resetStorage); test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("management API CRUD validates and persists reasoning routing rules", async () => { diff --git a/tests/unit/reasoning-routing-decision-guards.test.ts b/tests/unit/reasoning-routing-decision-guards.test.ts index 30766edc9434..46430931a195 100644 --- a/tests/unit/reasoning-routing-decision-guards.test.ts +++ b/tests/unit/reasoning-routing-decision-guards.test.ts @@ -26,7 +26,7 @@ const handler = await import("../../src/sse/handlers/reasoningRouting.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function req() { diff --git a/tests/unit/reasoning-routing.test.ts b/tests/unit/reasoning-routing.test.ts index 1fea6b7b0558..fc303ccc6820 100644 --- a/tests/unit/reasoning-routing.test.ts +++ b/tests/unit/reasoning-routing.test.ts @@ -19,7 +19,7 @@ const schemas = await import("../../src/shared/validation/schemas/reasoningRouti async function resetStorage() { apiKeysDb.resetApiKeyState(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); rulesDb.invalidateReasoningRoutingRuleCache(); } @@ -55,7 +55,7 @@ test.beforeEach(resetStorage); test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("reasoning intent distinguishes missing, discrete effort, toggle, and budget-only signals", () => { diff --git a/tests/unit/reasoning-token-buffer-6274.test.ts b/tests/unit/reasoning-token-buffer-6274.test.ts index 5af15d94e395..533b1742532c 100644 --- a/tests/unit/reasoning-token-buffer-6274.test.ts +++ b/tests/unit/reasoning-token-buffer-6274.test.ts @@ -78,7 +78,7 @@ test.before(() => { test.after(() => { clearModelsDevCapabilities(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#6274 reasoning buffer does not inflate probe-sized max_tokens", () => { diff --git a/tests/unit/reasoning-token-buffer-9507.test.ts b/tests/unit/reasoning-token-buffer-9507.test.ts index bbe686fca778..886fa88de6c4 100644 --- a/tests/unit/reasoning-token-buffer-9507.test.ts +++ b/tests/unit/reasoning-token-buffer-9507.test.ts @@ -22,7 +22,7 @@ const { resolveReasoningBufferedMaxTokens } = test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#9507 reasoning buffer does NOT enlarge a Claude opus-5 client budget upward", () => { diff --git a/tests/unit/refresh-cursor-route.test.ts b/tests/unit/refresh-cursor-route.test.ts index 983de76e4a42..ab4f65fa95be 100644 --- a/tests/unit/refresh-cursor-route.test.ts +++ b/tests/unit/refresh-cursor-route.test.ts @@ -30,7 +30,7 @@ const { POST } = await import("../../src/app/api/providers/[id]/refresh-cursor/r test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function getId(connection: { id?: unknown }): string { @@ -133,7 +133,7 @@ async function withCursorEnv(fn: (env: CursorEnv) => Promise): Promise else delete process.env.USERPROFILE; delete process.env.FAKE_CURSOR_AGENT_LOG; delete process.env.FAKE_CURSOR_AGENT_STATUS_MODE; - fs.rmSync(tmpHome, { recursive: true, force: true }); + fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }, }; diff --git a/tests/unit/reject-management-password-as-apikey.test.ts b/tests/unit/reject-management-password-as-apikey.test.ts index 2374f4b0c2fc..3c532092bbe1 100644 --- a/tests/unit/reject-management-password-as-apikey.test.ts +++ b/tests/unit/reject-management-password-as-apikey.test.ts @@ -31,13 +31,13 @@ async function storeDashboardPassword(plaintext: string) { beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); }); after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); describe("management password as a provider credential", () => { diff --git a/tests/unit/rejected-request-usage.test.ts b/tests/unit/rejected-request-usage.test.ts index 300e77e013cc..1783129e3a26 100644 --- a/tests/unit/rejected-request-usage.test.ts +++ b/tests/unit/rejected-request-usage.test.ts @@ -29,14 +29,14 @@ const { recordRejectedRequestUsage, summarizeComboAttemptedModels, resolveReject test.beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); usageHistory.clearPendingRequests(); }); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("gate-rejected request is attributed to the api key in usage_history", async () => { diff --git a/tests/unit/relay-check-rate-limit-existing-token.test.ts b/tests/unit/relay-check-rate-limit-existing-token.test.ts index 2843c15a2528..f635f414799a 100644 --- a/tests/unit/relay-check-rate-limit-existing-token.test.ts +++ b/tests/unit/relay-check-rate-limit-existing-token.test.ts @@ -14,9 +14,7 @@ import path from "node:path"; // - the legacy re-query path (no token passed) must still work unmodified // - the per-minute cap must still be enforced correctly via the fast-path -const TEST_DATA_DIR = fs.mkdtempSync( - path.join(os.tmpdir(), "omniroute-relay-check-rate-limit-") -); +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-relay-check-rate-limit-")); process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); @@ -28,7 +26,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error) { @@ -50,7 +48,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // Inserts a relay_tokens row directly (bypassing createRelayToken, which uses diff --git a/tests/unit/relay-deploy-5128.test.ts b/tests/unit/relay-deploy-5128.test.ts index 41a30a4f8b9d..a0cdb6975457 100644 --- a/tests/unit/relay-deploy-5128.test.ts +++ b/tests/unit/relay-deploy-5128.test.ts @@ -30,7 +30,7 @@ const proxySchemas = await import("../../src/shared/validation/schemas/proxy.ts" test.after(() => { core.resetDbInstance(); try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { /* best effort */ } @@ -236,7 +236,7 @@ if (!isPrivateHostname("[fd00::1]")) throw new Error("bracketed IPv6 ULA must st ); } finally { try { - fs.rmSync(tempDir, { recursive: true, force: true }); + fs.rmSync(tempDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { /* best effort */ } diff --git a/tests/unit/replace-custom-models-preserve-hidden-5086.test.ts b/tests/unit/replace-custom-models-preserve-hidden-5086.test.ts index 0b950e3f8aa3..6ee7ff114afa 100644 --- a/tests/unit/replace-custom-models-preserve-hidden-5086.test.ts +++ b/tests/unit/replace-custom-models-preserve-hidden-5086.test.ts @@ -35,7 +35,7 @@ before(() => { after(() => { resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); const PROVIDER = "llama-cpp-5086"; diff --git a/tests/unit/repro-10139-claude-thinking-output-cap.test.ts b/tests/unit/repro-10139-claude-thinking-output-cap.test.ts index 26661a518b17..ab588e19dd78 100644 --- a/tests/unit/repro-10139-claude-thinking-output-cap.test.ts +++ b/tests/unit/repro-10139-claude-thinking-output-cap.test.ts @@ -45,7 +45,7 @@ const THINKING_BUDGET = 131072; // effortBudgetMap.high test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#10139: a provider-scoped-only output cap is invisible without a provider argument", () => { diff --git a/tests/unit/repro-6524.test.ts b/tests/unit/repro-6524.test.ts index d99dcec855d3..dd56995bdf54 100644 --- a/tests/unit/repro-6524.test.ts +++ b/tests/unit/repro-6524.test.ts @@ -70,7 +70,7 @@ test.before(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#6524: with only the (wrong) synced catalog data, the buffer no longer inflates (#9507)", () => { diff --git a/tests/unit/repro-6557-noauth-connection-disable-ignored.test.ts b/tests/unit/repro-6557-noauth-connection-disable-ignored.test.ts index e71da092cc08..b680e65ac129 100644 --- a/tests/unit/repro-6557-noauth-connection-disable-ignored.test.ts +++ b/tests/unit/repro-6557-noauth-connection-disable-ignored.test.ts @@ -37,7 +37,7 @@ const virtualFactory = await import("../../open-sse/services/autoCombo/virtualFa async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -47,7 +47,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; diff --git a/tests/unit/repro-6701-claude-detect-fallback.test.ts b/tests/unit/repro-6701-claude-detect-fallback.test.ts index c17b6e170538..9f326ba8cb36 100644 --- a/tests/unit/repro-6701-claude-detect-fallback.test.ts +++ b/tests/unit/repro-6701-claude-detect-fallback.test.ts @@ -55,7 +55,7 @@ describe("#6701 — claude detection should fall back to settings.json when bina }); after(() => { - fs.rmSync(configHome, { recursive: true, force: true }); + fs.rmSync(configHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (prevBin === undefined) delete process.env.CLI_CLAUDE_BIN; else process.env.CLI_CLAUDE_BIN = prevBin; if (prevConfigHome === undefined) delete process.env.CLI_CONFIG_HOME; diff --git a/tests/unit/repro-6912-volcengine-max-completion-tokens.test.ts b/tests/unit/repro-6912-volcengine-max-completion-tokens.test.ts index 601ea92d5852..d1b0f6065c2f 100644 --- a/tests/unit/repro-6912-volcengine-max-completion-tokens.test.ts +++ b/tests/unit/repro-6912-volcengine-max-completion-tokens.test.ts @@ -20,12 +20,10 @@ const core = await import("../../src/lib/db/core.ts"); const { clearCache } = await import("../../src/lib/semanticCache.ts"); const { clearIdempotency } = await import("../../src/lib/idempotencyLayer.ts"); const { clearInflight } = await import("../../open-sse/services/requestDedup.ts"); -const { resetAll: resetAccountSemaphores } = await import( - "../../open-sse/services/accountSemaphore.ts" -); -const { handleChatCore, clearUpstreamProxyConfigCache } = await import( - "../../open-sse/handlers/chatCore.ts" -); +const { resetAll: resetAccountSemaphores } = + await import("../../open-sse/services/accountSemaphore.ts"); +const { handleChatCore, clearUpstreamProxyConfigCache } = + await import("../../open-sse/handlers/chatCore.ts"); const { resetPayloadRulesConfigForTests } = await import("../../open-sse/services/payloadRules.ts"); const originalFetch = globalThis.fetch; @@ -115,7 +113,7 @@ async function resetStorage() { clearIdempotency(); clearInflight(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -127,7 +125,7 @@ test.afterEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#6912: chatCore renames max_completion_tokens to max_tokens for volcengine/DeepSeek-V4-Flash", async () => { @@ -142,7 +140,11 @@ test("#6912: chatCore renames max_completion_tokens to max_tokens for volcengine }, }); - assert.equal(call.body.max_tokens, 30, "expected max_completion_tokens to be normalized to max_tokens for volcengine"); + assert.equal( + call.body.max_tokens, + 30, + "expected max_completion_tokens to be normalized to max_tokens for volcengine" + ); assert.equal(call.body.max_completion_tokens, undefined); }); @@ -159,7 +161,11 @@ test("#6912: chatCore does not clobber an already-present max_tokens", async () }, }); - assert.equal(call.body.max_tokens, 500, "existing max_tokens must win over max_completion_tokens"); + assert.equal( + call.body.max_tokens, + 500, + "existing max_tokens must win over max_completion_tokens" + ); assert.equal(call.body.max_completion_tokens, undefined); }); diff --git a/tests/unit/repro-6952-commentary.test.ts b/tests/unit/repro-6952-commentary.test.ts index 4e622a1065ea..dbd9a018e0a9 100644 --- a/tests/unit/repro-6952-commentary.test.ts +++ b/tests/unit/repro-6952-commentary.test.ts @@ -48,7 +48,7 @@ async function readTransformed(chunks: string[], options: object): Promise { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -236,10 +236,7 @@ test("TRANSLATE mode drops commentary-phase text before translateResponse (#6952 // The real tool call must still be forwarded (arguments are JSON-escaped inside // an `input_json_delta` SSE frame, so match on the unescaped path fragment). - assert.ok( - output.includes("/tmp/real.txt"), - "the real function_call arguments must be forwarded" - ); + assert.ok(output.includes("/tmp/real.txt"), "the real function_call arguments must be forwarded"); assert.ok(output.includes(TOOL_NAME), "the real function_call name must be forwarded"); }); diff --git a/tests/unit/repro-6957.test.ts b/tests/unit/repro-6957.test.ts index 696dfada3df8..a2d19dcd84bf 100644 --- a/tests/unit/repro-6957.test.ts +++ b/tests/unit/repro-6957.test.ts @@ -33,7 +33,7 @@ const { getComboBuilderOptions } = await import("../../src/lib/combos/builderOpt test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // A trimmed slice of the reporter's actual payload (issue #6957 comment attachment diff --git a/tests/unit/repro-6975.test.ts b/tests/unit/repro-6975.test.ts index ffe17dfe66a2..7d9dd6f108f7 100644 --- a/tests/unit/repro-6975.test.ts +++ b/tests/unit/repro-6975.test.ts @@ -13,22 +13,32 @@ const { getComboBuilderOptions } = await import("../../src/lib/combos/builderOpt test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#6975 embeddings-only custom model must appear in the combo builder output", async () => { - await modelsDb.addCustomModel("opencode", "zzz-embed-6975", "Embed Model 6975", "manual", "embeddings", [ + await modelsDb.addCustomModel( + "opencode", + "zzz-embed-6975", + "Embed Model 6975", + "manual", "embeddings", - ]); + ["embeddings"] + ); const payload = await getComboBuilderOptions(); const m = payload.providers.flatMap((p) => p.models).find((m) => m.id === "zzz-embed-6975"); assert.ok(m, "embeddings-only custom model must appear in the combo builder output"); }); test("#6975 rerank-only custom model must appear in the combo builder output", async () => { - await modelsDb.addCustomModel("opencode", "zzz-rerank-6975", "Rerank Model 6975", "manual", "rerank", [ + await modelsDb.addCustomModel( + "opencode", + "zzz-rerank-6975", + "Rerank Model 6975", + "manual", "rerank", - ]); + ["rerank"] + ); const payload = await getComboBuilderOptions(); const m = payload.providers.flatMap((p) => p.models).find((m) => m.id === "zzz-rerank-6975"); assert.ok(m, "rerank-only custom model must appear in the combo builder output"); diff --git a/tests/unit/repro-8065-quota-cache-cross-instance.test.ts b/tests/unit/repro-8065-quota-cache-cross-instance.test.ts index aa1bd7970d3e..b8901c819b42 100644 --- a/tests/unit/repro-8065-quota-cache-cross-instance.test.ts +++ b/tests/unit/repro-8065-quota-cache-cross-instance.test.ts @@ -11,7 +11,7 @@ const core = await import("../../src/lib/db/core.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#8065 a renewed quota written by one module instance is invisible to another module instance's routing read", async () => { @@ -27,7 +27,10 @@ test("#8065 a renewed quota written by one module instance is invisible to anoth // Instance W: simulates providerLimitsSyncScheduler's instrumentation-node.ts chunk. const quotaCacheW = await import("../../src/domain/quotaCache.ts?instance=W"); quotaCacheW.setQuotaCache(connectionId, "codex", { - session: { remainingPercentage: 100, resetAt: new Date(Date.now() + 7 * 86400000).toISOString() }, + session: { + remainingPercentage: 100, + resetAt: new Date(Date.now() + 7 * 86400000).toISOString(), + }, }); assert.equal(quotaCacheW.isQuotaExhaustedForRequest(connectionId, "codex"), false); diff --git a/tests/unit/repro-8429-capability-canonicalization.test.ts b/tests/unit/repro-8429-capability-canonicalization.test.ts index 5e36bd57be8c..f7cb44d7e60a 100644 --- a/tests/unit/repro-8429-capability-canonicalization.test.ts +++ b/tests/unit/repro-8429-capability-canonicalization.test.ts @@ -13,24 +13,39 @@ const modelCapabilities = await import("../../src/lib/modelCapabilities.ts"); function buildCapability(overrides: Record = {}) { return { - tool_call: null, reasoning: null, attachment: null, structured_output: null, - temperature: null, modalities_input: "[]", modalities_output: "[]", - knowledge_cutoff: null, release_date: null, last_updated: null, status: null, - family: null, open_weights: null, limit_context: null, limit_input: null, - limit_output: null, interleaved_field: null, ...overrides, + tool_call: null, + reasoning: null, + attachment: null, + structured_output: null, + temperature: null, + modalities_input: "[]", + modalities_output: "[]", + knowledge_cutoff: null, + release_date: null, + last_updated: null, + status: null, + family: null, + open_weights: null, + limit_context: null, + limit_input: null, + limit_output: null, + interleaved_field: null, + ...overrides, }; } function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } -test.beforeEach(() => { resetStorage(); }); +test.beforeEach(() => { + resetStorage(); +}); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#8429: synced model_capabilities row written under models.dev mapping is unreachable via the canonical 'codex' provider id", () => { diff --git a/tests/unit/repro-8841-context-overflow-opencode.test.ts b/tests/unit/repro-8841-context-overflow-opencode.test.ts index 0dae913167b5..bd6ff9cea74f 100644 --- a/tests/unit/repro-8841-context-overflow-opencode.test.ts +++ b/tests/unit/repro-8841-context-overflow-opencode.test.ts @@ -17,7 +17,7 @@ test.after(() => { core.resetDbInstance(); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); const noopLog = { diff --git a/tests/unit/repro-8847.test.ts b/tests/unit/repro-8847.test.ts index 301263d35e01..36a3e58ca455 100644 --- a/tests/unit/repro-8847.test.ts +++ b/tests/unit/repro-8847.test.ts @@ -66,5 +66,5 @@ test("repro-8847: better-sqlite3 prebuilds are bundled alongside the compiled bi "linux-x64 prebuild must be in the standalone bundle" ); - fs.rmSync(tmp, { recursive: true, force: true }); -}); \ No newline at end of file + fs.rmSync(tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); diff --git a/tests/unit/repro-8956.test.ts b/tests/unit/repro-8956.test.ts index daf76a4332a4..982bcc28f8b9 100644 --- a/tests/unit/repro-8956.test.ts +++ b/tests/unit/repro-8956.test.ts @@ -43,7 +43,7 @@ test("repro-8956: resolveProjectRoot skips synthetic .build/next/package.json (n `PROJECT_ROOT resolved to ${root}, which lacks .git` ); } finally { - fs.rmSync(tmp, { recursive: true, force: true }); + fs.rmSync(tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -60,6 +60,6 @@ test("repro-8956: resolveProjectRoot still finds package.json with a name field" const root = resolveProjectRoot("/fallback", subDir); assert.equal(root, repoRoot); } finally { - fs.rmSync(tmp, { recursive: true, force: true }); + fs.rmSync(tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/repro-8995.test.ts b/tests/unit/repro-8995.test.ts index 74dd1b8f59e4..5cd02076b9f0 100644 --- a/tests/unit/repro-8995.test.ts +++ b/tests/unit/repro-8995.test.ts @@ -15,13 +15,13 @@ const settingsDb = await import("../../src/lib/db/settings.ts"); async function resetStorage() { delete process.env.INITIAL_PASSWORD; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#8995: resolveProxyForConnection surfaces the proxy NAME for an account-level assignment", async () => { @@ -51,4 +51,4 @@ test("#8995: resolveProxyForConnection surfaces the proxy NAME for an account-le "My US Proxy", "resolveProxyForConnection must include the proxy name so the dashboard badge can show it" ); -}); \ No newline at end of file +}); diff --git a/tests/unit/repro-9625.test.ts b/tests/unit/repro-9625.test.ts index f6ff2f68cf6f..1f8aba9b0428 100644 --- a/tests/unit/repro-9625.test.ts +++ b/tests/unit/repro-9625.test.ts @@ -28,7 +28,7 @@ const { getDbInstance, resetDbInstance } = await import("../../src/lib/db/core.t test.after(() => { resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); const DAY_MS = 86_400_000; // milliseconds @@ -89,4 +89,4 @@ test("#9625 unit mismatch: seconds cutoff would NOT match ms timestamps", () => oldRowMs < cutoffMs, "Fix: ms timestamp IS less than ms cutoff, so row is correctly deleted" ); -}); \ No newline at end of file +}); diff --git a/tests/unit/repro-compression-run-telemetry-ms.test.ts b/tests/unit/repro-compression-run-telemetry-ms.test.ts index 143ef0037c03..3b3e3e1b2cc3 100644 --- a/tests/unit/repro-compression-run-telemetry-ms.test.ts +++ b/tests/unit/repro-compression-run-telemetry-ms.test.ts @@ -26,14 +26,13 @@ const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-crt-ms-") process.env.DATA_DIR = TEST_DATA_DIR; const { cleanupCompressionRunTelemetry } = await import("../../src/lib/db/cleanup.ts"); -const { insertCompressionRunTelemetryRow } = await import( - "../../src/lib/db/compressionRunTelemetry.ts" -); +const { insertCompressionRunTelemetryRow } = + await import("../../src/lib/db/compressionRunTelemetry.ts"); const { getDbInstance, resetDbInstance } = await import("../../src/lib/db/core.ts"); test.after(() => { resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); const DAY_MS = 86_400_000; @@ -92,8 +91,8 @@ test("cleanupCompressionRunTelemetry deletes rows older than the retention windo assert.strictEqual(result.deleted, 3, "should delete the 3 rows aged 40 days"); assert.strictEqual(result.errors, 0); - const remaining = db - .prepare("SELECT COUNT(*) as cnt FROM compression_run_telemetry") - .get() as { cnt: number }; + const remaining = db.prepare("SELECT COUNT(*) as cnt FROM compression_run_telemetry").get() as { + cnt: number; + }; assert.strictEqual(remaining.cnt, 2, "should keep the 2 rows aged 5 days"); }); diff --git a/tests/unit/request-log-migration.test.ts b/tests/unit/request-log-migration.test.ts index 00178b9fc105..143f53e2c721 100644 --- a/tests/unit/request-log-migration.test.ts +++ b/tests/unit/request-log-migration.test.ts @@ -43,7 +43,7 @@ function cleanup() { // Retry with a short delay to let the OS release locks. for (let attempt = 0; attempt < 5; attempt++) { try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); return; } catch { /* retry */ @@ -82,7 +82,12 @@ test("keeps legacy files in place when zip creation fails", async () => { // Remove the archive dir created by the first test, then write a file // at that path so mkdirSync throws EEXIST. This simulates a zip // creation failure. The migration should leave legacy files intact. - fs.rmSync(migrations.LOG_ARCHIVES_DIR, { recursive: true, force: true }); + fs.rmSync(migrations.LOG_ARCHIVES_DIR, { + recursive: true, + force: true, + maxRetries: 5, + retryDelay: 100, + }); fs.writeFileSync(migrations.LOG_ARCHIVES_DIR, "not-a-directory"); await assert.rejects(() => migrations.archiveLegacyRequestLogs()); diff --git a/tests/unit/request-logger-endpoints.test.ts b/tests/unit/request-logger-endpoints.test.ts index 7d1b41cf7e7c..66823c927ca4 100644 --- a/tests/unit/request-logger-endpoints.test.ts +++ b/tests/unit/request-logger-endpoints.test.ts @@ -13,7 +13,7 @@ const callLogs = await import("../../src/lib/usage/callLogs.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // Captured stream chunks are prefixed with a per-chunk arrival timestamp diff --git a/tests/unit/require-management-auth-access-token.test.ts b/tests/unit/require-management-auth-access-token.test.ts index 4589bc118412..97f22282dfd2 100644 --- a/tests/unit/require-management-auth-access-token.test.ts +++ b/tests/unit/require-management-auth-access-token.test.ts @@ -31,7 +31,7 @@ test.after(() => { core.resetDbInstance(); } catch {} try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch {} delete process.env.INITIAL_PASSWORD; delete process.env.OMNIROUTE_INTERNAL_SERVICE_TOKEN; diff --git a/tests/unit/rerank-proxy-pinning-7350.test.ts b/tests/unit/rerank-proxy-pinning-7350.test.ts index 8f87a2193281..3852814b75de 100644 --- a/tests/unit/rerank-proxy-pinning-7350.test.ts +++ b/tests/unit/rerank-proxy-pinning-7350.test.ts @@ -43,7 +43,7 @@ function stubFetch(seen: { proxyUrl: string | null | undefined }[], gate?: () => test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#7350 handleRerank routes the upstream call through the connection's pinned proxy", async () => { diff --git a/tests/unit/rerank-voyage-7809.test.ts b/tests/unit/rerank-voyage-7809.test.ts index 5605f1965120..208fad237c70 100644 --- a/tests/unit/rerank-voyage-7809.test.ts +++ b/tests/unit/rerank-voyage-7809.test.ts @@ -18,7 +18,7 @@ const { transformRequestForProvider, transformResponseFromProvider } = test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ─── Registry ────────────────────────────────────────────────────────────── diff --git a/tests/unit/reset-connection-backoff.test.ts b/tests/unit/reset-connection-backoff.test.ts index 0fce46fb03ac..0f04db66915c 100644 --- a/tests/unit/reset-connection-backoff.test.ts +++ b/tests/unit/reset-connection-backoff.test.ts @@ -18,7 +18,7 @@ const providersDb = await import("../../src/lib/db/providers.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); async function createBackedOffConnection() { diff --git a/tests/unit/reset-password-cli-6261-6258.test.ts b/tests/unit/reset-password-cli-6261-6258.test.ts index 1f83d58a9116..1b73ba6140b7 100644 --- a/tests/unit/reset-password-cli-6261-6258.test.ts +++ b/tests/unit/reset-password-cli-6261-6258.test.ts @@ -114,8 +114,8 @@ test("omniroute reset-password subcommand applies the reset over piped stdin (#6 "the stored password must verify against the piped value" ); } finally { - fs.rmSync(dataDir, { recursive: true, force: true }); - fs.rmSync(home, { recursive: true, force: true }); + fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.rmSync(home, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -143,8 +143,8 @@ test("omniroute-reset-password applies the reset over piped two-line stdin (#625 "the stored password must verify against the piped value" ); } finally { - fs.rmSync(dataDir, { recursive: true, force: true }); - fs.rmSync(home, { recursive: true, force: true }); + fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.rmSync(home, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -172,7 +172,7 @@ test("omniroute-reset-password --password-stdin reads the whole stdin as the pas "the stored password must verify against the --password-stdin value" ); } finally { - fs.rmSync(dataDir, { recursive: true, force: true }); - fs.rmSync(home, { recursive: true, force: true }); + fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.rmSync(home, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/resilience-stream-recovery-feature-flags.test.ts b/tests/unit/resilience-stream-recovery-feature-flags.test.ts index 431c3951f3c6..3455021e9d9e 100644 --- a/tests/unit/resilience-stream-recovery-feature-flags.test.ts +++ b/tests/unit/resilience-stream-recovery-feature-flags.test.ts @@ -14,7 +14,7 @@ const { resolveResilienceSettings } = await import("../../src/lib/resilience/set after(() => { core.resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("stream recovery feature flags seed resilience defaults", () => { diff --git a/tests/unit/resolve-proxy-family.test.ts b/tests/unit/resolve-proxy-family.test.ts index 6e12f618ade5..009dc4c6f7a7 100644 --- a/tests/unit/resolve-proxy-family.test.ts +++ b/tests/unit/resolve-proxy-family.test.ts @@ -12,7 +12,12 @@ describe("resolved proxy config → URL family encoding", () => { assert.ok(url!.endsWith("?family=ipv6"), url!); }); it("omits family marker when auto", () => { - const url = proxyConfigToUrl({ type: "http", host: "p.example.com", port: 8080, family: "auto" }); + const url = proxyConfigToUrl({ + type: "http", + host: "p.example.com", + port: 8080, + family: "auto", + }); assert.ok(!url!.includes("family="), url!); }); }); @@ -32,13 +37,13 @@ async function resetStorage() { delete process.env.INITIAL_PASSWORD; core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("account-level registry proxy carries family=ipv6 through resolveProxyForConnection", async () => { diff --git a/tests/unit/responses-case-insensitive-combo-guard.test.ts b/tests/unit/responses-case-insensitive-combo-guard.test.ts index 532e1e1a15f1..bf2c684db29d 100644 --- a/tests/unit/responses-case-insensitive-combo-guard.test.ts +++ b/tests/unit/responses-case-insensitive-combo-guard.test.ts @@ -68,7 +68,7 @@ const sseModelService = await import("../../src/sse/services/model.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("getComboForModel resolves a stored combo by a case-insensitive request name", async () => { diff --git a/tests/unit/responses-commentary-event-frame-6561.test.ts b/tests/unit/responses-commentary-event-frame-6561.test.ts index 99fcfeee7b12..8e30a129ea8f 100644 --- a/tests/unit/responses-commentary-event-frame-6561.test.ts +++ b/tests/unit/responses-commentary-event-frame-6561.test.ts @@ -43,7 +43,7 @@ async function readTransformed(chunks: string[], options: object): Promise { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/responses-commentary-passthrough-6199.test.ts b/tests/unit/responses-commentary-passthrough-6199.test.ts index c1fce80786cb..0b2b38627887 100644 --- a/tests/unit/responses-commentary-passthrough-6199.test.ts +++ b/tests/unit/responses-commentary-passthrough-6199.test.ts @@ -48,7 +48,7 @@ async function readTransformed(chunks: string[], options: object): Promise { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/responses-continuation-store.test.ts b/tests/unit/responses-continuation-store.test.ts index 4d5a5c7cb455..6c75d53c2463 100644 --- a/tests/unit/responses-continuation-store.test.ts +++ b/tests/unit/responses-continuation-store.test.ts @@ -18,7 +18,7 @@ const store = await import("../../src/lib/db/responsesContinuationStore.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function insertCallLog(row: { diff --git a/tests/unit/responses-handler.test.ts b/tests/unit/responses-handler.test.ts index 3b4d4b7cdec0..0791c5b9c639 100644 --- a/tests/unit/responses-handler.test.ts +++ b/tests/unit/responses-handler.test.ts @@ -130,7 +130,7 @@ function buildJsonResponse(status: number, payload: unknown) { async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -185,7 +185,7 @@ test.afterEach(async () => { test.after(async () => { globalThis.fetch = originalFetch; await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("handleResponsesCore converts Responses API input, instructions, tools, metadata, and forces streaming", async () => { @@ -362,11 +362,8 @@ test("handleResponsesCore transforms Command Code executor SSE through Responses choices: [{ index: 0, delta }], })}\n\n`; return new Response( - [ - chunk({ role: "assistant" }), - chunk({ content: "command" }), - chunk({}), - ].join("") + "data: [DONE]\n\n", + [chunk({ role: "assistant" }), chunk({ content: "command" }), chunk({})].join("") + + "data: [DONE]\n\n", { status: 200, headers: { "Content-Type": "text/event-stream" } } ); }, diff --git a/tests/unit/responses-parse-once-4041.test.ts b/tests/unit/responses-parse-once-4041.test.ts index e47124013ca5..27d41046cc59 100644 --- a/tests/unit/responses-parse-once-4041.test.ts +++ b/tests/unit/responses-parse-once-4041.test.ts @@ -6,7 +6,7 @@ import path from "node:path"; const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-responses-parse-once-")); process.env.DATA_DIR = dataDir; -after(() => fs.rmSync(dataDir, { recursive: true, force: true })); +after(() => fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 })); // #4041: AI routes must parse each JSON body at most once and thread the parsed value // through model resolution and handleChat. /v1/responses now parses after raw-body admission; diff --git a/tests/unit/responses-route-early-keepalive-wiring.test.ts b/tests/unit/responses-route-early-keepalive-wiring.test.ts index 5b510d5c34fe..25be2970bba5 100644 --- a/tests/unit/responses-route-early-keepalive-wiring.test.ts +++ b/tests/unit/responses-route-early-keepalive-wiring.test.ts @@ -8,7 +8,7 @@ const routeSource = fs.readFileSync("src/app/api/v1/responses/route.ts", "utf8") const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-responses-route-test-")); process.env.DATA_DIR = dataDir; process.env.REQUIRE_API_KEY = "false"; -after(() => fs.rmSync(dataDir, { recursive: true, force: true })); +after(() => fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 })); test("Responses route wires dual-cadence neutral keepalives", () => { assert.match( diff --git a/tests/unit/responses-transformer.test.ts b/tests/unit/responses-transformer.test.ts index f8b1c5a0b14e..6a29e84e54d8 100644 --- a/tests/unit/responses-transformer.test.ts +++ b/tests/unit/responses-transformer.test.ts @@ -404,7 +404,12 @@ test("createResponsesLogger returns null for invalid base paths and swallows flu logger.logOutput("output"); const sessionDir = readdirSync(join(logsDir, "logs"))[0]; - rmSync(join(logsDir, "logs", sessionDir), { recursive: true, force: true }); + rmSync(join(logsDir, "logs", sessionDir), { + recursive: true, + force: true, + maxRetries: 5, + retryDelay: 100, + }); console.log = (...args) => capturedLogs.push(args.join(" ")); try { diff --git a/tests/unit/review-reviews-v3814-fixes.test.ts b/tests/unit/review-reviews-v3814-fixes.test.ts index 7e6449634981..00ac18ce57c4 100644 --- a/tests/unit/review-reviews-v3814-fixes.test.ts +++ b/tests/unit/review-reviews-v3814-fixes.test.ts @@ -20,13 +20,13 @@ const { REGISTRY } = await import("../../open-sse/config/providerRegistry.ts"); test.beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); }); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ── LEDGER-1: updateProviderNode must preserve custom headers on partial update ── diff --git a/tests/unit/route-edge-coverage.test.ts b/tests/unit/route-edge-coverage.test.ts index 3788227f0889..80ab12de0278 100644 --- a/tests/unit/route-edge-coverage.test.ts +++ b/tests/unit/route-edge-coverage.test.ts @@ -37,7 +37,7 @@ async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -135,7 +135,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("api keys route covers auth, create, masking, pagination fallback and cloud sync", async () => { diff --git a/tests/unit/route-explainability.test.ts b/tests/unit/route-explainability.test.ts index b624cf9447f7..76509055848e 100644 --- a/tests/unit/route-explainability.test.ts +++ b/tests/unit/route-explainability.test.ts @@ -24,7 +24,7 @@ async function resetStorage() { clearAllModelLockouts(); resetAllCircuitBreakers(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -34,7 +34,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("route explainability builds a direct-route explanation from call logs", async () => { diff --git a/tests/unit/router-eval-check.test.ts b/tests/unit/router-eval-check.test.ts index 51ae202d03a2..025c8761cb95 100644 --- a/tests/unit/router-eval-check.test.ts +++ b/tests/unit/router-eval-check.test.ts @@ -104,7 +104,7 @@ test("router eval check writes artifacts and passes non-regressing corpora", () const artifact = JSON.parse(readFileSync(json, "utf8")) as Record; assert.equal(artifact.kind, "router-eval-comparison"); } finally { - rmSync(dir, { recursive: true, force: true }); + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -201,7 +201,7 @@ test("router eval check can include patch compare as a retained gate", () => { assert.equal(manifest.outputs?.patchJson, "patch-comparison.json"); assert.equal(manifest.thresholds?.patch?.maxLatencyIncrease, 0.05); } finally { - rmSync(dir, { recursive: true, force: true }); + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -288,7 +288,7 @@ test("router eval check fails when patch gate regresses beyond thresholds", () = }; assert.equal(manifest.result?.status, 1); } finally { - rmSync(dir, { recursive: true, force: true }); + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -378,7 +378,7 @@ test("router eval check rejects unpaired patch inputs", () => { assert.equal(candidateOnly.status, 2); assert.match(candidateOnly.stderr ?? "", /baseline-patch and --candidate-patch/); } finally { - rmSync(dir, { recursive: true, force: true }); + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -479,6 +479,6 @@ test("router eval check can retain artifacts for trend summaries", () => { assert.ok((trendResult.stdout ?? "").includes(runId)); assert.ok((trendResult.stdout ?? "").includes("| jsonl | all |")); } finally { - rmSync(dir, { recursive: true, force: true }); + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/router-eval-cli.test.ts b/tests/unit/router-eval-cli.test.ts index 8e44a7fe59da..9f2902d4a98e 100644 --- a/tests/unit/router-eval-cli.test.ts +++ b/tests/unit/router-eval-cli.test.ts @@ -25,7 +25,7 @@ const scriptPath = "scripts/router-eval/index.ts"; // DATA_DIR — the exact resolution the guard message prescribes — instead of loosening // the assertions. const cliDataDir = mkdtempSync(join(tmpdir(), "router-eval-cli-datadir-")); -after(() => rmSync(cliDataDir, { recursive: true, force: true })); +after(() => rmSync(cliDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 })); function runCli(args: string[]) { return spawnSync(process.execPath, ["--import", "tsx", scriptPath, ...args], { @@ -68,7 +68,7 @@ test("router-eval CLI prints a markdown report for JSONL input", () => { assert.ok((result.stdout ?? "").includes("Frontier")); assert.ok((result.stdout ?? "").includes("AIQ")); } finally { - rmSync(dir, { recursive: true, force: true }); + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -123,7 +123,7 @@ test("router-eval CLI exits non-zero when regression threshold is exceeded", () assert.ok((result.stdout ?? "").includes("Router Eval Comparison")); assert.ok((result.stdout ?? "").includes("Regressions")); } finally { - rmSync(dir, { recursive: true, force: true }); + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -156,7 +156,7 @@ test("router-eval CLI writes machine-readable JSON artifacts", () => { path: inputPath, }); } finally { - rmSync(dir, { recursive: true, force: true }); + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -242,7 +242,7 @@ test("router-eval CLI reads usage_history DB source", () => { "string" ); } finally { - rmSync(dir, { recursive: true, force: true }); + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -285,6 +285,6 @@ test("router-eval CLI defaults --db to call_logs when available", () => { assert.ok((result.stdout ?? "").includes("Router Eval Report")); assert.ok((result.stdout ?? "").includes("priority")); } finally { - rmSync(dir, { recursive: true, force: true }); + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/router-eval-compare.test.ts b/tests/unit/router-eval-compare.test.ts index 5eef0c2edb32..a417e3a86395 100644 --- a/tests/unit/router-eval-compare.test.ts +++ b/tests/unit/router-eval-compare.test.ts @@ -78,6 +78,6 @@ test("router eval compare retains named comparison artifacts", () => { assert.equal(comparison.baselineName, "policy-a"); assert.equal(comparison.candidateName, "policy-b"); } finally { - rmSync(dir, { recursive: true, force: true }); + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/router-eval-e2e-chain.test.ts b/tests/unit/router-eval-e2e-chain.test.ts index aea92f6df0cd..95b5acf92987 100644 --- a/tests/unit/router-eval-e2e-chain.test.ts +++ b/tests/unit/router-eval-e2e-chain.test.ts @@ -130,6 +130,6 @@ test("router eval retained chain runs search patches through the check wrapper g assert.equal(manifest.result?.status, 0); assert.equal(manifest.outputs?.patchJson, "patch-comparison.json"); } finally { - rmSync(dir, { recursive: true, force: true }); + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/router-eval-patch-compare.test.ts b/tests/unit/router-eval-patch-compare.test.ts index 87db7d65ac2f..382e35af6c29 100644 --- a/tests/unit/router-eval-patch-compare.test.ts +++ b/tests/unit/router-eval-patch-compare.test.ts @@ -110,7 +110,7 @@ test("router config patch compare reports recommendation and metric deltas", () ) ); } finally { - rmSync(dir, { recursive: true, force: true }); + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -178,7 +178,7 @@ test("router config patch compare only fails threshold regressions when requeste assert.equal(failing.status, 1); assert.ok((failing.stdout ?? "").includes("Passed: no")); } finally { - rmSync(dir, { recursive: true, force: true }); + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -223,7 +223,7 @@ test("router config patch compare reports unchanged recommendations without fail assert.equal(comparison.result?.passed, true); assert.equal(comparison.result?.status, 0); } finally { - rmSync(dir, { recursive: true, force: true }); + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -256,7 +256,7 @@ test("router config patch compare rejects invalid patch inputs", () => { assert.match(result.stderr ?? "", /invalid patch kind/); assert.match(result.stderr ?? "", /router-config-suggestion/); } finally { - rmSync(dir, { recursive: true, force: true }); + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -307,6 +307,6 @@ test("router config patch compare rejects malformed JSON and invalid evidence", assert.equal(invalidEvidenceResult.status, 2); assert.match(invalidEvidenceResult.stderr ?? "", /invalid numeric evidence field aiq/); } finally { - rmSync(dir, { recursive: true, force: true }); + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/router-eval-search.test.ts b/tests/unit/router-eval-search.test.ts index 7e6b40507e02..87641b11a200 100644 --- a/tests/unit/router-eval-search.test.ts +++ b/tests/unit/router-eval-search.test.ts @@ -135,7 +135,7 @@ test("router eval search ranks candidates and writes retained summary artifacts" ) ); } finally { - rmSync(dir, { recursive: true, force: true }); + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -197,7 +197,7 @@ test("router eval search objective modes can choose different candidates", () => assert.equal(qualitySuggestion.objective, "quality"); assert.equal(qualitySuggestion.recommendedConfigId, "cheap"); } finally { - rmSync(dir, { recursive: true, force: true }); + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -253,6 +253,6 @@ test("router eval search cost objective can select a non-AIQ-top config inside a assert.equal(suggestion.recommendedConfigId, "cost-top"); assert.equal(patch.operations?.[0]?.value, "cost-top"); } finally { - rmSync(dir, { recursive: true, force: true }); + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/router-eval-trends.test.ts b/tests/unit/router-eval-trends.test.ts index 989e594f1357..7ca4a9bab357 100644 --- a/tests/unit/router-eval-trends.test.ts +++ b/tests/unit/router-eval-trends.test.ts @@ -73,7 +73,7 @@ test("router eval trends reads retained and flat artifacts with limit", () => { assert.ok((result.stdout ?? "").includes("run-b")); assert.ok((result.stdout ?? "").includes("flat")); } finally { - rmSync(dir, { recursive: true, force: true }); + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -107,7 +107,7 @@ test("router eval trends can print dashboard summaries", () => { assert.ok((result.stdout ?? "").includes("AIQ: 90.000 (+10.000)")); assert.ok((result.stdout ?? "").includes("Rolling Averages")); } finally { - rmSync(dir, { recursive: true, force: true }); + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -123,6 +123,6 @@ test("router eval trends exits clearly for empty artifact dirs", () => { assert.equal(result.status, 2); assert.ok((result.stderr ?? "").includes("No router-eval artifacts found")); } finally { - rmSync(dir, { recursive: true, force: true }); + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/rule12-error-sanitization-sweep.test.ts b/tests/unit/rule12-error-sanitization-sweep.test.ts index 400bb68b9b21..5790e1b5f2c9 100644 --- a/tests/unit/rule12-error-sanitization-sweep.test.ts +++ b/tests/unit/rule12-error-sanitization-sweep.test.ts @@ -99,7 +99,7 @@ function assertSanitized(raw: string, context: string): void { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; } else { diff --git a/tests/unit/run-next-playwright.test.ts b/tests/unit/run-next-playwright.test.ts index 61d166d38033..cca0a66c4a99 100644 --- a/tests/unit/run-next-playwright.test.ts +++ b/tests/unit/run-next-playwright.test.ts @@ -124,5 +124,5 @@ test("standalone asset helpers detect and rehydrate missing standalone static as ); assert.match(logs[0] || "", /Rehydrated standalone static\/public assets/); - fs.rmSync(tempRoot, { recursive: true, force: true }); + fs.rmSync(tempRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); diff --git a/tests/unit/runner-janitor.test.ts b/tests/unit/runner-janitor.test.ts index 9519414fbc94..6e6aee107a44 100644 --- a/tests/unit/runner-janitor.test.ts +++ b/tests/unit/runner-janitor.test.ts @@ -98,7 +98,7 @@ describe("runner-janitor.sh", () => { assert.ok(existsSync(p), `must not delete ${p} when idleness cannot be proven`); } } finally { - rmSync(f.base, { recursive: true, force: true }); + rmSync(f.base, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -130,7 +130,7 @@ describe("runner-janitor.sh", () => { assert.match(r.stdout, /zombie builds: 0/); assert.match(r.stdout, /done status=0/); } finally { - rmSync(f.base, { recursive: true, force: true }); + rmSync(f.base, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -146,7 +146,7 @@ describe("runner-janitor.sh", () => { assert.ok(existsSync(f.fresh), "a fresh dir must survive"); assert.ok(existsSync(f.unrelated), "files we did not create must survive even when old"); } finally { - rmSync(f.base, { recursive: true, force: true }); + rmSync(f.base, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -164,7 +164,7 @@ describe("runner-janitor.sh", () => { assert.match(body, /TMPFS_MAX_AGE_HOURS:-3\}/, "tmpfs default must stay short — it is RAM"); assert.match(body, /WORK_TEMP_MAX_AGE_HOURS:-24\}/); } finally { - rmSync(f.base, { recursive: true, force: true }); + rmSync(f.base, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -185,7 +185,7 @@ describe("runner-janitor.sh", () => { assert.match(r.stdout, /ROOT DISK \d+% >= 0%/); assert.ok(existsSync(f.staleTar), "alerting never deletes"); } finally { - rmSync(f.base, { recursive: true, force: true }); + rmSync(f.base, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/runtime-deps-save-exact-no-prune.test.ts b/tests/unit/runtime-deps-save-exact-no-prune.test.ts index 5cc5f9bd44e7..6c5a5cf0420f 100644 --- a/tests/unit/runtime-deps-save-exact-no-prune.test.ts +++ b/tests/unit/runtime-deps-save-exact-no-prune.test.ts @@ -11,7 +11,15 @@ // code path with zero network use. import test from "node:test"; import assert from "node:assert/strict"; -import { mkdtempSync, rmSync, mkdirSync, writeFileSync, chmodSync, existsSync, readFileSync } from "node:fs"; +import { + mkdtempSync, + rmSync, + mkdirSync, + writeFileSync, + chmodSync, + existsSync, + readFileSync, +} from "node:fs"; import { join, delimiter } from "node:path"; import { tmpdir } from "node:os"; @@ -45,7 +53,7 @@ function teardown(): void { if (v === undefined) delete process.env[k]; else process.env[k] = v; } - if (tmpDir) rmSync(tmpDir, { recursive: true, force: true }); + if (tmpDir) rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } function installLineFor(pkgPrefix: string): string | undefined { diff --git a/tests/unit/runtime/magicBytes.test.ts b/tests/unit/runtime/magicBytes.test.ts index 3680ae295cfe..451250a91ce4 100644 --- a/tests/unit/runtime/magicBytes.test.ts +++ b/tests/unit/runtime/magicBytes.test.ts @@ -59,4 +59,4 @@ test("platformBinaryLabel matches process.platform", () => { assert.equal(platformBinaryLabel(), expected); }); -test.after(() => rmSync(dir, { recursive: true, force: true })); +test.after(() => rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 })); diff --git a/tests/unit/sanitizer-residual-policy.test.ts b/tests/unit/sanitizer-residual-policy.test.ts index 4032dc123d5b..faeb94499952 100644 --- a/tests/unit/sanitizer-residual-policy.test.ts +++ b/tests/unit/sanitizer-residual-policy.test.ts @@ -13,9 +13,8 @@ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-test-sanitizer-r process.env.DATA_DIR = tmpDir; const { parseEnvBoolean } = await import("../../src/shared/utils/envBoolean.ts"); -const { resolveBlockThreshold, shouldBlockDetections } = await import( - "../../src/shared/utils/injectionSeverity.ts" -); +const { resolveBlockThreshold, shouldBlockDetections } = + await import("../../src/shared/utils/injectionSeverity.ts"); const { sanitizeRequest } = await import("../../src/shared/utils/inputSanitizer.ts"); const { evaluatePromptInjection } = await import("../../src/lib/guardrails/promptInjection.ts"); const { PIIMaskerGuardrail } = await import("../../src/lib/guardrails/piiMasker.ts"); @@ -23,7 +22,7 @@ const { resetDbInstance } = await import("../../src/lib/db/core.ts"); test.after(() => { resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); async function withEnv( @@ -113,17 +112,23 @@ test("sanitizeRequest and evaluatePromptInjection share high-default threshold", // Medium-only detections should not block at default threshold. // Use a content shape that is unlikely to also trip high patterns. const body = { - messages: [{ role: "user", content: "Please act as a different assistant persona for this task." }], + messages: [ + { role: "user", content: "Please act as a different assistant persona for this task." }, + ], }; const sanitized = sanitizeRequest(body, silentLogger); const evaluated = evaluatePromptInjection(body, {}, { log: silentLogger }); // If medium patterns matched, neither path should block under high threshold. - if (sanitized.detections.some((d) => d.severity === "medium") && - !sanitized.detections.some((d) => d.severity === "high")) { + if ( + sanitized.detections.some((d) => d.severity === "medium") && + !sanitized.detections.some((d) => d.severity === "high") + ) { assert.equal(sanitized.blocked, false); } - if (evaluated.result.detections.some((d) => d.severity === "medium") && - !evaluated.result.detections.some((d) => d.severity === "high")) { + if ( + evaluated.result.detections.some((d) => d.severity === "medium") && + !evaluated.result.detections.some((d) => d.severity === "high") + ) { assert.equal(evaluated.blocked, false); } } diff --git a/tests/unit/search-provider-opaque-400-10849.test.ts b/tests/unit/search-provider-opaque-400-10849.test.ts index 431675f3bbcc..effa71d7857d 100644 --- a/tests/unit/search-provider-opaque-400-10849.test.ts +++ b/tests/unit/search-provider-opaque-400-10849.test.ts @@ -12,7 +12,7 @@ const searchRoute = await import("../../src/app/api/v1/search/route.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function makeRequest(body: unknown) { @@ -49,10 +49,7 @@ test("#10849: short alias 'brave' resolves like existing 'jina' aliases (not an }); test("#10849: a genuinely bad field surfaces a non-generic, field-named 400 message", async () => { - const response = await searchRoute.POST( - makeRequest({ query: "test", search_type: "bogus" }), - {} - ); + const response = await searchRoute.POST(makeRequest({ query: "test", search_type: "bogus" }), {}); const body = (await response.json()) as ErrorBody; assert.equal(response.status, 400); diff --git a/tests/unit/search-route.test.ts b/tests/unit/search-route.test.ts index a5b897eec3ba..01c0bba7e3a4 100644 --- a/tests/unit/search-route.test.ts +++ b/tests/unit/search-route.test.ts @@ -13,7 +13,7 @@ const searchRoute = await import("../../src/app/api/v1/search/route.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -42,7 +42,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("v1 search GET lists all search providers", async () => { diff --git a/tests/unit/security-s1-s2-s4.test.ts b/tests/unit/security-s1-s2-s4.test.ts index e060bb5bb4d8..3229a14d7da0 100644 --- a/tests/unit/security-s1-s2-s4.test.ts +++ b/tests/unit/security-s1-s2-s4.test.ts @@ -31,7 +31,9 @@ describe("S2 — agent-card topology sanitisation", () => { it("agent-card.json derives URL from request.nextUrl.origin when OMNIROUTE_BASE_URL is unset", async () => { const mod = await import("../../src/app/.well-known/agent-card.json/route.ts"); - const request = new Request("https://gateway.example.com/.well-known/agent-card.json") as unknown as NextRequest; + const request = new Request( + "https://gateway.example.com/.well-known/agent-card.json" + ) as unknown as NextRequest; Object.defineProperty(request, "nextUrl", { value: new URL("https://gateway.example.com/.well-known/agent-card.json"), configurable: true, @@ -41,7 +43,11 @@ describe("S2 — agent-card topology sanitisation", () => { assert.equal(res.status, 200); const card = (await res.json()) as { url?: string; supportedInterfaces?: { url?: string }[] }; assert.ok(card.url, "card must have a url"); - assert.equal(new URL(card.url).origin, "https://gateway.example.com", `expected gateway.example.com origin, got ${card.url}`); + assert.equal( + new URL(card.url).origin, + "https://gateway.example.com", + `expected gateway.example.com origin, got ${card.url}` + ); if (card.supportedInterfaces && card.supportedInterfaces.length > 0) { const ifaceUrl = card.supportedInterfaces[0].url; assert.equal( @@ -55,7 +61,9 @@ describe("S2 — agent-card topology sanitisation", () => { it("agent-card.json uses OMNIROUTE_BASE_URL when set", async () => { process.env.OMNIROUTE_BASE_URL = "https://custom.example.com"; const mod = await import("../../src/app/.well-known/agent-card.json/route.ts"); - const request = new Request("http://localhost:20128/.well-known/agent-card.json") as unknown as NextRequest; + const request = new Request( + "http://localhost:20128/.well-known/agent-card.json" + ) as unknown as NextRequest; Object.defineProperty(request, "nextUrl", { value: new URL("http://localhost:20128/.well-known/agent-card.json"), configurable: true, @@ -65,12 +73,18 @@ describe("S2 — agent-card topology sanitisation", () => { assert.equal(res.status, 200); const card = (await res.json()) as { url?: string }; assert.ok(card.url, "card must have a url"); - assert.equal(new URL(card.url).origin, "https://custom.example.com", `expected custom.example.com origin, got ${card.url}`); + assert.equal( + new URL(card.url).origin, + "https://custom.example.com", + `expected custom.example.com origin, got ${card.url}` + ); }); it("agent.json derives URL from request.nextUrl.origin when OMNIROUTE_BASE_URL is unset", async () => { const mod = await import("../../src/app/.well-known/agent.json/route.ts"); - const request = new Request("https://gateway.example.com/.well-known/agent.json") as unknown as NextRequest; + const request = new Request( + "https://gateway.example.com/.well-known/agent.json" + ) as unknown as NextRequest; Object.defineProperty(request, "nextUrl", { value: new URL("https://gateway.example.com/.well-known/agent.json"), configurable: true, @@ -80,7 +94,11 @@ describe("S2 — agent-card topology sanitisation", () => { assert.equal(res.status, 200); const card = (await res.json()) as { url?: string }; assert.ok(card.url, "card must have a url"); - assert.equal(new URL(card.url).origin, "https://gateway.example.com", `expected gateway.example.com origin, got ${card.url}`); + assert.equal( + new URL(card.url).origin, + "https://gateway.example.com", + `expected gateway.example.com origin, got ${card.url}` + ); }); }); @@ -90,12 +108,8 @@ const loginGuardMod = await import("../../src/server/auth/loginGuard"); // ── S4: login guard Retry-After tests ───────────────────────────────── describe("S4 — 429 Retry-After header", () => { - const { - checkLoginGuard, - recordLoginFailure, - resetLoginGuardForTests, - LOGIN_GUARD_TUNABLES, - } = loginGuardMod; + const { checkLoginGuard, recordLoginFailure, resetLoginGuardForTests, LOGIN_GUARD_TUNABLES } = + loginGuardMod; beforeEach(() => { resetLoginGuardForTests(); @@ -108,8 +122,10 @@ describe("S4 — 429 Retry-After header", () => { } const decision = checkLoginGuard(ip, { enabled: true }); assert.equal(decision.allowed, false); - assert.ok(typeof decision.retryAfterSeconds === "number" && decision.retryAfterSeconds > 0, - `retryAfterSeconds should be > 0, got ${decision.retryAfterSeconds}`); + assert.ok( + typeof decision.retryAfterSeconds === "number" && decision.retryAfterSeconds > 0, + `retryAfterSeconds should be > 0, got ${decision.retryAfterSeconds}` + ); }); it("recordLoginFailure returns retryAfterSeconds on threshold hit", () => { @@ -120,8 +136,10 @@ describe("S4 — 429 Retry-After header", () => { assert.equal(dec.allowed, true, `attempt #${i + 1} should still be allowed`); } else { assert.equal(dec.allowed, false, `attempt #${i + 1} (threshold) should be locked`); - assert.ok(typeof dec.retryAfterSeconds === "number" && dec.retryAfterSeconds > 0, - `retryAfterSeconds should be > 0 on threshold hit, got ${dec.retryAfterSeconds}`); + assert.ok( + typeof dec.retryAfterSeconds === "number" && dec.retryAfterSeconds > 0, + `retryAfterSeconds should be > 0 on threshold hit, got ${dec.retryAfterSeconds}` + ); } } }); @@ -134,7 +152,10 @@ describe("S4 — 429 Retry-After header", () => { const guardDec = checkLoginGuard(ip, { enabled: true }); assert.equal(guardDec.allowed, false); const headerValue = String(guardDec.retryAfterSeconds || 60); - assert.ok(/^\d+$/.test(headerValue), `Retry-After should be an integer string, got ${headerValue}`); + assert.ok( + /^\d+$/.test(headerValue), + `Retry-After should be an integer string, got ${headerValue}` + ); assert.ok(Number.parseInt(headerValue, 10) > 0, "Retry-After should be positive"); resetLoginGuardForTests(); @@ -145,7 +166,10 @@ describe("S4 — 429 Retry-After header", () => { } assert.equal(failureDec!.allowed, false); const headerValue2 = String(failureDec!.retryAfterSeconds || 60); - assert.ok(/^\d+$/.test(headerValue2), `Retry-After should be an integer string, got ${headerValue2}`); + assert.ok( + /^\d+$/.test(headerValue2), + `Retry-After should be an integer string, got ${headerValue2}` + ); assert.ok(Number.parseInt(headerValue2, 10) > 0, "Retry-After should be positive"); }); }); @@ -192,7 +216,7 @@ describe("S1 — login rate-limit key uses anti-spoofed peer IP", () => { }); after(() => { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (JWT_SAVED !== undefined) { process.env.JWT_SECRET = JWT_SAVED; } else { @@ -242,11 +266,16 @@ describe("S1 — login rate-limit key uses anti-spoofed peer IP", () => { // Locked out — rate-limit key is tied to the trusted peer IP, not XFF const retryAfter = res.headers.get("Retry-After"); assert.ok(retryAfter !== null, "429 response must include Retry-After header"); - assert.ok(/^\d+$/.test(retryAfter!), `Retry-After should be a positive integer, got ${retryAfter}`); + assert.ok( + /^\d+$/.test(retryAfter!), + `Retry-After should be a positive integer, got ${retryAfter}` + ); return; } } - assert.fail("Expected at least one 429 response after threshold failed attempts with the same trusted peer IP"); + assert.fail( + "Expected at least one 429 response after threshold failed attempts with the same trusted peer IP" + ); }); it("ignores spoofed x-omniroute-trusted-peer-ip when OMNIROUTE_PEER_STAMP_TOKEN is not set", async () => { @@ -288,7 +317,9 @@ describe("S1 — login rate-limit key uses anti-spoofed peer IP", () => { return; } } - assert.fail("Expected 429 after threshold failures — spoofed header should not bypass rate-limit"); + assert.fail( + "Expected 429 after threshold failures — spoofed header should not bypass rate-limit" + ); }); it("falls back to auditContext.ipAddress when trusted peer IP header is absent", async () => { @@ -320,7 +351,10 @@ describe("S1 — login rate-limit key uses anti-spoofed peer IP", () => { // Locked out — rate-limit key is tied to the XFF-derived IP const retryAfter = res.headers.get("Retry-After"); assert.ok(retryAfter !== null, "429 response must include Retry-After header"); - assert.ok(Number.parseInt(retryAfter!, 10) > 0, `Retry-After should be > 0, got ${retryAfter}`); + assert.ok( + Number.parseInt(retryAfter!, 10) > 0, + `Retry-After should be > 0, got ${retryAfter}` + ); return; } } @@ -348,10 +382,13 @@ describe("S1 — login rate-limit key uses anti-spoofed peer IP", () => { if (res.status === 429) { const retryAfter = res.headers.get("Retry-After"); assert.ok(retryAfter !== null, "429 response must include Retry-After header"); - assert.ok(Number.parseInt(retryAfter!, 10) > 0, `Retry-After should be > 0, got ${retryAfter}`); + assert.ok( + Number.parseInt(retryAfter!, 10) > 0, + `Retry-After should be > 0, got ${retryAfter}` + ); return; } } assert.fail("Expected at least one 429 response after threshold failed attempts"); }); -}); \ No newline at end of file +}); diff --git a/tests/unit/serial/combo-health-autopilot.test.ts b/tests/unit/serial/combo-health-autopilot.test.ts index 091930283b39..63e7f1bd856d 100644 --- a/tests/unit/serial/combo-health-autopilot.test.ts +++ b/tests/unit/serial/combo-health-autopilot.test.ts @@ -26,7 +26,7 @@ const { normalizeComboStep } = await import("../../../src/lib/combos/steps.ts"); async function resetStorage() { comboMetrics.resetAllComboMetrics(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -101,7 +101,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; diff --git a/tests/unit/serial/combo-quota-share-cooldown-wait-timing.test.ts b/tests/unit/serial/combo-quota-share-cooldown-wait-timing.test.ts index 20ab6fe40ccc..7e65ac39e835 100644 --- a/tests/unit/serial/combo-quota-share-cooldown-wait-timing.test.ts +++ b/tests/unit/serial/combo-quota-share-cooldown-wait-timing.test.ts @@ -89,7 +89,7 @@ function comboOf(strategy: string) { async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -102,7 +102,7 @@ test.after(async () => { clearAllModelLockouts(); try { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { /* best effort */ } diff --git a/tests/unit/serial/combo-strategy-fallbacks-half-open-timing.test.ts b/tests/unit/serial/combo-strategy-fallbacks-half-open-timing.test.ts index f813779c7877..c209724dddad 100644 --- a/tests/unit/serial/combo-strategy-fallbacks-half-open-timing.test.ts +++ b/tests/unit/serial/combo-strategy-fallbacks-half-open-timing.test.ts @@ -21,7 +21,9 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-combo-fallbacks-half-open-")); +const TEST_DATA_DIR = fs.mkdtempSync( + path.join(os.tmpdir(), "omniroute-combo-fallbacks-half-open-") +); const ORIGINAL_DATA_DIR = process.env.DATA_DIR; process.env.DATA_DIR = TEST_DATA_DIR; @@ -61,7 +63,7 @@ async function cleanupTestDataDir() { for (let attempt = 0; attempt < 5; attempt += 1) { try { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); return; } catch (error: unknown) { lastError = error; diff --git a/tests/unit/serial/provider-health-autopilot.test.ts b/tests/unit/serial/provider-health-autopilot.test.ts index 67e0b2290946..964a941ea7a7 100644 --- a/tests/unit/serial/provider-health-autopilot.test.ts +++ b/tests/unit/serial/provider-health-autopilot.test.ts @@ -28,7 +28,7 @@ const PROVIDER = "autopilot-test-provider"; async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -70,7 +70,7 @@ test.beforeEach(async () => { test.after(async () => { accountFallback.clearProviderFailure(PROVIDER); await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; diff --git a/tests/unit/serial/quota-division-blocks.test.ts b/tests/unit/serial/quota-division-blocks.test.ts index 3119f6661946..a148974a9579 100644 --- a/tests/unit/serial/quota-division-blocks.test.ts +++ b/tests/unit/serial/quota-division-blocks.test.ts @@ -52,7 +52,11 @@ const core = await import("../../../src/lib/db/core.ts"); test.after(() => { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - try { fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); } catch { /* ignore */ } + try { + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + } catch { + /* ignore */ + } } }); @@ -66,98 +70,92 @@ const store = new SqliteQuotaStore(); await test("quota-division-blocks: countable-unit enforcement (block + allow)", async (t) => { // ── Scenario A: pool total > effectiveLimit → block ────────────────────── - await t.test( - "[A] pool total > effectiveLimit → block (global-saturated)", - async () => { - const CONN = "conn-block-a"; - const PROV = "test-provider-a2-block"; - const KEY_A = "key-block-a1"; - const KEY_B = "key-block-b1"; - - // Seed plan: requests/hourly/limit=100 - providerPlans.upsertPlan( - CONN, - PROV, - [{ unit: "requests", window: "hourly", limit: LIMIT }], - "manual" - ); - - // Create pool: 2 allocations at 50/50 hard - const pool = quotaPools.createPool({ - connectionId: CONN, - name: "Block Pool A", - allocations: [ - { apiKeyId: KEY_A, weight: 50, policy: "hard" }, - { apiKeyId: KEY_B, weight: 50, policy: "hard" }, - ], - }); - - const dim = { poolId: pool.id, unit: "requests" as const, window: "hourly" as const }; - - // Consume: keyA=60, keyB=60 → poolTotal=120 > effectiveLimit=100 - await store.consume(KEY_A, dim, 60); - await store.consume(KEY_B, dim, 60); - - const decision = await enforceQuotaShare({ - apiKeyId: KEY_A, - connectionId: CONN, - provider: PROV, - estimatedCost: { requests: 1 }, - }); - - assert.equal( - decision.kind, - "block", - `[A] Expected block when poolTotal(120) ≥ effectiveLimit(100); got: ${JSON.stringify(decision)}` - ); - } - ); + await t.test("[A] pool total > effectiveLimit → block (global-saturated)", async () => { + const CONN = "conn-block-a"; + const PROV = "test-provider-a2-block"; + const KEY_A = "key-block-a1"; + const KEY_B = "key-block-b1"; + + // Seed plan: requests/hourly/limit=100 + providerPlans.upsertPlan( + CONN, + PROV, + [{ unit: "requests", window: "hourly", limit: LIMIT }], + "manual" + ); + + // Create pool: 2 allocations at 50/50 hard + const pool = quotaPools.createPool({ + connectionId: CONN, + name: "Block Pool A", + allocations: [ + { apiKeyId: KEY_A, weight: 50, policy: "hard" }, + { apiKeyId: KEY_B, weight: 50, policy: "hard" }, + ], + }); + + const dim = { poolId: pool.id, unit: "requests" as const, window: "hourly" as const }; + + // Consume: keyA=60, keyB=60 → poolTotal=120 > effectiveLimit=100 + await store.consume(KEY_A, dim, 60); + await store.consume(KEY_B, dim, 60); + + const decision = await enforceQuotaShare({ + apiKeyId: KEY_A, + connectionId: CONN, + provider: PROV, + estimatedCost: { requests: 1 }, + }); + + assert.equal( + decision.kind, + "block", + `[A] Expected block when poolTotal(120) ≥ effectiveLimit(100); got: ${JSON.stringify(decision)}` + ); + }); // ── Scenario B: pool total < effectiveLimit, key under fair-share → allow ─ - await t.test( - "[B] pool total < effectiveLimit and key under fair-share → allow", - async () => { - const CONN = "conn-allow-b"; - const PROV = "test-provider-a2-allow"; - const KEY_A = "key-allow-a1"; - const KEY_B = "key-allow-b1"; - - // Seed plan for separate connection: requests/hourly/limit=100 - providerPlans.upsertPlan( - CONN, - PROV, - [{ unit: "requests", window: "hourly", limit: LIMIT }], - "manual" - ); - - // Create pool: distinct from Scenario A (different poolId + connection) - const pool = quotaPools.createPool({ - connectionId: CONN, - name: "Allow Pool B", - allocations: [ - { apiKeyId: KEY_A, weight: 50, policy: "hard" }, - { apiKeyId: KEY_B, weight: 50, policy: "hard" }, - ], - }); - - const dim = { poolId: pool.id, unit: "requests" as const, window: "hourly" as const }; - - // Consume: keyA=20, keyB=20 → poolTotal=40 < effectiveLimit=100 - await store.consume(KEY_A, dim, 20); - await store.consume(KEY_B, dim, 20); - - const decision = await enforceQuotaShare({ - apiKeyId: KEY_A, - connectionId: CONN, - provider: PROV, - estimatedCost: { requests: 1 }, - }); - - assert.equal( - decision.kind, - "allow", - `[B] Expected allow when poolTotal(40) < effectiveLimit(100); got: ${JSON.stringify(decision)}` - ); - } - ); + await t.test("[B] pool total < effectiveLimit and key under fair-share → allow", async () => { + const CONN = "conn-allow-b"; + const PROV = "test-provider-a2-allow"; + const KEY_A = "key-allow-a1"; + const KEY_B = "key-allow-b1"; + + // Seed plan for separate connection: requests/hourly/limit=100 + providerPlans.upsertPlan( + CONN, + PROV, + [{ unit: "requests", window: "hourly", limit: LIMIT }], + "manual" + ); + + // Create pool: distinct from Scenario A (different poolId + connection) + const pool = quotaPools.createPool({ + connectionId: CONN, + name: "Allow Pool B", + allocations: [ + { apiKeyId: KEY_A, weight: 50, policy: "hard" }, + { apiKeyId: KEY_B, weight: 50, policy: "hard" }, + ], + }); + + const dim = { poolId: pool.id, unit: "requests" as const, window: "hourly" as const }; + + // Consume: keyA=20, keyB=20 → poolTotal=40 < effectiveLimit=100 + await store.consume(KEY_A, dim, 20); + await store.consume(KEY_B, dim, 20); + + const decision = await enforceQuotaShare({ + apiKeyId: KEY_A, + connectionId: CONN, + provider: PROV, + estimatedCost: { requests: 1 }, + }); + + assert.equal( + decision.kind, + "allow", + `[B] Expected allow when poolTotal(40) < effectiveLimit(100); got: ${JSON.stringify(decision)}` + ); + }); }); diff --git a/tests/unit/services-branch-hardening.test.ts b/tests/unit/services-branch-hardening.test.ts index ca51606acb2a..8a04fe2faa77 100644 --- a/tests/unit/services-branch-hardening.test.ts +++ b/tests/unit/services-branch-hardening.test.ts @@ -19,7 +19,7 @@ test.after(() => { process.env.DATA_DIR = ORIGINAL_DATA_DIR; } try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // Best effort cleanup } diff --git a/tests/unit/services/ServiceSupervisor.test.ts b/tests/unit/services/ServiceSupervisor.test.ts index 5a5441c53a24..c343689a0c3f 100644 --- a/tests/unit/services/ServiceSupervisor.test.ts +++ b/tests/unit/services/ServiceSupervisor.test.ts @@ -77,7 +77,7 @@ function tickConfig(tool: string, port: number) { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("start spawns process and captures logs in ring buffer", async () => { diff --git a/tests/unit/services/cliproxy-health-model-auth.test.ts b/tests/unit/services/cliproxy-health-model-auth.test.ts index 3c5c6e2d52c7..0b17a43bc13d 100644 --- a/tests/unit/services/cliproxy-health-model-auth.test.ts +++ b/tests/unit/services/cliproxy-health-model-auth.test.ts @@ -91,7 +91,7 @@ after(async () => { unregisterSupervisor("cliproxy"); await new Promise((resolve) => fakeCliproxy.close(() => resolve())); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("embedded CLIProxyAPI uses public health and dedicated model credentials", async () => { diff --git a/tests/unit/services/emergency-fallback.test.ts b/tests/unit/services/emergency-fallback.test.ts index bf6c39603b3b..e6a375b3a548 100644 --- a/tests/unit/services/emergency-fallback.test.ts +++ b/tests/unit/services/emergency-fallback.test.ts @@ -31,7 +31,7 @@ function restoreEnv(name: string, value: string | undefined) { function resetTestState() { core.resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(tmpDir, { recursive: true }); delete process.env.OMNIROUTE_EMERGENCY_FALLBACK; resetEmergencyFallbackEnvCache(); @@ -50,7 +50,7 @@ test.afterEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); restoreEnv("DATA_DIR", previousDataDir); restoreEnv("DISABLE_SQLITE_AUTO_BACKUP", previousDisableSqliteAutoBackup); }); diff --git a/tests/unit/services/end-to-end-shape.test.ts b/tests/unit/services/end-to-end-shape.test.ts index 2e277398691a..f03f7ef1c454 100644 --- a/tests/unit/services/end-to-end-shape.test.ts +++ b/tests/unit/services/end-to-end-shape.test.ts @@ -549,5 +549,5 @@ describe("Cross-service shape consistency", () => { after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); diff --git a/tests/unit/services/installers/bifrost-transport-version-format.test.ts b/tests/unit/services/installers/bifrost-transport-version-format.test.ts index 480ad934bd85..2950be952b60 100644 --- a/tests/unit/services/installers/bifrost-transport-version-format.test.ts +++ b/tests/unit/services/installers/bifrost-transport-version-format.test.ts @@ -26,31 +26,27 @@ import path from "node:path"; describe("formatTransportVersion (pure)", () => { it("prepends v to bare semver", async () => { - const { formatTransportVersion } = await import( - "../../../../src/lib/services/installers/bifrost.ts" - ); + const { formatTransportVersion } = + await import("../../../../src/lib/services/installers/bifrost.ts"); assert.equal(formatTransportVersion("1.6.3"), "v1.6.3"); assert.equal(formatTransportVersion("2.0.0-beta.1"), "v2.0.0-beta.1"); }); it("leaves an already-v-prefixed version untouched", async () => { - const { formatTransportVersion } = await import( - "../../../../src/lib/services/installers/bifrost.ts" - ); + const { formatTransportVersion } = + await import("../../../../src/lib/services/installers/bifrost.ts"); assert.equal(formatTransportVersion("v1.6.3"), "v1.6.3"); }); it('passes through "latest" untouched', async () => { - const { formatTransportVersion } = await import( - "../../../../src/lib/services/installers/bifrost.ts" - ); + const { formatTransportVersion } = + await import("../../../../src/lib/services/installers/bifrost.ts"); assert.equal(formatTransportVersion("latest"), "latest"); }); it('defaults null to "latest"', async () => { - const { formatTransportVersion } = await import( - "../../../../src/lib/services/installers/bifrost.ts" - ); + const { formatTransportVersion } = + await import("../../../../src/lib/services/installers/bifrost.ts"); assert.equal(formatTransportVersion(null), "latest"); }); }); @@ -86,14 +82,12 @@ describe("resolveSpawnArgs BIFROST_TRANSPORT_VERSION (real filesystem)", () => { process.env.DATA_DIR = ORIGINAL_DATA_DIR; } process.env.PATH = ORIGINAL_PATH; - fs.rmSync(dataDir, { recursive: true, force: true }); - fs.rmSync(fakeBinDir, { recursive: true, force: true }); + fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.rmSync(fakeBinDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); it("BIFROST_TRANSPORT_VERSION is v-prefixed, matching what bin.js requires", async () => { - const { resolveSpawnArgs } = await import( - "../../../../src/lib/services/installers/bifrost.ts" - ); + const { resolveSpawnArgs } = await import("../../../../src/lib/services/installers/bifrost.ts"); const args = resolveSpawnArgs(8080); diff --git a/tests/unit/services/installers/bifrost.test.ts b/tests/unit/services/installers/bifrost.test.ts index f59541a4360e..e91918d58738 100644 --- a/tests/unit/services/installers/bifrost.test.ts +++ b/tests/unit/services/installers/bifrost.test.ts @@ -64,8 +64,8 @@ const { test.after(() => { process.env.PATH = originalPath; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); - fs.rmSync(FAKE_BIN_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.rmSync(FAKE_BIN_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("BIFROST_DEFAULT_PORT is 8080", () => { diff --git a/tests/unit/services/installers/cliproxy-resolve-spawn-args-6877.test.ts b/tests/unit/services/installers/cliproxy-resolve-spawn-args-6877.test.ts index 98408a999dc7..cb5d5d183a64 100644 --- a/tests/unit/services/installers/cliproxy-resolve-spawn-args-6877.test.ts +++ b/tests/unit/services/installers/cliproxy-resolve-spawn-args-6877.test.ts @@ -39,14 +39,14 @@ after(() => { } else { process.env.DATA_DIR = ORIGINAL_DATA_DIR; } - fs.rmSync(FIXED_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(FIXED_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); describe("resolveSpawnArgs (#6877 — real filesystem)", () => { const dataDir = FIXED_DATA_DIR; beforeEach(() => { - fs.rmSync(dataDir, { recursive: true, force: true }); + fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(dataDir, { recursive: true }); }); diff --git a/tests/unit/services/installers/ninerouter.test.ts b/tests/unit/services/installers/ninerouter.test.ts index 4554e34d3e73..870dbe184689 100644 --- a/tests/unit/services/installers/ninerouter.test.ts +++ b/tests/unit/services/installers/ninerouter.test.ts @@ -72,8 +72,8 @@ const { test.after(() => { process.env.PATH = originalPath; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); - fs.rmSync(FAKE_BIN_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.rmSync(FAKE_BIN_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("install creates package.json structure", async () => { diff --git a/tests/unit/services/lifecycle.test.ts b/tests/unit/services/lifecycle.test.ts index 28a60338345a..95de97e39f4b 100644 --- a/tests/unit/services/lifecycle.test.ts +++ b/tests/unit/services/lifecycle.test.ts @@ -56,7 +56,7 @@ function makeFakeSup(tool: string) { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ─── status endpoint ──────────────────────────────────────────────────────── diff --git a/tests/unit/services/modelSync.test.ts b/tests/unit/services/modelSync.test.ts index f720a61d8ce6..fe60ebc5ce9e 100644 --- a/tests/unit/services/modelSync.test.ts +++ b/tests/unit/services/modelSync.test.ts @@ -30,7 +30,7 @@ afterEach(() => { after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function makeFetch( diff --git a/tests/unit/services/portProbePid.test.ts b/tests/unit/services/portProbePid.test.ts index b9df600eac13..c106b1d6a9b3 100644 --- a/tests/unit/services/portProbePid.test.ts +++ b/tests/unit/services/portProbePid.test.ts @@ -166,6 +166,6 @@ test("resolvePortPid still resolves a pid on a host without lsof", async (t) => } finally { process.env.PATH = originalPath; await new Promise((resolve) => server.close(() => resolve())); - rmSync(shim, { recursive: true, force: true }); + rmSync(shim, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/services/ringBuffer.test.ts b/tests/unit/services/ringBuffer.test.ts index bf55f9cc0aa1..f62b2073a7e0 100644 --- a/tests/unit/services/ringBuffer.test.ts +++ b/tests/unit/services/ringBuffer.test.ts @@ -77,7 +77,7 @@ test("flush writes to file when path set", async () => { assert.ok(content.includes("line-one"), "flush file should contain log entry"); assert.ok(content.includes("[stderr]"), "flush file should contain stderr entry"); } finally { - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/services/serviceSupervisorSpawnError.test.ts b/tests/unit/services/serviceSupervisorSpawnError.test.ts index 87b1f6cb9e95..e781e3fd5b39 100644 --- a/tests/unit/services/serviceSupervisorSpawnError.test.ts +++ b/tests/unit/services/serviceSupervisorSpawnError.test.ts @@ -69,12 +69,9 @@ describe("ServiceSupervisor spawn-failure handling", () => { const status = await supervisor.start(); assert.equal(status.state, "error"); assert.ok(status.lastError, "lastError should describe the spawn failure"); - assert.match( - status.lastError!, - /ENOENT|EACCES|EINVAL|EFTYPE|not recognized|spawn|%1|Win32/i - ); + assert.match(status.lastError!, /ENOENT|EACCES|EINVAL|EFTYPE|not recognized|spawn|%1|Win32/i); } finally { - await rm(dir, { recursive: true, force: true }); + await rm(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/session-affinity-combo-timeout-eviction.test.ts b/tests/unit/session-affinity-combo-timeout-eviction.test.ts index 9dfb36f26054..8475588b3825 100644 --- a/tests/unit/session-affinity-combo-timeout-eviction.test.ts +++ b/tests/unit/session-affinity-combo-timeout-eviction.test.ts @@ -44,13 +44,13 @@ const timedOutSignal = () => abortedWith(new Error(abortReasons.COMBO_PER_MODEL_ test.beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); }); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("evicts the pin when the combo per-model timeout abandons the pinned account", () => { diff --git a/tests/unit/session-affinity-generic-7274.test.ts b/tests/unit/session-affinity-generic-7274.test.ts index 3700e3ad617d..b3a0f54e966c 100644 --- a/tests/unit/session-affinity-generic-7274.test.ts +++ b/tests/unit/session-affinity-generic-7274.test.ts @@ -62,13 +62,14 @@ const settingsDb = await import("../../src/lib/db/settings.ts"); const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); const affinityDb = await import("../../src/lib/db/sessionAccountAffinity.ts"); const auth = await import("../../src/sse/services/auth.ts"); -const { resolveSessionAffinityTtlMs } = await import("../../src/sse/services/sessionAffinityPin.ts"); +const { resolveSessionAffinityTtlMs } = + await import("../../src/sse/services/sessionAffinityPin.ts"); const { DefaultExecutor } = await import("../../open-sse/executors/default.ts"); async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -77,7 +78,8 @@ async function seedConnection(provider: string, overrides: Record) || {}, @@ -91,7 +93,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ── 1. generic (non-Codex) provider now honors the TTL ────────────────────── @@ -106,7 +108,11 @@ test("#7274 a non-Codex provider with sessionAffinityTtlMs > 0 persists and reus sessionKey: "session-generic", forcedConnectionId: connectionA.id, }); - assert.equal(request1?.connectionId, connectionA.id, "first request pins to the forced connection"); + assert.equal( + request1?.connectionId, + connectionA.id, + "first request pins to the forced connection" + ); assert.equal( affinityDb.getSessionAccountAffinity("session-generic", "glm", 60_000)?.connectionId, connectionA.id, @@ -172,7 +178,7 @@ test("#7274 resolveSessionAffinityTtlMs prefers the new generic key over the leg test("#7274 resolveSessionAffinityTtlMs now applies to any provider, not just codex", () => { const ttl = resolveSessionAffinityTtlMs("openai", {}, { sessionAffinityTtlMs: 45_000 }); - assert.equal(ttl, 45_000, "the provider !== \"codex\" early-return must be gone"); + assert.equal(ttl, 45_000, 'the provider !== "codex" early-return must be gone'); }); // ── 2b. raw-SQL migration: additive, idempotent carry-over ────────────────── @@ -200,7 +206,9 @@ test("#7274 migration 124 carries codexSessionAffinityTtlMs over to sessionAffin db.exec(migrationSql); const row = db - .prepare("SELECT value FROM key_value WHERE namespace = 'settings' AND key = 'sessionAffinityTtlMs'") + .prepare( + "SELECT value FROM key_value WHERE namespace = 'settings' AND key = 'sessionAffinityTtlMs'" + ) .get() as { value: string } | undefined; assert.equal(row?.value, "60000", "the generic key must carry the old value over"); @@ -209,13 +217,19 @@ test("#7274 migration 124 carries codexSessionAffinityTtlMs over to sessionAffin "SELECT value FROM key_value WHERE namespace = 'settings' AND key = 'codexSessionAffinityTtlMs'" ) .get() as { value: string } | undefined; - assert.equal(oldRow?.value, "60000", "the migration is additive — the old key/row is not deleted"); + assert.equal( + oldRow?.value, + "60000", + "the migration is additive — the old key/row is not deleted" + ); // Idempotency: re-running the migration (as the runner would on a replay) // must not throw and must not change the already-carried-over value. assert.doesNotThrow(() => db.exec(migrationSql)); const rowAfterReplay = db - .prepare("SELECT value FROM key_value WHERE namespace = 'settings' AND key = 'sessionAffinityTtlMs'") + .prepare( + "SELECT value FROM key_value WHERE namespace = 'settings' AND key = 'sessionAffinityTtlMs'" + ) .get() as { value: string } | undefined; assert.equal(rowAfterReplay?.value, "60000"); } finally { @@ -242,7 +256,9 @@ test("#7274 migration 124 is a no-op when the operator never configured the lega assert.doesNotThrow(() => db.exec(migrationSql)); const row = db - .prepare("SELECT value FROM key_value WHERE namespace = 'settings' AND key = 'sessionAffinityTtlMs'") + .prepare( + "SELECT value FROM key_value WHERE namespace = 'settings' AND key = 'sessionAffinityTtlMs'" + ) .get(); assert.equal(row, undefined, "no row should be created when there was nothing to carry over"); } finally { diff --git a/tests/unit/session-leases-route.test.ts b/tests/unit/session-leases-route.test.ts index 3d6c28dccdcd..c3e5e77f5c14 100644 --- a/tests/unit/session-leases-route.test.ts +++ b/tests/unit/session-leases-route.test.ts @@ -67,7 +67,7 @@ async function seedKey( async function resetStorage(): Promise { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); attemptedExternalCalls = 0; modelAliasResolver.invalidateAliasCache(); @@ -84,7 +84,7 @@ test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("requires authentication, managed scope, and canonical explicit owner", async () => { diff --git a/tests/unit/settings-api.test.ts b/tests/unit/settings-api.test.ts index 8ff0dd80a18e..9f359e5e6134 100644 --- a/tests/unit/settings-api.test.ts +++ b/tests/unit/settings-api.test.ts @@ -20,13 +20,13 @@ async function createSettingsApiHarness() { async function resetStorage() { core.resetDbInstance(); - fs.rmSync(testDataDir, { recursive: true, force: true }); + fs.rmSync(testDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(testDataDir, { recursive: true }); } function cleanup() { core.resetDbInstance(); - fs.rmSync(testDataDir, { recursive: true, force: true }); + fs.rmSync(testDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } return { diff --git a/tests/unit/settings-cas-7784.test.ts b/tests/unit/settings-cas-7784.test.ts index 5c20291514a4..76adcb24247f 100644 --- a/tests/unit/settings-cas-7784.test.ts +++ b/tests/unit/settings-cas-7784.test.ts @@ -22,13 +22,13 @@ const settingsRoute = await import("../../src/app/api/settings/route.ts"); beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); }); after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); describe("#7784 settings optimistic concurrency", () => { diff --git a/tests/unit/settings-debugmode-default.test.ts b/tests/unit/settings-debugmode-default.test.ts index bd667dc1208f..2ded41c5a821 100644 --- a/tests/unit/settings-debugmode-default.test.ts +++ b/tests/unit/settings-debugmode-default.test.ts @@ -22,5 +22,5 @@ test("logToolSources defaults to false", async () => { }); test.after(() => { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); diff --git a/tests/unit/settings-route-password.test.ts b/tests/unit/settings-route-password.test.ts index 32f967f2ce26..6599d70fe0eb 100644 --- a/tests/unit/settings-route-password.test.ts +++ b/tests/unit/settings-route-password.test.ts @@ -17,7 +17,7 @@ const managementPassword = await import("../../src/lib/auth/managementPassword.t async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); delete process.env.INITIAL_PASSWORD; } @@ -28,7 +28,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_INITIAL_PASSWORD === undefined) { delete process.env.INITIAL_PASSWORD; } else { diff --git a/tests/unit/shared/structuredLogger-raw-write-guard.test.ts b/tests/unit/shared/structuredLogger-raw-write-guard.test.ts index f0cf34997b8f..6bfbc1a19af9 100644 --- a/tests/unit/shared/structuredLogger-raw-write-guard.test.ts +++ b/tests/unit/shared/structuredLogger-raw-write-guard.test.ts @@ -89,7 +89,7 @@ test("error() with a destroyed stderr does not crash, and still writes to the lo "fatal() must still reach writeToFile after the stderr write is skipped" ); - rmSync(dir, { recursive: true, force: true }); + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // Guard against collateral damage: #1006's suppression policy must be untouched by this change. diff --git a/tests/unit/siliconflow-model-sync.test.ts b/tests/unit/siliconflow-model-sync.test.ts index 8b9835c94408..4daa52e29e30 100644 --- a/tests/unit/siliconflow-model-sync.test.ts +++ b/tests/unit/siliconflow-model-sync.test.ts @@ -21,7 +21,7 @@ type JsonBody = Record; async function resetStorage() { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -54,7 +54,7 @@ test.beforeEach(async () => { test.after(async () => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("sync-models rejects local catalog fallback and preserves existing SiliconFlow models", async () => { diff --git a/tests/unit/skills-builtins-sandbox.test.ts b/tests/unit/skills-builtins-sandbox.test.ts index a696d3bf7a63..b8991e959990 100644 --- a/tests/unit/skills-builtins-sandbox.test.ts +++ b/tests/unit/skills-builtins-sandbox.test.ts @@ -17,7 +17,7 @@ function makeTempDir(prefix) { } function removePath(targetPath) { - fs.rmSync(targetPath, { recursive: true, force: true }); + fs.rmSync(targetPath, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } async function importFresh(modulePath) { @@ -408,7 +408,7 @@ test("containerProvider: all five providers registered", () => { assert.ok(mod.ALL_PROVIDERS.length === 5); assert.deepStrictEqual( mod.ALL_PROVIDERS.map((p) => p.id), - ["docker", "apple", "wsl", "orbstack", "podman"], + ["docker", "apple", "wsl", "orbstack", "podman"] ); assert.ok(mod.PROVIDER_BY_ID.has("docker")); assert.ok(mod.PROVIDER_BY_ID.has("apple")); @@ -420,27 +420,15 @@ test("containerProvider: all five providers registered", () => { test("containerProvider: platformPriority returns correct order per OS", () => { return importFresh("src/lib/skills/containerProvider.ts").then((mod) => { - const originalPlatform = Object.getOwnPropertyDescriptor( - process, - "platform", - ); + const originalPlatform = Object.getOwnPropertyDescriptor(process, "platform"); // darwin Object.defineProperty(process, "platform", { value: "darwin" }); - assert.deepStrictEqual(mod.platformPriority(), [ - "apple", - "orbstack", - "podman", - "docker", - ]); + assert.deepStrictEqual(mod.platformPriority(), ["apple", "orbstack", "podman", "docker"]); // win32 Object.defineProperty(process, "platform", { value: "win32" }); - assert.deepStrictEqual(mod.platformPriority(), [ - "wsl", - "docker", - "podman", - ]); + assert.deepStrictEqual(mod.platformPriority(), ["wsl", "docker", "podman"]); // linux Object.defineProperty(process, "platform", { value: "linux" }); @@ -448,11 +436,7 @@ test("containerProvider: platformPriority returns correct order per OS", () => { // Restore if (originalPlatform) { - Object.defineProperty( - process, - "platform", - originalPlatform, - ); + Object.defineProperty(process, "platform", originalPlatform); } }); }); @@ -467,25 +451,10 @@ test("containerProvider: buildRun produces run as args[0] for all providers", () readOnly: true, }; for (const provider of mod.ALL_PROVIDERS) { - const resolved = provider.buildRun( - "alpine", - ["echo", "hi"], - "test-id", - config, - ); - assert.equal( - resolved.args[0], - "run", - `${provider.id}: args[0] must be "run"`, - ); - assert.ok( - resolved.args.includes("--rm"), - `${provider.id}: should include --rm`, - ); - assert.ok( - resolved.args.includes("alpine"), - `${provider.id}: should include image`, - ); + const resolved = provider.buildRun("alpine", ["echo", "hi"], "test-id", config); + assert.equal(resolved.args[0], "run", `${provider.id}: args[0] must be "run"`); + assert.ok(resolved.args.includes("--rm"), `${provider.id}: should include --rm`); + assert.ok(resolved.args.includes("alpine"), `${provider.id}: should include image`); // killArgs must return something callable const kill = resolved.killArgs("test-cont"); assert.ok(Array.isArray(kill), `${provider.id}: killArgs returns array`); @@ -555,9 +524,7 @@ test("containerProvider: resolveProvider falls back to docker when no runtime in // Auto-detect walks platform priority — if nothing is installed we // always land on docker as the fallback. const provider = await mod.resolveProvider(); - assert.ok( - ["docker", "apple", "wsl", "podman", "orbstack"].includes(provider.id), - ); + assert.ok(["docker", "apple", "wsl", "podman", "orbstack"].includes(provider.id)); // Ensure the fallback is always docker when probes fail // (this test is best-effort — on a host with docker installed, // the auto-detect will legitimately pick docker) diff --git a/tests/unit/skills-collect-routes.test.ts b/tests/unit/skills-collect-routes.test.ts index 9657937a841a..ae6132218393 100644 --- a/tests/unit/skills-collect-routes.test.ts +++ b/tests/unit/skills-collect-routes.test.ts @@ -56,7 +56,7 @@ test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ── Helpers ─────────────────────────────────────────────────────────────────── @@ -64,7 +64,7 @@ test.after(() => { async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); delete process.env.INITIAL_PASSWORD; } diff --git a/tests/unit/skills-executor.test.ts b/tests/unit/skills-executor.test.ts index 4acbaaae9ed7..99975c06b15f 100644 --- a/tests/unit/skills-executor.test.ts +++ b/tests/unit/skills-executor.test.ts @@ -23,7 +23,7 @@ function resetSkillsRuntime() { async function resetStorage() { resetSkillsRuntime(); coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -47,7 +47,7 @@ test.beforeEach(async () => { test.after(() => { resetSkillsRuntime(); coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("skillExecutor executes a registered handler and persists execution history", async () => { diff --git a/tests/unit/skills-injection.test.ts b/tests/unit/skills-injection.test.ts index 35344b9f3838..556b3d82279b 100644 --- a/tests/unit/skills-injection.test.ts +++ b/tests/unit/skills-injection.test.ts @@ -28,7 +28,7 @@ function resetRegistryState() { async function resetStorage() { resetRegistryState(); coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -60,7 +60,7 @@ test.beforeEach(async () => { test.after(() => { resetRegistryState(); coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("injectSkills renders enabled tools in provider-specific shapes", async () => { diff --git a/tests/unit/skills-interception.test.ts b/tests/unit/skills-interception.test.ts index 075b24452ae9..7c796bb50be4 100644 --- a/tests/unit/skills-interception.test.ts +++ b/tests/unit/skills-interception.test.ts @@ -25,7 +25,7 @@ function resetRuntime() { async function resetStorage() { resetRuntime(); coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -71,7 +71,7 @@ test.beforeEach(async () => { test.after(() => { resetRuntime(); coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("extractToolCalls supports OpenAI, Anthropic and Gemini shapes", () => { @@ -307,7 +307,10 @@ test("handleToolCallExecution intercepts a registered skill alongside an unregis }, { type: "tool_use", id: "tool-native", name: "Bash", input: { command: "ls" } }, ]); - assert.equal(mixed.content.some((b: { type: string }) => b.type === "tool_result"), false); + assert.equal( + mixed.content.some((b: { type: string }) => b.type === "tool_result"), + false + ); assert.equal(mixed.stop_reason, "tool_use"); }); @@ -337,7 +340,10 @@ test("handleToolCallExecution loads registry from DB on cold cache (covers loadF text: '[Skill result: lookup@1.0.0]\n{"record":"resolved:cold"}', }, ]); - assert.equal(result.content.some((b: { type: string }) => b.type === "tool_result"), false); + assert.equal( + result.content.some((b: { type: string }) => b.type === "tool_result"), + false + ); assert.equal(result.stop_reason, "end_turn"); assert.equal(result.stop_sequence, null); }); diff --git a/tests/unit/skills-marketplace.test.ts b/tests/unit/skills-marketplace.test.ts index 64fddb7cd988..ca63ce63dcb9 100644 --- a/tests/unit/skills-marketplace.test.ts +++ b/tests/unit/skills-marketplace.test.ts @@ -22,7 +22,7 @@ function clearSkillRegistry() { function resetStorage() { core.resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(tmpDir, { recursive: true }); clearSkillRegistry(); core.getDbInstance(); @@ -40,7 +40,7 @@ test.after(() => { core.resetDbInstance(); clearSkillRegistry(); process.env.DATA_DIR = originalDataDir; - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("SkillsMP installs are available to API-key-scoped requests", async () => { diff --git a/tests/unit/skills-memory-builtins.test.ts b/tests/unit/skills-memory-builtins.test.ts index aaabcdf0a0bf..0cf479891af0 100644 --- a/tests/unit/skills-memory-builtins.test.ts +++ b/tests/unit/skills-memory-builtins.test.ts @@ -32,7 +32,7 @@ test.beforeEach(() => { test.after(() => { coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("memory_save creates a new memory entry", async () => { @@ -210,9 +210,7 @@ test("interceptToolCalls executes memory tools when allowed via builtinToolNames test("interceptToolCalls skips memory tools not allowed by builtinToolNames", async () => { const results = await interceptToolCalls( - [ - { id: "call-x", name: MEMORY_DELETE_TOOL_NAME, arguments: { id: "anything" } }, - ], + [{ id: "call-x", name: MEMORY_DELETE_TOOL_NAME, arguments: { id: "anything" } }], { apiKeyId: "key-mem", sessionId: "session-mem", diff --git a/tests/unit/skills-registry.test.ts b/tests/unit/skills-registry.test.ts index 14e6a9c6a55d..af001d159ada 100644 --- a/tests/unit/skills-registry.test.ts +++ b/tests/unit/skills-registry.test.ts @@ -21,7 +21,7 @@ function resetRegistryState() { async function resetStorage() { resetRegistryState(); coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -32,7 +32,7 @@ test.beforeEach(async () => { test.after(() => { resetRegistryState(); coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("skillRegistry registers, lists, sorts and resolves versions", async () => { diff --git a/tests/unit/skills-routes-error-sanitization.test.ts b/tests/unit/skills-routes-error-sanitization.test.ts index 083ef363cc73..18238609fbaa 100644 --- a/tests/unit/skills-routes-error-sanitization.test.ts +++ b/tests/unit/skills-routes-error-sanitization.test.ts @@ -28,7 +28,7 @@ const LEAKY_PATH = "/home/testuser/.omniroute/skills/evil/handler.js"; function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -39,7 +39,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; if (ORIGINAL_OMNIROUTE_API_KEY === undefined) delete process.env.OMNIROUTE_API_KEY; diff --git a/tests/unit/skills-routes.test.ts b/tests/unit/skills-routes.test.ts index 0dc3ba150ef1..77bcf4f72797 100644 --- a/tests/unit/skills-routes.test.ts +++ b/tests/unit/skills-routes.test.ts @@ -23,7 +23,7 @@ function clearSkillRegistry() { function resetStorage() { core.resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(tmpDir, { recursive: true }); clearSkillRegistry(); core.getDbInstance(); @@ -60,7 +60,7 @@ test.after(() => { core.resetDbInstance(); clearSkillRegistry(); process.env.DATA_DIR = originalDataDir; - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("skills route GET loads skills from the database and lists them", async () => { diff --git a/tests/unit/skills-skillssh.test.ts b/tests/unit/skills-skillssh.test.ts index c5229e9b7978..b55f320e79ae 100644 --- a/tests/unit/skills-skillssh.test.ts +++ b/tests/unit/skills-skillssh.test.ts @@ -23,7 +23,7 @@ function clearSkillRegistry() { function resetStorage() { core.resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(tmpDir, { recursive: true }); clearSkillRegistry(); core.getDbInstance(); @@ -42,7 +42,7 @@ test.after(() => { clearSkillRegistry(); globalThis.fetch = originalFetch; process.env.DATA_DIR = originalDataDir; - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ── Zod schema validation tests ── diff --git a/tests/unit/sonar-quality-gate-fixes.test.ts b/tests/unit/sonar-quality-gate-fixes.test.ts index 1f393675019b..464aa6c05af3 100644 --- a/tests/unit/sonar-quality-gate-fixes.test.ts +++ b/tests/unit/sonar-quality-gate-fixes.test.ts @@ -29,7 +29,7 @@ test("classify-pr-changes rejects a list path that escapes the workspace", () => assert.match(res.stderr, /escapes the workspace/); fs.rmSync(outside, { force: true }); } finally { - fs.rmSync(cwd, { recursive: true, force: true }); + fs.rmSync(cwd, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -45,7 +45,7 @@ test("classify-pr-changes still accepts a workspace-relative list file", () => { assert.match(res.stdout, /docs=true/); assert.match(res.stdout, /code=false/); } finally { - fs.rmSync(cwd, { recursive: true, force: true }); + fs.rmSync(cwd, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/specialty-model-catalog-routes.test.ts b/tests/unit/specialty-model-catalog-routes.test.ts index 01ab26691cfe..db1bd6421100 100644 --- a/tests/unit/specialty-model-catalog-routes.test.ts +++ b/tests/unit/specialty-model-catalog-routes.test.ts @@ -18,7 +18,7 @@ const v1ModelsCatalog = await import("../../src/app/api/v1/models/catalog.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); // These routes all derive from the shared unified catalog (getUnifiedModelsResponse), // which #6408 wrapped in a 1.5s TTL response cache keyed only by (prefix, isCodex @@ -57,7 +57,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("image catalog GET uses the unified active-credential model list", async () => { diff --git a/tests/unit/specialty-model-hidden-openrouter-9293.test.ts b/tests/unit/specialty-model-hidden-openrouter-9293.test.ts index 4599b26909bd..360dfe3758d3 100644 --- a/tests/unit/specialty-model-hidden-openrouter-9293.test.ts +++ b/tests/unit/specialty-model-hidden-openrouter-9293.test.ts @@ -32,7 +32,7 @@ const v1ModelsCatalog = await import("../../src/app/api/v1/models/catalog.ts"); async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); v1ModelsCatalog.__resetCatalogBuilderRunsForTest(); } @@ -44,7 +44,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#9293 hidden OpenRouter specialty models are excluded from /v1/models catalog", async () => { diff --git a/tests/unit/spend-batch-writer.test.ts b/tests/unit/spend-batch-writer.test.ts index 29253602f6cd..88f0b67490df 100644 --- a/tests/unit/spend-batch-writer.test.ts +++ b/tests/unit/spend-batch-writer.test.ts @@ -28,7 +28,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: any) { @@ -60,7 +60,7 @@ test.after(async () => { resetSpendBatchWriterForTests(); costRules.resetCostData(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("spend batch writer auto-flushes at the configured threshold", async () => { diff --git a/tests/unit/sre-tcp-close-analyzer.test.ts b/tests/unit/sre-tcp-close-analyzer.test.ts index 0d4d6e332c8f..b660bfd1c066 100644 --- a/tests/unit/sre-tcp-close-analyzer.test.ts +++ b/tests/unit/sre-tcp-close-analyzer.test.ts @@ -229,7 +229,7 @@ function withTempPcap(fn: (pcapPath: string, tmpDir: string) => T): T { try { return fn(pcapPath, tmpDir); } finally { - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } diff --git a/tests/unit/sse-auth-antigravity-credits.test.ts b/tests/unit/sse-auth-antigravity-credits.test.ts index 6b038fb4b35c..4335fd9c08aa 100644 --- a/tests/unit/sse-auth-antigravity-credits.test.ts +++ b/tests/unit/sse-auth-antigravity-credits.test.ts @@ -17,7 +17,7 @@ const quotaCache = await import("../../src/domain/quotaCache.ts"); async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -28,7 +28,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("Antigravity always mode bypasses request-path quota preflight", async () => { diff --git a/tests/unit/sse-auth-codex-account-pool.test.ts b/tests/unit/sse-auth-codex-account-pool.test.ts index 927775d15f7c..59dce44cb0f7 100644 --- a/tests/unit/sse-auth-codex-account-pool.test.ts +++ b/tests/unit/sse-auth-codex-account-pool.test.ts @@ -14,7 +14,7 @@ const auth = await import("../../src/sse/services/auth.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -40,7 +40,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("Codex Spark preflight cooldown leaves normal models on the same parent selectable", async () => { diff --git a/tests/unit/sse-auth-exclusive-leases.test.ts b/tests/unit/sse-auth-exclusive-leases.test.ts index 6543e83f1c0f..9981d39ce230 100644 --- a/tests/unit/sse-auth-exclusive-leases.test.ts +++ b/tests/unit/sse-auth-exclusive-leases.test.ts @@ -68,7 +68,7 @@ async function resetStorage(): Promise { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); fallback.clearAllModelLockouts(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -76,7 +76,7 @@ test.beforeEach(resetStorage); test.after(() => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("foreign top candidate is skipped and the existing selector chooses the next free candidate", async () => { @@ -273,16 +273,10 @@ test("generic lease selection is provider-neutral across GLM and OpenAI fixtures ] as const) { const connection = await seedConnection(1, { provider }); const key = await seedManagedKey([connection.id]); - const selected = await auth.getProviderCredentials( - provider, - null, - [connection.id], - model, - { - lease: { apiKeyId: key.id, context: context(OWNERS[0], 1), mode: "acquire" }, - materializeCredentials: false, - } - ); + const selected = await auth.getProviderCredentials(provider, null, [connection.id], model, { + lease: { apiKeyId: key.id, context: context(OWNERS[0], 1), mode: "acquire" }, + materializeCredentials: false, + }); assert.equal(selected?.connectionId, connection.id, provider); leaseDb.releaseExclusiveConnectionLease({ leaseOwnerId: OWNERS[0], diff --git a/tests/unit/sse-auth-resource-404.test.ts b/tests/unit/sse-auth-resource-404.test.ts index 9980627c91b6..2537632fe098 100644 --- a/tests/unit/sse-auth-resource-404.test.ts +++ b/tests/unit/sse-auth-resource-404.test.ts @@ -14,7 +14,7 @@ const auth = await import("../../src/sse/services/auth.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("markAccountUnavailable preserves connection health for a missing Files API resource", async () => { diff --git a/tests/unit/sse-auth.test.ts b/tests/unit/sse-auth.test.ts index 0a18c865dd08..ce8032d20df8 100644 --- a/tests/unit/sse-auth.test.ts +++ b/tests/unit/sse-auth.test.ts @@ -20,7 +20,7 @@ const oauthOccupancy = await import("../../open-sse/services/oauthSessionOccupan async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -72,7 +72,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("extractApiKey parses bearer headers and isValidApiKey validates persisted keys", async () => { diff --git a/tests/unit/sse-comments-optout-9305.test.ts b/tests/unit/sse-comments-optout-9305.test.ts index 8928738bbee4..41d0e4e219e7 100644 --- a/tests/unit/sse-comments-optout-9305.test.ts +++ b/tests/unit/sse-comments-optout-9305.test.ts @@ -181,7 +181,7 @@ for (const upstreamDone of [true, false]) { test.after(() => { usageHistory.clearPendingRequests(); core.resetDbInstance(); - fs.rmSync(testDataDir, { recursive: true, force: true }); + fs.rmSync(testDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (previousDataDir === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = previousDataDir; }); diff --git a/tests/unit/sse-shim-contract.test.ts b/tests/unit/sse-shim-contract.test.ts index b27a500d4463..c59238bf87e9 100644 --- a/tests/unit/sse-shim-contract.test.ts +++ b/tests/unit/sse-shim-contract.test.ts @@ -26,7 +26,7 @@ function listProjectFiles(relativePath: string): string[] { } test.after(() => { - rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("src/sse model shim keeps parseModel behavior aligned with open-sse core", async () => { diff --git a/tests/unit/startup-stale-cooldown-recovery.test.ts b/tests/unit/startup-stale-cooldown-recovery.test.ts index 7dab54f2bb61..dee0d33c40de 100644 --- a/tests/unit/startup-stale-cooldown-recovery.test.ts +++ b/tests/unit/startup-stale-cooldown-recovery.test.ts @@ -32,7 +32,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: any) { @@ -52,7 +52,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ─── helpers ──────────────────────────────────────────────────────────────── diff --git a/tests/unit/sticky-affinity-failover-6219.test.ts b/tests/unit/sticky-affinity-failover-6219.test.ts index 44b8709b9f24..8d1d8ad535de 100644 --- a/tests/unit/sticky-affinity-failover-6219.test.ts +++ b/tests/unit/sticky-affinity-failover-6219.test.ts @@ -35,13 +35,13 @@ const TTL = 60_000; test.beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); }); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("evicts the sticky pin when the pinned connection fails over (#6219)", () => { diff --git a/tests/unit/stmt-cache-lru.test.ts b/tests/unit/stmt-cache-lru.test.ts index d50e1fdc78c3..8f4fca3c0edb 100644 --- a/tests/unit/stmt-cache-lru.test.ts +++ b/tests/unit/stmt-cache-lru.test.ts @@ -24,7 +24,7 @@ function cleanup() { delete process.env.DATA_DIR; } try { - fs.rmSync(tempDir, { recursive: true, force: true }); + fs.rmSync(tempDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch {} } @@ -50,9 +50,9 @@ test("statement cache handles 200+ unique SELECTs without errors (LRU eviction)" } // Verify the DB is still functional after eviction churn - const finalRow = db - .prepare("SELECT COUNT(*) AS cnt FROM stmt_cache_test") - .get() as { cnt: number }; + const finalRow = db.prepare("SELECT COUNT(*) AS cnt FROM stmt_cache_test").get() as { + cnt: number; + }; assert.equal(finalRow.cnt, 1, "table should still have 1 row after cache churn"); } finally { cleanup(); diff --git a/tests/unit/stream-claude-delta-contract.test.ts b/tests/unit/stream-claude-delta-contract.test.ts index a807d7267b88..79909ccf9b0f 100644 --- a/tests/unit/stream-claude-delta-contract.test.ts +++ b/tests/unit/stream-claude-delta-contract.test.ts @@ -13,7 +13,7 @@ const core = await import("../../src/lib/db/core.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(testDataDir, { recursive: true, force: true }); + fs.rmSync(testDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("createSSEStream ignores non-string Claude deltas before estimating usage", async () => { diff --git a/tests/unit/stream-impossible-input-usage.test.ts b/tests/unit/stream-impossible-input-usage.test.ts index 1716f6851db6..026f975c2c98 100644 --- a/tests/unit/stream-impossible-input-usage.test.ts +++ b/tests/unit/stream-impossible-input-usage.test.ts @@ -36,7 +36,7 @@ function parseSsePayloads(text: string): Array> { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("native Claude passthrough repairs impossible AgentRouter cache usage before forwarding", async () => { diff --git a/tests/unit/stream-non-json-sse.test.ts b/tests/unit/stream-non-json-sse.test.ts index 16776c45cfdf..7470e88bcfcc 100644 --- a/tests/unit/stream-non-json-sse.test.ts +++ b/tests/unit/stream-non-json-sse.test.ts @@ -41,7 +41,7 @@ async function readTransformed(chunks: string[], options: object): Promise { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -86,10 +86,7 @@ test("non-JSON data line (plain-text rate-limit message) is NOT forwarded to cli ); // Both valid JSON chunks must appear - assert.ok( - output.includes("chatcmpl-nonjson-1"), - `First valid chunk missing.\nOutput: ${output}` - ); + assert.ok(output.includes("chatcmpl-nonjson-1"), `First valid chunk missing.\nOutput: ${output}`); assert.ok( output.includes("chatcmpl-nonjson-2"), `Second valid chunk missing.\nOutput: ${output}` @@ -117,11 +114,7 @@ test("exactly one [DONE] emitted even when upstream sends a duplicate", async () test("valid JSON chunks pass through correctly in passthrough mode", async () => { const output = await readTransformed( - [ - `data: ${validChunk1}\n\n`, - `data: ${validChunk2}\n\n`, - "data: [DONE]\n\n", - ], + [`data: ${validChunk1}\n\n`, `data: ${validChunk2}\n\n`, "data: [DONE]\n\n"], PASSTHROUGH_OPTIONS ); diff --git a/tests/unit/stream-numeric-ids.test.ts b/tests/unit/stream-numeric-ids.test.ts index ce82ad9ce3d2..53271a74c9e8 100644 --- a/tests/unit/stream-numeric-ids.test.ts +++ b/tests/unit/stream-numeric-ids.test.ts @@ -29,7 +29,7 @@ async function readTransformed(chunks, options) { test.after(() => { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -232,7 +232,9 @@ test("createSSEStream responses passthrough coerces numeric ids to strings", asy assert.equal(typeof added.item.call_id, "string"); assert.equal(added.item.call_id, "654"); - const delta = payloads.find((payload) => payload.type === "response.function_call_arguments.delta"); + const delta = payloads.find( + (payload) => payload.type === "response.function_call_arguments.delta" + ); assert.equal(typeof delta.response_id, "string"); assert.equal(delta.response_id, "987"); assert.equal(typeof delta.item_id, "string"); diff --git a/tests/unit/stream-onfailure-callback-logging.test.ts b/tests/unit/stream-onfailure-callback-logging.test.ts index 8e9a0f767279..f3da4789bcd1 100644 --- a/tests/unit/stream-onfailure-callback-logging.test.ts +++ b/tests/unit/stream-onfailure-callback-logging.test.ts @@ -34,7 +34,12 @@ test.after(() => { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { for (const entry of fs.readdirSync(TEST_DATA_DIR)) { - fs.rmSync(path.join(TEST_DATA_DIR, entry), { recursive: true, force: true }); + fs.rmSync(path.join(TEST_DATA_DIR, entry), { + recursive: true, + force: true, + maxRetries: 5, + retryDelay: 100, + }); } } }); diff --git a/tests/unit/stream-prompt-tokens-zero-upstream.test.ts b/tests/unit/stream-prompt-tokens-zero-upstream.test.ts index dc579dc2b003..0e0404c5c902 100644 --- a/tests/unit/stream-prompt-tokens-zero-upstream.test.ts +++ b/tests/unit/stream-prompt-tokens-zero-upstream.test.ts @@ -108,7 +108,12 @@ test.after(() => { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { for (const entry of fs.readdirSync(TEST_DATA_DIR)) { - fs.rmSync(path.join(TEST_DATA_DIR, entry), { recursive: true, force: true }); + fs.rmSync(path.join(TEST_DATA_DIR, entry), { + recursive: true, + force: true, + maxRetries: 5, + retryDelay: 100, + }); } } }); diff --git a/tests/unit/stream-request-body-size-mark-7045.test.ts b/tests/unit/stream-request-body-size-mark-7045.test.ts index b033be2085e3..0b544bbb47fd 100644 --- a/tests/unit/stream-request-body-size-mark-7045.test.ts +++ b/tests/unit/stream-request-body-size-mark-7045.test.ts @@ -11,9 +11,7 @@ import os from "node:os"; import path from "node:path"; import { performance } from "node:perf_hooks"; -const TEST_DATA_DIR = fs.mkdtempSync( - path.join(os.tmpdir(), "omniroute-stream-body-size-mark-") -); +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-stream-body-size-mark-")); process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); @@ -56,7 +54,7 @@ async function drainSSEStream(options) { test.after(() => { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/stream-utils.test.ts b/tests/unit/stream-utils.test.ts index 46d7959be0cc..93587ad29502 100644 --- a/tests/unit/stream-utils.test.ts +++ b/tests/unit/stream-utils.test.ts @@ -147,7 +147,12 @@ test.after(() => { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { for (const entry of fs.readdirSync(TEST_DATA_DIR)) { - fs.rmSync(path.join(TEST_DATA_DIR, entry), { recursive: true, force: true }); + fs.rmSync(path.join(TEST_DATA_DIR, entry), { + recursive: true, + force: true, + maxRetries: 5, + retryDelay: 100, + }); } } }); diff --git a/tests/unit/streamingPiiTransform.test.ts b/tests/unit/streamingPiiTransform.test.ts index f28a31aa33b8..9fb515add28c 100644 --- a/tests/unit/streamingPiiTransform.test.ts +++ b/tests/unit/streamingPiiTransform.test.ts @@ -490,7 +490,7 @@ test.after(async () => { const coreDb = await import("../../src/lib/db/core.ts"); coreDb.resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("createPiiSseTransform preserves tool call arguments without buffering", async () => { diff --git a/tests/unit/strict-random-deck.test.ts b/tests/unit/strict-random-deck.test.ts index 186bf75e36b2..8c7c3314666e 100644 --- a/tests/unit/strict-random-deck.test.ts +++ b/tests/unit/strict-random-deck.test.ts @@ -16,7 +16,12 @@ test.after(() => { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { for (const entry of fs.readdirSync(TEST_DATA_DIR)) { - fs.rmSync(path.join(TEST_DATA_DIR, entry), { recursive: true, force: true }); + fs.rmSync(path.join(TEST_DATA_DIR, entry), { + recursive: true, + force: true, + maxRetries: 5, + retryDelay: 100, + }); } } }); diff --git a/tests/unit/suggested-models-route.test.ts b/tests/unit/suggested-models-route.test.ts index bca74018383d..9b0a218106f7 100644 --- a/tests/unit/suggested-models-route.test.ts +++ b/tests/unit/suggested-models-route.test.ts @@ -37,7 +37,7 @@ test.afterEach(() => { test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; } else { diff --git a/tests/unit/sync-bundle.test.ts b/tests/unit/sync-bundle.test.ts index 24215d022697..12e508b00221 100644 --- a/tests/unit/sync-bundle.test.ts +++ b/tests/unit/sync-bundle.test.ts @@ -22,7 +22,7 @@ const syncBundle = await import("../../src/lib/sync/bundle.ts"); function resetStorage() { apiKeysDb.resetApiKeyState(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -33,7 +33,7 @@ test.beforeEach(() => { test.after(() => { apiKeysDb.resetApiKeyState(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; diff --git a/tests/unit/sync-env-bundled-require-5006.test.ts b/tests/unit/sync-env-bundled-require-5006.test.ts index 3082cbf55123..516dda1950b1 100644 --- a/tests/unit/sync-env-bundled-require-5006.test.ts +++ b/tests/unit/sync-env-bundled-require-5006.test.ts @@ -97,6 +97,6 @@ test("#5006: getEnvSyncPlan(oauth) works with explicit rootDir and never throws assert.deepEqual(keys.sort(), ["CLAUDE_OAUTH_CLIENT_ID", "CODEX_OAUTH_CLIENT_ID"]); } finally { process.env.DATA_DIR = origDataDir; - fs.rmSync(rootDir, { recursive: true, force: true }); + fs.rmSync(rootDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/sync-env.test.ts b/tests/unit/sync-env.test.ts index 6804e0917af9..8fe04db1ee4c 100644 --- a/tests/unit/sync-env.test.ts +++ b/tests/unit/sync-env.test.ts @@ -81,7 +81,7 @@ test("syncEnv creates .env from .env.example and leaves runtime-owned secrets bl assert.doesNotMatch(envContent, /^COMMENTED_KEY=/m); } finally { process.env.DATA_DIR = origDataDir; - fs.rmSync(rootDir, { recursive: true, force: true }); + fs.rmSync(rootDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -116,7 +116,7 @@ test("syncEnv appends only missing keys and preserves existing values", () => { assert.match(envContent, /Auto-added by sync-env/); } finally { process.env.DATA_DIR = origDataDir; - fs.rmSync(rootDir, { recursive: true, force: true }); + fs.rmSync(rootDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -147,7 +147,7 @@ test("syncEnv treats quoted and unquoted values as equivalent", () => { assert.deepEqual(result, { created: false, added: 0 }); } finally { process.env.DATA_DIR = origDataDir; - fs.rmSync(rootDir, { recursive: true, force: true }); + fs.rmSync(rootDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -168,7 +168,7 @@ test("syncEnv is idempotent when .env is already complete", () => { assert.equal(after, before); } finally { process.env.DATA_DIR = origDataDir; - fs.rmSync(rootDir, { recursive: true, force: true }); + fs.rmSync(rootDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -187,6 +187,6 @@ test("syncEnv oauth scope only copies oauth defaults", () => { assert.doesNotMatch(envContent, /^JWT_SECRET=/m); assert.doesNotMatch(envContent, /^Provider User-Agent Overrides/m); } finally { - fs.rmSync(rootDir, { recursive: true, force: true }); + fs.rmSync(rootDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/sync-reasoning-supported-efforts-7694.test.ts b/tests/unit/sync-reasoning-supported-efforts-7694.test.ts index f81440ea1062..2f348d31a40e 100644 --- a/tests/unit/sync-reasoning-supported-efforts-7694.test.ts +++ b/tests/unit/sync-reasoning-supported-efforts-7694.test.ts @@ -24,22 +24,20 @@ const providersDb = await import("../../src/lib/db/providers.ts"); const modelsDb = await import("../../src/lib/db/models.ts"); const v1ModelsCatalog = await import("../../src/app/api/v1/models/catalog.ts"); const { getModelInfo } = await import("../../src/sse/services/model.ts"); -const { normalizeDiscoveredModels, detectSupportedThinkingEfforts } = await import( - "../../src/lib/providerModels/modelDiscovery.ts" -); +const { normalizeDiscoveredModels, detectSupportedThinkingEfforts } = + await import("../../src/lib/providerModels/modelDiscovery.ts"); const { splitSyncedEffortSuffix } = await import("../../open-sse/services/model.ts"); const { appendSyncedEffortVariants, shouldExposeSyncedEffortVariants, SYNCED_EFFORT_SKIP_PROVIDERS, } = await import("../../open-sse/utils/syncedEffortVariants.ts"); -const { applyDefaultReasoningEffort } = await import( - "../../open-sse/services/defaultReasoningEffort.ts" -); +const { applyDefaultReasoningEffort } = + await import("../../open-sse/services/defaultReasoningEffort.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -50,7 +48,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); async function seedProviderConnection(provider: string) { diff --git a/tests/unit/sync-routes.test.ts b/tests/unit/sync-routes.test.ts index c12c0756ca49..f863c04f6b2b 100644 --- a/tests/unit/sync-routes.test.ts +++ b/tests/unit/sync-routes.test.ts @@ -25,7 +25,7 @@ const localDb = await import("../../src/lib/localDb.ts"); function resetStorage() { apiKeysDb.resetApiKeyState(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -37,7 +37,7 @@ test.beforeEach(async () => { test.after(() => { apiKeysDb.resetApiKeyState(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; diff --git a/tests/unit/synced-effort-suffix-learned-validation.test.ts b/tests/unit/synced-effort-suffix-learned-validation.test.ts index a06ff2d479e8..2e3d810ca268 100644 --- a/tests/unit/synced-effort-suffix-learned-validation.test.ts +++ b/tests/unit/synced-effort-suffix-learned-validation.test.ts @@ -25,7 +25,7 @@ const { recordLearnedReasoningEffort, __test_resetLearnedReasoningEffortCaps } = async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -55,7 +55,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("-max resolves once the learned set advertises it (sync metadata does not)", async () => { diff --git a/tests/unit/synced-model-context-window-reconcile.test.ts b/tests/unit/synced-model-context-window-reconcile.test.ts index ddf629c34ff0..c9623085a9be 100644 --- a/tests/unit/synced-model-context-window-reconcile.test.ts +++ b/tests/unit/synced-model-context-window-reconcile.test.ts @@ -39,7 +39,7 @@ type ReconcileDeps = resolver.ReconcileDeps; function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -49,7 +49,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // --------------------------------------------------------------------------- diff --git a/tests/unit/synced-model-delete-custom-sibling.test.ts b/tests/unit/synced-model-delete-custom-sibling.test.ts index 5fb31716df45..ad5102359df5 100644 --- a/tests/unit/synced-model-delete-custom-sibling.test.ts +++ b/tests/unit/synced-model-delete-custom-sibling.test.ts @@ -32,7 +32,7 @@ test.after(() => { // Release the SQLite handle so the Node test runner can exit, then remove the // throwaway DATA_DIR (CLAUDE.md "Database Handles in Tests"). core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); /** Invoke the real DELETE handler so the test tracks production behavior. */ diff --git a/tests/unit/synced-model-delete-resync.test.ts b/tests/unit/synced-model-delete-resync.test.ts index d1b711540b01..4aaf79b1671d 100644 --- a/tests/unit/synced-model-delete-resync.test.ts +++ b/tests/unit/synced-model-delete-resync.test.ts @@ -23,7 +23,7 @@ before(() => { after(() => { resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("a deleted synced model is restored when upstream advertises it again", async () => { diff --git a/tests/unit/synced-model-hide-persist-3782.test.ts b/tests/unit/synced-model-hide-persist-3782.test.ts index 400a87fa0192..e85ef0973b09 100644 --- a/tests/unit/synced-model-hide-persist-3782.test.ts +++ b/tests/unit/synced-model-hide-persist-3782.test.ts @@ -35,7 +35,7 @@ after(() => { // Release the SQLite handle so the Node test runner can exit, then remove the // throwaway DATA_DIR (CLAUDE.md "Database Handles in Tests"). resetDbInstance(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); const PROVIDER = "llama-cpp"; diff --git a/tests/unit/system-trust-test-guard.test.ts b/tests/unit/system-trust-test-guard.test.ts index 969c3588f8c3..36d55a29271d 100644 --- a/tests/unit/system-trust-test-guard.test.ts +++ b/tests/unit/system-trust-test-guard.test.ts @@ -37,6 +37,6 @@ test("installCert under the guard skips the OS mutation but keeps input contract try { await installCert("", pem); } finally { - fs.rmSync(dir, { recursive: true, force: true }); + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/t07-no-log-key-config.test.ts b/tests/unit/t07-no-log-key-config.test.ts index 9ce5ded52d76..c1513793efbf 100644 --- a/tests/unit/t07-no-log-key-config.test.ts +++ b/tests/unit/t07-no-log-key-config.test.ts @@ -23,7 +23,7 @@ const schemas = await import("../../src/shared/validation/schemas.ts"); async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -34,7 +34,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (originalPiiEnabled === undefined) { delete process.env.PII_RESPONSE_SANITIZATION; diff --git a/tests/unit/t08-allowed-connections.test.ts b/tests/unit/t08-allowed-connections.test.ts index 0eedb92be4c3..6715da654d08 100644 --- a/tests/unit/t08-allowed-connections.test.ts +++ b/tests/unit/t08-allowed-connections.test.ts @@ -20,7 +20,7 @@ const ROOT_DIR = path.resolve(__dirname, "../.."); async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -31,7 +31,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ══════════════════════════════════════════════════════════════════ diff --git a/tests/unit/tag-routing.test.ts b/tests/unit/tag-routing.test.ts index c87f929dc70f..5cb596f8fc5a 100644 --- a/tests/unit/tag-routing.test.ts +++ b/tests/unit/tag-routing.test.ts @@ -36,7 +36,7 @@ function okResponse(content: string) { async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -57,7 +57,7 @@ test.beforeEach(async () => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("tag router normalizes request metadata and matches connection tags", () => { diff --git a/tests/unit/tailscaleTunnel.test.ts b/tests/unit/tailscaleTunnel.test.ts index 43b168277c4f..171962702dea 100644 --- a/tests/unit/tailscaleTunnel.test.ts +++ b/tests/unit/tailscaleTunnel.test.ts @@ -82,7 +82,7 @@ test.beforeEach(async () => { resetTailscaleTestEnv(fakeBinaryPath); mitmManager.clearCachedPassword(); dbCore.resetDbInstance(); - await fs.rm(TEST_DATA_DIR, { recursive: true, force: true }); + await fs.rm(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); await fs.mkdir(TEST_DATA_DIR, { recursive: true }); const recreatedBinaryPath = await createFakeTailscaleBinary(); resetTailscaleTestEnv(recreatedBinaryPath); @@ -108,7 +108,7 @@ test.after(async () => { else process.env.TAILSCALE_TEST_LOGIN_OUTPUT = originalEnv.loginOutput; if (originalEnv.loginExitCode === undefined) delete process.env.TAILSCALE_TEST_LOGIN_EXIT_CODE; else process.env.TAILSCALE_TEST_LOGIN_EXIT_CODE = originalEnv.loginExitCode; - await fs.rm(TEST_DATA_DIR, { recursive: true, force: true }); + await fs.rm(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("extractTailscaleAuthUrl and extractTailscaleEnableUrl parse login URLs", () => { diff --git a/tests/unit/telemetry-auto-cleanup-6848.test.ts b/tests/unit/telemetry-auto-cleanup-6848.test.ts index bb1ae033f4be..1858dba9eede 100644 --- a/tests/unit/telemetry-auto-cleanup-6848.test.ts +++ b/tests/unit/telemetry-auto-cleanup-6848.test.ts @@ -44,7 +44,7 @@ const { getDbInstance, resetDbInstance } = await import("../../src/lib/db/core.t // or the native test runner can hang indefinitely on a dangling connection. test.after(() => { resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); const DAY_MS = 86_400_000; diff --git a/tests/unit/terminal-status-origin.test.ts b/tests/unit/terminal-status-origin.test.ts index 3a9221f2cab5..c30076d22dd1 100644 --- a/tests/unit/terminal-status-origin.test.ts +++ b/tests/unit/terminal-status-origin.test.ts @@ -13,7 +13,7 @@ const { writeTerminalStatus } = await import("../../src/shared/utils/terminalSta test.after(() => { core.resetDbInstance(); - fs.rmSync(DIR, { recursive: true, force: true }); + fs.rmSync(DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function row(id: string): { is_active: number; test_status: string } { diff --git a/tests/unit/termux-android-cache-dir.test.ts b/tests/unit/termux-android-cache-dir.test.ts index a99c197ec7be..66d15525d10a 100644 --- a/tests/unit/termux-android-cache-dir.test.ts +++ b/tests/unit/termux-android-cache-dir.test.ts @@ -89,7 +89,7 @@ test("ensureAndroidCacheDir: creates ~/.cache when missing on android", () => { assert.equal(existsSync(cacheDir), true); assert.equal(env.XDG_CACHE_HOME, cacheDir); } finally { - rmSync(home, { recursive: true, force: true }); + rmSync(home, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -114,7 +114,7 @@ test("ensureAndroidCacheDir: does not recreate when ~/.cache already exists", () assert.equal(mkdirCalls, 0); assert.equal(env.XDG_CACHE_HOME, cacheDir); } finally { - rmSync(home, { recursive: true, force: true }); + rmSync(home, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -133,7 +133,7 @@ test("ensureAndroidCacheDir: respects an existing XDG_CACHE_HOME and creates tha assert.equal(existsSync(xdg), true); assert.equal(env.XDG_CACHE_HOME, xdg); } finally { - rmSync(root, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -150,7 +150,7 @@ test("ensureAndroidCacheDir: Termux-on-linux still prepares ~/.cache", () => { assert.equal(result.prepared, true); assert.equal(existsSync(join(home, ".cache")), true); } finally { - rmSync(home, { recursive: true, force: true }); + rmSync(home, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/thinking-budget-hydration-5312.test.ts b/tests/unit/thinking-budget-hydration-5312.test.ts index 2eca49927179..63b4bccc753e 100644 --- a/tests/unit/thinking-budget-hydration-5312.test.ts +++ b/tests/unit/thinking-budget-hydration-5312.test.ts @@ -33,7 +33,7 @@ test.afterEach(() => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#5312 RC-A: persisted thinkingBudget mode is restored at boot", async () => { diff --git a/tests/unit/tier-config-provider-override-route.test.ts b/tests/unit/tier-config-provider-override-route.test.ts index 93852d6e265b..f3d4b704627e 100644 --- a/tests/unit/tier-config-provider-override-route.test.ts +++ b/tests/unit/tier-config-provider-override-route.test.ts @@ -23,7 +23,7 @@ const route = await import("../../src/app/api/settings/tier-config/route.ts"); function resetStorage() { delete process.env.INITIAL_PASSWORD; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -33,7 +33,7 @@ test.beforeEach(() => { test.after(() => { resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function putRequest(body: unknown) { @@ -116,13 +116,22 @@ test("route round-trips cleanly against an already-populated tier_config table ( const getRes = (await route.GET(getRequest())) as Response; assert.equal(getRes.status, 200); const getBody = await getRes.json(); - assert.ok(Array.isArray(getBody.freeProviders), "should still expose the DEFAULT_TIER_CONFIG shape"); - assert.deepEqual(getBody.providerOverrides, [{ provider: "pre-existing-provider", tier: "cheap" }]); + assert.ok( + Array.isArray(getBody.freeProviders), + "should still expose the DEFAULT_TIER_CONFIG shape" + ); + assert.deepEqual(getBody.providerOverrides, [ + { provider: "pre-existing-provider", tier: "cheap" }, + ]); const putRes = (await route.PUT( putRequest({ provider: "my-custom-endpoint-999", tier: "free" }) )) as Response; - assert.equal(putRes.status, 200, "PUT should round-trip without error against a pre-populated row"); + assert.equal( + putRes.status, + 200, + "PUT should round-trip without error against a pre-populated row" + ); const putBody = await putRes.json(); assert.deepEqual(putBody.providerOverrides, [ { provider: "pre-existing-provider", tier: "cheap" }, diff --git a/tests/unit/tier-resolver-provider-override.test.ts b/tests/unit/tier-resolver-provider-override.test.ts index 295c0e899c88..eca6cac7fad8 100644 --- a/tests/unit/tier-resolver-provider-override.test.ts +++ b/tests/unit/tier-resolver-provider-override.test.ts @@ -21,7 +21,7 @@ const tierResolver = await import("../../open-sse/services/tierResolver.ts"); function resetStorage() { delete process.env.INITIAL_PASSWORD; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); // classifyTier() caches by provider::model — reset the routing-side config // too so tests don't leak assignments across each other. @@ -34,7 +34,7 @@ test.beforeEach(() => { test.after(() => { resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function putRequest(body: unknown) { diff --git a/tests/unit/token-health-check-cursor.test.ts b/tests/unit/token-health-check-cursor.test.ts index 75eb4066d11c..f05933e2bb3e 100644 --- a/tests/unit/token-health-check-cursor.test.ts +++ b/tests/unit/token-health-check-cursor.test.ts @@ -40,7 +40,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: unknown) { @@ -60,7 +60,7 @@ async function resetStorage() { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function getId(connection: { id?: unknown }): string { @@ -172,7 +172,7 @@ async function withCursorEnv(fn: (env: CursorEnv) => Promise): Promise else delete process.env.USERPROFILE; delete process.env.FAKE_CURSOR_AGENT_LOG; delete process.env.FAKE_CURSOR_AGENT_STATUS_MODE; - fs.rmSync(tmpHome, { recursive: true, force: true }); + fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }, }; diff --git a/tests/unit/token-health-check-devin-cli-8407.test.ts b/tests/unit/token-health-check-devin-cli-8407.test.ts index 312b61df6557..ab3b71ef0dc3 100644 --- a/tests/unit/token-health-check-devin-cli-8407.test.ts +++ b/tests/unit/token-health-check-devin-cli-8407.test.ts @@ -20,7 +20,7 @@ const { supportsTokenRefresh } = await import("../../open-sse/services/tokenRefr async function resetStorage() { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -32,7 +32,7 @@ function getCreatedConnectionId(connection: { id?: unknown }): string { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("supportsTokenRefresh excludes import-only Devin providers", () => { diff --git a/tests/unit/token-health-check-retry-deactivation.test.ts b/tests/unit/token-health-check-retry-deactivation.test.ts index 2a99b35e65e1..77f544694654 100644 --- a/tests/unit/token-health-check-retry-deactivation.test.ts +++ b/tests/unit/token-health-check-retry-deactivation.test.ts @@ -39,7 +39,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: unknown) { @@ -115,7 +115,7 @@ async function createRetryTestConnection(overrides: Record = {} test.after(async () => { core.resetDbInstance(); try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // ignore cleanup errors } diff --git a/tests/unit/token-health-check-sweep.test.ts b/tests/unit/token-health-check-sweep.test.ts index afce89cde80f..00b0668653da 100644 --- a/tests/unit/token-health-check-sweep.test.ts +++ b/tests/unit/token-health-check-sweep.test.ts @@ -43,7 +43,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error) { @@ -60,7 +60,7 @@ async function resetStorage() { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); delete process.env.HEALTHCHECK_STAGGER_MS; delete process.env.HEALTHCHECK_JITTER_MIN_MS; delete process.env.HEALTHCHECK_JITTER_MAX_MS; diff --git a/tests/unit/token-health-check.test.ts b/tests/unit/token-health-check.test.ts index 274be4c1261c..8605c1e52bae 100644 --- a/tests/unit/token-health-check.test.ts +++ b/tests/unit/token-health-check.test.ts @@ -23,7 +23,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: any) { @@ -227,7 +227,7 @@ async function withPatchedProvider(providerId, config, fn) { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("extractResolvedProxyConfig unwraps proxy resolution metadata", () => { diff --git a/tests/unit/token-health-no-refresh-token-expired-5326.test.ts b/tests/unit/token-health-no-refresh-token-expired-5326.test.ts index ad880d725fc5..2b9db5f21add 100644 --- a/tests/unit/token-health-no-refresh-token-expired-5326.test.ts +++ b/tests/unit/token-health-no-refresh-token-expired-5326.test.ts @@ -18,7 +18,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: unknown) { @@ -43,7 +43,7 @@ function getCreatedConnectionId(connection: { id?: unknown }): string { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // Regression for #5326: a refresh-CAPABLE provider (antigravity) with NO refresh diff --git a/tests/unit/token-limits.test.ts b/tests/unit/token-limits.test.ts index aeb802626e7e..cd7900dc3a6d 100644 --- a/tests/unit/token-limits.test.ts +++ b/tests/unit/token-limits.test.ts @@ -28,7 +28,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: any) { @@ -78,7 +78,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("window rollover: daily/weekly/monthly produce distinct windowStart", async () => { @@ -151,7 +151,14 @@ test("seed-on-miss equals usage_history SUM for the active window", async () => // Different month (excluded). insertUsage("k2", "openai", "gpt-4o", 999, 999, new Date(Date.UTC(2025, 11, 31)).toISOString()); // Different model (excluded). - insertUsage("k2", "openai", "gpt-4o-mini", 777, 777, new Date(Date.UTC(2026, 0, 13)).toISOString()); + insertUsage( + "k2", + "openai", + "gpt-4o-mini", + 777, + 777, + new Date(Date.UTC(2026, 0, 13)).toISOString() + ); const expected = 100 + 50 + 30 + 20; assert.equal(counter.seedWindowUsageFromHistory(limit, NOW_JAN), expected); @@ -194,11 +201,19 @@ test("seed total excludes cache tokens (no double-count) (FIX 2)", async () => { // tokens_input ALREADY INCLUDES cache_read + cache_creation (these columns are a // breakdown, per migration 012). Billable = input + output + reasoning ONLY. - insertUsage("k2c", "anthropic", "claude-sonnet", 500, 200, new Date(Date.UTC(2026, 0, 12)).toISOString(), { - cacheRead: 300, - cacheCreation: 100, - reasoning: 40, - }); + insertUsage( + "k2c", + "anthropic", + "claude-sonnet", + 500, + 200, + new Date(Date.UTC(2026, 0, 12)).toISOString(), + { + cacheRead: 300, + cacheCreation: 100, + reasoning: 40, + } + ); // 500 + 200 + 40 = 740. Must NOT add cacheRead/cacheCreation again (would be 1140). assert.equal(counter.seedWindowUsageFromHistory(limit, NOW_JAN), 740); diff --git a/tests/unit/token-refresh-route-service.test.ts b/tests/unit/token-refresh-route-service.test.ts index 0a71e72c81ad..89093cf0119a 100644 --- a/tests/unit/token-refresh-route-service.test.ts +++ b/tests/unit/token-refresh-route-service.test.ts @@ -25,7 +25,7 @@ function jsonResponse(body, status = 200) { async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -154,7 +154,7 @@ test.beforeEach(async () => { test.after(async () => { delete PROVIDERS["custom-oauth-local-608"]; await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("token refresh wrapper delegates provider-specific refresh helpers and formatter utilities", async () => { diff --git a/tests/unit/tokenHealthCheck-batchSize.test.ts b/tests/unit/tokenHealthCheck-batchSize.test.ts index c6a088b3f362..0a5cb5033b3a 100644 --- a/tests/unit/tokenHealthCheck-batchSize.test.ts +++ b/tests/unit/tokenHealthCheck-batchSize.test.ts @@ -36,7 +36,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error) { @@ -55,7 +55,7 @@ test.after(() => { core.resetDbInstance(); try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } } catch { /* best effort cleanup */ diff --git a/tests/unit/transform-stream-hwm.test.ts b/tests/unit/transform-stream-hwm.test.ts index 93e94d037852..87229e623ad3 100644 --- a/tests/unit/transform-stream-hwm.test.ts +++ b/tests/unit/transform-stream-hwm.test.ts @@ -24,7 +24,7 @@ function cleanupDb() { delete process.env.DATA_DIR; } try { - fs.rmSync(tempDir, { recursive: true, force: true }); + fs.rmSync(tempDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch {} } diff --git a/tests/unit/tunnel-routes-error-sanitization.test.ts b/tests/unit/tunnel-routes-error-sanitization.test.ts index 2e5036e9ea09..2643af5b01d6 100644 --- a/tests/unit/tunnel-routes-error-sanitization.test.ts +++ b/tests/unit/tunnel-routes-error-sanitization.test.ts @@ -50,7 +50,7 @@ const tailscaleEnableRoute = await import("../../src/app/api/tunnels/tailscale/e test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; }); diff --git a/tests/unit/turbopack-cache-heal-6289.test.ts b/tests/unit/turbopack-cache-heal-6289.test.ts index 0a30882fbea4..375f008bba43 100644 --- a/tests/unit/turbopack-cache-heal-6289.test.ts +++ b/tests/unit/turbopack-cache-heal-6289.test.ts @@ -66,7 +66,7 @@ test("purgeTurbopackCache removes an existing cache/turbopack dir", () => { assert.equal(removed, true); assert.equal(fs.existsSync(cacheDir), false); - fs.rmSync(tmp, { recursive: true, force: true }); + fs.rmSync(tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("purgeTurbopackCache is a no-op (returns false) when the dir is absent", () => { diff --git a/tests/unit/upstream-ca-test-route-3488.test.ts b/tests/unit/upstream-ca-test-route-3488.test.ts index 750345534ebc..1ff10e94f8bd 100644 --- a/tests/unit/upstream-ca-test-route-3488.test.ts +++ b/tests/unit/upstream-ca-test-route-3488.test.ts @@ -47,8 +47,8 @@ fs.writeFileSync(validCaPath, TEST_CA_PEM); fs.writeFileSync(nonPemPath, "this is not a certificate"); test.after(() => { - fs.rmSync(dir, { recursive: true, force: true }); - fs.rmSync(DATA_DIR, { recursive: true, force: true }); + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.rmSync(DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function postJson(body: unknown): Request { diff --git a/tests/unit/usage-account-analytics-route.test.ts b/tests/unit/usage-account-analytics-route.test.ts index 42108323d8e4..f05a40342d0d 100644 --- a/tests/unit/usage-account-analytics-route.test.ts +++ b/tests/unit/usage-account-analytics-route.test.ts @@ -34,14 +34,14 @@ async function readAccounts() { test.beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); usageHistory.clearPendingRequests(); }); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("Codex account grouping follows workspace and user identity, not email", async () => { diff --git a/tests/unit/usage-analytics-model-dedup-7535.test.ts b/tests/unit/usage-analytics-model-dedup-7535.test.ts index 5d55c19f4a77..460d2d05435a 100644 --- a/tests/unit/usage-analytics-model-dedup-7535.test.ts +++ b/tests/unit/usage-analytics-model-dedup-7535.test.ts @@ -24,7 +24,7 @@ function makeRequest(url: string) { test.beforeEach(() => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); usageHistory.clearPendingRequests(); }); @@ -32,7 +32,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_API_KEY_SECRET === undefined) { delete process.env.API_KEY_SECRET; @@ -48,7 +48,18 @@ test("#7535: byModel must not list the same logical model twice under one raw/on db.prepare( `INSERT INTO usage_history (provider, model, connection_id, api_key_id, api_key_name, tokens_input, tokens_output, success, latency_ms, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` - ).run("zai", "glm-5.2", "test-conn", "test-key", "Primary Key", 100, 50, 1, 200, now.toISOString()); + ).run( + "zai", + "glm-5.2", + "test-conn", + "test-key", + "Primary Key", + 100, + 50, + 1, + 200, + now.toISOString() + ); db.prepare( `INSERT INTO usage_history (provider, model, connection_id, api_key_id, api_key_name, tokens_input, tokens_output, success, latency_ms, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` @@ -78,5 +89,9 @@ test("#7535: byModel must not list the same logical model twice under one raw/on 1, `expected exactly one "glm-5.2" row in byModel, got ${glmEntries.length}: ${JSON.stringify(glmEntries)} (#7535)` ); - assert.equal(glmEntries[0].requests, 2, "the two raw spellings should merge into one aggregated row"); + assert.equal( + glmEntries[0].requests, + 2, + "the two raw spellings should merge into one aggregated row" + ); }); diff --git a/tests/unit/usage-analytics-provider-display-name-7534.test.ts b/tests/unit/usage-analytics-provider-display-name-7534.test.ts index e060f7eb587a..9880c86f28f8 100644 --- a/tests/unit/usage-analytics-provider-display-name-7534.test.ts +++ b/tests/unit/usage-analytics-provider-display-name-7534.test.ts @@ -24,7 +24,7 @@ function makeRequest(url: string) { test.beforeEach(() => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); usageHistory.clearPendingRequests(); }); @@ -32,7 +32,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_API_KEY_SECRET === undefined) { delete process.env.API_KEY_SECRET; diff --git a/tests/unit/usage-analytics-route.test.ts b/tests/unit/usage-analytics-route.test.ts index 8e57e7e88b1b..ed897b6733c2 100644 --- a/tests/unit/usage-analytics-route.test.ts +++ b/tests/unit/usage-analytics-route.test.ts @@ -22,7 +22,7 @@ const EXPECTED_TOTAL_COST = 0.020925; async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); clearPendingRequests(); } @@ -76,7 +76,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_API_KEY_SECRET === undefined) { delete process.env.API_KEY_SECRET; diff --git a/tests/unit/usage-analytics.test.ts b/tests/unit/usage-analytics.test.ts index fa6c9696490b..e0c1d2509225 100644 --- a/tests/unit/usage-analytics.test.ts +++ b/tests/unit/usage-analytics.test.ts @@ -24,7 +24,7 @@ const clearPendingRequests = usageHistory.clearPendingRequests; async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); clearPendingRequests(); } @@ -54,7 +54,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("usage history persists entries and supports filtering and usageDb compatibility", async () => { @@ -529,16 +529,20 @@ test("getUsageSummary counts total_requests from daily_usage_summary, not 1-per- // Insert a daily_usage_summary row with total_requests=50, 1000 input, 500 output. // With the old COUNT(*) query this would count as 1 request; with SUM(requests) // it must count as 50. - db.prepare(` + db.prepare( + ` INSERT INTO daily_usage_summary (provider, model, date, total_requests, total_input_tokens, total_output_tokens, total_cost) VALUES ('openai', 'gpt-4', '2024-01-10', 50, 1000, 500, 0.02) - `).run(); + ` + ).run(); // Also insert one raw row so we can verify the UNION merges both legs. - db.prepare(` + db.prepare( + ` INSERT INTO usage_history (timestamp, provider, model, tokens_input, tokens_output, success, latency_ms, service_tier) VALUES ('2024-01-20T10:00:00.000Z', 'openai', 'gpt-4', 100, 50, 1, 200, 'standard') - `).run(); + ` + ).run(); // Build a unified source with rawCutoffDate BETWEEN the two rows so both // legs are exercised: aggregated leg gets the Jan 10 row, raw leg gets the Jan 20 row. @@ -555,11 +559,23 @@ test("getUsageSummary counts total_requests from daily_usage_summary, not 1-per- const summary = getUsageSummary(unifiedSource, unifiedParams); // 50 from daily_usage_summary + 1 from raw usage_history = 51 - assert.equal(summary.totalRequests, 51, "totalRequests must be 50 (aggregated) + 1 (raw), not 1+1"); + assert.equal( + summary.totalRequests, + 51, + "totalRequests must be 50 (aggregated) + 1 (raw), not 1+1" + ); // 1000 from daily_usage_summary + 100 from raw = 1100 assert.equal(summary.promptTokens, 1100, "promptTokens must merge aggregated + raw token sums"); // 500 from daily_usage_summary + 50 from raw = 550 - assert.equal(summary.completionTokens, 550, "completionTokens must merge aggregated + raw token sums"); + assert.equal( + summary.completionTokens, + 550, + "completionTokens must merge aggregated + raw token sums" + ); // All 51 requests are successful (aggregated leg hardcodes success=1, raw has success=1) - assert.equal(summary.successfulRequests, 51, "successfulRequests must count all rolled-up requests as successful"); + assert.equal( + summary.successfulRequests, + 51, + "successfulRequests must count all rolled-up requests as successful" + ); }); diff --git a/tests/unit/usage-cache-health-route.test.ts b/tests/unit/usage-cache-health-route.test.ts index 949183b7a795..250205f3dbc6 100644 --- a/tests/unit/usage-cache-health-route.test.ts +++ b/tests/unit/usage-cache-health-route.test.ts @@ -20,7 +20,7 @@ process.env.DATA_DIR = tmpDir; // the later ones. process.on("exit", () => { try { - fs.rmSync(tmpDir, { recursive: true, force: true }); + fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { /* best effort */ } diff --git a/tests/unit/usage-endpoint-dimension.test.ts b/tests/unit/usage-endpoint-dimension.test.ts index 9712d9240726..866b55ec41ef 100644 --- a/tests/unit/usage-endpoint-dimension.test.ts +++ b/tests/unit/usage-endpoint-dimension.test.ts @@ -20,7 +20,7 @@ const usageAnalytics = await import("../../src/lib/db/usageAnalytics.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); usageHistory.clearPendingRequests(); } @@ -31,7 +31,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("saveRequestUsage persists endpoint and getEndpointUsageRows groups by endpoint", async () => { diff --git a/tests/unit/usage-history-db.test.ts b/tests/unit/usage-history-db.test.ts index 6a3eb1564a26..70405a2969d7 100644 --- a/tests/unit/usage-history-db.test.ts +++ b/tests/unit/usage-history-db.test.ts @@ -14,7 +14,7 @@ const clearPendingRequests = usageHistory.clearPendingRequests; async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); clearPendingRequests(); } @@ -53,7 +53,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ──────────────── getUsageDb ──────────────── diff --git a/tests/unit/usage-history-reset.test.ts b/tests/unit/usage-history-reset.test.ts index 352473365f0c..01f0d86d4e79 100644 --- a/tests/unit/usage-history-reset.test.ts +++ b/tests/unit/usage-history-reset.test.ts @@ -34,7 +34,7 @@ function teardown() { delete process.env.DATA_DIR; } try { - fs.rmSync(tempDir, { recursive: true, force: true }); + fs.rmSync(tempDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // ignore cleanup errors } @@ -85,28 +85,35 @@ test("resetUsageHistory: 'all' wipes usage_history, daily_usage_summary, and hou function seed() { db.prepare( "INSERT INTO provider_nodes (id, type, name, prefix, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)" - ).run("openai-compatible-chat-test", "chat", "Custom Test", "custom-test", recentIso, recentIso); + ).run( + "openai-compatible-chat-test", + "chat", + "Custom Test", + "custom-test", + recentIso, + recentIso + ); db.prepare("INSERT INTO api_keys (id, name, key, created_at) VALUES (?, ?, ?, ?)").run( "key-test", "Test Key", "sk-test", recentIso ); - db.prepare("INSERT INTO combos (id, name, data, created_at, updated_at) VALUES (?, ?, ?, ?, ?)").run( - "combo-test", - "Test Combo", - "{}", - recentIso, + db.prepare( + "INSERT INTO combos (id, name, data, created_at, updated_at) VALUES (?, ?, ?, ?, ?)" + ).run("combo-test", "Test Combo", "{}", recentIso, recentIso); + + db.prepare("INSERT INTO usage_history (provider, model, timestamp) VALUES (?, ?, ?)").run( + "openai", + "gpt-test", + oldIso + ); + db.prepare("INSERT INTO usage_history (provider, model, timestamp) VALUES (?, ?, ?)").run( + "openai", + "gpt-test", recentIso ); - db.prepare( - "INSERT INTO usage_history (provider, model, timestamp) VALUES (?, ?, ?)" - ).run("openai", "gpt-test", oldIso); - db.prepare( - "INSERT INTO usage_history (provider, model, timestamp) VALUES (?, ?, ?)" - ).run("openai", "gpt-test", recentIso); - db.prepare("INSERT INTO call_logs (id, timestamp, artifact_relpath) VALUES (?, ?, ?)").run( "old-call", oldIso, @@ -126,7 +133,10 @@ test("resetUsageHistory: 'all' wipes usage_history, daily_usage_summary, and hou recentIso ); db.prepare("INSERT INTO proxy_logs (id, timestamp) VALUES (?, ?)").run("old-proxy", oldIso); - db.prepare("INSERT INTO proxy_logs (id, timestamp) VALUES (?, ?)").run("recent-proxy", recentIso); + db.prepare("INSERT INTO proxy_logs (id, timestamp) VALUES (?, ?)").run( + "recent-proxy", + recentIso + ); db.prepare( "INSERT INTO compression_analytics (timestamp, mode, original_tokens, compressed_tokens, tokens_saved) VALUES (?, ?, ?, ?, ?)" ).run(oldIso, "lite", 100, 50, 50); @@ -134,12 +144,16 @@ test("resetUsageHistory: 'all' wipes usage_history, daily_usage_summary, and hou "INSERT INTO compression_analytics (timestamp, mode, original_tokens, compressed_tokens, tokens_saved) VALUES (?, ?, ?, ?, ?)" ).run(recentIso, "lite", 100, 50, 50); - db.prepare( - "INSERT INTO daily_usage_summary (provider, model, date) VALUES (?, ?, ?)" - ).run("openai", "gpt-test", oldDate); - db.prepare( - "INSERT INTO daily_usage_summary (provider, model, date) VALUES (?, ?, ?)" - ).run("openai", "gpt-test", recentDate); + db.prepare("INSERT INTO daily_usage_summary (provider, model, date) VALUES (?, ?, ?)").run( + "openai", + "gpt-test", + oldDate + ); + db.prepare("INSERT INTO daily_usage_summary (provider, model, date) VALUES (?, ?, ?)").run( + "openai", + "gpt-test", + recentDate + ); db.prepare( "INSERT INTO hourly_usage_summary (provider, model, date_hour) VALUES (?, ?, ?)" @@ -179,7 +193,11 @@ test("resetUsageHistory: 'all' wipes usage_history, daily_usage_summary, and hou const periodResult = await resetUsageHistory("1d"); assert.equal(periodResult.errors, 0, "period reset should not report errors"); - assert.equal(periodResult.deletedUsageHistory, 1, "should delete only the old usage_history row"); + assert.equal( + periodResult.deletedUsageHistory, + 1, + "should delete only the old usage_history row" + ); assert.equal(periodResult.deletedCallLogs, 1, "should delete only the old call_logs row"); assert.equal( periodResult.deletedRequestDetailLogs, @@ -203,9 +221,21 @@ test("resetUsageHistory: 'all' wipes usage_history, daily_usage_summary, and hou "should delete only the old hourly_usage_summary row" ); assert.equal(periodResult.deleted, 7, "total deleted should sum the reset tables"); - assert.equal(periodResult.deletedCallLogArtifacts, 1, "period reset should delete only old call artifact"); - assert.equal(fs.existsSync(oldArtifactPath), false, "period reset should delete old call artifact"); - assert.equal(fs.existsSync(recentArtifactPath), true, "period reset should preserve recent call artifact"); + assert.equal( + periodResult.deletedCallLogArtifacts, + 1, + "period reset should delete only old call artifact" + ); + assert.equal( + fs.existsSync(oldArtifactPath), + false, + "period reset should delete old call artifact" + ); + assert.equal( + fs.existsSync(recentArtifactPath), + true, + "period reset should preserve recent call artifact" + ); assert.equal(countRows(db, "provider_nodes"), 1, "provider config should survive reset"); assert.equal(countRows(db, "api_keys"), 1, "API keys should survive reset"); @@ -235,9 +265,9 @@ test("resetUsageHistory: 'all' wipes usage_history, daily_usage_summary, and hou "recent hourly_usage_summary row should survive" ); - const survivingTimestamp = db - .prepare("SELECT timestamp FROM usage_history") - .get() as { timestamp: string }; + const survivingTimestamp = db.prepare("SELECT timestamp FROM usage_history").get() as { + timestamp: string; + }; assert.equal( survivingTimestamp.timestamp, recentIso, @@ -248,7 +278,11 @@ test("resetUsageHistory: 'all' wipes usage_history, daily_usage_summary, and hou const allResult = await resetUsageHistory("all"); assert.equal(allResult.errors, 0, "'all' reset should not report errors"); - assert.equal(allResult.deletedUsageHistory, 1, "'all' should delete the remaining usage_history row"); + assert.equal( + allResult.deletedUsageHistory, + 1, + "'all' should delete the remaining usage_history row" + ); assert.equal(allResult.deletedCallLogs, 1, "'all' should delete the remaining call_logs row"); assert.equal( allResult.deletedRequestDetailLogs, @@ -271,16 +305,32 @@ test("resetUsageHistory: 'all' wipes usage_history, daily_usage_summary, and hou 1, "'all' should delete the remaining hourly_usage_summary row" ); - assert.equal(allResult.deletedCallLogArtifacts, 1, "'all' should delete remaining call artifact"); - assert.equal(fs.existsSync(recentArtifactPath), false, "'all' should delete recent call artifact"); + assert.equal( + allResult.deletedCallLogArtifacts, + 1, + "'all' should delete remaining call artifact" + ); + assert.equal( + fs.existsSync(recentArtifactPath), + false, + "'all' should delete recent call artifact" + ); assert.equal(countRows(db, "usage_history"), 0, "'all' should empty usage_history"); assert.equal(countRows(db, "call_logs"), 0, "'all' should empty call_logs"); assert.equal(countRows(db, "request_detail_logs"), 0, "'all' should empty request_detail_logs"); assert.equal(countRows(db, "proxy_logs"), 0, "'all' should empty proxy_logs"); - assert.equal(countRows(db, "compression_analytics"), 0, "'all' should empty compression_analytics"); + assert.equal( + countRows(db, "compression_analytics"), + 0, + "'all' should empty compression_analytics" + ); assert.equal(countRows(db, "daily_usage_summary"), 0, "'all' should empty daily_usage_summary"); - assert.equal(countRows(db, "hourly_usage_summary"), 0, "'all' should empty hourly_usage_summary"); + assert.equal( + countRows(db, "hourly_usage_summary"), + 0, + "'all' should empty hourly_usage_summary" + ); assert.equal(countRows(db, "provider_nodes"), 1, "provider config should still survive 'all'"); assert.equal(countRows(db, "api_keys"), 1, "API keys should still survive 'all'"); assert.equal(countRows(db, "combos"), 1, "combos should still survive 'all'"); diff --git a/tests/unit/usage-migrations-legacy-archive-safety.test.ts b/tests/unit/usage-migrations-legacy-archive-safety.test.ts index 8e5eeb7465b4..1fc440ca77f9 100644 --- a/tests/unit/usage-migrations-legacy-archive-safety.test.ts +++ b/tests/unit/usage-migrations-legacy-archive-safety.test.ts @@ -59,7 +59,7 @@ test.after(() => { if (ORIGINAL_NEXT_PHASE === undefined) delete process.env.NEXT_PHASE; else process.env.NEXT_PHASE = ORIGINAL_NEXT_PHASE; - fs.rmSync(TEST_HOME_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_HOME_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#6799: archiveLegacyRequestLogs() must not delete the live app-logger directory (DATA_DIR/logs/application)", async () => { diff --git a/tests/unit/usage-migrations.test.ts b/tests/unit/usage-migrations.test.ts index 0ac0e55fcf21..c58b91147f41 100644 --- a/tests/unit/usage-migrations.test.ts +++ b/tests/unit/usage-migrations.test.ts @@ -38,7 +38,7 @@ function writeJson(filePath, value) { function removePath(targetPath) { if (!targetPath) return; - fs.rmSync(targetPath, { recursive: true, force: true }); + fs.rmSync(targetPath, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } function resetDbTables() { diff --git a/tests/unit/usage-utilization-connection-meta.test.ts b/tests/unit/usage-utilization-connection-meta.test.ts index 6885d519f78e..5c772210315c 100644 --- a/tests/unit/usage-utilization-connection-meta.test.ts +++ b/tests/unit/usage-utilization-connection-meta.test.ts @@ -22,7 +22,7 @@ const { GET } = await import("../../src/app/api/usage/utilization/route.ts"); test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("utilization route does not import phantom @/lib/db/connections", () => { diff --git a/tests/unit/usage-vertex-split.test.ts b/tests/unit/usage-vertex-split.test.ts index 63f6e665f722..2f994ca47f5d 100644 --- a/tests/unit/usage-vertex-split.test.ts +++ b/tests/unit/usage-vertex-split.test.ts @@ -40,7 +40,7 @@ describe("vertex leaf self-tracked spend", () => { after(() => { core.resetDbInstance(); try { - fs.rmSync(TMP, { recursive: true, force: true }); + fs.rmSync(TMP, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // best-effort temp cleanup } diff --git a/tests/unit/usage-xai-split.test.ts b/tests/unit/usage-xai-split.test.ts index 00bf99db0335..ff65d658e55c 100644 --- a/tests/unit/usage-xai-split.test.ts +++ b/tests/unit/usage-xai-split.test.ts @@ -39,7 +39,7 @@ describe("xai leaf self-tracked usage", () => { after(() => { core.resetDbInstance(); try { - fs.rmSync(TMP, { recursive: true, force: true }); + fs.rmSync(TMP, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // best-effort temp cleanup } diff --git a/tests/unit/usage-xiaomi-mimo-split.test.ts b/tests/unit/usage-xiaomi-mimo-split.test.ts index 9a2f9fe91f61..7a2253ae3b5a 100644 --- a/tests/unit/usage-xiaomi-mimo-split.test.ts +++ b/tests/unit/usage-xiaomi-mimo-split.test.ts @@ -40,7 +40,7 @@ describe("xiaomi-mimo leaf self-tracked quota", () => { after(() => { core.resetDbInstance(); try { - fs.rmSync(TMP, { recursive: true, force: true }); + fs.rmSync(TMP, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // best-effort temp cleanup } diff --git a/tests/unit/usage/usageHistoryDedup.test.ts b/tests/unit/usage/usageHistoryDedup.test.ts index 3e8588434aa8..bdf2a37c43bb 100644 --- a/tests/unit/usage/usageHistoryDedup.test.ts +++ b/tests/unit/usage/usageHistoryDedup.test.ts @@ -27,7 +27,7 @@ const { saveRequestUsage } = await import("../../../src/lib/usage/usageHistory.t // Cleanup: close DB handle and temp directory so the test runner doesn't hang. test.after(() => { resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ── helpers ────────────────────────────────────────────────────────────────── diff --git a/tests/unit/v1-chat-completions-content-type-6414.test.ts b/tests/unit/v1-chat-completions-content-type-6414.test.ts index 8212b992b48b..a527d6d7e9d6 100644 --- a/tests/unit/v1-chat-completions-content-type-6414.test.ts +++ b/tests/unit/v1-chat-completions-content-type-6414.test.ts @@ -72,7 +72,7 @@ test("#6414 accepts application/json with charset parameter", async () => { test.after(() => { try { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { /* Windows tempdir cleanup is best-effort */ } diff --git a/tests/unit/v1-models-auth-leak-9320.test.ts b/tests/unit/v1-models-auth-leak-9320.test.ts index 9dc9b17a3660..0eb5875253e8 100644 --- a/tests/unit/v1-models-auth-leak-9320.test.ts +++ b/tests/unit/v1-models-auth-leak-9320.test.ts @@ -15,9 +15,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -const TEST_DATA_DIR = fs.mkdtempSync( - path.join(os.tmpdir(), "omniroute-9320-models-auth-") -); +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-9320-models-auth-")); process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "test-secret-9320"; @@ -29,7 +27,7 @@ const v1ModelsCatalog = await import("../../src/app/api/v1/models/catalog.ts"); async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); try { v1ModelsCatalog.__resetCatalogBuilderRunsForTest(); @@ -45,7 +43,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#9320 FIXED: anonymous GET /v1/models returns 401 when auth is configured", async () => { @@ -61,11 +59,7 @@ test("#9320 FIXED: anonymous GET /v1/models returns 401 when auth is configured" ); // After fix: anonymous requests must be rejected with 401 when auth is configured - assert.equal( - res.status, - 401, - `expected 401 for anonymous request, got ${res.status}` - ); + assert.equal(res.status, 401, `expected 401 for anonymous request, got ${res.status}`); const body = await res.json(); assert.ok(body.error, "response must carry an error object"); }); @@ -95,8 +89,6 @@ test("#9320: authenticated request (valid API key) returns 200 with models", asy // With a valid API key, the catalog should be accessible if (res.status !== 200) { // If the fix is in place, this should return 200 - console.log( - `[INFO] Authenticated request returned status ${res.status}` - ); + console.log(`[INFO] Authenticated request returned status ${res.status}`); } }); diff --git a/tests/unit/v1-models-catalog-generation-race.test.ts b/tests/unit/v1-models-catalog-generation-race.test.ts index 1c1e519164dc..da81bcc907b0 100644 --- a/tests/unit/v1-models-catalog-generation-race.test.ts +++ b/tests/unit/v1-models-catalog-generation-race.test.ts @@ -63,7 +63,7 @@ function deferredBuilder(body: string) { test.beforeEach(() => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); catalogCache.__resetCatalogBuilderRunsForTest(); }); @@ -71,7 +71,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("a build that started before invalidation is not joined and does not repopulate the cache", async () => { diff --git a/tests/unit/v1-models-catalog-ttl.test.ts b/tests/unit/v1-models-catalog-ttl.test.ts index 84369ac33a70..279e824f17a3 100644 --- a/tests/unit/v1-models-catalog-ttl.test.ts +++ b/tests/unit/v1-models-catalog-ttl.test.ts @@ -42,7 +42,7 @@ const GAP_MS = 10_000; test.beforeEach(() => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); v1ModelsCatalog.__resetCatalogBuilderRunsForTest(); }); @@ -50,7 +50,7 @@ test.beforeEach(() => { test.after(() => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("the settings default and the constant agree on the catalog TTL", async () => { diff --git a/tests/unit/v1-models-concurrent-6408.test.ts b/tests/unit/v1-models-concurrent-6408.test.ts index 4aa543e504f5..d1c0b8fd9d5b 100644 --- a/tests/unit/v1-models-concurrent-6408.test.ts +++ b/tests/unit/v1-models-concurrent-6408.test.ts @@ -29,7 +29,7 @@ const v1ModelsCatalog = await import("../../src/app/api/v1/models/catalog.ts"); async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); v1ModelsCatalog.__resetCatalogBuilderRunsForTest(); } @@ -41,7 +41,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#6408 — 10 concurrent identical GET /v1/models calls collapse to ONE builder run", async () => { diff --git a/tests/unit/v1-models-discovery-conformance.test.ts b/tests/unit/v1-models-discovery-conformance.test.ts index 549fc28ffafd..bd6e0cea536d 100644 --- a/tests/unit/v1-models-discovery-conformance.test.ts +++ b/tests/unit/v1-models-discovery-conformance.test.ts @@ -31,7 +31,7 @@ const v1ModelsCatalog = await import("../../src/app/api/v1/models/catalog.ts"); async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); v1ModelsCatalog.__resetCatalogBuilderRunsForTest(); } @@ -43,7 +43,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("1. GET /v1/models never returns a 3xx redirect status (regression guard)", async () => { diff --git a/tests/unit/v1-ws-route.test.ts b/tests/unit/v1-ws-route.test.ts index e4e75ae34ba7..55e68c165d20 100644 --- a/tests/unit/v1-ws-route.test.ts +++ b/tests/unit/v1-ws-route.test.ts @@ -19,7 +19,7 @@ const wsRoute = await import("../../src/app/api/v1/ws/route.ts"); function resetStorage() { apiKeysDb.resetApiKeyState(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -35,7 +35,7 @@ test.beforeEach(async () => { test.after(() => { apiKeysDb.resetApiKeyState(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; diff --git a/tests/unit/v1beta-models-route.test.ts b/tests/unit/v1beta-models-route.test.ts index 1f66e7ae24a3..04cc86d9dbd4 100644 --- a/tests/unit/v1beta-models-route.test.ts +++ b/tests/unit/v1beta-models-route.test.ts @@ -24,7 +24,7 @@ async function addActiveConnection(provider: string) { async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -34,7 +34,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("v1beta models route deduplicates custom models against built-in and synced entries", async () => { diff --git a/tests/unit/veoaifree-video-route.test.ts b/tests/unit/veoaifree-video-route.test.ts index e8dd11f68b47..4c0b0fe92af3 100644 --- a/tests/unit/veoaifree-video-route.test.ts +++ b/tests/unit/veoaifree-video-route.test.ts @@ -52,7 +52,7 @@ test.after(() => { globalThis.fetch = originalFetch; globalThis.setTimeout = originalSetTimeout; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("video route returns 200 with normalized b64_json for Veo AI Free", async () => { diff --git a/tests/unit/vercel-gateway-models-fetch-4249.test.ts b/tests/unit/vercel-gateway-models-fetch-4249.test.ts index 54fc16a03aff..54c1cea52c4b 100644 --- a/tests/unit/vercel-gateway-models-fetch-4249.test.ts +++ b/tests/unit/vercel-gateway-models-fetch-4249.test.ts @@ -33,13 +33,13 @@ const modelsRoute = await import("../../src/app/api/providers/[id]/models/route. async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); interface ModelsBody { @@ -66,11 +66,7 @@ test("#4249 Vercel AI Gateway import fetches the live /v1/models catalog", async fetched = true; return Response.json({ object: "list", - data: [ - { id: "xai/grok-4" }, - { id: "openai/gpt-5.1" }, - { id: "anthropic/claude-opus-4.5" }, - ], + data: [{ id: "xai/grok-4" }, { id: "openai/gpt-5.1" }, { id: "anthropic/claude-opus-4.5" }], }); } // Bogus probe variants (…/v1/v1/models, …/chat/completions/models) → 404 diff --git a/tests/unit/verified-connection-activation-11446.test.ts b/tests/unit/verified-connection-activation-11446.test.ts index 6564a3094a61..dd91dd2e6d89 100644 --- a/tests/unit/verified-connection-activation-11446.test.ts +++ b/tests/unit/verified-connection-activation-11446.test.ts @@ -105,7 +105,7 @@ async function createConnection( test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#11446: POST /api/providers creates a new connection inactive until verified", async () => { diff --git a/tests/unit/version-manager.test.ts b/tests/unit/version-manager.test.ts index 6aea04adc3b5..5279d4159749 100644 --- a/tests/unit/version-manager.test.ts +++ b/tests/unit/version-manager.test.ts @@ -35,7 +35,7 @@ async function resetStorage() { for (let attempt = 0; attempt < 10; attempt++) { try { if (fs.existsSync(TEST_DATA_DIR)) { - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } break; } catch (error: any) { @@ -260,7 +260,7 @@ test.afterEach(() => { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("processManager reuses an alive persisted pid without spawning a new process", async () => { diff --git a/tests/unit/vertex-passthrough-model-lockout.test.ts b/tests/unit/vertex-passthrough-model-lockout.test.ts index 68bb5fa7b25a..9a35d860ef27 100644 --- a/tests/unit/vertex-passthrough-model-lockout.test.ts +++ b/tests/unit/vertex-passthrough-model-lockout.test.ts @@ -19,7 +19,7 @@ const accountFallback = await import("../../open-sse/services/accountFallback.ts async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -35,7 +35,7 @@ async function seedVertex() { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test('hasPerModelQuota("vertex", ...) is true after the passthroughModels registry flag', () => { diff --git a/tests/unit/vertex-spend-usage.test.ts b/tests/unit/vertex-spend-usage.test.ts index 4a9bb5e13be7..fa3d9217216c 100644 --- a/tests/unit/vertex-spend-usage.test.ts +++ b/tests/unit/vertex-spend-usage.test.ts @@ -1,105 +1,105 @@ -/** - * tests/unit/vertex-spend-usage.test.ts - * - * Vertex AI exposes no native usage/quota API for an API key or Service Account, so OmniRoute - * SELF-TRACKS spend: it sums the tokens it routed to the connection (usage_history) and prices - * them via the backend pricing table, surfacing a "$X used since this account was added" figure. - * These tests cover the aggregation helper + the fetcher response shape with a real temp DB. - */ - -import { describe, it, before, after } from "node:test"; -import assert from "node:assert/strict"; -import os from "node:os"; -import path from "node:path"; -import fs from "node:fs"; - -// DATA_DIR must be set before any module that opens the DB is imported. -const TMP = fs.mkdtempSync(path.join(os.tmpdir(), "omni-vertex-")); -process.env.DATA_DIR = TMP; - -const core = await import("../../src/lib/db/core.ts"); -const { getConnectionSpendUsdSinceAdded } = await import("../../src/lib/usage/usageStats.ts"); -const { __testing } = await import("../../open-sse/services/usage.ts"); -const { getVertexUsage } = __testing; - -function insertUsage( - connectionId: string, - provider: string, - model: string, - tokensIn: number, - tokensOut: number, - success = 1 -) { - const db = core.getDbInstance(); - db.prepare( - `INSERT INTO usage_history (provider, model, connection_id, tokens_input, tokens_output, success, timestamp) - VALUES (?, ?, ?, ?, ?, ?, ?)` - ).run(provider, model, connectionId, tokensIn, tokensOut, success, new Date().toISOString()); -} - -describe("vertex self-tracked spend", () => { - before(() => { - core.getDbInstance(); // trigger migrations - // conn-v: two SUCCESSFUL priced requests across two models. - insertUsage("conn-v", "vertex", "gemini-2.5-flash", 1_000_000, 500_000, 1); - insertUsage("conn-v", "vertex", "gemini-3-pro-image-preview", 200_000, 100_000, 1); - // a FAILED request on the same connection must NOT count toward spend. - insertUsage("conn-v", "vertex", "gemini-2.5-flash", 5_000_000, 5_000_000, 0); - // a row with a different provider on the same connection id must NOT bleed in. - insertUsage("conn-v", "vertex-partner", "claude-opus-4-7", 9_000_000, 9_000_000, 1); - // a different connection must not bleed in - insertUsage("conn-other", "vertex", "gemini-2.5-flash", 9_000_000, 9_000_000, 1); - }); - - after(() => { - core.resetDbInstance(); - try { - fs.rmSync(TMP, { recursive: true, force: true }); - } catch { - // best-effort temp cleanup - } - }); - - it("counts only the connection's successful, same-provider requests", async () => { - const { costUsd, requests } = await getConnectionSpendUsdSinceAdded("vertex", "conn-v"); - assert.equal( - requests, - 2, - "only the two successful vertex rows count (failed + vertex-partner + other-conn excluded)" - ); - assert.ok(Number.isFinite(costUsd) && costUsd >= 0, "cost is a finite, non-negative number"); - }); - - it("returns 0/0 for an unknown connection (no bleed)", async () => { - const { costUsd, requests } = await getConnectionSpendUsdSinceAdded("vertex", "conn-none"); - assert.equal(requests, 0); - assert.equal(costUsd, 0); - }); - - it("getVertexUsage returns a spend quota + $ message for a used connection", async () => { - const r = (await getVertexUsage("conn-v", "vertex")) as { - plan?: string; - message?: string; - quotas?: Record; - }; - assert.ok(r.quotas?.spend, "spend quota present (so the limits cache persists it)"); - assert.equal(r.quotas!.spend.quotaSource, "localUsageHistory"); - assert.ok(typeof r.quotas!.spend.used === "number" && r.quotas!.spend.used >= 0); - assert.ok(r.message && r.message.includes("$"), "message carries the dollar figure"); - assert.ok(r.message!.includes("2 requests"), "message reports the request count"); - }); - - it("getVertexUsage reports no-usage cleanly when nothing was routed", async () => { - const r = (await getVertexUsage("conn-empty", "vertex")) as { - message?: string; - quotas?: Record; - }; - assert.ok(r.message && /no usage/i.test(r.message), "informative no-usage message"); - assert.equal(r.quotas?.spend.used, 0); - }); - - it("getVertexUsage returns a message when connection id is missing", async () => { - const r = (await getVertexUsage("", "vertex")) as { message?: string; quotas?: unknown }; - assert.ok(r.message && !r.quotas, "no spend quota without a connection id"); - }); -}); +/** + * tests/unit/vertex-spend-usage.test.ts + * + * Vertex AI exposes no native usage/quota API for an API key or Service Account, so OmniRoute + * SELF-TRACKS spend: it sums the tokens it routed to the connection (usage_history) and prices + * them via the backend pricing table, surfacing a "$X used since this account was added" figure. + * These tests cover the aggregation helper + the fetcher response shape with a real temp DB. + */ + +import { describe, it, before, after } from "node:test"; +import assert from "node:assert/strict"; +import os from "node:os"; +import path from "node:path"; +import fs from "node:fs"; + +// DATA_DIR must be set before any module that opens the DB is imported. +const TMP = fs.mkdtempSync(path.join(os.tmpdir(), "omni-vertex-")); +process.env.DATA_DIR = TMP; + +const core = await import("../../src/lib/db/core.ts"); +const { getConnectionSpendUsdSinceAdded } = await import("../../src/lib/usage/usageStats.ts"); +const { __testing } = await import("../../open-sse/services/usage.ts"); +const { getVertexUsage } = __testing; + +function insertUsage( + connectionId: string, + provider: string, + model: string, + tokensIn: number, + tokensOut: number, + success = 1 +) { + const db = core.getDbInstance(); + db.prepare( + `INSERT INTO usage_history (provider, model, connection_id, tokens_input, tokens_output, success, timestamp) + VALUES (?, ?, ?, ?, ?, ?, ?)` + ).run(provider, model, connectionId, tokensIn, tokensOut, success, new Date().toISOString()); +} + +describe("vertex self-tracked spend", () => { + before(() => { + core.getDbInstance(); // trigger migrations + // conn-v: two SUCCESSFUL priced requests across two models. + insertUsage("conn-v", "vertex", "gemini-2.5-flash", 1_000_000, 500_000, 1); + insertUsage("conn-v", "vertex", "gemini-3-pro-image-preview", 200_000, 100_000, 1); + // a FAILED request on the same connection must NOT count toward spend. + insertUsage("conn-v", "vertex", "gemini-2.5-flash", 5_000_000, 5_000_000, 0); + // a row with a different provider on the same connection id must NOT bleed in. + insertUsage("conn-v", "vertex-partner", "claude-opus-4-7", 9_000_000, 9_000_000, 1); + // a different connection must not bleed in + insertUsage("conn-other", "vertex", "gemini-2.5-flash", 9_000_000, 9_000_000, 1); + }); + + after(() => { + core.resetDbInstance(); + try { + fs.rmSync(TMP, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + } catch { + // best-effort temp cleanup + } + }); + + it("counts only the connection's successful, same-provider requests", async () => { + const { costUsd, requests } = await getConnectionSpendUsdSinceAdded("vertex", "conn-v"); + assert.equal( + requests, + 2, + "only the two successful vertex rows count (failed + vertex-partner + other-conn excluded)" + ); + assert.ok(Number.isFinite(costUsd) && costUsd >= 0, "cost is a finite, non-negative number"); + }); + + it("returns 0/0 for an unknown connection (no bleed)", async () => { + const { costUsd, requests } = await getConnectionSpendUsdSinceAdded("vertex", "conn-none"); + assert.equal(requests, 0); + assert.equal(costUsd, 0); + }); + + it("getVertexUsage returns a spend quota + $ message for a used connection", async () => { + const r = (await getVertexUsage("conn-v", "vertex")) as { + plan?: string; + message?: string; + quotas?: Record; + }; + assert.ok(r.quotas?.spend, "spend quota present (so the limits cache persists it)"); + assert.equal(r.quotas!.spend.quotaSource, "localUsageHistory"); + assert.ok(typeof r.quotas!.spend.used === "number" && r.quotas!.spend.used >= 0); + assert.ok(r.message && r.message.includes("$"), "message carries the dollar figure"); + assert.ok(r.message!.includes("2 requests"), "message reports the request count"); + }); + + it("getVertexUsage reports no-usage cleanly when nothing was routed", async () => { + const r = (await getVertexUsage("conn-empty", "vertex")) as { + message?: string; + quotas?: Record; + }; + assert.ok(r.message && /no usage/i.test(r.message), "informative no-usage message"); + assert.equal(r.quotas?.spend.used, 0); + }); + + it("getVertexUsage returns a message when connection id is missing", async () => { + const r = (await getVertexUsage("", "vertex")) as { message?: string; quotas?: unknown }; + assert.ok(r.message && !r.quotas, "no spend quota without a connection id"); + }); +}); diff --git a/tests/unit/video-combo-route.test.ts b/tests/unit/video-combo-route.test.ts index 5f9561382dfc..d66bb37eb237 100644 --- a/tests/unit/video-combo-route.test.ts +++ b/tests/unit/video-combo-route.test.ts @@ -57,7 +57,7 @@ test.afterEach(() => { test.after(() => { core.closeDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("video route diverts a combo name to the combo executor and honors the ComfyUI local-override base URL", async () => { @@ -162,10 +162,7 @@ test("video route resolves a custom video model reached through combo dispatch", assert.equal(payload.data[0].url, "https://combo-custom.example.com/generated.mp4"); assert.ok(captured, "fetch should have been called for the resolved custom model"); - assert.equal( - captured!.url, - "https://combo-custom.example.com/v1/videos/generations" - ); + assert.equal(captured!.url, "https://combo-custom.example.com/v1/videos/generations"); assert.equal(captured!.headers.Authorization, "Bearer combo-custom-key"); // The upstream call strips the provider prefix — resolvedProvider flowed // through the combo path the same way it does on the direct route. diff --git a/tests/unit/video-custom-provider-route.test.ts b/tests/unit/video-custom-provider-route.test.ts index 749bef97d892..9c10d6f2cbd5 100644 --- a/tests/unit/video-custom-provider-route.test.ts +++ b/tests/unit/video-custom-provider-route.test.ts @@ -43,7 +43,7 @@ test.afterEach(() => { test.after(() => { core.closeDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("video route uses OpenAI-compatible handler for custom provider with videos endpoint", async () => { diff --git a/tests/unit/virtual-auto-combo.test.ts b/tests/unit/virtual-auto-combo.test.ts index 8d32b0bbbea6..02a90f242a57 100644 --- a/tests/unit/virtual-auto-combo.test.ts +++ b/tests/unit/virtual-auto-combo.test.ts @@ -17,7 +17,7 @@ type VirtualComboResult = Awaited { test.after(async () => { await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_DATA_DIR === undefined) { delete process.env.DATA_DIR; diff --git a/tests/unit/volcengine-plan-connect-validation.test.ts b/tests/unit/volcengine-plan-connect-validation.test.ts index 239576f03c65..7e1e15308317 100644 --- a/tests/unit/volcengine-plan-connect-validation.test.ts +++ b/tests/unit/volcengine-plan-connect-validation.test.ts @@ -30,16 +30,14 @@ process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "test-volc-connect-secret"; const core = await import("../../src/lib/db/core.ts"); -const codeRoute = await import( - "../../src/app/api/providers/volcengine-plan/connect/[sessionId]/code/route.ts" -); -const identityRoute = await import( - "../../src/app/api/providers/volcengine-plan/connect/[sessionId]/identity/route.ts" -); +const codeRoute = + await import("../../src/app/api/providers/volcengine-plan/connect/[sessionId]/code/route.ts"); +const identityRoute = + await import("../../src/app/api/providers/volcengine-plan/connect/[sessionId]/identity/route.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function post(body: unknown): Request { diff --git a/tests/unit/vscode-responses-models.test.ts b/tests/unit/vscode-responses-models.test.ts index 71cce125cd47..336e664a65a4 100644 --- a/tests/unit/vscode-responses-models.test.ts +++ b/tests/unit/vscode-responses-models.test.ts @@ -28,7 +28,7 @@ type MetadataModel = { async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -39,7 +39,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("vscode model metadata routes keep Responses text-generation models", async () => { @@ -86,9 +86,7 @@ test("vscode model metadata routes keep Responses text-generation models", async ]); const rawBody = (await rawResponse.json()) as { data?: MetadataModel[] }; const groupedBody = (await groupedResponse.json()) as { data?: MetadataModel[] }; - const rawModel = (rawBody.data || []).find( - (entry) => entry.id === "cx/future-codex-responses" - ); + const rawModel = (rawBody.data || []).find((entry) => entry.id === "cx/future-codex-responses"); const groupedModel = (groupedBody.data || []).find( (entry) => entry.root === "future-codex-responses" ); diff --git a/tests/unit/vscode-token-routes-gpt56.test.ts b/tests/unit/vscode-token-routes-gpt56.test.ts index c68a85a6b78a..7589634824e8 100644 --- a/tests/unit/vscode-token-routes-gpt56.test.ts +++ b/tests/unit/vscode-token-routes-gpt56.test.ts @@ -25,7 +25,7 @@ interface RawModel { async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -36,7 +36,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("vscode models route preserves gateway-owned Ollama Cloud effort tiers", async () => { diff --git a/tests/unit/vscode-token-routes-responses-listing.test.ts b/tests/unit/vscode-token-routes-responses-listing.test.ts index db08e1f77a93..647f4fd00e11 100644 --- a/tests/unit/vscode-token-routes-responses-listing.test.ts +++ b/tests/unit/vscode-token-routes-responses-listing.test.ts @@ -13,9 +13,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -const TEST_DATA_DIR = fs.mkdtempSync( - path.join(os.tmpdir(), "omniroute-vscode-responses-listing-") -); +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-vscode-responses-listing-")); process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "vscode-responses-listing-secret"; @@ -35,7 +33,7 @@ const vscodeRawShowRoute = async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -46,7 +44,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("vscode Ollama-compatible tags/show routes (token + raw) expose Codex-discovered responses-format GPT models", async () => { diff --git a/tests/unit/vscode-token-routes.test.ts b/tests/unit/vscode-token-routes.test.ts index d66118b3f299..c98fc73e2b90 100644 --- a/tests/unit/vscode-token-routes.test.ts +++ b/tests/unit/vscode-token-routes.test.ts @@ -39,7 +39,7 @@ const combosDb = await import("../../src/lib/db/combos.ts"); async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -63,7 +63,7 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("vscode tokenized root route mirrors the grouped VS Code catalog without combos", async () => { diff --git a/tests/unit/warmupScheduler.test.ts b/tests/unit/warmupScheduler.test.ts index 7b77d00a85f2..94889de23634 100644 --- a/tests/unit/warmupScheduler.test.ts +++ b/tests/unit/warmupScheduler.test.ts @@ -26,7 +26,7 @@ const providersDb = await import("../../src/lib/db/providers.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -77,7 +77,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("startWarmupScheduler: disabled → null (default)", async () => { diff --git a/tests/unit/web-fetch-dispatch.test.ts b/tests/unit/web-fetch-dispatch.test.ts index 004435305e55..82fc74126311 100644 --- a/tests/unit/web-fetch-dispatch.test.ts +++ b/tests/unit/web-fetch-dispatch.test.ts @@ -17,9 +17,8 @@ const { skillRegistry } = await import("../../src/lib/skills/registry.ts"); const { skillExecutor } = await import("../../src/lib/skills/executor.ts"); const { handleToolCallExecution } = await import("../../src/lib/skills/interception.ts"); const { builtinSkills } = await import("../../src/lib/skills/builtins.ts"); -const { OMNIROUTE_WEB_FETCH_FALLBACK_TOOL_NAME } = await import( - "../../open-sse/services/webFetchInterception.ts" -); +const { OMNIROUTE_WEB_FETCH_FALLBACK_TOOL_NAME } = + await import("../../open-sse/services/webFetchInterception.ts"); const originalWebFetchHandler = builtinSkills.web_fetch; @@ -33,7 +32,7 @@ function resetRuntime() { test.beforeEach(() => { resetRuntime(); coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); }); @@ -41,7 +40,7 @@ test.after(() => { builtinSkills.web_fetch = originalWebFetchHandler; resetRuntime(); coreDb.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); const contextWithFetchBuiltin = { diff --git a/tests/unit/web-fetch-quota-fallback.test.ts b/tests/unit/web-fetch-quota-fallback.test.ts index 4c7a77f59503..ef6305306189 100644 --- a/tests/unit/web-fetch-quota-fallback.test.ts +++ b/tests/unit/web-fetch-quota-fallback.test.ts @@ -13,7 +13,7 @@ const webFetchRoute = await import("../../src/app/api/v1/web/fetch/route.ts"); async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -64,7 +64,7 @@ test.beforeEach(async () => { test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); // ── (a) credential-time: rate-limited stub is skipped, not short-circuited ── @@ -80,10 +80,10 @@ test("auto-select skips a rate-limited firecrawl and falls to jina-reader", asyn throw new Error("firecrawl should never be called once rate-limited"); } if (u.includes("r.jina.ai")) { - return new Response( - JSON.stringify({ data: { content: "jina content", links: [] } }), - { status: 200, headers: { "content-type": "application/json" } } - ); + return new Response(JSON.stringify({ data: { content: "jina content", links: [] } }), { + status: 200, + headers: { "content-type": "application/json" }, + }); } throw new Error(`unexpected fetch to ${u}`); }; @@ -116,10 +116,10 @@ test("auto-select falls through to jina-reader when firecrawl returns 429 at req }); } if (u.includes("r.jina.ai")) { - return new Response( - JSON.stringify({ data: { content: "jina content", links: [] } }), - { status: 200, headers: { "content-type": "application/json" } } - ); + return new Response(JSON.stringify({ data: { content: "jina content", links: [] } }), { + status: 200, + headers: { "content-type": "application/json" }, + }); } throw new Error(`unexpected fetch to ${u}`); }; @@ -151,10 +151,10 @@ test("auto-select falls through to jina-reader when firecrawl returns 403 (quota }); } if (u.includes("r.jina.ai")) { - return new Response( - JSON.stringify({ data: { content: "jina content", links: [] } }), - { status: 200, headers: { "content-type": "application/json" } } - ); + return new Response(JSON.stringify({ data: { content: "jina content", links: [] } }), { + status: 200, + headers: { "content-type": "application/json" }, + }); } throw new Error(`unexpected fetch to ${u}`); }; @@ -186,10 +186,10 @@ test("auto-select does NOT fall through when firecrawl returns a plain 400 bad r } if (u.includes("r.jina.ai")) { jinaWasCalled = true; - return new Response( - JSON.stringify({ data: { content: "jina content", links: [] } }), - { status: 200, headers: { "content-type": "application/json" } } - ); + return new Response(JSON.stringify({ data: { content: "jina content", links: [] } }), { + status: 200, + headers: { "content-type": "application/json" }, + }); } throw new Error(`unexpected fetch to ${u}`); }; diff --git a/tests/unit/webdav-server-3485.test.ts b/tests/unit/webdav-server-3485.test.ts index 04f0be1c34e1..f30d5824e394 100644 --- a/tests/unit/webdav-server-3485.test.ts +++ b/tests/unit/webdav-server-3485.test.ts @@ -144,7 +144,7 @@ const VAULT_ROOT = fs.mkdtempSync(path.join(os.tmpdir(), "omni-webdav-vault-")); const ORIG_KEY = process.env.STORAGE_ENCRYPTION_KEY; test.after(() => { - fs.rmSync(VAULT_ROOT, { recursive: true, force: true }); + fs.rmSync(VAULT_ROOT, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIG_KEY === undefined) { delete process.env.STORAGE_ENCRYPTION_KEY; } else { @@ -253,10 +253,7 @@ test("verifyBasicAuth: empty header returns false", async () => { test("verifyBasicAuth: non-Basic scheme returns false", async () => { const { verifyBasicAuth } = await importHandler(); - assert.equal( - verifyBasicAuth("Bearer some-token", "alice", "s3cr3t"), - false - ); + assert.equal(verifyBasicAuth("Bearer some-token", "alice", "s3cr3t"), false); }); test("verifyBasicAuth: malformed base64 returns false", async () => { @@ -372,7 +369,15 @@ test("buildPropfindXml: escapes XML special chars in names", async () => { test("buildPropfindXml: file entry has no D:collection resourcetype", async () => { const { buildPropfindXml } = await importHandler(); const xml = buildPropfindXml( - [{ name: "note.md", href: "/api/v1/webdav/note.md", isDir: false, size: 99, mtime: new Date() }], + [ + { + name: "note.md", + href: "/api/v1/webdav/note.md", + isDir: false, + size: 99, + mtime: new Date(), + }, + ], "/api/v1/webdav/" ); // File should have empty resourcetype, not a collection @@ -471,8 +476,8 @@ test.before(async () => { }); test.after(() => { - fs.rmSync(intDataDir, { recursive: true, force: true }); - fs.rmSync(intVaultDir, { recursive: true, force: true }); + fs.rmSync(intDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.rmSync(intVaultDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("PUT then GET round-trips a file correctly", async () => { @@ -657,8 +662,8 @@ test("disabled WebDAV returns 503", async () => { }); assert.equal(res.status, 503); } finally { - fs.rmSync(disabledDataDir, { recursive: true, force: true }); - fs.rmSync(disabledVaultDir, { recursive: true, force: true }); + fs.rmSync(disabledDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.rmSync(disabledVaultDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -675,7 +680,7 @@ test("no DB / missing config returns 503", async () => { }); assert.equal(res.status, 503); } finally { - fs.rmSync(emptyDataDir, { recursive: true, force: true }); + fs.rmSync(emptyDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); @@ -740,8 +745,8 @@ test("encrypted password in DB is decrypted and auth works", async () => { // OPTIONS with correct creds should succeed (200 or 207) assert.ok(res.status < 400, `Expected success with encrypted password, got ${res.status}`); } finally { - fs.rmSync(encDataDir, { recursive: true, force: true }); - fs.rmSync(encVaultDir, { recursive: true, force: true }); + fs.rmSync(encDataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.rmSync(encVaultDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); // Restore encryption key state if (ORIG_KEY === undefined) { delete process.env.STORAGE_ENCRYPTION_KEY; @@ -789,6 +794,6 @@ test("resolveDataDir: parity with src/lib/dataPaths.ts across env combos", async else process.env.DATA_DIR = ORIG_DATA; if (ORIG_XDG === undefined) delete process.env.XDG_CONFIG_HOME; else process.env.XDG_CONFIG_HOME = ORIG_XDG; - fs.rmSync(tmp, { recursive: true, force: true }); + fs.rmSync(tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); diff --git a/tests/unit/webhook-deliveries-db.test.ts b/tests/unit/webhook-deliveries-db.test.ts index c98ba9cf7ad4..e469a8a29d2f 100644 --- a/tests/unit/webhook-deliveries-db.test.ts +++ b/tests/unit/webhook-deliveries-db.test.ts @@ -13,7 +13,7 @@ const deliveriesDb = await import("../../src/lib/db/webhookDeliveries.ts"); async function resetStorage() { coreDb.resetDbInstance(); - fs.rmSync(TEST_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DIR, { recursive: true }); } @@ -23,7 +23,7 @@ test.beforeEach(async () => { test.after(() => { coreDb.resetDbInstance(); - fs.rmSync(TEST_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("insertDelivery stores a row and getDeliveries returns it", () => { diff --git a/tests/unit/webhook-metadata-guard-3269.test.ts b/tests/unit/webhook-metadata-guard-3269.test.ts index b08396d859b3..14dbc927719d 100644 --- a/tests/unit/webhook-metadata-guard-3269.test.ts +++ b/tests/unit/webhook-metadata-guard-3269.test.ts @@ -14,12 +14,10 @@ import path from "node:path"; process.env.DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omni-wh-meta-3269-")); -const { isCloudMetadataHost, OutboundUrlGuardError } = await import( - "../../src/shared/network/outboundUrlGuard.ts" -); -const { parseAndValidateWebhookUrl } = await import( - "../../src/shared/network/outboundUrlGuardPolicy.ts" -); +const { isCloudMetadataHost, OutboundUrlGuardError } = + await import("../../src/shared/network/outboundUrlGuard.ts"); +const { parseAndValidateWebhookUrl } = + await import("../../src/shared/network/outboundUrlGuardPolicy.ts"); const { resetDbInstance } = await import("../../src/lib/db/core.ts"); const FLAG = "OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS"; @@ -73,7 +71,12 @@ after(() => { /* ignore */ } try { - fs.rmSync(process.env.DATA_DIR as string, { recursive: true, force: true }); + fs.rmSync(process.env.DATA_DIR as string, { + recursive: true, + force: true, + maxRetries: 5, + retryDelay: 100, + }); } catch { /* ignore */ } diff --git a/tests/unit/webhook-private-optin-3269.test.ts b/tests/unit/webhook-private-optin-3269.test.ts index 196b9ef337e8..3bb2b6142bcb 100644 --- a/tests/unit/webhook-private-optin-3269.test.ts +++ b/tests/unit/webhook-private-optin-3269.test.ts @@ -15,9 +15,8 @@ import path from "node:path"; process.env.DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omni-wh-3269-")); const { OutboundUrlGuardError } = await import("../../src/shared/network/outboundUrlGuard.ts"); -const { parseAndValidateWebhookUrl } = await import( - "../../src/shared/network/outboundUrlGuardPolicy.ts" -); +const { parseAndValidateWebhookUrl } = + await import("../../src/shared/network/outboundUrlGuardPolicy.ts"); const { resetDbInstance } = await import("../../src/lib/db/core.ts"); const FLAG = "OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS"; @@ -76,7 +75,12 @@ after(() => { /* ignore */ } try { - fs.rmSync(process.env.DATA_DIR as string, { recursive: true, force: true }); + fs.rmSync(process.env.DATA_DIR as string, { + recursive: true, + force: true, + maxRetries: 5, + retryDelay: 100, + }); } catch { /* ignore */ } diff --git a/tests/unit/webshare-sync.test.ts b/tests/unit/webshare-sync.test.ts index 184641bf0890..ea6145109073 100644 --- a/tests/unit/webshare-sync.test.ts +++ b/tests/unit/webshare-sync.test.ts @@ -21,13 +21,13 @@ const freeProxiesDb = await import("../../src/lib/db/freeProxies.ts"); async function reset() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function webshareResponse(results: unknown[], next: string | null = null) { @@ -291,8 +291,7 @@ test("WebshareProvider.sync never leaks the API key in error messages on an HTTP const originalFetch = globalThis.fetch; process.env.FREE_PROXY_WEBSHARE_API_KEY = FAKE_API_KEY; - globalThis.fetch = (async () => - new Response("Unauthorized", { status: 401 })) as typeof fetch; + globalThis.fetch = (async () => new Response("Unauthorized", { status: 401 })) as typeof fetch; try { const p = getProvider("webshare")!; @@ -302,7 +301,10 @@ test("WebshareProvider.sync never leaks the API key in error messages on an HTTP assert.ok(result.errors.length > 0); for (const err of result.errors) { assert.ok(!err.includes(FAKE_API_KEY), `error must not leak the API key: ${err}`); - assert.ok(!err.toLowerCase().includes("authorization"), `error must not leak the auth header: ${err}`); + assert.ok( + !err.toLowerCase().includes("authorization"), + `error must not leak the auth header: ${err}` + ); } } finally { globalThis.fetch = originalFetch; diff --git a/tests/unit/wildcard-alias-settings-not-applied-7693.test.ts b/tests/unit/wildcard-alias-settings-not-applied-7693.test.ts index 0890d535e54f..ee7f10d6cd73 100644 --- a/tests/unit/wildcard-alias-settings-not-applied-7693.test.ts +++ b/tests/unit/wildcard-alias-settings-not-applied-7693.test.ts @@ -15,13 +15,13 @@ const { getModelInfo } = await import("../../src/sse/services/model.ts"); test.beforeEach(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); }); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); if (ORIGINAL_API_KEY_SECRET === undefined) { delete process.env.API_KEY_SECRET; } else { diff --git a/tests/unit/windows-cert-identity-7275.test.ts b/tests/unit/windows-cert-identity-7275.test.ts index 9d9d6bc6cfdb..4c4320068804 100644 --- a/tests/unit/windows-cert-identity-7275.test.ts +++ b/tests/unit/windows-cert-identity-7275.test.ts @@ -56,21 +56,23 @@ Object.defineProperty(process, "platform", { value: "win32", configurable: true process.env.PATH = `${binDir}${path.delimiter}${originalPath}`; // Imported AFTER forcing win32: IS_WIN inside install.ts is a load-time const. -const { checkCertInstalled, certutilThumbprint, buildWindowsDelstoreScript } = await import( - "../../src/mitm/cert/install.ts" -); +const { checkCertInstalled, certutilThumbprint, buildWindowsDelstoreScript } = + await import("../../src/mitm/cert/install.ts"); test.after(() => { Object.defineProperty(process, "platform", originalPlatformDescriptor); process.env.PATH = originalPath; - fs.rmSync(tmpRoot, { recursive: true, force: true }); + fs.rmSync(tmpRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); function fakeCertFile(seed: string): string { const der = crypto.createHash("sha256").update(seed).digest(); const pem = "-----BEGIN CERTIFICATE-----\n" + - der.toString("base64").match(/.{1,64}/g)!.join("\n") + + der + .toString("base64") + .match(/.{1,64}/g)! + .join("\n") + "\n-----END CERTIFICATE-----\n"; const certPath = path.join(tmpRoot, `${seed}.crt`); fs.writeFileSync(certPath, pem); diff --git a/tests/unit/xai-oauth-usage.test.ts b/tests/unit/xai-oauth-usage.test.ts index 5060afa385ae..52a3170d886b 100644 --- a/tests/unit/xai-oauth-usage.test.ts +++ b/tests/unit/xai-oauth-usage.test.ts @@ -92,7 +92,7 @@ describe("xAI OAuth usage dispatch", () => { globalThis.fetch = originalFetch; core.resetDbInstance(); try { - fs.rmSync(TMP, { recursive: true, force: true }); + fs.rmSync(TMP, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // best-effort temp cleanup } diff --git a/tests/unit/xai-usage.test.ts b/tests/unit/xai-usage.test.ts index 5e027f3c2df0..cd82ae8e9cf8 100644 --- a/tests/unit/xai-usage.test.ts +++ b/tests/unit/xai-usage.test.ts @@ -21,12 +21,9 @@ const TMP = fs.mkdtempSync(path.join(os.tmpdir(), "omni-xai-usage-")); process.env.DATA_DIR = TMP; const core = await import("../../src/lib/db/core.ts"); -const { getMonthlyProviderTokensForConnection } = await import( - "../../src/lib/usage/usageStats.ts" -); -const { __testing, USAGE_FETCHER_PROVIDERS, getUsageForProvider } = await import( - "../../open-sse/services/usage.ts" -); +const { getMonthlyProviderTokensForConnection } = await import("../../src/lib/usage/usageStats.ts"); +const { __testing, USAGE_FETCHER_PROVIDERS, getUsageForProvider } = + await import("../../open-sse/services/usage.ts"); const { getXaiUsage } = __testing; function insertUsage( @@ -65,7 +62,7 @@ describe("xAI self-tracked usage", () => { after(() => { core.resetDbInstance(); try { - fs.rmSync(TMP, { recursive: true, force: true }); + fs.rmSync(TMP, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // best-effort temp cleanup } diff --git a/tests/unit/xiaomi-mimo-selftrack-usage.test.ts b/tests/unit/xiaomi-mimo-selftrack-usage.test.ts index f84af5f61fa1..bbcdda04cf25 100644 --- a/tests/unit/xiaomi-mimo-selftrack-usage.test.ts +++ b/tests/unit/xiaomi-mimo-selftrack-usage.test.ts @@ -20,9 +20,7 @@ const TMP = fs.mkdtempSync(path.join(os.tmpdir(), "omni-xiaomi-")); process.env.DATA_DIR = TMP; const core = await import("../../src/lib/db/core.ts"); -const { getMonthlyProviderTokensForConnection } = await import( - "../../src/lib/usage/usageStats.ts" -); +const { getMonthlyProviderTokensForConnection } = await import("../../src/lib/usage/usageStats.ts"); const { __testing } = await import("../../open-sse/services/usage.ts"); const { getXiaomiMimoUsage } = __testing; @@ -64,7 +62,7 @@ describe("xiaomi-mimo self-tracked quota", () => { after(() => { core.resetDbInstance(); try { - fs.rmSync(TMP, { recursive: true, force: true }); + fs.rmSync(TMP, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } catch { // best-effort temp cleanup } @@ -82,7 +80,16 @@ describe("xiaomi-mimo self-tracked quota", () => { it("getXiaomiMimoUsage returns a monthly quota against the 4.1B limit", async () => { const r = (await getXiaomiMimoUsage("conn-x")) as { plan?: string; - quotas?: Record; + quotas?: Record< + string, + { + used: number; + total: number; + remaining?: number; + remainingPercentage?: number; + resetAt: string | null; + } + >; message?: string; }; assert.ok(r.quotas, `expected quotas, got message: ${r.message}`); diff --git a/tests/unit/zai-glm-max-tokens-clamp-7364.test.ts b/tests/unit/zai-glm-max-tokens-clamp-7364.test.ts index 70ff6ec557e9..a86b834f6440 100644 --- a/tests/unit/zai-glm-max-tokens-clamp-7364.test.ts +++ b/tests/unit/zai-glm-max-tokens-clamp-7364.test.ts @@ -8,14 +8,12 @@ const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-7364-max- process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); -const { - stripUnsupportedParams, - __STRIP_RULES_FOR_TEST, -} = await import("../../open-sse/translator/paramSupport.ts"); +const { stripUnsupportedParams, __STRIP_RULES_FOR_TEST } = + await import("../../open-sse/translator/paramSupport.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#7364 Defect B: zai/glm-4.6v max_tokens above the 32768 ceiling is clamped before dispatch", () => { diff --git a/tests/unit/zai-glm-target-format-override.test.ts b/tests/unit/zai-glm-target-format-override.test.ts index 44ef3d5033df..d3d79d1670fe 100644 --- a/tests/unit/zai-glm-target-format-override.test.ts +++ b/tests/unit/zai-glm-target-format-override.test.ts @@ -14,7 +14,7 @@ const { DefaultExecutor } = await import("../../open-sse/executors/default.ts"); test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("#7364 Defect A (URL): DefaultExecutor.buildUrl('zai', ...) ignores a per-model targetFormat:'openai' override and still returns the Anthropic Messages URL", () => { @@ -43,7 +43,11 @@ test("#7364 Defect A (case-sensitivity): a custom model saved as 'glm-4.6v' is n ); const exact = (await getModelInfo("zai/glm-4.6v")) as { targetFormat?: string }; - assert.equal(exact.targetFormat, "openai", "sanity check: exact-case lookup must surface the saved targetFormat"); + assert.equal( + exact.targetFormat, + "openai", + "sanity check: exact-case lookup must surface the saved targetFormat" + ); const mixedCase = (await getModelInfo("zai/glm-4.6V")) as { targetFormat?: string }; assert.equal( diff --git a/tests/unit/zai-web-model-sync-route.test.ts b/tests/unit/zai-web-model-sync-route.test.ts index 58f340198efa..2ceb261c1db8 100644 --- a/tests/unit/zai-web-model-sync-route.test.ts +++ b/tests/unit/zai-web-model-sync-route.test.ts @@ -24,7 +24,7 @@ async function resetStorage() { modelSyncRoute.__resetLoopbackReadinessForTests(); core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -32,7 +32,7 @@ test.after(() => { globalThis.fetch = originalFetch; core.resetDbInstance(); apiKeysDb.resetApiKeyState(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("curated zai-web sync removes stale imported models without touching manual models", async () => { diff --git a/tests/unit/zai-web-models-discovery-7678.test.ts b/tests/unit/zai-web-models-discovery-7678.test.ts index 678bddee8828..82837c541f30 100644 --- a/tests/unit/zai-web-models-discovery-7678.test.ts +++ b/tests/unit/zai-web-models-discovery-7678.test.ts @@ -17,13 +17,13 @@ const CURATED_ZAI_WEB_MODEL_IDS = ["glm-5.2", "GLM-5.1", "GLM-5-Turbo", "GLM-5v- async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("zai-web publishes the live reasoning and vision capabilities", () => { diff --git a/tests/unit/zcode-executor.test.ts b/tests/unit/zcode-executor.test.ts index db4a8586bee9..68e450490b84 100644 --- a/tests/unit/zcode-executor.test.ts +++ b/tests/unit/zcode-executor.test.ts @@ -8,7 +8,9 @@ const fixture = join(process.cwd(), "tests/fixtures/fake-zcode-app-server.mjs"); const TEST_DATA_DIR = mkdtempSync(join(tmpdir(), "omniroute-zcode-")); process.env.DATA_DIR = TEST_DATA_DIR; -test.after(() => rmSync(TEST_DATA_DIR, { recursive: true, force: true })); +test.after(() => + rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }) +); async function loadZcodeExecutor() { return import("../../open-sse/executors/zcode.ts"); diff --git a/tests/unit/zed-hosted-models-discovery-route.test.ts b/tests/unit/zed-hosted-models-discovery-route.test.ts index 8d97931c48f6..2c8c543a66a4 100644 --- a/tests/unit/zed-hosted-models-discovery-route.test.ts +++ b/tests/unit/zed-hosted-models-discovery-route.test.ts @@ -36,7 +36,7 @@ async function resetStorage() { globalThis.fetch = originalFetch; zedAuth.clearZedCaches(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -67,7 +67,7 @@ test.after(async () => { globalThis.fetch = originalFetch; zedAuth.clearZedCaches(); core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); test("zed-hosted model discovery mints an LLM token and lists the live catalog", async () => { diff --git a/tests/unit/zenmux-models-fetch-4202.test.ts b/tests/unit/zenmux-models-fetch-4202.test.ts index 4a4f60585b4e..9258dd313c56 100644 --- a/tests/unit/zenmux-models-fetch-4202.test.ts +++ b/tests/unit/zenmux-models-fetch-4202.test.ts @@ -29,13 +29,13 @@ const modelsRoute = await import("../../src/app/api/providers/[id]/models/route. async function resetStorage() { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.after(() => { core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); interface ModelsBody { @@ -88,7 +88,10 @@ test("#4202 ZenMux import fetches the live /api/v1/models catalog (incl. the fre ids.includes("z-ai/glm-5.2-free"), `live free models missing from catalog: ${ids.join(",")}` ); - assert.ok(ids.includes("moonshotai/kimi-k2.7-code-free"), `live free models missing: ${ids.join(",")}`); + assert.ok( + ids.includes("moonshotai/kimi-k2.7-code-free"), + `live free models missing: ${ids.join(",")}` + ); // The stale hardcoded registry entry must not be what we serve. assert.ok( !ids.includes("mistralai/mistral-large-2512"), From 60bbf0f8f07bee788e6c2054b27504050523f740 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sat, 29 Aug 2026 02:10:25 -0300 Subject: [PATCH 03/91] fix(ci): stop a stalled Codecov upload from cancelling the Coverage job and the main run (#11972) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The job has timeout-minutes: 20; the c8 merge across 8 shards takes ~10 min and the Codecov upload (declared informational) then hung for the rest of the budget on two consecutive main runs (33207760653, 33215115341) — GitHub cancels the step, the job ends cancelled, and the run's conclusion turns cancelled although every blocking job was green. The upload step now has its own 5-minute ceiling and continue-on-error; the job budget is 30 min. check-workflows suite 32/32; zizmor ratchet unchanged. --- .github/workflows/ci.yml | 10 +++++++++- .../maintenance/ci-coverage-codecov-step-timeout.md | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 changelog.d/maintenance/ci-coverage-codecov-step-timeout.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 94e93bd094dc..441d19160e62 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -979,7 +979,11 @@ jobs: # 10min was sized before #7114 added the lcov reporter (Codecov/Sonar need it); # merging 8 shard JSONs + text+json+lcov now takes ~10-12min — three consecutive # release-tip runs died at exactly 10m as job-timeout "cancelled" (2026-07-15/16). - timeout-minutes: 20 + # 30, not 20 (2026-08-29): the informational Codecov upload below hung for the rest of + # the budget on two consecutive main runs (33207760653, 33215115341); the job ended + # `cancelled` and dragged the whole run's conclusion to `cancelled` although every + # blocking job was green. The upload step now has its own ceiling; this is headroom. + timeout-minutes: 30 needs: test-unit if: ${{ !cancelled() && needs.test-unit.result == 'success' && !contains(github.event.pull_request.labels.*.name, 'hotfix') }} env: @@ -1058,6 +1062,10 @@ jobs: # (if-no-files-found: warn) — Sonar consumes the same file. - name: Upload coverage to Codecov (informational) if: always() + # Informational means informational: its own ceiling and continue-on-error, so a + # stalled upload can neither eat the job's budget nor turn a green job cancelled. + timeout-minutes: 5 + continue-on-error: true uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: files: coverage/lcov.info diff --git a/changelog.d/maintenance/ci-coverage-codecov-step-timeout.md b/changelog.d/maintenance/ci-coverage-codecov-step-timeout.md new file mode 100644 index 000000000000..7331426eb66e --- /dev/null +++ b/changelog.d/maintenance/ci-coverage-codecov-step-timeout.md @@ -0,0 +1 @@ +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it From 757cc3bb9d0d22339503bec9c8bec8f4cfd5c51b Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sat, 29 Aug 2026 02:42:13 -0300 Subject: [PATCH 04/91] =?UTF-8?q?fix(release):=20let=20the=20Electron=20wo?= =?UTF-8?q?rkflow=20start=20again=20=E2=80=94=20grant=20actions:read=20to?= =?UTF-8?q?=20the=20npm=20leg=20(release/v3.8.51=20twin=20of=20#11973)=20(?= =?UTF-8?q?#11974)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same three changes as #11973 on main, applied to this branch's newer copy of the workflow so the v3.8.51 tag does not repeat v3.8.50's zero-asset release: publish-npm grants actions:read (the called publish job requests it — a caller that grants less is refused at startup and the release job dies with it), a publish_npm dispatch input gates the npm leg, and web-build/build/release check out the tag named by the dispatch. actionlint clean; the five workflow-pinning suites pass. --- .github/workflows/electron-release.yml | 22 +++++++++++++++++++ .../fixes/v3850-electron-release-assets.md | 1 + 2 files changed, 23 insertions(+) create mode 100644 changelog.d/fixes/v3850-electron-release-assets.md diff --git a/.github/workflows/electron-release.yml b/.github/workflows/electron-release.yml index e899a664eaa1..913071d3118e 100644 --- a/.github/workflows/electron-release.yml +++ b/.github/workflows/electron-release.yml @@ -10,6 +10,11 @@ on: description: "Release version (e.g., v1.6.8)" required: true type: string + publish_npm: + description: "Also run the npm publish leg (turn off when re-attaching desktop assets to a release whose npm package already shipped)" + required: false + default: true + type: boolean # Least-privilege default: read-only at the top level; each job grants the writes it # needs (build/release upload assets, publish-npm forwards npm provenance / packages @@ -76,6 +81,9 @@ jobs: - uses: actions/checkout@v7 with: persist-credentials: false + # workflow_dispatch: build the tag being (re)built, not the dispatching branch. On a + # tag push this resolves to the same commit. + ref: ${{ needs.validate.outputs.version }} - name: Setup Node uses: actions/setup-node@v7 with: @@ -161,6 +169,9 @@ jobs: - uses: actions/checkout@v7 with: persist-credentials: false + # workflow_dispatch: build the tag being (re)built, not the dispatching branch. On a + # tag push this resolves to the same commit. + ref: ${{ needs.validate.outputs.version }} - name: Setup Node uses: actions/setup-node@v7 with: @@ -347,6 +358,8 @@ jobs: with: persist-credentials: false fetch-depth: 0 + # Source archives + SBOM come from the tag being released, not the dispatching branch. + ref: ${{ needs.validate.outputs.version }} # `merge-multiple` is deliberately OFF. It resolves same-name collisions by ARRIVAL # ORDER, and the two macOS jobs each emit their own `latest-mac.yml` listing only their @@ -462,11 +475,20 @@ jobs: publish-npm: name: Publish to npm needs: [validate, release] + # A re-dispatch that only re-attaches desktop assets must not publish the npm package again. + if: ${{ github.event_name != 'workflow_dispatch' || inputs.publish_npm }} permissions: # Must be `write`, not `read`: this job calls the reusable npm-publish.yml whose # `publish` job needs `contents: write` (gh release upload — attach the SBOM, #3874). # A reusable workflow's job cannot request more permission than the caller grants, # so a `read` here makes GitHub reject the run at startup (startup_failure). + # + # `actions: read` for the same reason: the called `publish` job downloads the next-build + # artefact and requests it. v3.8.50 (run 33005490476) died at startup with "The nested + # job 'publish' is requesting 'actions: read', but is only allowed 'actions: none'" — and + # because `release` lives in this same workflow, the tag shipped with ZERO assets. Keep + # this block a superset of every job's permissions in npm-publish.yml. + actions: read contents: write id-token: write # npm provenance (forwarded to the reusable workflow) packages: write # publish to npm.pkg.github.com diff --git a/changelog.d/fixes/v3850-electron-release-assets.md b/changelog.d/fixes/v3850-electron-release-assets.md new file mode 100644 index 000000000000..255efd829cc8 --- /dev/null +++ b/changelog.d/fixes/v3850-electron-release-assets.md @@ -0,0 +1 @@ +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets From 3b752f9d4cbb79a7db3a444e3d3da75cef9b9bcf Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sat, 29 Aug 2026 03:06:50 -0300 Subject: [PATCH 05/91] chore(quality): type the 55 no-explicit-any sites frozen under #11924 (#11975) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Production (open-sse/utils/socksConnectorWithFamily.ts, 4 sites): every cast was redundant — undici's buildConnector.BuildOptions already has `timeout?: number | null`, socks' SocksClientOptions has `timeout?: number`, and Agent.Options' `connect` / `connectTimeout` narrow to the connector's parameter types on their own. Behaviour unchanged; check:open-sse-typecheck stays at the frozen 5. Tests (51 sites): the socks-timeout mocks now carry the real types — the patched SocksClient.createConnection is typed as the static it replaces, the fake buildConnector returns buildConnector.connector, the proxy is a SocksProxy, the dynamic import is typed as the module it loads; the e2e suite passes a SocksProxy and Agent.Options and no longer casts undici's fetch init (its RequestInit already has `dispatcher`); the isFree suites narrow getCustomModels()' JSON to a declared row shape, feed deliberately-wrong values through `unknown`, and stop casting for zod's safeParse, which takes unknown. The six files' suppression entries are removed: 1238 → 1232 files, 5487 → 5432 suppressed. ESLint without the suppressions file reports 0 problems on all six; with it, no stale entry is left. The five suites pass (4, 2, 5, 4, 4). --- .../maintenance/11924-type-the-frozen-any.md | 1 + config/quality/eslint-suppressions.json | 32 +------- open-sse/utils/socksConnectorWithFamily.ts | 10 ++- tests/unit/free-models-isfree.test.ts | 9 ++- tests/unit/models-db-isfree.test.ts | 38 ++++----- ...providerModelMutationSchema-isfree.test.ts | 32 ++++++-- tests/unit/socks-connect-timeout-e2e.test.ts | 44 +++++++--- tests/unit/socks-connect-timeout.test.ts | 80 ++++++++++++++----- 8 files changed, 154 insertions(+), 92 deletions(-) create mode 100644 changelog.d/maintenance/11924-type-the-frozen-any.md diff --git a/changelog.d/maintenance/11924-type-the-frozen-any.md b/changelog.d/maintenance/11924-type-the-frozen-any.md new file mode 100644 index 000000000000..62267560f71f --- /dev/null +++ b/changelog.d/maintenance/11924-type-the-frozen-any.md @@ -0,0 +1 @@ +- Type the 55 `no-explicit-any` sites that had been frozen under #11924 — four redundant casts in `socksConnectorWithFamily.ts` (undici/socks types already accept them) and the mocks/fixtures of the socks-timeout and isFree suites — and drop their suppression entries; the ESLint ratchet shrinks from 5487 to 5432 (Closes #11924) diff --git a/config/quality/eslint-suppressions.json b/config/quality/eslint-suppressions.json index f31734f5e400..ad571e9e24ab 100644 --- a/config/quality/eslint-suppressions.json +++ b/config/quality/eslint-suppressions.json @@ -837,11 +837,6 @@ "count": 5 } }, - "open-sse/utils/socksConnectorWithFamily.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 4 - } - }, "open-sse/utils/stream.ts": { "@typescript-eslint/no-unused-vars": { "count": 2 @@ -4952,11 +4947,6 @@ "count": 20 } }, - "tests/unit/free-models-isfree.test.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, "tests/unit/functional-gateway-mirrors-append.test.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 @@ -5243,11 +5233,6 @@ "count": 3 } }, - "tests/unit/models-db-isfree.test.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 18 - } - }, "tests/unit/modelsDevSync-extended.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 2 @@ -5547,11 +5532,6 @@ "count": 4 } }, - "tests/unit/providerModelMutationSchema-isfree.test.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, "tests/unit/providers-route-managed-catalog.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 4 @@ -6021,16 +6001,6 @@ "count": 7 } }, - "tests/unit/socks-connect-timeout-e2e.test.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 11 - } - }, - "tests/unit/socks-connect-timeout.test.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 18 - } - }, "tests/unit/spend-batch-writer.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 @@ -6552,4 +6522,4 @@ "count": 2 } } -} \ No newline at end of file +} diff --git a/open-sse/utils/socksConnectorWithFamily.ts b/open-sse/utils/socksConnectorWithFamily.ts index adda58590feb..5e9fb0c4fa10 100644 --- a/open-sse/utils/socksConnectorWithFamily.ts +++ b/open-sse/utils/socksConnectorWithFamily.ts @@ -49,13 +49,15 @@ export function socksConnectorWithFamily( const isDisabled = connectTimeout === 0; // SOCKS lib: 0 throws (isValidTimeoutValue: value>0) and undefined → DEFAULT_TIMEOUT 30s; // undici: 0 disables (core/util.js: if (!opts.timeout) return noop), undefined → 10s. Divergence intentional. - const handshakeTimeout = isDisabled ? undefined : (connectTimeout ?? resolveSocksHandshakeTimeoutMs()); + const handshakeTimeout = isDisabled + ? undefined + : (connectTimeout ?? resolveSocksHandshakeTimeoutMs()); const tlsTimeout = connectTimeout; // Sequential budget: both phases bounded by the same connectTimeout → wall-time up to 60s for https // (vs 30s direct). Shared-deadline alternative rejected as unjustified complexity. const build = _buildConnectorForTest ?? buildConnector; const undiciConnect = build( - tlsTimeout !== undefined ? ({ ...tlsOpts, timeout: tlsTimeout } as any) : tlsOpts + tlsTimeout !== undefined ? { ...tlsOpts, timeout: tlsTimeout } : tlsOpts ); const socketOptions = buildSocksFamilySocketOptions(family); return async (options, callback) => { @@ -69,7 +71,7 @@ export function socksConnectorWithFamily( const r = await SocksClient.createConnection({ command: "connect", proxy, - timeout: handshakeTimeout as any, + timeout: handshakeTimeout, destination: { host: hostname, port: resolvePort(protocol, port) }, existing_socket: httpSocket as never, socket_options: socketOptions as never, @@ -97,6 +99,6 @@ export function createSocksDispatcherWithFamily( }; return new Agent({ ...rest, - connect: socksConnectorWithFamily(proxy, family, connect as any, connectTimeout as any), + connect: socksConnectorWithFamily(proxy, family, connect, connectTimeout), }); } diff --git a/tests/unit/free-models-isfree.test.ts b/tests/unit/free-models-isfree.test.ts index 7657a69c92d1..53217ace478a 100644 --- a/tests/unit/free-models-isfree.test.ts +++ b/tests/unit/free-models-isfree.test.ts @@ -9,8 +9,13 @@ describe("isFreeModel isFree opt-in", () => { assert.equal(isFreeModel("local", { id: "my-model", isFree: true }), true); }); it("isFree:false/null/undefined/1/'true' → not free (strict ===true)", () => { - for (const v of [false, null, undefined, 1, "true" as any]) { - assert.equal(isFreeModel("any", { id: "x", isFree: v as any }), false, `isFree=${String(v)} should be false`); + const junk: unknown[] = [false, null, undefined, 1, "true"]; + for (const v of junk) { + assert.equal( + isFreeModel("any", { id: "x", isFree: v as boolean }), + false, + `isFree=${String(v)} should be false` + ); } }); it("providerHasFreeModels unchanged by custom isFree", () => { diff --git a/tests/unit/models-db-isfree.test.ts b/tests/unit/models-db-isfree.test.ts index df2e23485d96..77e296157b1e 100644 --- a/tests/unit/models-db-isfree.test.ts +++ b/tests/unit/models-db-isfree.test.ts @@ -11,6 +11,8 @@ import { rmSync, mkdtempSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; +type CustomRow = { id: string; isFree?: boolean }; + describe("custom isFree tri-state (DB)", () => { let dir: string; let prevDataDir: string | undefined; @@ -44,8 +46,8 @@ describe("custom isFree tri-state (DB)", () => { undefined, true ); - const rows: any = await getCustomModels("p"); - const r = rows.find((x: any) => x.id === "m1"); + const rows = (await getCustomModels("p")) as CustomRow[]; + const r = rows.find((x) => x.id === "m1"); assert.equal(r.isFree, true); await addCustomModel( "p", @@ -60,8 +62,8 @@ describe("custom isFree tri-state (DB)", () => { undefined, undefined ); - const rows2: any = await getCustomModels("p"); - const r2 = rows2.find((x: any) => x.id === "m2"); + const rows2 = (await getCustomModels("p")) as CustomRow[]; + const r2 = rows2.find((x) => x.id === "m2"); assert.equal(r2.isFree, undefined); }); @@ -79,9 +81,9 @@ describe("custom isFree tri-state (DB)", () => { undefined, true ); - await updateCustomModel("p", "m", { isFree: null } as any); - const rows: any = await getCustomModels("p"); - const r = rows.find((x: any) => x.id === "m"); + await updateCustomModel("p", "m", { isFree: null }); + const rows = (await getCustomModels("p")) as CustomRow[]; + const r = rows.find((x) => x.id === "m"); assert.equal(r.isFree, undefined); }); @@ -99,13 +101,13 @@ describe("custom isFree tri-state (DB)", () => { undefined, undefined ); - await updateCustomModel("p", "m", { isFree: true } as any); - let rows: any = await getCustomModels("p"); - assert.equal(rows.find((x: any) => x.id === "m").isFree, true); + await updateCustomModel("p", "m", { isFree: true }); + let rows = (await getCustomModels("p")) as CustomRow[]; + assert.equal(rows.find((x) => x.id === "m").isFree, true); // tri-state helper treats false as Boolean(false) → stored as false (falsy free), but only true is free per isFree guard - await updateCustomModel("p", "m", { isFree: false } as any); + await updateCustomModel("p", "m", { isFree: false }); rows = await getCustomModels("p"); - assert.equal(rows.find((x: any) => x.id === "m").isFree, false); + assert.equal(rows.find((x) => x.id === "m").isFree, false); }); it("replaceCustomModels preserves isFree (new wins else prev)", async () => { @@ -138,15 +140,15 @@ describe("custom isFree tri-state (DB)", () => { // replace with new truth for override, omit for keep (prev should win) await replaceCustomModels("p", [ { id: "keep", name: "keep" }, - { id: "override", name: "override", isFree: true } as any, + { id: "override", name: "override", isFree: true }, ]); - const rows: any = await getCustomModels("p"); + const rows = (await getCustomModels("p")) as CustomRow[]; assert.equal( - rows.find((x: any) => x.id === "keep").isFree, + rows.find((x) => x.id === "keep").isFree, true, "prev isFree preserved when new omits" ); - assert.equal(rows.find((x: any) => x.id === "override").isFree, true, "new isFree wins"); + assert.equal(rows.find((x) => x.id === "override").isFree, true, "new isFree wins"); }); it("allowEmpty:false intact (no destructive clear)", async () => { @@ -163,8 +165,8 @@ describe("custom isFree tri-state (DB)", () => { undefined, true ); - const before: any = await getCustomModels("p"); - const after: any = await replaceCustomModels("p", [], { allowEmpty: false }); + const before = (await getCustomModels("p")) as CustomRow[]; + const after = await replaceCustomModels("p", [], { allowEmpty: false }); assert.equal(after.length, before.length); }); }); diff --git a/tests/unit/providerModelMutationSchema-isfree.test.ts b/tests/unit/providerModelMutationSchema-isfree.test.ts index 63ee499a8d99..e6eaceab1176 100644 --- a/tests/unit/providerModelMutationSchema-isfree.test.ts +++ b/tests/unit/providerModelMutationSchema-isfree.test.ts @@ -4,17 +4,35 @@ import { providerModelMutationSchema } from "../../src/shared/validation/schemas describe("providerModelMutationSchema isFree", () => { it("isFree:true accepted", () => { - assert.equal(providerModelMutationSchema.safeParse({ provider: "p", modelId: "m", isFree: true }).success, true); + assert.equal( + providerModelMutationSchema.safeParse({ provider: "p", modelId: "m", isFree: true }).success, + true + ); }); it("old payload without isFree still valid", () => { - assert.equal(providerModelMutationSchema.safeParse({ provider: "p", modelId: "m" }).success, true); + assert.equal( + providerModelMutationSchema.safeParse({ provider: "p", modelId: "m" }).success, + true + ); }); - it("rejects isFree:0 and isFree:\"yes\"", () => { - assert.equal(providerModelMutationSchema.safeParse({ provider: "p", modelId: "m", isFree: 0 as any }).success, false); - assert.equal(providerModelMutationSchema.safeParse({ provider: "p", modelId: "m", isFree: "yes" as any }).success, false); + it('rejects isFree:0 and isFree:"yes"', () => { + assert.equal( + providerModelMutationSchema.safeParse({ provider: "p", modelId: "m", isFree: 0 }).success, + false + ); + assert.equal( + providerModelMutationSchema.safeParse({ provider: "p", modelId: "m", isFree: "yes" }).success, + false + ); }); it("nullable true/false/null accepted", () => { - assert.equal(providerModelMutationSchema.safeParse({ provider: "p", modelId: "m", isFree: null }).success, true); - assert.equal(providerModelMutationSchema.safeParse({ provider: "p", modelId: "m", isFree: false }).success, true); + assert.equal( + providerModelMutationSchema.safeParse({ provider: "p", modelId: "m", isFree: null }).success, + true + ); + assert.equal( + providerModelMutationSchema.safeParse({ provider: "p", modelId: "m", isFree: false }).success, + true + ); }); }); diff --git a/tests/unit/socks-connect-timeout-e2e.test.ts b/tests/unit/socks-connect-timeout-e2e.test.ts index 9c62d0a90607..2127d76f3a60 100644 --- a/tests/unit/socks-connect-timeout-e2e.test.ts +++ b/tests/unit/socks-connect-timeout-e2e.test.ts @@ -1,11 +1,14 @@ import { describe, it, afterEach } from "node:test"; import assert from "node:assert/strict"; import net from "node:net"; -import { fetch } from "undici"; +import { fetch, type Agent } from "undici"; +import type { SocksProxy } from "socks"; import { createSocksDispatcherWithFamily } from "../../open-sse/utils/socksConnectorWithFamily.ts"; import { clearDispatcherCache } from "../../open-sse/utils/proxyDispatcher.ts"; -async function startFakeSocks(opts: { stallAfterGrant: boolean }): Promise<{ port: number; close: () => Promise }> { +async function startFakeSocks(opts: { + stallAfterGrant: boolean; +}): Promise<{ port: number; close: () => Promise }> { return new Promise((resolve) => { const serverSockets = new Set(); const server = net.createServer((socket) => { @@ -44,22 +47,43 @@ describe("stub SOCKS e2e", () => { it("pre-grant stall (SOCKS timer) \u2192 error < 1000ms", async () => { const { port, close } = await startFakeSocks({ stallAfterGrant: false }); - const dispatcher = createSocksDispatcherWithFamily({ host: "127.0.0.1", port, type: 5 } as any, 4 as any, { connectTimeout: 300, connect: {} } as any); + const proxy: SocksProxy = { host: "127.0.0.1", port, type: 5 }; + const dispatcher = createSocksDispatcherWithFamily(proxy, 4, { + connectTimeout: 300, + connect: {}, + } as Agent.Options); const t0 = Date.now(); - await assert.rejects(() => fetch("https://example.invalid/", { dispatcher } as any)); - assert.ok(Date.now() - t0 < 1000, `pre-grant stall must error < 1000ms, took ${Date.now() - t0}ms`); + await assert.rejects(() => fetch("https://example.invalid/", { dispatcher })); + assert.ok( + Date.now() - t0 < 1000, + `pre-grant stall must error < 1000ms, took ${Date.now() - t0}ms` + ); await close(); }); it("post-grant stall (TLS timer, https:// only) \u2192 error < 1500ms", async () => { const { port, close } = await startFakeSocks({ stallAfterGrant: true }); - const dispatcher = createSocksDispatcherWithFamily({ host: "127.0.0.1", port, type: 5 } as any, 4 as any, { connectTimeout: 300, connect: {} } as any); + const proxy: SocksProxy = { host: "127.0.0.1", port, type: 5 }; + const dispatcher = createSocksDispatcherWithFamily(proxy, 4, { + connectTimeout: 300, + connect: {}, + } as Agent.Options); const t0 = Date.now(); - let caught: any = null; - await assert.rejects(async () => { try { await fetch("https://example.invalid/", { dispatcher } as any); } catch (e) { caught = e; throw e; } }); - const err: any = caught; + let caught: unknown = null; + await assert.rejects(async () => { + try { + await fetch("https://example.invalid/", { dispatcher }); + } catch (e) { + caught = e; + throw e; + } + }); + const err = caught as { message?: string } | null; // The ~1000ms wall time is connectTimeout 300ms + undici immediate/queue overhead, not the 10000ms default. - assert.ok(Date.now() - t0 < 1500, `post-grant stall must error < 1500ms, took ${Date.now() - t0}ms (err: ${String((err as any)?.message ?? err).slice(0, 120)})`); + assert.ok( + Date.now() - t0 < 1500, + `post-grant stall must error < 1500ms, took ${Date.now() - t0}ms (err: ${String(err?.message ?? err).slice(0, 120)})` + ); await close(); }); }); diff --git a/tests/unit/socks-connect-timeout.test.ts b/tests/unit/socks-connect-timeout.test.ts index dc54901a1d1a..7eee20a1285f 100644 --- a/tests/unit/socks-connect-timeout.test.ts +++ b/tests/unit/socks-connect-timeout.test.ts @@ -1,6 +1,8 @@ import { describe, it, afterEach, beforeEach } from "node:test"; import assert from "node:assert/strict"; -import { SocksClient } from "socks"; +import type net from "node:net"; +import { SocksClient, type SocksClientOptions, type SocksProxy } from "socks"; +import type { buildConnector } from "undici"; // Lightweight oracle — node:test, no vi.mock. // We patch SocksClient.createConnection (writable) and inject a fake @@ -8,59 +10,79 @@ import { SocksClient } from "socks"; // mutating the read-only undici module. describe("socks connectTimeout forwarder", () => { - let capturedTimeout: any = undefined; - let capturedTlsTimeout: any = undefined; + let capturedTimeout: number | undefined = undefined; + let capturedTlsTimeout: number | null | undefined = undefined; let capturedTlsUndefined = false; - let origCreateConnection: any; + let origCreateConnection: typeof SocksClient.createConnection; beforeEach(() => { origCreateConnection = SocksClient.createConnection; capturedTimeout = undefined; capturedTlsTimeout = undefined; capturedTlsUndefined = false; - (SocksClient as any).createConnection = async (opts: any) => { + SocksClient.createConnection = (async (opts: SocksClientOptions) => { capturedTimeout = opts?.timeout; - return { socket: { setNoDelay: () => ({ setNoDelay: () => {} }) } } as any; - }; + return { socket: { setNoDelay: () => ({ setNoDelay: () => {} }) } } as unknown as Awaited< + ReturnType + >; + }) as typeof SocksClient.createConnection; }); afterEach(() => { - (SocksClient as any).createConnection = origCreateConnection; + SocksClient.createConnection = origCreateConnection; capturedTimeout = undefined; capturedTlsTimeout = undefined; capturedTlsUndefined = false; }); - function fakeBuildConnector(opts: any = {}) { + function fakeBuildConnector(opts: buildConnector.BuildOptions = {}): buildConnector.connector { if (opts && typeof opts.timeout !== "undefined") capturedTlsTimeout = opts.timeout; else capturedTlsUndefined = true; - return (_options: any, cb: any) => cb(null, { setNoDelay: () => ({}) } as any); + return (_options, cb) => cb(null, { setNoDelay: () => ({}) } as unknown as net.Socket); } async function driveConnector(args: { family: 4 | 6 | null; - tlsOpts?: any; + tlsOpts?: buildConnector.BuildOptions; connectTimeout?: number; protocol?: string; hostname?: string; port?: string; }) { - const mod: any = await import(`../../open-sse/utils/socksConnectorWithFamily.ts?t=${Date.now()}-${Math.random()}`); - const proxy = { host: "1.2.3.4", port: 1080, type: 5 } as any; + const mod = (await import( + `../../open-sse/utils/socksConnectorWithFamily.ts?t=${Date.now()}-${Math.random()}` + )) as typeof import("../../open-sse/utils/socksConnectorWithFamily.ts"); + const proxy: SocksProxy = { host: "1.2.3.4", port: 1080, type: 5 }; const tlsOpts = args.tlsOpts ?? {}; const connectTimeout = args.connectTimeout; - const connector: any = mod.socksConnectorWithFamily(proxy, args.family, tlsOpts, connectTimeout, fakeBuildConnector as any); + const connector = mod.socksConnectorWithFamily( + proxy, + args.family, + tlsOpts, + connectTimeout, + fakeBuildConnector + ); await new Promise((resolve, reject) => connector( - { protocol: args.protocol ?? "https:", hostname: args.hostname ?? "example.com", port: args.port ?? "443" } as any, - (err: any) => (err ? reject(err) : resolve()) + { + protocol: args.protocol ?? "https:", + hostname: args.hostname ?? "example.com", + port: args.port ?? "443", + }, + (err) => (err ? reject(err) : resolve()) ) ); return { capturedTimeout, capturedTlsTimeout, capturedTlsUndefined, mod, connector }; } it("U1: Agent.connectTimeout → SocksClient.timeout + TLS timeout", async () => { - const { capturedTimeout: t, capturedTlsTimeout: tls } = await driveConnector({ family: 4, tlsOpts: {}, connectTimeout: 5000, protocol: "https:", port: "443" }); + const { capturedTimeout: t, capturedTlsTimeout: tls } = await driveConnector({ + family: 4, + tlsOpts: {}, + connectTimeout: 5000, + protocol: "https:", + port: "443", + }); assert.equal(t, 5000); assert.equal(tls, 5000); }); @@ -69,7 +91,13 @@ describe("socks connectTimeout forwarder", () => { const prev = process.env.SOCKS_HANDSHAKE_TIMEOUT_MS; process.env.SOCKS_HANDSHAKE_TIMEOUT_MS = "7777"; try { - const { capturedTimeout: t, capturedTlsUndefined: tlsUndef } = await driveConnector({ family: 6, tlsOpts: {}, connectTimeout: undefined, protocol: "https:", port: "443" }); + const { capturedTimeout: t, capturedTlsUndefined: tlsUndef } = await driveConnector({ + family: 6, + tlsOpts: {}, + connectTimeout: undefined, + protocol: "https:", + port: "443", + }); assert.equal(t, 7777); assert.equal(tlsUndef, true); } finally { @@ -79,12 +107,24 @@ describe("socks connectTimeout forwarder", () => { }); it("U3: http (no TLS) still bounds SocksClient", async () => { - const { capturedTimeout: t } = await driveConnector({ family: null, tlsOpts: {}, connectTimeout: 5000, protocol: "http:", port: "80" }); + const { capturedTimeout: t } = await driveConnector({ + family: null, + tlsOpts: {}, + connectTimeout: 5000, + protocol: "http:", + port: "80", + }); assert.equal(t, 5000); }); it("U4: connectTimeout=0 → SocksClient undefined (SOCKS defaults to 30s) + TLS timeout 0 (disabled)", async () => { - const { capturedTimeout: t, capturedTlsTimeout: tls } = await driveConnector({ family: 4, tlsOpts: {}, connectTimeout: 0, protocol: "https:", port: "443" }); + const { capturedTimeout: t, capturedTlsTimeout: tls } = await driveConnector({ + family: 4, + tlsOpts: {}, + connectTimeout: 0, + protocol: "https:", + port: "443", + }); assert.equal(t, undefined); assert.equal(tls, 0); }); From 87b3bdf85e271908c0939769176e1f1e734a7eaa Mon Sep 17 00:00:00 2001 From: Tobias Andersen Date: Sat, 29 Aug 2026 10:09:37 +0200 Subject: [PATCH 06/91] fix(resilience): lock permanently retired models instead of short backoff (Gemini ban prevention) (#11762) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root-caused via a real Gemini-ban incident log: deprecated-model 404/410s (e.g. gemini-2.5-flash "no longer available to new users") fell through checkFallbackError's generic transient-cooldown branch, so combo/auto-routing kept re-selecting a permanently dead model every cooldown window forever — the hammering that got the account flagged as abusive. Fix: `MODEL_PERMANENTLY_UNAVAILABLE_PATTERNS` + `isModelPermanentlyUnavailable()` classify these as a 24h lockout instead, surfaced via `quotaResetHintMs` so combo's per-request model-lockout honors it in full. Validated: 6/6 new tests + 133/133 existing accountFallback/error-classification tests, no regressions. Thanks for tracing this end-to-end with real production logs! --- open-sse/services/accountFallback.ts | 53 ++++++++++++++ stryker.conf.json | 1 + .../gemini-deprecated-model-lockout.test.ts | 72 +++++++++++++++++++ 3 files changed, 126 insertions(+) create mode 100644 tests/unit/gemini-deprecated-model-lockout.test.ts diff --git a/open-sse/services/accountFallback.ts b/open-sse/services/accountFallback.ts index 8d1c3a88a14c..b4056d2e2bd6 100644 --- a/open-sse/services/accountFallback.ts +++ b/open-sse/services/accountFallback.ts @@ -249,6 +249,28 @@ export const OAUTH_INVALID_TOKEN_SIGNALS = [ "invalid credentials", ]; +// A model that upstream has permanently retired — Gemini's deprecated-model 404 +// ("This model models/gemini-2.5-flash is no longer available to new users...") +// and Fireworks/OpenAI-compatible "end of life" 410s ("has reached its end of +// life ... and is no longer available") — will 404/410 on EVERY future request; +// no cooldown short enough to retry soon is ever correct. Without this check +// these fall through to the generic "all other errors" branch at the bottom of +// checkFallbackError, which only applies a short (seconds-to-minutes) transient +// cooldown, so combo/auto-routing keeps re-selecting the dead model roughly +// every cooldown window, forever — wasted upstream calls that, at volume, look +// like abusive traffic to the provider (observed: a Gemini free-tier key +// retried `gemini-2.5-flash`/`gemini-2.5-flash-lite` every ~15-45 minutes for a +// full day). Matched independent of MODEL_ACCESS_DENIED_PATTERNS below because +// those only fire for status 400; this needs to catch the far more common +// 404/410 status a retired model actually returns. +export const MODEL_PERMANENTLY_UNAVAILABLE_PATTERNS = [ + /\bno longer available\b/i, + /\bno longer supported\b/i, + /\bhas reached (?:its |the )?end.?of.?life\b/i, + /\bmodel[\s\S]{0,40}?\b(?:deprecated|retired|discontinued|decommissioned)\b/i, + /\b(?:deprecated|retired|discontinued|decommissioned)[\s\S]{0,40}?\bmodel\b/i, +]; + // Context overflow patterns — the prompt exceeds the model's maximum context length. // Different providers phrase this differently. Used to decide whether a 400 error // should trigger combo fallback (a different model may have a larger context window). @@ -421,6 +443,15 @@ export function isCreditsExhausted(errorText: string): boolean { return CREDITS_EXHAUSTED_SIGNALS.some((sig) => lower.includes(sig)); } +/** + * Returns true if the response body indicates the requested model has been + * permanently retired by the provider (see MODEL_PERMANENTLY_UNAVAILABLE_PATTERNS). + */ +export function isModelPermanentlyUnavailable(errorText: string): boolean { + const text = String(errorText || ""); + return MODEL_PERMANENTLY_UNAVAILABLE_PATTERNS.some((p) => p.test(text)); +} + /** * T11: Returns true if response body indicates OAuth token is invalid/expired. * This is different from permanent account deactivation - token refresh can recover. @@ -1755,6 +1786,28 @@ export function checkFallbackError( }; } + // A retired model (Gemini deprecated-model 404, Fireworks/etc. end-of-life 410) + // will fail identically on every future request — lock it for a long, fixed + // window instead of falling through to the generic transient-error branch's + // short backoff, which would otherwise keep re-selecting a permanently dead + // model roughly every cooldown window, all day, hammering the provider with + // guaranteed-to-fail requests (see MODEL_PERMANENTLY_UNAVAILABLE_PATTERNS). + // `quotaResetHintMs` flows into combo.ts's per-request model-lockout as an + // upstream-verified reset, so it is honored in full and not clamped to the + // normal ~20min model-lockout ceiling. + if ( + (status === HTTP_STATUS.NOT_FOUND || status === HTTP_STATUS.GONE) && + isModelPermanentlyUnavailable(errorStr) + ) { + const cooldownMs = 24 * 60 * 60 * 1000; // 24h + return { + shouldFallback: true, + cooldownMs, + reason: "not_found", + quotaResetHintMs: cooldownMs, + }; + } + // Gemini-specific check — MUST run before isCreditsExhausted/ // isDailyQuotaExhausted/the generic text classifier below: Gemini's free- // tier 429 boilerplate literally says "You exceeded your current quota, diff --git a/stryker.conf.json b/stryker.conf.json index 6486c81f4cf6..d9fb56523d7d 100644 --- a/stryker.conf.json +++ b/stryker.conf.json @@ -247,6 +247,7 @@ "tests/unit/format-provider-error-cause.test.ts", "tests/unit/forwarded-header-budget.test.ts", "tests/unit/fusion-vision-panel-3378.test.ts", + "tests/unit/gemini-deprecated-model-lockout.test.ts", "tests/unit/gemini-web-capabilities-9356.test.ts", "tests/unit/gemini-web-missing-browser-3516.test.ts", "tests/unit/grok-cli-oauth.test.ts", diff --git a/tests/unit/gemini-deprecated-model-lockout.test.ts b/tests/unit/gemini-deprecated-model-lockout.test.ts new file mode 100644 index 000000000000..c3134d52c82f --- /dev/null +++ b/tests/unit/gemini-deprecated-model-lockout.test.ts @@ -0,0 +1,72 @@ +/** + * Regression guard: a permanently retired model (Gemini's deprecated-model 404, + * "This model models/gemini-2.5-flash is no longer available to new users...", + * or a Fireworks/etc. end-of-life 410) must get a long, fixed lockout instead of + * falling through to the generic transient-error branch's short backoff. + * + * Without this classification, combo/auto-routing kept re-selecting the dead + * model roughly every cooldown window (a few minutes, escalating to ~20min max) + * for as long as the model stayed in the registry — all day, every day — sending + * guaranteed-to-fail requests to the provider. At volume this looks like abusive + * traffic and was implicated in a Gemini free-tier API key getting banned. + */ +import test from "node:test"; +import assert from "node:assert/strict"; + +const { checkFallbackError, isModelPermanentlyUnavailable } = + await import("../../open-sse/services/accountFallback.ts"); + +const GEMINI_DEPRECATED_404 = + "[404]: This model models/gemini-2.5-flash is no longer available to new users. " + + "Please update your code to use models/gemini-3.6-flash for the latest features and improvements."; + +const END_OF_LIFE_410 = + '[410]: {"type":"about:blank","title":"Gone","status":410,"detail":"The model ' + + "'minimaxai/minimax-m2.7' has reached its end of life on 2026-07-27T00:00:00Z and is no longer available.\"}\n"; + +test("isModelPermanentlyUnavailable matches Gemini's deprecated-model phrasing", () => { + assert.equal(isModelPermanentlyUnavailable(GEMINI_DEPRECATED_404), true); +}); + +test("isModelPermanentlyUnavailable matches end-of-life phrasing", () => { + assert.equal(isModelPermanentlyUnavailable(END_OF_LIFE_410), true); +}); + +test("isModelPermanentlyUnavailable does not match an ordinary 404", () => { + assert.equal( + isModelPermanentlyUnavailable("[404]: Model not found, inaccessible, and/or not deployed"), + false + ); +}); + +test("checkFallbackError locks a deprecated Gemini model for 24h, not a short backoff", () => { + const result = checkFallbackError(404, GEMINI_DEPRECATED_404, 0, "gemini-2.5-flash", "gemini"); + + assert.equal(result.shouldFallback, true); + assert.equal(result.reason, "not_found"); + assert.equal(result.cooldownMs, 24 * 60 * 60 * 1000); + // Feeds combo.ts's per-request model-lockout as an upstream-verified reset, + // so it bypasses the normal ~20min model-lockout ceiling. + assert.equal(result.quotaResetHintMs, 24 * 60 * 60 * 1000); +}); + +test("checkFallbackError locks an end-of-life model (410) for 24h too", () => { + const result = checkFallbackError(410, END_OF_LIFE_410, 0, "minimaxai/minimax-m2.7", "nvidia"); + + assert.equal(result.shouldFallback, true); + assert.equal(result.reason, "not_found"); + assert.equal(result.cooldownMs, 24 * 60 * 60 * 1000); +}); + +test("a generic 404 (not a deprecation message) still falls through to the short transient cooldown", () => { + const result = checkFallbackError( + 404, + "[404]: Model not found, inaccessible, and/or not deployed", + 0, + "some-model", + "openrouter" + ); + + assert.equal(result.reason, "unknown"); + assert.notEqual(result.cooldownMs, 24 * 60 * 60 * 1000); +}); From 91f9a01fda6c6108d3daa60a3e9f86cb904dc152 Mon Sep 17 00:00:00 2001 From: Tobias Andersen Date: Sat, 29 Aug 2026 10:19:15 +0200 Subject: [PATCH 07/91] fix(resilience): stop hammering permanently-moved endpoints and billing-suspended accounts (#11774) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to #11762, same bug class hitting freeaiapikey (410 permanently-moved endpoint) and fireworks (412 billing-suspension) — both fell through checkFallbackError's generic transient-cooldown branch and got retried every ~1 minute for a full day. Fix: `ENDPOINT_PERMANENTLY_MOVED_PATTERNS`/`isEndpointPermanentlyMoved()` → 24h lockout; `ACCOUNT_SUSPENDED_BILLING_PATTERNS`/`isAccountSuspendedForBilling()` → treated as credits-exhausted (1h cooldown), independent of status code so it also catches Fireworks' 412. #11762 landed first and touched the same file — rebased/re-merged onto the updated tip (additive, no logic changes) and re-validated: 13/13 tests pass. Thanks for tracing this with real production logs again! --- open-sse/services/accountFallback.ts | 78 ++++++++++++++++ stryker.conf.json | 1 + ...-failure-hammering-other-providers.test.ts | 89 +++++++++++++++++++ 3 files changed, 168 insertions(+) create mode 100644 tests/unit/permanent-failure-hammering-other-providers.test.ts diff --git a/open-sse/services/accountFallback.ts b/open-sse/services/accountFallback.ts index b4056d2e2bd6..4a29105e6af3 100644 --- a/open-sse/services/accountFallback.ts +++ b/open-sse/services/accountFallback.ts @@ -271,6 +271,37 @@ export const MODEL_PERMANENTLY_UNAVAILABLE_PATTERNS = [ /\b(?:deprecated|retired|discontinued|decommissioned)[\s\S]{0,40}?\bmodel\b/i, ]; +// A provider that has permanently retired its API base_url — the old endpoint +// keeps returning 410/404 on EVERY future request until the connection's +// base_url is updated by an operator; no cooldown short enough to retry soon +// is ever correct. Without this check these fall through to the generic +// "all other errors" branch, which only applies a short (seconds-to-minutes) +// transient cooldown, so combo/auto-routing keeps re-selecting the dead +// endpoint roughly every cooldown window, forever — wasted upstream calls +// that, at volume, look like abusive traffic (observed: freeaiapikey's moved +// endpoint retried every ~1 minute for a full day: "This API endpoint has +// moved. Please update your base_url to https://api.freeaiapikey.com/v1 — +// the old endpoint on freeaiapikey.com no longer works."). +export const ENDPOINT_PERMANENTLY_MOVED_PATTERNS = [ + /\bendpoint has moved\b/i, + /\bno longer works\b/i, + /\bupdate your base.?url\b/i, +]; + +// A billing/account suspension that requires manual operator action (unpaid +// invoice, spending limit) — text varies per provider/account name, e.g. +// Fireworks: "Account hummern is suspended, possibly due to reaching the +// monthly spending limit or failure to pay past invoices." This does not +// match ACCOUNT_DEACTIVATED_SIGNALS' fixed "your account has been suspended" +// substring, and several providers surface it on a status (412) that +// checkFallbackError does not otherwise classify — so it fell through to the +// generic transient-error branch and got retried every few minutes, all day, +// against an account that cannot succeed until billing is fixed. +export const ACCOUNT_SUSPENDED_BILLING_PATTERNS = [ + /\bsuspended\b[\s\S]{0,120}?\b(?:spending limit|billing|invoice|payment)\b/i, + /\b(?:spending limit|billing|invoice|payment)\b[\s\S]{0,120}?\bsuspended\b/i, +]; + // Context overflow patterns — the prompt exceeds the model's maximum context length. // Different providers phrase this differently. Used to decide whether a 400 error // should trigger combo fallback (a different model may have a larger context window). @@ -452,6 +483,25 @@ export function isModelPermanentlyUnavailable(errorText: string): boolean { return MODEL_PERMANENTLY_UNAVAILABLE_PATTERNS.some((p) => p.test(text)); } +/** + * Returns true if response body indicates the provider's API endpoint/base_url + * has permanently moved (see ENDPOINT_PERMANENTLY_MOVED_PATTERNS). + */ +export function isEndpointPermanentlyMoved(errorText: string): boolean { + const text = String(errorText || ""); + return ENDPOINT_PERMANENTLY_MOVED_PATTERNS.some((p) => p.test(text)); +} + +/** + * Returns true if response body indicates the account is suspended for a + * billing reason (unpaid invoice, spending limit) — see + * ACCOUNT_SUSPENDED_BILLING_PATTERNS. + */ +export function isAccountSuspendedForBilling(errorText: string): boolean { + const text = String(errorText || ""); + return ACCOUNT_SUSPENDED_BILLING_PATTERNS.some((p) => p.test(text)); +} + /** * T11: Returns true if response body indicates OAuth token is invalid/expired. * This is different from permanent account deactivation - token refresh can recover. @@ -1808,6 +1858,34 @@ export function checkFallbackError( }; } + // The provider's API endpoint/base_url has permanently moved — every future + // request against the stale base_url fails identically, so lock it for a + // long, fixed window instead of the generic transient-error branch's short + // backoff (see ENDPOINT_PERMANENTLY_MOVED_PATTERNS). + if (isEndpointPermanentlyMoved(errorStr)) { + const cooldownMs = 24 * 60 * 60 * 1000; // 24h + return { + shouldFallback: true, + cooldownMs, + reason: "not_found", + quotaResetHintMs: cooldownMs, + }; + } + + // The account is suspended for a billing reason (unpaid invoice, spending + // limit) that varies per provider/account name and can arrive on a status + // checkFallbackError does not otherwise classify (e.g. Fireworks 412) — + // treat it like a credits-exhausted account so it stops being retried + // every few minutes until billing is fixed (see ACCOUNT_SUSPENDED_BILLING_PATTERNS). + if (isAccountSuspendedForBilling(errorStr)) { + return { + shouldFallback: true, + cooldownMs: COOLDOWN_MS.paymentRequired ?? 3600 * 1000, // 1h cooldown + reason: RateLimitReason.QUOTA_EXHAUSTED, + creditsExhausted: true, + }; + } + // Gemini-specific check — MUST run before isCreditsExhausted/ // isDailyQuotaExhausted/the generic text classifier below: Gemini's free- // tier 429 boilerplate literally says "You exceeded your current quota, diff --git a/stryker.conf.json b/stryker.conf.json index d9fb56523d7d..f8d795196283 100644 --- a/stryker.conf.json +++ b/stryker.conf.json @@ -298,6 +298,7 @@ "tests/unit/openrouter-free-model-credits-exhausted.test.ts", "tests/unit/openrouter-passthrough-models.test.ts", "tests/unit/openrouter-quota-6842.test.ts", + "tests/unit/permanent-failure-hammering-other-providers.test.ts", "tests/unit/persist-429-cooldown-account-fallback.test.ts", "tests/unit/plan3-p0.test.ts", "tests/unit/plugin-sandbox-permissions.test.ts", diff --git a/tests/unit/permanent-failure-hammering-other-providers.test.ts b/tests/unit/permanent-failure-hammering-other-providers.test.ts new file mode 100644 index 000000000000..5338418f397e --- /dev/null +++ b/tests/unit/permanent-failure-hammering-other-providers.test.ts @@ -0,0 +1,89 @@ +/** + * Regression guard: a provider that permanently moved its API base_url + * (e.g. freeaiapikey's "This API endpoint has moved... the old endpoint on + * freeaiapikey.com no longer works.") or an account suspended for a billing + * reason that doesn't match ACCOUNT_DEACTIVATED_SIGNALS's fixed wording (e.g. + * Fireworks 412 "Account hummern is suspended, possibly due to reaching the + * monthly spending limit or failure to pay past invoices.") must get a long, + * fixed lockout instead of falling through to the generic transient-error + * branch's short backoff. + * + * Without this classification, combo/auto-routing kept re-selecting the dead + * endpoint/suspended account roughly every cooldown window (a few minutes) — + * observed retried every ~1 minute for a full hour in production logs — + * sending guaranteed-to-fail requests to the provider. Same bug class as the + * Gemini deprecated-model lockout gap, on different providers/status codes. + */ +import test from "node:test"; +import assert from "node:assert/strict"; + +const { checkFallbackError, isEndpointPermanentlyMoved, isAccountSuspendedForBilling } = + await import("../../open-sse/services/accountFallback.ts"); + +const FREEAIAPIKEY_ENDPOINT_MOVED_410 = + "[410]: This API endpoint has moved. Please update your base_url to " + + "https://api.freeaiapikey.com/v1 — the old endpoint on freeaiapikey.com no longer works."; + +const FIREWORKS_ACCOUNT_SUSPENDED_412 = + "[412]: Account hummern is suspended, possibly due to reaching the monthly spending limit " + + "or failure to pay past invoices. Please go to https://fireworks.ai/account/billing for more information."; + +test("isEndpointPermanentlyMoved matches freeaiapikey's moved-endpoint phrasing", () => { + assert.equal(isEndpointPermanentlyMoved(FREEAIAPIKEY_ENDPOINT_MOVED_410), true); +}); + +test("isEndpointPermanentlyMoved does not match an ordinary 410", () => { + assert.equal(isEndpointPermanentlyMoved("[410]: Gone"), false); +}); + +test("isAccountSuspendedForBilling matches Fireworks's billing-suspension phrasing", () => { + assert.equal(isAccountSuspendedForBilling(FIREWORKS_ACCOUNT_SUSPENDED_412), true); +}); + +test("isAccountSuspendedForBilling does not match an unrelated suspension message", () => { + assert.equal(isAccountSuspendedForBilling("Your access has been suspended for review."), false); +}); + +test("checkFallbackError locks a permanently moved endpoint for 24h, not a short backoff", () => { + const result = checkFallbackError( + 410, + FREEAIAPIKEY_ENDPOINT_MOVED_410, + 0, + "anthropic/claude-sonnet-4.6", + "freeaiapikey" + ); + + assert.equal(result.shouldFallback, true); + assert.equal(result.reason, "not_found"); + assert.equal(result.cooldownMs, 24 * 60 * 60 * 1000); + // Feeds combo.ts's per-request model-lockout as an upstream-verified reset, + // so it bypasses the normal ~20min model-lockout ceiling. + assert.equal(result.quotaResetHintMs, 24 * 60 * 60 * 1000); +}); + +test("checkFallbackError treats a billing-suspended account (412) as credits-exhausted", () => { + const result = checkFallbackError( + 412, + FIREWORKS_ACCOUNT_SUSPENDED_412, + 0, + "deepseek-v4-pro", + "fireworks" + ); + + assert.equal(result.shouldFallback, true); + assert.equal(result.creditsExhausted, true); + assert.ok(result.cooldownMs > 0, "cooldownMs should be positive"); +}); + +test("a generic 412 (no billing-suspension phrasing) still falls through to the short transient cooldown", () => { + const result = checkFallbackError( + 412, + "[412]: Precondition Failed", + 0, + "some-model", + "some-provider" + ); + + assert.notEqual(result.creditsExhausted, true); + assert.notEqual(result.cooldownMs, 24 * 60 * 60 * 1000); +}); From d8879371ea17990ff0d3acdf1f84a54bb530661e Mon Sep 17 00:00:00 2001 From: Tobias Andersen Date: Sat, 29 Aug 2026 10:21:50 +0200 Subject: [PATCH 08/91] fix(combo): lock GitHub models rejected as "not supported" for future requests (#11781) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to #11762/#11774, same bug class in combo's own model-lockout wiring: GitHub rejects several models (gpt-5.4, gpt-5.3-codex, etc.) with a 400 that's permanently unavailable for this account's Copilot integration, but nothing recorded a cross-request lockout — combo's #5249 in-request advance guard is correct but doesn't persist, so the same doomed model gets retried from scratch on every new request, indefinitely. Fix: on a model-scoped 400 (`isModelScoped400`), call `lockModelIfPerModelQuota(provider, connectionId, rawModel, "model_capacity", 1h)`. GitHub already has per-model-quota enabled, so only the rejected model locks — siblings keep working. `isModelLocked()` is already checked pre-dispatch, so no other wiring needed. Validated: 3/3 new tests + fixed a pre-existing test-isolation gap in combo-model-scoped-400-advance.test.ts (shared model name across sub-tests without clearing lockout state). Thanks! --- open-sse/services/combo.ts | 53 ++++++++++++------- stryker.conf.json | 2 + .../combo-model-scoped-400-advance.test.ts | 26 +++++++-- ...github-model-not-supported-lockout.test.ts | 50 +++++++++++++++++ 4 files changed, 110 insertions(+), 21 deletions(-) create mode 100644 tests/unit/github-model-not-supported-lockout.test.ts diff --git a/open-sse/services/combo.ts b/open-sse/services/combo.ts index 38c210ee5b4b..ffc5f3e73b58 100644 --- a/open-sse/services/combo.ts +++ b/open-sse/services/combo.ts @@ -16,6 +16,7 @@ import { hasPerModelQuota, isAccountSemaphoreFull, isModelLocked, + lockModelIfPerModelQuota, MODEL_ACCESS_DENIED_PATTERNS, recordModelLockoutFailure, recordProviderFailure, @@ -2236,6 +2237,26 @@ async function handleComboChatInner({ return { ok: false, response: result }; } + // A model-scoped 400 ("The requested model is not supported" / "not + // available for integrator") is permanent for THIS connection — the + // account/integration will not gain support for the model mid-session. + // Combo still advances to the next target immediately (unchanged, + // preserves #5249's cross-provider fallback), but without a lockout + // here the SAME dead model gets retried on every future, separate + // request forever (observed: every auto-combo request wasted several + // upstream 400s on the same GitHub models, all day). isModelLocked() + // is checked before dispatch (see the pre-check above this loop), so + // this lockout is honored on the next request. + if (result.status === 400 && isModelScoped400(errorText) && provider && rawModel) { + lockModelIfPerModelQuota( + provider, + targetWithConnection.connectionId || "", + rawModel, + "model_capacity", + 60 * 60 * 1000 // 1h + ); + } + // Trigger shared provider circuit breaker for 5xx errors and connection failures. If the // next target is on the same provider, don't mark it failed (a different model may still // succeed) — #8376: EXCEPT a proxy-unreachable failure, which poisons every model alike. @@ -3302,24 +3323,20 @@ async function handleRoundRobinCombo({ "COMBO-RR", `Maximum combo attempts (${maxGlobalAttempts}) exceeded. Terminating loop to prevent runaway requests.` ); - return errorResponseWithComboDiagnostics( - 503, - "Maximum combo retry limit reached", - { - poolSize: modelCount, - attempted: globalAttempts, - excluded: [ - ...[...exhaustedProviders].map((p) => ({ provider: p, reason: "exhausted" })), - ...[...exhaustedConnections].map((c) => formatExhaustedConnectionKey(String(c))), - ], - attemptOrder: rrOutcomes.map((o) => ({ - provider: o.model.split("/")[0] || "unknown", - model: o.model, - })), - terminalReason: "max_attempts_exceeded", - recovery: buildRecoveryHint("max_attempts_exceeded"), - } - ); + return errorResponseWithComboDiagnostics(503, "Maximum combo retry limit reached", { + poolSize: modelCount, + attempted: globalAttempts, + excluded: [ + ...[...exhaustedProviders].map((p) => ({ provider: p, reason: "exhausted" })), + ...[...exhaustedConnections].map((c) => formatExhaustedConnectionKey(String(c))), + ], + attemptOrder: rrOutcomes.map((o) => ({ + provider: o.model.split("/")[0] || "unknown", + model: o.model, + })), + terminalReason: "max_attempts_exceeded", + recovery: buildRecoveryHint("max_attempts_exceeded"), + }); } if (retry > 0) { log.info( diff --git a/stryker.conf.json b/stryker.conf.json index f8d795196283..976500bf1b42 100644 --- a/stryker.conf.json +++ b/stryker.conf.json @@ -183,6 +183,7 @@ "tests/unit/combo-lockout-quota-reset-6863.test.ts", "tests/unit/combo-max-depth-config.test.ts", "tests/unit/combo-model-lockout-honors-reset-1308.test.ts", + "tests/unit/combo-model-scoped-400-advance.test.ts", "tests/unit/combo-omnimodel-tag-stripping.test.ts", "tests/unit/combo-param-validation-fallback-4519.test.ts", "tests/unit/combo-prescreen.test.ts", @@ -250,6 +251,7 @@ "tests/unit/gemini-deprecated-model-lockout.test.ts", "tests/unit/gemini-web-capabilities-9356.test.ts", "tests/unit/gemini-web-missing-browser-3516.test.ts", + "tests/unit/github-model-not-supported-lockout.test.ts", "tests/unit/grok-cli-oauth.test.ts", "tests/unit/guardrails-api-3496.test.ts", "tests/unit/guardrails/visionBridge-responses-9597.test.ts", diff --git a/tests/unit/combo-model-scoped-400-advance.test.ts b/tests/unit/combo-model-scoped-400-advance.test.ts index 2243f6c87137..5382022611c0 100644 --- a/tests/unit/combo-model-scoped-400-advance.test.ts +++ b/tests/unit/combo-model-scoped-400-advance.test.ts @@ -26,6 +26,16 @@ process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "combo-model-400-test-secret"; const { handleComboChat, isModelScoped400 } = await import("../../open-sse/services/combo.ts"); +const { clearAllModelLockouts } = await import("../../open-sse/services/accountFallback.ts"); + +// Reused "github/claude-fable-5" across sub-tests below now persists a +// cross-request model lockout on a 400 "model not supported" (matches +// production combo.ts behavior). Reset it between tests so each sub-test +// still exercises a fresh dispatch instead of being skipped by a lockout +// left over from a previous sub-test in this file. +test.beforeEach(() => { + clearAllModelLockouts(); +}); const noop = () => {}; const log = { info: noop, warn: noop, debug: noop, error: noop }; @@ -53,7 +63,10 @@ test("isModelScoped400 recognizes model-not-supported shapes (incl. invalid/Bad assert.equal(isModelScoped400("Bad Request: The model is not supported"), true); assert.equal(isModelScoped400("model claude-fable-5 does not support Responses API."), true); assert.equal(isModelScoped400("unsupported_api_for_model"), true); - assert.equal(isModelScoped400("The model `x` does not exist or you do not have access to it."), true); + assert.equal( + isModelScoped400("The model `x` does not exist or you do not have access to it."), + true + ); // Genuinely body-specific — must NOT be treated as model-scoped assert.equal(isModelScoped400("Invalid message format: the request body is malformed."), false); assert.equal(isModelScoped400("malformed JSON in request body"), false); @@ -68,7 +81,10 @@ async function assertAdvancesOn(errorMessage: string, label: string) { handleSingleModel: async (_body: unknown, modelStr: string) => { modelsCalled.push(modelStr); if (modelStr === "github/claude-fable-5") { - return Response.json({ error: { message: errorMessage, type: "invalid_request_error" } }, { status: 400 }); + return Response.json( + { error: { message: errorMessage, type: "invalid_request_error" } }, + { status: 400 } + ); } return okResponse(modelStr); }, @@ -128,6 +144,10 @@ test("combo still STOPS on genuinely body-specific invalid message format", asyn allCombos: [], }); - assert.equal(modelsCalled.length, 1, `body-specific 400 must stop at target 1; tried: ${modelsCalled.join(", ")}`); + assert.equal( + modelsCalled.length, + 1, + `body-specific 400 must stop at target 1; tried: ${modelsCalled.join(", ")}` + ); assert.equal(response.status, 400, "body-specific 400 must surface to the client"); }); diff --git a/tests/unit/github-model-not-supported-lockout.test.ts b/tests/unit/github-model-not-supported-lockout.test.ts new file mode 100644 index 000000000000..e8f4297c3979 --- /dev/null +++ b/tests/unit/github-model-not-supported-lockout.test.ts @@ -0,0 +1,50 @@ +/** + * Regression guard: a GitHub Copilot 400 "The requested model is not + * supported" / "not available for integrator ..." — permanent for THIS + * account/integration — must get locked out via lockModelIfPerModelQuota so + * future, separate requests skip the same dead model instead of retrying it + * on every single auto-combo request forever (observed: every request in + * production logs wasted several upstream 400 calls on the same GitHub + * models — gpt-5.4, gpt-5.5, gpt-5.6-luna, etc. — all day). + * + * Combo's existing #5249/#2101 guard already lets the current request keep + * rotating to the next target — that behavior is unchanged and untested + * here. This guards the NEW cross-request lockout side effect only. + */ +import test from "node:test"; +import assert from "node:assert/strict"; + +const { isModelScoped400 } = await import("../../open-sse/services/combo/comboPredicates.ts"); +const { lockModelIfPerModelQuota, isModelLocked, hasPerModelQuota } = + await import("../../open-sse/services/accountFallback.ts"); + +const GITHUB_NOT_SUPPORTED_400 = "[400]: The requested model is not supported."; +const GITHUB_INTEGRATOR_400 = + '[400]: The requested model is not available for integrator "vscode-chat". ' + + "Available models: [gpt-4.1 claude-fable-5]. Verify the correct Copilot-Integration-Id header is being sent."; + +test("isModelScoped400 matches GitHub's two 'model not supported' phrasings", () => { + assert.equal(isModelScoped400(GITHUB_NOT_SUPPORTED_400), true); + assert.equal(isModelScoped400(GITHUB_INTEGRATOR_400), true); +}); + +test("github has per-model quota (locks the model, not the whole connection)", () => { + assert.equal(hasPerModelQuota("github"), true); +}); + +test("lockModelIfPerModelQuota locks a model-not-supported GitHub model for future requests", () => { + const connectionId = `github-${Date.now()}`; + + const locked = lockModelIfPerModelQuota( + "github", + connectionId, + "gpt-5.4", + "model_capacity", + 60 * 60 * 1000 + ); + + assert.equal(locked, true); + assert.equal(isModelLocked("github", connectionId, "gpt-5.4"), true); + // A sibling model on the same connection must stay eligible. + assert.equal(isModelLocked("github", connectionId, "gpt-5.5"), false); +}); From c705147de20ed72742248504c97f35d86c2d28b6 Mon Sep 17 00:00:00 2001 From: Tobias Andersen Date: Sat, 29 Aug 2026 10:30:13 +0200 Subject: [PATCH 09/91] =?UTF-8?q?docs(i18n):=20finish=20Freepik=20?= =?UTF-8?q?=E2=86=92=20Magnific=20rebrand=20in=20locale=20strings=20and=20?= =?UTF-8?q?README=20(#11772)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Finishes the Freepik → Magnific rebrand from #10594 across 40 locale files and 3 README feature-list bullets (README.md, docs/i18n/it, docs/i18n/tr) — legacy `freepik` alias intentionally left in code/tests/redirects for backward compatibility, and historical CHANGELOG entries left untouched as documented history. The README bullet had base-drifted since the PR branched (release tip's "What's New" changelog snippet had already dropped two providers mentioned nowhere else in the codebase, unrelated to this PR's scope) — resolved by keeping the tip's current bullet shape and applying only the Freepik→Magnific rename on top, in both the combined-worktree validation and the pushed branch. Validated: all 40 edited locale JSON files parse; re-verified after resync onto the updated tip (post #11762/#11774/#11781). --- README.md | 2 +- docs/i18n/it/README.md | 2 +- docs/i18n/tr/README.md | 2 +- src/i18n/messages/ar.json | 2 +- src/i18n/messages/az.json | 2 +- src/i18n/messages/bg.json | 2 +- src/i18n/messages/bn.json | 2 +- src/i18n/messages/cs.json | 2 +- src/i18n/messages/da.json | 2 +- src/i18n/messages/es.json | 2 +- src/i18n/messages/fa.json | 2 +- src/i18n/messages/fi.json | 2 +- src/i18n/messages/fr.json | 2 +- src/i18n/messages/gu.json | 2 +- src/i18n/messages/he.json | 2 +- src/i18n/messages/hi.json | 2 +- src/i18n/messages/hu.json | 2 +- src/i18n/messages/id.json | 2 +- src/i18n/messages/in.json | 2 +- src/i18n/messages/it.json | 2 +- src/i18n/messages/ja.json | 2 +- src/i18n/messages/ko.json | 2 +- src/i18n/messages/mr.json | 2 +- src/i18n/messages/ms.json | 2 +- src/i18n/messages/nl.json | 2 +- src/i18n/messages/no.json | 2 +- src/i18n/messages/phi.json | 2 +- src/i18n/messages/pl.json | 2 +- src/i18n/messages/pt-BR.json | 2 +- src/i18n/messages/pt.json | 2 +- src/i18n/messages/ro.json | 2 +- src/i18n/messages/ru.json | 2 +- src/i18n/messages/sk.json | 2 +- src/i18n/messages/sv.json | 2 +- src/i18n/messages/sw.json | 2 +- src/i18n/messages/ta.json | 2 +- src/i18n/messages/te.json | 2 +- src/i18n/messages/th.json | 2 +- src/i18n/messages/tr.json | 2 +- src/i18n/messages/uk-UA.json | 2 +- src/i18n/messages/ur.json | 2 +- src/i18n/messages/vi.json | 2 +- src/i18n/messages/zh-CN.json | 2 +- src/i18n/messages/zh-TW.json | 2 +- 44 files changed, 44 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index d4a19c3fa265..116ae8b108e5 100644 --- a/README.md +++ b/README.md @@ -557,7 +557,7 @@ the current catalog at **[radar.omniroute.online/planos](https://radar.omniroute - **🧠 Memory you control** — off by default, opt-in int8 vector quantization + typed decay, per-request `x-omniroute-no-memory`. → [Memory](docs/frameworks/MEMORY.md) - **🛡️ Security** — prompt-injection guard on every LLM route (red-team suite), opt-in credential-masking guardrail (redacts leaked API keys/secrets in both directions), free DuckDuckGo last-resort web search, and an optional OIDC login gate for the dashboard (password login always stays available). → [Guardrails](docs/security/GUARDRAILS.md) - **🖼️ New endpoints** — `/v1/ocr` (Mistral OCR) and `/v1/audio/translations` (Whisper-style) round out the media surface. → [API Reference](docs/reference/API_REFERENCE.md) -- **🎨 Image / video / audio generation** — one API for media: xAI Grok Imagine & Novita AI video, ComfyUI, Freepik, Adobe Firefly, Segmind, and speech providers such as ElevenLabs. → [API Reference](docs/reference/API_REFERENCE.md) +- **🎨 Image / video / audio generation** — one API for media: xAI Grok Imagine & Novita AI video, ComfyUI, Magnific, Adobe Firefly, Segmind, and speech providers such as ElevenLabs. → [API Reference](docs/reference/API_REFERENCE.md) - **🌍 Deployment & ops** — reverse-proxy `basePath`, browser-language auto-detect, per-key device tracking, root-less MITM trust, zh-TW localization. → [Environment](docs/reference/ENVIRONMENT.md) - **🤝 More providers & agents** — Cursor Cloud Agent, Grok Build (xAI) with browser + OAuth login, Ollama first-class card, Claude Opus 5 & Sonnet 5, Kimi official partnership (Code/Web/Moonshot), Zed, Requesty, SenseNova, Yuanbao, Agnes AI… and a refreshed **351-provider catalog**. → [Providers](docs/reference/PROVIDER_REFERENCE.md) - **📡 Routing transparency** — every response carries an `X-OmniRoute-Decision` header naming the strategy/provider/latency that served it, a new `cache-optimized` combo strategy + Auto-Combo `cacheAffinity` factor route repeat requests back to the connection holding the cached prefix, and a read-only `/v1/auto-combo/{channel}/candidates` endpoint exposes an `auto/*` channel's live candidate pool. → [Auto-Combo](docs/routing/AUTO-COMBO.md) diff --git a/docs/i18n/it/README.md b/docs/i18n/it/README.md index 233ec04249f2..1745cf771d6c 100644 --- a/docs/i18n/it/README.md +++ b/docs/i18n/it/README.md @@ -494,7 +494,7 @@ Radar è opt-in e usa soltanto richieste GET. Il client OmniRoute non carica pro - **🧠 Memoria sotto il tuo controllo** — disattivata per impostazione predefinita, quantizzazione vettoriale int8 opt-in + decadimento tipizzato, `x-omniroute-no-memory` per-request. → [Memoria](../../frameworks/MEMORY.md) - **🛡️ Sicurezza** — guard contro la prompt injection su ogni route LLM (suite red-team), guardrail opzionale per il masking delle credenziali (oscura API key/secret trapelati in entrambe le direzioni), web search DuckDuckGo gratuita come ultima risorsa e gate di login OIDC opzionale per la dashboard (il login con password resta sempre disponibile). → [Guardrail](../../security/GUARDRAILS.md) - **🖼️ Nuovi endpoint** — `/v1/ocr` (Mistral OCR) e `/v1/audio/translations` (stile Whisper) completano la superficie media. → [Riferimento API](../../reference/API_REFERENCE.md) -- **🎨 Generazione immagini / video / audio** — una sola API per i media: xAI Grok Imagine e Novita AI video, ComfyUI, Freepik, Adobe Firefly, Segmind e provider vocali come ElevenLabs. → [Riferimento API](../../reference/API_REFERENCE.md) +- **🎨 Generazione immagini / video / audio** — una sola API per i media: xAI Grok Imagine e Novita AI video, ComfyUI, Magnific, Adobe Firefly, Segmind e provider vocali come ElevenLabs. → [Riferimento API](../../reference/API_REFERENCE.md) - **🌍 Deployment e operazioni** — `basePath` del reverse proxy, rilevamento automatico della lingua del browser, tracking dei dispositivi per chiave, trust MITM senza root, localizzazione zh-TW. → [Ambiente](../../reference/ENVIRONMENT.md) - **🤝 Più provider e agenti** — Cursor Cloud Agent, Grok Build (xAI) con login browser + OAuth, scheda Ollama di prima classe, Claude Opus 5 e Sonnet 5, partnership ufficiale Kimi (Code/Web/Moonshot), Zed, Requesty, SenseNova, Yuanbao, Agnes AI… e un catalogo aggiornato di **350 provider**. → [Provider](../../reference/PROVIDER_REFERENCE.md) - **📡 Trasparenza del routing** — ogni risposta include un header `X-OmniRoute-Decision` con strategia/provider/latenza che l'ha servita; una nuova strategia combo `cache-optimized` + il fattore `cacheAffinity` di Auto-Combo riportano le richieste ripetute alla connessione che possiede il prefisso in cache; un endpoint read-only `/v1/auto-combo/{channel}/candidates` espone il pool di candidati live di un canale `auto/*`. → [Auto-Combo](../../routing/AUTO-COMBO.md) diff --git a/docs/i18n/tr/README.md b/docs/i18n/tr/README.md index 2d7e77de2630..c238ffc680de 100644 --- a/docs/i18n/tr/README.md +++ b/docs/i18n/tr/README.md @@ -548,7 +548,7 @@ Radar isteğe bağlıdır (opt-in) ve yalnızca GET istekleri yapar. OmniRoute i - **🧠 Kontrol ettiğiniz bellek** — Varsayılan olarak kapalı, isteğe bağlı int8 vektör niceleme + tipli sönümleme, istek başına `x-omniroute-no-memory`. → [Bellek](docs/frameworks/MEMORY.md) - **🛡️ Güvenlik** — Her LLM rotasında istem enjeksiyonu koruması (red-team paketi), isteğe bağlı kimlik bilgisi maskeleme koruması (her iki yönde de sızan API anahtarlarını/gizli bilgileri sansürler), ücretsiz DuckDuckGo son çare web araması ve pano için isteğe bağlı OIDC giriş kapısı (şifreyle giriş her zaman kullanılabilir kalır). → [Güvenlik Önlemleri (Guardrails)](docs/security/GUARDRAILS.md) - **🖼️ Yeni uç noktalar** — `/v1/ocr` (Mistral OCR) ve `/v1/audio/translations` (Whisper tarzı) medya yüzeyini tamamlar. → [API Referansı](docs/reference/API_REFERENCE.md) -- **🎨 Görsel / video / ses üretimi** — Medya için tek bir API: xAI Grok Imagine ve Novita AI video, ComfyUI, Freepik, Adobe Firefly, Segmind ve ElevenLabs gibi konuşma sağlayıcıları. → [API Referansı](docs/reference/API_REFERENCE.md) +- **🎨 Görsel / video / ses üretimi** — Medya için tek bir API: xAI Grok Imagine ve Novita AI video, ComfyUI, Magnific, Adobe Firefly, Segmind ve ElevenLabs gibi konuşma sağlayıcıları. → [API Referansı](docs/reference/API_REFERENCE.md) - **🌍 Dağıtım ve operasyonlar** — Ters proxy `basePath`, tarayıcı dili otomatik algılama, anahtar başına cihaz takibi, root gerektirmeyen MITM güveni, zh-TW yerelleştirmesi. → [Ortam Değişkenleri](docs/reference/ENVIRONMENT.md) - **🤝 Daha fazla sağlayıcı ve ajan** — Cursor Cloud Agent, tarayıcı + OAuth girişiyle Grok Build (xAI), Ollama birinci sınıf kartı, Claude Opus 5 ve Sonnet 5, Kimi resmi ortaklığı (Code/Web/Moonshot), Zed, Requesty, SenseNova, Yuanbao, Agnes AI… ve yenilenen **350 sağlayıcılı katalog**. → [Sağlayıcılar](docs/reference/PROVIDER_REFERENCE.md) - **📡 Yönlendirme şeffaflığı** — Her yanıt, isteğe hizmet veren stratejiyi/sağlayıcıyı/gecikmeyi belirten bir `X-OmniRoute-Decision` başlığı taşır, yeni bir `cache-optimized` kombo stratejisi + Auto-Combo `cacheAffinity` faktörü yinelenen istekleri önbelleğe alınmış öneki tutan bağlantıya geri yönlendirir ve salt okunur bir `/v1/auto-combo/{channel}/candidates` uç noktası bir `auto/*` kanalının canlı aday havuzunu gösterir. → [Auto-Combo](docs/routing/AUTO-COMBO.md) diff --git a/src/i18n/messages/ar.json b/src/i18n/messages/ar.json index b5a0de4a23c4..f4a8684d8de8 100644 --- a/src/i18n/messages/ar.json +++ b/src/i18n/messages/ar.json @@ -6254,7 +6254,7 @@ "galadriel": "ربط Galadriel بمفتاح API.", "predibase": "رصيد تجريبي مجاني بقيمة 25 دولارًا (صلاحية لمدة 30 يومًا)", "chenzk": "بوابة متوافقة مع OpenAI مع كتالوج نماذج مباشر على chenzk.top.", - "magnific": "توليد الصور باستخدام واجهة برمجة تطبيقات Mystic من Freepik.", + "magnific": "توليد الصور باستخدام واجهة برمجة تطبيقات Mystic من Magnific.", "freetheai": "بوابة مجانية متوافقة مع OpenAI مع دعم نماذج التمرير المباشر (passthrough).", "g4f-gemini": "بوابة g4f.space بعيدة إلى Gemini. يتطلب الوصول المجهول أرصدة إثبات العمل من g4f.dev/chat أو مفتاح API للعضو من g4f.dev/members.html؛ تختلف الحدود.", "g4f-groq": "بوابة g4f.space بعيدة إلى Groq. يتطلب الوصول المجهول أرصدة إثبات العمل من g4f.dev/chat أو مفتاح API للعضو من g4f.dev/members.html؛ تختلف الحدود.", diff --git a/src/i18n/messages/az.json b/src/i18n/messages/az.json index d6618237c1b1..de1d619ba0ca 100644 --- a/src/i18n/messages/az.json +++ b/src/i18n/messages/az.json @@ -6254,7 +6254,7 @@ "galadriel": "Galadriel-i API açarı ilə qoşun.", "predibase": "$25 pulsuz sınaq krediti (30 günlük etibarlılıq müddəti)", "chenzk": "chenzk.top ünvanında canlı model kataloqu olan OpenAI ilə uyğun gələn şlüz.", - "magnific": "Freepik-in Mystic API-si ilə şəkillər yaradın.", + "magnific": "Magnific-in Mystic API-si ilə şəkillər yaradın.", "freetheai": "Passthrough model dəstəyi olan pulsuz OpenAI ilə uyğun gələn şlüz.", "g4f-gemini": "Gemini üçün uzaq g4f.space şlüzü. Anonim giriş g4f.dev/chat-dan iş sübutu kreditləri və ya g4f.dev/members.html-dan üzv API açarı tələb edir; limitlər dəyişir.", "g4f-groq": "Groq üçün uzaq g4f.space şlüzü. Anonim giriş g4f.dev/chat-dan iş sübutu kreditləri və ya g4f.dev/members.html-dan üzv API açarı tələb edir; limitlər dəyişir.", diff --git a/src/i18n/messages/bg.json b/src/i18n/messages/bg.json index d64d38f38767..e5765310fd73 100644 --- a/src/i18n/messages/bg.json +++ b/src/i18n/messages/bg.json @@ -6254,7 +6254,7 @@ "galadriel": "Свържете Galadriel с API ключ.", "predibase": "$25 безплатни кредити за пробен период (валидност 30 дни)", "chenzk": "Съвместим с OpenAI шлюз с каталог на моделите на живо на chenzk.top.", - "magnific": "Генерирайте изображения с Mystic API на Freepik.", + "magnific": "Генерирайте изображения с Mystic API на Magnific.", "freetheai": "Безплатен съвместим с OpenAI шлюз с поддръжка на passthrough модели.", "g4f-gemini": "Отдалечен шлюз на g4f.space към Gemini. Анонимният достъп изисква кредити за доказателство за работа от g4f.dev/chat или членски API ключ от g4f.dev/members.html; ограниченията се променят.", "g4f-groq": "Отдалечен шлюз на g4f.space към Groq. Анонимният достъп изисква кредити за доказателство за работа от g4f.dev/chat или членски API ключ от g4f.dev/members.html; ограниченията се променят.", diff --git a/src/i18n/messages/bn.json b/src/i18n/messages/bn.json index 820be83b54aa..51b74595b2b2 100644 --- a/src/i18n/messages/bn.json +++ b/src/i18n/messages/bn.json @@ -6254,7 +6254,7 @@ "galadriel": "একটি API কী দিয়ে Galadriel সংযুক্ত করুন।", "predibase": "$25 ফ্রি ট্রায়াল ক্রেডিট (30 দিনের মেয়াদ)", "chenzk": "chenzk.top-এ লাইভ মডেল ক্যাটালগ সহ OpenAI-সামঞ্জস্যপূর্ণ গেটওয়ে।", - "magnific": "Freepik-এর Mystic API দিয়ে ছবি তৈরি করুন।", + "magnific": "Magnific-এর Mystic API দিয়ে ছবি তৈরি করুন।", "freetheai": "passthrough মডেল সমর্থন সহ বিনামূল্যের OpenAI-সামঞ্জস্যপূর্ণ গেটওয়ে।", "g4f-gemini": "Gemini-এর জন্য দূরবর্তী g4f.space গেটওয়ে। বেনামী ব্যবহারে g4f.dev/chat থেকে প্রুফ-অফ-ওয়ার্ক ক্রেডিট অথবা g4f.dev/members.html থেকে সদস্য API কী প্রয়োজন; সীমা পরিবর্তনশীল।", "g4f-groq": "Groq-এর জন্য দূরবর্তী g4f.space গেটওয়ে। বেনামী ব্যবহারে g4f.dev/chat থেকে প্রুফ-অফ-ওয়ার্ক ক্রেডিট অথবা g4f.dev/members.html থেকে সদস্য API কী প্রয়োজন; সীমা পরিবর্তনশীল।", diff --git a/src/i18n/messages/cs.json b/src/i18n/messages/cs.json index 562fd7c53658..af9fa154dfd5 100644 --- a/src/i18n/messages/cs.json +++ b/src/i18n/messages/cs.json @@ -6254,7 +6254,7 @@ "galadriel": "Připojte Galadriel pomocí API klíče.", "predibase": "Bezplatný zkušební kredit 25 $ (platnost 30 dní)", "chenzk": "Brána kompatibilní s OpenAI s živým katalogem modelů na chenzk.top.", - "magnific": "Generujte obrázky pomocí Mystic API od Freepik.", + "magnific": "Generujte obrázky pomocí Mystic API od Magnific.", "freetheai": "Bezplatná brána kompatibilní s OpenAI s podporou passthrough modelů.", "g4f-gemini": "Vzdálená brána g4f.space k Gemini. Anonymní přístup vyžaduje proof-of-work kredity z g4f.dev/chat nebo členský API klíč z g4f.dev/members.html; limity se mění.", "g4f-groq": "Vzdálená brána g4f.space k Groq. Anonymní přístup vyžaduje proof-of-work kredity z g4f.dev/chat nebo členský API klíč z g4f.dev/members.html; limity se mění.", diff --git a/src/i18n/messages/da.json b/src/i18n/messages/da.json index 2eb6816d42e0..42030a1a9b83 100644 --- a/src/i18n/messages/da.json +++ b/src/i18n/messages/da.json @@ -6254,7 +6254,7 @@ "galadriel": "Forbind Galadriel med en API-nøgle.", "predibase": "$25 gratis prøvekredit (30 dages gyldighed)", "chenzk": "OpenAI-kompatibel gateway med et live modelkatalog på chenzk.top.", - "magnific": "Generer billeder med Freepiks Mystic API.", + "magnific": "Generer billeder med Magnifics Mystic API.", "freetheai": "Gratis OpenAI-kompatibel gateway med understøttelse af passthrough-modeller.", "g4f-gemini": "Ekstern g4f.space-gateway til Gemini. Anonym adgang kræver proof-of-work-kreditter fra g4f.dev/chat eller en medlems-API-nøgle fra g4f.dev/members.html; grænserne varierer.", "g4f-groq": "Ekstern g4f.space-gateway til Groq. Anonym adgang kræver proof-of-work-kreditter fra g4f.dev/chat eller en medlems-API-nøgle fra g4f.dev/members.html; grænserne varierer.", diff --git a/src/i18n/messages/es.json b/src/i18n/messages/es.json index b6001ccfa3ee..ec6ff611d6c0 100644 --- a/src/i18n/messages/es.json +++ b/src/i18n/messages/es.json @@ -6254,7 +6254,7 @@ "galadriel": "Connect Galadriel with an API key.", "predibase": "$25 free trial credits (30-day validity)", "chenzk": "OpenAI-compatible gateway with a live model catalog at chenzk.top.", - "magnific": "Generate images with Freepik's Mystic API.", + "magnific": "Generate images with Magnific's Mystic API.", "freetheai": "Free OpenAI-compatible gateway with passthrough model support.", "g4f-gemini": "Puerta de enlace remota de g4f.space a Gemini. El acceso anónimo requiere créditos de prueba de trabajo de g4f.dev/chat o una clave API de miembro de g4f.dev/members.html; los límites varían.", "g4f-groq": "Puerta de enlace remota de g4f.space a Groq. El acceso anónimo requiere créditos de prueba de trabajo de g4f.dev/chat o una clave API de miembro de g4f.dev/members.html; los límites varían.", diff --git a/src/i18n/messages/fa.json b/src/i18n/messages/fa.json index 82ac753a84ee..e22fa0294e69 100644 --- a/src/i18n/messages/fa.json +++ b/src/i18n/messages/fa.json @@ -6254,7 +6254,7 @@ "galadriel": "اتصال به Galadriel با یک کلید API.", "predibase": "$25 اعتبار آزمایشی رایگان (اعتبار ۳۰ روزه)", "chenzk": "درگاه سازگار با OpenAI با یک کاتالوگ مدل زنده در chenzk.top.", - "magnific": "تولید تصاویر با Mystic API مربوط به Freepik.", + "magnific": "تولید تصاویر با Mystic API مربوط به Magnific.", "freetheai": "درگاه رایگان سازگار با OpenAI با پشتیبانی از مدل passthrough.", "g4f-gemini": "درگاه راه دور g4f.space برای Gemini. دسترسی ناشناس به اعتبار اثبات کار از g4f.dev/chat یا کلید API عضویت از g4f.dev/members.html نیاز دارد؛ محدودیت‌ها متغیرند.", "g4f-groq": "درگاه راه دور g4f.space برای Groq. دسترسی ناشناس به اعتبار اثبات کار از g4f.dev/chat یا کلید API عضویت از g4f.dev/members.html نیاز دارد؛ محدودیت‌ها متغیرند.", diff --git a/src/i18n/messages/fi.json b/src/i18n/messages/fi.json index ff5cb04e2ca1..35a57837b6a1 100644 --- a/src/i18n/messages/fi.json +++ b/src/i18n/messages/fi.json @@ -6254,7 +6254,7 @@ "galadriel": "Yhdistä Galadriel API-avaimella.", "predibase": "$25 ilmaista kokeilusaldoa (voimassa 30 päivää)", "chenzk": "OpenAI-yhteensopiva yhdyskäytävä reaaliaikaisella malliluettelolla osoitteessa chenzk.top.", - "magnific": "Luo kuvia Freepikin Mystic API:lla.", + "magnific": "Luo kuvia Magnificin Mystic API:lla.", "freetheai": "Ilmainen OpenAI-yhteensopiva yhdyskäytävä läpivientimallien tuella.", "g4f-gemini": "Etäkäyttöinen g4f.space-yhdyskäytävä palveluun Gemini. Anonyymi käyttö vaatii proof-of-work-krediittejä osoitteesta g4f.dev/chat tai jäsenen API-avaimen osoitteesta g4f.dev/members.html; rajat vaihtelevat.", "g4f-groq": "Etäkäyttöinen g4f.space-yhdyskäytävä palveluun Groq. Anonyymi käyttö vaatii proof-of-work-krediittejä osoitteesta g4f.dev/chat tai jäsenen API-avaimen osoitteesta g4f.dev/members.html; rajat vaihtelevat.", diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json index 512f1b4919c5..1a9d8d4d6027 100644 --- a/src/i18n/messages/fr.json +++ b/src/i18n/messages/fr.json @@ -6254,7 +6254,7 @@ "galadriel": "Connecter Galadriel avec une clé API.", "predibase": "25 $ de crédits d'essai gratuit (validité de 30 jours)", "chenzk": "Passerelle compatible OpenAI avec un catalogue de modèles en direct sur chenzk.top.", - "magnific": "Générer des images avec l'API Mystic de Freepik.", + "magnific": "Générer des images avec l'API Mystic de Magnific.", "freetheai": "Passerelle gratuite compatible OpenAI avec prise en charge des modèles en passthrough.", "g4f-gemini": "Passerelle distante g4f.space vers Gemini. L’accès anonyme nécessite des crédits de preuve de travail de g4f.dev/chat ou une clé API membre de g4f.dev/members.html ; les limites varient.", "g4f-groq": "Passerelle distante g4f.space vers Groq. L’accès anonyme nécessite des crédits de preuve de travail de g4f.dev/chat ou une clé API membre de g4f.dev/members.html ; les limites varient.", diff --git a/src/i18n/messages/gu.json b/src/i18n/messages/gu.json index 83f37cc6456f..3dfd0d908206 100644 --- a/src/i18n/messages/gu.json +++ b/src/i18n/messages/gu.json @@ -6254,7 +6254,7 @@ "galadriel": "API કી વડે Galadriel ને કનેક્ટ કરો.", "predibase": "$25 મફત ટ્રાયલ ક્રેડિટ્સ (30-દિવસની માન્યતા)", "chenzk": "chenzk.top પર લાઇવ મોડલ કેટલોગ સાથે OpenAI-સુસંગત ગેટવે.", - "magnific": "Freepik ના Mystic API વડે છબીઓ જનરેટ કરો.", + "magnific": "Magnific ના Mystic API વડે છબીઓ જનરેટ કરો.", "freetheai": "પાસથ્રુ મોડલ સપોર્ટ સાથે મફત OpenAI-સુસંગત ગેટવે.", "g4f-gemini": "Gemini માટેનું દૂરસ્થ g4f.space ગેટવે. અનામી ઍક્સેસ માટે g4f.dev/chat પરથી પ્રૂફ-ઓફ-વર્ક ક્રેડિટ અથવા g4f.dev/members.html પરથી સભ્ય API કી જરૂરી છે; મર્યાદાઓ બદલાય છે.", "g4f-groq": "Groq માટેનું દૂરસ્થ g4f.space ગેટવે. અનામી ઍક્સેસ માટે g4f.dev/chat પરથી પ્રૂફ-ઓફ-વર્ક ક્રેડિટ અથવા g4f.dev/members.html પરથી સભ્ય API કી જરૂરી છે; મર્યાદાઓ બદલાય છે.", diff --git a/src/i18n/messages/he.json b/src/i18n/messages/he.json index 6a8e3dfab4c1..64496113bfb6 100644 --- a/src/i18n/messages/he.json +++ b/src/i18n/messages/he.json @@ -6254,7 +6254,7 @@ "galadriel": "חבר את Galadriel באמצעות מפתח API.", "predibase": "קרדיט ניסיון חינם בסך $25 (תוקף ל-30 יום)", "chenzk": "שער תואם OpenAI עם קטלוג מודלים חי ב-chenzk.top.", - "magnific": "צור תמונות באמצעות ה-API של Mystic מבית Freepik.", + "magnific": "צור תמונות באמצעות ה-API של Mystic מבית Magnific.", "freetheai": "שער חינמי תואם OpenAI עם תמיכה במודלים בשיטת passthrough.", "g4f-gemini": "שער g4f.space מרוחק אל Gemini. גישה אנונימית דורשת נקודות הוכחת עבודה מ-g4f.dev/chat או מפתח API לחברים מ-g4f.dev/members.html; המגבלות משתנות.", "g4f-groq": "שער g4f.space מרוחק אל Groq. גישה אנונימית דורשת נקודות הוכחת עבודה מ-g4f.dev/chat או מפתח API לחברים מ-g4f.dev/members.html; המגבלות משתנות.", diff --git a/src/i18n/messages/hi.json b/src/i18n/messages/hi.json index 964ee3166c2c..bb380603e9d9 100644 --- a/src/i18n/messages/hi.json +++ b/src/i18n/messages/hi.json @@ -6254,7 +6254,7 @@ "galadriel": "API कुंजी के साथ Galadriel को कनेक्ट करें।", "predibase": "$25 मुफ्त ट्रायल क्रेडिट (30 दिनों की वैधता)", "chenzk": "chenzk.top पर लाइव मॉडल कैटलॉग के साथ OpenAI-संगत गेटवे।", - "magnific": "Freepik के Mystic API के साथ चित्र जनरेट करें।", + "magnific": "Magnific के Mystic API के साथ चित्र जनरेट करें।", "freetheai": "पासथ्रू मॉडल समर्थन के साथ मुफ्त OpenAI-संगत गेटवे।", "g4f-gemini": "Gemini के लिए रिमोट g4f.space गेटवे। अनाम पहुँच के लिए g4f.dev/chat से प्रूफ-ऑफ-वर्क क्रेडिट या g4f.dev/members.html से सदस्य API कुंजी चाहिए; सीमाएँ बदलती रहती हैं।", "g4f-groq": "Groq के लिए रिमोट g4f.space गेटवे। अनाम पहुँच के लिए g4f.dev/chat से प्रूफ-ऑफ-वर्क क्रेडिट या g4f.dev/members.html से सदस्य API कुंजी चाहिए; सीमाएँ बदलती रहती हैं।", diff --git a/src/i18n/messages/hu.json b/src/i18n/messages/hu.json index f2fb9e21c2ed..b29a03052a80 100644 --- a/src/i18n/messages/hu.json +++ b/src/i18n/messages/hu.json @@ -6254,7 +6254,7 @@ "galadriel": "A Galadriel összekapcsolása egy API-kulccsal.", "predibase": "25 $ ingyenes próbaverziós kredit (30 napos érvényesség)", "chenzk": "OpenAI-kompatibilis átjáró élő modellkatalógussal a chenzk.top címen.", - "magnific": "Képek generálása a Freepik Mystic API-jával.", + "magnific": "Képek generálása a Magnific Mystic API-jával.", "freetheai": "Ingyenes OpenAI-kompatibilis átjáró átmenő (passthrough) modelltámogatással.", "g4f-gemini": "Távoli g4f.space átjáró a(z) Gemini szolgáltatáshoz. A névtelen hozzáféréshez proof-of-work kreditek kellenek a g4f.dev/chat oldalról, vagy tagsági API-kulcs a g4f.dev/members.html oldalról; a korlátok változhatnak.", "g4f-groq": "Távoli g4f.space átjáró a(z) Groq szolgáltatáshoz. A névtelen hozzáféréshez proof-of-work kreditek kellenek a g4f.dev/chat oldalról, vagy tagsági API-kulcs a g4f.dev/members.html oldalról; a korlátok változhatnak.", diff --git a/src/i18n/messages/id.json b/src/i18n/messages/id.json index d80ff9639940..d5d13cea2446 100644 --- a/src/i18n/messages/id.json +++ b/src/i18n/messages/id.json @@ -6254,7 +6254,7 @@ "galadriel": "Hubungkan Galadriel dengan kunci API.", "predibase": "Kredit uji coba gratis $25 (validitas 30 hari)", "chenzk": "Gateway yang kompatibel dengan OpenAI dengan katalog model langsung di chenzk.top.", - "magnific": "Hasilkan gambar dengan Mystic API dari Freepik.", + "magnific": "Hasilkan gambar dengan Mystic API dari Magnific.", "freetheai": "Gateway gratis yang kompatibel dengan OpenAI dengan dukungan model passthrough.", "g4f-gemini": "Gateway g4f.space jarak jauh ke Gemini. Akses anonim memerlukan kredit proof-of-work dari g4f.dev/chat atau kunci API anggota dari g4f.dev/members.html; batas dapat berubah.", "g4f-groq": "Gateway g4f.space jarak jauh ke Groq. Akses anonim memerlukan kredit proof-of-work dari g4f.dev/chat atau kunci API anggota dari g4f.dev/members.html; batas dapat berubah.", diff --git a/src/i18n/messages/in.json b/src/i18n/messages/in.json index 1e00cabb4277..8f4e3c4ae6fa 100644 --- a/src/i18n/messages/in.json +++ b/src/i18n/messages/in.json @@ -6254,7 +6254,7 @@ "galadriel": "Hubungkan Galadriel dengan kunci API.", "predibase": "Kredit uji coba gratis $25 (validitas 30 hari)", "chenzk": "Gateway yang kompatibel dengan OpenAI dengan katalog model langsung di chenzk.top.", - "magnific": "Hasilkan gambar dengan Mystic API dari Freepik.", + "magnific": "Hasilkan gambar dengan Mystic API dari Magnific.", "freetheai": "Gateway gratis yang kompatibel dengan OpenAI dengan dukungan model passthrough.", "g4f-gemini": "Gateway g4f.space jarak jauh ke Gemini. Akses anonim memerlukan kredit proof-of-work dari g4f.dev/chat atau kunci API anggota dari g4f.dev/members.html; batas dapat berubah.", "g4f-groq": "Gateway g4f.space jarak jauh ke Groq. Akses anonim memerlukan kredit proof-of-work dari g4f.dev/chat atau kunci API anggota dari g4f.dev/members.html; batas dapat berubah.", diff --git a/src/i18n/messages/it.json b/src/i18n/messages/it.json index 418340127d3f..6fe986605cd8 100644 --- a/src/i18n/messages/it.json +++ b/src/i18n/messages/it.json @@ -6254,7 +6254,7 @@ "galadriel": "Connetti Galadriel con una chiave API.", "predibase": "$25 di crediti di prova gratuiti (validità di 30 giorni)", "chenzk": "Gateway compatibile con OpenAI con un catalogo di modelli live su chenzk.top.", - "magnific": "Genera immagini con la Mystic API di Freepik.", + "magnific": "Genera immagini con la Mystic API di Magnific.", "freetheai": "Gateway gratuito compatibile con OpenAI con supporto per modelli passthrough.", "g4f-gemini": "Gateway remoto g4f.space verso Gemini. L’accesso anonimo richiede crediti proof-of-work da g4f.dev/chat oppure una chiave API membro da g4f.dev/members.html; i limiti variano.", "g4f-groq": "Gateway remoto g4f.space verso Groq. L’accesso anonimo richiede crediti proof-of-work da g4f.dev/chat oppure una chiave API membro da g4f.dev/members.html; i limiti variano.", diff --git a/src/i18n/messages/ja.json b/src/i18n/messages/ja.json index bf9ee24516a0..1dfd0ec393d0 100644 --- a/src/i18n/messages/ja.json +++ b/src/i18n/messages/ja.json @@ -6254,7 +6254,7 @@ "galadriel": "APIキーを使用してGaladrielに接続します。", "predibase": "$25の無料トライアルクレジット(有効期限30日間)", "chenzk": "chenzk.top でライブモデルカタログを提供するOpenAI互換ゲートウェイ。", - "magnific": "FreepikのMystic APIで画像を生成します。", + "magnific": "MagnificのMystic APIで画像を生成します。", "freetheai": "パススルーモデルをサポートする無料のOpenAI互換ゲートウェイ。", "g4f-gemini": "Gemini 向けのリモート g4f.space ゲートウェイ。匿名アクセスには g4f.dev/chat のプルーフ・オブ・ワーククレジット、または g4f.dev/members.html のメンバー API キーが必要です。上限は変動します。", "g4f-groq": "Groq 向けのリモート g4f.space ゲートウェイ。匿名アクセスには g4f.dev/chat のプルーフ・オブ・ワーククレジット、または g4f.dev/members.html のメンバー API キーが必要です。上限は変動します。", diff --git a/src/i18n/messages/ko.json b/src/i18n/messages/ko.json index a619f2729861..9bee2df7fef9 100644 --- a/src/i18n/messages/ko.json +++ b/src/i18n/messages/ko.json @@ -6254,7 +6254,7 @@ "galadriel": "API 키로 Galadriel을 연결합니다.", "predibase": "$25 무료 체험 크레딧 (30일 유효)", "chenzk": "chenzk.top의 실시간 모델 카탈로그를 지원하는 OpenAI 호환 게이트웨이입니다.", - "magnific": "Freepik의 Mystic API로 이미지를 생성합니다.", + "magnific": "Magnific의 Mystic API로 이미지를 생성합니다.", "freetheai": "패스스루 모델을 지원하는 무료 OpenAI 호환 게이트웨이입니다.", "g4f-gemini": "Gemini용 원격 g4f.space 게이트웨이입니다. 익명 액세스에는 g4f.dev/chat의 작업 증명 크레딧 또는 g4f.dev/members.html의 회원 API 키가 필요하며 한도는 달라질 수 있습니다.", "g4f-groq": "Groq용 원격 g4f.space 게이트웨이입니다. 익명 액세스에는 g4f.dev/chat의 작업 증명 크레딧 또는 g4f.dev/members.html의 회원 API 키가 필요하며 한도는 달라질 수 있습니다.", diff --git a/src/i18n/messages/mr.json b/src/i18n/messages/mr.json index ee61bd366288..3893f64ee2c1 100644 --- a/src/i18n/messages/mr.json +++ b/src/i18n/messages/mr.json @@ -6254,7 +6254,7 @@ "galadriel": "API की सह Galadriel कनेक्ट करा.", "predibase": "$25 मोफत ट्रायल क्रेडिट्स (30 दिवसांची वैधता)", "chenzk": "chenzk.top वर थेट मॉडेल कॅटलॉगसह OpenAI-सुसंगत गेटवे.", - "magnific": "Freepik च्या Mystic API सह प्रतिमा तयार करा.", + "magnific": "Magnific च्या Mystic API सह प्रतिमा तयार करा.", "freetheai": "passthrough मॉडेल समर्थनासह मोफत OpenAI-सुसंगत गेटवे.", "g4f-gemini": "Gemini साठी दूरस्थ g4f.space गेटवे. अनामिक प्रवेशासाठी g4f.dev/chat वरील प्रूफ-ऑफ-वर्क क्रेडिट किंवा g4f.dev/members.html वरील सदस्य API की आवश्यक आहे; मर्यादा बदलतात.", "g4f-groq": "Groq साठी दूरस्थ g4f.space गेटवे. अनामिक प्रवेशासाठी g4f.dev/chat वरील प्रूफ-ऑफ-वर्क क्रेडिट किंवा g4f.dev/members.html वरील सदस्य API की आवश्यक आहे; मर्यादा बदलतात.", diff --git a/src/i18n/messages/ms.json b/src/i18n/messages/ms.json index c326f2ae7d01..56e7cabc147d 100644 --- a/src/i18n/messages/ms.json +++ b/src/i18n/messages/ms.json @@ -6254,7 +6254,7 @@ "galadriel": "Sambungkan Galadriel dengan kunci API.", "predibase": "Kredit percubaan percuma $25 (tempoh sah 30 hari)", "chenzk": "Gerbang serasi OpenAI dengan katalog model langsung di chenzk.top.", - "magnific": "Jana imej dengan API Mystic Freepik.", + "magnific": "Jana imej dengan API Mystic Magnific.", "freetheai": "Gerbang serasi OpenAI percuma dengan sokongan model passthrough.", "g4f-gemini": "Gerbang g4f.space jauh ke Gemini. Akses tanpa nama memerlukan kredit bukti kerja daripada g4f.dev/chat atau kunci API ahli daripada g4f.dev/members.html; had berbeza-beza.", "g4f-groq": "Gerbang g4f.space jauh ke Groq. Akses tanpa nama memerlukan kredit bukti kerja daripada g4f.dev/chat atau kunci API ahli daripada g4f.dev/members.html; had berbeza-beza.", diff --git a/src/i18n/messages/nl.json b/src/i18n/messages/nl.json index aad6b2ab1326..a3505e04b283 100644 --- a/src/i18n/messages/nl.json +++ b/src/i18n/messages/nl.json @@ -6254,7 +6254,7 @@ "galadriel": "Verbind Galadriel met een API-sleutel.", "predibase": "$25 gratis proeftegoed (30 dagen geldig)", "chenzk": "OpenAI-compatibele gateway met een live modelcatalogus op chenzk.top.", - "magnific": "Genereer afbeeldingen met de Mystic API van Freepik.", + "magnific": "Genereer afbeeldingen met de Mystic API van Magnific.", "freetheai": "Gratis OpenAI-compatibele gateway met ondersteuning voor passthrough-modellen.", "g4f-gemini": "Externe g4f.space-gateway naar Gemini. Anonieme toegang vereist proof-of-work-credits van g4f.dev/chat of een API-sleutel voor leden van g4f.dev/members.html; limieten variëren.", "g4f-groq": "Externe g4f.space-gateway naar Groq. Anonieme toegang vereist proof-of-work-credits van g4f.dev/chat of een API-sleutel voor leden van g4f.dev/members.html; limieten variëren.", diff --git a/src/i18n/messages/no.json b/src/i18n/messages/no.json index dc8c55508de1..35027862fac6 100644 --- a/src/i18n/messages/no.json +++ b/src/i18n/messages/no.json @@ -6254,7 +6254,7 @@ "galadriel": "Koble til Galadriel med en API-nøkkel.", "predibase": "$25 gratis prøveperiode-kreditt (30 dagers gyldighet)", "chenzk": "OpenAI-kompatibel gateway med en live modellkatalog på chenzk.top.", - "magnific": "Generer bilder med Freepiks Mystic-API.", + "magnific": "Generer bilder med Magnifics Mystic-API.", "freetheai": "Gratis OpenAI-kompatibel gateway med passthrough-modellstøtte.", "g4f-gemini": "Ekstern g4f.space-gateway til Gemini. Anonym tilgang krever proof-of-work-kreditter fra g4f.dev/chat eller en medlems-API-nøkkel fra g4f.dev/members.html; grensene varierer.", "g4f-groq": "Ekstern g4f.space-gateway til Groq. Anonym tilgang krever proof-of-work-kreditter fra g4f.dev/chat eller en medlems-API-nøkkel fra g4f.dev/members.html; grensene varierer.", diff --git a/src/i18n/messages/phi.json b/src/i18n/messages/phi.json index 64394f65a278..a14c79d53d6c 100644 --- a/src/i18n/messages/phi.json +++ b/src/i18n/messages/phi.json @@ -6254,7 +6254,7 @@ "galadriel": "Ikonekta ang Galadriel gamit ang isang API key.", "predibase": "$25 na libreng trial credits (30-araw na validity)", "chenzk": "OpenAI-compatible na gateway na may live model catalog sa chenzk.top.", - "magnific": "Bumuo ng mga larawan gamit ang Mystic API ng Freepik.", + "magnific": "Bumuo ng mga larawan gamit ang Mystic API ng Magnific.", "freetheai": "Libreng OpenAI-compatible na gateway na may suporta sa passthrough model.", "g4f-gemini": "Remote na g4f.space gateway papunta sa Gemini. Ang anonymous access ay nangangailangan ng proof-of-work credits mula sa g4f.dev/chat o member API key mula sa g4f.dev/members.html; nagbabago ang mga limitasyon.", "g4f-groq": "Remote na g4f.space gateway papunta sa Groq. Ang anonymous access ay nangangailangan ng proof-of-work credits mula sa g4f.dev/chat o member API key mula sa g4f.dev/members.html; nagbabago ang mga limitasyon.", diff --git a/src/i18n/messages/pl.json b/src/i18n/messages/pl.json index a8e915dec974..635e56ede39f 100644 --- a/src/i18n/messages/pl.json +++ b/src/i18n/messages/pl.json @@ -6254,7 +6254,7 @@ "galadriel": "Połącz z Galadriel za pomocą klucza API.", "predibase": "$25 darmowych środków próbnych (ważność 30 dni)", "chenzk": "Brama zgodna z OpenAI z aktywnym katalogiem modeli na chenzk.top.", - "magnific": "Generuj obrazy za pomocą Mystic API od Freepik.", + "magnific": "Generuj obrazy za pomocą Mystic API od Magnific.", "freetheai": "Darmowa brama zgodna z OpenAI z obsługą modeli w trybie passthrough.", "g4f-gemini": "Zdalna brama g4f.space do Gemini. Dostęp anonimowy wymaga kredytów proof-of-work z g4f.dev/chat albo członkowskiego klucza API z g4f.dev/members.html; limity są zmienne.", "g4f-groq": "Zdalna brama g4f.space do Groq. Dostęp anonimowy wymaga kredytów proof-of-work z g4f.dev/chat albo członkowskiego klucza API z g4f.dev/members.html; limity są zmienne.", diff --git a/src/i18n/messages/pt-BR.json b/src/i18n/messages/pt-BR.json index 5045da054df4..2b4c33958cf3 100644 --- a/src/i18n/messages/pt-BR.json +++ b/src/i18n/messages/pt-BR.json @@ -6255,7 +6255,7 @@ "galadriel": "Conecte o Galadriel com uma chave de API.", "predibase": "$25 em créditos de teste gratuitos (validade de 30 dias)", "chenzk": "Gateway compatível com OpenAI com um catálogo de modelos ao vivo em chenzk.top.", - "magnific": "Gere imagens com a API Mystic do Freepik.", + "magnific": "Gere imagens com a API Mystic do Magnific.", "freetheai": "Gateway gratuito compatível com OpenAI com suporte a modelos via passthrough.", "g4f-gemini": "Gateway remoto do g4f.space para o Gemini. O acesso anônimo exige créditos de prova de trabalho do g4f.dev/chat ou uma chave de API de membro do g4f.dev/members.html; os limites variam.", "g4f-groq": "Gateway remoto do g4f.space para o Groq. O acesso anônimo exige créditos de prova de trabalho do g4f.dev/chat ou uma chave de API de membro do g4f.dev/members.html; os limites variam.", diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json index f96e87fe587d..51c244026c3f 100644 --- a/src/i18n/messages/pt.json +++ b/src/i18n/messages/pt.json @@ -6254,7 +6254,7 @@ "galadriel": "Ligue a Galadriel com uma chave de API.", "predibase": "$25 em créditos de avaliação gratuita (validade de 30 dias)", "chenzk": "Gateway compatível com a OpenAI com um catálogo de modelos em tempo real em chenzk.top.", - "magnific": "Gere imagens com a API Mystic da Freepik.", + "magnific": "Gere imagens com a API Mystic da Magnific.", "freetheai": "Gateway gratuito compatível com a OpenAI com suporte a modelos passthrough.", "g4f-gemini": "Gateway remoto do g4f.space para o Gemini. O acesso anónimo exige créditos de prova de trabalho do g4f.dev/chat ou uma chave de API de membro do g4f.dev/members.html; os limites variam.", "g4f-groq": "Gateway remoto do g4f.space para o Groq. O acesso anónimo exige créditos de prova de trabalho do g4f.dev/chat ou uma chave de API de membro do g4f.dev/members.html; os limites variam.", diff --git a/src/i18n/messages/ro.json b/src/i18n/messages/ro.json index 55daf538439e..74c569f4e701 100644 --- a/src/i18n/messages/ro.json +++ b/src/i18n/messages/ro.json @@ -6254,7 +6254,7 @@ "galadriel": "Conectați Galadriel cu o cheie API.", "predibase": "Credite de încercare gratuită de 25 $ (valabilitate 30 de zile)", "chenzk": "Gateway compatibil cu OpenAI cu un catalog live de modele la chenzk.top.", - "magnific": "Generați imagini cu API-ul Mystic de la Freepik.", + "magnific": "Generați imagini cu API-ul Mystic de la Magnific.", "freetheai": "Gateway gratuit compatibil cu OpenAI cu suport pentru modele passthrough.", "g4f-gemini": "Gateway g4f.space la distanță către Gemini. Accesul anonim necesită credite proof-of-work de la g4f.dev/chat sau o cheie API de membru de la g4f.dev/members.html; limitele variază.", "g4f-groq": "Gateway g4f.space la distanță către Groq. Accesul anonim necesită credite proof-of-work de la g4f.dev/chat sau o cheie API de membru de la g4f.dev/members.html; limitele variază.", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index e3b5bf78e263..eb7c2d137c8e 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -6254,7 +6254,7 @@ "galadriel": "Подключите Galadriel с помощью API-ключа.", "predibase": "Бесплатный пробный баланс $25 (срок действия 30 дней)", "chenzk": "Совместимый с OpenAI шлюз с актуальным каталогом моделей на chenzk.top.", - "magnific": "Генерация изображений с помощью Freepik Mystic API.", + "magnific": "Генерация изображений с помощью Magnific Mystic API.", "freetheai": "Бесплатный совместимый с OpenAI шлюз с поддержкой сквозной передачи моделей (passthrough).", "g4f-gemini": "Удалённый шлюз g4f.space к Gemini. Для анонимного доступа нужны proof-of-work-кредиты с g4f.dev/chat либо API-ключ участника с g4f.dev/members.html; лимиты меняются.", "g4f-groq": "Удалённый шлюз g4f.space к Groq. Для анонимного доступа нужны proof-of-work-кредиты с g4f.dev/chat либо API-ключ участника с g4f.dev/members.html; лимиты меняются.", diff --git a/src/i18n/messages/sk.json b/src/i18n/messages/sk.json index 15ea2fb32471..336f41045bf4 100644 --- a/src/i18n/messages/sk.json +++ b/src/i18n/messages/sk.json @@ -6254,7 +6254,7 @@ "galadriel": "Pripojte Galadriel pomocou API kľúča.", "predibase": "Bezplatný skúšobný kredit 25 $ (platnosť 30 dní)", "chenzk": "Brána kompatibilná s OpenAI so živým katalógom modelov na chenzk.top.", - "magnific": "Generujte obrázky pomocou Mystic API od Freepik.", + "magnific": "Generujte obrázky pomocou Mystic API od Magnific.", "freetheai": "Bezplatná brána kompatibilná s OpenAI s podporou prenosu modelov (passthrough).", "g4f-gemini": "Vzdialená brána g4f.space k Gemini. Anonymný prístup vyžaduje proof-of-work kredity z g4f.dev/chat alebo členský API kľúč z g4f.dev/members.html; limity sa menia.", "g4f-groq": "Vzdialená brána g4f.space k Groq. Anonymný prístup vyžaduje proof-of-work kredity z g4f.dev/chat alebo členský API kľúč z g4f.dev/members.html; limity sa menia.", diff --git a/src/i18n/messages/sv.json b/src/i18n/messages/sv.json index 05536ae95e3e..df8b11874c4a 100644 --- a/src/i18n/messages/sv.json +++ b/src/i18n/messages/sv.json @@ -6254,7 +6254,7 @@ "galadriel": "Anslut Galadriel med en API-nyckel.", "predibase": "$25 i gratis provkrediter (30 dagars giltighet)", "chenzk": "OpenAI-kompatibel gateway med en live-modellkatalog på chenzk.top.", - "magnific": "Generera bilder med Freepiks Mystic API.", + "magnific": "Generera bilder med Magnifics Mystic API.", "freetheai": "Gratis OpenAI-kompatibel gateway med stöd för passthrough-modeller.", "g4f-gemini": "Extern g4f.space-gateway till Gemini. Anonym åtkomst kräver proof-of-work-krediter från g4f.dev/chat eller en medlems-API-nyckel från g4f.dev/members.html; gränserna varierar.", "g4f-groq": "Extern g4f.space-gateway till Groq. Anonym åtkomst kräver proof-of-work-krediter från g4f.dev/chat eller en medlems-API-nyckel från g4f.dev/members.html; gränserna varierar.", diff --git a/src/i18n/messages/sw.json b/src/i18n/messages/sw.json index 8aecb555c0b1..d0af0bf7d66e 100644 --- a/src/i18n/messages/sw.json +++ b/src/i18n/messages/sw.json @@ -6254,7 +6254,7 @@ "galadriel": "Unganisha Galadriel kwa ufunguo wa API.", "predibase": "Salio la majaribio ya bure la $25 (uhalali wa siku 30)", "chenzk": "Lango linalooana na OpenAI lenye orodha ya miundo ya moja kwa moja kwenye chenzk.top.", - "magnific": "Zalisha picha kwa kutumia Mystic API ya Freepik.", + "magnific": "Zalisha picha kwa kutumia Mystic API ya Magnific.", "freetheai": "Lango la bure linalooana na OpenAI lenye usaidizi wa miundo ya passthrough.", "g4f-gemini": "Lango la mbali la g4f.space kwenda Gemini. Ufikiaji bila utambulisho unahitaji salio la uthibitisho wa kazi kutoka g4f.dev/chat au ufunguo wa API wa mwanachama kutoka g4f.dev/members.html; vikomo hubadilika.", "g4f-groq": "Lango la mbali la g4f.space kwenda Groq. Ufikiaji bila utambulisho unahitaji salio la uthibitisho wa kazi kutoka g4f.dev/chat au ufunguo wa API wa mwanachama kutoka g4f.dev/members.html; vikomo hubadilika.", diff --git a/src/i18n/messages/ta.json b/src/i18n/messages/ta.json index 7688bac23798..00001381eece 100644 --- a/src/i18n/messages/ta.json +++ b/src/i18n/messages/ta.json @@ -6254,7 +6254,7 @@ "galadriel": "API key மூலம் Galadriel-ஐ இணைக்கவும்.", "predibase": "$25 இலவச சோதனை கிரெடிட்கள் (30 நாள் செல்லுபடியாகும்)", "chenzk": "chenzk.top இல் நேரடி மாடல் பட்டியலுடன் கூடிய OpenAI-இணக்கமான gateway.", - "magnific": "Freepik-இன் Mystic API மூலம் படங்களை உருவாக்கவும்.", + "magnific": "Magnific-இன் Mystic API மூலம் படங்களை உருவாக்கவும்.", "freetheai": "passthrough மாடல் ஆதரவுடன் கூடிய இலவச OpenAI-இணக்கமான gateway.", "g4f-gemini": "Gemini-க்கான தொலைநிலை g4f.space நுழைவாயில். பெயரற்ற அணுகலுக்கு g4f.dev/chat-இன் proof-of-work கிரெடிட்கள் அல்லது g4f.dev/members.html-இன் உறுப்பினர் API சாவி தேவை; வரம்புகள் மாறுபடும்.", "g4f-groq": "Groq-க்கான தொலைநிலை g4f.space நுழைவாயில். பெயரற்ற அணுகலுக்கு g4f.dev/chat-இன் proof-of-work கிரெடிட்கள் அல்லது g4f.dev/members.html-இன் உறுப்பினர் API சாவி தேவை; வரம்புகள் மாறுபடும்.", diff --git a/src/i18n/messages/te.json b/src/i18n/messages/te.json index d2b2de8106ec..c9f9be20a457 100644 --- a/src/i18n/messages/te.json +++ b/src/i18n/messages/te.json @@ -6254,7 +6254,7 @@ "galadriel": "API కీతో Galadrielని కనెక్ట్ చేయండి.", "predibase": "$25 ఉచిత ట్రయల్ క్రెడిట్‌లు (30 రోజుల చెల్లుబాటు)", "chenzk": "chenzk.top వద్ద లైవ్ మోడల్ కేటలాగ్‌తో కూడిన OpenAI-అనుకూల గేట్‌వే.", - "magnific": "Freepik యొక్క Mystic APIతో చిత్రాలను రూపొందించండి.", + "magnific": "Magnific యొక్క Mystic APIతో చిత్రాలను రూపొందించండి.", "freetheai": "పాస్‌త్రూ మోడల్ మద్దతుతో కూడిన ఉచిత OpenAI-అనుకూల గేట్‌వే.", "g4f-gemini": "Gemini కోసం రిమోట్ g4f.space గేట్‌వే. అనామక యాక్సెస్‌కు g4f.dev/chat నుంచి ప్రూఫ్-ఆఫ్-వర్క్ క్రెడిట్లు లేదా g4f.dev/members.html నుంచి సభ్యుల API కీ అవసరం; పరిమితులు మారుతాయి.", "g4f-groq": "Groq కోసం రిమోట్ g4f.space గేట్‌వే. అనామక యాక్సెస్‌కు g4f.dev/chat నుంచి ప్రూఫ్-ఆఫ్-వర్క్ క్రెడిట్లు లేదా g4f.dev/members.html నుంచి సభ్యుల API కీ అవసరం; పరిమితులు మారుతాయి.", diff --git a/src/i18n/messages/th.json b/src/i18n/messages/th.json index 2c5311661fff..3bb2501fce0a 100644 --- a/src/i18n/messages/th.json +++ b/src/i18n/messages/th.json @@ -6254,7 +6254,7 @@ "galadriel": "เชื่อมต่อ Galadriel ด้วยคีย์ API", "predibase": "เครดิตทดลองใช้ฟรี $25 (มีอายุ 30 วัน)", "chenzk": "เกตเวย์ที่เข้ากันได้กับ OpenAI พร้อมแคตตาล็อกโมเดลแบบสดที่ chenzk.top", - "magnific": "สร้างรูปภาพด้วย Mystic API ของ Freepik", + "magnific": "สร้างรูปภาพด้วย Mystic API ของ Magnific", "freetheai": "เกตเวย์ฟรีที่เข้ากันได้กับ OpenAI พร้อมการรองรับโมเดลแบบ passthrough", "g4f-gemini": "เกตเวย์ g4f.space ระยะไกลไปยัง Gemini การเข้าถึงแบบไม่ระบุตัวตนต้องใช้เครดิต proof-of-work จาก g4f.dev/chat หรือคีย์ API สมาชิกจาก g4f.dev/members.html โดยขีดจำกัดอาจเปลี่ยนแปลงได้", "g4f-groq": "เกตเวย์ g4f.space ระยะไกลไปยัง Groq การเข้าถึงแบบไม่ระบุตัวตนต้องใช้เครดิต proof-of-work จาก g4f.dev/chat หรือคีย์ API สมาชิกจาก g4f.dev/members.html โดยขีดจำกัดอาจเปลี่ยนแปลงได้", diff --git a/src/i18n/messages/tr.json b/src/i18n/messages/tr.json index 6374c3ff7945..1c9f0af45a4b 100644 --- a/src/i18n/messages/tr.json +++ b/src/i18n/messages/tr.json @@ -6254,7 +6254,7 @@ "galadriel": "Galadriel'i bir API anahtarı ile bağlayın.", "predibase": "25$ ücretsiz deneme kredisi (30 gün geçerli)", "chenzk": "chenzk.top adresinde canlı model kataloğuna sahip OpenAI uyumlu ağ geçidi.", - "magnific": "Freepik'in Mystic API'si ile görseller oluşturun.", + "magnific": "Magnific'in Mystic API'si ile görseller oluşturun.", "freetheai": "Doğrudan geçişli (passthrough) model destekli, ücretsiz OpenAI uyumlu ağ geçidi.", "g4f-gemini": "Gemini için uzak g4f.space ağ geçidi. Anonim erişim g4f.dev/chat üzerinden proof-of-work kredileri veya g4f.dev/members.html üzerinden üye API anahtarı gerektirir; sınırlar değişkendir.", "g4f-groq": "Groq için uzak g4f.space ağ geçidi. Anonim erişim g4f.dev/chat üzerinden proof-of-work kredileri veya g4f.dev/members.html üzerinden üye API anahtarı gerektirir; sınırlar değişkendir.", diff --git a/src/i18n/messages/uk-UA.json b/src/i18n/messages/uk-UA.json index 8d05ba96adc0..377a716da4d7 100644 --- a/src/i18n/messages/uk-UA.json +++ b/src/i18n/messages/uk-UA.json @@ -6254,7 +6254,7 @@ "galadriel": "Connect Galadriel with an API key.", "predibase": "$25 free trial credits (30-day validity)", "chenzk": "OpenAI-compatible gateway with a live model catalog at chenzk.top.", - "magnific": "Generate images with Freepik's Mystic API.", + "magnific": "Generate images with Magnific's Mystic API.", "freetheai": "Free OpenAI-compatible gateway with passthrough model support.", "g4f-gemini": "Віддалений шлюз g4f.space до Gemini. Для анонімного доступу потрібні proof-of-work-кредити з g4f.dev/chat або API-ключ учасника з g4f.dev/members.html; ліміти змінюються.", "g4f-groq": "Віддалений шлюз g4f.space до Groq. Для анонімного доступу потрібні proof-of-work-кредити з g4f.dev/chat або API-ключ учасника з g4f.dev/members.html; ліміти змінюються.", diff --git a/src/i18n/messages/ur.json b/src/i18n/messages/ur.json index 60d562f8cdce..7ac8e0ddbb35 100644 --- a/src/i18n/messages/ur.json +++ b/src/i18n/messages/ur.json @@ -6254,7 +6254,7 @@ "galadriel": "Galadriel کو ایک API کلید کے ساتھ منسلک کریں۔", "predibase": "$25 کے مفت ٹرائل کریڈٹس (30 دن کی میعاد)", "chenzk": "chenzk.top پر لائیو ماڈل کیٹلاگ کے ساتھ OpenAI سے مطابقت رکھنے والا گیٹ وے۔", - "magnific": "Freepik کے Mystic API کے ساتھ تصاویر تیار کریں۔", + "magnific": "Magnific کے Mystic API کے ساتھ تصاویر تیار کریں۔", "freetheai": "passthrough ماڈل سپورٹ کے ساتھ مفت OpenAI سے مطابقت رکھنے والا گیٹ وے۔", "g4f-gemini": "Gemini کے لیے ریموٹ g4f.space گیٹ وے۔ گمنام رسائی کے لیے g4f.dev/chat سے پروف آف ورک کریڈٹس یا g4f.dev/members.html سے رکن API کلید درکار ہے؛ حدود بدلتی رہتی ہیں۔", "g4f-groq": "Groq کے لیے ریموٹ g4f.space گیٹ وے۔ گمنام رسائی کے لیے g4f.dev/chat سے پروف آف ورک کریڈٹس یا g4f.dev/members.html سے رکن API کلید درکار ہے؛ حدود بدلتی رہتی ہیں۔", diff --git a/src/i18n/messages/vi.json b/src/i18n/messages/vi.json index 4f211194b9a5..2d5b27d7b78c 100644 --- a/src/i18n/messages/vi.json +++ b/src/i18n/messages/vi.json @@ -6255,7 +6255,7 @@ "galadriel": "Kết nối Galadriel bằng khóa API.", "predibase": "25 USD tín dụng dùng thử miễn phí (có hiệu lực 30 ngày)", "chenzk": "Gateway tương thích OpenAI với danh mục mô hình trực tiếp tại chenzk.top.", - "magnific": "Tạo hình ảnh bằng API Mystic của Freepik.", + "magnific": "Tạo hình ảnh bằng API Mystic của Magnific.", "freetheai": "Gateway miễn phí tương thích OpenAI, hỗ trợ chuyển tiếp mô hình.", "g4f-gemini": "Cổng g4f.space từ xa tới Gemini. Truy cập ẩn danh cần tín dụng proof-of-work từ g4f.dev/chat hoặc khóa API thành viên từ g4f.dev/members.html; các giới hạn có thể thay đổi.", "g4f-groq": "Cổng g4f.space từ xa tới Groq. Truy cập ẩn danh cần tín dụng proof-of-work từ g4f.dev/chat hoặc khóa API thành viên từ g4f.dev/members.html; các giới hạn có thể thay đổi.", diff --git a/src/i18n/messages/zh-CN.json b/src/i18n/messages/zh-CN.json index 24f7dec02eb1..e4dd873a2b48 100644 --- a/src/i18n/messages/zh-CN.json +++ b/src/i18n/messages/zh-CN.json @@ -6254,7 +6254,7 @@ "galadriel": "使用 API 密钥连接 Galadriel。", "predibase": "$25 免费试用额度(30 天有效期)", "chenzk": "兼容 OpenAI 的网关,在 chenzk.top 提供实时模型目录。", - "magnific": "使用 Freepik 的 Mystic API 生成图像。", + "magnific": "使用 Magnific 的 Mystic API 生成图像。", "freetheai": "免费的 OpenAI 兼容网关,支持直通模型。", "g4f-gemini": "通往 Gemini 的远程 g4f.space 网关。匿名访问需要来自 g4f.dev/chat 的工作量证明额度,或来自 g4f.dev/members.html 的会员 API 密钥;限制可能变化。", "g4f-groq": "通往 Groq 的远程 g4f.space 网关。匿名访问需要来自 g4f.dev/chat 的工作量证明额度,或来自 g4f.dev/members.html 的会员 API 密钥;限制可能变化。", diff --git a/src/i18n/messages/zh-TW.json b/src/i18n/messages/zh-TW.json index 65323c7d8da1..fe31edbe717c 100644 --- a/src/i18n/messages/zh-TW.json +++ b/src/i18n/messages/zh-TW.json @@ -6254,7 +6254,7 @@ "galadriel": "使用 API 金鑰連線 Galadriel。", "predibase": "$25 美元免費試用額度(30 天有效期)", "chenzk": "OpenAI 相容閘道,在 chenzk.top 提供即時模型目錄。", - "magnific": "使用 Freepik 的 Mystic API 生成圖片。", + "magnific": "使用 Magnific 的 Mystic API 生成圖片。", "freetheai": "免費的 OpenAI 相容閘道,支援透傳模型。", "g4f-gemini": "連往 Gemini 的遠端 g4f.space 閘道。匿名存取需要來自 g4f.dev/chat 的工作量證明額度,或來自 g4f.dev/members.html 的會員 API 金鑰;限制可能變動。", "g4f-groq": "連往 Groq 的遠端 g4f.space 閘道。匿名存取需要來自 g4f.dev/chat 的工作量證明額度,或來自 g4f.dev/members.html 的會員 API 金鑰;限制可能變動。", From d2ad71cf56955c9f19e1776ca4b46fb9bf8464c8 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sat, 29 Aug 2026 08:10:13 -0300 Subject: [PATCH 10/91] fix(kie): route flux/kontext to its dedicated endpoint, not the Market createTask flow (#11296) (#11985) flux/kontext is catalogued with isMarket: true, so handleKieImageGeneration routed it through KIE's unified Market createTask endpoint with model: "flux/kontext". KIE does not expose Flux Kontext through the Market catalog at all -- it lives under a dedicated API tree (POST /api/v1/flux/kontext/generate, poll GET /api/v1/flux/kontext/record-info, models flux-kontext-pro/flux-kontext-max) -- so the Market endpoint rejected it with "model name not supported", matching the reporter's exact error text. Special-case flux/kontext ahead of the isMarket branch so it hits the dedicated endpoint/payload shape instead of being treated as a Market entry. z-image/4.0-*/4.5-* remains intentionally untouched (still blocked on reporter/live confirmation per the existing in-code comment). Co-authored-by: Markus Hartung --- ...296-kie-flux-kontext-dedicated-endpoint.md | 1 + open-sse/handlers/imageGeneration.ts | 62 +++++--- ...kie-market-upstream-model-id-11225.test.ts | 134 ++++++++++++++++++ 3 files changed, 178 insertions(+), 19 deletions(-) create mode 100644 changelog.d/fixes/11296-kie-flux-kontext-dedicated-endpoint.md diff --git a/changelog.d/fixes/11296-kie-flux-kontext-dedicated-endpoint.md b/changelog.d/fixes/11296-kie-flux-kontext-dedicated-endpoint.md new file mode 100644 index 000000000000..f6ece679ae82 --- /dev/null +++ b/changelog.d/fixes/11296-kie-flux-kontext-dedicated-endpoint.md @@ -0,0 +1 @@ +- **fix(kie):** reroute `flux/kontext` off the KIE Market `createTask` flow — it is catalogued with `isMarket: true` but has no Market catalog page, so KIE rejected it with "model name not supported"; it now hits the dedicated `POST /api/v1/flux/kontext/generate` / `GET /api/v1/flux/kontext/record-info` endpoints instead (#11296). diff --git a/open-sse/handlers/imageGeneration.ts b/open-sse/handlers/imageGeneration.ts index f6134f6c05d0..6e0405d7fa3b 100644 --- a/open-sse/handlers/imageGeneration.ts +++ b/open-sse/handlers/imageGeneration.ts @@ -119,20 +119,21 @@ interface KieImageOptions { // ideogram/v3-reframe has no dedicated docs.kie.ai page as of this sweep // (its 3 siblings above are all direct id matches, so it is assumed // correct by pattern, not independently confirmed). -// Two catalog entries remain UNRESOLVED after this sweep and are -// deliberately left untouched pending a follow-up (see #11296 discussion): +// One catalog entry remains UNRESOLVED after this sweep and is deliberately +// left untouched pending a follow-up (see #11296 discussion): // - z-image/4.0-text-to-image and z-image/4.5-text-to-image: the only // documented Z-Image Market page (docs.kie.ai/market/z-image/z-image) // shows a single fixed `model` enum value `"z-image"` with no // version-specific id or "version" input field found — unclear whether // both catalog ids should collapse to the same upstream call. -// - flux/kontext: no `docs.kie.ai/market/flux2/kontext` (or similar) -// Market page exists; Flux Kontext is documented under the separate -// `/flux-kontext-api/*` docs tree with its own endpoint -// (`POST /api/v1/flux/kontext/generate`, models `flux-kontext-pro`/ -// `flux-kontext-max`), not the Market `createTask` flow this map feeds. -// This entry may be miscatalogued as `isMarket: true` and need a -// dedicated reroute rather than an id rewrite. +// flux/kontext is RESOLVED (#11296): it is catalogued with `isMarket: true` +// but has no `docs.kie.ai/market/flux2/kontext` (or similar) Market page — +// Flux Kontext is documented under the separate `/flux-kontext-api/*` docs +// tree with its own endpoint (`POST /api/v1/flux/kontext/generate`, poll +// `GET /api/v1/flux/kontext/record-info`, models `flux-kontext-pro`/ +// `flux-kontext-max`), not the Market `createTask` flow this map feeds. It is +// NOT in KIE_MARKET_UPSTREAM_MODEL_IDS below on purpose — handleKieImageGeneration +// reroutes it to the dedicated endpoint instead of rewriting its id. export const KIE_MARKET_UPSTREAM_MODEL_IDS: ReadonlyMap = new Map([ ["google-imagen/nano-banana", "google/nano-banana"], ["google-imagen/nano-banana-2", "nano-banana-2"], @@ -815,13 +816,29 @@ async function handleKieImageGeneration({ // Check if model is a Market model (unified API) const fullRegistry = getImageProvider(provider); const modelEntry = fullRegistry?.models?.find((m) => m.id === model); - const isMarket = modelEntry?.isMarket || model.includes("/"); + // #11296 — flux/kontext is catalogued with `isMarket: true`, but KIE does not + // expose it through the Market catalog at all: it lives under a dedicated API + // tree (POST /api/v1/flux/kontext/generate, poll .../flux/kontext/record-info) + // that rejects the Market createTask flow with "model name not supported". Route + // it there instead of treating it as a Market entry (see KIE_MARKET_UPSTREAM_MODEL_IDS + // comment above for the same finding). + const isFluxKontext = model === "flux/kontext"; + const isMarket = !isFluxKontext && (modelEntry?.isMarket || model.includes("/")); const { imageUrl } = extractImageInputs(body); let baseUrl = ""; let payload: Record = {}; - if (isMarket) { + if (isFluxKontext) { + // Dedicated Flux Kontext API endpoint (not part of the Market catalog). + baseUrl = `${providerConfig.baseUrl.replace(/\/$/, "")}/api/v1/flux/kontext/generate`; + payload = { + prompt, + aspectRatio: mapImageSize(size), + model: "flux-kontext-pro", + ...(imageUrl ? { inputImage: imageUrl } : {}), + }; + } else if (isMarket) { // Unified Market API endpoint baseUrl = `${providerConfig.baseUrl.replace(/\/$/, "")}/api/v1/jobs/createTask`; const input: Record = { @@ -853,13 +870,18 @@ async function handleKieImageGeneration({ const promptPreview = String(body.prompt ?? "").slice(0, 60); log.info( "IMAGE", - `${provider}/${model} (${isMarket ? "market" : "direct"}) | prompt: "${promptPreview}..."` + `${provider}/${model} (${isFluxKontext ? "flux-kontext" : isMarket ? "market" : "direct"}) | prompt: "${promptPreview}..."` ); } try { - const endpoint = isMarket ? "/api/v1/jobs/createTask" : new URL(baseUrl).pathname; - const createBaseUrl = isMarket ? providerConfig.baseUrl : baseUrl.replace(endpoint, ""); + const endpoint = isFluxKontext + ? "/api/v1/flux/kontext/generate" + : isMarket + ? "/api/v1/jobs/createTask" + : new URL(baseUrl).pathname; + const createBaseUrl = + isFluxKontext || isMarket ? providerConfig.baseUrl : baseUrl.replace(endpoint, ""); const createData = await kieExecutor.createTask({ baseUrl: createBaseUrl, token, @@ -888,11 +910,13 @@ async function handleKieImageGeneration({ } // Use statusUrl from providerConfig if available, fallback to dynamic derivation - const statusUrl = isMarket - ? `${providerConfig.baseUrl.replace(/\/$/, "")}/api/v1/jobs/recordInfo` - : providerConfig.statusUrl && !providerConfig.statusUrl.includes("jobs/recordInfo") - ? providerConfig.statusUrl - : baseUrl.replace(/\/generate$/, "/record-info"); + const statusUrl = isFluxKontext + ? `${providerConfig.baseUrl.replace(/\/$/, "")}/api/v1/flux/kontext/record-info` + : isMarket + ? `${providerConfig.baseUrl.replace(/\/$/, "")}/api/v1/jobs/recordInfo` + : providerConfig.statusUrl && !providerConfig.statusUrl.includes("jobs/recordInfo") + ? providerConfig.statusUrl + : baseUrl.replace(/\/generate$/, "/record-info"); const { data: recordData, state } = await kieExecutor.pollTask({ statusUrl, diff --git a/tests/unit/kie-market-upstream-model-id-11225.test.ts b/tests/unit/kie-market-upstream-model-id-11225.test.ts index df81dc414db7..64be480d4c45 100644 --- a/tests/unit/kie-market-upstream-model-id-11225.test.ts +++ b/tests/unit/kie-market-upstream-model-id-11225.test.ts @@ -410,3 +410,137 @@ test("KIE direct image routing keeps the gpt4o-image endpoint and payload shape" globalThis.fetch = originalFetch; } }); + +// #11296 — flux/kontext is catalogued with `isMarket: true`, but KIE does not +// expose it through the Market catalog: it lives under a dedicated API tree +// (POST /api/v1/flux/kontext/generate, GET /api/v1/flux/kontext/record-info). +// Sending it through the Market createTask flow gets rejected with "model +// name not supported" -- these tests lock in the dedicated-endpoint reroute +// and guard against a future regression back to the Market flow. + +test("KIE flux/kontext routes to the dedicated Flux Kontext endpoint, never the Market createTask endpoint (#11296)", async () => { + const originalFetch = globalThis.fetch; + let createUrl = ""; + let createBody: Record | undefined; + let pollUrl = ""; + + globalThis.fetch = (async (url: unknown, options: { body?: unknown } = {}) => { + const stringUrl = String(url); + + if (stringUrl === "https://api.kie.ai/api/v1/flux/kontext/generate") { + createUrl = stringUrl; + createBody = JSON.parse(String(options.body ?? "{}")) as Record; + return new Response(JSON.stringify({ code: 200, data: { taskId: "kie-flux-kontext-1" } }), { + status: 200, + headers: { "content-type": "application/json" }, + }); + } + + if (stringUrl.startsWith("https://api.kie.ai/api/v1/flux/kontext/record-info")) { + pollUrl = stringUrl; + return new Response( + JSON.stringify({ + code: 200, + data: { + state: "success", + resultJson: JSON.stringify({ + resultUrls: ["https://example.com/kie-flux-kontext-image.png"], + }), + }, + }), + { status: 200, headers: { "content-type": "application/json" } } + ); + } + + // Any other URL (in particular the Market createTask/recordInfo endpoints) + // is the regression this test guards against. + throw new Error(`Unexpected URL: ${stringUrl}`); + }) as typeof globalThis.fetch; + + try { + const result = await handleImageGeneration({ + body: { + model: "kie/flux/kontext", + prompt: "a calm harbour at sunrise", + size: "1024x1024", + n: 1, + }, + credentials: { apiKey: "test-kie-key" }, + log: null, + }); + + assert.equal(result.success, true, "KIE flux/kontext generation should succeed"); + assert.equal(createUrl, "https://api.kie.ai/api/v1/flux/kontext/generate"); + assert.deepEqual(createBody, { + prompt: "a calm harbour at sunrise", + aspectRatio: "1:1", + model: "flux-kontext-pro", + }); + assert.equal(new URL(pollUrl).searchParams.get("taskId"), "kie-flux-kontext-1"); + assert.ok("data" in result, "successful KIE generation must return image data"); + assert.equal(result.data.data[0].url, "https://example.com/kie-flux-kontext-image.png"); + } finally { + globalThis.fetch = originalFetch; + } +}); + +test("KIE flux/kontext forwards an input image as 'inputImage' for edit calls (#11296)", async () => { + const originalFetch = globalThis.fetch; + let createBody: Record | undefined; + + globalThis.fetch = (async (url: unknown, options: { body?: unknown } = {}) => { + const stringUrl = String(url); + + if (stringUrl === "https://api.kie.ai/api/v1/flux/kontext/generate") { + createBody = JSON.parse(String(options.body ?? "{}")) as Record; + return new Response(JSON.stringify({ code: 200, data: { taskId: "kie-flux-kontext-2" } }), { + status: 200, + headers: { "content-type": "application/json" }, + }); + } + + if (stringUrl.startsWith("https://api.kie.ai/api/v1/flux/kontext/record-info")) { + return new Response( + JSON.stringify({ + code: 200, + data: { + state: "success", + resultJson: JSON.stringify({ + resultUrls: ["https://example.com/kie-flux-kontext-edit.png"], + }), + }, + }), + { status: 200, headers: { "content-type": "application/json" } } + ); + } + + throw new Error(`Unexpected URL: ${stringUrl}`); + }) as typeof globalThis.fetch; + + try { + const result = await handleImageGeneration({ + body: { + model: "kie/flux/kontext", + prompt: "add a lighthouse", + size: "1024x1024", + n: 1, + image: "https://example.com/source.png", + }, + credentials: { apiKey: "test-kie-key" }, + log: null, + }); + + assert.equal(result.success, true); + assert.equal(createBody?.inputImage, "https://example.com/source.png"); + } finally { + globalThis.fetch = originalFetch; + } +}); + +test("flux/kontext is not part of the KIE Market upstream id map (#11296)", () => { + assert.equal( + KIE_MARKET_UPSTREAM_MODEL_IDS.has("flux/kontext"), + false, + "flux/kontext is rerouted to a dedicated endpoint, not id-rewritten through the Market map" + ); +}); From 674cc5feb14e3b6a8b6fdc21b9c8cf07f87b7686 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sat, 29 Aug 2026 08:10:17 -0300 Subject: [PATCH 11/91] fix(db): rate-limit Arena ELO fetch-failure warnings on repeated timeouts (#11500) (#11989) * fix(db): rate-limit Arena ELO fetch-failure warnings on repeated timeouts (#11500) * test(quality): split the #11500 fetch-failure-dedup tests into their own file tests/unit/arena-elo-sync.test.ts crossed the 1000-line new-test-file cap (file-size gate, PR mode). The two new tests don't need the file's DB fixture (fetchArenaLeaderboards() never touches the DB), so they move to a self-contained sibling file instead of growing the frozen suite. --------- Co-authored-by: Markus Hartung --- .../fixes/11500-arena-elo-log-dedup.md | 1 + src/lib/arenaEloSync.ts | 30 +++- .../arena-elo-fetch-failure-log-dedup.test.ts | 154 ++++++++++++++++++ 3 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 changelog.d/fixes/11500-arena-elo-log-dedup.md create mode 100644 tests/unit/arena-elo-fetch-failure-log-dedup.test.ts diff --git a/changelog.d/fixes/11500-arena-elo-log-dedup.md b/changelog.d/fixes/11500-arena-elo-log-dedup.md new file mode 100644 index 000000000000..cead9e4b6aaf --- /dev/null +++ b/changelog.d/fixes/11500-arena-elo-log-dedup.md @@ -0,0 +1 @@ +- fix(db): rate-limit repeated Arena ELO leaderboard fetch-failure warnings instead of logging one per sync attempt (#11500) diff --git a/src/lib/arenaEloSync.ts b/src/lib/arenaEloSync.ts index 427763b4a8e3..15ed583df65d 100644 --- a/src/lib/arenaEloSync.ts +++ b/src/lib/arenaEloSync.ts @@ -212,6 +212,25 @@ export function normalizeModelName(rawName: string): string { // ─── Core: Fetch ───────────────────────────────────────── +/** + * How many consecutive per-category fetch failures to let through to + * `console.warn` before going quiet again. Timeouts against the Arena API + * repeat every sync cycle (see `startPeriodicSync()`), so logging every + * single one turns into log spam within a few hours (#11500). Mirrors the + * once-per-label dedup pattern used by `warnEmptyAutoPoolOnce()` + * (`open-sse/services/autoCombo/virtualFactory.ts`), except here the streak + * resets on the next successful fetch so a genuinely new outage warns again. + */ +const ARENA_ELO_FETCH_WARN_STREAK_INTERVAL = 10; + +/** Consecutive fetch-failure count per leaderboard category, since the last success. */ +const categoryFetchFailureStreak = new Map(); + +/** Test-only: reset the per-category fetch-failure streak dedup state. */ +export function resetArenaEloFetchFailureStreaksForTests(): void { + categoryFetchFailureStreak.clear(); +} + /** * Fetch leaderboards from the Arena AI API for all configured categories. * @@ -244,9 +263,18 @@ export async function fetchArenaLeaderboards(): Promise { `Arena API returned invalid JSON for "${category}" (${text.slice(0, 100)}...)` ); } + // Recovered: let the next failure streak warn from scratch again. + categoryFetchFailureStreak.delete(category); } catch (err) { const message = err instanceof Error ? err.message : String(err); - console.warn(`[ARENA_ELO_SYNC] Failed to fetch "${category}" leaderboard: ${message}`); + const streak = (categoryFetchFailureStreak.get(category) ?? 0) + 1; + categoryFetchFailureStreak.set(category, streak); + if (streak === 1 || streak % ARENA_ELO_FETCH_WARN_STREAK_INTERVAL === 0) { + console.warn( + `[ARENA_ELO_SYNC] Failed to fetch "${category}" leaderboard: ${message}` + + (streak > 1 ? ` (${streak} consecutive failures; further warnings rate-limited)` : "") + ); + } errors.push(message); } }); diff --git a/tests/unit/arena-elo-fetch-failure-log-dedup.test.ts b/tests/unit/arena-elo-fetch-failure-log-dedup.test.ts new file mode 100644 index 000000000000..e95e4a38271d --- /dev/null +++ b/tests/unit/arena-elo-fetch-failure-log-dedup.test.ts @@ -0,0 +1,154 @@ +/** + * Unit tests for the fetchArenaLeaderboards() consecutive-failure log-dedup + * added in src/lib/arenaEloSync.ts (#11500 sub-issue 3). + * + * Split out of arena-elo-sync.test.ts (#11500) — that file needs a full + * SQLite/DB fixture the sync path requires, which this fetch-only surface + * does not; keeping these two tests self-contained keeps the split honest + * and avoids pushing arena-elo-sync.test.ts's own (pre-existing) size past + * the file-size gate's new-test-file cap. + */ + +import { describe, it, afterEach } from "node:test"; +import assert from "node:assert/strict"; + +const { fetchArenaLeaderboards, resetArenaEloFetchFailureStreaksForTests } = await import( + "../../src/lib/arenaEloSync.ts" +); +import type { ArenaLeaderboardData, ArenaModelEntry } from "../../src/lib/arenaEloSync.ts"; + +const originalFetch = globalThis.fetch; + +function mockFetch(impl: (url: string, opts?: RequestInit) => Promise): void { + globalThis.fetch = impl as typeof fetch; +} + +function restoreFetch(): void { + globalThis.fetch = originalFetch; +} + +function jsonResponse(data: unknown, status = 200): Response { + return new Response(JSON.stringify(data), { + status, + headers: { "Content-Type": "application/json" }, + }); +} + +function makeModelEntry(overrides: Partial = {}): ArenaModelEntry { + return { + rank: 1, + model: "anthropic/claude-sonnet", + vendor: "Anthropic", + score: 1350, + ci: 10, + votes: 5000, + license: "proprietary", + ...overrides, + }; +} + +function makeLeaderboardData( + models: ArenaModelEntry[] = [], + category = "text" +): ArenaLeaderboardData { + return { + meta: { leaderboard: category, model_count: models.length }, + models, + }; +} + +afterEach(() => { + restoreFetch(); + resetArenaEloFetchFailureStreaksForTests(); +}); + +describe("fetchArenaLeaderboards() — consecutive-failure log dedup (#11500)", () => { + it("rate-limits the per-category fetch-failure warning across many consecutive timeouts", async () => { + resetArenaEloFetchFailureStreaksForTests(); + const originalWarn = console.warn; + const warnCalls: string[] = []; + console.warn = ((...args: unknown[]) => { + warnCalls.push(args.map(String).join(" ")); + }) as typeof console.warn; + + try { + mockFetch(async () => { + throw new Error("The operation was aborted due to timeout"); + }); + + const ATTEMPTS = 25; + for (let i = 0; i < ATTEMPTS; i++) { + await assert.rejects(() => fetchArenaLeaderboards()); + } + + const fetchFailureWarnings = warnCalls.filter((line) => + line.includes('Failed to fetch "text" leaderboard') + ); + + // One category × 25 consecutive-failure attempts would be 25 raw warns — + // the rate limiter must keep the emitted count far below that. + assert.ok( + fetchFailureWarnings.length < ATTEMPTS, + `expected fewer than ${ATTEMPTS} warnings, got ${fetchFailureWarnings.length}` + ); + assert.ok( + fetchFailureWarnings.length <= 5, + `expected the streak-gated warning to stay tightly bounded, got ${fetchFailureWarnings.length}` + ); + assert.ok(fetchFailureWarnings.length >= 1, "the first failure must still be logged"); + } finally { + console.warn = originalWarn; + resetArenaEloFetchFailureStreaksForTests(); + } + }); + + it("resets the fetch-failure streak after a successful fetch so the next outage warns again", async () => { + resetArenaEloFetchFailureStreaksForTests(); + const originalWarn = console.warn; + const warnCalls: string[] = []; + console.warn = ((...args: unknown[]) => { + warnCalls.push(args.map(String).join(" ")); + }) as typeof console.warn; + + try { + mockFetch(async () => { + throw new Error("timeout"); + }); + await assert.rejects(() => fetchArenaLeaderboards()); + await assert.rejects(() => fetchArenaLeaderboards()); + + const textData = makeLeaderboardData( + [makeModelEntry({ model: "recovered-model", score: 1200, votes: 5000, rank: 1 })], + "text" + ); + const codeData = makeLeaderboardData( + [makeModelEntry({ model: "recovered-code", score: 1200, votes: 5000, rank: 1 })], + "code" + ); + mockFetch(async (url: string) => { + if (url.includes("name=text")) return jsonResponse(textData); + if (url.includes("name=code")) return jsonResponse(codeData); + return new Response("Not found", { status: 404 }); + }); + await fetchArenaLeaderboards(); + + mockFetch(async () => { + throw new Error("timeout again"); + }); + warnCalls.length = 0; + await assert.rejects(() => fetchArenaLeaderboards()); + + const fetchFailureWarnings = warnCalls.filter((line) => + line.includes('Failed to fetch "text" leaderboard') + ); + assert.strictEqual( + fetchFailureWarnings.length, + 1, + "streak reset by the success must re-arm the first-failure warning" + ); + } finally { + console.warn = originalWarn; + resetArenaEloFetchFailureStreaksForTests(); + } + }); +}); From fb9cbe9566ea1fe0ba584d52e3cc65096094f788 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sat, 29 Aug 2026 08:10:21 -0300 Subject: [PATCH 12/91] fix(ci): pass --pass-on-unpruned-suppressions in run-eslint-json.mjs (#11600) (#11983) Co-authored-by: Markus Hartung --- ...lint-json-pass-on-unpruned-suppressions.md | 1 + scripts/quality/run-eslint-json.mjs | 6 + .../unit/run-eslint-json-suppressions.test.ts | 113 ++++++++++++++++++ 3 files changed, 120 insertions(+) create mode 100644 changelog.d/fixes/11600-eslint-json-pass-on-unpruned-suppressions.md create mode 100644 tests/unit/run-eslint-json-suppressions.test.ts diff --git a/changelog.d/fixes/11600-eslint-json-pass-on-unpruned-suppressions.md b/changelog.d/fixes/11600-eslint-json-pass-on-unpruned-suppressions.md new file mode 100644 index 000000000000..ddcde2335859 --- /dev/null +++ b/changelog.d/fixes/11600-eslint-json-pass-on-unpruned-suppressions.md @@ -0,0 +1 @@ +- fix(ci): pass `--pass-on-unpruned-suppressions` in `run-eslint-json.mjs` so the CI Lint job no longer fails when a suppression is merely orphaned by a genuine fix, mirroring the identical fix already in `validate-release-green.mjs` (#11600) diff --git a/scripts/quality/run-eslint-json.mjs b/scripts/quality/run-eslint-json.mjs index cbefa23e254b..8535c3caeb34 100644 --- a/scripts/quality/run-eslint-json.mjs +++ b/scripts/quality/run-eslint-json.mjs @@ -36,6 +36,12 @@ const args = [ ".eslintcache", "--suppressions-location", "config/quality/eslint-suppressions.json", + // An "unpruned" suppression means a previously-frozen violation was legitimately + // fixed — release-time housekeeping (same bucket as ratchet drift), never a + // contributor-blocking defect. Without this flag ESLint 9.x exits 2 for that + // reason alone, which would fail this script's own JSON pass on a clean tree + // (same failure class already fixed in validate-release-green.mjs — #7837 / #11600). + "--pass-on-unpruned-suppressions", "--format", "json", "--output-file", diff --git a/tests/unit/run-eslint-json-suppressions.test.ts b/tests/unit/run-eslint-json-suppressions.test.ts new file mode 100644 index 000000000000..597fd264ec75 --- /dev/null +++ b/tests/unit/run-eslint-json-suppressions.test.ts @@ -0,0 +1,113 @@ +// Repro for issue #11600 ("Detalhe adicional — supressao obsoleta derruba o job"): +// +// scripts/quality/run-eslint-json.mjs is the script behind `npm run lint:json`, +// which backs the CI `Lint` job's blocking ESLint pass (.github/workflows/ci.yml +// step "ESLint (JSON report)"). It builds this exact args array (see +// scripts/quality/run-eslint-json.mjs lines 31-42): +// +// [".", "--cache", "--cache-location", ".eslintcache", +// "--suppressions-location", "config/quality/eslint-suppressions.json", +// "--format", "json", "--output-file", outFile, ...extra] +// +// and does `process.exit(result.status)` verbatim — i.e. it propagates ESLint's +// raw exit code, including exit code 2, which ESLint uses specifically for "There +// are suppressions left that do not occur anymore" (a stale/orphaned suppression +// entry), REGARDLESS of whether the tree has any real (unsuppressed) lint error. +// +// scripts/quality/validate-release-green.mjs already had to fix the identical +// failure class by adding --pass-on-unpruned-suppressions (PR #7962, issue #7837) +// — see that script's own comment: "An 'unpruned' suppression means a previously- +// frozen violation was legitimately fixed ... Without this flag ESLint 9.x exits 2 +// for that reason alone". run-eslint-json.mjs never received the same fix, so the +// CI `Lint` job goes red whenever a suppression is orphaned by someone genuinely +// fixing pre-existing debt, even with zero real errors in the tree. +// +// This test reproduces run-eslint-json.mjs's EXACT args array (mirrored above, +// verified against the source at HEAD) against a minimal, isolated ESLint fixture +// (not OmniRoute's own 10k+ file tree — a cold full-tree lint costs 14-60min per +// open PR #11734, impractical for a fast regression test) and asserts the +// spec-correct outcome: exit 0. + +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); +const eslintBin = path.join(repoRoot, "node_modules", "eslint", "bin", "eslint.js"); + +function makeFixture() { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "eslint-suppression-repro-")); + fs.writeFileSync( + path.join(dir, "eslint.config.mjs"), + 'export default [{files:["*.js"],rules:{"no-unused-vars":"error"}}];\n' + ); + fs.writeFileSync(path.join(dir, "a.js"), "const unused = 1;\nmodule.exports = {};\n"); + fs.writeFileSync(path.join(dir, "suppressions.json"), "{}"); + return dir; +} + +test("run-eslint-json.mjs's shipped eslint args must not exit non-zero on a pruned (stale) suppression with zero real errors (#11600)", () => { + const dir = makeFixture(); + + // Freeze the pre-existing violation (mirrors an eslint-suppressions.json baseline freeze). + const freeze = spawnSync( + process.execPath, + [eslintBin, ".", "--suppressions-location", "suppressions.json", "--suppress-all"], + { cwd: dir, encoding: "utf8" } + ); + assert.equal(freeze.status, 0, `baseline freeze should succeed: ${freeze.stderr}`); + + // Someone genuinely fixes the violation without regenerating suppressions.json — + // the suppression entry is now orphaned, and the tree has 0 real errors. + fs.writeFileSync(path.join(dir, "a.js"), "module.exports = {};\n"); + + // EXACT args run-eslint-json.mjs currently ships (scripts/quality/run-eslint-json.mjs:32-44), + // plus the fix under test (--pass-on-unpruned-suppressions) once implemented. + const result = spawnSync( + process.execPath, + [ + eslintBin, + ".", + "--cache", + "--cache-location", + ".eslintcache", + "--suppressions-location", + "suppressions.json", + "--format", + "json", + "--output-file", + "lint-out.json", + "--pass-on-unpruned-suppressions", + ], + { cwd: dir, encoding: "utf8" } + ); + + fs.rmSync(dir, { recursive: true, force: true }); + + // SPEC: a clean tree (0 real lint errors) must not fail the gate merely because a + // suppression became stale. + assert.equal( + result.status, + 0, + `eslint args exited ${result.status} on a stale suppression despite 0 real lint errors — ` + + `the CI Lint job goes red for cleanup, not defects. stderr: ${result.stderr}` + ); +}); + +test("run-eslint-json.mjs source must pass --pass-on-unpruned-suppressions to ESLint (#11600)", () => { + const source = fs.readFileSync( + path.join(repoRoot, "scripts", "quality", "run-eslint-json.mjs"), + "utf8" + ); + assert.match( + source, + /--pass-on-unpruned-suppressions/, + "run-eslint-json.mjs must pass --pass-on-unpruned-suppressions in its ESLint args array, " + + "mirroring the identical fix already present in scripts/quality/validate-release-green.mjs " + + "(PR #7962 / issue #7837) — otherwise the CI Lint job goes red on a merely-stale suppression." + ); +}); From 71093eda770d3cde3dfdd6a2db79a07b91181de0 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sat, 29 Aug 2026 08:10:26 -0300 Subject: [PATCH 13/91] fix(codex): keep parallel_tool_calls:false on translated Responses Lite path (#11707) (#11984) enforceCodexResponsesLiteParallelToolCalls() forces parallel_tool_calls:false at the top of CodexExecutor.execute(), but transformRequest() early-returns the body before its RESPONSES_API_ALLOWLIST field filter only when _nativeCodexPassthrough is set. Any request that reaches the codex executor via the translated (non-native-passthrough) path never gets that flag, so the allowlist filter silently deleted parallel_tool_calls right before the fetch body was sent, reproducing the reported upstream rejection ('X-OpenAI-Internal-Codex-Responses-Lite requires parallel_tool_calls to be false') for every model. Add parallel_tool_calls to RESPONSES_API_ALLOWLIST so the value survives the translated path too. Update the sibling #2608 allowlist test that previously asserted parallel_tool_calls gets stripped like other Chat Completions-only fields -- it is a legitimate Responses API field that must now survive. Co-authored-by: Markus Hartung --- ...odex-responses-lite-parallel-tool-calls.md | 1 + open-sse/executors/codex.ts | 5 ++ ...dex-responses-lite-translated-path.test.ts | 74 +++++++++++++++++++ tests/unit/executor-codex.test.ts | 2 - 4 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 changelog.d/fixes/11707-codex-responses-lite-parallel-tool-calls.md create mode 100644 tests/unit/executor-codex-responses-lite-translated-path.test.ts diff --git a/changelog.d/fixes/11707-codex-responses-lite-parallel-tool-calls.md b/changelog.d/fixes/11707-codex-responses-lite-parallel-tool-calls.md new file mode 100644 index 000000000000..a8cfd0cc1e14 --- /dev/null +++ b/changelog.d/fixes/11707-codex-responses-lite-parallel-tool-calls.md @@ -0,0 +1 @@ +- fix(codex): keep `parallel_tool_calls:false` on the translated Codex Responses Lite path (#11707) diff --git a/open-sse/executors/codex.ts b/open-sse/executors/codex.ts index 9fc9a925cf90..62e3a1da13a7 100644 --- a/open-sse/executors/codex.ts +++ b/open-sse/executors/codex.ts @@ -1478,6 +1478,11 @@ export class CodexExecutor extends BaseExecutor { "client_metadata", // GPT-5 output verbosity ({ verbosity } — normalized above by normalizeCodexVerbosity). "text", + // Responses Lite (#7171/#7821/#11707): enforceCodexResponsesLiteParallelToolCalls() + // forces this field on the translated (non-_nativeCodexPassthrough) path too — it + // must survive this allowlist filter or upstream rejects with "X-OpenAI-Internal- + // Codex-Responses-Lite requires `parallel_tool_calls` to be false." + "parallel_tool_calls", // Internal markers used by OmniRoute pipeline "_omnirouteResponsesStore", ]); diff --git a/tests/unit/executor-codex-responses-lite-translated-path.test.ts b/tests/unit/executor-codex-responses-lite-translated-path.test.ts new file mode 100644 index 000000000000..13283e3a88e7 --- /dev/null +++ b/tests/unit/executor-codex-responses-lite-translated-path.test.ts @@ -0,0 +1,74 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +import { CodexExecutor } from "../../open-sse/executors/codex.ts"; + +// Issue #11707: "X-OpenAI-Internal-Codex-Responses-Lite requires parallel_tool_calls +// to be false" persists even though #7171/#7821 already force +// parallel_tool_calls:false in enforceCodexResponsesLiteParallelToolCalls() at the +// top of CodexExecutor.execute(). +// +// Root cause: that enforcement result flows into transformRequest(), which early- +// returns the body BEFORE field filtering only when `_nativeCodexPassthrough` is +// true (client sent native Responses-API-shaped input straight through). For any +// request that reaches the codex executor via the TRANSLATED path (client format +// is not the native Responses API — e.g. Chat Completions shaped input translated +// by chatCore for the codex provider), `_nativeCodexPassthrough` is never set, so +// transformRequest() falls through to the RESPONSES_API_ALLOWLIST loop — and that +// allowlist did not include "parallel_tool_calls", so the very value +// enforceCodexResponsesLiteParallelToolCalls() just forced to `false` gets +// silently deleted again right before the fetch body is serialized. +// +// This reproduces with any model — matching the reporter's "every model I select +// fails" — because the deletion is unconditional on the translated path. + +async function runLiteRequest(body: Record): Promise[]> { + const executor = new CodexExecutor(); + const originalFetch = globalThis.fetch; + const capturedBodies: Record[] = []; + + globalThis.fetch = async (_url, init) => { + capturedBodies.push(JSON.parse(String(init?.body || "{}"))); + return new Response(JSON.stringify({ id: "resp_lite", object: "response" }), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + }; + + try { + await executor.execute({ + model: String(body.model), + body, + stream: true, + credentials: { accessToken: "codex-token" }, + clientHeaders: { "X-OpenAI-Internal-Codex-Responses-Lite": "true" }, + }); + } finally { + globalThis.fetch = originalFetch; + } + + return capturedBodies; +} + +test("#11707: Responses Lite parallel_tool_calls:false survives the TRANSLATED (non-native-passthrough) codex path", async () => { + // No `_nativeCodexPassthrough` marker — this is how a request arrives when the + // client's own format isn't detected as native OpenAI Responses (e.g. a + // manually configured Codex client sending Chat-Completions-shaped input, or + // any other translated path chatCore routes into the codex provider). + const body = { + model: "gpt-5", + messages: [{ role: "user", content: "hi" }], + }; + + const capturedBodies = await runLiteRequest(body); + + assert.equal( + capturedBodies[0].parallel_tool_calls, + false, + "Responses Lite must force parallel_tool_calls:false on the outbound Codex " + + "request even when the request took the translated (non-passthrough) path " + + "through transformRequest()'s RESPONSES_API_ALLOWLIST filter — otherwise " + + "upstream rejects with 'X-OpenAI-Internal-Codex-Responses-Lite requires " + + "`parallel_tool_calls` to be false.' (#11707)" + ); +}); diff --git a/tests/unit/executor-codex.test.ts b/tests/unit/executor-codex.test.ts index 62ae4655586d..725d8d4e1073 100644 --- a/tests/unit/executor-codex.test.ts +++ b/tests/unit/executor-codex.test.ts @@ -273,7 +273,6 @@ test("CodexExecutor.transformRequest non-passthrough allowlist strips all residu function_call: "auto", functions: [{ name: "test", parameters: {} }], max_completion_tokens: 1000, - parallel_tool_calls: true, user: "cursor-user", metadata: { key: "value" }, stream_options: { include_usage: true }, @@ -310,7 +309,6 @@ test("CodexExecutor.transformRequest non-passthrough allowlist strips all residu assert.equal(result.function_call, undefined, "function_call should be stripped"); assert.equal(result.functions, undefined, "functions should be stripped"); assert.equal(result.max_completion_tokens, undefined, "max_completion_tokens should be stripped"); - assert.equal(result.parallel_tool_calls, undefined, "parallel_tool_calls should be stripped"); assert.equal(result.user, undefined, "user should be stripped"); assert.equal(result.metadata, undefined, "metadata should be stripped"); assert.equal(result.stream_options, undefined, "stream_options should be stripped"); From 322b218f06cbbf503e68babd26d7c431f9402c72 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sat, 29 Aug 2026 08:10:29 -0300 Subject: [PATCH 14/91] fix(cli): drop the never-produced dist/index.cjs requirement from prepublish's opencode-plugin skip check (#11787) (#11990) * fix(cli): drop the never-produced dist/index.cjs requirement from prepublish's opencode-plugin skip check (#11787) * test(build): resolve tsup/npm portably in the #11787 regression test instead of a hardcoded .bin path The old test assumed @omniroute/opencode-plugin/node_modules/.bin/tsup already existed. A fresh checkout (CI's npm ci never installs this standalone package's own deps) has no such node_modules at all, so the test failed with MODULE_NOT_FOUND in CI while passing locally on a devbox that had installed it before. Mirror scripts/build/prepublish.ts's own install-then-resolveLocalBinEntry approach. --------- Co-authored-by: Markus Hartung --- ...87-prepublish-opencode-plugin-cjs-check.md | 1 + scripts/build/prepublish.ts | 6 +- ...publish-opencode-plugin-build-skip.test.ts | 95 +++++++++++++++++++ 3 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 changelog.d/fixes/11787-prepublish-opencode-plugin-cjs-check.md create mode 100644 tests/unit/prepublish-opencode-plugin-build-skip.test.ts diff --git a/changelog.d/fixes/11787-prepublish-opencode-plugin-cjs-check.md b/changelog.d/fixes/11787-prepublish-opencode-plugin-cjs-check.md new file mode 100644 index 000000000000..87fde1e4c022 --- /dev/null +++ b/changelog.d/fixes/11787-prepublish-opencode-plugin-cjs-check.md @@ -0,0 +1 @@ +- fix(cli): stop prepublish from re-rebuilding the already-built ESM-only opencode-plugin dist (#11787) diff --git a/scripts/build/prepublish.ts b/scripts/build/prepublish.ts index 346cf5c67626..1625318cc40d 100644 --- a/scripts/build/prepublish.ts +++ b/scripts/build/prepublish.ts @@ -483,9 +483,11 @@ if (existsSync(cliSrcFile)) { // flow for every downstream user. const opencodePluginSrc = join(ROOT, "@omniroute", "opencode-plugin"); const opencodePluginDist = join(opencodePluginSrc, "dist", "index.js"); -const opencodePluginCjs = join(opencodePluginSrc, "dist", "index.cjs"); if (existsSync(opencodePluginSrc) && existsSync(join(opencodePluginSrc, "package.json"))) { - const pluginAlreadyBuilt = existsSync(opencodePluginDist) && existsSync(opencodePluginCjs); + // The plugin's tsup config is ESM-only (format: ["esm"]), so a successful + // build only ever produces dist/index.js (+ dist/index.d.ts) — never + // dist/index.cjs. Gate the skip solely on dist/index.js. + const pluginAlreadyBuilt = existsSync(opencodePluginDist); if (!pluginAlreadyBuilt) { console.log("\n 🔨 Building @omniroute/opencode-plugin (tsup)..."); try { diff --git a/tests/unit/prepublish-opencode-plugin-build-skip.test.ts b/tests/unit/prepublish-opencode-plugin-build-skip.test.ts new file mode 100644 index 000000000000..87461666e467 --- /dev/null +++ b/tests/unit/prepublish-opencode-plugin-build-skip.test.ts @@ -0,0 +1,95 @@ +// Regression test for issue #11787. +// +// scripts/build/prepublish.ts gates the "@omniroute/opencode-plugin already +// built -> skip rebuild" fast path on both dist/index.js AND dist/index.cjs +// existing. @omniroute/opencode-plugin/tsup.config.ts is ESM-only +// (format: ["esm"]), so a successful `tsup` run in that package never +// produces dist/index.cjs -- the old predicate could never be true. +// +// This test builds the REAL plugin package with the REAL tsup config (no +// mocks) and then evaluates the fixed predicate copied from prepublish.ts +// against the resulting dist/, proving the "already built" skip path now +// works for an ESM-only build, and still correctly reports "not built" when +// dist/ is absent. +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { execFileSync } from "node:child_process"; +import { existsSync, rmSync } from "node:fs"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import { resolveLocalBinEntry, isNativeExecutable } from "../../scripts/build/buildToolRunner.mjs"; +import { resolveBundledNpmEntry } from "../../scripts/build/resolveNpmEntry.ts"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const ROOT = join(__dirname, "..", ".."); +const opencodePluginSrc = join(ROOT, "@omniroute", "opencode-plugin"); +const opencodePluginDist = join(opencodePluginSrc, "dist", "index.js"); +const opencodePluginCjs = join(opencodePluginSrc, "dist", "index.cjs"); + +test("prepublish pluginAlreadyBuilt predicate recognizes a real ESM-only tsup build (#11787)", () => { + rmSync(join(opencodePluginSrc, "dist"), { recursive: true, force: true }); + // The plugin is a standalone package, not an npm workspace member (see + // scripts/build/prepublish.ts's own comment above the equivalent build step) — a + // root `npm ci` never populates its node_modules, so a fresh checkout (CI, or any + // devbox that hasn't built the plugin before) needs its own install first. Mirror + // prepublish.ts's own install step instead of hardcoding a `.bin/tsup` path that + // only exists on a devbox someone happened to `npm install` in already. + if (!existsSync(join(opencodePluginSrc, "node_modules"))) { + const npmEntry = resolveBundledNpmEntry("npm-cli.js"); + const installArgs = ["install", "--no-audit", "--no-fund"]; + if (npmEntry) { + execFileSync(process.execPath, [npmEntry, ...installArgs], { + cwd: opencodePluginSrc, + stdio: "inherit", + }); + } else { + execFileSync("npm", installArgs, { cwd: opencodePluginSrc, stdio: "inherit" }); + } + } + // Resolve the real tsup binary the same way scripts/build/prepublish.ts's + // runBuildTool() does — never a hardcoded `.bin/tsup` path, since npm's hoisting can + // place the binary at the plugin's own node_modules/.bin OR the repo root's, + // depending on the install. + const localEntry = resolveLocalBinEntry("tsup", "tsup", opencodePluginSrc); + if (localEntry) { + if (isNativeExecutable(localEntry)) { + execFileSync(localEntry, [], { cwd: opencodePluginSrc, stdio: "inherit" }); + } else { + execFileSync(process.execPath, [localEntry], { cwd: opencodePluginSrc, stdio: "inherit" }); + } + } else { + const npxEntry = resolveBundledNpmEntry("npx-cli.js"); + if (npxEntry) { + execFileSync(process.execPath, [npxEntry, "tsup"], { + cwd: opencodePluginSrc, + stdio: "inherit", + }); + } else { + execFileSync("npx", ["tsup"], { cwd: opencodePluginSrc, stdio: "inherit" }); + } + } + + assert.equal(existsSync(opencodePluginDist), true, "dist/index.js should exist after tsup"); + assert.equal( + existsSync(opencodePluginCjs), + false, + "dist/index.cjs should NOT exist for an ESM-only tsup build" + ); + + // This is the fixed predicate from scripts/build/prepublish.ts. + const pluginAlreadyBuilt = existsSync(opencodePluginDist); + + assert.equal( + pluginAlreadyBuilt, + true, + "expected the ESM-only build to be recognized as already built (index.js present is sufficient)" + ); +}); + +test("prepublish pluginAlreadyBuilt predicate is false when dist/ is absent (#11787)", () => { + rmSync(join(opencodePluginSrc, "dist"), { recursive: true, force: true }); + + const pluginAlreadyBuilt = existsSync(opencodePluginDist); + + assert.equal(pluginAlreadyBuilt, false, "expected a missing dist/ to require a rebuild"); +}); From bd04bb9cc6724458cfb7cfea1072d6a5c9ebab90 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sat, 29 Aug 2026 08:10:33 -0300 Subject: [PATCH 15/91] fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches (#11810) (#11986) Co-authored-by: Markus Hartung --- .../11810-combo-success-connection-header.md | 1 + src/sse/handlers/chat.ts | 8 +- ...s-selected-connection-header-11810.test.ts | 143 ++++++++++++++++++ 3 files changed, 150 insertions(+), 2 deletions(-) create mode 100644 changelog.d/fixes/11810-combo-success-connection-header.md create mode 100644 tests/unit/combo-success-selected-connection-header-11810.test.ts diff --git a/changelog.d/fixes/11810-combo-success-connection-header.md b/changelog.d/fixes/11810-combo-success-connection-header.md new file mode 100644 index 000000000000..f79f29e324d2 --- /dev/null +++ b/changelog.d/fixes/11810-combo-success-connection-header.md @@ -0,0 +1 @@ +- fix(sse): set X-OmniRoute-Selected-Connection-Id on successful combo dispatches so downstream consumers stop falling back to an empty connection id (#11810) diff --git a/src/sse/handlers/chat.ts b/src/sse/handlers/chat.ts index 316227e96244..d504d67086c7 100644 --- a/src/sse/handlers/chat.ts +++ b/src/sse/handlers/chat.ts @@ -1913,11 +1913,15 @@ async function handleSingleModelChat( } if (telemetry) telemetry.startPhase("finalize"); if (telemetry) telemetry.endPhase(); + const successResponse = withSelectedConnectionHeader( + result.response, + credentials?.connectionId + ); if (requestBody.stream === true) { - return wrapResponseWithOAuthSessionRelease(result.response, releaseOAuthSession); + return wrapResponseWithOAuthSessionRelease(successResponse, releaseOAuthSession); } releaseOAuthSession(); - return result.response; + return successResponse; } // A final hard-lease fence rejection is authoritative. It must never mutate diff --git a/tests/unit/combo-success-selected-connection-header-11810.test.ts b/tests/unit/combo-success-selected-connection-header-11810.test.ts new file mode 100644 index 000000000000..0d8b039a3812 --- /dev/null +++ b/tests/unit/combo-success-selected-connection-header-11810.test.ts @@ -0,0 +1,143 @@ +/** + * Regression test for #11810. + * + * On a successful provider dispatch, `handleSingleModelChat()`'s + * `result.success` branch in `src/sse/handlers/chat.ts` used to return + * `result.response` (non-streaming) or + * `wrapResponseWithOAuthSessionRelease(result.response, releaseOAuthSession)` + * (streaming) directly, without first calling `withSelectedConnectionHeader()` + * — unlike every failure exit in the same function, which does call it. + * + * As a result `X-OmniRoute-Selected-Connection-Id` was absent on every + * successful response for a dynamically-selected (unpinned) connection, so + * combo.ts's consumers (success-decay, provider cooldown recovery, webhook + * attribution, session stickiness, LKGP) fell back to the target's static + * `connectionId`, which is empty for provider-level combo targets like + * `openai/o3-mini`. + */ + +import test from "node:test"; +import assert from "node:assert/strict"; + +import { createChatPipelineHarness } from "../integration/_chatPipelineHarness.ts"; + +const harness = await createChatPipelineHarness("combo-success-sel-conn-11810"); +const { buildOpenAIResponse, buildRequest, combosDb, handleChat, resetStorage, seedConnection } = + harness; + +const textEncoder = new TextEncoder(); + +function buildOpenAIStreamResponse(text = "hello streamed") { + return new Response( + new ReadableStream({ + start(controller) { + controller.enqueue( + textEncoder.encode( + `data: ${JSON.stringify({ + id: "chatcmpl_stream_11810", + object: "chat.completion.chunk", + created: 1, + model: "o3-mini", + choices: [{ index: 0, delta: { role: "assistant", content: text } }], + })}\n\n` + ) + ); + controller.enqueue(textEncoder.encode(`data: [DONE]\n\n`)); + controller.close(); + }, + }), + { + status: 200, + headers: { "Content-Type": "text/event-stream" }, + } + ); +} + +test.beforeEach(async () => { + await resetStorage(); +}); + +test.afterEach(async () => { + await resetStorage(); +}); + +test.after(async () => { + await harness.cleanup(); +}); + +test("#11810 combo success response carries X-OmniRoute-Selected-Connection-Id for a dynamically selected connection", async () => { + const connection = await seedConnection("openai", { + apiKey: "sk-openai-11810", + }); + + await combosDb.createCombo({ + name: "combo-11810-priority", + strategy: "priority", + config: { maxRetries: 0, retryDelayMs: 0 }, + // Provider-level target, no pinned connectionId — the connection is selected + // dynamically at dispatch time. + models: ["openai/o3-mini"], + }); + + globalThis.fetch = async () => buildOpenAIResponse("hello from o3-mini", "o3-mini"); + + const response = await handleChat( + buildRequest({ + body: { + model: "combo-11810-priority", + stream: false, + messages: [{ role: "user", content: "hi" }], + }, + }) + ); + + assert.equal(response.status, 200); + const body = (await response.json()) as { choices: Array<{ message: { content: string } }> }; + assert.equal(body.choices[0].message.content, "hello from o3-mini"); + + const selectedConnectionId = response.headers.get("X-OmniRoute-Selected-Connection-Id"); + assert.equal( + selectedConnectionId, + connection.id, + `expected the response to carry the dynamically selected connection id (${connection.id}), got ${selectedConnectionId}` + ); +}); + +test("#11810 combo streaming success response carries X-OmniRoute-Selected-Connection-Id for a dynamically selected connection", async () => { + const connection = await seedConnection("openai", { + apiKey: "sk-openai-11810-stream", + }); + + await combosDb.createCombo({ + name: "combo-11810-priority-stream", + strategy: "priority", + config: { maxRetries: 0, retryDelayMs: 0 }, + // Provider-level target, no pinned connectionId — the connection is selected + // dynamically at dispatch time. + models: ["openai/o3-mini"], + }); + + globalThis.fetch = async () => buildOpenAIStreamResponse("hello streamed from o3-mini"); + + const response = await handleChat( + buildRequest({ + body: { + model: "combo-11810-priority-stream", + stream: true, + messages: [{ role: "user", content: "hi" }], + }, + }) + ); + + assert.equal(response.status, 200); + + const selectedConnectionId = response.headers.get("X-OmniRoute-Selected-Connection-Id"); + assert.equal( + selectedConnectionId, + connection.id, + `expected the streaming response to carry the dynamically selected connection id (${connection.id}), got ${selectedConnectionId}` + ); + + // Drain the stream so the harness's fetch mock/db handles are released cleanly. + await response.text(); +}); From 02ba5737305b6b31fd5d010936d66926650fb378 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sat, 29 Aug 2026 08:10:37 -0300 Subject: [PATCH 16/91] fix(providers): scope Antigravity mitmAlias tier ids to the safe static alias (#11824) (#11988) Co-authored-by: Markus Hartung --- ...24-antigravity-mitm-alias-cross-account.md | 1 + src/lib/providerModels/managedModelImport.ts | 18 ++++++ tests/unit/managed-model-import.test.ts | 62 ++++++++++++++++++- 3 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 changelog.d/fixes/11824-antigravity-mitm-alias-cross-account.md diff --git a/changelog.d/fixes/11824-antigravity-mitm-alias-cross-account.md b/changelog.d/fixes/11824-antigravity-mitm-alias-cross-account.md new file mode 100644 index 000000000000..3da3ff5d95e1 --- /dev/null +++ b/changelog.d/fixes/11824-antigravity-mitm-alias-cross-account.md @@ -0,0 +1 @@ +- **fix(providers):** Antigravity's dynamic mitmAlias table no longer routes `gemini-3.7-flash-{high,medium,low}` to a literal tier-suffixed upstream id just because one connected account's own discovery listed it directly — those display ids always resolve through the safe `gemini-3.7-flash-tiered` static alias, so one account's Google-provisioned access no longer 404s every sibling account of the provider ([#11824](https://github.com/diegosouzapw/OmniRoute/issues/11824), [#11651](https://github.com/diegosouzapw/OmniRoute/issues/11651)) diff --git a/src/lib/providerModels/managedModelImport.ts b/src/lib/providerModels/managedModelImport.ts index 172ba0647967..8faff3613d71 100644 --- a/src/lib/providerModels/managedModelImport.ts +++ b/src/lib/providerModels/managedModelImport.ts @@ -387,6 +387,24 @@ export async function importManagedModels({ } } + // #11824/#11651: `syncedIds` is a UNION across every connection of this provider + // (getSyncedAvailableModels), so an identity mapping derived above can route a + // display id to the literal tier-suffixed upstream id (e.g. "gemini-3.7-flash-high") + // just because ONE connected account's own discovery happens to list it directly. + // Google's Cloud Code Assist backend only allows those tier-suffixed ids on + // accounts/projects it specifically provisioned for them — every other account can + // only call the shared "-tiered" endpoint id. Since this mitmAlias table is global + // (not scoped per connection) and consulted first/authoritatively by + // cleanModelName(), letting one account's discovery win here silently 404s every + // sibling account. Force every display id that the static ANTIGRAVITY_MODEL_ALIASES + // table already knows only has a safe "-tiered" target to always resolve there, + // regardless of what any single connection's discovery reported. + for (const [displayId, safeTarget] of Object.entries(ANTIGRAVITY_MODEL_ALIASES)) { + if (safeTarget === "gemini-3.7-flash-tiered") { + mappings[displayId] = `antigravity/${safeTarget}`; + } + } + await setMitmAliasAll("antigravity", mappings); } diff --git a/tests/unit/managed-model-import.test.ts b/tests/unit/managed-model-import.test.ts index 5e75583c71f5..a3f1e1942db7 100644 --- a/tests/unit/managed-model-import.test.ts +++ b/tests/unit/managed-model-import.test.ts @@ -296,10 +296,70 @@ test("antigravity sync dynamically builds and saves mitmAlias mappings", async ( models.some((model) => model.id === "gemini-3.5-flash"), false ); - assert.equal(mitmMappings["gemini-3.7-flash-high"], "antigravity/gemini-3.7-flash-high"); + // #11824: gemini-3.7-flash-high is a known cross-account-unsafe display id (the + // tier-suffixed upstream id is only callable on Google projects specifically + // provisioned for it); it must always resolve through the static alias to the + // safe shared "-tiered" endpoint id, never as an identity mapping to the literal + // tier id — even when it appears directly in this connection's own discovery. + assert.equal(mitmMappings["gemini-3.7-flash-high"], "antigravity/gemini-3.7-flash-tiered"); assert.equal(mitmMappings["custom-antigravity-model"], "antigravity/custom-antigravity-model"); // Removed Antigravity 2.0 preview/agent aliases must not be reintroduced. assert.equal(mitmMappings["gemini-3.5-flash-preview"], undefined); assert.equal(mitmMappings["gemini-3-flash-agent"], undefined); }); + +test("#11824: syncing account A's model catalog must not route account B's gemini-3.7-flash-high to an id B's own discovery never advertised", async () => { + const db = core.getDbInstance(); + const now = "2026-08-27"; + db.prepare( + "INSERT INTO provider_connections (id, provider, auth_type, name, is_active, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)" + ).run("acct-a", "antigravity", "oauth", "Antigravity A", 1, now, now); + db.prepare( + "INSERT INTO provider_connections (id, provider, auth_type, name, is_active, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)" + ).run("acct-b", "antigravity", "oauth", "Antigravity B", 1, now, now); + + // Account A: Google has provisioned direct tier-suffixed callability for this + // project -- its own :fetchAvailableModels lists "gemini-3.7-flash-high" verbatim. + await importManagedModels({ + providerId: "antigravity", + connectionId: "acct-a", + mode: "sync", + fetchedModels: [{ id: "gemini-3.7-flash-high", name: "Gemini 3.7 Flash High" }], + }); + + // Account B: Google has NOT provisioned the direct tier id for this project -- + // its own :fetchAvailableModels only ever lists the tiered endpoint id. + await importManagedModels({ + providerId: "antigravity", + connectionId: "acct-b", + mode: "sync", + fetchedModels: [{ id: "gemini-3.7-flash-tiered", name: "Gemini 3.7 Flash (Tiered)" }], + }); + + // Account B's OWN synced catalog never included the direct tier id. + const acctBOwnCatalog = await modelsDb.getSyncedAvailableModelsForConnection( + "antigravity", + "acct-b" + ); + const acctBOwnIds = acctBOwnCatalog.map((m) => m.id); + assert.ok( + !acctBOwnIds.includes("gemini-3.7-flash-high"), + "precondition: account B's own discovery must not include the direct tier id" + ); + + // But the GLOBAL mitmAlias table used by cleanModelName() for EVERY connection + // (including B's requests) was rebuilt from the union of A + B, so it must not + // still route "gemini-3.7-flash-high" to the id only A supports. + const mitmMappings = await modelsDb.getMitmAlias("antigravity"); + + assert.notEqual( + mitmMappings["gemini-3.7-flash-high"], + "antigravity/gemini-3.7-flash-high", + "BUG: the shared mitmAlias table routes gemini-3.7-flash-high to the literal " + + "upstream id (only account A's project supports it) even for account B, " + + "whose own discovery never advertised that id -- this is what produces the " + + "per-account 404 reported in #11824/#11651." + ); + assert.equal(mitmMappings["gemini-3.7-flash-high"], "antigravity/gemini-3.7-flash-tiered"); +}); From e6de61f0c2cdbf94c4fdae97cc9e0110160dd0ee Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sat, 29 Aug 2026 08:10:40 -0300 Subject: [PATCH 17/91] fix(sse): stop the auto-combo candidates inspector from dropping blocked rows (#9133) (#11994) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(sse): stop the auto-combo candidates inspector from dropping blocked rows (#9133) prepareVirtualAutoComboInputs applied filterResilienceBlockedCandidates before the #7819 read-only candidate inspector ever saw the pool, so a model-locked or cooled-down candidate silently disappeared from /auto-combo/*/candidates instead of showing up as reachable:false with a reason (modelLocked/connectionCooldown/breakerState were dead fields by construction). Add an opt-in `skip` parameter so the inspector builds its own unfiltered pool; routing (createVirtualAutoCombo/createBuiltinAutoCombo called without a prepared override) is unchanged. Also aligns isModelLocked's model argument to the bare model id, matching every lock writer and the routing-side filter, instead of the "provider/model" string. Regression test: tests/unit/auto-combo-candidates-locked-model-visible.test.ts (red before the fix — locked account's row silently missing; green after). * chore(quality): register the #9133 regression test in stryker tap.testFiles tests/unit/auto-combo-candidates-locked-model-visible.test.ts covers open-sse/services/accountFallback.ts (via isModelLocked) but wasn't listed, so its mutant kills wouldn't count toward mutation coverage. --------- Co-authored-by: Markus Hartung --- .../9133-auto-combo-candidates-unfiltered.md | 1 + config/quality/file-size-baseline.json | 3 +- open-sse/handlers/autoComboCandidates.ts | 61 ++++++++-- .../autoCombo/resilienceCandidateFilter.ts | 20 +++- open-sse/services/autoCombo/virtualFactory.ts | 15 +-- stryker.conf.json | 1 + ...bo-candidates-locked-model-visible.test.ts | 110 ++++++++++++++++++ 7 files changed, 191 insertions(+), 20 deletions(-) create mode 100644 changelog.d/fixes/9133-auto-combo-candidates-unfiltered.md create mode 100644 tests/unit/auto-combo-candidates-locked-model-visible.test.ts diff --git a/changelog.d/fixes/9133-auto-combo-candidates-unfiltered.md b/changelog.d/fixes/9133-auto-combo-candidates-unfiltered.md new file mode 100644 index 000000000000..ddd3f6165f34 --- /dev/null +++ b/changelog.d/fixes/9133-auto-combo-candidates-unfiltered.md @@ -0,0 +1 @@ +- fix(sse): stop the auto-combo candidates inspector from silently dropping model-locked/cooled-down rows (#9133) diff --git a/config/quality/file-size-baseline.json b/config/quality/file-size-baseline.json index 20d3c00edc1f..acce0e90b8f9 100644 --- a/config/quality/file-size-baseline.json +++ b/config/quality/file-size-baseline.json @@ -477,7 +477,8 @@ "_rebaseline_2026_08_23_tip_drift_post_batch0823": "Tip drift after the 2026-08-23 merge wave: chatBodyAdmission.ts 1009->1118 (+109, gate count incl. +1) and auth.ts 3337->3344 (+7), both grown by merges already on origin/release/v3.8.50 (verified identical on the pristine tip) — not by the codex-appserver-hardening PR that carries this bump. Owner pre-authorized baseline bumps 2026-08-22.", "_rebaseline_2026_08_24_11355_cooldown_recovery_guards": "PR #11355 own growth: test/route.ts 1215->1237, +22 (startup crash-recovery guard: clearStaleCrashCooldowns() now parses the persisted rate_limited_until deadline and skips clearing rows still genuinely in the future, instead of clearing every non-terminal cooldown unconditionally). Cohesive fix at the existing test-route dispatch chokepoint alongside the #11141 probe builder. Covered by tests/unit/startup-stale-cooldown-recovery.test.ts + tests/unit/repro-zai-cooldown-cleared-by-connection-test.test.ts.", "_rebaseline_2026_08_24_video_bridge_fu02_fu07_sampler": "PRs #11344 (FU-02 one-frame scene-aware determinism) + #11381 (FU-07 opt-in segment_aware structural sampling) own growth: videoBridgeRuntime.ts <1000->1009, +9 (sum of both boarded together in the same merge-batch). #11344 adds the deterministic one-frame midpoint fallback + policyEffective=uniform report at the existing scene_aware seam; #11381 adds the bounded local-only FFmpeg structural pre-analysis pass (scene/freeze/blur/exposure/SI-TI) and its budget-reallocation logic. Covered by tests/unit/guardrails/videoBridgeSampler.test.ts, tests/unit/guardrails/videoBridgeFu07StructuralSampling.test.ts, tests/integration/video-bridge-sampler-ffmpeg.test.ts. Owner pre-authorized rebaseline for legitimate PR growth (2026-08-19 directive).", - "open-sse/services/autoCombo/virtualFactory.ts": 1138, + "open-sse/services/autoCombo/virtualFactory.ts": 1139, + "_rebaseline_2026_08_29_9133_candidates_inspector_skip_flag": "#9133 own growth: open-sse/services/autoCombo/virtualFactory.ts 1138->1139 (+1, net of extraction). Fix: prepareVirtualAutoComboInputs gained an opt-in `skip` parameter so the read-only #7819 candidate inspector (open-sse/handlers/autoComboCandidates.ts) can build the FULL, unfiltered pool and decorate a resilience-blocked candidate as reachable:false instead of filterResilienceBlockedCandidates silently dropping the row before the inspector ever sees it (routing is unaffected — it never passes `skip`). The connectionsById map-building loop was extracted to buildConnectionResilienceMap() in resilienceCandidateFilter.ts (net 0 there since Prettier still breaks the call over multiple lines) and the now-unused ConnectionResilienceView import was dropped; the sole remaining growth is the new `skip` default parameter itself, which Prettier always places on its own line once the preceding options object parameter already breaks across lines — not further reducible without splitting prepareVirtualAutoComboInputs's signature away from its own body. Covered by tests/unit/auto-combo-candidates-locked-model-visible.test.ts (TDD repro: red before the fix, green after) plus the existing tests/unit/noauth-autocombo-lockout-7623.test.ts and tests/unit/auto-combo-credentialed-model-pool.test.ts (unaffected routing-path behavior).", "_rebaseline_2026_08_28_mergebatch_v3851_qwen_retirement": "/merge-batch 2026-08-28 (v3.8.51): #11713 (Qwen Web retirement) own growth: open-sse/services/autoCombo/virtualFactory.ts 1132->1135 (+3, combining the Designer + Runtime retirement-guard filter into the single runtimeConnections predicate at the existing candidate-pool chokepoint, now excluding Qwen Web alongside Felo Web). Irreducible per-mechanism wiring, additive not a new branch. Covered by tests/unit/virtual-auto-combo.test.ts.", "_rebaseline_2026_08_28_mergebatch_v3851_chatgptweb_retirement": "/merge-batch 2026-08-28 (v3.8.51): #11754 (common ChatGPT Web retirement) own growth: open-sse/services/autoCombo/virtualFactory.ts 1135->1138 (+3, an early `available` connection filter for the retired chatgpt-web/cgpt-web ids applied to both the active and disabled-noauth connection lists, ahead of the existing Designer+Runtime runtimeConnections filter). Irreducible per-mechanism wiring, additive not a new branch. Covered by tests/unit/virtual-auto-combo.test.ts.", "src/lib/cloudflaredTunnel.ts": 1078, diff --git a/open-sse/handlers/autoComboCandidates.ts b/open-sse/handlers/autoComboCandidates.ts index c133032306da..e7a2762ee747 100644 --- a/open-sse/handlers/autoComboCandidates.ts +++ b/open-sse/handlers/autoComboCandidates.ts @@ -2,11 +2,13 @@ * #7819 (Level 1) — read-only candidate pool + reachability listing for an * `auto/*` channel. * - * Builds the SAME candidate pool `virtualFactory.createVirtualAutoCombo` uses - * for routing (via `createBuiltinAutoCombo`, unfiltered by any per-key - * exclusion so the operator can see — and toggle — excluded candidates), then - * decorates each candidate with live reachability derived from the existing - * resilience reads (CLAUDE.md "Resilience Runtime State"): + * Builds its OWN candidate pool via `prepareVirtualAutoComboInputs({}, true)` + * (#9133, the `skip` opt-out) — the full, unfiltered pool that routing would + * otherwise narrow with `filterResilienceBlockedCandidates` before ever + * handing it to this endpoint. Not filtered by any per-key exclusion either, + * so the operator can see — and toggle — excluded candidates. Every + * candidate is then decorated with live reachability derived from the + * existing resilience reads (CLAUDE.md "Resilience Runtime State"): * - provider circuit breaker: `getCircuitBreaker(provider).getStatus()` / * `.canExecute()` — NEVER raw `state`, so an expired breaker (lazy * recovery) doesn't show as permanently open. @@ -14,13 +16,31 @@ * provider_connections row (no-auth synthetic connections have no row — * treated as always reachable on this axis). * - model lockout: `isModelLocked(provider, connectionId, model)`. + * + * A candidate the routing path would currently skip (model-locked, cooled + * down, breaker open) is never dropped from this listing — it is surfaced + * with `reachable:false` and the specific reason field (`modelLocked` / + * `connectionCooldown` / `breakerState`) set, since the panel's whole + * purpose is read-only transparency, not a preview of the dispatch pool. + * The actual routing path (`createVirtualAutoCombo` / + * `createBuiltinAutoCombo` called without a `prepared` override) is + * unchanged and keeps excluding these candidates before dispatch. */ import { buildErrorBody } from "@omniroute/open-sse/utils/error.ts"; import { getCircuitBreaker } from "@/shared/utils/circuitBreaker"; import { isModelLocked } from "@omniroute/open-sse/services/accountFallback.ts"; +import { parseModel } from "@omniroute/open-sse/services/model.ts"; import { getProviderConnectionById } from "@/lib/db/providers"; import { getExcludedConnectionIds } from "@/lib/db/autoCandidateOverrides"; +/** + * One row of the unfiltered, reason-annotated candidate pool (#9133): every + * eligible provider/model/account combination the routing pool would + * otherwise contain, including rows routing itself would currently skip. + * `reachable:false` plus the relevant reason field (`modelLocked` / + * `connectionCooldown` / `breakerState`) is how a blocked candidate is + * represented here — it is never simply absent from `candidates`. + */ export interface AutoComboCandidateView { provider: string; connectionId: string; @@ -69,7 +89,17 @@ async function decorateCandidate(candidate: { } } - const modelLocked = isModelLocked(candidate.provider, candidate.connectionId, candidate.model); + // #9133 (latent alignment): every lock writer (accountFallback.ts) and the + // routing-side filter (resilienceCandidateFilter.ts, combo.ts) key + // `isModelLocked` off the BARE model id, never the "provider/model" + // `modelStr`. `candidate.model` here is the prefixed form (it comes from + // the materialized combo's `models[].model`, not the raw candidate pool), + // so it must be parsed back to bare before the lock lookup — currently + // masked (both spellings map to the same quota-family key for every + // provider exercised so far) but not guaranteed for a provider whose lock + // key isn't family-scoped. + const bareModel = parseModel(candidate.modelStr).model ?? candidate.model; + const modelLocked = isModelLocked(candidate.provider, candidate.connectionId, bareModel); return { provider: candidate.provider, @@ -99,15 +129,26 @@ export async function getAutoComboCandidates( // virtualFactory — createBuiltinAutoCombo() only recognizes `auto/` // ids (matches classifyAutoModel()'s special-casing of the literal "auto" // model string in src/sse/handlers/autoRouting.ts). + // #9133 — this endpoint's stated role is read-only transparency, so it must + // build its OWN unfiltered pool (`prepareVirtualAutoComboInputs({}, true)`, + // the `skip` opt-out) rather than reuse the routing path's pool. Routing + // legitimately drops resilience-blocked candidates before dispatch; this + // inspector must not, or a model-locked/cooled-down row silently vanishes + // instead of showing up as `reachable:false` with a reason. The routing + // path itself is unchanged — it keeps calling + // `prepareVirtualAutoComboInputs()`/`createVirtualAutoCombo()` with the + // default (filtered) behavior. + const { prepareVirtualAutoComboInputs, createVirtualAutoComboFromPrepared } = + await import("@omniroute/open-sse/services/autoCombo/virtualFactory.ts"); + const prepared = await prepareVirtualAutoComboInputs({}, true); + let virtualCombo; if (channel === "auto") { - const { createVirtualAutoCombo } = - await import("@omniroute/open-sse/services/autoCombo/virtualFactory.ts"); - virtualCombo = await createVirtualAutoCombo(undefined); + virtualCombo = await createVirtualAutoComboFromPrepared(prepared, undefined); } else { const { createBuiltinAutoCombo } = await import("@omniroute/open-sse/services/autoCombo/builtinCatalog.ts"); - virtualCombo = await createBuiltinAutoCombo(modelStr, channel); + virtualCombo = await createBuiltinAutoCombo(modelStr, channel, prepared); } const excludedConnectionIds = apiKeyId diff --git a/open-sse/services/autoCombo/resilienceCandidateFilter.ts b/open-sse/services/autoCombo/resilienceCandidateFilter.ts index 72c2cc9445ad..8b3a4e682b85 100644 --- a/open-sse/services/autoCombo/resilienceCandidateFilter.ts +++ b/open-sse/services/autoCombo/resilienceCandidateFilter.ts @@ -31,6 +31,15 @@ export interface ConnectionResilienceView { testStatus?: string | null; } +/** Index connection resilience views by id, for the O(1) lookups this filter needs. */ +export function buildConnectionResilienceMap( + connections: Iterable +): Map { + const byId = new Map(); + for (const conn of connections) byId.set(conn.id, conn); + return byId; +} + function isConnectionResilienceBlocked(connection: ConnectionResilienceView): boolean { if (isAccountUnavailable(connection.rateLimitedUntil)) return true; const status = connection.testStatus; @@ -54,12 +63,19 @@ function isConnectionEligibleForModel( * Remove auto-combo candidates whose provider/model pair is model-locked, and * trim credentialed logical candidates whose allowed connections are all blocked. * Returns the input reference when nothing changed. + * + * `skip` (#9133) lets the read-only candidate inspector + * (`open-sse/handlers/autoComboCandidates.ts`) opt out entirely: it needs the + * FULL pool so it can decorate blocked candidates as `reachable:false` + * instead of dropping them before they are ever surfaced. Routing callers + * never pass it, so dispatch behavior is unchanged. */ export function filterResilienceBlockedCandidates( pool: T[], - connectionsById: Map + connectionsById: Map, + skip = false ): T[] { - if (!Array.isArray(pool) || pool.length === 0) return pool; + if (skip || !Array.isArray(pool) || pool.length === 0) return pool; let changed = false; const filtered = pool.flatMap((candidate) => { diff --git a/open-sse/services/autoCombo/virtualFactory.ts b/open-sse/services/autoCombo/virtualFactory.ts index 96a46dde2a0d..a4d0338819a6 100644 --- a/open-sse/services/autoCombo/virtualFactory.ts +++ b/open-sse/services/autoCombo/virtualFactory.ts @@ -44,8 +44,8 @@ import { filterExcludedCandidates } from "./candidateOverrides"; import { getExcludedConnectionIds } from "@/lib/db/autoCandidateOverrides"; import { filterResilienceBlockedCandidates, + buildConnectionResilienceMap, SYNTHETIC_NOAUTH_CONNECTION_ID as RESILIENCE_NOAUTH_CONNECTION_ID, - type ConnectionResilienceView, } from "./resilienceCandidateFilter"; import type { ChaosTuning } from "./chaosEngine"; @@ -584,7 +584,8 @@ export async function prepareVirtualAutoComboInputs( options: { includeResolvedCapabilities?: boolean; resolutionSnapshot?: ModelCapabilityResolutionSnapshot; - } = {} + } = {}, + skip = false // #9133 — inspector opt-out, see filterResilienceBlockedCandidates ): Promise { const [rawConnections, rawDisabledNoAuthConnections, settings] = await Promise.all([ getCachedProviderConnections({ isActive: true }) as Promise, @@ -708,10 +709,10 @@ export async function prepareVirtualAutoComboInputs( // #7623: honor existing model lockouts + connection cooldown/terminal state so // auto/* never advertises models the dispatch path would immediately skip. - const connectionsById = new Map(); - for (const conn of [...runtimeConnections, ...disabledNoAuthConnections]) { - connectionsById.set(conn.id, conn); - } + const connectionsById = buildConnectionResilienceMap([ + ...runtimeConnections, + ...disabledNoAuthConnections, + ]); const connectedProviders = new Set(validConnections.map((conn) => conn.provider)); const buildPreparedPool = (bypassNoAuthAllowlist: boolean) => { @@ -727,7 +728,7 @@ export async function prepareVirtualAutoComboInputs( ), ]; - const resilienceFilteredPool = filterResilienceBlockedCandidates(pool, connectionsById); + const resilienceFilteredPool = filterResilienceBlockedCandidates(pool, connectionsById, skip); if (resilienceFilteredPool !== pool) pool = resilienceFilteredPool; // #6512 (follow-up to #6328/#6495): when the operator opts into `hidePaidModels`, diff --git a/stryker.conf.json b/stryker.conf.json index 976500bf1b42..7d98b0848a37 100644 --- a/stryker.conf.json +++ b/stryker.conf.json @@ -100,6 +100,7 @@ "tests/unit/authz/route-guard-version-get-exemption.test.ts", "tests/unit/authz/route-guard-vnc-session-local-only.test.ts", "tests/unit/authz/routeGuard.test.ts", + "tests/unit/auto-combo-candidates-locked-model-visible.test.ts", "tests/unit/auto-combo-context-advertising.test.ts", "tests/unit/auto-combo-engine.test.ts", "tests/unit/auto-combo-scoring-clamp.test.ts", diff --git a/tests/unit/auto-combo-candidates-locked-model-visible.test.ts b/tests/unit/auto-combo-candidates-locked-model-visible.test.ts new file mode 100644 index 000000000000..6a5e18440ca1 --- /dev/null +++ b/tests/unit/auto-combo-candidates-locked-model-visible.test.ts @@ -0,0 +1,110 @@ +/** + * #9133 — a model lockout on one account must not silently drop that + * account's row from the auto-combo candidates inspector endpoint. The endpoint + * is read-only transparency (#7819 Level 1): a locked/cooled-down candidate + * must still be listed, decorated with `reachable:false` and an accurate + * reason (`modelLocked`), not deleted from the pool before the inspector + * ever sees it. + * + * Root cause (measured by community contributor ntdat812, ratified by the + * owner 2026-08-22): `prepareVirtualAutoComboInputs` unconditionally ran + * `filterResilienceBlockedCandidates` before the listing endpoint decorated + * the pool, so `modelLocked`/`reachable:false` were dead fields — the rows + * they would apply to were already gone. + */ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-auto-locked-visible-")); +const ORIGINAL_DATA_DIR = process.env.DATA_DIR; + +process.env.DATA_DIR = TEST_DATA_DIR; + +const core = await import("../../src/lib/db/core.ts"); +const providersDb = await import("../../src/lib/db/providers.ts"); +const candidateHandler = await import("../../open-sse/handlers/autoComboCandidates.ts"); +const accountFallback = await import("../../open-sse/services/accountFallback.ts"); + +async function resetStorage() { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); +} + +async function seedConnections() { + const tokenExpiresAt = new Date(Date.now() + 60_000).toISOString(); + const first = await providersDb.createProviderConnection({ + provider: "antigravity", + authType: "oauth", + email: "antigravity-locked@example.com", + accessToken: "fake-antigravity-access-token-one", + tokenExpiresAt, + }); + const second = await providersDb.createProviderConnection({ + provider: "antigravity", + authType: "oauth", + email: "antigravity-clean@example.com", + accessToken: "fake-antigravity-access-token-two", + tokenExpiresAt, + }); + return { first, second }; +} + +test.beforeEach(async () => { + await resetStorage(); + accountFallback.clearAllModelLockouts(); +}); + +test.after(async () => { + accountFallback.clearAllModelLockouts(); + await resetStorage(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + + if (ORIGINAL_DATA_DIR === undefined) { + delete process.env.DATA_DIR; + } else { + process.env.DATA_DIR = ORIGINAL_DATA_DIR; + } +}); + +test("a model-locked account row stays in the listing with reachable:false + modelLocked:true", async () => { + const { first, second } = await seedConnections(); + + // Lock a single model on a single account — the bare model id, matching + // every real lock writer (accountFallback.ts, resilienceCandidateFilter.ts). + accountFallback.lockModel( + "antigravity", + first.id, + "claude-sonnet-4-6", + "rate_limit", + 60_000 + ); + + const result = await candidateHandler.getAutoComboCandidates("auto", null); + const sonnetRows = result.candidates.filter( + (candidate) => + candidate.provider === "antigravity" && candidate.model === "antigravity/claude-sonnet-4-6" + ); + + // Baseline: both accounts must still be represented — the endpoint's + // stated role is read-only transparency, so a lock must never make a row + // disappear. + assert.deepEqual( + new Set(sonnetRows.map((candidate) => candidate.connectionId)), + new Set([first.id, second.id]), + "the locked account's row must remain listed, not be silently dropped" + ); + + const lockedRow = sonnetRows.find((candidate) => candidate.connectionId === first.id); + assert.ok(lockedRow, "locked account row must be present"); + assert.equal(lockedRow?.modelLocked, true, "locked row must report modelLocked:true"); + assert.equal(lockedRow?.reachable, false, "locked row must report reachable:false"); + + const cleanRow = sonnetRows.find((candidate) => candidate.connectionId === second.id); + assert.ok(cleanRow, "unlocked account row must be present"); + assert.equal(cleanRow?.modelLocked, false, "unlocked row must not report modelLocked"); + assert.equal(cleanRow?.reachable, true, "unlocked row must remain reachable"); +}); From c4bd8b8ec41ca63cb616fe02383622895bb8ca3b Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sat, 29 Aug 2026 09:28:03 -0300 Subject: [PATCH 18/91] fix(release): electron lockfile resync, build_ref, curated notes and SBOM on dispatch (twin of #11982 + #12020) (#12022) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(release): resync the electron lockfile, build a dispatch from a repaired ref, keep curated notes, attach the SBOM on dispatch (release/v3.8.51 twin of #11982 + #12020) Same four changes as #11982 and #12020 on main, applied to this branch's own copies: - electron/package-lock.json regenerated (271 -> 284 entries): the optional electron-builder-squirrel-windows subtree was missing and `npm ci` refused the lock (EUSAGE) on the Linux and macOS legs; a clean `npm ci --ignore-scripts` on the result exits 0. - electron-release.yml: `build_ref` dispatch input (default: the version tag) and `generate_release_notes` only on the tag push (a re-attach dispatch appended GitHub's auto notes to the curated body on v3.8.50). - npm-publish.yml: the SBOM attaches to the GitHub Release on workflow_dispatch publishes too, whenever a release for the tag exists. actionlint and prettier clean; electron-release-desktop-channel-8949, electron-release-efficiency, electron-release-latest-yml.repro, check-workflows and npm-publish-artifact-provenance suites pass. * fix(release): validate build_ref in the validate job before any checkout uses it CodeQL (actions/cache-poisoning/poisonable-step, high) on release/v3.8.51 — the default branch: a raw dispatch input checked out next to setup-node's npm cache is a cache-poisoning vector. The input now goes through the validate job's regex allowlist (main or release/vX.Y.Z, empty = the version tag) and every build job checks out needs.validate.outputs.build_ref, never the input itself. * fix(release): drop the build_ref input — a dispatch builds the ref it is dispatched on CodeQL (actions/cache-poisoning/poisonable-step) tracks the input through the validate job's output regardless of the regex allowlist: an input-controlled checkout next to setup-node's npm cache on the default branch is a cache-poisoning vector. The ref is not an input any more; the checkouts use github.ref, so `gh workflow run electron-release.yml --ref v3.8.50 -f version=v3.8.50` rebuilds the tag and `--ref main` builds the repaired line. The tag-push path is unchanged. --- .github/workflows/electron-release.yml | 17 +- .github/workflows/npm-publish.yml | 15 +- .../v3851-electron-lockfile-build-ref-sbom.md | 1 + electron/package-lock.json | 195 ++++++++++++++++++ 4 files changed, 216 insertions(+), 12 deletions(-) create mode 100644 changelog.d/fixes/v3851-electron-lockfile-build-ref-sbom.md diff --git a/.github/workflows/electron-release.yml b/.github/workflows/electron-release.yml index 913071d3118e..4d5a36903a2e 100644 --- a/.github/workflows/electron-release.yml +++ b/.github/workflows/electron-release.yml @@ -4,6 +4,10 @@ on: push: tags: - "v*" + # A dispatch builds the ref it is dispatched ON (`gh workflow run … --ref v3.8.50` rebuilds + # that tag; `--ref main` builds the repaired line). The ref is deliberately NOT an input: + # CodeQL flags an input-controlled checkout next to the npm cache on the default branch as + # cache poisoning (actions/cache-poisoning/poisonable-step), and `github.ref` is trusted. workflow_dispatch: inputs: version: @@ -81,9 +85,6 @@ jobs: - uses: actions/checkout@v7 with: persist-credentials: false - # workflow_dispatch: build the tag being (re)built, not the dispatching branch. On a - # tag push this resolves to the same commit. - ref: ${{ needs.validate.outputs.version }} - name: Setup Node uses: actions/setup-node@v7 with: @@ -169,9 +170,6 @@ jobs: - uses: actions/checkout@v7 with: persist-credentials: false - # workflow_dispatch: build the tag being (re)built, not the dispatching branch. On a - # tag push this resolves to the same commit. - ref: ${{ needs.validate.outputs.version }} - name: Setup Node uses: actions/setup-node@v7 with: @@ -358,8 +356,6 @@ jobs: with: persist-credentials: false fetch-depth: 0 - # Source archives + SBOM come from the tag being released, not the dispatching branch. - ref: ${{ needs.validate.outputs.version }} # `merge-multiple` is deliberately OFF. It resolves same-name collisions by ARRIVAL # ORDER, and the two macOS jobs each emit their own `latest-mac.yml` listing only their @@ -417,7 +413,10 @@ jobs: tag_name: ${{ needs.validate.outputs.version }} draft: false prerelease: false - generate_release_notes: true + # Only on the tag push. A re-attach dispatch runs against a release whose curated + # notes already exist, and `true` APPENDS GitHub's auto-generated "What's Changed" + # block to them (v3.8.50, run 33238093090: +1,416 chars on a 121 KB body). + generate_release_notes: ${{ github.event_name != 'workflow_dispatch' }} fail_on_unmatched_files: false files: | release-assets/*.dmg diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index c34dd6e01449..5ba76f069fcc 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -273,11 +273,20 @@ jobs: if-no-files-found: error - name: Attach SBOM to GitHub Release - if: steps.resolve.outputs.skip != 'true' && github.event_name == 'release' + # Not only on the `release` event: the v3.8.50 package shipped through a + # workflow_dispatch (staged publish, 11 attempts) and this step was skipped, so the + # GitHub Release carried no SBOM until it was attached by hand from the run's + # `sbom-npm` artifact. Attach whenever a release for the published tag exists. + if: steps.resolve.outputs.skip != 'true' && (github.event_name == 'release' || github.event_name == 'workflow_dispatch') env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TAG: ${{ github.ref_name }} - run: gh release upload "$TAG" sbom-npm.cdx.json --clobber + TAG: ${{ github.event_name == 'release' && github.ref_name || format('v{0}', inputs.version) }} + run: | + if ! gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then + echo "::notice::no GitHub Release for $TAG yet — SBOM stays on the sbom-npm workflow artifact" + exit 0 + fi + gh release upload "$TAG" sbom-npm.cdx.json --repo "$GITHUB_REPOSITORY" --clobber # WS1.2/WS1.3 (#7065 class): the artifact that is about to be published must # BOOT. build:cli already assembled dist/ above; this packs+installs+boots the diff --git a/changelog.d/fixes/v3851-electron-lockfile-build-ref-sbom.md b/changelog.d/fixes/v3851-electron-lockfile-build-ref-sbom.md new file mode 100644 index 000000000000..3e4462e0e43c --- /dev/null +++ b/changelog.d/fixes/v3851-electron-lockfile-build-ref-sbom.md @@ -0,0 +1 @@ +- Electron release: `electron/package-lock.json` regained the optional `electron-builder-squirrel-windows` subtree (13 entries) that `npm ci` had been refusing as out of sync, `electron-release.yml` gained a `build_ref` dispatch input and stops regenerating release notes on a re-attach dispatch, and the npm publish workflow attaches the SBOM to the GitHub Release on dispatch publishes too — so the v3.8.51 tag ships every desktop asset and the SBOM like v3.8.49 did diff --git a/electron/package-lock.json b/electron/package-lock.json index 77101ed8c559..61b1da8b88d3 100644 --- a/electron/package-lock.json +++ b/electron/package-lock.json @@ -297,6 +297,45 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@electron/windows-sign": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@electron/windows-sign/-/windows-sign-1.2.2.tgz", + "integrity": "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "peer": true, + "dependencies": { + "cross-dirname": "^0.1.0", + "debug": "^4.3.4", + "fs-extra": "^11.1.1", + "minimist": "^1.2.8", + "postject": "^1.0.0-alpha.6" + }, + "bin": { + "electron-windows-sign": "bin/electron-windows-sign.js" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/windows-sign/node_modules/fs-extra": { + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.0.tgz", + "integrity": "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", @@ -1091,6 +1130,15 @@ "dev": true, "license": "MIT" }, + "node_modules/cross-dirname": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/cross-dirname/-/cross-dirname-0.1.0.tgz", + "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1411,6 +1459,19 @@ "node": ">=14.0.0" } }, + "node_modules/electron-builder-squirrel-windows": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-26.15.3.tgz", + "integrity": "sha512-Jc19XPV9y9+2bAdZPkXuVNGNIEFBq9poHC61l8Kv6FdK7DRG3+Ic0rerC0DXOaeHNz8yW0fg/JnF8GQROOF5MA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "app-builder-lib": "26.15.3", + "builder-util": "26.15.3", + "electron-winstaller": "5.4.0" + } + }, "node_modules/electron-publish": { "version": "26.15.3", "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-26.15.3.tgz", @@ -1445,6 +1506,66 @@ "tiny-typed-emitter": "^2.1.0" } }, + "node_modules/electron-winstaller": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/electron-winstaller/-/electron-winstaller-5.4.0.tgz", + "integrity": "sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@electron/asar": "^3.2.1", + "debug": "^4.1.1", + "fs-extra": "^7.0.1", + "lodash": "^4.17.21", + "temp": "^0.9.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "optionalDependencies": { + "@electron/windows-sign": "^1.1.2" + } + }, + "node_modules/electron-winstaller/node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/electron-winstaller/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "peer": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-winstaller/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -2359,6 +2480,20 @@ "node": ">= 18" } }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2622,6 +2757,36 @@ "node": ">=18" } }, + "node_modules/postject": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/postject/-/postject-1.0.0-alpha.6.tgz", + "integrity": "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "commander": "^9.4.0" + }, + "bin": { + "postject": "dist/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/postject/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, "node_modules/proc-log": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", @@ -2816,6 +2981,21 @@ "node": ">= 4" } }, + "node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, "node_modules/roarr": { "version": "2.15.4", "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", @@ -3071,6 +3251,21 @@ "node": ">=18" } }, + "node_modules/temp": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", + "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "mkdirp": "^0.5.1", + "rimraf": "~2.6.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/temp-file": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz", From 38e2616464fac4681c1f7a4e05dc9974e99e1dde Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sat, 29 Aug 2026 09:52:00 -0300 Subject: [PATCH 19/91] fix(ci): stop hosted docker-publish OOM and unpaint Build (advisory) (#12021) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(ci): stop hosted docker-publish OOM and unpaint Build (advisory) docker-publish was firing 8 concurrent hosted builds on every merge storm; each died ResourceExhausted in npm run build (#11976). One publish per ref, webpack instead of Turbopack so native RSS stays inside the V8 heap we can cap. Build (advisory) is skipped: continue-on-error still reports FAILURE and was painting every fork PR red. Closes #11976 * fix(ci): run docker-publish amd64 on omni-build and share the heavy lane The .113 box is 31 GB / 32 cores — enough for one next-build. Hosted ubuntu-24.04 is ~7 GB and ResourceExhausted every publish (#11976). amd64 now targets [self-hosted, omni-build] (Turbopack) when USE_VPS_RUNNER is on, joins the existing heavy-build-main group so it queues beside ci.yml Build instead of becoming a third heavy, and falls back to hosted + webpack if the VPS is off. arm64 stays on ubuntu-24.04-arm with webpack (no ARM box). * test(ci): align the advisory-build contract with the hosted-OOM skip if: ${{ false }} tripped zizmor obfuscation (194→195). Bare if: false skips the job without a new finding. The #7307 test now pins the skip and keeps the job body as the restore recipe. --- .github/workflows/ci.yml | 6 +-- .github/workflows/docker-publish.yml | 37 +++++++++++++++++-- .github/workflows/quality.yml | 11 +++++- ...cker-publish-webpack-and-advisory-build.md | 1 + docs/ops/RUNNER_BOX.md | 26 +++++++++---- tests/unit/build/check-workflows.test.ts | 26 +++++-------- 6 files changed, 77 insertions(+), 30 deletions(-) create mode 100644 changelog.d/maintenance/11976-docker-publish-webpack-and-advisory-build.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 441d19160e62..c15bf1ca12f5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -621,9 +621,9 @@ jobs: # 13:50Z the kernel OOM-killed main's build while a PR build ran beside it # (five Build jobs had been queued by a burst of PRs). Two lanes: main keeps # its own so a release is never queued behind PR traffic; PR builds serialize - # among themselves. GitHub keeps one running + one pending per group and - # CANCELS older pendings — a cancelled PR build is re-runnable; a dead main - # build costs the publish its artefact and a 40-minute rebuild that OOMs. + # among themselves. docker-publish.yml's amd64 leg joins `heavy-build-main` + # so a :next image build waits beside this artefact instead of becoming the + # third heavy (#11976). GitHub keeps one running + one pending per group. concurrency: group: heavy-build-${{ github.ref == 'refs/heads/main' && 'main' || 'pr' }} cancel-in-progress: false diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 37578d6e4f72..fc288a9cb739 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -26,6 +26,14 @@ on: type: boolean default: false +# One publish per ref. A merge storm used to fan out 8 concurrent hosted builds, +# every one OOM-killing `npm run build` inside BuildKit (#11976). The :next +# channel only needs the newest SHA; cancel-in-progress is the same pattern as +# quality.yml / nightly-release-green. +concurrency: + group: docker-publish-${{ github.ref }} + cancel-in-progress: true + # Least-privilege default: read-only at the top level; the build and merge jobs that # push to GHCR grant packages: write themselves (Scorecard TokenPermissions). permissions: @@ -118,7 +126,19 @@ jobs: name: Build Docker (${{ matrix.platform }}) needs: prepare if: needs.prepare.outputs.skip != 'true' - runs-on: ${{ matrix.runner }} + # amd64: the .113 omni-build pool (31 GB / 32 cores, two listeners). A + # next-build peaks at 14–16 GB; hosted ubuntu-24.04 is ~7 GB and dies + # ResourceExhausted (#11976). Falls back to hosted + webpack when + # USE_VPS_RUNNER is off. arm64: no ARM box — stay on GitHub's ubuntu-24.04-arm + # with webpack (Turbopack native RSS does not fit 7 GB). + runs-on: ${{ matrix.arch == 'amd64' && (vars.USE_VPS_RUNNER == 'true' && fromJSON('["self-hosted","omni-build"]') || 'ubuntu-24.04') || 'ubuntu-24.04-arm' }} + # Share the 2-slot omni-build ceiling with ci.yml `Build` / npm-publish. + # Same group as main's Build so a :next publish waits beside the artefact + # instead of becoming the third heavy and getting kernel-OOM'd (2026-08-28). + # arm64 is hosted — its own group, cancelled by the workflow-level concurrency. + concurrency: + group: ${{ matrix.arch == 'amd64' && 'heavy-build-main' || format('docker-publish-arm-{0}', github.ref) }} + cancel-in-progress: ${{ matrix.arch != 'amd64' }} permissions: contents: read packages: write @@ -127,14 +147,14 @@ jobs: matrix: include: - platform: linux/amd64 - runner: ubuntu-24.04 arch: amd64 - platform: linux/arm64 - runner: ubuntu-24.04-arm arch: arm64 env: IMAGE_NAME: diegosouzapw/omniroute GHCR_IMAGE_NAME: ghcr.io/diegosouzapw/omniroute + # Turbopack on the 31 GB box; webpack on any hosted 7 GB fallback / ARM. + USE_TURBOPACK: ${{ (matrix.arch == 'amd64' && vars.USE_VPS_RUNNER == 'true') && '1' || '0' }} steps: - name: Checkout uses: actions/checkout@v7 @@ -143,6 +163,9 @@ jobs: ref: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/v{0}', inputs.version) || '' }} fetch-depth: 0 + - name: Assert Docker Engine + run: docker info + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 @@ -166,6 +189,8 @@ jobs: context: . target: runner-base platforms: ${{ matrix.platform }} + build-args: | + OMNIROUTE_USE_TURBOPACK=${{ env.USE_TURBOPACK }} outputs: type=image,push-by-digest=true,name-canonical=true,push=true tags: | ${{ env.IMAGE_NAME }} @@ -183,6 +208,8 @@ jobs: context: . target: runner-web platforms: ${{ matrix.platform }} + build-args: | + OMNIROUTE_USE_TURBOPACK=${{ env.USE_TURBOPACK }} outputs: type=image,push-by-digest=true,name-canonical=true,push=true tags: | ${{ env.IMAGE_NAME }} @@ -208,6 +235,8 @@ jobs: file: Dockerfile.bun target: runner-base platforms: ${{ matrix.platform }} + build-args: | + OMNIROUTE_USE_TURBOPACK=${{ env.USE_TURBOPACK }} outputs: type=image,push-by-digest=true,name-canonical=true,push=true tags: | ${{ env.IMAGE_NAME }} @@ -233,6 +262,8 @@ jobs: file: Dockerfile.bun target: runner-web platforms: ${{ matrix.platform }} + build-args: | + OMNIROUTE_USE_TURBOPACK=${{ env.USE_TURBOPACK }} outputs: type=image,push-by-digest=true,name-canonical=true,push=true tags: | ${{ env.IMAGE_NAME }} diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index f4006490ffb5..a21597e8ba7c 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -70,7 +70,16 @@ jobs: # 2026-08-14: 72 of the last 100 PRs into release/** came from forks, so the fork case is # the majority of the traffic, not the exception — this job earns its place, it just should # not duplicate build.yml for the own-origin 28%. - if: ${{ github.event_name != 'pull_request' || ((github.event.pull_request.draft == false || startsWith(github.head_ref, 'mergify/merge-queue/')) && needs.changes.outputs.code == 'true' && github.event.pull_request.head.repo.full_name != github.repository) }} + # Disabled 2026-08-29 (#11976 follow-up). `continue-on-error: true` still + # reports a GitHub check FAILURE, so every fork PR into release/** was born + # with a red "Build (advisory)" even when every required gate was green + # (sweep-reds, 41 PRs). Hosted ubuntu-latest cannot finish `npm run build` + # on this tree — VM shutdown ~6 min in, same class as build.yml going + # workflow_dispatch-only in #11962. Pre-merge build signal for release/** + # is nightly-release-green (omni-build); for main it is ci.yml `Build`. + # Restore this job when a runner that actually fits the tree is wired here. + # Bare `false` (not `${{ false }}`) — zizmor obfuscation flags the expression form. + if: false # PINNED to hosted — this was the last job in THIS workflow still on the USE_VPS_RUNNER # switch (ci.yml's Build, nightly-release-green and npm-publish keep it, so the variable # stays meaningful), and with USE_VPS_RUNNER=true it produced NO signal at all here. diff --git a/changelog.d/maintenance/11976-docker-publish-webpack-and-advisory-build.md b/changelog.d/maintenance/11976-docker-publish-webpack-and-advisory-build.md new file mode 100644 index 000000000000..ac3c86ae82e5 --- /dev/null +++ b/changelog.d/maintenance/11976-docker-publish-webpack-and-advisory-build.md @@ -0,0 +1 @@ +- Stop painting every fork PR into `release/**` red: `quality.yml` `Build (advisory)` is skipped (GitHub still reports `continue-on-error` failures as check FAILURE). Hosted `ubuntu-latest` cannot finish `npm run build` on this tree — same class as #11962 taking `build.yml` off the PR rail. `docker-publish.yml` amd64 now runs on the `.113` `omni-build` pool (31 GB / 32 cores, two listeners) with Turbopack, shares the `heavy-build-main` lane with `ci.yml` `Build` so it queues instead of becoming a third heavy, and keeps per-ref concurrency (a merge storm was starting 8 concurrent OOM builds). arm64 stays on `ubuntu-24.04-arm` with webpack — there is no ARM box. Fallback when `USE_VPS_RUNNER` is off: hosted amd64 + webpack (#11976). diff --git a/docs/ops/RUNNER_BOX.md b/docs/ops/RUNNER_BOX.md index c9719bb05348..4b6fef353dde 100644 --- a/docs/ops/RUNNER_BOX.md +++ b/docs/ops/RUNNER_BOX.md @@ -50,13 +50,25 @@ a time, only when idle**, with the idle check and the restart in the same comman ## Operating rules - **Heavy-build ceiling: 2 at a time — enforced by label.** Every job that runs a - `next build` (`ci.yml` `build`, `npm-publish.yml` `publish`, both `nightly-release-green` - validations) targets `[self-hosted, omni-build]`, and only **two** runners carry that - label (`omniroute-113-5`, `omniroute-113-6`, added through the runners API — no - re-registration). The other six keep `omni-release` and take nothing heavy; GitHub - queues a third build instead of the kernel killing one. Pair with the `heavy-build-*` - concurrency lanes in `ci.yml`. To add capacity, label another runner — never raise - the count past what 31 GB holds (one next-build ≈ 14–16 GB). + full `next build` targets `[self-hosted, omni-build]`, and only **two** runners carry + that label (`omniroute-113-5`, `omniroute-113-6`, added through the runners API — no + re-registration): + - `ci.yml` `Build` and `npm-publish.yml` `publish` + - both `nightly-release-green` validations + - `docker-publish.yml` **amd64** (the hosted 7 GB runner ResourceExhausted this + tree — #11976). The arm64 leg stays on `ubuntu-24.04-arm` (no ARM box) with + webpack. + The other listeners keep `omni-release` / `omni-light` and take nothing heavy; + GitHub queues a third build instead of the kernel killing one. Pair with the + `heavy-build-*` concurrency lanes: `ci.yml` `Build` on `main` and docker-publish + amd64 share `heavy-build-main` (`cancel-in-progress: false`) so a `:next` publish + waits beside the artefact instead of sitting next to it. PR builds use + `heavy-build-pr`. To add capacity, label another runner — never raise the count + past what 31 GB holds (one next-build ≈ 14–16 GB; a Docker amd64 build is the + same class plus the daemon — still one slot). + Docker Engine must be on those two units (`docker info` is the first step of + the publish job). If it is missing, the job fails closed instead of hanging on + `setup-buildx`. - **Light pool: `omni-light` (2026-08-29, #11965).** `omniroute-113` and `omniroute-113-2` carry `omni-light` for jobs that need a backend-only `next build` (~5–6 GB) but not a full one: the nightly Schemathesis, promptfoo, garak and axe-a11y jobs. They ran on the hosted 7 GB runner and diff --git a/tests/unit/build/check-workflows.test.ts b/tests/unit/build/check-workflows.test.ts index fea3f3476ea4..245ca625c61f 100644 --- a/tests/unit/build/check-workflows.test.ts +++ b/tests/unit/build/check-workflows.test.ts @@ -318,7 +318,7 @@ test("readBaselineZizmorValue: invalid JSON returns null (does not throw)", () = // quality.yml — release PR build gate regression coverage (#7307) // ───────────────────────────────────────────────────────────────────────────── -test("#7307 quality.yml adds an advisory production build for release PR code changes", () => { +test("#7307 quality.yml keeps the advisory production build (disabled: hosted 7 GB cannot finish it)", () => { const source = readQualityWorkflow(); const buildJob = source.match(/\n build:\n[\s\S]*?\n # Docs\/OpenAPI contract gates only/); @@ -326,17 +326,15 @@ test("#7307 quality.yml adds an advisory production build for release PR code ch assert.ok(buildJob, "quality.yml must define the build job before docs-gates"); assert.match(buildJob[0], /name: Build \(advisory\)/); assert.match(buildJob[0], /needs: changes/); - assert.match(buildJob[0], /needs\.changes\.outputs\.code == 'true'/); - assert.match(buildJob[0], /github\.event\.pull_request\.draft == false/); - assert.match(buildJob[0], /startsWith\(github\.head_ref, 'mergify\/merge-queue\/'\)/); - // FORK PRs ONLY (2026-08-14). build.yml's `Fast Production Build` fires on - // `push: branches: ["**"]` and runs the superset `build:release`, so own-origin branches - // were building twice; a fork's push never reaches this repo, making this their only - // pre-merge build signal — and forks are 72 of the last 100 PRs into release/**. - assert.match( - buildJob[0], - /github\.event\.pull_request\.head\.repo\.full_name != github\.repository/ - ); + // Live predicate is a static skip. `continue-on-error` still paints a GitHub + // check FAILURE, and the hosted runner ResourceExhausts this tree (#11976) — + // so the job must not run. Bare `if: false` (zizmor obfuscation flags + // `${{ false }}`). The body below is the restore recipe, not dead code. + const buildDirectives = buildJob[0] + .split("\n") + .filter((line) => !/^\s*#/.test(line)) + .join("\n"); + assert.match(buildDirectives, /\n {4}if: false\n/); // Runner PINNED to hosted. The self-hosted pool is 2 permanently-busy runners, where this // job either queued for hours or was killed by cancel-in-progress — ~10-15% of runs ever // reached a conclusion across 2026-08-13/14. It must NOT go back on the USE_VPS_RUNNER @@ -345,10 +343,6 @@ test("#7307 quality.yml adds an advisory production build for release PR code ch // Check the DIRECTIVES, not the prose: the comment above legitimately explains why the // self-hosted pool was abandoned, so a naive /self-hosted/ scan over the whole block would // match its own rationale. - const buildDirectives = buildJob[0] - .split("\n") - .filter((line) => !/^\s*#/.test(line)) - .join("\n"); assert.doesNotMatch(buildDirectives, /self-hosted/); assert.doesNotMatch(buildDirectives, /USE_VPS_RUNNER/); // Memory provisioning mirrored from build.yml: --max-old-space-size bounds only V8's heap, From 47f7e5a3060fdd52312788b980e276ef9d99ee47 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sat, 29 Aug 2026 14:07:58 -0300 Subject: [PATCH 20/91] fix(release): the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (twin of #12032) (#12047) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(release): the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (release/v3.8.51 twin of #12032) Same change as #12032 on main: the packaged app opens SQLite during the smoke but its primary open path prints no "[DB] Driver: …" line (only the recovery path and the sql.js fallback do), so the #7592 assertion failed every Linux release leg. The guard rejects the sql.js fallback line, accepts a native driver line, and otherwise accepts demonstrable database activity; after readiness the smoke requests /api/monitoring/health and waits for that activity outside the readiness loop. electron-smoke-script suite 10/10. * fix(release): reapply the smoke rework on top of release/v3.8.51's own copy of the script The previous commit copied main's file wholesale and dropped this branch's ensureSmokeEnvDirs(currentPlatform) fix and its tests; this reapplies only the DB-open evidence change as a patch. electron-smoke-script suite green. --- scripts/dev/smoke-electron-packaged.mjs | 51 +++++++++++++++++++++++- tests/unit/electron-smoke-script.test.ts | 44 +++++++++++++++++++- 2 files changed, 93 insertions(+), 2 deletions(-) diff --git a/scripts/dev/smoke-electron-packaged.mjs b/scripts/dev/smoke-electron-packaged.mjs index e98de67a2128..cda84d3d0def 100644 --- a/scripts/dev/smoke-electron-packaged.mjs +++ b/scripts/dev/smoke-electron-packaged.mjs @@ -123,6 +123,48 @@ function discoverPackagedExecutable() { throw new Error(`Packaged Electron smoke check does not support ${platform()}.`); } +/** + * The packaged app opens SQLite lazily: `/login` (the readiness URL) never touches the + * database, so a smoke that only waits for readiness sees no `[DB]` line at all. After + * readiness the smoke requests a DB-backed endpoint and waits for evidence that the + * database opened. The primary open path does NOT print "[DB] Driver: ..." (only the + * recovery path and the sql.js fallback do), so the evidence is any `[DB]`/`[Migration]` + * startup line — and the #7592 guard below rejects the fallback's own line explicitly. + */ +export const DB_TOUCH_PATH = "/api/monitoring/health"; +export const DB_OPEN_EVIDENCE_PATTERN = + /\[DB\] (Driver: |SQLite database ready|Added [^\n]* column|Changing cache_size|cache_size changed)|\[Migration\] (Applied|Pre-migration backup)/; + +export async function waitForDatabaseOpen(getLogs, { timeoutMs = 15_000, pollMs = 250 } = {}) { + const startedAt = Date.now(); + while (Date.now() - startedAt < timeoutMs) { + const logs = getLogs(); + assertNoFatalLogs(logs); + if (DB_OPEN_EVIDENCE_PATTERN.test(logs)) return logs; + await sleep(pollMs); + } + throw new Error( + `Packaged Electron app logged no [DB]/[Migration] startup line within ${timeoutMs}ms of ` + + `touching ${DB_TOUCH_PATH} — the database never opened, so the SQLite driver cannot be verified.` + ); +} + +async function openDatabaseForSmoke({ logs, smokeUrl }) { + const touchUrl = new URL(DB_TOUCH_PATH, smokeUrl).toString(); + try { + const response = await fetchWithTimeout(touchUrl, 5_000); + console.log( + `[electron-smoke] touched ${touchUrl} (HTTP ${response.status}) to open the database` + ); + } catch (error) { + console.log( + `[electron-smoke] touching ${touchUrl} failed (${error instanceof Error ? error.message : String(error)}) — waiting for the database anyway` + ); + } + await waitForDatabaseOpen(() => logs.value); + console.log("[electron-smoke] database opened"); +} + async function fetchWithTimeout(url, timeoutMs) { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), timeoutMs); @@ -465,8 +507,13 @@ export function assertNativeDriverSelected(logs) { ); } + // The primary open path prints no "[DB] Driver: ..." line at all (only the recovery path and + // the sql.js fallback do), so a database that demonstrably opened WITHOUT the fallback's own + // line is the native driver — that is exactly what #7592 guards. + if (DB_OPEN_EVIDENCE_PATTERN.test(logs)) return; + throw new Error( - "Packaged Electron app logs contain no '[DB] Driver: ...' line — cannot confirm which SQLite " + + "Packaged Electron app logs show no database activity at all — cannot confirm which SQLite " + "driver loaded." ); } @@ -560,6 +607,8 @@ async function launchAndCollectLogs({ try { await waitForReady({ logs, smokeUrl, timeoutMs, settleMs, exitState }); + // Outside waitForReady on purpose: a missing database is a verdict, not a readiness retry. + await openDatabaseForSmoke({ logs, smokeUrl }); return logs.value; } catch (error) { if (!streamLogs) { diff --git a/tests/unit/electron-smoke-script.test.ts b/tests/unit/electron-smoke-script.test.ts index 5c1ad11dc391..3fd5c40aea42 100644 --- a/tests/unit/electron-smoke-script.test.ts +++ b/tests/unit/electron-smoke-script.test.ts @@ -11,6 +11,8 @@ import { FATAL_LOG_PATTERNS, LINUX_EXECUTABLE_NAMES, stopApp, + waitForDatabaseOpen, + DB_TOUCH_PATH, } from "../../scripts/dev/smoke-electron-packaged.mjs"; import { tarPack } from "../../scripts/build/optionalPackStaging.mjs"; @@ -147,6 +149,46 @@ test("electron smoke flags a cold-restart fallback to the sql.js WASM driver", ( test("electron smoke flags startup logs missing any driver selection line", () => { assert.throws( () => assertNativeDriverSelected("[electron] [server] listening on 20128"), - /no '\[DB\] Driver: \.\.\.' line/ + /no database activity/ + ); +}); + +test("electron smoke waits for database-open evidence after touching a DB-backed endpoint", async () => { + assert.equal(DB_TOUCH_PATH, "/api/monitoring/health"); + let logs = "[electron] [Server] [STARTUP] ready\n"; + setTimeout(() => { + logs += "[electron] [Server] [DB] Added usage_history.combo_strategy column\n"; + }, 60); + const seen = await waitForDatabaseOpen(() => logs, { timeoutMs: 2_000, pollMs: 20 }); + assert.match(seen, /\[DB\] Added/); +}); + +test("electron smoke fails clearly when the database never opens", async () => { + await assert.rejects( + () => + waitForDatabaseOpen(() => "[electron] [Server] [STARTUP] ready\n", { + timeoutMs: 120, + pollMs: 20, + }), + /logged no \[DB\]\/\[Migration\] startup line within 120ms/ + ); +}); + +test("electron smoke driver guard: native line, DB evidence and sql.js fallback", () => { + assert.doesNotThrow(() => + assertNativeDriverSelected("[DB] Driver: better-sqlite3 | file: /tmp/x/storage.sqlite\n") + ); + assert.doesNotThrow(() => + assertNativeDriverSelected( + "[electron] [Server] [DB] Added call_logs.session_tag column\n[electron] [Server] [Migration] Applied: 046_database_settings\n" + ) + ); + assert.throws( + () => assertNativeDriverSelected("[DB] Driver: sql.js | file: /tmp/x/storage.sqlite\n"), + /sql\.js \(WASM\) driver/ + ); + assert.throws( + () => assertNativeDriverSelected("[STARTUP] nothing here\n"), + /no database activity/ ); }); From a9aee94a0060aaf0f578e4589c4962d0b6dea28d Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sat, 29 Aug 2026 14:41:20 -0300 Subject: [PATCH 21/91] docs(ops): the .113 heavy-build ceiling is one runner, not two (#12048) * docs(ops): the .113 heavy-build ceiling is one runner, not two Two concurrent next-builds (15.4 GB + 17.2 GB RSS) OOM-killed one on 2026-08-29 17:26 UTC; systemd booked the kill on the other runner's unit and its job died with the same "shutdown signal" text a hosted-runner OOM shows. omni-build now lives on omniroute-113-5 only; 113-6 keeps omni-release. The janitor ceiling counts every listener on the box (4 OmniRoute + OmniHeuris + OmniMind = 6). The second heavy slot returns when the Proxmox VM gets more RAM; the exact command is in the doc. * docs(ops): apply the single-heavy-slot text (previous commit only carried formatting) --- docs/ops/RUNNER_BOX.md | 52 +++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/docs/ops/RUNNER_BOX.md b/docs/ops/RUNNER_BOX.md index 4b6fef353dde..96b638a1ebbc 100644 --- a/docs/ops/RUNNER_BOX.md +++ b/docs/ops/RUNNER_BOX.md @@ -7,13 +7,13 @@ title: Self-Hosted Runner Box Operations The self-hosted pool (`self-hosted, omni-release` on all eight runners; `omni-build` on two) runs on the **.113** box. Measured 2026-08-28 (v3.8.50 postmortem, Parte III): -| resource | value | what it means for scheduling | -| --------- | -------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | -| RAM / CPU | **31 GB / 32 cores** (was 16 GB when this doc was first written) | one `next-build` peaks at **~14 GB** → 2 concurrent heavy builds saturate the box, 3 take it down (2026-08-28 06:42Z: load 56, two jobs lost) | -| swap | 15 GB | it swapped its way through the v3.8.50 publish; pressure shows in `/proc/pressure/memory` | -| `/tmp` | **12 GB tmpfs = RAM** | anything parked there is memory; leftovers are swept after 3 h | -| disk | 188 GB | `_work` checkouts of 8 runners reach ~70 GB with no cap | -| runners | **6 listeners**: 4 OmniRoute (2 `omni-build` + 2 `omni-light`) + OmniHeuris + OmniMind | all share the memory above; `omniroute-113-3/-4/-7/-8` are disabled (`systemctl enable --now` brings one back) | +| resource | value | what it means for scheduling | +| --------- | -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | +| RAM / CPU | **31 GB / 32 cores** (was 16 GB when this doc was first written) | one `next-build` peaks at **~14 GB** → 2 concurrent heavy builds saturate the box, 3 take it down (2026-08-28 06:42Z: load 56, two jobs lost) | +| swap | 15 GB | it swapped its way through the v3.8.50 publish; pressure shows in `/proc/pressure/memory` | +| `/tmp` | **12 GB tmpfs = RAM** | anything parked there is memory; leftovers are swept after 3 h | +| disk | 188 GB | `_work` checkouts of 8 runners reach ~70 GB with no cap | +| runners | **6 listeners**: 4 OmniRoute (1 `omni-build` + 1 `omni-release`-only + 2 `omni-light`) + OmniHeuris + OmniMind | all share the memory above; `omniroute-113-3/-4/-7/-8` are disabled (`systemctl enable --now` brings one back) | ## Install the janitor (one-time, on the box) @@ -21,7 +21,7 @@ Measured 2026-08-28 (v3.8.50 postmortem, Parte III): scp scripts/ops/runner-janitor.sh root@192.168.0.113:/opt/omniroute-ops/runner-janitor.sh ssh root@192.168.0.113 'chmod +x /opt/omniroute-ops/runner-janitor.sh; apt-get install -y lsof' # cron (root): every 30 min, log to /var/log/runner-janitor.log -*/30 * * * * MAX_ACTIVE_RUNNERS=4 /opt/omniroute-ops/runner-janitor.sh >> /var/log/runner-janitor.log 2>&1 +*/30 * * * * MAX_ACTIVE_RUNNERS=6 /opt/omniroute-ops/runner-janitor.sh >> /var/log/runner-janitor.log 2>&1 ``` `lsof` is required: the janitor proves a path is idle with one snapshot of open @@ -49,26 +49,19 @@ a time, only when idle**, with the idle check and the restart in the same comman ## Operating rules -- **Heavy-build ceiling: 2 at a time — enforced by label.** Every job that runs a - full `next build` targets `[self-hosted, omni-build]`, and only **two** runners carry - that label (`omniroute-113-5`, `omniroute-113-6`, added through the runners API — no - re-registration): - - `ci.yml` `Build` and `npm-publish.yml` `publish` - - both `nightly-release-green` validations - - `docker-publish.yml` **amd64** (the hosted 7 GB runner ResourceExhausted this - tree — #11976). The arm64 leg stays on `ubuntu-24.04-arm` (no ARM box) with - webpack. - The other listeners keep `omni-release` / `omni-light` and take nothing heavy; - GitHub queues a third build instead of the kernel killing one. Pair with the - `heavy-build-*` concurrency lanes: `ci.yml` `Build` on `main` and docker-publish - amd64 share `heavy-build-main` (`cancel-in-progress: false`) so a `:next` publish - waits beside the artefact instead of sitting next to it. PR builds use - `heavy-build-pr`. To add capacity, label another runner — never raise the count - past what 31 GB holds (one next-build ≈ 14–16 GB; a Docker amd64 build is the - same class plus the daemon — still one slot). - Docker Engine must be on those two units (`docker info` is the first step of - the publish job). If it is missing, the job fails closed instead of hanging on - `setup-buildx`. +- **Heavy-build ceiling: ONE at a time — enforced by label (since 2026-08-29).** Every job + that runs a `next build` (`ci.yml` `build`, `npm-publish.yml` `publish`, both + `nightly-release-green` validations) targets `[self-hosted, omni-build]`, and only + **`omniroute-113-5`** carries that label (added through the runners API — no + re-registration). Two was the previous ceiling and it was wrong for 31 GB: on + 2026-08-29 17:26 UTC two concurrent `next-build`s (15.4 GB + 17.2 GB RSS) drove the box + to 5 GB free with 4 GB of swap in use and the kernel OOM-killed one of them — systemd + booked the kill on the _other_ runner's unit, `runsvc.sh` SIGKILLed that listener, and + the job on it died with "The runner has received a shutdown signal" (same text as a + hosted-runner OOM). `omniroute-113-6` keeps `omni-release` only. Heavy builds from + `main` merges, PRs and the nightly now serialize on one slot; the queue is the price. + The second slot comes back the day the Proxmox VM gets more RAM (48–64 GB): + `gh api -X POST repos//actions/runners//labels -f 'labels[]=omni-build'`. - **Light pool: `omni-light` (2026-08-29, #11965).** `omniroute-113` and `omniroute-113-2` carry `omni-light` for jobs that need a backend-only `next build` (~5–6 GB) but not a full one: the nightly Schemathesis, promptfoo, garak and axe-a11y jobs. They ran on the hosted 7 GB runner and @@ -77,7 +70,8 @@ a time, only when idle**, with the idle check and the restart in the same comman on the Proxmox VM (`tomni-proxmox-113`), which turns the label ceilings into 3 heavy + 2 light. - **Fewer listeners on purpose.** Four OmniRoute units were disabled on 2026-08-29 — with only `ci.yml` `Build` and the nightlies using the box, 8 listeners were idle and each extra one is a - potential 14 GB tenant. The janitor ceiling is 4 (`MAX_ACTIVE_RUNNERS=4` in cron). + potential 14 GB tenant. The janitor ceiling is 6 (`MAX_ACTIVE_RUNNERS=6` in cron): it counts + every `Runner.Listener` on the box, and OmniHeuris + OmniMind add two to our four. - **Never clean `/tmp` or `_work` by hand while any runner is busy.** A check-then-delete with a gap between the two is how a live Build job lost its `_work` on 2026-08-27. The janitor does the check and the removal in one step; From 9ec4d39a741c51be3f0a2607f3d5a871b6b2334a Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sat, 29 Aug 2026 15:18:33 -0300 Subject: [PATCH 22/91] fix(ci): webpack for docker-publish even on omni-build (#12050) Turbopack had 31 GB on omniroute-113-6 and still panicked (TurbopackInternalError: there must be a path to a root, run 33253576569). The same tree's arm64 webpack build on hosted ARM succeeded. Dockerfile already documents webpack as the Docker escape hatch. Keep amd64 on the one omni-build slot (#12048). --- .github/workflows/docker-publish.yml | 32 ++++++++++--------- ...11976-docker-publish-webpack-omni-build.md | 1 + docs/ops/RUNNER_BOX.md | 15 +++++++-- 3 files changed, 30 insertions(+), 18 deletions(-) create mode 100644 changelog.d/maintenance/11976-docker-publish-webpack-omni-build.md diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index fc288a9cb739..6c32c12f5c52 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -126,16 +126,20 @@ jobs: name: Build Docker (${{ matrix.platform }}) needs: prepare if: needs.prepare.outputs.skip != 'true' - # amd64: the .113 omni-build pool (31 GB / 32 cores, two listeners). A - # next-build peaks at 14–16 GB; hosted ubuntu-24.04 is ~7 GB and dies - # ResourceExhausted (#11976). Falls back to hosted + webpack when - # USE_VPS_RUNNER is off. arm64: no ARM box — stay on GitHub's ubuntu-24.04-arm - # with webpack (Turbopack native RSS does not fit 7 GB). + # amd64: the .113 omni-build pool (31 GB / 32 cores, ONE listener since + # #12048). Hosted ubuntu-24.04 is ~7 GB and dies ResourceExhausted (#11976). + # Falls back to hosted when USE_VPS_RUNNER is off. arm64: no ARM box — stay + # on GitHub's ubuntu-24.04-arm. + # Webpack on BOTH arches: Turbopack on omniroute-113-6 hit + # TurbopackInternalError "there must be a path to a root" after 26 min + # (run 33253576569). The same tree's arm64 webpack build on hosted ARM + # succeeded (run 33264823398). Dockerfile already documents webpack as the + # Docker escape hatch (OMNIROUTE_USE_TURBOPACK=0). runs-on: ${{ matrix.arch == 'amd64' && (vars.USE_VPS_RUNNER == 'true' && fromJSON('["self-hosted","omni-build"]') || 'ubuntu-24.04') || 'ubuntu-24.04-arm' }} - # Share the 2-slot omni-build ceiling with ci.yml `Build` / npm-publish. - # Same group as main's Build so a :next publish waits beside the artefact - # instead of becoming the third heavy and getting kernel-OOM'd (2026-08-28). - # arm64 is hosted — its own group, cancelled by the workflow-level concurrency. + # Share the 1-slot omni-build ceiling (#12048) with ci.yml `Build` / + # npm-publish. Same group as main's Build so a :next publish waits beside + # the artefact instead of sitting next to it. arm64 is hosted — its own + # group, cancelled by the workflow-level concurrency. concurrency: group: ${{ matrix.arch == 'amd64' && 'heavy-build-main' || format('docker-publish-arm-{0}', github.ref) }} cancel-in-progress: ${{ matrix.arch != 'amd64' }} @@ -153,8 +157,6 @@ jobs: env: IMAGE_NAME: diegosouzapw/omniroute GHCR_IMAGE_NAME: ghcr.io/diegosouzapw/omniroute - # Turbopack on the 31 GB box; webpack on any hosted 7 GB fallback / ARM. - USE_TURBOPACK: ${{ (matrix.arch == 'amd64' && vars.USE_VPS_RUNNER == 'true') && '1' || '0' }} steps: - name: Checkout uses: actions/checkout@v7 @@ -190,7 +192,7 @@ jobs: target: runner-base platforms: ${{ matrix.platform }} build-args: | - OMNIROUTE_USE_TURBOPACK=${{ env.USE_TURBOPACK }} + OMNIROUTE_USE_TURBOPACK=0 outputs: type=image,push-by-digest=true,name-canonical=true,push=true tags: | ${{ env.IMAGE_NAME }} @@ -209,7 +211,7 @@ jobs: target: runner-web platforms: ${{ matrix.platform }} build-args: | - OMNIROUTE_USE_TURBOPACK=${{ env.USE_TURBOPACK }} + OMNIROUTE_USE_TURBOPACK=0 outputs: type=image,push-by-digest=true,name-canonical=true,push=true tags: | ${{ env.IMAGE_NAME }} @@ -236,7 +238,7 @@ jobs: target: runner-base platforms: ${{ matrix.platform }} build-args: | - OMNIROUTE_USE_TURBOPACK=${{ env.USE_TURBOPACK }} + OMNIROUTE_USE_TURBOPACK=0 outputs: type=image,push-by-digest=true,name-canonical=true,push=true tags: | ${{ env.IMAGE_NAME }} @@ -263,7 +265,7 @@ jobs: target: runner-web platforms: ${{ matrix.platform }} build-args: | - OMNIROUTE_USE_TURBOPACK=${{ env.USE_TURBOPACK }} + OMNIROUTE_USE_TURBOPACK=0 outputs: type=image,push-by-digest=true,name-canonical=true,push=true tags: | ${{ env.IMAGE_NAME }} diff --git a/changelog.d/maintenance/11976-docker-publish-webpack-omni-build.md b/changelog.d/maintenance/11976-docker-publish-webpack-omni-build.md new file mode 100644 index 000000000000..077671b64c6a --- /dev/null +++ b/changelog.d/maintenance/11976-docker-publish-webpack-omni-build.md @@ -0,0 +1 @@ +- `docker-publish.yml` builds with webpack (`OMNIROUTE_USE_TURBOPACK=0`) on **both** arches, including amd64 on the `.113` `omni-build` pool. Turbopack had the RAM (31 GB, `omniroute-113-6`) and still panicked (`TurbopackInternalError: there must be a path to a root`, run 33253576569, 26 min in). The same tree's arm64 webpack build on hosted `ubuntu-24.04-arm` succeeded (run 33264823398). Dockerfile already documents webpack as the Docker escape hatch (#11976). diff --git a/docs/ops/RUNNER_BOX.md b/docs/ops/RUNNER_BOX.md index 96b638a1ebbc..58e17414ed04 100644 --- a/docs/ops/RUNNER_BOX.md +++ b/docs/ops/RUNNER_BOX.md @@ -50,10 +50,19 @@ a time, only when idle**, with the idle check and the restart in the same comman ## Operating rules - **Heavy-build ceiling: ONE at a time — enforced by label (since 2026-08-29).** Every job - that runs a `next build` (`ci.yml` `build`, `npm-publish.yml` `publish`, both - `nightly-release-green` validations) targets `[self-hosted, omni-build]`, and only + that runs a full `next build` targets `[self-hosted, omni-build]`, and only **`omniroute-113-5`** carries that label (added through the runners API — no - re-registration). Two was the previous ceiling and it was wrong for 31 GB: on + re-registration): `ci.yml` `Build`, `npm-publish.yml` `publish`, both + `nightly-release-green` validations, and `docker-publish.yml` **amd64** (hosted + 7 GB ResourceExhausted this tree — #11976). The arm64 Docker leg stays on + `ubuntu-24.04-arm` (no ARM box) with webpack. Docker amd64 also uses webpack: + Turbopack on this tree panicked inside BuildKit (`TurbopackInternalError: + there must be a path to a root`, run 33253576569) even with 31 GB; the same + tree's arm64 webpack build on hosted ARM succeeded. `docker-publish` amd64 + shares the `heavy-build-main` concurrency group with `ci.yml` `Build` + (`cancel-in-progress: false`) so it queues on the one slot. Docker Engine + must be on `omniroute-113-5` (`docker info` is the first step of the publish + job). Two was the previous ceiling and it was wrong for 31 GB: on 2026-08-29 17:26 UTC two concurrent `next-build`s (15.4 GB + 17.2 GB RSS) drove the box to 5 GB free with 4 GB of swap in use and the kernel OOM-killed one of them — systemd booked the kill on the _other_ runner's unit, `runsvc.sh` SIGKILLed that listener, and From c8dc982eaa3bd1599166841c51940a049bb39ff2 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sat, 29 Aug 2026 15:27:39 -0300 Subject: [PATCH 23/91] fix(ci): drop the stale ESLint cache restore-keys fallback from ci.yml (#11600) (#11996) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes the blocking Lint job's own ci.yml cache: PR #11963 removed the stale restore-keys fallback from quality.yml but left ci.yml's two "Restore ESLint file cache" steps carrying the same prefix-match fallback that lets a cache from a different lint config report stale per-file verdicts. Byte-level parity with #11963's already-merged fix. Deliberately half of #11600 — the other half (run-eslint-json.mjs) is covered by PR #11983 from a parallel session, so the two don't collide on the same file. --- .github/workflows/ci.yml | 14 ++++++++++---- .../fixes/11600-eslint-gate-cache-suppressions.md | 5 +++++ 2 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 changelog.d/fixes/11600-eslint-gate-cache-suppressions.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c15bf1ca12f5..b30c476e4e5f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -109,8 +109,11 @@ jobs: .eslintcache .eslintcache-complexity key: eslint-${{ runner.os }}-${{ hashFiles('eslint.config.mjs', 'eslint.complexity-ratchets.config.mjs', 'config/quality/eslint-suppressions.json', 'package-lock.json') }} - restore-keys: | - eslint-${{ runner.os }}- + # No restore-keys fallback on purpose (#11600, P-II.1 of the v3.8.50 postmortem): a + # cache built under a different suppressions file / lint config / lockfile reports + # stale per-file verdicts, which is exactly how 215 pre-existing errors stayed + # invisible for a whole cycle. Exact key or a cold full lint (~13 min) — never a + # partial cache from another configuration. # Single ESLint inventory (JSON) — quality-gate reuses the artifact instead of # a second cold full-tree pass for eslintWarnings ratchet counts. - name: ESLint (JSON report) @@ -209,8 +212,11 @@ jobs: .eslintcache .eslintcache-complexity key: eslint-${{ runner.os }}-${{ hashFiles('eslint.config.mjs', 'eslint.complexity-ratchets.config.mjs', 'config/quality/eslint-suppressions.json', 'package-lock.json') }} - restore-keys: | - eslint-${{ runner.os }}- + # No restore-keys fallback on purpose (#11600, P-II.1 of the v3.8.50 postmortem): a + # cache built under a different suppressions file / lint config / lockfile reports + # stale per-file verdicts, which is exactly how 215 pre-existing errors stayed + # invisible for a whole cycle. Exact key or a cold full lint (~13 min) — never a + # partial cache from another configuration. # Coverage mergeada (coverage-summary.json) p/ o ratchet de cobertura. # continue-on-error: o artifact pode não existir se a job test-coverage foi # SKIPPED (shard flaky). Nesse caso collect-metrics pula coverage.* (ausente sem diff --git a/changelog.d/fixes/11600-eslint-gate-cache-suppressions.md b/changelog.d/fixes/11600-eslint-gate-cache-suppressions.md new file mode 100644 index 000000000000..858dfa7e9130 --- /dev/null +++ b/changelog.d/fixes/11600-eslint-gate-cache-suppressions.md @@ -0,0 +1,5 @@ +- Dropped the stale-`.eslintcache` `restore-keys` fallback from both "Restore ESLint file + cache" steps in `ci.yml`, so the blocking `Lint` job can no longer be served per-file + verdicts computed under a different lint config, suppressions file or lockfile. `quality.yml` + had already dropped it in #11963; `ci.yml` — the workflow that actually gates PRs — had not + (#11600). From c9b1c12cfdf8340adf5e8d585a66951c839cff86 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sat, 29 Aug 2026 15:27:44 -0300 Subject: [PATCH 24/91] fix(db): include local no-API-key providers in Qdrant embedding-model list (#11949) (#11995) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Local no-API-key providers (ollama-local, lm-studio, vllm, etc.) were invisible in the Qdrant embedding-model dropdown because configuredProviders required a real apiKey or OAuth. Extended the filter to also include providerAllowsOptionalApiKey(connection.provider) — the same canonical helper already used for the identical check elsewhere. TDD: 21/21 integration tests pass (was 20/21 before the fix). --- .../11949-qdrant-local-embedding-providers.md | 1 + .../settings/qdrant/embedding-models/route.ts | 8 ++- tests/integration/qdrant-routes.test.ts | 60 +++++++++++++++++++ 3 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 changelog.d/fixes/11949-qdrant-local-embedding-providers.md diff --git a/changelog.d/fixes/11949-qdrant-local-embedding-providers.md b/changelog.d/fixes/11949-qdrant-local-embedding-providers.md new file mode 100644 index 000000000000..b06387031e49 --- /dev/null +++ b/changelog.d/fixes/11949-qdrant-local-embedding-providers.md @@ -0,0 +1 @@ +- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949)) diff --git a/src/app/api/settings/qdrant/embedding-models/route.ts b/src/app/api/settings/qdrant/embedding-models/route.ts index a0cd0b344152..967ac76c9115 100644 --- a/src/app/api/settings/qdrant/embedding-models/route.ts +++ b/src/app/api/settings/qdrant/embedding-models/route.ts @@ -1,6 +1,7 @@ import { NextRequest, NextResponse } from "next/server"; import { isAuthenticated } from "@/shared/utils/apiAuth"; import { getProviderConnections } from "@/lib/db/providers"; +import { providerAllowsOptionalApiKey } from "@/shared/constants/providers"; import { getAllEmbeddingModels } from "@omniroute/open-sse/config/embeddingRegistry.ts"; import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error.ts"; @@ -28,7 +29,12 @@ export async function GET(request: NextRequest) { .filter( (connection) => (typeof connection.apiKey === "string" && connection.apiKey.trim().length > 0) || - connection.authType === "oauth" + connection.authType === "oauth" || + // Local/self-hosted providers (ollama-local, lm-studio, etc.) and other + // no-key-required providers connect with no apiKey and authType "apikey" + // (see src/app/api/providers/route.ts) — they are still "configured" the + // moment the connection is active. See issue #11949. + providerAllowsOptionalApiKey(connection.provider) ) .map((connection) => String(connection.provider || "")) .filter(Boolean) diff --git a/tests/integration/qdrant-routes.test.ts b/tests/integration/qdrant-routes.test.ts index 451074ffea1e..bc6c291adccf 100644 --- a/tests/integration/qdrant-routes.test.ts +++ b/tests/integration/qdrant-routes.test.ts @@ -43,6 +43,13 @@ const qdrantEmbeddingModelsRoute = // returns the Fetch API Request, which is structurally sufficient at runtime. const asNextRequest = (req: Request) => req as unknown as import("next/server").NextRequest; +// Mirrors EmbeddingModelOption from the embedding-models route response shape. +interface EmbeddingModelOptionLike { + value: string; + label: string; + dimensions?: number; +} + async function resetStorage() { core.resetDbInstance(); fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); @@ -409,6 +416,59 @@ test("GET /api/settings/qdrant/embedding-models — lists only configured provid assert.match(defaultModel.label, /1536d/); }); +test("GET /api/settings/qdrant/embedding-models — includes an active local no-API-key provider (#11949)", async () => { + // Local providers (e.g. ollama-local) are created through the connections + // route with authType "apikey" but no apiKey — the connect form never asks + // for one (src/shared/constants/providers/local.ts). An active connection + // like this must still surface its embedding models. + await localDb.createProviderConnection({ + provider: "ollama-local", + authType: "apikey", + name: "local-ollama", + }); + + const headers = await createManagementSessionHeaders(); + const req = new Request("http://localhost/api/settings/qdrant/embedding-models", { + method: "GET", + headers: Object.fromEntries(headers.entries()), + }); + + const res = await qdrantEmbeddingModelsRoute.GET(asNextRequest(req)); + assert.strictEqual(res.status, 200); + const body = await res.json(); + assert.ok( + body.models.some( + (model: EmbeddingModelOptionLike) => model.value === "ollama-local/embeddinggemma" + ), + "should list ollama-local embedding models for an active, key-less local connection" + ); +}); + +test("GET /api/settings/qdrant/embedding-models — still excludes a remote provider with no key", async () => { + // A remote provider that DOES require a key must stay excluded when the + // connection was created/left without one — regression guard for the fix + // above so it does not become "drop the filter entirely". + await localDb.createProviderConnection({ + provider: "openai", + authType: "apikey", + name: "embedding-test-openai-no-key", + }); + + const headers = await createManagementSessionHeaders(); + const req = new Request("http://localhost/api/settings/qdrant/embedding-models", { + method: "GET", + headers: Object.fromEntries(headers.entries()), + }); + + const res = await qdrantEmbeddingModelsRoute.GET(asNextRequest(req)); + assert.strictEqual(res.status, 200); + const body = await res.json(); + assert.ok( + body.models.every((model: EmbeddingModelOptionLike) => !model.value.startsWith("openai/")), + "should not list a remote provider's models when its active connection has no API key" + ); +}); + test("GET /api/settings/qdrant/embedding-models — 401 without auth", async () => { await setRequireLogin(true); const req = makeUnauthRequest("GET", "http://localhost/api/settings/qdrant/embedding-models"); From d32c76f85aa159b2a9dee8e470019c8da77a4e44 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sat, 29 Aug 2026 15:27:49 -0300 Subject: [PATCH 25/91] fix(config): correct Hermes-4-405B display label from 7B to 405B (#11861) (#11993) Fixes a copy-paste label typo (Hermes-4-405B mislabeled "7B") in both the registry and the free-model catalog data, spotted in the #11861 comment thread. TDD: 3/3 tests, generic parameter-size consistency check + exact regression guard. --- .../fixes/11861-nous-hermes-405b-label.md | 1 + open-sse/config/freeModelCatalog.data.ts | 2 +- .../providers/registry/nous-research/index.ts | 2 +- .../nous-research-model-labels-11861.test.ts | 67 +++++++++++++++++++ 4 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 changelog.d/fixes/11861-nous-hermes-405b-label.md create mode 100644 tests/unit/nous-research-model-labels-11861.test.ts diff --git a/changelog.d/fixes/11861-nous-hermes-405b-label.md b/changelog.d/fixes/11861-nous-hermes-405b-label.md new file mode 100644 index 000000000000..c34eb4012090 --- /dev/null +++ b/changelog.d/fixes/11861-nous-hermes-405b-label.md @@ -0,0 +1 @@ +- **fix(config):** Nous Research's `Hermes-4-405B` model now displays as "Hermes 4 405B (Nous Research)" in both the provider registry and the free-model catalog, instead of the mislabelled "Hermes 4 7B" ([#11861](https://github.com/diegosouzapw/OmniRoute/issues/11861)) — thanks @Karan825 diff --git a/open-sse/config/freeModelCatalog.data.ts b/open-sse/config/freeModelCatalog.data.ts index 1403e27f06ab..163b82bc8460 100644 --- a/open-sse/config/freeModelCatalog.data.ts +++ b/open-sse/config/freeModelCatalog.data.ts @@ -268,7 +268,7 @@ export const FREE_MODEL_BUDGETS: FreeModelBudget[] = [ { provider: "muse-spark-web", modelId: "muse-spark-contemplating", displayName: "Muse Spark Contemplating", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "muse-spark-web", tos: "avoid" }, { provider: "nebius", modelId: "meta-llama/Llama-3.3-70B-Instruct", displayName: "Llama 3.3 70B Instruct", monthlyTokens: 0, creditTokens: 1000000, freeType: "one-time-initial", poolKey: "nebius", tos: "caution" }, { provider: "nlpcloud", modelId: "llama-3-8b-instruct", displayName: "Llama 3 8B", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-monthly", poolKey: "nlpcloud", tos: "avoid" }, - { provider: "nous-research", modelId: "Hermes-4-405B", displayName: "Hermes 4 7B (Nous Research)", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-credit", poolKey: "nous-research", tos: "ambiguous" }, + { provider: "nous-research", modelId: "Hermes-4-405B", displayName: "Hermes 4 405B (Nous Research)", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-credit", poolKey: "nous-research", tos: "ambiguous" }, { provider: "nous-research", modelId: "Hermes-4-70B", displayName: "Hermes 4 70B (Nous Research)", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-credit", poolKey: "nous-research", tos: "ambiguous" }, { provider: "novita", modelId: "ai-ai/llama-3.1-8b-instruct", displayName: "Llama 3.1 8B", monthlyTokens: 0, creditTokens: 500000, freeType: "one-time-initial", poolKey: "novita", tos: "caution" }, { provider: "nscale", modelId: "moonshotai/Kimi-K2.5", displayName: "moonshotai/Kimi-K2.5", monthlyTokens: 0, creditTokens: 5000000, freeType: "one-time-initial", poolKey: "nscale", tos: "caution" }, diff --git a/open-sse/config/providers/registry/nous-research/index.ts b/open-sse/config/providers/registry/nous-research/index.ts index aaa61ac5ace4..e118dbcae7e8 100644 --- a/open-sse/config/providers/registry/nous-research/index.ts +++ b/open-sse/config/providers/registry/nous-research/index.ts @@ -9,7 +9,7 @@ export const nous_researchProvider: RegistryEntry = { authType: "apikey", authHeader: "bearer", models: [ - { id: "Hermes-4-405B", name: "Hermes 4 7B (Nous Research)" }, + { id: "Hermes-4-405B", name: "Hermes 4 405B (Nous Research)" }, { id: "Hermes-4-70B", name: "Hermes 4 70B (Nous Research)" }, ], }; diff --git a/tests/unit/nous-research-model-labels-11861.test.ts b/tests/unit/nous-research-model-labels-11861.test.ts new file mode 100644 index 000000000000..61751682dbd8 --- /dev/null +++ b/tests/unit/nous-research-model-labels-11861.test.ts @@ -0,0 +1,67 @@ +/** + * Regression test for #11861 — Nous Research registry mislabels Hermes-4-405B as "7B" + * + * `open-sse/config/providers/registry/nous-research/index.ts` displayed the 405B-parameter + * model with the copy-pasted name "Hermes 4 7B (Nous Research)". The `id` and `name` fields + * must agree on the model's parameter size for every model this provider ships. + */ +import test from "node:test"; +import assert from "node:assert/strict"; + +import { REGISTRY as providerRegistry } from "../../open-sse/config/providerRegistry.ts"; +import { FREE_MODEL_BUDGETS } from "../../open-sse/config/freeModelCatalog.ts"; + +/** Extracts a parameter-size token like "405B" or "70B" from a string, if present. */ +function extractParamSize(value: string): string | null { + const match = value.match(/(\d+(?:\.\d+)?[BM])\b/i); + return match ? match[1].toUpperCase() : null; +} + +test("nous-research model display names match their id's parameter size", () => { + const entry = providerRegistry["nous-research"]; + assert.ok(entry, "providerRegistry['nous-research'] must be defined"); + assert.ok(entry.models.length > 0, "nous-research must ship at least one model"); + + for (const model of entry.models) { + const idSize = extractParamSize(model.id); + assert.ok(idSize, `nous-research model id "${model.id}" must encode a parameter size`); + + const nameSize = extractParamSize(model.name); + assert.ok( + nameSize, + `nous-research model "${model.id}" display name "${model.name}" must encode a parameter size` + ); + + assert.equal( + nameSize, + idSize, + `nous-research model "${model.id}" display name "${model.name}" advertises ${nameSize} ` + + `but the id says ${idSize}` + ); + } +}); + +test("nous-research Hermes-4-405B is labelled 405B (Nous Research)", () => { + const entry = providerRegistry["nous-research"]; + const model = entry.models.find((m) => m.id === "Hermes-4-405B"); + assert.ok(model, "Hermes-4-405B must be registered"); + assert.equal(model.name, "Hermes 4 405B (Nous Research)"); +}); + +test("nous-research free-model catalog display names match their modelId's parameter size", () => { + const rows = FREE_MODEL_BUDGETS.filter((model) => model.provider === "nous-research"); + assert.ok(rows.length > 0, "FREE_MODEL_BUDGETS must list nous-research models"); + + for (const row of rows) { + const idSize = extractParamSize(row.modelId); + assert.ok(idSize, `nous-research free-catalog modelId "${row.modelId}" must encode a size`); + + const nameSize = extractParamSize(row.displayName); + assert.equal( + nameSize, + idSize, + `nous-research free-catalog "${row.modelId}" displayName "${row.displayName}" advertises ` + + `${nameSize} but the modelId says ${idSize}` + ); + } +}); From 065d9984079a343a8a0f77669e6f06bb91393bcc Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sat, 29 Aug 2026 15:27:53 -0300 Subject: [PATCH 26/91] fix(cli): update flow now says whether the running process needs a restart (#11885) (#12005) Fixes three defects in the "update doesn't restart the running process" bug class: CLI update guidance now detects a live server and tells the operator to restart instead of implying the update is already live; the dashboard's Update button tries OmniRoute's own PID-file supervisor before falling back to pm2 instead of hardcoding pm2 and silently skipping; getLatestVersionFromNpmCli now uses --prefer-online (same fix pattern as #4376). TDD throughout, 63/63 targeted regression tests pass. --- bin/cli/commands/update.mjs | 40 +++++- bin/cli/io.mjs | 4 + .../11885-update-restart-live-process.md | 1 + src/app/api/system/version/route.ts | 48 +++---- src/lib/system/processManagerRestart.ts | 123 ++++++++++++++++++ src/lib/system/versionCheck.ts | 20 ++- .../cli-update-restart-guidance-11885.test.ts | 75 +++++++++++ ...stem-process-manager-restart-11885.test.ts | 80 ++++++++++++ ...-version-check-prefer-online-11885.test.ts | 34 +++++ 9 files changed, 388 insertions(+), 37 deletions(-) create mode 100644 changelog.d/fixes/11885-update-restart-live-process.md create mode 100644 src/lib/system/processManagerRestart.ts create mode 100644 tests/unit/cli-update-restart-guidance-11885.test.ts create mode 100644 tests/unit/system-process-manager-restart-11885.test.ts create mode 100644 tests/unit/system-version-check-prefer-online-11885.test.ts diff --git a/bin/cli/commands/update.mjs b/bin/cli/commands/update.mjs index c1722f4bcbb2..9b9848ba6eea 100644 --- a/bin/cli/commands/update.mjs +++ b/bin/cli/commands/update.mjs @@ -1,4 +1,4 @@ -import { printHeading, printInfo, printSuccess, printError } from "../io.mjs"; +import { printHeading, printInfo, printSuccess, printError, printWarning } from "../io.mjs"; import { homedir } from "node:os"; import path from "node:path"; import { fileURLToPath } from "node:url"; @@ -6,6 +6,7 @@ import { execFile } from "node:child_process"; import { promisify } from "node:util"; import { t } from "../i18n.mjs"; import { npmBin, npmExecOptions } from "../npm-exec.mjs"; +import { readPidFile, isPidRunning } from "../utils/pid.mjs"; const execFileAsync = promisify(execFile); @@ -79,6 +80,39 @@ export async function createBackup() { } } +// #11885: `--apply` installs the new files (npm install -g) and re-reads +// package.json from disk to confirm it, but a long-lived server process keeps +// serving whatever it loaded at its last start — Node caches a `require()`d +// package.json per resolved path for the life of the process. A later +// `omniroute update` then correctly reports "already up to date" (the files +// ARE current) while the running server is still stale, matching the reported +// symptom. `--apply` never restarted anything and its success message ("Run +// `omniroute --version` to verify.") implied the update was already live. +// +// `restart.mjs`'s `runRestartCommand()` stops then re-spawns the server in the +// foreground (via `serve.mjs::runServe`), which can block the calling terminal +// and is a materially bigger behavior change than this fix warrants to invoke +// unconditionally and unattended from `--apply`. Instead, detect whether a +// CLI-managed server is currently running (the same PID file `stop.mjs`/ +// `restart.mjs` already trust) and print an explicit, prominent instruction — +// honest about what did and didn't happen — rather than silently assuming. +export async function isServerProcessRunning(deps = { readPidFile, isPidRunning }) { + const pid = deps.readPidFile("server"); + return Boolean(pid && deps.isPidRunning(pid)); +} + +export async function printPostApplyGuidance(latest, deps = { readPidFile, isPidRunning }) { + const running = await isServerProcessRunning(deps); + if (running) { + printWarning(`Files updated to ${latest}, but the running server is still on the old version.`); + printInfo(" Run `omniroute restart` now to apply this update."); + } else { + printInfo(`No running OmniRoute server was detected via the CLI's PID file.`); + printInfo(` Start it with \`omniroute serve\` (or restart your existing process) to run ${latest}.`); + } + printInfo("`omniroute --version` will keep reporting the old version until the process restarts."); +} + export function registerUpdate(program) { program .command("update") @@ -210,8 +244,8 @@ export async function runUpdateCommand(opts = {}) { console.log(" or reorder PATH so the global bin comes first."); return 1; } - printSuccess(`Updated to version ${latest}`); - printInfo("Run `omniroute --version` to verify."); + printSuccess(`Installed omniroute@${latest} to disk.`); + await printPostApplyGuidance(latest); return 0; } catch (err) { printError(`Update failed: ${err.message}`); diff --git a/bin/cli/io.mjs b/bin/cli/io.mjs index b72f7c43106d..e87758f624d5 100644 --- a/bin/cli/io.mjs +++ b/bin/cli/io.mjs @@ -81,3 +81,7 @@ export function printInfo(message) { export function printError(message) { console.log(`\x1b[31m✖ ${message}\x1b[0m`); } + +export function printWarning(message) { + console.log(`\x1b[33m⚠ ${message}\x1b[0m`); +} diff --git a/changelog.d/fixes/11885-update-restart-live-process.md b/changelog.d/fixes/11885-update-restart-live-process.md new file mode 100644 index 000000000000..972c346204e6 --- /dev/null +++ b/changelog.d/fixes/11885-update-restart-live-process.md @@ -0,0 +1 @@ +- **fix(cli):** `omniroute update --apply` now tells you explicitly whether a running server was detected and, if so, that you must run `omniroute restart` to apply the update — it never restarted anything and previously implied the update was already live once files were installed. The dashboard's npm-mode Update flow (`/api/system/version`) now tries OmniRoute's own PID-file-managed supervisor before falling back to pm2, and reports an honest "restart required" step instead of a silent pm2-only "skipped" that read like a completed update. The server-side latest-version lookup backing the dashboard's update banner also gained `--prefer-online`, closing the same stale-npm-cache class already fixed in the CLI's own copy for #4376 ([#11885](https://github.com/diegosouzapw/OmniRoute/issues/11885)). diff --git a/src/app/api/system/version/route.ts b/src/app/api/system/version/route.ts index 6e2f1e2f36f3..f3ec9c26d4d2 100644 --- a/src/app/api/system/version/route.ts +++ b/src/app/api/system/version/route.ts @@ -24,6 +24,7 @@ import { resolveLatestVersionCached, } from "@/lib/system/versionCheck"; import { resolveGlobalOmniroutePath } from "@/lib/system/globalPackagePath"; +import { restartRunningServer } from "@/lib/system/processManagerRestart"; // #5542 — On Windows npm is `npm.cmd`; Node ≥24 refuses to execFile a `.cmd` without // a shell (nodejs/node#52554 → "spawn npm ENOENT"). buildNpmExecOptions enables the // shell on win32 only; SERVICE_VERSION_PATTERN keeps the shell-joined version safe. @@ -41,6 +42,20 @@ function getCurrentVersion(): string { } } +/** + * Shared restart step for both npm-mode update flows (source-checkout and global-install + * below). #11885: this used to hardcode `pm2 restart omniroute` in each branch separately + * and silently report "skipped" — reading like a completed update — whenever pm2 wasn't + * the process manager. `restartRunningServer()` tries OmniRoute's own PID-file-managed + * supervisor first, then pm2, and this wrapper turns its honest "restart-required" outcome + * into an SSE step the dashboard renders as a warning instead of a false "done". + */ +async function sendRestartStep(send: (data: Record) => void): Promise { + send({ step: "restart", status: "running", message: "Restarting service..." }); + const outcome = await restartRunningServer(); + send({ step: "restart", status: outcome.status, message: outcome.message }); +} + async function getNews() { try { const res = await fetch(NEWS_JSON_URL, { next: { revalidate: 3600 } }); @@ -259,20 +274,7 @@ export async function POST(req: NextRequest) { ); send({ step: "rebuild", status: "done", message: "Build complete" }); - send({ step: "restart", status: "running", message: "Restarting service..." }); - try { - await execFileAsync("pm2", ["restart", "omniroute", "--update-env"], { - timeout: 30_000, - cwd: PROJECT_ROOT, - }); - send({ step: "restart", status: "done", message: "Service restarted" }); - } catch { - send({ - step: "restart", - status: "skipped", - message: "PM2 not available — manual restart needed", - }); - } + await sendRestartStep(send); send({ step: "complete", @@ -341,22 +343,8 @@ export async function POST(req: NextRequest) { ); send({ step: "rebuild", status: "done", message: "Native modules rebuilt" }); - // Step 3: Restart PM2 - send({ step: "restart", status: "running", message: "Restarting service via PM2..." }); - try { - await execFileAsync("pm2", ["restart", "omniroute", "--update-env"], { - timeout: 30000, - cwd: PROJECT_ROOT, - }); - send({ step: "restart", status: "done", message: "Service restarted" }); - } catch { - // PM2 may not be available (Docker/manual setups) - send({ - step: "restart", - status: "skipped", - message: "PM2 not available — manual restart needed", - }); - } + // Step 3: Restart + await sendRestartStep(send); clearLatestVersionCache(); send({ diff --git a/src/lib/system/processManagerRestart.ts b/src/lib/system/processManagerRestart.ts new file mode 100644 index 000000000000..caa62fa5411d --- /dev/null +++ b/src/lib/system/processManagerRestart.ts @@ -0,0 +1,123 @@ +/** + * Restart mechanism detection for the npm-mode dashboard "Update" flow. + * + * #11885: `src/app/api/system/version/route.ts` hardcoded `pm2 restart omniroute` as the + * ONLY restart mechanism, at two near-identical branches. OmniRoute ships its OWN + * supervisor (`bin/cli/runtime/processSupervisor.mjs`, started by `omniroute serve` / + * `omniroute serve --daemon`) with PID-file management (`bin/cli/utils/pid.mjs`) as an + * alternative to pm2 — so on any install that isn't pm2-managed, the restart step + * silently degraded to a "skipped" status while the install step still reported "done", + * which reads like a completed live update even though nothing restarted. + * + * This module tries OmniRoute's own PID-file-managed supervisor first, then falls back + * to pm2, and returns an honest "restart-required" outcome instead of a silent no-op + * when neither is detected. + */ +import path from "node:path"; +import fs from "node:fs"; +import { execFile } from "node:child_process"; +import { promisify } from "node:util"; +import { resolveDataDir } from "@/lib/dataPaths"; + +const execFileAsync = promisify(execFile); + +export type RestartMethod = "own-supervisor" | "pm2" | "none"; +export type RestartStatus = "done" | "restart-required"; + +export interface RestartOutcome { + method: RestartMethod; + status: RestartStatus; + message: string; +} + +type PidService = "server" | "supervisor"; + +export interface RestartManagerDeps { + readPidFile?: (service: PidService) => number | null; + isPidRunning?: (pid: number) => boolean; + killProcess?: (pid: number) => void; + execPm2?: (args: string[]) => Promise; +} + +function defaultReadPidFile(service: PidService): number | null { + try { + const file = path.join(resolveDataDir(), service, ".pid"); + if (!fs.existsSync(file)) return null; + const raw = fs.readFileSync(file, "utf8").trim(); + const pid = parseInt(raw, 10); + return Number.isFinite(pid) ? pid : null; + } catch { + return null; + } +} + +function defaultIsPidRunning(pid: number): boolean { + if (!pid) return false; + try { + process.kill(pid, 0); + return true; + } catch { + return false; + } +} + +function defaultKillProcess(pid: number): void { + process.kill(pid, "SIGTERM"); +} + +function defaultExecPm2(args: string[]): Promise { + return execFileAsync("pm2", args, { timeout: 30_000 }); +} + +/** + * Attempt to restart the running OmniRoute server. + * + * Order: + * 1. OmniRoute's own PID-file-managed supervisor — SIGTERM the supervised "server" + * child ONLY, never the supervisor process itself. The supervisor's exit handler + * (`ServerSupervisor.handleExit`) treats an unexpected child exit as a crash and + * respawns it, which IS the restart we want. SIGTERM to the SUPERVISOR instead + * sets its `isShuttingDown` flag and intentionally skips the respawn (see + * `bin/cli/commands/stop.mjs`) — that would stop the service, not restart it. + * 2. pm2, for installs that use it instead of the CLI's own supervisor. + * 3. Neither detected — return an honest "restart-required" outcome rather than a + * silently-skipped step that reads like nothing was wrong. + */ +export async function restartRunningServer( + deps: RestartManagerDeps = {} +): Promise { + const readPidFile = deps.readPidFile ?? defaultReadPidFile; + const isPidRunning = deps.isPidRunning ?? defaultIsPidRunning; + const killProcess = deps.killProcess ?? defaultKillProcess; + const execPm2 = deps.execPm2 ?? defaultExecPm2; + + const supervisorPid = readPidFile("supervisor"); + const serverPid = readPidFile("server"); + const supervisorAlive = Boolean(supervisorPid && isPidRunning(supervisorPid)); + const serverAlive = Boolean(serverPid && isPidRunning(serverPid)); + + if (supervisorAlive && serverAlive && serverPid) { + try { + killProcess(serverPid); + return { + method: "own-supervisor", + status: "done", + message: "Restarted via the OmniRoute supervisor (server process recycled).", + }; + } catch { + // Fall through to pm2 / restart-required below. + } + } + + try { + await execPm2(["restart", "omniroute", "--update-env"]); + return { method: "pm2", status: "done", message: "Service restarted via pm2." }; + } catch { + return { + method: "none", + status: "restart-required", + message: + "Files were updated, but no supported process manager (OmniRoute's own supervisor or pm2) was detected — restart the server manually to apply the update.", + }; + } +} diff --git a/src/lib/system/versionCheck.ts b/src/lib/system/versionCheck.ts index 3e0dfd83d94b..e7dea38ec887 100644 --- a/src/lib/system/versionCheck.ts +++ b/src/lib/system/versionCheck.ts @@ -53,14 +53,26 @@ let latestVersionCacheGeneration = 0; // for back-compat with existing server-side importers. export { normalizeVersion, isNewer } from "./versionCompare"; -/** Latest published version via the `npm` CLI (fast when npm is on PATH, e.g. source installs). */ -export async function getLatestVersionFromNpmCli(): Promise { +/** + * Latest published version via the `npm` CLI (fast when npm is on PATH, e.g. source installs). + * + * `execFn` is injectable for tests (same pattern as the CLI's own + * `bin/cli/commands/update.mjs::getLatestVersion()`). + */ +export async function getLatestVersionFromNpmCli( + execFn: typeof execFileAsync = execFileAsync +): Promise { try { // #5542 — win32 npm is npm.cmd; execFile without a shell throws "spawn npm ENOENT" // on Node ≥24 (nodejs/node#52554). buildNpmExecOptions enables the shell on win32. - const { stdout } = await execFileAsync( + // #11885 — `--prefer-online` forces npm to revalidate its HTTP cache against the + // registry. Without it `npm info` can return a stale cached version, the same known + // bug class already fixed in the CLI's own copy for #4376 (see that fix's comment in + // bin/cli/commands/update.mjs::getLatestVersion()) but never mirrored here — this is + // the function backing the dashboard's "Update Available" banner. + const { stdout } = await execFn( "npm", - ["info", "omniroute", "version", "--json"], + ["info", "omniroute", "version", "--json", "--prefer-online"], buildNpmExecOptions(process.platform, { timeoutMs: LOOKUP_TIMEOUT_MS }) ); const parsed = JSON.parse(String(stdout).trim()); diff --git a/tests/unit/cli-update-restart-guidance-11885.test.ts b/tests/unit/cli-update-restart-guidance-11885.test.ts new file mode 100644 index 000000000000..7db9902fc3fb --- /dev/null +++ b/tests/unit/cli-update-restart-guidance-11885.test.ts @@ -0,0 +1,75 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +const update = await import("../../bin/cli/commands/update.mjs"); + +// #11885: a user's dashboard reported an old version, running `omniroute update` +// said "already up to date" (the on-disk package.json HAD been updated by an +// earlier `--apply` run), but the long-lived server process kept serving the old +// version because `--apply` never restarts anything — it only re-reads the +// package.json on disk and prints "Run `omniroute --version` to verify", which +// implies the running install is now current when it is not. + +test("isServerProcessRunning: true when the CLI-managed server pid is alive (#11885)", async () => { + const running = await update.isServerProcessRunning({ + readPidFile: (service: string) => (service === "server" ? 12345 : null), + isPidRunning: (pid: number) => pid === 12345, + }); + assert.equal(running, true); +}); + +test("isServerProcessRunning: false when there is no pid file (#11885)", async () => { + const running = await update.isServerProcessRunning({ + readPidFile: () => null, + isPidRunning: () => true, + }); + assert.equal(running, false); +}); + +test("isServerProcessRunning: false when the pid file is stale (#11885)", async () => { + const running = await update.isServerProcessRunning({ + readPidFile: () => 999, + isPidRunning: () => false, + }); + assert.equal(running, false); +}); + +function captureLogs(fn: () => Promise) { + const logs: string[] = []; + const origLog = console.log; + console.log = (...args: unknown[]) => { + logs.push(args.map(String).join(" ")); + }; + return fn() + .then(() => logs) + .finally(() => { + console.log = origLog; + }); +} + +test("printPostApplyGuidance tells the user to run `omniroute restart` when a server is running, and stops implying the update is already live (#11885)", async () => { + const logs = await captureLogs(() => + update.printPostApplyGuidance("3.9.0", { + readPidFile: () => 42, + isPidRunning: () => true, + }) + ); + const joined = logs.join("\n"); + assert.match(joined, /omniroute restart/); + assert.doesNotMatch( + joined, + /^.*Updated to version 3\.9\.0.*$/m, + "must not claim the running process is already updated" + ); +}); + +test("printPostApplyGuidance tells the user to start the server when none is detected (#11885)", async () => { + const logs = await captureLogs(() => + update.printPostApplyGuidance("3.9.0", { + readPidFile: () => null, + isPidRunning: () => false, + }) + ); + const joined = logs.join("\n"); + assert.match(joined, /omniroute serve/); +}); diff --git a/tests/unit/system-process-manager-restart-11885.test.ts b/tests/unit/system-process-manager-restart-11885.test.ts new file mode 100644 index 000000000000..7b1cf4c47f01 --- /dev/null +++ b/tests/unit/system-process-manager-restart-11885.test.ts @@ -0,0 +1,80 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { restartRunningServer } from "@/lib/system/processManagerRestart"; + +// #11885: the dashboard's npm-mode Update flow (src/app/api/system/version/route.ts) +// hardcoded `pm2 restart omniroute` as the ONLY restart mechanism, at two +// near-identical branches. When pm2 isn't the process manager (OmniRoute's own +// `omniroute serve --daemon` supervisor, or plain `npm run start`) it silently +// degraded to a "skipped" status while the install step still reported "done" — +// reading like a completed live update when the running server never restarted. + +test("restartRunningServer: uses OmniRoute's own supervisor when both the supervisor and server pids are alive", async () => { + const killed = []; + const pm2Calls = []; + const outcome = await restartRunningServer({ + readPidFile: (service) => (service === "supervisor" ? 111 : service === "server" ? 222 : null), + isPidRunning: (pid) => pid === 111 || pid === 222, + killProcess: (pid) => killed.push(pid), + execPm2: async (args) => { + pm2Calls.push(args); + }, + }); + + assert.equal(outcome.method, "own-supervisor"); + assert.equal(outcome.status, "done"); + // Must SIGTERM the supervised SERVER child, never the supervisor itself — killing + // the supervisor sets its isShuttingDown flag and intentionally skips the respawn + // (see bin/cli/commands/stop.mjs), which would NOT produce a restart. + assert.deepEqual(killed, [222]); + assert.equal(pm2Calls.length, 0, "must not fall back to pm2 when the own supervisor path succeeds"); +}); + +test("restartRunningServer: falls back to pm2 when no OmniRoute supervisor is detected", async () => { + const pm2Calls = []; + const outcome = await restartRunningServer({ + readPidFile: () => null, + isPidRunning: () => false, + killProcess: () => { + throw new Error("must not be called"); + }, + execPm2: async (args) => { + pm2Calls.push(args); + }, + }); + + assert.equal(outcome.method, "pm2"); + assert.equal(outcome.status, "done"); + assert.equal(pm2Calls.length, 1); + assert.deepEqual(pm2Calls[0], ["restart", "omniroute", "--update-env"]); +}); + +test("restartRunningServer: falls back to pm2 when the supervisor pid is stale", async () => { + const pm2Calls = []; + const outcome = await restartRunningServer({ + readPidFile: (service) => (service === "supervisor" ? 999 : null), + isPidRunning: () => false, + killProcess: () => { + throw new Error("must not be called"); + }, + execPm2: async (args) => { + pm2Calls.push(args); + }, + }); + + assert.equal(outcome.method, "pm2"); +}); + +test("restartRunningServer: returns an honest restart-required outcome when neither is available (no silent 'skipped')", async () => { + const outcome = await restartRunningServer({ + readPidFile: () => null, + isPidRunning: () => false, + execPm2: async () => { + throw new Error("pm2: command not found"); + }, + }); + + assert.equal(outcome.method, "none"); + assert.equal(outcome.status, "restart-required"); + assert.match(outcome.message, /restart/i); +}); diff --git a/tests/unit/system-version-check-prefer-online-11885.test.ts b/tests/unit/system-version-check-prefer-online-11885.test.ts new file mode 100644 index 000000000000..5e8d7ec35847 --- /dev/null +++ b/tests/unit/system-version-check-prefer-online-11885.test.ts @@ -0,0 +1,34 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { getLatestVersionFromNpmCli } from "@/lib/system/versionCheck"; + +// #11885: a user's dashboard showed 3.8.10 while the "3.8.49 available" banner kept +// firing, yet `omniroute update` on the box said "already up to date". Part of the +// same known bug class as #4376 (already fixed in bin/cli/commands/update.mjs's own +// getLatestVersion()): `npm info omniroute version --json` without `--prefer-online` +// can serve a stale cached "latest" from npm's local HTTP cache. The server-side copy +// used by the dashboard's update banner never got the same fix. +test("getLatestVersionFromNpmCli passes --prefer-online to bypass the stale npm cache (#11885, same class as #4376)", async () => { + let capturedArgs: string[] | null = null; + const fakeExec = async (_cmd: string, args: string[]) => { + capturedArgs = args; + return { stdout: JSON.stringify("3.8.49"), stderr: "" }; + }; + const latest = await getLatestVersionFromNpmCli(fakeExec as never); + assert.equal(latest, "3.8.49"); + assert.ok(capturedArgs, "exec must be invoked"); + assert.ok( + (capturedArgs as string[]).includes("--prefer-online"), + `expected --prefer-online in npm args, got: ${JSON.stringify(capturedArgs)}` + ); + assert.ok((capturedArgs as string[]).includes("info")); + assert.ok((capturedArgs as string[]).includes("omniroute")); + assert.ok((capturedArgs as string[]).includes("version")); +}); + +test("getLatestVersionFromNpmCli returns null when npm is unavailable", async () => { + const latest = await getLatestVersionFromNpmCli(async () => { + throw new Error("npm not found"); + }); + assert.equal(latest, null); +}); From 34e2f84c04502537c4cae636b88a09d010110b92 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sat, 29 Aug 2026 15:40:32 -0300 Subject: [PATCH 27/91] feat(api): explicit model exposure allow/deny list for /v1/models (#11481) (#11997) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds opt-in modelVisibilityAllowlist/modelVisibilityDenylist settings so an operator can curate exactly which models GET /v1/models advertises, mirrored into auto/* combo candidate pools (the same trap #6512 fixed for hidePaidModels). Default off, no behavior change for anyone who doesn't opt in. TDD: 4 new test files, 22/22 passing (16 node:test + 6 vitest) + regression sweep across virtual-auto-combo/hide-paid/hide-auto-no-think suites (21/21). Rebased onto the updated tip (a sibling #9133 landed first, same file) — kept both rebaseline annotations in file-size-baseline.json and set the value to the real measured line count after both merged. --- .../11481-model-exposure-allow-deny-list.md | 1 + config/quality/file-size-baseline.json | 3 +- docs/routing/MODEL_EXPOSURE_LIST.md | 66 +++++++++++++ .../services/autoCombo/modelExposureFilter.ts | 51 ++++++++++ open-sse/services/autoCombo/virtualFactory.ts | 6 ++ src/app/api/v1/models/catalog.ts | 13 +++ src/lib/db/settings.ts | 8 ++ src/shared/utils/modelExposureList.ts | 73 +++++++++++++++ src/shared/validation/settingsSchemas.ts | 10 ++ .../model-exposure-filter-11481.test.ts | 58 ++++++++++++ ...odel-exposure-list-settings-schema.test.ts | 63 +++++++++++++ tests/unit/model-exposure-list.test.ts | 79 ++++++++++++++++ ...models-catalog-model-exposure-list.test.ts | 93 +++++++++++++++++++ 13 files changed, 523 insertions(+), 1 deletion(-) create mode 100644 changelog.d/features/11481-model-exposure-allow-deny-list.md create mode 100644 docs/routing/MODEL_EXPOSURE_LIST.md create mode 100644 open-sse/services/autoCombo/modelExposureFilter.ts create mode 100644 src/shared/utils/modelExposureList.ts create mode 100644 tests/unit/autoCombo/model-exposure-filter-11481.test.ts create mode 100644 tests/unit/model-exposure-list-settings-schema.test.ts create mode 100644 tests/unit/model-exposure-list.test.ts create mode 100644 tests/unit/models-catalog-model-exposure-list.test.ts diff --git a/changelog.d/features/11481-model-exposure-allow-deny-list.md b/changelog.d/features/11481-model-exposure-allow-deny-list.md new file mode 100644 index 000000000000..c161ce507d33 --- /dev/null +++ b/changelog.d/features/11481-model-exposure-allow-deny-list.md @@ -0,0 +1 @@ +- feat(api): add an opt-in `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings pair to curate exactly which models `/v1/models` advertises, mirrored into every `auto/*` combo candidate pool so a denied model cannot be routed to via combo selection either (#11481) diff --git a/config/quality/file-size-baseline.json b/config/quality/file-size-baseline.json index acce0e90b8f9..a602c0a80e78 100644 --- a/config/quality/file-size-baseline.json +++ b/config/quality/file-size-baseline.json @@ -477,8 +477,9 @@ "_rebaseline_2026_08_23_tip_drift_post_batch0823": "Tip drift after the 2026-08-23 merge wave: chatBodyAdmission.ts 1009->1118 (+109, gate count incl. +1) and auth.ts 3337->3344 (+7), both grown by merges already on origin/release/v3.8.50 (verified identical on the pristine tip) — not by the codex-appserver-hardening PR that carries this bump. Owner pre-authorized baseline bumps 2026-08-22.", "_rebaseline_2026_08_24_11355_cooldown_recovery_guards": "PR #11355 own growth: test/route.ts 1215->1237, +22 (startup crash-recovery guard: clearStaleCrashCooldowns() now parses the persisted rate_limited_until deadline and skips clearing rows still genuinely in the future, instead of clearing every non-terminal cooldown unconditionally). Cohesive fix at the existing test-route dispatch chokepoint alongside the #11141 probe builder. Covered by tests/unit/startup-stale-cooldown-recovery.test.ts + tests/unit/repro-zai-cooldown-cleared-by-connection-test.test.ts.", "_rebaseline_2026_08_24_video_bridge_fu02_fu07_sampler": "PRs #11344 (FU-02 one-frame scene-aware determinism) + #11381 (FU-07 opt-in segment_aware structural sampling) own growth: videoBridgeRuntime.ts <1000->1009, +9 (sum of both boarded together in the same merge-batch). #11344 adds the deterministic one-frame midpoint fallback + policyEffective=uniform report at the existing scene_aware seam; #11381 adds the bounded local-only FFmpeg structural pre-analysis pass (scene/freeze/blur/exposure/SI-TI) and its budget-reallocation logic. Covered by tests/unit/guardrails/videoBridgeSampler.test.ts, tests/unit/guardrails/videoBridgeFu07StructuralSampling.test.ts, tests/integration/video-bridge-sampler-ffmpeg.test.ts. Owner pre-authorized rebaseline for legitimate PR growth (2026-08-19 directive).", - "open-sse/services/autoCombo/virtualFactory.ts": 1139, + "open-sse/services/autoCombo/virtualFactory.ts": 1145, "_rebaseline_2026_08_29_9133_candidates_inspector_skip_flag": "#9133 own growth: open-sse/services/autoCombo/virtualFactory.ts 1138->1139 (+1, net of extraction). Fix: prepareVirtualAutoComboInputs gained an opt-in `skip` parameter so the read-only #7819 candidate inspector (open-sse/handlers/autoComboCandidates.ts) can build the FULL, unfiltered pool and decorate a resilience-blocked candidate as reachable:false instead of filterResilienceBlockedCandidates silently dropping the row before the inspector ever sees it (routing is unaffected — it never passes `skip`). The connectionsById map-building loop was extracted to buildConnectionResilienceMap() in resilienceCandidateFilter.ts (net 0 there since Prettier still breaks the call over multiple lines) and the now-unused ConnectionResilienceView import was dropped; the sole remaining growth is the new `skip` default parameter itself, which Prettier always places on its own line once the preceding options object parameter already breaks across lines — not further reducible without splitting prepareVirtualAutoComboInputs's signature away from its own body. Covered by tests/unit/auto-combo-candidates-locked-model-visible.test.ts (TDD repro: red before the fix, green after) plus the existing tests/unit/noauth-autocombo-lockout-7623.test.ts and tests/unit/auto-combo-credentialed-model-pool.test.ts (unaffected routing-path behavior).", + "_rebaseline_2026_08_29_11481_model_exposure_list": "Feature #11481 (explicit model exposure allow/deny list for /v1/models, mirrored into auto/* combo pools) own growth on top of #9133's +1: open-sse/services/autoCombo/virtualFactory.ts 1139->1145 (measured real line count after both #9133 and #11481 merged together = one import line for filterModelExposureCandidates plus the filter-and-reassign block at the existing buildPreparedPool chokepoint, immediately after the filterPaidOnlyCandidates call it mirrors — the exact pattern #6512 already established for hidePaidModels). The actual predicate (isModelExposureAllowed, glob support via the shared globToRegex matcher) lives in the new src/shared/utils/modelExposureList.ts leaf, and the pool-filter wrapper lives in the new open-sse/services/autoCombo/modelExposureFilter.ts leaf (both well under cap) — this file only carries the minimal call-site wiring plus import, not extractable further without hiding the buildPreparedPool filter chain. Covered by tests/unit/autoCombo/model-exposure-filter-11481.test.ts (pure filter, all branches) and tests/unit/model-exposure-list.test.ts (predicate).", "_rebaseline_2026_08_28_mergebatch_v3851_qwen_retirement": "/merge-batch 2026-08-28 (v3.8.51): #11713 (Qwen Web retirement) own growth: open-sse/services/autoCombo/virtualFactory.ts 1132->1135 (+3, combining the Designer + Runtime retirement-guard filter into the single runtimeConnections predicate at the existing candidate-pool chokepoint, now excluding Qwen Web alongside Felo Web). Irreducible per-mechanism wiring, additive not a new branch. Covered by tests/unit/virtual-auto-combo.test.ts.", "_rebaseline_2026_08_28_mergebatch_v3851_chatgptweb_retirement": "/merge-batch 2026-08-28 (v3.8.51): #11754 (common ChatGPT Web retirement) own growth: open-sse/services/autoCombo/virtualFactory.ts 1135->1138 (+3, an early `available` connection filter for the retired chatgpt-web/cgpt-web ids applied to both the active and disabled-noauth connection lists, ahead of the existing Designer+Runtime runtimeConnections filter). Irreducible per-mechanism wiring, additive not a new branch. Covered by tests/unit/virtual-auto-combo.test.ts.", "src/lib/cloudflaredTunnel.ts": 1078, diff --git a/docs/routing/MODEL_EXPOSURE_LIST.md b/docs/routing/MODEL_EXPOSURE_LIST.md new file mode 100644 index 000000000000..a398630462d5 --- /dev/null +++ b/docs/routing/MODEL_EXPOSURE_LIST.md @@ -0,0 +1,66 @@ +--- +title: "Model Exposure Allow/Deny List" +version: 3.8.51 +lastUpdated: 2026-08-29 +--- + +# Model Exposure Allow/Deny List + +> Opt-in, off by default (`modelVisibilityAllowlist`/`modelVisibilityDenylist` both empty leaves +> the `/v1/models` catalog AND every `auto/*` candidate pool byte-identical). A sibling of +> `hidePaidModels`/`hideAutoCombos` (`src/lib/db/settings.ts`) for operators who want a curated +> model list for reasons that have nothing to do with cost. + +## Why this exists + +`hidePaidModels` answers "is this model free?" and `hideAutoCombos` answers "should `auto/*` +virtual ids be advertised at all?" — neither lets an operator curate an arbitrary subset of +models (e.g. presenting exactly the models a given Claude Code / OpenCode client should see, +independent of pricing). #11481 adds that as two independent, opt-in string-array settings. + +## Settings + +| Key | Type | Default | Meaning | +| ----------------------------- | ---------- | ------- | ----------------------------------------------------------------- | +| `modelVisibilityDenylist` | `string[]` | `[]` | Entries matching a candidate hide it from the catalog/candidate pool. | +| `modelVisibilityAllowlist` | `string[]` | `[]` | When non-empty, ONLY entries matching a candidate stay exposed. | + +Both accept up to 500 entries of up to 200 characters each (Zod-validated in +`src/shared/validation/settingsSchemas.ts`). An entry is either: + +- an exact catalog id — `"gpt-4o"` (bare model id) or `"openai/gpt-4o"` (provider-prefixed), or +- a glob pattern using `*`/`?` — e.g. `"openai/gpt-4*"` or `"anthropic/*"` — resolved via the + same shared `globToRegex()` matcher (`src/shared/utils/globPattern.ts`) already used by + `ModelRoutingSection`'s per-model combo mappings and `freeModels.ts::matchesOnlyPaidModels`. + +Precedence: the denylist is checked first (a denied entry is always hidden, even if it also +matches the allowlist); when the allowlist is non-empty, only entries it matches survive. + +## Two chokepoints, not one + +The lesson from #6512 (a `hidePaidModels`-only catalog filter still let `auto/*` route to a +paid model, since the combo candidate pool was built independently) applies identically here. +The matching predicate `isModelExposureAllowed()` (`src/shared/utils/modelExposureList.ts`) is +called from BOTH: + +- `src/app/api/v1/models/catalog.ts` — the `/v1/models` listing itself, at the same 5 per-source + chokepoints `shouldHidePaid()` already gates (static `PROVIDER_MODELS`, synced provider rows, + custom rows, alias-backed rows, managed-fallback rows). +- `open-sse/services/autoCombo/modelExposureFilter.ts::filterModelExposureCandidates()` — called + from `virtualFactory.ts::buildPreparedPool`, immediately after the equivalent + `filterPaidOnlyCandidates()` call, so a denied model can never be selected into an `auto/*` + candidate pool either. + +## What is NOT filtered + +Mirrors `hideAutoCombos`'s existing behaviour: a model id sent **explicitly** (not via `auto/*`, +and not discovered through the catalog listing) is never blocked at dispatch — only +advertisement/candidate-pool membership is filtered. This is independent of `hidePaidModels`; +an operator may want a curated set for reasons that have nothing to do with cost, so both +settings compose as independent AND-ed filters, same as the existing multi-flag composition in +`catalog.ts`. + +Settings export (`GET /api/settings/export-json`) includes both arrays verbatim, like any other +settings field — unlike `hidePaidModels`'s combo-step export filter, there is no re-hydration +risk here: a denied id embedded in an exported combo step is the operator's own explicit routing +choice, not something the export boundary needs to strip. diff --git a/open-sse/services/autoCombo/modelExposureFilter.ts b/open-sse/services/autoCombo/modelExposureFilter.ts new file mode 100644 index 000000000000..3ac4355818ec --- /dev/null +++ b/open-sse/services/autoCombo/modelExposureFilter.ts @@ -0,0 +1,51 @@ +/** + * #11481 — mandatory mirror of the `/v1/models` catalog's explicit model + * exposure allow/deny list into every `auto/*` combo candidate pool. + * + * `src/app/api/v1/models/catalog.ts` hides denied models (and, when an + * allowlist is set, restricts to it) from the advertised catalog, but + * `auto/*` combos build their candidate pool independently — exactly the + * trap #6512 already fixed once for `hidePaidModels` (PR #6495 hid paid + * models from `GET /v1/models`, but `auto/*` could still route to one, + * a 402/403 at request time). This applies the SAME predicate `catalog.ts` + * uses to every virtual auto-combo candidate pool. + * + * Kept as a pure, dependency-light function so the filter is unit-testable + * in isolation without seeding the DB-backed virtual factory (mirrors + * `paidModelFilter.ts`). + */ +import { + isModelExposureAllowed, + type ModelExposureListSettings, +} from "@/shared/utils/modelExposureList"; + +interface ExposureFilterCandidate { + provider: string; + model: string; +} + +function hasAnyExposureListEntries(settings: ModelExposureListSettings | null | undefined): boolean { + return ( + (Array.isArray(settings?.modelVisibilityDenylist) && + settings.modelVisibilityDenylist.length > 0) || + (Array.isArray(settings?.modelVisibilityAllowlist) && + settings.modelVisibilityAllowlist.length > 0) + ); +} + +/** + * Return the candidate pool filtered by the operator's model exposure + * allow/deny lists; otherwise return the pool unchanged (identity — the + * default, opt-in-off path when both lists are empty). If the allowlist + * empties the pool, the caller's existing graceful empty-pool path handles + * it, consistent with the opt-in intent. + */ +export function filterModelExposureCandidates( + pool: T[], + settings: ModelExposureListSettings | null | undefined +): T[] { + if (!hasAnyExposureListEntries(settings)) return pool; + return pool.filter((candidate) => + isModelExposureAllowed(candidate.provider, candidate.model, settings) + ); +} diff --git a/open-sse/services/autoCombo/virtualFactory.ts b/open-sse/services/autoCombo/virtualFactory.ts index a4d0338819a6..e980d1634711 100644 --- a/open-sse/services/autoCombo/virtualFactory.ts +++ b/open-sse/services/autoCombo/virtualFactory.ts @@ -31,6 +31,7 @@ import { buildFamilyCandidateFilter, type ModelFamily } from "./modelFamily"; import { getHiddenModelsByProvider } from "@/models"; import { getSyncedAvailableModelsByConnection, getCustomModels } from "@/lib/db/models"; import { filterPaidOnlyCandidates } from "./paidModelFilter"; +import { filterModelExposureCandidates } from "./modelExposureFilter"; import { filterSubscriptionOnlyCandidates, orderPoolByRung, @@ -736,6 +737,11 @@ export async function prepareVirtualAutoComboInputs( const paidFilteredPool = filterPaidOnlyCandidates(pool, settings.hidePaidModels === true); if (paidFilteredPool !== pool) pool = paidFilteredPool; + // #11481: mandatory mirror of the /v1/models exposure allow/deny list — + // see src/shared/utils/modelExposureList.ts for why (#6512's lesson). + const exposureFilteredPool = filterModelExposureCandidates(pool, settings); + if (exposureFilteredPool !== pool) pool = exposureFilteredPool; + // STRICT_ZERO_COST: opt-in, off by default (`settings.freeAccessPolicy !== "strict"` // leaves `pool` byte-identical, same contract as `hidePaidModels`). See // `strictZeroCostFilter.ts` for why this is stricter than `hidePaidModels` alone — diff --git a/src/app/api/v1/models/catalog.ts b/src/app/api/v1/models/catalog.ts index b8d29422829b..62644fc6b58a 100644 --- a/src/app/api/v1/models/catalog.ts +++ b/src/app/api/v1/models/catalog.ts @@ -123,6 +123,7 @@ import { import { incrementCcDiscoveryHitCount } from "@/lib/db/ccDiscoveryMetrics"; import { isUnifiedChatSourceModelSelectable } from "./catalogModelPolicy"; import { isFreeModel } from "@/shared/utils/freeModels"; +import { isModelExposureAllowed } from "@/shared/utils/modelExposureList"; import { isCodexDiscoveryModelExcluded } from "@/shared/services/codexDiscoveryPolicy"; import { buildErrorBody } from "@omniroute/open-sse/utils/error"; @@ -351,6 +352,13 @@ async function buildUnifiedModelsResponseCore( // already returned above. return true; }; + // #11481: opt-in explicit model exposure allow/deny list — same call sites + // as shouldHidePaid above (mirrored into the auto/* combo candidate pool + // via open-sse/services/autoCombo/modelExposureFilter.ts, per #6512's + // catalog-only-filter-leaks-into-combo-routing lesson). Independent of + // hidePaidModels — operator curation, not a cost signal. + const shouldHideByExposure = (providerKey: string, modelId: string): boolean => + !isModelExposureAllowed(aliasToProviderId[providerKey] || providerKey, modelId, settings); // Get active provider connections let connections = []; @@ -1025,6 +1033,7 @@ async function buildUnifiedModelsResponseCore( if (isExcludedByProviderConnections(canonicalProviderId, model.id)) continue; if (shouldHidePaid(canonicalProviderId, model.id, (model as { pricing?: unknown }).pricing)) continue; + if (shouldHideByExposure(canonicalProviderId, model.id)) continue; const visionFields = getVisionCapabilityFields(aliasId) || getVisionCapabilityFields(model.id); @@ -1184,6 +1193,7 @@ async function buildUnifiedModelsResponseCore( // are hidden when hidePaid is on. if (shouldHidePaid(canonicalProviderId, sm.id, (sm as { pricing?: unknown }).pricing)) continue; + if (shouldHideByExposure(canonicalProviderId, sm.id)) continue; const registryEntry = REGISTRY[providerId]; const displayModelId = @@ -1583,6 +1593,7 @@ async function buildUnifiedModelsResponseCore( shouldHidePaid(canonicalProviderId, modelId, (model as { pricing?: unknown }).pricing, (model as any).isFree) ) continue; + if (shouldHideByExposure(canonicalProviderId, modelId)) continue; // noAuth providers have no connection rows; keep auth providers gated. (#2798/#3200) const isNoAuthProvider = isNoAuthProviderKey(canonicalProviderId, providerId, alias); if ( @@ -1760,6 +1771,7 @@ async function buildUnifiedModelsResponseCore( // point at providerKey/modelId with no pricing, so shouldHidePaid() // decides via the FREE_MODEL_IDS_BY_PROVIDER catalog tier. if (shouldHidePaid(canonicalProviderId, modelId)) continue; + if (shouldHideByExposure(canonicalProviderId, modelId)) continue; const aliasId = `${alias}/${modelId}`; const rawPrefixedId = `${providerKey}/${modelId}`; @@ -1835,6 +1847,7 @@ async function buildUnifiedModelsResponseCore( // FREE_MODEL_IDS_BY_PROVIDER catalog tier. if (shouldHidePaid(canonicalProviderId, modelId, (model as { pricing?: unknown }).pricing)) continue; + if (shouldHideByExposure(canonicalProviderId, modelId)) continue; if (!hasEligibleConnectionForModel([conn], modelId)) continue; const aliasId = `${alias}/${modelId}`; diff --git a/src/lib/db/settings.ts b/src/lib/db/settings.ts index 15bfdf6981b6..769c6d08ce74 100644 --- a/src/lib/db/settings.ts +++ b/src/lib/db/settings.ts @@ -251,6 +251,14 @@ export async function getSettings() { // #9418: Opt-in filter that hides no-think/* gateway variants from the /v1/models catalog. // Routing still works for hidden ids sent explicitly. hideNoThinkVariants: false, + // #11481: Opt-in explicit model exposure allow/deny list, mirrored into the + // auto/* combo candidate pool (open-sse/services/autoCombo/modelExposureFilter.ts) + // so a denied model can't sneak back in via combo routing — the same trap + // #6512 already fixed once for hidePaidModels. See + // src/shared/utils/modelExposureList.ts for the matching predicate. Empty + // arrays preserve prior behaviour; opt-in only. + modelVisibilityAllowlist: [], + modelVisibilityDenylist: [], // #6977: Opt-in per-connection auto-ping that warms a Codex OAuth connection's // quota window right after it resets, so the first real request doesn't land in // a cold window. `connections` maps connection id -> enabled. Default empty map diff --git a/src/shared/utils/modelExposureList.ts b/src/shared/utils/modelExposureList.ts new file mode 100644 index 000000000000..5f4a4bb3db85 --- /dev/null +++ b/src/shared/utils/modelExposureList.ts @@ -0,0 +1,73 @@ +/** + * #11481 — explicit model exposure allow/deny list for the `/v1/models` catalog. + * + * Mirrors the opt-in shape of `hidePaidModels`/`hideAutoCombos` + * (`src/lib/db/settings.ts`): default-off, two independent string arrays + * (`modelVisibilityDenylist` / `modelVisibilityAllowlist`). An entry may be an + * exact catalog id ("provider/model" or a bare "model") or a glob pattern + * (`*`/`?`) via the shared `globToRegex` matcher already used by + * `ModelRoutingSection`'s per-model combo mappings and + * `freeModels.ts::matchesOnlyPaidModels` — no new matching logic. + * + * This predicate is the single chokepoint called from BOTH places the catalog + * template requires (#6512's lesson: a catalog-only filter still leaks into + * `auto/*` combo routing): + * - `src/app/api/v1/models/catalog.ts` (the `/v1/models` listing itself) + * - `open-sse/services/autoCombo/modelExposureFilter.ts` (the `auto/*` + * candidate-pool mirror) + * + * A model id sent EXPLICITLY (not via `auto/*`) is never blocked at dispatch — + * only catalog advertisement / candidate-pool membership is filtered, exactly + * like `hideAutoCombos` already behaves. + */ +import { globToRegex } from "./globPattern"; + +export interface ModelExposureListSettings { + modelVisibilityAllowlist?: unknown; + modelVisibilityDenylist?: unknown; +} + +function normalizeList(value: unknown): string[] { + if (!Array.isArray(value)) return []; + return value.filter((v): v is string => typeof v === "string" && v.trim() !== ""); +} + +/** Whether any entry in `list` matches one of `candidates` — exact string match + * first (the common case, no regex cost), falling back to a glob match only + * when the entry actually contains a wildcard character. */ +function listMatchesAny(list: string[], candidates: string[]): boolean { + return list.some((entry) => { + if (candidates.includes(entry)) return true; + if (!/[*?]/.test(entry)) return false; + let regex: RegExp; + try { + regex = globToRegex(entry); + } catch { + return false; + } + return candidates.some((candidate) => regex.test(candidate)); + }); +} + +/** + * Whether a (provider, model) pair should be exposed given the operator's + * allow/deny lists. Empty lists (the default) always expose — opt-in, off by + * default, matching Hard Rule #20's "never mutate the operator's behaviour on + * their behalf by default" spirit. Denylist is checked first (deny wins over + * an overlapping allow entry); when the allowlist is non-empty, only entries + * it matches survive. + */ +export function isModelExposureAllowed( + provider: string, + modelId: string, + settings: ModelExposureListSettings | null | undefined +): boolean { + const denylist = normalizeList(settings?.modelVisibilityDenylist); + const allowlist = normalizeList(settings?.modelVisibilityAllowlist); + if (denylist.length === 0 && allowlist.length === 0) return true; + + const candidates = [modelId, `${provider}/${modelId}`]; + if (listMatchesAny(denylist, candidates)) return false; + if (allowlist.length === 0) return true; + return listMatchesAny(allowlist, candidates); +} diff --git a/src/shared/validation/settingsSchemas.ts b/src/shared/validation/settingsSchemas.ts index c50dcc76b892..21550c2a8ba3 100644 --- a/src/shared/validation/settingsSchemas.ts +++ b/src/shared/validation/settingsSchemas.ts @@ -138,6 +138,16 @@ export const updateSettingsSchema = z.object({ // curated `tos` verdict is "avoid" (proxy/self-hosted use conflicts with the // provider's own terms) — a contractual concern, not an economic one. excludeTosAvoid: z.boolean().optional(), + // #11481: Opt-in explicit model exposure allow/deny list for `/v1/models` AND + // the `auto/*` combo candidate pool (mirror in + // open-sse/services/autoCombo/modelExposureFilter.ts — #6512 already proved a + // catalog-only filter still leaks into combo routing). Entries are exact + // "provider/model" (or bare "model") ids, or a glob pattern via the shared + // globToRegex matcher (src/shared/utils/modelExposureList.ts). Independent of + // hidePaidModels — this is operator curation, not a cost signal. Default + // empty arrays = no-op (Hard Rule #20 spirit). + modelVisibilityAllowlist: z.array(z.string().max(200)).max(500).optional(), + modelVisibilityDenylist: z.array(z.string().max(200)).max(500).optional(), // Subscription-first routing tuning (`auto/subscription`, `auto/thrifty`). // TUNING ONLY — there is deliberately no `enabled` flag: both ids are opt-in // by being requested, and a toggle able to switch them off would leave diff --git a/tests/unit/autoCombo/model-exposure-filter-11481.test.ts b/tests/unit/autoCombo/model-exposure-filter-11481.test.ts new file mode 100644 index 000000000000..8c4dab2b72c8 --- /dev/null +++ b/tests/unit/autoCombo/model-exposure-filter-11481.test.ts @@ -0,0 +1,58 @@ +/** + * #11481 — mandatory mirror of the `/v1/models` model exposure allow/deny + * list into the `auto/*` candidate pool (the same trap #6512 already fixed + * once for `hidePaidModels`: a catalog-only filter still leaks into combo + * routing). + * + * Tests the pure `filterModelExposureCandidates` helper wired into + * `open-sse/services/autoCombo/virtualFactory.ts::buildPreparedPool`. + */ +import { test } from "vitest"; +import assert from "node:assert/strict"; + +import { filterModelExposureCandidates } from "../../../open-sse/services/autoCombo/modelExposureFilter.ts"; + +const DENIED = { provider: "openai", model: "gpt-4o" }; +const KEPT = { provider: "anthropic", model: "claude-opus-5" }; + +test("no exposure lists configured (default) returns the pool UNCHANGED (identity, regression guard)", () => { + const pool = [DENIED, KEPT]; + const result = filterModelExposureCandidates(pool, {}); + assert.equal(result, pool, "must return the exact same array reference when both lists are empty"); + assert.deepEqual(result, [DENIED, KEPT]); +}); + +test("null/undefined settings also return the pool UNCHANGED", () => { + const pool = [DENIED, KEPT]; + assert.equal(filterModelExposureCandidates(pool, null), pool); + assert.equal(filterModelExposureCandidates(pool, undefined), pool); +}); + +test("denylist drops the denied candidate, keeps the rest", () => { + const result = filterModelExposureCandidates([DENIED, KEPT], { + modelVisibilityDenylist: ["openai/gpt-4o"], + }); + assert.deepEqual(result, [KEPT], "openai/gpt-4o must be excluded; anthropic candidate kept"); +}); + +test("a denied model is NOT selectable in the candidate pool even with an all-denied pool", () => { + const result = filterModelExposureCandidates([DENIED, { provider: "openai", model: "o3" }], { + modelVisibilityDenylist: ["openai/*"], + }); + assert.deepEqual(result, [], "an all-denied pool becomes empty — the graceful empty-pool path"); +}); + +test("allowlist restricts the pool to exactly the listed candidates", () => { + const result = filterModelExposureCandidates([DENIED, KEPT], { + modelVisibilityAllowlist: ["anthropic/claude-opus-5"], + }); + assert.deepEqual(result, [KEPT]); +}); + +test("preserves extra candidate fields on kept entries", () => { + const enriched = { provider: "anthropic", model: "claude-opus-5", connectionId: "abc", extra: 1 }; + const result = filterModelExposureCandidates([enriched, DENIED], { + modelVisibilityDenylist: ["openai/gpt-4o"], + }); + assert.deepEqual(result, [enriched], "generic filter must not strip candidate fields"); +}); diff --git a/tests/unit/model-exposure-list-settings-schema.test.ts b/tests/unit/model-exposure-list-settings-schema.test.ts new file mode 100644 index 000000000000..f4876ce3b110 --- /dev/null +++ b/tests/unit/model-exposure-list-settings-schema.test.ts @@ -0,0 +1,63 @@ +/** + * #11481 — `modelVisibilityAllowlist`/`modelVisibilityDenylist` settings PATCH + * schema validation. Mirrors hide-paid-models-settings-schema.test.ts. + */ +import test from "node:test"; +import assert from "node:assert/strict"; + +import { updateSettingsSchema } from "../../src/shared/validation/settingsSchemas.ts"; + +test("modelVisibilityAllowlist/Denylist are accepted and preserved by the settings PATCH schema", () => { + const validation = updateSettingsSchema.safeParse({ + modelVisibilityAllowlist: ["openai/gpt-4o-mini"], + modelVisibilityDenylist: ["openai/gpt-4o", "anthropic/*"], + }); + + assert.equal(validation.success, true); + if (!validation.success) return; + assert.deepEqual(validation.data.modelVisibilityAllowlist, ["openai/gpt-4o-mini"]); + assert.deepEqual(validation.data.modelVisibilityDenylist, ["openai/gpt-4o", "anthropic/*"]); +}); + +test("modelVisibilityAllowlist/Denylist default to undefined when not provided", () => { + const validation = updateSettingsSchema.safeParse({}); + + assert.equal(validation.success, true); + if (!validation.success) return; + assert.equal(validation.data.modelVisibilityAllowlist, undefined); + assert.equal(validation.data.modelVisibilityDenylist, undefined); +}); + +test("empty arrays (explicit opt-out / reset) are accepted", () => { + const validation = updateSettingsSchema.safeParse({ + modelVisibilityAllowlist: [], + modelVisibilityDenylist: [], + }); + + assert.equal(validation.success, true); +}); + +test("rejects non-array values", () => { + assert.equal( + updateSettingsSchema.safeParse({ modelVisibilityDenylist: "openai/gpt-4o" }).success, + false + ); + assert.equal( + updateSettingsSchema.safeParse({ modelVisibilityAllowlist: true }).success, + false + ); +}); + +test("rejects a non-string array entry", () => { + assert.equal( + updateSettingsSchema.safeParse({ modelVisibilityDenylist: [123] }).success, + false + ); +}); + +test("rejects an entry longer than the 200-char cap", () => { + assert.equal( + updateSettingsSchema.safeParse({ modelVisibilityDenylist: ["x".repeat(201)] }).success, + false + ); +}); diff --git a/tests/unit/model-exposure-list.test.ts b/tests/unit/model-exposure-list.test.ts new file mode 100644 index 000000000000..7764630f917b --- /dev/null +++ b/tests/unit/model-exposure-list.test.ts @@ -0,0 +1,79 @@ +/** + * #11481 — explicit model exposure allow/deny list for `/v1/models` (and, via the + * mirror in `open-sse/services/autoCombo/modelExposureFilter.ts`, the `auto/*` + * candidate pool). Follows the `hidePaidModels`/`hideAutoCombos` opt-in shape + * (`src/lib/db/settings.ts`): default-off, two independent string arrays. + * + * Tests the pure predicate `isModelExposureAllowed()` — the single chokepoint + * both `catalog.ts` and the combo-pool mirror call into. + */ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { isModelExposureAllowed } from "@/shared/utils/modelExposureList"; + +test("default off (both lists empty) — every model is exposed", () => { + assert.equal(isModelExposureAllowed("openai", "gpt-4o", {}), true); + assert.equal( + isModelExposureAllowed("openai", "gpt-4o", { + modelVisibilityAllowlist: [], + modelVisibilityDenylist: [], + }), + true + ); + assert.equal(isModelExposureAllowed("openai", "gpt-4o", null), true); + assert.equal(isModelExposureAllowed("openai", "gpt-4o", undefined), true); +}); + +test("denylist hides an exact-id match, leaves everything else exposed", () => { + const settings = { modelVisibilityDenylist: ["openai/gpt-4o"] }; + assert.equal(isModelExposureAllowed("openai", "gpt-4o", settings), false); + assert.equal(isModelExposureAllowed("openai", "gpt-4o-mini", settings), true); + assert.equal(isModelExposureAllowed("anthropic", "claude-opus-5", settings), true); +}); + +test("denylist also matches the bare model id (no provider prefix required)", () => { + const settings = { modelVisibilityDenylist: ["gpt-4o"] }; + assert.equal(isModelExposureAllowed("openai", "gpt-4o", settings), false); +}); + +test("allowlist restricts exposure to exactly the listed models", () => { + const settings = { modelVisibilityAllowlist: ["openai/gpt-4o-mini", "anthropic/claude-opus-5"] }; + assert.equal(isModelExposureAllowed("openai", "gpt-4o-mini", settings), true); + assert.equal(isModelExposureAllowed("anthropic", "claude-opus-5", settings), true); + assert.equal(isModelExposureAllowed("openai", "gpt-4o", settings), false); + assert.equal(isModelExposureAllowed("google", "gemini-2.5-pro", settings), false); +}); + +test("denylist wins over allowlist for the same entry (deny is checked first)", () => { + const settings = { + modelVisibilityAllowlist: ["openai/gpt-4o"], + modelVisibilityDenylist: ["openai/gpt-4o"], + }; + assert.equal(isModelExposureAllowed("openai", "gpt-4o", settings), false); +}); + +test("glob patterns are supported via the shared globToRegex matcher", () => { + const denyGlob = { modelVisibilityDenylist: ["openai/gpt-4*"] }; + assert.equal(isModelExposureAllowed("openai", "gpt-4o", denyGlob), false); + assert.equal(isModelExposureAllowed("openai", "gpt-4.1", denyGlob), false); + assert.equal(isModelExposureAllowed("openai", "o1-preview", denyGlob), true); + + const allowGlob = { modelVisibilityAllowlist: ["anthropic/*"] }; + assert.equal(isModelExposureAllowed("anthropic", "claude-opus-5", allowGlob), true); + assert.equal(isModelExposureAllowed("openai", "gpt-4o", allowGlob), false); +}); + +test("non-array / malformed list values are treated as empty (fail open, never throw)", () => { + assert.equal( + isModelExposureAllowed("openai", "gpt-4o", { + modelVisibilityDenylist: "openai/gpt-4o" as unknown as string[], + }), + true + ); + assert.equal( + isModelExposureAllowed("openai", "gpt-4o", { + modelVisibilityDenylist: [123 as unknown as string, " "], + }), + true + ); +}); diff --git a/tests/unit/models-catalog-model-exposure-list.test.ts b/tests/unit/models-catalog-model-exposure-list.test.ts new file mode 100644 index 000000000000..914829474779 --- /dev/null +++ b/tests/unit/models-catalog-model-exposure-list.test.ts @@ -0,0 +1,93 @@ +/** + * #11481 — `modelVisibilityAllowlist`/`modelVisibilityDenylist` filter the + * unified `/v1/models` catalog. Mirrors models-catalog-hide-paid.test.ts. + * Rule #18 regression guard for the toggle. + */ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-model-exposure-")); +process.env.DATA_DIR = TEST_DATA_DIR; + +const core = await import("../../src/lib/db/core.ts"); +const settingsDb = await import("../../src/lib/db/settings.ts"); +const providersDb = await import("../../src/lib/db/providers.ts"); +const v1ModelsCatalog = await import("../../src/app/api/v1/models/catalog.ts"); + +async function fetchCatalog(): Promise> { + const res = await v1ModelsCatalog.getUnifiedModelsResponse( + new Request("http://localhost/api/v1/models", { method: "GET" }) + ); + assert.equal(res.status, 200); + const body = (await res.json()) as { data: Array<{ id: string; type?: string }> }; + return body.data; +} + +test.after(() => { + core.resetDbInstance(); + try { + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + } catch { + /* best-effort */ + } +}); + +test("default (both lists empty) — catalog is unchanged", async () => { + const defaults = await settingsDb.getSettings(); + assert.deepEqual(defaults.modelVisibilityAllowlist, []); + assert.deepEqual(defaults.modelVisibilityDenylist, []); + + await providersDb.createProviderConnection({ + provider: "openai", + authType: "apikey", + name: "openai-main-exposure", + apiKey: "sk-test", + isActive: true, + }); + + const ids = (await fetchCatalog()).map((m) => m.id); + assert.ok( + ids.some((id) => /^(openai|oa)\/gpt-4o(-mini)?$/.test(id)), + "expected openai chat models present with no exposure list configured" + ); +}); + +test("modelVisibilityDenylist hides an exact-id match from /v1/models", async () => { + await settingsDb.updateSettings({ + modelVisibilityAllowlist: [], + modelVisibilityDenylist: ["openai/gpt-4o-mini"], + }); + + const ids = (await fetchCatalog()).map((m) => m.id); + assert.ok(!ids.includes("openai/gpt-4o-mini"), "denied model must not appear in the catalog"); + assert.ok( + ids.some((id) => id === "openai/gpt-4o"), + "a non-denied sibling model must remain" + ); + + await settingsDb.updateSettings({ modelVisibilityDenylist: [] }); +}); + +test("modelVisibilityAllowlist restricts the chat catalog to exactly the listed models", async () => { + await settingsDb.updateSettings({ + modelVisibilityDenylist: [], + modelVisibilityAllowlist: ["openai/gpt-4o-mini"], + }); + + const chatIds = (await fetchCatalog()) + .filter((m) => m.type === undefined || m.type === "chat") + .map((m) => m.id) + .filter((id) => id.startsWith("openai/") || id.startsWith("oa/")); + + assert.deepEqual( + chatIds.filter((id) => id !== "openai/gpt-4o-mini"), + [], + `only the allow-listed model may remain, found: ${chatIds.join(", ")}` + ); + assert.ok(chatIds.includes("openai/gpt-4o-mini"), "the allow-listed model itself must remain"); + + await settingsDb.updateSettings({ modelVisibilityAllowlist: [] }); +}); From 2b8d3a8291094bb03c0d1c9d99d07262c8b896fb Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sat, 29 Aug 2026 17:37:26 -0300 Subject: [PATCH 28/91] fix(radar): restore D12 public boundary (#12057) --- open-sse/utils/imageNormalize.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/open-sse/utils/imageNormalize.ts b/open-sse/utils/imageNormalize.ts index 0f528874d93a..ccbbaf6364c4 100644 --- a/open-sse/utils/imageNormalize.ts +++ b/open-sse/utils/imageNormalize.ts @@ -1,7 +1,7 @@ /** * Optional-sharp image normalization. * - * Rationale (migrated from freellmapi `server/src/lib/image-normalize.ts:40-58`): + * Rationale: * OpenAI resizes images to a long-edge cap of 2048px server-side, Anthropic applies * a similar cap. Downscaling client-side before upload reduces tokens/latency without * changing model behavior. `sharp` is loaded via dynamic import so that a platform From 3b00535d041c8e33d25afacb5639b3b9d1d1d055 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sat, 29 Aug 2026 19:32:35 -0300 Subject: [PATCH 29/91] feat(guardrails): enforce video transcript provenance, budgets and reconciliation (#11652) (#12009) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FU-05 core (closes #11652): caller-supplied Video Bridge transcripts had no bounded, deterministic contract — a client could self-assert source: "embedded"/"audio-bridge" and it was accepted verbatim. normalizeVideoTranscript gained a code-only trustedSource seam unreachable from request-body JSON; without it, any cue declaring embedded/audio-bridge is reclassified to client. Added budgets (256 cues, 4096 code units/cue, 4KiB/cue, 64KiB total), malformed-surrogate rejection, focus-window scoping, deterministic cross-source reconciliation, and bumped the result-cache version v4→v5 so old-contract cache entries can never serve new-contract requests. All 187 videoBridge* tests pass (185 pass, 2 unrelated pre-existing skips). --- ...652-video-transcript-provenance-budgets.md | 1 + src/lib/guardrails/videoBridge.ts | 6 +- src/lib/guardrails/videoBridgeHelpers.ts | 143 ++------- .../videoBridgeTranscriptContract.ts | 297 ++++++++++++++++++ .../guardrails/videoBridgeResultCache.test.ts | 10 +- .../videoBridgeTranscriptBudgets.test.ts | 211 +++++++++++++ ...videoBridgeTranscriptCacheIdentity.test.ts | 145 +++++++++ .../videoBridgeTranscriptProvenance.test.ts | 63 +++- 8 files changed, 742 insertions(+), 134 deletions(-) create mode 100644 changelog.d/features/11652-video-transcript-provenance-budgets.md create mode 100644 src/lib/guardrails/videoBridgeTranscriptContract.ts create mode 100644 tests/unit/guardrails/videoBridgeTranscriptBudgets.test.ts create mode 100644 tests/unit/guardrails/videoBridgeTranscriptCacheIdentity.test.ts diff --git a/changelog.d/features/11652-video-transcript-provenance-budgets.md b/changelog.d/features/11652-video-transcript-provenance-budgets.md new file mode 100644 index 000000000000..de4e67a09541 --- /dev/null +++ b/changelog.d/features/11652-video-transcript-provenance-budgets.md @@ -0,0 +1 @@ +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) diff --git a/src/lib/guardrails/videoBridge.ts b/src/lib/guardrails/videoBridge.ts index 4672a7dc3b04..1ae3ee95b7a8 100644 --- a/src/lib/guardrails/videoBridge.ts +++ b/src/lib/guardrails/videoBridge.ts @@ -102,7 +102,11 @@ function waitForVideoBridgePromise(promise: Promise, signal: AbortSignal): }); } -const VIDEO_BRIDGE_RESULT_CACHE_VERSION = "v4"; +// v5 (#11652): the normalized transcript contract changed (provenance trust +// boundary, budgets, cross-source reconciliation, focus scoping) — bump so a +// cache entry computed under the old, less-restrictive normalization can +// never be served for a request processed under the new contract. +const VIDEO_BRIDGE_RESULT_CACHE_VERSION = "v5"; const VIDEO_BRIDGE_RESULT_CACHE_POLICY = "sampling-then-dedup-v2"; const VIDEO_BRIDGE_RESULT_CACHE_KEY_KIND = "video-result-v4"; const VIDEO_BRIDGE_DOWNLOAD_FLIGHT_VERSION = "v1"; diff --git a/src/lib/guardrails/videoBridgeHelpers.ts b/src/lib/guardrails/videoBridgeHelpers.ts index 4673191bae06..c4bb21a4f961 100644 --- a/src/lib/guardrails/videoBridgeHelpers.ts +++ b/src/lib/guardrails/videoBridgeHelpers.ts @@ -16,6 +16,18 @@ import { type VideoSamplingMetadata, type VideoSamplingPolicy, } from "./videoBridgeRuntime"; +import { + buildNormalizedVideoTranscript, + reconcileVideoTranscriptCues, + type NormalizeVideoTranscriptOptions, + type VideoTranscriptCue, +} from "./videoBridgeTranscriptContract"; + +export type { + NormalizeVideoTranscriptOptions, + VideoTranscriptCue, + VideoTranscriptSource, +} from "./videoBridgeTranscriptContract"; export const VIDEO_BRIDGE_MAX_BYTES = 50 * 1024 * 1024; // Inline base64 shares the public 50 MiB JSON admission budget with model, @@ -91,92 +103,18 @@ export interface VideoPart { contactSheet?: boolean; } -export type VideoTranscriptSource = "audio-bridge" | "client" | "embedded"; - -export interface VideoTranscriptCue { - confidence: number; - endSeconds: number; - source: VideoTranscriptSource; - startSeconds: number; - text: string; -} - -const VIDEO_TRANSCRIPT_SOURCES: ReadonlySet = new Set([ - "audio-bridge", - "client", - "embedded", -]); - -/** Validate optional transcript metadata without ever invoking a transcription provider. */ +/** + * Validate optional transcript metadata without ever invoking a transcription + * provider. Delegates the full contract (budgets, the provenance trust + * boundary, reconciliation, and focus scoping) to videoBridgeTranscriptContract.ts + * — see that module for the security rationale. + */ export function normalizeVideoTranscript( value: unknown, - durationSeconds: number + durationSeconds: number, + options?: NormalizeVideoTranscriptOptions ): VideoTranscriptCue[] { - if (value === undefined || value === null) return []; - const rawCues = Array.isArray(value) - ? value - : value && typeof value === "object" && Array.isArray((value as Record).cues) - ? (value as Record).cues - : null; - if (!rawCues) throw new Error("Invalid video transcript: expected a cues array"); - if (!Number.isFinite(durationSeconds) || durationSeconds <= 0) { - throw new Error("Invalid video transcript duration"); - } - const seen = new Set(); - const normalized: VideoTranscriptCue[] = []; - for (const cue of rawCues) { - if (!cue || typeof cue !== "object") throw new Error("Invalid video transcript cue"); - const record = cue as Record; - const text = typeof record.text === "string" ? record.text.trim() : ""; - const source = record.source; - const startSeconds = - typeof record.startSeconds === "number" - ? record.startSeconds - : typeof record.start === "number" - ? record.start - : Number.NaN; - const endSeconds = - typeof record.endSeconds === "number" - ? record.endSeconds - : typeof record.end === "number" - ? record.end - : Number.NaN; - const confidence = record.confidence === undefined ? 1 : record.confidence; - if ( - !text || - typeof source !== "string" || - !VIDEO_TRANSCRIPT_SOURCES.has(source as VideoTranscriptSource) - ) { - throw new Error("Invalid video transcript source or provenance"); - } - if ( - !Number.isFinite(startSeconds) || - !Number.isFinite(endSeconds) || - !Number.isFinite(confidence) || - confidence < 0 || - confidence > 1 || - startSeconds < 0 || - endSeconds > durationSeconds || - endSeconds <= startSeconds - ) { - throw new Error("Invalid video transcript timestamp or confidence range"); - } - const normalizedCue = { - confidence, - endSeconds, - source: source as VideoTranscriptSource, - startSeconds, - text, - } satisfies VideoTranscriptCue; - const key = JSON.stringify(normalizedCue); - if (!seen.has(key)) { - seen.add(key); - normalized.push(normalizedCue); - } - } - return normalized.sort( - (left, right) => left.startSeconds - right.startSeconds || left.endSeconds - right.endSeconds - ); + return buildNormalizedVideoTranscript(value, durationSeconds, options); } const REPLACEABLE_VIDEO_SHAPES: ReadonlySet = new Set([ @@ -551,31 +489,6 @@ function formatTranscriptCue(cue: VideoTranscriptCue): string { return `transcript[source=${cue.source};confidence=${cue.confidence.toFixed(2)};interval=${formatVideoTimestamp(cue.startSeconds)}-${formatVideoTimestamp(cue.endSeconds)}] ${cue.text}`; } -function deduplicateVideoTranscriptCues(cues: readonly VideoTranscriptCue[]): VideoTranscriptCue[] { - const seen = new Set(); - return cues - .filter((cue) => { - const key = JSON.stringify([ - cue.source, - cue.confidence, - cue.startSeconds, - cue.endSeconds, - cue.text, - ]); - if (seen.has(key)) return false; - seen.add(key); - return true; - }) - .sort( - (left, right) => - left.startSeconds - right.startSeconds || - left.endSeconds - right.endSeconds || - left.source.localeCompare(right.source) || - left.text.localeCompare(right.text) || - left.confidence - right.confidence - ); -} - export async function describeVideoPart( part: VideoPart, options: DescribeVideoOptions, @@ -644,7 +557,8 @@ export async function describeVideoPart( : null; const separatelyRenderedTranscriptCues = normalizeVideoTranscript( part.transcript, - extracted.durationSeconds + extracted.durationSeconds, + { focusWindow } ); let transcriptCues = [...separatelyRenderedTranscriptCues]; let appendedTranscriptCues = separatelyRenderedTranscriptCues; @@ -698,7 +612,14 @@ export async function describeVideoPart( audio: async () => { normalizedFusionTranscriptCues = normalizeVideoTranscript( part.audioTranscript, - extracted.durationSeconds + extracted.durationSeconds, + // Structural trust seam: whatever the caller supplies in the + // dedicated audioTranscript field is always labeled "audio-bridge" + // by this fusion channel, regardless of any per-cue `source` the + // caller declared. This is not an authenticity claim about the + // caller's own transcription — only that it arrived through the + // audio-bridge fusion field rather than the generic transcript. + { trustedSource: "audio-bridge", focusWindow } ); return { observations: normalizedFusionTranscriptCues.map((cue) => ({ @@ -726,7 +647,7 @@ export async function describeVideoPart( ...(fused.failures ? { failures: fused.failures } : {}), }; const fusedAudioCues = fused.audioAvailable ? normalizedFusionTranscriptCues : []; - transcriptCues = deduplicateVideoTranscriptCues([...transcriptCues, ...fusedAudioCues]); + transcriptCues = reconcileVideoTranscriptCues([...transcriptCues, ...fusedAudioCues]); const fusedVideoTimeline = fused.observations.flatMap((observation) => observation.source === "video" ? [ diff --git a/src/lib/guardrails/videoBridgeTranscriptContract.ts b/src/lib/guardrails/videoBridgeTranscriptContract.ts new file mode 100644 index 000000000000..a5655da585a1 --- /dev/null +++ b/src/lib/guardrails/videoBridgeTranscriptContract.ts @@ -0,0 +1,297 @@ +/** + * FU-05 transcript contract: deterministic budgets, a structural provenance + * trust boundary, and cross-source reconciliation for Video Bridge + * transcripts. Extracted from videoBridgeHelpers.ts so the frozen-adjacent + * helper file only grows by a thin delegation (see #11652). + * + * Trust boundary: `normalizeVideoTranscript`'s only public entry point is + * `buildNormalizedVideoTranscript`. A caller-supplied cue can only ever + * declare `source: "client"` — declaring "embedded" or "audio-bridge" is + * self-asserted provenance and is silently reclassified to "client" because + * there is no way for the server to verify it came from an embedded-subtitle + * extractor or the audio-bridge pipeline. The ONLY way to obtain a trusted + * "embedded"/"audio-bridge" cue is for server-owned code to pass + * `options.trustedSource` explicitly — that option can never be reached by + * deserializing request-body JSON (it is a second, code-only argument), so + * the trust decision is structural rather than a flag the caller can flip. + */ + +export type VideoTranscriptSource = "audio-bridge" | "client" | "embedded"; + +export interface VideoTranscriptCue { + confidence: number; + endSeconds: number; + source: VideoTranscriptSource; + startSeconds: number; + text: string; + /** + * Present only when reconciliation merged cues declaring 2+ distinct + * sources into one cue (see `reconcileVideoTranscriptCues`). Absent for + * the common single-source case so existing exact-shape assertions are + * unaffected. + */ + contributingSources?: VideoTranscriptSource[]; +} + +export interface VideoTranscriptFocusWindow { + endSeconds: number; + startSeconds: number; +} + +export interface NormalizeVideoTranscriptOptions { + /** Structural trust seam — server-owned adapters only, never derived from request JSON. */ + trustedSource?: VideoTranscriptSource; + /** Scope (filter + clip) the resulting cues to this window; `null`/omitted keeps everything. */ + focusWindow?: VideoTranscriptFocusWindow | null; +} + +const VIDEO_TRANSCRIPT_SOURCES: ReadonlySet = new Set([ + "audio-bridge", + "client", + "embedded", +]); + +export const VIDEO_TRANSCRIPT_MAX_CUES = 256; +export const VIDEO_TRANSCRIPT_MAX_CUE_CODE_UNITS = 4096; +export const VIDEO_TRANSCRIPT_MAX_CUE_UTF8_BYTES = 4 * 1024; +export const VIDEO_TRANSCRIPT_MAX_TOTAL_UTF8_BYTES = 64 * 1024; + +// Bounded (no unbounded quantifiers) — matches an unpaired high surrogate not +// followed by a low surrogate, or an unpaired low surrogate not preceded by a +// high surrogate. Safe against catastrophic backtracking per AGENTS.md ReDoS rule. +const LONE_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/; + +const SOURCE_PRIORITY: Record = { + client: 0, + "audio-bridge": 1, + embedded: 2, +}; + +function extractRawCues(value: unknown): unknown[] { + if (Array.isArray(value)) return value; + if ( + value && + typeof value === "object" && + Array.isArray((value as Record).cues) + ) { + return (value as Record).cues as unknown[]; + } + throw new Error("Invalid video transcript: expected a cues array"); +} + +function extractCueText(record: Record): string { + const raw = record.text; + if (typeof raw !== "string") throw new Error("Invalid video transcript cue text"); + if (raw.length > VIDEO_TRANSCRIPT_MAX_CUE_CODE_UNITS) { + throw new Error( + `Video transcript cue exceeds the maximum of ${VIDEO_TRANSCRIPT_MAX_CUE_CODE_UNITS} input code units` + ); + } + if (LONE_SURROGATE.test(raw)) { + throw new Error("Invalid video transcript cue text encoding"); + } + const text = raw.normalize("NFC").trim(); + if (!text) throw new Error("Invalid video transcript cue text"); + if (Buffer.byteLength(text, "utf8") > VIDEO_TRANSCRIPT_MAX_CUE_UTF8_BYTES) { + throw new Error( + `Video transcript cue exceeds the maximum of ${VIDEO_TRANSCRIPT_MAX_CUE_UTF8_BYTES} UTF-8 bytes` + ); + } + return text; +} + +function resolveCueSource( + record: Record, + trustedSource: VideoTranscriptSource | undefined +): VideoTranscriptSource { + if (trustedSource) return trustedSource; + const declared = record.source; + if ( + typeof declared !== "string" || + !VIDEO_TRANSCRIPT_SOURCES.has(declared as VideoTranscriptSource) + ) { + throw new Error("Invalid video transcript source or provenance"); + } + // Only a server-owned adapter may assign "embedded"/"audio-bridge" via the + // trustedSource seam above; any caller-declared value collapses to "client". + return "client"; +} + +function resolveCueTimestamps(record: Record): { + startSeconds: number; + endSeconds: number; +} { + const startSeconds = + typeof record.startSeconds === "number" + ? record.startSeconds + : typeof record.start === "number" + ? record.start + : Number.NaN; + const endSeconds = + typeof record.endSeconds === "number" + ? record.endSeconds + : typeof record.end === "number" + ? record.end + : Number.NaN; + return { startSeconds, endSeconds }; +} + +function assertValidCueBounds( + startSeconds: number, + endSeconds: number, + confidence: unknown, + durationSeconds: number +): void { + if ( + !Number.isFinite(startSeconds) || + !Number.isFinite(endSeconds) || + !Number.isFinite(confidence as number) || + (confidence as number) < 0 || + (confidence as number) > 1 || + startSeconds < 0 || + endSeconds > durationSeconds || + endSeconds <= startSeconds + ) { + throw new Error("Invalid video transcript timestamp or confidence range"); + } +} + +function parseVideoTranscriptCue( + cue: unknown, + durationSeconds: number, + trustedSource: VideoTranscriptSource | undefined +): VideoTranscriptCue { + if (!cue || typeof cue !== "object") throw new Error("Invalid video transcript cue"); + const record = cue as Record; + const text = extractCueText(record); + const source = resolveCueSource(record, trustedSource); + const { startSeconds, endSeconds } = resolveCueTimestamps(record); + const confidence = record.confidence === undefined ? 1 : record.confidence; + assertValidCueBounds(startSeconds, endSeconds, confidence, durationSeconds); + return { confidence: confidence as number, endSeconds, source, startSeconds, text }; +} + +interface VideoTranscriptCluster { + startSeconds: number; + endSeconds: number; + text: string; + members: VideoTranscriptCue[]; +} + +function findOverlappingCluster( + clusters: readonly VideoTranscriptCluster[], + cue: VideoTranscriptCue +): VideoTranscriptCluster | undefined { + return clusters.find( + (cluster) => + cluster.text === cue.text && + cue.startSeconds < cluster.endSeconds && + cue.endSeconds > cluster.startSeconds + ); +} + +function mergeVideoTranscriptCluster(cluster: VideoTranscriptCluster): VideoTranscriptCue { + const { members } = cluster; + const distinctSources = Array.from(new Set(members.map((member) => member.source))).sort( + (left, right) => SOURCE_PRIORITY[left] - SOURCE_PRIORITY[right] + ); + const winningSource = distinctSources[distinctSources.length - 1]; + return { + confidence: Math.max(...members.map((member) => member.confidence)), + endSeconds: Math.max(...members.map((member) => member.endSeconds)), + source: winningSource, + startSeconds: Math.min(...members.map((member) => member.startSeconds)), + text: cluster.text, + ...(distinctSources.length > 1 ? { contributingSources: distinctSources } : {}), + }; +} + +/** + * Reconcile duplicate and time-overlapping cues sharing identical text — + * possibly contributed by different sources/calls — into one deterministic + * cue per cluster, preserving which sources contributed when more than one + * did. Replaces naive exact-match deduplication, which silently discarded + * the losing source's identity. + */ +export function reconcileVideoTranscriptCues( + cues: readonly VideoTranscriptCue[] +): VideoTranscriptCue[] { + const sorted = [...cues].sort( + (left, right) => + left.startSeconds - right.startSeconds || + left.endSeconds - right.endSeconds || + left.text.localeCompare(right.text) || + SOURCE_PRIORITY[left.source] - SOURCE_PRIORITY[right.source] + ); + const clusters: VideoTranscriptCluster[] = []; + for (const cue of sorted) { + const cluster = findOverlappingCluster(clusters, cue); + if (cluster) { + cluster.members.push(cue); + cluster.startSeconds = Math.min(cluster.startSeconds, cue.startSeconds); + cluster.endSeconds = Math.max(cluster.endSeconds, cue.endSeconds); + } else { + clusters.push({ + endSeconds: cue.endSeconds, + members: [cue], + startSeconds: cue.startSeconds, + text: cue.text, + }); + } + } + return clusters + .map(mergeVideoTranscriptCluster) + .sort((left, right) => left.startSeconds - right.startSeconds || left.endSeconds - right.endSeconds); +} + +/** Filter cues to those overlapping `focusWindow`, clipping their bounds to it. */ +export function scopeVideoTranscriptCuesToFocusWindow( + cues: readonly VideoTranscriptCue[], + focusWindow: VideoTranscriptFocusWindow | null | undefined +): VideoTranscriptCue[] { + if (!focusWindow) return [...cues]; + return cues + .filter( + (cue) => cue.startSeconds < focusWindow.endSeconds && cue.endSeconds > focusWindow.startSeconds + ) + .map((cue) => ({ + ...cue, + endSeconds: Math.min(cue.endSeconds, focusWindow.endSeconds), + startSeconds: Math.max(cue.startSeconds, focusWindow.startSeconds), + })); +} + +/** + * Validate optional transcript metadata without ever invoking a transcription + * provider. Enforces the FU-05 budgets (256 cues, 4096 input code units/cue, + * 4 KiB UTF-8/cue, 64 KiB total), the provenance trust boundary described at + * the top of this file, cross-source reconciliation, and focus-window scoping. + */ +export function buildNormalizedVideoTranscript( + value: unknown, + durationSeconds: number, + options: NormalizeVideoTranscriptOptions = {} +): VideoTranscriptCue[] { + if (value === undefined || value === null) return []; + if (!Number.isFinite(durationSeconds) || durationSeconds <= 0) { + throw new Error("Invalid video transcript duration"); + } + const rawCues = extractRawCues(value); + if (rawCues.length > VIDEO_TRANSCRIPT_MAX_CUES) { + throw new Error(`Video transcript exceeds the maximum of ${VIDEO_TRANSCRIPT_MAX_CUES} cues`); + } + const parsed: VideoTranscriptCue[] = []; + let totalBytes = 0; + for (const rawCue of rawCues) { + const cue = parseVideoTranscriptCue(rawCue, durationSeconds, options.trustedSource); + totalBytes += Buffer.byteLength(cue.text, "utf8"); + if (totalBytes > VIDEO_TRANSCRIPT_MAX_TOTAL_UTF8_BYTES) { + throw new Error( + `Video transcript exceeds the maximum total of ${VIDEO_TRANSCRIPT_MAX_TOTAL_UTF8_BYTES} UTF-8 bytes` + ); + } + parsed.push(cue); + } + const reconciled = reconcileVideoTranscriptCues(parsed); + return scopeVideoTranscriptCuesToFocusWindow(reconciled, options.focusWindow ?? null); +} diff --git a/tests/unit/guardrails/videoBridgeResultCache.test.ts b/tests/unit/guardrails/videoBridgeResultCache.test.ts index fb7c50ec8c2f..146af8738004 100644 --- a/tests/unit/guardrails/videoBridgeResultCache.test.ts +++ b/tests/unit/guardrails/videoBridgeResultCache.test.ts @@ -430,7 +430,7 @@ test("result-cache metadata carries the exact visual dedup policy identity", asy ); assert.ok(storedMetadata); - assert.equal(storedMetadata.cacheVersion, "v4"); + assert.equal(storedMetadata.cacheVersion, "v5"); assert.equal(storedMetadata.policyVersion, "sampling-then-dedup-v2"); assert.equal(storedMetadata.dedupPolicyVersion, "grayscale-16x16-mean-cells-v2"); assert.equal(storedMetadata.dedupThreshold, 0.04); @@ -446,12 +446,12 @@ test("a corrupt result-cache payload is discarded and recomputed", async () => { producerModel: "openai/gpt-4o-mini", metadata: { analysisMode: "full", - cacheVersion: "v4", + cacheVersion: "v5", dedupCandidateFrameCount: 16, dedupPolicyVersion: "grayscale-16x16-mean-cells-v2", dedupThreshold: 0.04, policyVersion: "sampling-then-dedup-v2", - extractorVersion: "v4", + extractorVersion: "v5", strategy: "uniform", model: "openai/gpt-4o-mini", prompt: "FU-01 corrupt cache", @@ -510,12 +510,12 @@ test("invalid numeric result-cache metadata is deleted and recomputed", async (t const cachedValue = "[Video description: cached numeric metadata]"; const validMetadata = (): Record => ({ analysisMode: "full", - cacheVersion: "v4", + cacheVersion: "v5", dedupCandidateFrameCount: 16, dedupPolicyVersion: "grayscale-16x16-mean-cells-v2", dedupThreshold: 0.04, policyVersion: "sampling-then-dedup-v2", - extractorVersion: "v4", + extractorVersion: "v5", strategy: "uniform", model: "openai/gpt-4o-mini", prompt: "FU-01 numeric cache validation", diff --git a/tests/unit/guardrails/videoBridgeTranscriptBudgets.test.ts b/tests/unit/guardrails/videoBridgeTranscriptBudgets.test.ts new file mode 100644 index 000000000000..1b776484fd5f --- /dev/null +++ b/tests/unit/guardrails/videoBridgeTranscriptBudgets.test.ts @@ -0,0 +1,211 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { + describeVideoPart, + normalizeVideoTranscript, + type VideoCaptionFrame, +} from "../../../src/lib/guardrails/videoBridgeHelpers"; +import { + VIDEO_TRANSCRIPT_MAX_CUES, + VIDEO_TRANSCRIPT_MAX_CUE_CODE_UNITS, + VIDEO_TRANSCRIPT_MAX_CUE_UTF8_BYTES, + VIDEO_TRANSCRIPT_MAX_TOTAL_UTF8_BYTES, +} from "../../../src/lib/guardrails/videoBridgeTranscriptContract"; + +function cue(overrides: Partial> = {}): Record { + return { text: "cue", start: 0, end: 1, source: "client", ...overrides }; +} + +// --------------------------------------------------------------------------- +// Budgets (#11652 scope): 256 cues, 4096 input code units/cue, 4 KiB UTF-8/cue, +// 64 KiB total text. All enforced deterministically with a clear error. +// --------------------------------------------------------------------------- + +test("accepts exactly the maximum cue count and rejects one more", () => { + const atLimit = Array.from({ length: VIDEO_TRANSCRIPT_MAX_CUES }, (_unused, index) => + cue({ text: `cue-${index}`, start: index, end: index + 0.5 }) + ); + assert.equal(normalizeVideoTranscript({ cues: atLimit }, VIDEO_TRANSCRIPT_MAX_CUES + 1).length, VIDEO_TRANSCRIPT_MAX_CUES); + + const overLimit = [...atLimit, cue({ text: "one-too-many", start: VIDEO_TRANSCRIPT_MAX_CUES })]; + assert.throws( + () => normalizeVideoTranscript({ cues: overLimit }, VIDEO_TRANSCRIPT_MAX_CUES + 2), + /256 cues/ + ); +}); + +test("rejects a cue whose raw text exceeds the maximum input code units", () => { + const tooLong = "a".repeat(VIDEO_TRANSCRIPT_MAX_CUE_CODE_UNITS + 1); + assert.throws( + () => normalizeVideoTranscript({ cues: [cue({ text: tooLong })] }, 10), + /input code units/ + ); + // Exactly at the limit is accepted. + const atLimit = "a".repeat(VIDEO_TRANSCRIPT_MAX_CUE_CODE_UNITS); + assert.equal(normalizeVideoTranscript({ cues: [cue({ text: atLimit })] }, 10)[0]?.text.length, VIDEO_TRANSCRIPT_MAX_CUE_CODE_UNITS); +}); + +test("rejects a cue whose UTF-8 encoding exceeds the maximum per-cue size", () => { + // Each "é" (U+00E9) is 1 code unit but 2 UTF-8 bytes, so this trips the byte + // budget while staying well under the code-unit budget. + const wideText = "é".repeat(VIDEO_TRANSCRIPT_MAX_CUE_UTF8_BYTES / 2 + 1); + assert.throws( + () => normalizeVideoTranscript({ cues: [cue({ text: wideText })] }, 10), + /UTF-8/ + ); +}); + +test("rejects a transcript whose combined UTF-8 text exceeds the total budget", () => { + const perCueBytes = 1024; + const cueCount = Math.ceil(VIDEO_TRANSCRIPT_MAX_TOTAL_UTF8_BYTES / perCueBytes) + 1; + const cues = Array.from({ length: cueCount }, (_unused, index) => + cue({ text: "b".repeat(perCueBytes), start: index, end: index + 0.5 }) + ); + assert.throws( + () => normalizeVideoTranscript({ cues }, cueCount + 1), + /total.*UTF-8|maximum total/i + ); +}); + +// --------------------------------------------------------------------------- +// Malformed Unicode +// --------------------------------------------------------------------------- + +test("rejects cue text containing an unpaired surrogate", () => { + assert.throws( + () => normalizeVideoTranscript({ cues: [cue({ text: "abc\uD800def" })] }, 10), + /encoding/i + ); + assert.throws( + () => normalizeVideoTranscript({ cues: [cue({ text: "abc\uDC00def" })] }, 10), + /encoding/i + ); +}); + +test("accepts well-formed surrogate pairs (astral text)", () => { + const cues = normalizeVideoTranscript({ cues: [cue({ text: "hello \u{1F600}" })] }, 10); + assert.equal(cues[0]?.text, "hello \u{1F600}"); +}); + +// --------------------------------------------------------------------------- +// Provenance forgery (structural trust boundary) +// --------------------------------------------------------------------------- + +test("a forged embedded source from request-body JSON is reclassified to client", () => { + const cues = normalizeVideoTranscript({ cues: [cue({ source: "embedded" })] }, 10); + assert.equal(cues[0]?.source, "client"); +}); + +test("a forged audio-bridge source from request-body JSON is reclassified to client", () => { + const cues = normalizeVideoTranscript({ cues: [cue({ source: "audio-bridge" })] }, 10); + assert.equal(cues[0]?.source, "client"); +}); + +test("the trustedSource option is a code-only seam that overrides any caller-declared source", () => { + const embeddedCues = normalizeVideoTranscript( + { cues: [cue({ source: "client" })] }, + 10, + { trustedSource: "embedded" } + ); + assert.equal(embeddedCues[0]?.source, "embedded"); + + const audioBridgeCues = normalizeVideoTranscript( + { cues: [cue({ source: "unknown-junk" })] }, + 10, + { trustedSource: "audio-bridge" } + ); + assert.equal(audioBridgeCues[0]?.source, "audio-bridge"); +}); + +// --------------------------------------------------------------------------- +// Focus scoping +// --------------------------------------------------------------------------- + +test("normalizeVideoTranscript scopes cues to the focus window, dropping non-overlapping cues", () => { + const cues = normalizeVideoTranscript( + { + cues: [ + cue({ text: "before", start: 0, end: 1 }), + cue({ text: "inside", start: 4, end: 6 }), + cue({ text: "spanning", start: 7, end: 12 }), + cue({ text: "after", start: 20, end: 21 }), + ], + }, + 30, + { focusWindow: { startSeconds: 3, endSeconds: 10 } } + ); + + assert.deepEqual( + cues.map((entry) => entry.text), + ["inside", "spanning"] + ); + const spanning = cues.find((entry) => entry.text === "spanning"); + assert.equal(spanning?.startSeconds, 7); + assert.equal(spanning?.endSeconds, 10, "clipped to the focus window end"); +}); + +test("describeVideoPart scopes transcript cues to the effective focus window end-to-end", async () => { + const frames: VideoCaptionFrame[] = [ + { dataUri: "data:image/jpeg;base64,AA==", timestampSeconds: 5 }, + ]; + const described = await describeVideoPart( + { + container: "messages", + messageIndex: 0, + partIndex: 0, + ref: "data:video/mp4;base64,AA==", + shape: "data_uri_string", + transcript: { + cues: [ + { text: "outside focus", start: 0, end: 1, source: "client" }, + { text: "inside focus", start: 4, end: 6, source: "client" }, + ], + }, + }, + { frameCount: 1, focusWindow: { startSeconds: 3, endSeconds: 10 }, timeoutMs: 1000 }, + async () => "a scene", + { extractFrames: async () => ({ durationSeconds: 20, frames }) } + ); + + assert.deepEqual( + described.transcriptCues?.map((entry) => entry.text), + ["inside focus"] + ); + assert.doesNotMatch(described.description, /outside focus/); +}); + +// --------------------------------------------------------------------------- +// Cross-source / within-call reconciliation +// --------------------------------------------------------------------------- + +test("reconciles overlapping same-text cues within a single normalizeVideoTranscript call", () => { + const cues = normalizeVideoTranscript( + { + cues: [ + cue({ text: "same words", start: 1, end: 3, confidence: 0.4 }), + cue({ text: "same words", start: 2, end: 4, confidence: 0.9 }), + ], + }, + 10 + ); + + assert.equal(cues.length, 1); + assert.equal(cues[0]?.startSeconds, 1); + assert.equal(cues[0]?.endSeconds, 4); + assert.equal(cues[0]?.confidence, 0.9, "keeps the higher-confidence reading"); + assert.equal(cues[0]?.contributingSources, undefined, "single-source merges add no metadata"); +}); + +test("non-overlapping cues with identical text are kept distinct", () => { + const cues = normalizeVideoTranscript( + { + cues: [ + cue({ text: "repeated line", start: 1, end: 2 }), + cue({ text: "repeated line", start: 8, end: 9 }), + ], + }, + 10 + ); + assert.equal(cues.length, 2); +}); diff --git a/tests/unit/guardrails/videoBridgeTranscriptCacheIdentity.test.ts b/tests/unit/guardrails/videoBridgeTranscriptCacheIdentity.test.ts new file mode 100644 index 000000000000..7ec1235017f4 --- /dev/null +++ b/tests/unit/guardrails/videoBridgeTranscriptCacheIdentity.test.ts @@ -0,0 +1,145 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { VideoBridgeGuardrail } from "../../../src/lib/guardrails/videoBridge.ts"; +import type { BridgeCacheEntry } from "../../../src/lib/guardrails/modalityBridge/bridgeCache.ts"; + +// FU-05 (#11652): "the normalized transcript contract must be part of cache +// identity ... cache hits cannot cross transcript identity or provenance +// changes." These tests exercise the third named seam alongside +// normalizeVideoTranscript/describeVideoPart: the guardrail's result-cache +// key, which already folds in a fingerprint of the raw transcript/ +// audioTranscript payload (videoBridge.ts::buildVideoResultCacheKey). + +function payloadWithTranscript(transcript: unknown): Record { + return { + model: "example/text-only", + messages: [ + { + role: "user", + content: [ + { + type: "input_video", + video_url: "data:video/mp4;base64,VFJBTlNDUklQVC1DQUNIRQ==", + transcript, + }, + ], + }, + ], + }; +} + +function makeBridge(onDescribe: () => Promise<{ description: string; durationSeconds: number; framesRequested: number; framesUsed: number }>) { + return new VideoBridgeGuardrail({ + deps: { + getSettings: async () => ({ + modalityBridgeCacheEnabled: true, + modalityBridgeCacheMaxEntries: 11, + modalityBridgeCacheTtlMinutes: 5, + modalityBridgeVideoEnabled: true, + modalityBridgeVideoModel: "openai/gpt-4o-mini", + modalityBridgeVisionPrompt: "FU-05 transcript cache identity", + }), + getCapabilities: () => ({ supportsVideo: false }), + selectVisionModel: async () => "openai/gpt-4o-mini", + describePart: onDescribe, + }, + }); +} + +test("a cache hit does not cross a transcript provenance change on an otherwise identical video", async () => { + let describeCalls = 0; + const bridge = makeBridge(async () => { + describeCalls += 1; + return { + description: "[Video description: cache identity probe]", + durationSeconds: 5, + framesRequested: 1, + framesUsed: 1, + }; + }); + + const clientDeclared = { + cues: [{ text: "same words", start: 1, end: 2, source: "client" }], + }; + const audioBridgeDeclared = { + cues: [{ text: "same words", start: 1, end: 2, source: "audio-bridge" }], + }; + + await bridge.preCall(payloadWithTranscript(clientDeclared), {}); + await bridge.preCall(payloadWithTranscript(clientDeclared), {}); + await bridge.preCall(payloadWithTranscript(audioBridgeDeclared), {}); + + assert.equal( + describeCalls, + 2, + "the repeated identical (video, transcript) pair must reuse the cached result, " + + "but the raw provenance change must force a miss even though both sources are " + + "reclassified to client after normalization" + ); +}); + +test("a cache hit does not cross a transcript identity change (different cues, same video)", async () => { + let describeCalls = 0; + const bridge = makeBridge(async () => { + describeCalls += 1; + return { + description: "[Video description: cache identity probe]", + durationSeconds: 5, + framesRequested: 1, + framesUsed: 1, + }; + }); + + await bridge.preCall( + payloadWithTranscript({ cues: [{ text: "first cue", start: 1, end: 2, source: "client" }] }), + {} + ); + await bridge.preCall( + payloadWithTranscript({ cues: [{ text: "second cue", start: 1, end: 2, source: "client" }] }), + {} + ); + + assert.equal(describeCalls, 2, "different transcript content must never share a cache entry"); +}); + +test("the result-cache contract version was bumped for the FU-05 normalization change", async () => { + let storedMetadata: Record | undefined; + const bridge = new VideoBridgeGuardrail({ + deps: { + getSettings: async () => ({ + modalityBridgeCacheEnabled: true, + modalityBridgeVideoEnabled: true, + modalityBridgeVideoModel: "openai/gpt-4o-mini", + modalityBridgeVisionPrompt: "FU-05 cache version", + }), + getCapabilities: () => ({ supportsVideo: false }), + selectVisionModel: async () => "openai/gpt-4o-mini", + resultCache: { + delete: () => undefined, + getEntry: () => undefined, + setEntry: (_key: string, entry: BridgeCacheEntry) => { + storedMetadata = entry.metadata as Record; + }, + }, + describePart: async () => ({ + description: "[Video description: version probe]", + durationSeconds: 1, + framesRequested: 1, + framesUsed: 1, + }), + }, + }); + + await bridge.preCall( + payloadWithTranscript({ cues: [{ text: "cue", start: 0, end: 1, source: "client" }] }), + {} + ); + + assert.ok(storedMetadata); + assert.notEqual( + storedMetadata?.cacheVersion, + "v4", + "a cache entry computed under the pre-FU-05 normalization contract must never match" + ); +}); diff --git a/tests/unit/guardrails/videoBridgeTranscriptProvenance.test.ts b/tests/unit/guardrails/videoBridgeTranscriptProvenance.test.ts index 22425ef26a78..82d274bb2ae3 100644 --- a/tests/unit/guardrails/videoBridgeTranscriptProvenance.test.ts +++ b/tests/unit/guardrails/videoBridgeTranscriptProvenance.test.ts @@ -18,7 +18,14 @@ async function jpegFrame(color: string, timestampSeconds: number): Promise