mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-29 18:42:17 +03:00
Compare commits
7 Commits
fix/tests-
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cbb05f88c9 | ||
|
|
3b752f9d4c | ||
|
|
757cc3bb9d | ||
|
|
60bbf0f8f0 | ||
|
|
3d4f3e4960 | ||
|
|
751710616a | ||
|
|
1ecd83c6af |
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
@@ -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
|
||||
|
||||
2
.github/workflows/docker-publish.yml
vendored
2
.github/workflows/docker-publish.yml
vendored
@@ -499,7 +499,7 @@ jobs:
|
||||
- name: Upload Trivy SARIF to Security tab
|
||||
if: needs.prepare.outputs.version != 'main'
|
||||
continue-on-error: true
|
||||
uses: github/codeql-action/upload-sarif@v4.37.7
|
||||
uses: github/codeql-action/upload-sarif@v4.37.8
|
||||
with:
|
||||
sarif_file: trivy-results.sarif
|
||||
category: trivy-image
|
||||
|
||||
22
.github/workflows/electron-release.yml
vendored
22
.github/workflows/electron-release.yml
vendored
@@ -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
|
||||
|
||||
10
.github/workflows/nightly-llm-security.yml
vendored
10
.github/workflows/nightly-llm-security.yml
vendored
@@ -10,7 +10,10 @@ permissions:
|
||||
jobs:
|
||||
promptfoo-guard:
|
||||
name: promptfoo — injection guard (block mode, no secret)
|
||||
runs-on: ubuntu-latest
|
||||
# #11965: this job runs a backend-only `next build`; the hosted 7 GB runner cannot build
|
||||
# release/v3.8.51 (VM shutdown ~7 min in), so it targets the box's light pool (`omni-light`:
|
||||
# two listeners, jobs ≤ ~6 GB). Falls back to hosted when USE_VPS_RUNNER is off.
|
||||
runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && fromJSON('["self-hosted","omni-light"]')) || 'ubuntu-latest' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
@@ -46,7 +49,10 @@ jobs:
|
||||
|
||||
garak:
|
||||
name: garak probes (skip without provider secret)
|
||||
runs-on: ubuntu-latest
|
||||
# #11965: this job runs a backend-only `next build`; the hosted 7 GB runner cannot build
|
||||
# release/v3.8.51 (VM shutdown ~7 min in), so it targets the box's light pool (`omni-light`:
|
||||
# two listeners, jobs ≤ ~6 GB). Falls back to hosted when USE_VPS_RUNNER is off.
|
||||
runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && fromJSON('["self-hosted","omni-light"]')) || 'ubuntu-latest' }}
|
||||
# NOTE: the `secrets` context is NOT available in a job-level `if:` — referencing
|
||||
# it there makes GitHub reject the file on push (startup_failure on every push).
|
||||
# Map the secret into a job-level env and gate each step on a presence check, so
|
||||
|
||||
5
.github/workflows/nightly-resilience.yml
vendored
5
.github/workflows/nightly-resilience.yml
vendored
@@ -78,7 +78,10 @@ jobs:
|
||||
|
||||
a11y:
|
||||
name: A11y axe (nightly, freeze-and-alert)
|
||||
runs-on: ubuntu-latest
|
||||
# #11965: this job runs a backend-only `next build`; the hosted 7 GB runner cannot build
|
||||
# release/v3.8.51 (VM shutdown ~7 min in), so it targets the box's light pool (`omni-light`:
|
||||
# two listeners, jobs ≤ ~6 GB). Falls back to hosted when USE_VPS_RUNNER is off.
|
||||
runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && fromJSON('["self-hosted","omni-light"]')) || 'ubuntu-latest' }}
|
||||
# The Playwright webServer (`start` mode) builds Next via build-next-isolated.mjs and
|
||||
# boots the standalone server itself (waits on /api/monitoring/health, 15min webServer
|
||||
# timeout). Unlike the per-PR test-e2e job, this nightly job has no pre-built artifact,
|
||||
|
||||
5
.github/workflows/nightly-schemathesis.yml
vendored
5
.github/workflows/nightly-schemathesis.yml
vendored
@@ -10,7 +10,10 @@ permissions:
|
||||
jobs:
|
||||
schemathesis:
|
||||
name: Schemathesis — OpenAPI contract fuzz (advisory)
|
||||
runs-on: ubuntu-latest
|
||||
# #11965: this job runs a backend-only `next build`; the hosted 7 GB runner cannot build
|
||||
# release/v3.8.51 (VM shutdown ~7 min in), so it targets the box's light pool (`omni-light`:
|
||||
# two listeners, jobs ≤ ~6 GB). Falls back to hosted when USE_VPS_RUNNER is off.
|
||||
runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && fromJSON('["self-hosted","omni-light"]')) || 'ubuntu-latest' }}
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
1
changelog.d/fixes/v3850-electron-release-assets.md
Normal file
1
changelog.d/fixes/v3850-electron-release-assets.md
Normal file
@@ -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
|
||||
1
changelog.d/maintenance/11924-type-the-frozen-any.md
Normal file
1
changelog.d/maintenance/11924-type-the-frozen-any.md
Normal file
@@ -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)
|
||||
1
changelog.d/maintenance/11965-nightly-jobs-omni-light.md
Normal file
1
changelog.d/maintenance/11965-nightly-jobs-omni-light.md
Normal file
@@ -0,0 +1 @@
|
||||
- Move the four nightly jobs that build the backend (`nightly-schemathesis`, `nightly-llm-security` promptfoo + garak, `nightly-resilience` axe-a11y) off the hosted 7 GB runner — where they died on `release/v3.8.51` unseen — onto the box's new `omni-light` pool (two listeners), and document the reshaped fleet (4 active OmniRoute listeners: 2 `omni-build` + 2 `omni-light`, janitor ceiling 4) (Closes #11965)
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 | **10 listeners**: 8 OmniRoute + OmniHeuris + OmniMind | all share the memory above |
|
||||
| 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) |
|
||||
|
||||
## 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=8 /opt/omniroute-ops/runner-janitor.sh >> /var/log/runner-janitor.log 2>&1
|
||||
*/30 * * * * MAX_ACTIVE_RUNNERS=4 /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
|
||||
@@ -57,6 +57,15 @@ a time, only when idle**, with the idle check and the restart in the same comman
|
||||
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).
|
||||
- **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
|
||||
died on `release/v3.8.51` with nobody watching. Worst case on the box is 2 heavy + 2 light ≈
|
||||
30 + 12 GB — over 31 GB of RAM, inside the 16 GB of swap; the real fix for headroom is more RAM
|
||||
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).
|
||||
- **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;
|
||||
|
||||
@@ -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),
|
||||
});
|
||||
}
|
||||
|
||||
101
scripts/ad-hoc/codemod-rm-maxretries.mjs
Normal file
101
scripts/ad-hoc/codemod-rm-maxretries.mjs
Normal file
@@ -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}`);
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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 = {}) {
|
||||
|
||||
@@ -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<string, unknown>;
|
||||
const body = (await res.json()) as Record<string, unknown>;
|
||||
const errMsg = (body.error as Record<string, unknown>)?.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<string, unknown>;
|
||||
const body = (await res.json()) as Record<string, unknown>;
|
||||
const errMsg = (body.error as Record<string, unknown>)?.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);
|
||||
});
|
||||
|
||||
@@ -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<string, unknown>;
|
||||
const body = (await res.json()) as Record<string, unknown>;
|
||||
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<string, unknown>;
|
||||
const body = (await res.json()) as Record<string, unknown>;
|
||||
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<string, unknown>;
|
||||
const body = (await res.json()) as Record<string, unknown>;
|
||||
const errMsg = (body.error as Record<string, unknown>)?.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<string, unknown>;
|
||||
const body = (await res.json()) as Record<string, unknown>;
|
||||
const errMsg = (body.error as Record<string, unknown>)?.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<string, unknown>;
|
||||
const body = (await res.json()) as Record<string, unknown>;
|
||||
const errMsg = (body.error as Record<string, unknown>)?.message as string;
|
||||
assert.ok(!errMsg.includes("at /"), "stack trace leaked in download 404");
|
||||
});
|
||||
|
||||
@@ -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<string, unknown>;
|
||||
const body = (await res.json()) as Record<string, unknown>;
|
||||
const errMsg = (body.error as Record<string, unknown>)?.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");
|
||||
});
|
||||
|
||||
@@ -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 */
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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 });
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -268,7 +268,7 @@ async function stopProcess(child: ReturnType<typeof spawn>) {
|
||||
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;
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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 {}
|
||||
});
|
||||
|
||||
|
||||
@@ -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 {}
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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<string, string> = {
|
||||
"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<LiveHarness> {
|
||||
}
|
||||
}
|
||||
} 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<LiveHarness> {
|
||||
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<LiveHarness> {
|
||||
});
|
||||
}
|
||||
|
||||
function liveBody(model: string, overrides: Record<string, unknown> = {}): Record<string, unknown> {
|
||||
function liveBody(
|
||||
model: string,
|
||||
overrides: Record<string, unknown> = {}
|
||||
): Record<string, unknown> {
|
||||
return {
|
||||
model,
|
||||
stream: false,
|
||||
@@ -400,7 +403,7 @@ export async function createLiveHarness(prefix: string): Promise<LiveHarness> {
|
||||
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
|
||||
|
||||
@@ -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 ──────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
|
||||
|
||||
@@ -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 ──
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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 ──
|
||||
|
||||
@@ -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", {
|
||||
|
||||
@@ -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 ──
|
||||
|
||||
@@ -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 ──
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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[]) {
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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 } };
|
||||
|
||||
@@ -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 } };
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -20,7 +20,11 @@ const { pluginManager } = await import("../../src/lib/plugins/manager.ts");
|
||||
// Scanner expects: sourceDir/<plugin-name>/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");
|
||||
|
||||
@@ -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)", () => {
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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 ──
|
||||
|
||||
@@ -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<string, unknown>).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 () => {
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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<string, unknown>).action === "quota.pool.created"
|
||||
(e) =>
|
||||
typeof e === "object" &&
|
||||
e !== null &&
|
||||
(e as Record<string, unknown>).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);
|
||||
});
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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 });
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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<string> {
|
||||
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");
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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<string, string> = {
|
||||
@@ -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<string, unknown> = {}): Record<string, unknown> {
|
||||
@@ -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<string, string> = {
|
||||
"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);
|
||||
});
|
||||
|
||||
@@ -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 ──────────────────────────────────────────────
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
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";
|
||||
}> = {}) {
|
||||
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
|
||||
|
||||
@@ -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<string> {
|
||||
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");
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -49,7 +49,7 @@ async function readTransformed(chunks: string[], options: object): Promise<strin
|
||||
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 });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
assert.ok(
|
||||
lines.includes("event: content_block_delta"),
|
||||
"Claude delta event framing must be preserved"
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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> }).arrayBuffer()).toString("utf8");
|
||||
hitBody = Buffer.from(
|
||||
await (raw as { arrayBuffer(): Promise<ArrayBuffer> }).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" } }
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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)"
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user