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.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", }); }); });