// Draft unit tests for HV-1 — sync timestamp clamp + remote-mutation alerts. // Target location after integration: src/background/vault.sync-clamp.test.ts // (or merged into the existing vault sync tests). Uses the same chrome // storage mock harness as src/background/vault.test.ts. import { beforeEach, describe, expect, it } from "vitest"; import { MAX_CLOCK_SKEW_MS, clampSyncTimestamp, } from "../shared/sync-limits"; import { applyMergedRemoteEntries, addEntry, createVault, getEntries, getEntryById, } from "./vault"; import { getPendingSyncAlerts, clearPendingSyncAlerts } from "./sync-alerts"; describe("clampSyncTimestamp", () => { it("clamps a far-future timestamp to now + MAX_CLOCK_SKEW_MS", () => { const now = 1_750_000_000_000; const farFuture = now + 10_000_000; expect(clampSyncTimestamp(farFuture, now)).toBe(now + MAX_CLOCK_SKEW_MS); }); it("passes through timestamps within the skew window", () => { const now = 1_750_000_000_000; expect(clampSyncTimestamp(now - 1000, now)).toBe(now - 1000); expect(clampSyncTimestamp(now + MAX_CLOCK_SKEW_MS - 1, now)).toBe( now + MAX_CLOCK_SKEW_MS - 1, ); }); }); describe("applyMergedRemoteEntries — HV-1", () => { beforeEach(async () => { // chrome.storage mock reset per the existing vault.test.ts harness. await createVault("correct horse battery staple"); await clearPendingSyncAlerts(); }); it("clamps a far-future remote modifiedAt so a local edit can win", async () => { const local = await addEntry({ title: "Bank", username: "june", password: "strong-local-password", url: "https://bank.example.com", type: "login", }); const farFuture = Date.now() + 10_000_000; await applyMergedRemoteEntries( [{ ...local, password: "attacker-password", modifiedAt: farFuture }], {}, ); const merged = getEntryById(local.id)!; // The remote value wins NOW (it is newer than the local edit), but its // timestamp is clamped — it cannot pin the far future. expect(merged.modifiedAt).toBeLessThanOrEqual(Date.now() + MAX_CLOCK_SKEW_MS); // A subsequent local edit (monotonic clock) beats the clamped value. const { updateEntry } = await import("./vault"); await updateEntry(local.id, { password: "honest-newer-password" }); // Re-sync the stale attacker value — it must NOT win again. await applyMergedRemoteEntries( [{ ...merged, password: "attacker-password", modifiedAt: farFuture }], {}, ); expect(getEntryById(local.id)!.password).toBe("honest-newer-password"); }); it("clamps a far-future tombstone ts before applying", async () => { const local = await addEntry({ title: "Forum", username: "june", password: "pw", url: "https://forum.example.com", type: "login", }); const farFuture = Date.now() + 10_000_000; await applyMergedRemoteEntries([], { [local.id]: farFuture }); const deleted = getEntries().length === 0 ? undefined : undefined; // entry is soft-deleted — read via getDeletedEntries void deleted; const { getDeletedEntries } = await import("./vault"); const tombstoned = getDeletedEntries().find((e) => e.id === local.id)!; expect(tombstoned.deletedAt).toBeLessThanOrEqual( Date.now() + MAX_CLOCK_SKEW_MS, ); }); it("records a REMOTE_PASSWORD_CHANGE alert when a remote replaces a local password", async () => { const local = await addEntry({ title: "Email", username: "june", password: "old-password", url: "https://mail.example.com", type: "login", }); await applyMergedRemoteEntries( [{ ...local, password: "replaced-by-peer", modifiedAt: Date.now() + 1000 }], {}, { deviceName: "Pixel 9" }, ); const alerts = await getPendingSyncAlerts(); expect(alerts).toHaveLength(1); expect(alerts[0]).toMatchObject({ type: "REMOTE_PASSWORD_CHANGE", entryId: local.id, entryTitle: "Email", deviceName: "Pixel 9", }); }); it("records a REMOTE_DELETE alert when a remote tombstone deletes a live entry", async () => { const local = await addEntry({ title: "Shop", username: "june", password: "pw", url: "https://shop.example.com", type: "login", }); await applyMergedRemoteEntries([], { [local.id]: Date.now() + 1000 }, { deviceName: "Pixel 9", }); const alerts = await getPendingSyncAlerts(); expect(alerts).toHaveLength(1); expect(alerts[0]).toMatchObject({ type: "REMOTE_DELETE", entryId: local.id, entryTitle: "Shop", deviceName: "Pixel 9", }); }); });