inspector/clients/web/src/test/integration/node/version.test.ts
cliffhall a26cbac609 review: harden version read + fix MonitoringScreen docstring
Address PR #1641 review feedback:

- Add readInspectorVersionSafe() (non-throwing) in core/node/version.ts and use
  it in the web backend, so a failure to resolve the root manifest hides the
  (cosmetic) version badge instead of crashing the backend at startup. Covered
  by tests for both the resolved and unresolvable paths.
- Update MonitoringScreen's docstrings: it now wraps every supplied screen in a
  ScreenStage (only the active tab mounts, cross-fading) rather than rendering
  just the active tab's node.

The third fetch-consolidation suggestion (one useInitialConfig instead of three
GET /api/config hooks) is tracked as a follow-up in #1643.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
2026-07-10 22:40:34 -04:00

86 lines
2.9 KiB
TypeScript

import { describe, it, expect } from "vitest";
import {
mkdtempSync,
mkdirSync,
rmSync,
writeFileSync,
readFileSync,
} from "fs";
import { tmpdir } from "os";
import { join, dirname } from "path";
import { pathToFileURL, fileURLToPath } from "url";
import {
readInspectorVersion,
readInspectorVersionSafe,
ROOT_PACKAGE_NAME,
} from "@inspector/core/node/version.js";
/** Absolute path to the repo root package.json (three levels above core/node). */
const rootPackageJsonPath = join(
dirname(fileURLToPath(import.meta.url)),
"..",
"..",
"..",
"..",
"..",
"..",
"package.json",
);
function readRootVersion(): string {
return (
JSON.parse(readFileSync(rootPackageJsonPath, "utf-8")) as {
version: string;
}
).version;
}
describe("readInspectorVersion", () => {
it("resolves the version from the root package.json for a caller inside the repo", () => {
// This test file lives below the root manifest; walking up from it passes
// clients/web/package.json (wrong name → skipped) and several levels with no
// package.json (the catch branch) before reaching the root.
expect(readInspectorVersion(import.meta.url)).toBe(readRootVersion());
// Sanity: the root manifest really is the one we match on.
expect(ROOT_PACKAGE_NAME).toBe("@modelcontextprotocol/inspector");
});
it("skips a matching-name manifest that has no version, then throws when no versioned root exists above the caller", () => {
// A fake root manifest with the right name but NO version must not satisfy
// the search — the walk keeps going up. Rooted in the OS temp dir (outside
// the repo), so the walk reaches the filesystem root without finding a
// versioned inspector manifest and throws.
const work = mkdtempSync(join(tmpdir(), "version-test-"));
try {
writeFileSync(
join(work, "package.json"),
JSON.stringify({ name: ROOT_PACKAGE_NAME }),
);
const sub = join(work, "sub");
mkdirSync(sub);
const callerUrl = pathToFileURL(join(sub, "caller.js")).href;
expect(() => readInspectorVersion(callerUrl)).toThrow(
/Could not locate the @modelcontextprotocol\/inspector root package\.json/,
);
} finally {
rmSync(work, { recursive: true, force: true });
}
});
});
describe("readInspectorVersionSafe", () => {
it("returns the version when the root manifest resolves", () => {
expect(readInspectorVersionSafe(import.meta.url)).toBe(readRootVersion());
});
it("returns undefined instead of throwing when the root can't be resolved", () => {
// A caller rooted in the OS temp dir (outside the repo) has no inspector
// root manifest above it — readInspectorVersion would throw, but the safe
// variant swallows it so a cosmetic version read never crashes the caller.
const callerUrl = pathToFileURL(
join(tmpdir(), "nowhere", "caller.js"),
).href;
expect(readInspectorVersionSafe(callerUrl)).toBeUndefined();
});
});