1234567import { beforeEach, describe, expect, it, vi } from "vitest";8import { MAX_CLOCK_SKEW_MS, clampSyncTimestamp } from "../shared/sync-limits";910describe("clampSyncTimestamp", () => {11 it("clamps a far-future timestamp to now + MAX_CLOCK_SKEW_MS", () => {12 const now = 1_750_000_000_000;13 const farFuture = now + 10_000_000;14 expect(clampSyncTimestamp(farFuture, now)).toBe(now + MAX_CLOCK_SKEW_MS);15 });1617 it("passes through timestamps within the skew window", () => {18 const now = 1_750_000_000_000;19 expect(clampSyncTimestamp(now - 1000, now)).toBe(now - 1000);20 expect(clampSyncTimestamp(now + MAX_CLOCK_SKEW_MS - 1, now)).toBe(21 now + MAX_CLOCK_SKEW_MS - 1,22 );23 });24});2526describe("applyMergedRemoteEntries — HV-1", () => {27 let storage: Record<string, unknown>;2829 beforeEach(() => {30 vi.resetModules();31 storage = {};3233 const chromeMock = {34 storage: {35 local: {36 get: vi.fn(async (key?: string | string[]) => {37 if (!key) return storage;38 if (typeof key === "string") return { [key]: storage[key] };39 return key.reduce<Record<string, unknown>>((result, entry) => {40 result[entry] = storage[entry];41 return result;42 }, {});43 }),44 set: vi.fn(async (next: Record<string, unknown>) => {45 Object.assign(storage, next);46 }),47 remove: vi.fn(async (keys: string | string[]) => {48 const list = Array.isArray(keys) ? keys : [keys];49 for (const key of list) delete storage[key];50 }),51 },52 },53 alarms: {54 clear: vi.fn(),55 create: vi.fn(),56 onAlarm: { addListener: vi.fn() },57 },58 idle: {59 setDetectionInterval: vi.fn(),60 onStateChanged: { addListener: vi.fn() },61 },62 } as unknown as typeof chrome;6364 (globalThis as { chrome?: typeof chrome }).chrome = chromeMock;6566 vi.stubGlobal(67 "fetch",68 vi.fn(async () => {69 throw new Error("network disabled in tests");70 }),71 );72 });7374 it("clamps a far-future remote modifiedAt so a local edit can win", async () => {75 const vault = await import("./vault");76 await vault.createVault("correct horse battery staple");7778 const local = await vault.addEntry({79 title: "Bank",80 username: "june",81 password: "strong-local-password",82 url: "https://bank.example.com",83 type: "login",84 });8586 87 88 89 90 91 vi.useFakeTimers({ toFake: ["Date"] });92 vi.setSystemTime(new Date(local.modifiedAt + 1000));93 try {94 const farFuture = Date.now() + 10_000_000;95 await vault.applyMergedRemoteEntries(96 [{ ...local, password: "attacker-password", modifiedAt: farFuture }],97 {},98 );99100 const merged = vault.getEntryById(local.id)!;101 102 103 expect(merged.modifiedAt).toBeLessThanOrEqual(104 Date.now() + MAX_CLOCK_SKEW_MS,105 );106107 108 109 await vault.updateEntry(local.id, { password: "honest-newer-password" });110 111 await vault.applyMergedRemoteEntries(112 [{ ...merged, password: "attacker-password", modifiedAt: farFuture }],113 {},114 );115 expect(vault.getEntryById(local.id)!.password).toBe(116 "honest-newer-password",117 );118 } finally {119 vi.useRealTimers();120 }121 });122123 it("clamps a far-future tombstone ts before applying", async () => {124 const vault = await import("./vault");125 await vault.createVault("correct horse battery staple");126127 const local = await vault.addEntry({128 title: "Forum",129 username: "june",130 password: "pw",131 url: "https://forum.example.com",132 type: "login",133 });134 const farFuture = Date.now() + 10_000_000;135 await vault.applyMergedRemoteEntries([], { [local.id]: farFuture });136137 const tombstoned = vault138 .getDeletedEntries()139 .find((e) => e.id === local.id)!;140 expect(tombstoned.deletedAt).toBeLessThanOrEqual(141 Date.now() + MAX_CLOCK_SKEW_MS,142 );143 });144145 it("applies remote entry changes quietly without queuing activity alerts", async () => {146 const vault = await import("./vault");147 const { getPendingSyncAlerts, clearPendingSyncAlerts } = await import(148 "./sync-alerts"149 );150 await vault.createVault("correct horse battery staple");151 await clearPendingSyncAlerts();152153 const local = await vault.addEntry({154 title: "Email",155 username: "june",156 password: "old-password",157 url: "https://mail.example.com",158 type: "login",159 });160 await vault.applyMergedRemoteEntries(161 [162 {163 ...local,164 password: "replaced-by-peer",165 modifiedAt: Date.now() + 1000,166 },167 ],168 {},169 { deviceName: "Pixel 9" },170 );171 await vault.applyMergedRemoteEntries(172 [],173 { [local.id]: Date.now() + 1000 },174 { deviceName: "Pixel 9" },175 );176 const alerts = await getPendingSyncAlerts();177 expect(alerts).toEqual([]);178 });179});