diff --git a/.env.example b/.env.example index e9b64d0ee6..0c06e4767d 100644 --- a/.env.example +++ b/.env.example @@ -3023,3 +3023,11 @@ QUOTA_STORE_DRIVER=sqlite # corpus-aware retrieval. Higher values keep more index entries hot. # Used by: src/lib/localCorpus/configured.ts # OMNIROUTE_CORPUS_CACHE_SIZE=5 + +# Service-worker cache-busting id for the PWA shell (#11779). NEXT_PUBLIC_SW_BUILD_ID is +# derived at build time from OMNIROUTE_SW_BUILD_ID, then SOURCE_VERSION (set by some PaaS +# builders), then the git SHA — override only when the build cannot see git. Used by: +# next.config.mjs, scripts/build/assembleStandalone.mjs, src/shared/components/PwaRegister.tsx. +#OMNIROUTE_SW_BUILD_ID=2026-08-28T12-00-00 +#SOURCE_VERSION=abcdef0123456789 +#NEXT_PUBLIC_SW_BUILD_ID=abcdef0123456789 diff --git a/changelog.d/fixes/v3851-sweep-reds.md b/changelog.d/fixes/v3851-sweep-reds.md new file mode 100644 index 0000000000..9ed3e3388c --- /dev/null +++ b/changelog.d/fixes/v3851-sweep-reds.md @@ -0,0 +1,7 @@ +- Drained the reds every PR against `release/v3.8.51` was born with: documented the + three service-worker build-id variables, registered the six retirement/tunnel tests + with the mutation gate, approved `eslint-plugin-react-hooks` in the dependency + allowlist, added the six `combo.sort.*` strings to `vi` and `pt-BR`, pointed the + ChatGPT Web doc at the real migration-168 test, worded the g4f hint around the + member key, and realigned four tests to the retired-provider catalog and the + legacy-schema fixtures the retirement migrations touch. diff --git a/config/quality/dependency-allowlist.json b/config/quality/dependency-allowlist.json index 2e794ef503..1a6097e13c 100644 --- a/config/quality/dependency-allowlist.json +++ b/config/quality/dependency-allowlist.json @@ -2,7 +2,8 @@ "_comment": "Allowlist anti-slopsquatting (check-deps.mjs). Toda dep nova exige adicao EXPLICITA aqui apos verificar que e legitima.", "_justifications": { "@testing-library/dom": "Peer dep obrigatoria de @testing-library/react v16 (adicionada no PR #11224); Refs #9985.", - "@testing-library/user-event": "Utilitario oficial do ecossistema testing-library para testes de UI (adicionada no PR #11224); Refs #9985." + "@testing-library/user-event": "Utilitario oficial do ecossistema testing-library para testes de UI (adicionada no PR #11224); Refs #9985.", + "eslint-plugin-react-hooks": "React Hooks lint rules (set-state-in-effect, immutability, refs, purity) pinned at 7.0.1 by the release/v3.8.51 cycle; the 224 findings it raised are tracked in #11924. Refs #11924." }, "allowed": [ "@atjsh/llmlingua-2", @@ -48,8 +49,8 @@ "clsx", "commander", "concurrently", - "cross-env", "cron-parser", + "cross-env", "csv-stringify", "ctrf", "dompurify", @@ -60,6 +61,7 @@ "esbuild", "eslint", "eslint-config-next", + "eslint-plugin-react-hooks", "eslint-plugin-sonarjs", "express", "fast-check", @@ -102,9 +104,9 @@ "node-loader", "node-machine-id", "omniglyph", + "onnxruntime-node", "open", "opencode-ai", - "onnxruntime-node", "ora", "parse5", "pino", @@ -131,10 +133,10 @@ "tailwind-merge", "tailwindcss", "tls-client-node", - "turndown", - "turndown-plugin-gfm", "tsup", "tsx", + "turndown", + "turndown-plugin-gfm", "type-coverage", "typescript", "typescript-eslint", diff --git a/docs/providers/CHATGPT_WEB.md b/docs/providers/CHATGPT_WEB.md index 2256b92660..52cb53fbbb 100644 --- a/docs/providers/CHATGPT_WEB.md +++ b/docs/providers/CHATGPT_WEB.md @@ -130,4 +130,4 @@ Retirement regression guards live in: - `tests/unit/chatgpt-web-runtime-block.test.ts` - `tests/unit/chatgpt-web-image-handler-retirement.test.ts` - `tests/unit/chatgpt-web-source-retirement.test.ts` -- `tests/unit/migration-163-retire-chatgpt-web.test.ts` +- `tests/unit/migration-168-retire-chatgpt-web.test.ts` diff --git a/docs/reference/ENVIRONMENT.md b/docs/reference/ENVIRONMENT.md index b992b7a2b4..7e48aa9074 100644 --- a/docs/reference/ENVIRONMENT.md +++ b/docs/reference/ENVIRONMENT.md @@ -59,6 +59,9 @@ These **must** be set before the first run. Without them, the application will e | `API_KEY_SECRET` | **Yes** | _(none)_ | `src/lib/db/apiKeys.ts` | AES encryption key for API key values at rest in SQLite. Generate with `openssl rand -hex 32`. | | `INITIAL_PASSWORD` | **Yes** | `CHANGEME` | Bootstrap script | Sets the initial admin dashboard password (matches `.env.example` default — kept obviously insecure to force a change). **Change before first use.** After login, change via Dashboard → Settings → Security. | | `OMNIROUTE_WS_BRIDGE_SECRET` | **Yes** (production) | _(unset)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Shared secret for the internal Codex Responses WebSocket bridge. Authenticates bridge requests between the Electron/browser WS relay and OmniRoute. ⚠️ **REQUIRED in production — when unset, all WS bridge requests are rejected.** Generate with `openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | No | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Explicit service-worker cache-busting id for the PWA shell (#11779); first in the resolution chain. | +| `SOURCE_VERSION` | No | _(unset)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Second in the chain — set by PaaS builders (e.g. Heroku-style) as the deployed commit. | +| `NEXT_PUBLIC_SW_BUILD_ID` | No | _(derived)_ | `src/shared/components/PwaRegister.tsx` | Build-time public value the client uses to register `/sw.js?v=…`; derived from the two above, then the git SHA. | | `OMNIROUTE_PEER_STAMP_TOKEN` | No (auto) | _(auto per boot)_ | `src/server/authz/policies/management.ts` | Per-process secret proving the trusted peer-IP stamp came from OmniRoute's own HTTP server (`scripts/dev/peer-stamp.mjs`). The authz middleware trusts request locality (loopback/LAN gating of LOCAL_ONLY routes) only when the stamp carries this token. Auto-generated each boot — leave unset; only pin it for multi-process setups that must share the stamp. | ### Generation Commands diff --git a/src/app/(dashboard)/dashboard/combos/page.tsx b/src/app/(dashboard)/dashboard/combos/page.tsx index e4c8818a57..ecc001db33 100644 --- a/src/app/(dashboard)/dashboard/combos/page.tsx +++ b/src/app/(dashboard)/dashboard/combos/page.tsx @@ -2037,7 +2037,9 @@ function ComboFormModal({ isOpen, combo, onClose, onSave, activeProviders, combo const [showAdvanced, setShowAdvanced] = useState(false); const [config, setConfig] = useState(sanitizeComboRuntimeConfig(combo?.config)); // Validate persisted enum; ensure reset on combo change not just first mount. - const initialSortMethod = normalizeSortMethod(config.modelSort?.method); + const initialSortMethod = normalizeSortMethod( + (config.modelSort as { method?: unknown } | undefined)?.method + ); const [sortMethod, setSortMethod] = useState(initialSortMethod); useEffect(() => { // Sync point: when the combo identity changes, re-derive sort method. @@ -2733,13 +2735,15 @@ function ComboFormModal({ isOpen, combo, onClose, onSave, activeProviders, combo const rankings = await fetchProviderRankings(); // Functional note: `next` is the post-batch snapshot. Concurrent single-add // racing this batch is low-probability single-user; last write wins. - const sorted = await sortComboStepsByScore(next, rankings); - setModels(sorted); + const sorted = await sortComboStepsByScore(next as ComboStep[], rankings); + setModels(sorted as typeof next); } catch { setModels(next); } } else { - setModels(sortComboStepsSync(next, currentMethod as "provider" | "name")); + setModels( + sortComboStepsSync(next as ComboStep[], currentMethod as "provider" | "name") as typeof next + ); } setBuilderError(""); }; @@ -3642,7 +3646,11 @@ function ComboFormModal({ isOpen, combo, onClose, onSave, activeProviders, combo
- + getI18nOrFallback(t, k, f)} + />
{models.length === 0 ? ( diff --git a/src/i18n/messages/pt-BR.json b/src/i18n/messages/pt-BR.json index f514c31af2..5045da054d 100644 --- a/src/i18n/messages/pt-BR.json +++ b/src/i18n/messages/pt-BR.json @@ -7679,7 +7679,8 @@ "backupCleanupSuccess": "Excluído(s) {backups} conjunto(s) de backup e {files} arquivo(s).", "backupCleanupFailed": "Falha ao limpar backups do banco de dados", "purgeQuotaSnapshotsSuccess": "{count} snapshots de cota removidos", - "purgeQuotaSnapshotsFailed": "Falha ao remover snapshots de cota", "purgeCallLogsSuccess": "{count} logs de chamadas removidos", + "purgeQuotaSnapshotsFailed": "Falha ao remover snapshots de cota", + "purgeCallLogsSuccess": "{count} logs de chamadas removidos", "purgeCallLogsFailed": "Falha ao remover logs de chamadas", "purgeDetailedLogsSuccess": "{count} logs detalhados removidos", "purgeDetailedLogsFailed": "Falha ao remover logs detalhados", @@ -7689,7 +7690,8 @@ "invalidJsonFileType": "Tipo de arquivo inválido. Apenas arquivos .json são permitidos.", "legacyJsonImportSuccess": "JSON legado importado com sucesso!", "jsonImportFailed": "Falha ao importar JSON", - "jsonImportError": "Erro durante a importação de JSON", "storagePurgeData": "Limpar dados", + "jsonImportError": "Erro durante a importação de JSON", + "storagePurgeData": "Limpar dados", "storagePurgeDataDesc": "Excluir imediatamente todos os registros sem aplicar verificações de retenção. Use com cautela.", "storageRetentionCleanup": "Configurações de Retenção", "storageRetentionCleanupDesc": "Configure a retenção de registros operacionais e a limpeza de backup do banco de dados.", @@ -13937,5 +13939,17 @@ "cta": "Obter uma chave de API", "partnerLinkNote": "Link de parceiro", "dismissAriaLabel": "Dispensar" + }, + "combo": { + "sort": { + "label": "Ordenar por", + "method": { + "manual": "Manual", + "provider": "Provedor", + "score": "Pontuação (modelos gratuitos)", + "name": "Nome" + }, + "scoreHint": "A ordenação por pontuação vale só para provedores gratuitos; os demais ficam onde estão." + } } } diff --git a/src/i18n/messages/vi.json b/src/i18n/messages/vi.json index 1ae8cbb638..4f211194b9 100644 --- a/src/i18n/messages/vi.json +++ b/src/i18n/messages/vi.json @@ -13939,5 +13939,17 @@ "cta": "Lấy khóa API", "partnerLinkNote": "Liên kết đối tác", "dismissAriaLabel": "Đóng" + }, + "combo": { + "sort": { + "label": "Sắp xếp theo", + "method": { + "manual": "Thủ công", + "provider": "Nhà cung cấp", + "score": "Điểm (mô hình miễn phí)", + "name": "Tên" + }, + "scoreHint": "Xếp hạng theo điểm chỉ áp dụng cho nhà cung cấp miễn phí; các nhà cung cấp khác giữ nguyên vị trí." + } } } diff --git a/src/shared/constants/providers/apikey/gateways.ts b/src/shared/constants/providers/apikey/gateways.ts index c38c20ee7a..06b0a23f2e 100644 --- a/src/shared/constants/providers/apikey/gateways.ts +++ b/src/shared/constants/providers/apikey/gateways.ts @@ -695,7 +695,7 @@ export const APIKEY_PROVIDERS_GATEWAYS = { "Anonymous access to Groq requires proof-of-work cake credits from g4f.dev/chat; alternatively, use a g4f.dev member API key. Limits vary.", passthroughModels: true, authHint: - "Bake anonymous cake credits at g4f.dev/chat, or create a member API key at g4f.dev/members.html.", + "Bake anonymous cake credits at g4f.dev/chat, or use a g4f.dev member key (create one at g4f.dev/members.html).", notice: { text: "Remote third-party gateway: prompts and request metadata leave OmniRoute and are handled by g4f.space. Its Terms and Privacy links were unavailable when last verified on 2026-08-27.", apiKeyUrl: "https://g4f.dev/members.html", @@ -714,7 +714,7 @@ export const APIKEY_PROVIDERS_GATEWAYS = { "Anonymous access to Gemini requires proof-of-work cake credits from g4f.dev/chat; alternatively, use a g4f.dev member API key. Limits vary.", passthroughModels: true, authHint: - "Bake anonymous cake credits at g4f.dev/chat, or create a member API key at g4f.dev/members.html.", + "Bake anonymous cake credits at g4f.dev/chat, or use a g4f.dev member key (create one at g4f.dev/members.html).", notice: { text: "Remote third-party gateway: prompts and request metadata leave OmniRoute and are handled by g4f.space. Its Terms and Privacy links were unavailable when last verified on 2026-08-27.", apiKeyUrl: "https://g4f.dev/members.html", @@ -733,7 +733,7 @@ export const APIKEY_PROVIDERS_GATEWAYS = { "Anonymous access to Pollinations requires proof-of-work cake credits from g4f.dev/chat; alternatively, use a g4f.dev member API key. Limits vary.", passthroughModels: true, authHint: - "Bake anonymous cake credits at g4f.dev/chat, or create a member API key at g4f.dev/members.html.", + "Bake anonymous cake credits at g4f.dev/chat, or use a g4f.dev member key (create one at g4f.dev/members.html).", notice: { text: "Remote third-party gateway: prompts and request metadata leave OmniRoute and are handled by g4f.space. Its Terms and Privacy links were unavailable when last verified on 2026-08-27.", apiKeyUrl: "https://g4f.dev/members.html", @@ -752,7 +752,7 @@ export const APIKEY_PROVIDERS_GATEWAYS = { "Anonymous access to hosted Ollama requires proof-of-work cake credits from g4f.dev/chat; alternatively, use a g4f.dev member API key. Limits vary.", passthroughModels: true, authHint: - "Bake anonymous cake credits at g4f.dev/chat, or create a member API key at g4f.dev/members.html.", + "Bake anonymous cake credits at g4f.dev/chat, or use a g4f.dev member key (create one at g4f.dev/members.html).", notice: { text: "Remote third-party gateway: prompts and request metadata leave OmniRoute and are handled by g4f.space. Its Terms and Privacy links were unavailable when last verified on 2026-08-27.", apiKeyUrl: "https://g4f.dev/members.html", @@ -771,7 +771,7 @@ export const APIKEY_PROVIDERS_GATEWAYS = { "Anonymous access to NVIDIA NIM requires proof-of-work cake credits from g4f.dev/chat; alternatively, use a g4f.dev member API key. Limits vary.", passthroughModels: true, authHint: - "Bake anonymous cake credits at g4f.dev/chat, or create a member API key at g4f.dev/members.html.", + "Bake anonymous cake credits at g4f.dev/chat, or use a g4f.dev member key (create one at g4f.dev/members.html).", notice: { text: "Remote third-party gateway: prompts and request metadata leave OmniRoute and are handled by g4f.space. Its Terms and Privacy links were unavailable when last verified on 2026-08-27.", apiKeyUrl: "https://g4f.dev/members.html", diff --git a/stryker.conf.json b/stryker.conf.json index 79a2db685b..6486c81f4c 100644 --- a/stryker.conf.json +++ b/stryker.conf.json @@ -1,14 +1,14 @@ { "$schema": "https://stryker-mutator.io/schemas/stryker-schema.json", "_comment": [ - "Mutation testing for the ~8 critical modules (Task 11 \u2014 Fase 7).", - "NIGHTLY ONLY \u2014 DO NOT run on every PR. Mutation testing is expensive:", + "Mutation testing for the ~8 critical modules (Task 11 — Fase 7).", + "NIGHTLY ONLY — DO NOT run on every PR. Mutation testing is expensive:", " - Each mutant requires a full test suite execution.", - " - The 8 modules produce ~200\u2013500 mutants; est. 30\u201390 min per run.", + " - The 8 modules produce ~200–500 mutants; est. 30–90 min per run.", " - Wired to the nightly CI workflow (.github/workflows/nightly-mutation.yml),", " NOT to the 'lint' / 'quality-gate' PR jobs.", "", - "TEST RUNNER \u2014 @stryker-mutator/tap-runner (NOT vitest):", + "TEST RUNNER — @stryker-mutator/tap-runner (NOT vitest):", " The 8 critical modules are covered by node:test files in tests/unit/", " (run via `node --import tsx --test`), NOT by vitest. The vitest config", " only includes a small set of .test.tsx + open-sse/**/__tests__ files, so", @@ -22,24 +22,26 @@ " npm install --save-dev @stryker-mutator/core @stryker-mutator/tap-runner", "", "Run manually:", - " npm run test:mutation # full run (slow \u2014 nightly budget)", + " npm run test:mutation # full run (slow — nightly budget)", " npx stryker run --dryRunOnly # validate the baseline only (no mutants)", " (single-module probe: temporarily narrow `mutate` + `tap.testFiles` in this file)", "", - "VALIDATED 2026-06-15: `npx stryker run --dryRunOnly` exits 0 \u2014 all 129 covering", + "VALIDATED 2026-06-15: `npx stryker run --dryRunOnly` exits 0 — all 129 covering", "test files run green in the Stryker sandbox and the perTest coverage map builds for", "all 8 instrumented modules (15k+ mutants). The baseline dry-run takes ~20 min with", "concurrency=1; the full mutation phase runs on top (advisory, capped by the workflow", "timeout). So the nightly produces REAL mutation scores for the 8 modules.", "", - "Mutation score per module \u2192 quality-baseline.json key 'mutationScore.'", - "Direction: up (score can only improve; ratchet blocks drops \u2014 wired in a later INT phase)." + "Mutation score per module → quality-baseline.json key 'mutationScore.'", + "Direction: up (score can only improve; ratchet blocks drops — wired in a later INT phase)." ], "packageManager": "npm", "incremental": true, "incrementalFile": "reports/mutation/stryker-incremental.json", "testRunner": "tap", - "plugins": ["@stryker-mutator/tap-runner"], + "plugins": [ + "@stryker-mutator/tap-runner" + ], "tap": { "testFiles": [ "tests/unit/10085-compatible-generic-vs-uuid-credential.test.ts", @@ -389,7 +391,13 @@ "tests/unit/vertex-passthrough-model-lockout.test.ts", "tests/unit/video-bridge-drilldown-route.test.ts", "tests/unit/video-bridge-route-security.test.ts", - "tests/unit/xai-agent-tools-passthrough.test.ts" + "tests/unit/xai-agent-tools-passthrough.test.ts", + "tests/unit/combo/connection-aware-expansion.test.ts", + "tests/unit/chatgpt-web-runtime-block.test.ts", + "tests/unit/felo-web-runtime-block.test.ts", + "tests/unit/microsoft-designer-web-runtime-block.test.ts", + "tests/unit/qwen-web-runtime-block.test.ts", + "tests/unit/tunnel-routes-error-sanitization.test.ts" ], "nodeArgs": [ "--import", @@ -405,7 +413,7 @@ ] }, "_mutate_godfiles_excluded_comment": [ - "2026-06-18 (Onda 2 budget): chatCore.ts + combo.ts \u2014 the two god-files \u2014 were REMOVED", + "2026-06-18 (Onda 2 budget): chatCore.ts + combo.ts — the two god-files — were REMOVED", "from `mutate`. They dominated ~2/3 of the ~15k mutants; the full 8-module run TIMED OUT", "at the 180min nightly cap (run 27705123780: 16:47:33 -> killed 19:47:48 = exactly 180min;", "the prior 120min scheduled run also timed out). #4078 made concurrency safe but the", @@ -425,23 +433,23 @@ "comboContextCache/idempotency/passthroughHelpers/responseHeaders/sanitization/upstreamTimeouts).", "A follow-up then added DEDICATED unit tests for 6 more leaves (tests/unit/chatcore-headers,", "-log-truncation, -memory-extraction, -non-streaming-sse, -passthrough-tool-names,", - "-executor-helpers \u2014 wired into tap.testFiles above) and added those leaves as batch h", + "-executor-helpers — wired into tap.testFiles above) and added those leaves as batch h", "(headers/logTruncation/memoryExtraction/nonStreamingSse/passthroughToolNames/executorHelpers).", - "A later follow-up added dedicated tests (NO mock.module \u2014 unavailable under the tap-runner; used", + "A later follow-up added dedicated tests (NO mock.module — unavailable under the tap-runner; used", "fetch-override + crafted inputs + temp-DATA_DIR) for telemetryHelpers (both fns, all branches) and", "memorySkillsInjection (getSkillsProviderForFormat fully + injectMemoryAndSkills guards/empty-DB", "path) and added them as batch i.", "", "The FINAL chatCore leaf, semanticCache.ts, was added to batch i once its cache-HIT block had a", "fixture: chatcore-semantic-cache now SEEDS the real cache via setCachedResponse (the in-memory", - "store getCachedResponse checks first \u2014 no mock.module needed) under the exact signature", + "store getCachedResponse checks first — no mock.module needed) under the exact signature", "checkSemanticCache rebuilds, so the HIT branch runs end-to-end (status 200 / 'semantic' / 'HIT' /", "the stream + content-type ternaries / the cost fallback / the side-effect calls all get killed).", "ALL 15 chatCore leaves are now mutated.", "", "STILL EXCLUDED (follow-ups, NOT in `mutate` yet):", " - combo.ts + chatCore.ts barrels: their handleComboChat/handleChatCore CORES were not", - " split (out of scope \u2014 Fase 3 ChatCoreContext refactor). The barrels are now thin-ish", + " split (out of scope — Fase 3 ChatCoreContext refactor). The barrels are now thin-ish", " but still large; keep excluded until the cores are split.", "See project memory: Quality Gate v2 / Fase 9 (project-combo-split)." ], @@ -498,7 +506,11 @@ ".worktrees", ".stryker-tmp" ], - "reporters": ["progress", "html", "json"], + "reporters": [ + "progress", + "html", + "json" + ], "htmlReporter": { "fileName": "reports/mutation/mutation.html" }, @@ -525,11 +537,11 @@ "would break the required all-green baseline dry-run (e.g. body-timeout-integration,", "heap-pressure, sse-heartbeat-integration, *-stream-readiness, chatcore-memory-pressure).", "It is enumerated (not a broad glob) so the Stryker dry-run stays tractable for the", - "nightly budget \u2014 a glob over the full ~1300-file unit suite would make the per-test", + "nightly budget — a glob over the full ~1300-file unit suite would make the per-test", "dry-run take hours. coverageAnalysis:perTest then narrows which files run per mutant.", "Regenerate the base union after adding/renaming covering tests, then re-prune flaky ones:", " grep -rlE \"circuitBreaker|publicCreds|accountFallback|routeGuard|services/auth|chatCore|services/combo|utils/error|public-client|account-fallback|route-guard|circuit-breaker\" tests/unit --include=\"*.test.ts\" | sort -u" ], "dryRunTimeoutMinutes": 30, - "_concurrency_comment": "concurrency=4 (was 1): the covering node:test files used to share SQLite/module state via the default DATA_DIR (~/.omniroute), so running them concurrently in the Stryker sandbox caused cross-file races that failed the all-green baseline. tap.nodeArgs now imports ./tests/_setup/isolateDataDir.ts, which gives each spawned test process its own temp DATA_DIR \u2014 eliminating the shared on-disk DB, so concurrency>1 is deterministic. A/B verified 2026-06-17: dry-run at concurrency=4 fails WITHOUT the isolation import (account-fallback-service tap exit 9) and passes WITH it. Raise further only if the runner has spare cores." + "_concurrency_comment": "concurrency=4 (was 1): the covering node:test files used to share SQLite/module state via the default DATA_DIR (~/.omniroute), so running them concurrently in the Stryker sandbox caused cross-file races that failed the all-green baseline. tap.nodeArgs now imports ./tests/_setup/isolateDataDir.ts, which gives each spawned test process its own temp DATA_DIR — eliminating the shared on-disk DB, so concurrency>1 is deterministic. A/B verified 2026-06-17: dry-run at concurrency=4 fails WITHOUT the isolation import (account-fallback-service tap exit 9) and passes WITH it. Raise further only if the runner has spare cores." } diff --git a/tests/unit/db-core-init.test.ts b/tests/unit/db-core-init.test.ts index 15ab1b576b..e93736e2af 100644 --- a/tests/unit/db-core-init.test.ts +++ b/tests/unit/db-core-init.test.ts @@ -223,6 +223,12 @@ function createRecoverableDb(sqliteFile) { auth_type TEXT, name TEXT, is_active INTEGER DEFAULT 1, + test_status TEXT, + error_code TEXT, + last_error TEXT, + last_error_at TEXT, + last_error_type TEXT, + last_error_source TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); @@ -659,6 +665,12 @@ test( name TEXT, priority INTEGER DEFAULT 0, is_active INTEGER DEFAULT 1, + test_status TEXT, + error_code TEXT, + last_error TEXT, + last_error_at TEXT, + last_error_type TEXT, + last_error_source TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); diff --git a/tests/unit/no-js-extension-on-repo-imports-10674.test.ts b/tests/unit/no-js-extension-on-repo-imports-10674.test.ts index 8c2bab9ef5..f7a736f6a7 100644 --- a/tests/unit/no-js-extension-on-repo-imports-10674.test.ts +++ b/tests/unit/no-js-extension-on-repo-imports-10674.test.ts @@ -1,6 +1,7 @@ import assert from "node:assert/strict"; import { test } from "node:test"; import { execFileSync } from "node:child_process"; +import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; @@ -30,7 +31,19 @@ function relativeJsImports(): string[] { if ((err as { status?: number }).status === 1) return []; throw err; } - return out.split("\n").filter((line) => line.trim().length > 0); + return ( + out + .split("\n") + .filter((line) => line.trim().length > 0) + // A `.js` specifier whose target really is a JavaScript file (e.g. + // open-sse/lib/deepseek-pow-hash.js, shared with a worker) is correct — + // the defect #10674 guards against is a `.js` suffix on a `.ts` source. + .filter((line) => { + const m = /^([^:]+):\d+:.*from "(\.{1,2}\/[^"]*\.js)"/.exec(line); + if (!m) return true; + return !fs.existsSync(path.resolve(REPO_ROOT, path.dirname(m[1]), m[2])); + }) + ); } test("no first-party TypeScript module is imported through a .js specifier", () => { diff --git a/tests/unit/translator/openai-to-claude-trailing-usage.test.ts b/tests/unit/openai-to-claude-trailing-usage-deferred-finish.test.ts similarity index 100% rename from tests/unit/translator/openai-to-claude-trailing-usage.test.ts rename to tests/unit/openai-to-claude-trailing-usage-deferred-finish.test.ts diff --git a/tests/unit/token-health-check-webcookie.test.ts b/tests/unit/token-health-check-webcookie.test.ts index 5f29081e8b..2c25919a18 100644 --- a/tests/unit/token-health-check-webcookie.test.ts +++ b/tests/unit/token-health-check-webcookie.test.ts @@ -29,7 +29,7 @@ function baseParams(over: Partial = {}): ProbeParams { describe("web-cookie health probe (#11488)", () => { it("candidate detection matches catalogued cookie providers only", () => { assert.equal(isWebCookieHealthProbeCandidate("claude-web"), true); - assert.equal(isWebCookieHealthProbeCandidate("chatgpt-web"), true); + assert.equal(isWebCookieHealthProbeCandidate("chatgpt-web-codex"), true); assert.equal(isWebCookieHealthProbeCandidate("openai"), false); assert.equal(isWebCookieHealthProbeCandidate(undefined), false); assert.equal(isWebCookieHealthProbeCandidate(""), false); @@ -210,7 +210,7 @@ describe("web-cookie health probe (#11488)", () => { baseParams({ conn: { id: "c1", - provider: "qwen-web", + provider: "grok-web", apiKey: "", providerSpecificData: { cookie: "token=abc" }, }, diff --git a/tests/unit/web-session-contract.test.ts b/tests/unit/web-session-contract.test.ts index 7c43e441a4..ac44718aa3 100644 --- a/tests/unit/web-session-contract.test.ts +++ b/tests/unit/web-session-contract.test.ts @@ -47,7 +47,7 @@ test("web-session contract preserves representative token and cookie semantics", assert.equal(providers.get("deepseek-web")?.credential.kind, "token"); assert.equal(providers.get("zai-web")?.credential.kind, "token"); assert.equal(providers.get("gemini-web")?.credential.kind, "cookie"); - assert.equal(providers.get("qwen-web")?.credential.kind, "cookie"); + assert.equal(providers.get("perplexity-web")?.credential.kind, "cookie"); assert.ok( providers