Files
OmniRoute/tests/unit/cli-helper-tool-detector-paths-6162.test.ts
Aris 74546ed854 fix(doctor): resolve two false-positive WARNs (#6162) (#6163)
* fix(doctor): resolve two false-positive WARNs (#6162)

The `omniroute doctor` command reported two warnings on healthy installs
even though the underlying checks actually passed. Both came from the
doctor probing state that already worked; they looked like bugs but users
couldn't tell without manual digging.

Issue 1 — Server liveness HTTP 401
  /api/health and /api/health/degradation both require the management
  token. Doctor called them without auth → 401 → WARN, even when the
  Next.js server was clearly alive and listening.

  Fix: probe the configured health endpoint first; on 401/403, fall
  back to a publicly served static asset (/favicon.ico) to confirm the
  server is alive. WARN now only fires when both probes fail.

Issue 2 — CLI Tools '@/shared' import
  tool-detector.ts (and 3 other cli-helper files) import @/shared/...
  aliases that resolve via tsconfig.json paths. The CLI ships raw TS
  source (no compile step) and runs through tsx, but tsx does not honor
  tsconfig paths at runtime, and tsconfig-paths only hooks CJS
  Module._resolveFilename while doctor uses ESM `import()`.

  Fix: replace @/shared/... with relative imports in the 4 cli-helper
  files. This is the same pattern these files already use for ./config-
  generator/* imports. No new dependency, no architectural change, and
  the fix doesn't regress Next.js itself which keeps using @/shared.

Verified on v3.8.43 (Node v24.17, Windows 11):
  Before: 7 ok, 2 warning(s), 0 failure(s)
  After:  8 ok, N warning(s), 0 failure(s)
    where N accurately reflects which CLI tools are installed and
    configured for OmniRoute (e.g. Hermes Agent installed but not
    pointed at 20128 → 2 real warnings, not 1 false-positive).

Refs #6162

* fix(doctor): derive fallback URL from primary URL via new URL()

Per Gemini code-assist review feedback: the previous fallback constructed
the /favicon.ico URL from defaults (127.0.0.1:PORT) which ignored custom
host/port/protocol configurations supplied via:
  - OMNIROUTE_DOCTOR_LIVENESS_URL
  - OMNIROUTE_DOCTOR_HOST
  - --liveness-url / --host CLI flags

Parse the primary URL with new URL() to preserve protocol, host, port, and
subpaths. The previous default-based fallback remains as a catch-all for
invalid primary URLs.

* test(doctor): add regression tests for #6162 fixes

Two new test files lock the fix and satisfy the PR Test Policy gate
("production code change without tests"):

- tests/unit/cli-helper-tool-detector-paths-6162.test.ts
    Locks the @/shared → relative imports fix across all 4 cli-helper
    files. Asserts (a) no @/shared alias remains in the cli-helper
    sources, and (b) each file is importable at runtime via tsx/ESM,
    which would have thrown "Cannot find package '@/shared'" before
    the fix.

- tests/unit/cli-doctor-liveness-fallback-6162.test.ts
    Locks the /favicon.ico fallback in doctor.mjs. Asserts the
    fallback probe exists, derives its URL from the primary URL via
    new URL() (per Gemini review feedback), and that the buggy
    'Server responded with HTTP 401' WARN path is gone.

Both tests use only node:test + node:assert/strict so they slot into
the existing 'test' and 'test:unit' scripts with no extra config.

* test(doctor): fix primary.ok regex in fallback test

The earlier regex /primary\.ok\s*\?/ required a '?' immediately after,
but the actual doctor.mjs code uses a multi-line if-block:

  if (primary.ok) {
    return ok(...);
  }

Use /\bprimary\.ok\b/ instead so the assertion matches the existing
branching.

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
2026-07-05 02:41:07 -03:00

61 lines
2.7 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync, existsSync } from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
// Regression test for #6162: published `omniroute doctor` failed with
// "Could not run CLI tool checks: Cannot find package '@/shared'" because
// src/lib/cli-helper/*.ts files imported `@/shared/...` aliases that the
// CLI runtime (tsx + ESM `import()`) cannot resolve. Fix: replace
// `@/shared/...` with relative imports in the cli-helper files so they work
// in the published package without a compile step.
//
// Lock the fix by asserting that no cli-helper source file uses the
// `@/shared` alias any more, and that the runtime module load succeeds.
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
const CLI_HELPER_FILES = [
"src/lib/cli-helper/tool-detector.ts",
"src/lib/cli-helper/claudeProfileAutoSync.ts",
"src/lib/cli-helper/codexProfileAutoSync.ts",
"src/lib/cli-helper/config-generator/opencode.ts",
];
for (const file of CLI_HELPER_FILES) {
test(`${file} must not use @/shared path alias (fix for #6162)`, () => {
const abs = join(ROOT, file);
assert.ok(existsSync(abs), `${file} should exist`);
const content = readFileSync(abs, "utf8");
assert.ok(
!/@\/shared/.test(content),
`${file} must not import via "@/shared/..." alias — the published CLI runtime (tsx + ESM import) cannot resolve tsconfig path aliases. Use relative paths instead. See #6162.`
);
});
}
test("tool-detector.ts is importable at runtime (regression for #6162)", async () => {
// This would have failed before the fix with
// "Cannot find package '@/shared' imported from .../tool-detector.ts".
const mod = await import("../../src/lib/cli-helper/tool-detector.ts");
assert.equal(typeof mod.detectAllTools, "function");
assert.equal(typeof mod.detectTool, "function");
});
test("claudeProfileAutoSync.ts is importable at runtime (regression for #6162)", async () => {
const mod = await import("../../src/lib/cli-helper/claudeProfileAutoSync.ts");
// The module exports at least the sync function; we don't care about its
// specific name, only that the import resolves without throwing.
assert.equal(typeof mod, "object");
});
test("codexProfileAutoSync.ts is importable at runtime (regression for #6162)", async () => {
const mod = await import("../../src/lib/cli-helper/codexProfileAutoSync.ts");
assert.equal(typeof mod, "object");
});
test("config-generator/opencode.ts is importable at runtime (regression for #6162)", async () => {
const mod = await import("../../src/lib/cli-helper/config-generator/opencode.ts");
assert.equal(typeof mod, "object");
});