import { mkdtemp, open, readFile, readdir, rename, rm, writeFile, } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { z } from "zod"; import { createValidatedJsonArtifactWriter, writeValidatedJsonArtifact, } from "../../scripts/lib/validated-json-artifact.ts"; const artifactSchema = z .object({ schemaVersion: z.literal(1), name: z.string().min(1) }) .strict(); const temporaryDirectories: string[] = []; async function temporaryDirectory(): Promise { const directory = await mkdtemp( path.join(tmpdir(), "validated-json-artifact-"), ); temporaryDirectories.push(directory); return directory; } afterEach(async () => { await Promise.all( temporaryDirectories.splice(0).map((directory) => rm(directory, { force: true, recursive: true }), ), ); }); describe("validated JSON artifact writer", () => { it.each(["existing", "missing"] as const)( "rejects invalid %s artifacts before changing destination state", async (destinationState) => { const directory = await temporaryDirectory(); const destination = path.join(directory, "artifact.json"); if (destinationState === "existing") { await writeFile(destination, "previous-bytes\n", "utf8"); } const entriesBefore = await readdir(directory); await expect( writeValidatedJsonArtifact({ path: destination, schema: artifactSchema, value: { schemaVersion: 1, name: "" }, }), ).rejects.toThrow(); expect(await readdir(directory)).toEqual(entriesBefore); if (destinationState === "existing") { await expect(readFile(destination, "utf8")).resolves.toBe( "previous-bytes\n", ); } else { await expect(readFile(destination, "utf8")).rejects.toMatchObject({ code: "ENOENT", }); } }, ); it("publishes complete formatted bytes through a sibling rename", async () => { const directory = await temporaryDirectory(); const destination = path.join(directory, "artifact.json"); await writeValidatedJsonArtifact({ path: destination, schema: artifactSchema, value: { schemaVersion: 1, name: "valid" }, }); await expect(readFile(destination, "utf8")).resolves.toBe( '{\n "schemaVersion": 1,\n "name": "valid"\n}\n', ); expect(await readdir(directory)).toEqual(["artifact.json"]); }); it("writes the schema's defaulted and transformed parsed output", async () => { const directory = await temporaryDirectory(); const destination = path.join(directory, "artifact.json"); const parsedOutputSchema = z .object({ name: z.string().default("generated"), count: z.string().transform((value) => Number(value)), }) .strict(); await writeValidatedJsonArtifact({ path: destination, schema: parsedOutputSchema, value: { count: "3" }, }); expect(JSON.parse(await readFile(destination, "utf8"))).toEqual({ name: "generated", count: 3, }); }); it("reports a close-only failure and cleans its owned temp", async () => { const directory = await temporaryDirectory(); const destination = path.join(directory, "artifact.json"); const ownedTemp = path.join(directory, ".artifact.json.close-only.tmp"); const closeError = new Error("injected close-only failure"); const writer = createValidatedJsonArtifactWriter({ createNonce: () => "close-only", fileSystem: { open: async (target, flags) => { const handle = await open(target, flags); return { writeFile: async (data, encoding) => handle.writeFile(data, encoding), close: async () => { await handle.close(); throw closeError; }, }; }, rename, rm, }, }); await expect( writer({ path: destination, schema: artifactSchema, value: { schemaVersion: 1, name: "valid" }, }), ).rejects.toBe(closeError); await expect(readFile(destination, "utf8")).rejects.toMatchObject({ code: "ENOENT", }); await expect(readFile(ownedTemp, "utf8")).rejects.toMatchObject({ code: "ENOENT", }); }); it("preserves the write error when write and close both fail", async () => { const directory = await temporaryDirectory(); const destination = path.join(directory, "artifact.json"); const ownedTemp = path.join(directory, ".artifact.json.double-failure.tmp"); const writeError = new Error("injected primary write failure"); const closeError = new Error("injected secondary close failure"); const writer = createValidatedJsonArtifactWriter({ createNonce: () => "double-failure", fileSystem: { open: async (target, flags) => { const handle = await open(target, flags); return { writeFile: async (data, encoding) => { await handle.writeFile(data, encoding); throw writeError; }, close: async () => { await handle.close(); throw closeError; }, }; }, rename, rm, }, }); await expect( writer({ path: destination, schema: artifactSchema, value: { schemaVersion: 1, name: "valid" }, }), ).rejects.toBe(writeError); await expect(readFile(destination, "utf8")).rejects.toMatchObject({ code: "ENOENT", }); await expect(readFile(ownedTemp, "utf8")).rejects.toMatchObject({ code: "ENOENT", }); }); it("publishes one complete document when two valid writers race", async () => { const directory = await temporaryDirectory(); const destination = path.join(directory, "artifact.json"); const nonces = ["first", "second"]; const writer = createValidatedJsonArtifactWriter({ createNonce: () => { const nonce = nonces.shift(); if (nonce === undefined) throw new Error("nonce fixture exhausted"); return nonce; }, }); await Promise.all([ writer({ path: destination, schema: artifactSchema, value: { schemaVersion: 1, name: "first" }, }), writer({ path: destination, schema: artifactSchema, value: { schemaVersion: 1, name: "second" }, }), ]); expect([ { schemaVersion: 1, name: "first" }, { schemaVersion: 1, name: "second" }, ]).toContainEqual(JSON.parse(await readFile(destination, "utf8"))); expect(await readdir(directory)).toEqual(["artifact.json"]); }); it.each(["write", "rename"] as const)( "cleans only its owned sibling temp when %s fails", async (failurePoint) => { const directory = await temporaryDirectory(); const destination = path.join(directory, "artifact.json"); const unrelatedTemp = path.join(directory, ".artifact.json.unrelated.tmp"); const ownedTemp = path.join(directory, ".artifact.json.owned.tmp"); await writeFile(destination, "previous-bytes\n", "utf8"); await writeFile(unrelatedTemp, "unrelated\n", "utf8"); const writer = createValidatedJsonArtifactWriter({ createNonce: () => "owned", fileSystem: { open: async (target, flags) => { const handle = await open(target, flags); return { writeFile: async (data, encoding) => { await handle.writeFile(data, encoding); if (failurePoint === "write") { throw new Error("injected write failure"); } }, close: async () => handle.close(), }; }, rename: async (source, target) => { if (failurePoint === "rename") { throw new Error("injected rename failure"); } await rename(source, target); }, rm, }, }); await expect( writer({ path: destination, schema: artifactSchema, value: { schemaVersion: 1, name: "valid" }, }), ).rejects.toThrow(`injected ${failurePoint} failure`); await expect(readFile(destination, "utf8")).resolves.toBe( "previous-bytes\n", ); await expect(readFile(unrelatedTemp, "utf8")).resolves.toBe( "unrelated\n", ); await expect(readFile(ownedTemp, "utf8")).rejects.toMatchObject({ code: "ENOENT", }); }, ); it("does not delete a pre-existing colliding sibling temp", async () => { const directory = await temporaryDirectory(); const destination = path.join(directory, "artifact.json"); const collidingTemp = path.join(directory, ".artifact.json.collision.tmp"); await writeFile(collidingTemp, "another-writer\n", "utf8"); const writer = createValidatedJsonArtifactWriter({ createNonce: () => "collision", }); await expect( writer({ path: destination, schema: artifactSchema, value: { schemaVersion: 1, name: "valid" }, }), ).rejects.toMatchObject({ code: "EEXIST" }); await expect(readFile(collidingTemp, "utf8")).resolves.toBe( "another-writer\n", ); await expect(readFile(destination, "utf8")).rejects.toMatchObject({ code: "ENOENT", }); }); });