import { expect, test } from "@playwright/test"; test("M7-05 restores old revision, hash and bytes after every storage save interruption", async ({ page }) => { await page.goto("/"); const result = await page.evaluate(async () => { const { StorageClient } = await import("/src/storage/StorageClient.ts"); const projectId = `m7-save-interrupt-${Date.now()}-${Math.random().toString(16).slice(2)}`; const oldBytes = Uint8Array.from([0x42, 0x4c, 0x45, 0x4e, 0x44, 7, 7, 7]); const nextBytes = Uint8Array.from([0x42, 0x4c, 0x45, 0x4e, 0x44, 8, 8, 8, 8]); const initial = new StorageClient(); const baseline = await initial.saveProject(projectId, 7, oldBytes.slice().buffer); initial.terminate(); const observations = []; for (const faultAt of ["after-stage", "after-scene-commit", "before-metadata-commit"] as const) { const attempt = new StorageClient(); let error = ""; try { await attempt.saveProject(projectId, 8, nextBytes.slice().buffer, faultAt); } catch (reason) { error = reason instanceof Error ? reason.message : String(reason); } attempt.terminate(); const restarted = new StorageClient(); const restored = await restarted.readProject(projectId); restarted.terminate(); observations.push({ faultAt, error, revision: restored.revision, sha256: restored.sha256, bytes: Array.from(new Uint8Array(restored.buffer)), recovered: restored.recovered, }); } const completed = new StorageClient(); const committed = await completed.saveProject(projectId, 8, nextBytes.slice().buffer); const reopened = await completed.readProject(projectId); completed.terminate(); return { baseline: { revision: baseline.revision, sha256: baseline.sha256, bytes: Array.from(oldBytes) }, observations, committed: { revision: committed.revision, sha256: committed.sha256, reopenedRevision: reopened.revision, reopenedSha256: reopened.sha256 }, }; }); expect(result.observations).toHaveLength(3); for (const item of result.observations) { expect(item.error).toContain(`PROJECT_SAVE_FAULT_INJECTED: ${item.faultAt}`); expect(item.revision).toBe(result.baseline.revision); expect(item.sha256).toBe(result.baseline.sha256); expect(item.bytes).toEqual(result.baseline.bytes); expect(item.recovered).toBe(false); } expect(result.committed.revision).toBe(8); expect(result.committed.reopenedRevision).toBe(8); expect(result.committed.sha256).toBe(result.committed.reopenedSha256); expect(result.committed.sha256).not.toBe(result.baseline.sha256); });