refactor: validate generated evidence artifacts
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
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<string> {
|
||||
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",
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user