Files
reverse-engineering-anything/tests/boundary/filesystem/javascriptNestedReaderCleanup.test.ts
T
morluto bdf15b9bd5 refactor(javascript): share the reconstruction input contract
Remove the reconstruction-only request schema and consume the canonical parsed
JavaScript application input. Production already owns parsing at the service
boundary; direct test seams now use that same schema instead of a weaker path
contract. Preserve format, filesystem, source selection and lifecycle validation.

Add a NUL-path regression for canonical admission and typed input-error
projection. Verified 63 focused cases plus static, build, docs, discovery and
duplication checks; no target execution or real-provider claims.
2026-10-24 15:02:26 +08:00

80 lines
3.2 KiB
TypeScript

import { mkdir, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { createPackage } from "@electron/asar";
import { expect, it, onTestFinished, vi } from "vitest";
import { AsarArtifactReader } from "../../../src/artifacts/AsarArtifactReader.js";
import { ArtifactResourceScope } from "../../../src/artifacts/ArtifactResourceScope.js";
import { ArtifactReaderFailure } from "../../../src/artifacts/ArtifactReader.js";
import { reconstructJavaScriptArtifact } from "../../../src/application/javascript/JavaScriptArtifactReconstruction.js";
import { analyzeJavaScriptApplicationInputSchema } from "../../../src/domain/javascript/javascriptApplicationAnalysis.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
it("retains a failed nested reader through blocked work and scope cleanup", async () => {
const root = await createTestTempDirectory("rea-js-nested-reader-cleanup-");
const bundle = join(root, "bundle");
const contents = join(root, "asar-contents");
const archive = join(bundle, "app.asar");
await Promise.all([mkdir(bundle), mkdir(contents)]);
await writeFile(join(contents, "package.json"), '{"main":"index.js"}\n');
await writeFile(join(contents, "index.js"), "module.exports = 1;\n");
await createPackage(contents, archive);
const scope = new ArtifactResourceScope();
const originalClose = AsarArtifactReader.prototype.close;
const closedReaders: AsarArtifactReader[] = [];
const closeFailure = new Error("nested ASAR close failed");
let blockReadPhaseClose = true;
onTestFinished(async () => {
blockReadPhaseClose = false;
await scope.close().catch(() => undefined);
vi.restoreAllMocks();
});
vi.spyOn(AsarArtifactReader.prototype, "close").mockImplementation(
async function (this: AsarArtifactReader) {
closedReaders.push(this);
// Inventory closes its nested reader first. Fail the next, read-phase
// reader close after its native handle remains available for scope retry.
if (this === closedReaders[1] && blockReadPhaseClose) throw closeFailure;
await originalClose.call(this);
},
);
const input = analyzeJavaScriptApplicationInputSchema.parse({
input_path: bundle,
});
const firstFailure = await reconstructJavaScriptArtifact(input, scope).catch(
(cause: unknown) => cause,
);
expect(firstFailure).toBeInstanceOf(ArtifactReaderFailure);
expect(firstFailure).toMatchObject({
cleanup: {
reason: closeFailure.message,
resources: [expect.stringContaining("app.asar")],
},
});
expect(closedReaders).toHaveLength(2);
const retainedReader = closedReaders[1];
expect(retainedReader).toBeDefined();
expect(retainedReader).not.toBe(closedReaders[0]);
const blockedWork = await reconstructJavaScriptArtifact(input, scope).catch(
(cause: unknown) => cause,
);
expect(blockedWork).toMatchObject({
reason: "unavailable",
cleanup: {
reason: closeFailure.message,
resources: [expect.stringContaining("app.asar")],
},
});
expect(closedReaders).toHaveLength(3);
expect(closedReaders[2]).toBe(retainedReader);
blockReadPhaseClose = false;
await scope.close();
expect(closedReaders).toHaveLength(4);
expect(closedReaders[3]).toBe(retainedReader);
});