singularity-forge/src/tests/web-boot-node24.test.ts
Iouri Goussev 642c0f5a9e test: fix Assertion Roulette, Eager Test, and contract test regressions (#1938)
* test: add assertion messages to fix Assertion Roulette in GSD tests

Add descriptive messages to multi-assertion tests where a bare failure
output ("expected true, got false") wouldn't identify which assertion
broke. Affected tests: auto-secrets-gate, search-tavily, search-provider-
command, tavily-helpers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: fix Eager Test smell in captures and worktree-manager tests

- Split captures: loadPendingCaptures test — extracted loadAllCaptures
  assertion into its own focused test
- Refactor worktree-manager: replace monolithic main() script with 11
  isolated test() calls, each with its own repo setup via helpers

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: add assertion messages to remaining test files

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: fix contract test gate, dynamic roots, and shared fetch helpers

- Fix reject-notice sub-test gated on outcome.kind (actual) instead of
  expectedKind (map value) in web-command-parity-contract.test.ts
- Restore dynamic loop over registered non-gsd passthrough roots with
  an explicit count assertion so new registrations fail loudly
- Extract normalizeHeaders/parseJsonBody to src/tests/fetch-test-helpers.ts
  and import in both search-tavily and llm-context-tavily tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 21:24:15 -06:00

153 lines
5.9 KiB
TypeScript

import test from "node:test"
import assert from "node:assert/strict"
import { resolveTypeStrippingFlag } from "../web/ts-subprocess-flags.ts"
// ---------------------------------------------------------------------------
// Bug 1 — resolveTypeStrippingFlag selects the correct flag
// ---------------------------------------------------------------------------
const [nodeMajor, nodeMinor] = process.versions.node.split(".").map(Number)
const isNode22_7OrNewer = nodeMajor > 22 || (nodeMajor === 22 && nodeMinor >= 7)
test("resolveTypeStrippingFlag returns --experimental-strip-types for paths outside node_modules", () => {
const flag = resolveTypeStrippingFlag("/home/user/projects/gsd")
assert.equal(flag, "--experimental-strip-types")
})
test("resolveTypeStrippingFlag returns --experimental-strip-types for path with node_modules substring not as directory", () => {
// e.g. /home/user/my_node_modules_backup/gsd — not actually under node_modules/
const flag = resolveTypeStrippingFlag("/home/user/my_node_modules_backup/gsd")
assert.equal(flag, "--experimental-strip-types")
})
test(
"resolveTypeStrippingFlag returns --experimental-transform-types for paths under node_modules/ on Node >= 22.7",
{ skip: !isNode22_7OrNewer },
() => {
const flag = resolveTypeStrippingFlag("/usr/lib/node_modules/gsd-pi")
assert.equal(flag, "--experimental-transform-types")
},
)
test(
"resolveTypeStrippingFlag returns --experimental-strip-types for paths under node_modules/ on Node < 22.7",
{ skip: isNode22_7OrNewer },
() => {
const flag = resolveTypeStrippingFlag("/usr/lib/node_modules/gsd-pi")
// On older Node, falls back to strip-types since transform-types isn't available
assert.equal(flag, "--experimental-strip-types")
},
)
test(
"resolveTypeStrippingFlag handles Windows-style paths under node_modules on Node >= 22.7",
{ skip: !isNode22_7OrNewer },
() => {
const flag = resolveTypeStrippingFlag("C:\\Users\\dev\\AppData\\node_modules\\gsd-pi")
assert.equal(flag, "--experimental-transform-types")
},
)
test(
"resolveTypeStrippingFlag handles Windows-style paths under node_modules on Node < 22.7",
{ skip: isNode22_7OrNewer },
() => {
const flag = resolveTypeStrippingFlag("C:\\Users\\dev\\AppData\\node_modules\\gsd-pi")
assert.equal(flag, "--experimental-strip-types")
},
)
// ---------------------------------------------------------------------------
// Bug 2 — waitForBootReady fails fast on consecutive 5xx
// ---------------------------------------------------------------------------
// The waitForBootReady function is not exported, but the behavior is testable
// by verifying the launchWebMode deps injection. We test the core logic
// pattern directly: 3 consecutive 5xx should abort without waiting for timeout.
type RetryEvent = { type: "response"; status: number } | { type: "error" }
/**
* Simulate the consecutive-5xx abort logic extracted from waitForBootReady.
* Returns { abortedEarly, consecutiveCount }.
*/
function simulateConsecutive5xxDetection(
events: RetryEvent[],
maxConsecutive: number,
): { abortedEarly: boolean; consecutiveCount: number } {
return events.reduce(
(acc, event) => {
if (acc.abortedEarly) return acc
const is5xx = event.type === "response" && event.status >= 500
const consecutive = is5xx ? acc.consecutiveCount + 1 : 0
const abortedEarly = consecutive >= maxConsecutive
return { abortedEarly, consecutiveCount: consecutive }
},
{ abortedEarly: false, consecutiveCount: 0 },
)
}
test("waitForBootReady pattern: consecutive 5xx detection aborts early", () => {
const responses: RetryEvent[] = [
{ type: "response", status: 500 },
{ type: "response", status: 500 },
{ type: "response", status: 500 },
]
const { abortedEarly, consecutiveCount } = simulateConsecutive5xxDetection(responses, 3)
assert.equal(abortedEarly, true, "should abort after 3 consecutive 5xx responses")
assert.equal(consecutiveCount, 3)
})
test("waitForBootReady pattern: non-5xx responses reset the consecutive counter", () => {
// 500, 500, connection-refused (resets), 500, 500 — should NOT trigger abort
const events: RetryEvent[] = [
{ type: "response", status: 500 },
{ type: "response", status: 500 },
{ type: "error" }, // connection refused resets counter
{ type: "response", status: 500 },
{ type: "response", status: 500 },
]
const { abortedEarly } = simulateConsecutive5xxDetection(events, 3)
assert.equal(abortedEarly, false, "should not abort when errors reset the counter")
})
test("waitForBootReady pattern: mixed 4xx and 5xx only counts 5xx", () => {
const responses: RetryEvent[] = [
{ type: "response", status: 500 },
{ type: "response", status: 404 },
{ type: "response", status: 500 },
{ type: "response", status: 500 },
]
const { abortedEarly } = simulateConsecutive5xxDetection(responses, 3)
assert.equal(abortedEarly, false, "404 should reset the consecutive 5xx counter")
})
// ---------------------------------------------------------------------------
// Bug 3 — /api/boot route error handling
// ---------------------------------------------------------------------------
test("boot route returns { error } JSON on handler failure", async () => {
// Read the route source to verify try/catch wrapping is present
const { readFileSync } = await import("node:fs")
const { join } = await import("node:path")
const routeSource = readFileSync(
join(process.cwd(), "web", "app", "api", "boot", "route.ts"),
"utf-8",
)
// The route must catch errors and return { error: message }
assert.match(routeSource, /try\s*\{/, "boot route must have try block")
assert.match(routeSource, /catch\s*\(/, "boot route must have catch block")
assert.match(
routeSource,
/\{\s*error:\s*message\s*\}/,
"boot route must return { error: message } on failure",
)
assert.match(
routeSource,
/status:\s*500/,
"boot route must return status 500 on error",
)
})