import { test } from "node:test"; import assert from "node:assert/strict"; import { mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { snapshotFromIndex, writeSnapshotFile, readSnapshotFile } from "../out/agent/snapshot.js"; import { findAssets, findReferenceGroups, isFileActive, listAssetsByType, resolveIncludeSource, statusFromSnapshot, } from "../out/agent/query.js"; function makeStats() { return { projectDir: "P", sdkDir: "", phase: "art", complete: true, indexedFiles: 3, parsedFiles: 2, shallowScannedFiles: 1, deferredArtFiles: 0, shallowCacheHits: 0, recordsCacheHits: 0, resolveCacheHits: 0, resolveCalls: 0, snapshotHits: 0, snapshotFallbacks: 0, candidatesMs: 0, walkMs: 0, artScanMs: 0, assetCount: 2, referenceCount: 1, defineCount: 1, manifestFiles: 0, manifestAssetCount: 0, streams: 1, sourceCandidates: 1, elapsedMs: 1, }; } function makeIndex() { const file = "D:/Mods/Example/Data/Units/Example.xml"; const assets = new Map([ [ "GameObject", new Map([ [ "exampleunit", [ { type: "GameObject", id: "ExampleUnit", file, line: 5, origin: "project", stream: "static", }, ], ], ]), ], ]); const references = new Map([ [ "GameObject\u0000exampleunit\u0000D:/Mods/Example/Data/Units/Example.xml\u00005", [ { file: "D:/Mods/Example/Data/Other.xml", line: 3, start: 10, end: 21, kind: "attr", }, ], ], ]); return { projectDir: "D:/Mods/Example", sdkDir: "", complete: true, phase: "art", stale: false, assets, assetsById: new Map([ [ "exampleunit", [ { type: "GameObject", id: "ExampleUnit", file, line: 5, origin: "project", stream: "static", }, ], ], ]), defines: new Map([ [ "exampledefine", [ { name: "ExampleDefine", value: "1", file, line: 2, origin: "project", }, ], ], ]), files: new Map(), streams: [ { name: "static", entry: "D:/Mods/Example/Data/Mod.xml", files: new Set(["d:/mods/example/data/units/example.xml"]), }, ], manifests: new Map(), sourceCandidates: [ { source: "DATA:Units/Example.xml", path: "D:/Mods/Example/Data/Units/Example.xml", prefix: "DATA", baseDir: "D:/Mods/Example/Data", }, ], diagnostics: [], references, recordsHashes: new Map(), stats: makeStats(), }; } test("snapshotFromIndex flattens assets, defines, streams and references", () => { const snapshot = snapshotFromIndex(makeIndex(), 42); assert.equal(snapshot.schemaVersion, 1); assert.equal(snapshot.assets.length, 1); assert.equal(snapshot.assets[0].id, "ExampleUnit"); assert.equal(snapshot.defines.length, 1); assert.equal(snapshot.streams.length, 1); assert.deepEqual(snapshot.streams[0].files, [ "d:/mods/example/data/units/example.xml", ]); assert.equal(snapshot.references.length, 1); assert.equal(snapshot.references[0].sites.length, 1); assert.equal(snapshot.buildId, 42); }); test("query helpers operate on snapshots", () => { const snapshot = snapshotFromIndex(makeIndex(), 1); assert.equal(findAssets(snapshot, "ExampleUnit").length, 1); assert.equal(findAssets(snapshot, "exampleunit", "GameObject").length, 1); assert.equal(findAssets(snapshot, "exampleunit", "WeaponTemplate").length, 0); assert.equal(listAssetsByType(snapshot, "gameobject", "exa").length, 1); assert.equal(findReferenceGroups(snapshot, "ExampleUnit").length, 1); assert.equal(isFileActive(snapshot, "D:/Mods/Example/Data/Units/Example.xml"), true); assert.equal(isFileActive(snapshot, "D:/Mods/Example/Data/Dead.xml"), false); assert.equal(resolveIncludeSource(snapshot, "data:units/example.xml")?.path, "D:/Mods/Example/Data/Units/Example.xml"); assert.equal(statusFromSnapshot(snapshot).state, "ready"); }); test("snapshot file write/read round-trips", async () => { const dir = mkdtempSync(join(tmpdir(), "ra3-agent-test-")); try { const file = join(dir, "snapshot.json.gz"); const snapshot = snapshotFromIndex(makeIndex(), 7); await writeSnapshotFile(file, snapshot); const loaded = await readSnapshotFile(file); assert.ok(loaded); assert.equal(loaded.assets.length, snapshot.assets.length); assert.equal(loaded.references[0].sites[0].file, "D:/Mods/Example/Data/Other.xml"); assert.equal(loaded.buildId, 7); } finally { rmSync(dir, { recursive: true, force: true }); } });