mirror of
https://github.com/morluto/rea.git
synced 2026-10-27 18:05:45 +00:00
Keep explicitly selected Node Inspector targets observable when discovery cannot verify their file location. Resolve literal POSIX discovery paths without URI decoding, and retain lossy Windows or underscore paths as unresolved metadata. Validate scriptParsed file URLs independently. Preserve unknown target identity through runtime reconciliation, regenerate contracts, and verify real CLI/MCP behavior on Linux and Windows. Closes #530 Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)
196 lines
6.0 KiB
TypeScript
196 lines
6.0 KiB
TypeScript
import { rm, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { pathToFileURL } from "node:url";
|
|
|
|
import { afterEach, expect, test } from "vitest";
|
|
|
|
import { V8InspectorProvider } from "../../../src/browser/V8InspectorProvider.js";
|
|
import { startFakeV8Inspector } from "../../fixtures/fakeV8Inspector.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
|
|
const roots: string[] = [];
|
|
afterEach(async () => {
|
|
await Promise.all(
|
|
roots.splice(0).map((root) => rm(root, { recursive: true, force: true })),
|
|
);
|
|
});
|
|
|
|
async function entryFile(name: string): Promise<string> {
|
|
const root = await createTestTempDirectory("rea-node-discovery-");
|
|
roots.push(root);
|
|
const entry = join(root, name);
|
|
await writeFile(entry, "setInterval(() => {}, 1000);\n");
|
|
return entry;
|
|
}
|
|
|
|
test("lists and observes a Windows target without inventing a decoded path", async () => {
|
|
const entry = await entryFile("entry.js");
|
|
const reported = "file://C:_tools_entry.js";
|
|
const fake = await startFakeV8Inspector({
|
|
targetUrl: reported,
|
|
scriptUrls: [pathToFileURL(entry).href],
|
|
});
|
|
try {
|
|
const provider = new V8InspectorProvider();
|
|
const listed = await provider.listTargets({
|
|
inspector_endpoint: fake.endpoint,
|
|
});
|
|
expect(listed.ok).toBe(true);
|
|
if (!listed.ok) return;
|
|
expect(listed.value.targets).toMatchObject([
|
|
{
|
|
target_id: fake.targetId,
|
|
location: {
|
|
kind: "unresolved",
|
|
reported_url: reported,
|
|
reason: "unverifiable-file-location",
|
|
},
|
|
},
|
|
]);
|
|
expect(listed.value.excluded.unsupported_location).toBe(0);
|
|
expect(fake.commands).toEqual([]);
|
|
|
|
const missing = await provider.observe({
|
|
inspector_endpoint: fake.endpoint,
|
|
target_id: "not-the-selected-target",
|
|
observation_ms: 10,
|
|
});
|
|
expect(missing).toMatchObject({
|
|
ok: false,
|
|
error: { reason: "target_not_found" },
|
|
});
|
|
expect(fake.commands).toEqual([]);
|
|
|
|
const observed = await provider.observe({
|
|
inspector_endpoint: fake.endpoint,
|
|
target_id: fake.targetId,
|
|
observation_ms: 10,
|
|
});
|
|
expect(observed.ok).toBe(true);
|
|
if (!observed.ok) return;
|
|
expect(observed.value.target.location).toEqual(
|
|
listed.value.targets[0]?.location,
|
|
);
|
|
expect(observed.value.scripts.items).toMatchObject([
|
|
{ location: { kind: "file", file_path: entry } },
|
|
]);
|
|
expect(observed.value.unknowns).toContain(
|
|
"The discovery-reported file location cannot be verified; loaded script locations are resolved independently from Debugger.scriptParsed.",
|
|
);
|
|
expect(fake.commands.map(({ method }) => method)).toEqual([
|
|
"Runtime.enable",
|
|
"Debugger.enable",
|
|
]);
|
|
} finally {
|
|
await fake.close();
|
|
}
|
|
});
|
|
|
|
test
|
|
.runIf(process.platform !== "win32")
|
|
.each(["entry#hash.js", "entry%percent.js", "entry%23hash.js"])(
|
|
"interprets %s as a literal Node discovery path on POSIX",
|
|
async (name) => {
|
|
const entry = await entryFile(name);
|
|
const fake = await startFakeV8Inspector({ targetUrl: `file://${entry}` });
|
|
try {
|
|
const listed = await new V8InspectorProvider().listTargets({
|
|
inspector_endpoint: fake.endpoint,
|
|
});
|
|
expect(listed.ok).toBe(true);
|
|
if (!listed.ok) return;
|
|
expect(listed.value.targets).toMatchObject([
|
|
{ location: { kind: "file", file_path: entry } },
|
|
]);
|
|
} finally {
|
|
await fake.close();
|
|
}
|
|
},
|
|
);
|
|
|
|
test("keeps a lossy discovery name unresolved even when its underscore alias exists", async () => {
|
|
const alias = await entryFile("entry_quote.js");
|
|
const reported = `file://${alias}`;
|
|
const fake = await startFakeV8Inspector({ targetUrl: reported });
|
|
try {
|
|
const listed = await new V8InspectorProvider().listTargets({
|
|
inspector_endpoint: fake.endpoint,
|
|
});
|
|
expect(listed.ok).toBe(true);
|
|
if (!listed.ok) return;
|
|
expect(listed.value.targets).toMatchObject([
|
|
{ location: { kind: "unresolved", reported_url: reported } },
|
|
]);
|
|
} finally {
|
|
await fake.close();
|
|
}
|
|
});
|
|
|
|
test.each([
|
|
"file://",
|
|
"file://remote.test/share/entry.js",
|
|
"javascript:entry()",
|
|
"file://a:secret@remote.test/entry.js",
|
|
])("keeps unsupported discovery location %s excluded", async (targetUrl) => {
|
|
const fake = await startFakeV8Inspector({ targetUrl });
|
|
try {
|
|
const listed = await new V8InspectorProvider().listTargets({
|
|
inspector_endpoint: fake.endpoint,
|
|
});
|
|
expect(listed.ok).toBe(true);
|
|
if (!listed.ok) return;
|
|
expect(listed.value.targets).toEqual([]);
|
|
expect(listed.value.excluded.unsupported_location).toBe(1);
|
|
} finally {
|
|
await fake.close();
|
|
}
|
|
});
|
|
|
|
test.each([
|
|
{ targetType: "page" as const },
|
|
{ runtimeProduct: "Chrome/132.0" },
|
|
])("does not apply Node discovery semantics to %j", async (context) => {
|
|
const fake = await startFakeV8Inspector({
|
|
targetUrl: "file://C:_tools_entry.js",
|
|
...context,
|
|
});
|
|
try {
|
|
const listed = await new V8InspectorProvider().listTargets({
|
|
inspector_endpoint: fake.endpoint,
|
|
});
|
|
expect(listed.ok).toBe(true);
|
|
if (!listed.ok) return;
|
|
expect(listed.value.targets).toEqual([]);
|
|
expect(listed.value.excluded.unsupported_location).toBe(1);
|
|
} finally {
|
|
await fake.close();
|
|
}
|
|
});
|
|
|
|
test("does not reinterpret lossy discovery paths in script metadata", async () => {
|
|
const entry = await entryFile("entry.js");
|
|
const fake = await startFakeV8Inspector({
|
|
targetUrl: "file://C:_tools_entry.js",
|
|
scriptUrls: [
|
|
"file://C:_tools_entry.js",
|
|
"file://remote.test/share/entry.js",
|
|
pathToFileURL(entry).href,
|
|
],
|
|
});
|
|
try {
|
|
const observed = await new V8InspectorProvider().observe({
|
|
inspector_endpoint: fake.endpoint,
|
|
target_id: fake.targetId,
|
|
observation_ms: 10,
|
|
});
|
|
expect(observed.ok).toBe(true);
|
|
if (!observed.ok) return;
|
|
expect(observed.value.scripts.items).toMatchObject([
|
|
{ location: { kind: "file", file_path: entry } },
|
|
]);
|
|
expect(observed.value.scripts.excluded.unsupported_location).toBe(2);
|
|
} finally {
|
|
await fake.close();
|
|
}
|
|
});
|