import { describe, it, expect, afterEach, vi } from 'vitest' import { mkdtempSync, rmSync, writeFileSync, readdirSync } from 'fs' import { tmpdir } from 'os' import { join } from 'path' import { readJsonArraySafe, writeJsonAtomic } from '../src/main/jsonStore' interface Row { id: string } const isRow = (o: unknown): o is Row => typeof o === 'object' && o !== null && typeof (o as Row).id === 'string' describe('jsonStore atomic write + safe read', () => { const dirs: string[] = [] function tmp(): string { const d = mkdtempSync(join(tmpdir(), 'aerofetch-jsonstore-')) dirs.push(d) return d } afterEach(() => { for (const d of dirs.splice(0)) { try { rmSync(d, { recursive: true, force: true }) } catch { /* ignore */ } } }) it('round-trips a valid array (R1 write → read)', () => { const file = join(tmp(), 'data.json') writeJsonAtomic(file, [{ id: 'a' }, { id: 'b' }]) expect(readJsonArraySafe(file, isRow)).toEqual([{ id: 'a' }, { id: 'b' }]) }) it('leaves no .tmp file behind (atomic temp+rename)', () => { const d = tmp() writeJsonAtomic(join(d, 'data.json'), [{ id: 'a' }]) expect(readdirSync(d)).toEqual(['data.json']) }) it('returns [] for a missing file (first run)', () => { expect(readJsonArraySafe(join(tmp(), 'nope.json'), isRow)).toEqual([]) }) it('drops rows that fail validation', () => { const file = join(tmp(), 'data.json') writeFileSync(file, JSON.stringify([{ id: 'a' }, { nope: 1 }, { id: 'c' }])) expect(readJsonArraySafe(file, isRow)).toEqual([{ id: 'a' }, { id: 'c' }]) }) it('reports success/failure instead of swallowing a bad write (R6)', () => { const d = tmp() const err = vi.spyOn(console, 'error').mockImplementation(() => {}) try { // A normal write lands and reports true, with nothing logged. expect(writeJsonAtomic(join(d, 'data.json'), [{ id: 'a' }])).toBe(true) expect(err).not.toHaveBeenCalled() // A target under a non-existent directory can't be written — it must report // false (so a caller can warn/retry) and log, not throw or silently drop it. expect(writeJsonAtomic(join(d, 'no', 'such', 'dir', 'data.json'), [{ id: 'a' }])).toBe(false) expect(err).toHaveBeenCalledOnce() } finally { err.mockRestore() } // No stray .tmp left behind by the failed write. expect(readdirSync(d)).toEqual(['data.json']) }) it('backs up a corrupt file instead of silently wiping it (R2)', () => { const d = tmp() const file = join(d, 'data.json') writeFileSync(file, '{ truncated, not valid json') expect(readJsonArraySafe(file, isRow)).toEqual([]) const backups = readdirSync(d).filter((f) => f.startsWith('data.json.corrupt-')) expect(backups.length).toBe(1) }) })