fix: preserve artifact writer failures

This commit is contained in:
DongHyeonka
2026-08-02 04:46:33 +09:00
parent c9f5887cac
commit 381d5549e2
8 changed files with 575 additions and 149 deletions
+34
View File
@@ -198,6 +198,40 @@ describe("checked-in JSON Schema execution", () => {
fixture.name,
),
).toThrow(/checked-in JSON Schema/u);
expect(() =>
assertMatchesJsonSchema(
schema,
{
...fixture.value,
dependencyCount: 0,
directDependencyCount: 0,
dependencies: [],
},
fixture.name,
),
).toThrow(/checked-in JSON Schema/u);
}
if (fixture.name === "registry snapshot") {
expect(() =>
assertMatchesJsonSchema(
schema,
{ ...fixture.value, failures: [], registries: [] },
fixture.name,
),
).toThrow(/checked-in JSON Schema/u);
expect(() =>
assertMatchesJsonSchema(
schema,
{
...fixture.value,
baselineDigest: null,
compatibility: { impact: "not-evaluated", changes: [] },
failures: ["missing registry source"],
registries: [],
},
fixture.name,
),
).not.toThrow();
}
});
});
+37
View File
@@ -165,6 +165,14 @@ describe("release artifact contracts", () => {
dependencyCount: 2,
}),
).toThrow();
expect(() =>
dependencyInventoryArtifactSchema.parse({
...inventory,
dependencyCount: 0,
directDependencyCount: 0,
dependencies: [],
}),
).toThrow();
});
it("rejects undeclared supply-chain verification evidence", () => {
@@ -215,6 +223,35 @@ describe("release artifact contracts", () => {
);
});
it("requires the complete repository registry set for success evidence", () => {
const successfulEvidence = {
schemaVersion: 2,
generatedAt: "2026-08-01T00:00:00.000Z",
baselineDigest: null,
currentDigest: "a".repeat(64),
compatibility: { impact: "not-evaluated", changes: [] },
failures: [],
registries: Array.from({ length: 11 }, (_, index) => ({
registryId: `registry-${index}`,
owner: "platform",
source: `src/registry-${index}.ts`,
rowCount: 0,
contract: {},
rows: {},
})),
} as const;
expect(registrySnapshotArtifactSchema.parse(successfulEvidence)).toEqual(
successfulEvidence,
);
expect(() =>
registrySnapshotArtifactSchema.parse({
...successfulEvidence,
registries: [],
}),
).toThrow();
});
it("preserves unverified field evidence when no samples are eligible", () => {
const report = {
schemaVersion: 1,
+135
View File
@@ -88,6 +88,141 @@ describe("validated JSON artifact writer", () => {
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) => {