import { beforeEach, describe, expect, it, vi } from 'vitest'; import { readDevices, addDevice, revokeDevice } from '../devices'; import type { GitHost } from '../git-host'; function makeGitHost(devicesJson = '{"devices":[]}'): GitHost { // Per-path storage — revokeDevice writes devices.json AND revoked.json, // so a single slot would corrupt the second read. const files = new Map(); files.set('.relicario/devices.json', devicesJson); return { readFile: vi.fn().mockImplementation(async (path: string) => { const content = files.get(path); if (content === undefined) throw new Error(`404: ${path}`); return new TextEncoder().encode(content); }), writeFile: vi.fn().mockImplementation(async (path: string, bytes: Uint8Array) => { files.set(path, new TextDecoder().decode(bytes)); }), deleteFile: vi.fn(), listDir: vi.fn(), putBlob: vi.fn(), getBlob: vi.fn(), deleteBlob: vi.fn(), }; } describe('devices', () => { it('readDevices returns empty array when file missing', async () => { const host = makeGitHost(); (host.readFile as ReturnType).mockRejectedValueOnce(new Error('404')); const result = await readDevices(host); expect(result).toEqual([]); }); it('readDevices parses existing devices', async () => { const host = makeGitHost('{"devices":[{"name":"CLI","public_key":"abc123","added_at":1000}]}'); const result = await readDevices(host); expect(result).toHaveLength(1); expect(result[0].name).toBe('CLI'); }); it('addDevice appends to list', async () => { const host = makeGitHost(); await addDevice(host, { name: 'Chrome', public_key: 'def456', added_at: 2000 }); expect(host.writeFile).toHaveBeenCalled(); const written = (host.writeFile as ReturnType).mock.calls[0][1]; const parsed = JSON.parse(new TextDecoder().decode(written)); expect(parsed.devices).toHaveLength(1); expect(parsed.devices[0].name).toBe('Chrome'); }); it('addDevice rejects duplicate name', async () => { const host = makeGitHost('{"devices":[{"name":"Chrome","public_key":"abc","added_at":1000}]}'); await expect(addDevice(host, { name: 'Chrome', public_key: 'xyz', added_at: 2000 })) .rejects.toThrow(/already exists/); }); it('revokeDevice removes by name', async () => { const host = makeGitHost('{"devices":[{"name":"CLI","public_key":"a","added_at":1},{"name":"Chrome","public_key":"b","added_at":2}]}'); await revokeDevice(host, 'CLI'); const written = (host.writeFile as ReturnType).mock.calls[0][1]; const parsed = JSON.parse(new TextDecoder().decode(written)); expect(parsed.devices).toHaveLength(1); expect(parsed.devices[0].name).toBe('Chrome'); }); it('revokeDevice throws if not found', async () => { const host = makeGitHost(); await expect(revokeDevice(host, 'nonexistent')).rejects.toThrow(/not found/); }); });