/*
Unit tests for r2 durable sync.integration covering R2 durable sync integration.
- Not a contract source of truth — the module under test owns the authoritative behavior; fixtures are minimal scaffolding only.
- RFC-1311: Compass header sweep — declares the r2 suite's contract for test-file scaffold coverage.
*/
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { createR2StorageAdapter } from "../r2-adapter.ts";
import { verifyStoredObject } from "../adapter.ts";
import { verifyDurableSync } from "../../../leitstand/deploy-helpers.ts";
import type { Sha256Digest } from "@warpgogol/werkstatt-shared/fingerprint";
const mockConfig = {
accountId: "test-account-id",
accessKeyId: "test-access-key-id",
secretAccessKey: "test-secret-access-key",
bucketName: "warpgogol-certification",
apiToken: "test-api-token",
};
const artifactDigest = ("sha256:" + "3".repeat(64)) as Sha256Digest;
const gateDecisionBytes = new TextEncoder().encode(
JSON.stringify({
schemaVersion: "werkstatt/gate-decision@1",
decisionId: "dec-2026-08-16t07-48-11-848z-ooaavn",
gate: "alt",
outcome: "pass",
artifactHash: artifactDigest,
}),
);
function createMockResponse(
status: number,
headers: Record = {},
body: ArrayBuffer | string = new ArrayBuffer(0),
): Response {
return new Response(body, {
status,
headers,
});
}
describe("R2 durable sync integration", () => {
let fetchSpy: ReturnType;
beforeEach(() => {
fetchSpy = vi.spyOn(globalThis, "fetch");
});
afterEach(() => {
vi.restoreAllMocks();
});
it("full cycle: putObject → verifyDurableSync → getObject returns same bytes", async () => {
const adapter = createR2StorageAdapter(mockConfig);
// Phase 1: putObject (certify uploads gate decision)
fetchSpy.mockResolvedValueOnce(
createMockResponse(200, { etag: '"put-etag-123"' }),
);
const putResult = await adapter.putObject({
digest: artifactDigest,
bytes: gateDecisionBytes,
mediaType: "application/json",
});
expect(putResult.locator).toContain("r2://warpgogol-certification/");
expect(putResult.sizeBytes).toBe(gateDecisionBytes.byteLength);
// Phase 2: verifyDurableSync (propagate checks existence)
fetchSpy.mockResolvedValueOnce(
createMockResponse(206, {
"content-range": `bytes 0-0/${gateDecisionBytes.byteLength}`,
etag: '"put-etag-123"',
}),
);
const verified = await verifyDurableSync(artifactDigest, mockConfig);
expect(verified).toBe(true);
// Phase 3: getObject (retrieve on demand)
fetchSpy.mockResolvedValueOnce(
createMockResponse(200, {}, gateDecisionBytes.buffer.slice(0)),
);
const retrieved = await adapter.getObject(artifactDigest);
expect(retrieved).toBeInstanceOf(Uint8Array);
expect(Buffer.compare(Buffer.from(retrieved), Buffer.from(gateDecisionBytes))).toBe(0);
});
it("verifyDurableSync returns false when artifact not in R2 (404)", async () => {
fetchSpy.mockResolvedValueOnce(createMockResponse(404));
const verified = await verifyDurableSync(artifactDigest, mockConfig);
expect(verified).toBe(false);
});
it("verifyDurableSync returns false on headObject error (500)", async () => {
fetchSpy.mockResolvedValueOnce(createMockResponse(500, {}, "Internal Server Error"));
await expect(verifyDurableSync(artifactDigest, mockConfig)).rejects.toThrow("CERT-R2-02");
});
it("verifyStoredObject reports size mismatch", async () => {
const adapter = createR2StorageAdapter(mockConfig);
fetchSpy.mockResolvedValueOnce(
createMockResponse(206, {
"content-range": "bytes 0-0/999",
etag: '"wrong-size"',
}),
);
const result = await verifyStoredObject(adapter, artifactDigest, gateDecisionBytes.byteLength);
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.ruleId).toBe("CERT-STORAGE-02");
expect(result.message).toContain("999");
}
});
it("Bearer token is used for all requests in the cycle", async () => {
const adapter = createR2StorageAdapter(mockConfig);
// put
fetchSpy.mockResolvedValueOnce(createMockResponse(200, { etag: '"e1"' }));
await adapter.putObject({
digest: artifactDigest,
bytes: gateDecisionBytes,
mediaType: "application/json",
});
// head (via GET + Range)
fetchSpy.mockResolvedValueOnce(
createMockResponse(206, {
"content-range": `bytes 0-0/${gateDecisionBytes.byteLength}`,
etag: '"e1"',
}),
);
await adapter.headObject(artifactDigest);
// get
fetchSpy.mockResolvedValueOnce(
createMockResponse(200, {}, gateDecisionBytes.buffer.slice(0)),
);
await adapter.getObject(artifactDigest);
// All 3 calls must use Bearer token
for (const call of fetchSpy.mock.calls) {
const init = call[1] as RequestInit;
const headers = init.headers as Record;
expect(headers["Authorization"]).toBe("Bearer test-api-token");
}
});
it("headObject uses GET with Range header (not HEAD)", async () => {
const adapter = createR2StorageAdapter(mockConfig);
fetchSpy.mockResolvedValueOnce(
createMockResponse(206, {
"content-range": "bytes 0-0/42",
etag: '"range-etag"',
}),
);
const result = await adapter.headObject(artifactDigest);
const init = fetchSpy.mock.calls[0][1] as RequestInit;
expect(init.method).toBe("GET");
const headers = init.headers as Record;
expect(headers["Range"]).toBe("bytes=0-0");
expect(result.exists).toBe(true);
expect(result.sizeBytes).toBe(42);
});
});