fix: preserve artifact writer failures
This commit is contained in:
@@ -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();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
Reference in New Issue
Block a user