Skip to content

Commit f6f3ff2

Browse files
author
DavidQ
committed
Add fixture-driven launch and route verification for all V2 tools without fallback logic - PR 11.205
1 parent 155f1d9 commit f6f3ff2

2 files changed

Lines changed: 240 additions & 0 deletions

File tree

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# PR_11_205 Report
2+
3+
## Files Changed
4+
- `tests/runtime/V2ToolLaunch.test.mjs`
5+
- `docs/dev/reports/PR_11_205_report.md`
6+
7+
## Tools Validated
8+
- `asset-browser-v2`
9+
- `palette-manager-v2`
10+
- `svg-asset-studio-v2`
11+
- `tilemap-studio-v2`
12+
- `vector-map-editor-v2`
13+
14+
## Route Checks
15+
Per tool, the runtime test validates:
16+
- launch route from tools index (`tools/index.html` link target)
17+
- direct route path exists (`tools/<tool>/index.html`)
18+
- direct URL shape is generated with fixture host context:
19+
- `tools/<tool>/index.html?hostContextId=<fixture.hostContextId>`
20+
21+
All five tools: **PASS** for index-route and direct-route checks.
22+
23+
## Fixture Usage Confirmation
24+
Per tool fixture loaded from:
25+
- `tests/fixtures/v2-tools/<tool>.json`
26+
27+
Per tool checks:
28+
- fixture file exists
29+
- fixture parses as valid JSON
30+
- `hostContextId` is present and non-empty
31+
- `sessionContext` object exists
32+
- `sessionContext.toolId` matches target tool
33+
- tool-specific payload exists
34+
35+
All five tools: **PASS** for fixture validation and payload presence.
36+
37+
## Runtime Launch Test
38+
Added executable:
39+
- `tests/runtime/V2ToolLaunch.test.mjs`
40+
41+
Output artifact written by test:
42+
- `tmp/v2-tool-launch-results.json`
43+
44+
Command summary:
45+
- `node --check tests/runtime/V2ToolLaunch.test.mjs` -> **PASS**
46+
- `node tests/runtime/V2ToolLaunch.test.mjs` -> **PASS**
47+
- `toolCount: 5`
48+
- `failures: 0`
49+
50+
## Additional Syntax Validation
51+
- Requested command run:
52+
- `node --check tools/*-v2/index.js` -> **FAIL** in PowerShell/Node wildcard resolution context (`MODULE_NOT_FOUND` for literal `*-v2` path)
53+
- Equivalent per-file validation run:
54+
- `node --check tools/asset-browser-v2/index.js` -> **PASS**
55+
- `node --check tools/palette-manager-v2/index.js` -> **PASS**
56+
- `node --check tools/svg-asset-studio-v2/index.js` -> **PASS**
57+
- `node --check tools/tilemap-studio-v2/index.js` -> **PASS**
58+
- `node --check tools/vector-map-editor-v2/index.js` -> **PASS**
59+
60+
## Failures and Fixes Applied
61+
- No tool-route or fixture failures were detected by `V2ToolLaunch.test.mjs`.
62+
- No tool implementation fixes were required in this PR.
63+
64+
## Fallback/Data Safety Confirmation
65+
- No fallback or demo data loading was introduced.
66+
- No V2 tool internals were modified.
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import assert from "node:assert/strict";
2+
import fs from "node:fs";
3+
import path from "node:path";
4+
import { execFileSync } from "node:child_process";
5+
import { fileURLToPath, pathToFileURL } from "node:url";
6+
7+
const __filename = fileURLToPath(import.meta.url);
8+
const __dirname = path.dirname(__filename);
9+
const repoRoot = path.resolve(__dirname, "..", "..");
10+
const toolsRoot = path.join(repoRoot, "tools");
11+
const fixturesRoot = path.join(repoRoot, "tests", "fixtures", "v2-tools");
12+
const toolsIndexPath = path.join(toolsRoot, "index.html");
13+
const resultsPath = path.join(repoRoot, "tmp", "v2-tool-launch-results.json");
14+
15+
const REQUIRED_V2_TOOLS = [
16+
"asset-browser-v2",
17+
"palette-manager-v2",
18+
"svg-asset-studio-v2",
19+
"tilemap-studio-v2",
20+
"vector-map-editor-v2"
21+
];
22+
23+
function readText(filePath) {
24+
return fs.readFileSync(filePath, "utf8");
25+
}
26+
27+
function hasIndexRoute(indexHtmlText, toolId) {
28+
const relHref = `./${toolId}/index.html`;
29+
const absHref = `/tools/${toolId}/index.html`;
30+
const bareHref = `${toolId}/index.html`;
31+
return (
32+
indexHtmlText.includes(`href="${relHref}"`) ||
33+
indexHtmlText.includes(`href="${absHref}"`) ||
34+
indexHtmlText.includes(`href="${bareHref}"`)
35+
);
36+
}
37+
38+
function hasToolSpecificPayload(toolId, fixtureJson) {
39+
const sessionContext = fixtureJson?.sessionContext;
40+
if (!sessionContext || typeof sessionContext !== "object" || Array.isArray(sessionContext)) {
41+
return false;
42+
}
43+
if (toolId === "asset-browser-v2") {
44+
return Boolean(sessionContext?.payloadJson?.assetCatalog);
45+
}
46+
if (toolId === "palette-manager-v2") {
47+
return Boolean(sessionContext?.paletteJson);
48+
}
49+
if (toolId === "svg-asset-studio-v2") {
50+
return Boolean(sessionContext?.payloadJson?.vectorAssetDocument);
51+
}
52+
if (toolId === "tilemap-studio-v2") {
53+
return Boolean(sessionContext?.payloadJson?.tileMapDocument);
54+
}
55+
if (toolId === "vector-map-editor-v2") {
56+
return Boolean(sessionContext?.payloadJson?.vectorMapDocument);
57+
}
58+
return false;
59+
}
60+
61+
function checkJsSyntax(jsPath) {
62+
try {
63+
execFileSync(process.execPath, ["--check", jsPath], {
64+
cwd: repoRoot,
65+
stdio: ["ignore", "pipe", "pipe"]
66+
});
67+
return { syntaxValid: true, syntaxError: "" };
68+
} catch (error) {
69+
return {
70+
syntaxValid: false,
71+
syntaxError: (error?.stderr || error?.stdout || error?.message || "").toString().trim()
72+
};
73+
}
74+
}
75+
76+
function validateTool(toolId, toolsIndexHtmlText) {
77+
const toolIndexHtmlPath = path.join(toolsRoot, toolId, "index.html");
78+
const toolIndexJsPath = path.join(toolsRoot, toolId, "index.js");
79+
const fixturePath = path.join(fixturesRoot, `${toolId}.json`);
80+
81+
const routeFromIndexValid = hasIndexRoute(toolsIndexHtmlText, toolId);
82+
const routePathExists = fs.existsSync(toolIndexHtmlPath);
83+
const directUrl = `tools/${toolId}/index.html`;
84+
const fixtureExists = fs.existsSync(fixturePath);
85+
86+
let fixtureValidJson = false;
87+
let hostContextId = "";
88+
let fixtureToolId = "";
89+
let fixtureToolIdMatches = false;
90+
let fixtureHasSessionContext = false;
91+
let fixtureHasToolPayload = false;
92+
if (fixtureExists) {
93+
try {
94+
const fixtureJson = JSON.parse(readText(fixturePath));
95+
fixtureValidJson = true;
96+
hostContextId = typeof fixtureJson?.hostContextId === "string" ? fixtureJson.hostContextId.trim() : "";
97+
fixtureToolId = typeof fixtureJson?.sessionContext?.toolId === "string" ? fixtureJson.sessionContext.toolId.trim() : "";
98+
fixtureToolIdMatches = fixtureToolId === toolId;
99+
fixtureHasSessionContext = Boolean(
100+
fixtureJson?.sessionContext &&
101+
typeof fixtureJson.sessionContext === "object" &&
102+
!Array.isArray(fixtureJson.sessionContext)
103+
);
104+
fixtureHasToolPayload = hasToolSpecificPayload(toolId, fixtureJson);
105+
} catch {
106+
fixtureValidJson = false;
107+
}
108+
}
109+
110+
const launchUrlWithHostContextId = `${directUrl}?hostContextId=${encodeURIComponent(hostContextId)}`;
111+
const { syntaxValid, syntaxError } = checkJsSyntax(toolIndexJsPath);
112+
113+
const failures = [];
114+
if (!routeFromIndexValid) failures.push("Missing V2 route from tools/index.html.");
115+
if (!routePathExists) failures.push("Missing tools/<tool>-v2/index.html route target.");
116+
if (!fs.existsSync(toolIndexJsPath)) failures.push("Missing tools/<tool>-v2/index.js route runtime target.");
117+
if (!fixtureExists) failures.push("Missing fixture file.");
118+
if (fixtureExists && !fixtureValidJson) failures.push("Fixture is not valid JSON.");
119+
if (fixtureValidJson && !hostContextId) failures.push("Fixture hostContextId is missing or empty.");
120+
if (fixtureValidJson && !fixtureToolIdMatches) failures.push(`Fixture sessionContext.toolId does not match tool (${fixtureToolId || "missing"}).`);
121+
if (fixtureValidJson && !fixtureHasSessionContext) failures.push("Fixture sessionContext object is missing.");
122+
if (fixtureValidJson && !fixtureHasToolPayload) failures.push("Fixture is missing tool-specific payload.");
123+
if (!syntaxValid) failures.push("Tool index.js failed node --check.");
124+
125+
return {
126+
tool: toolId,
127+
routeFromIndexValid,
128+
routePath: path.relative(repoRoot, toolIndexHtmlPath).replace(/\\/g, "/"),
129+
routePathExists,
130+
directUrl,
131+
launchUrlWithHostContextId,
132+
fixturePath: path.relative(repoRoot, fixturePath).replace(/\\/g, "/"),
133+
fixtureExists,
134+
fixtureValidJson,
135+
hostContextId,
136+
fixtureToolId,
137+
fixtureToolIdMatches,
138+
fixtureHasSessionContext,
139+
fixtureHasToolPayload,
140+
syntaxCheckedPath: path.relative(repoRoot, toolIndexJsPath).replace(/\\/g, "/"),
141+
syntaxValid,
142+
syntaxError,
143+
failures
144+
};
145+
}
146+
147+
export function run() {
148+
assert.ok(fs.existsSync(toolsIndexPath), "tools/index.html is missing.");
149+
const toolsIndexHtmlText = readText(toolsIndexPath);
150+
const rows = REQUIRED_V2_TOOLS.map((toolId) => validateTool(toolId, toolsIndexHtmlText));
151+
const failures = rows.flatMap((row) => row.failures.map((entry) => `${row.tool}: ${entry}`));
152+
153+
fs.mkdirSync(path.dirname(resultsPath), { recursive: true });
154+
fs.writeFileSync(resultsPath, `${JSON.stringify({
155+
generatedAt: new Date().toISOString(),
156+
toolCount: rows.length,
157+
failures,
158+
rows
159+
}, null, 2)}\n`, "utf8");
160+
161+
console.log(`v2 tool launch results: ${resultsPath}`);
162+
assert.equal(failures.length, 0, `V2 tool launch failures: ${failures.join(" | ")}`);
163+
return { toolCount: rows.length, failures, rows };
164+
}
165+
166+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
167+
try {
168+
const summary = run();
169+
console.log(JSON.stringify(summary, null, 2));
170+
} catch (error) {
171+
console.error(error);
172+
process.exitCode = 1;
173+
}
174+
}

0 commit comments

Comments
 (0)