1376c2dee8
Audit-pass over CODE-AUDIT.md (~48 items closed this pass; all verified — typecheck + 234 tests + eslint + prettier green). Correctness / bugs: - B3: match the release checksum to the asset's filename line (no wrong-hash verify) - B4: newline-safe metadata probe (one --print with a unit-separator delimiter) - B5 / L88: guard the meta event against canceled items; progress no longer promotes a queued item outside pump() - B7: cookie-login promise always resolves (handles destroy-without-close) - L146: trim parser rejects >2 colon-group times; M36: Library selection counts only actionable rows - L11 / L50 / L156 / L57 / L159 / L15 / L3: live queue count, empty-cookie message, schedule picker min, dead-code/comment cleanup Type safety: - Enable noUncheckedIndexedAccess + noFallthroughCasesInSwitch (15 real edge cases fixed) Resilience / Windows / metadata: - R5: settings write failure handled (no unhandled IPC rejection; reconciles to truth) - W1 / W5 / W6: min window size, seeded folder picker, parented sign-in window; L147 dead macOS branches removed - CL1: shared stdout markers; package/builder metadata (license, homepage, repository, copyright, tsbuildinfo glob) Copy / docs / tests: - M37 / SR9 dev-jargon cleanup in hints; M8 / M25 / M26 / L66 / L80 / L81 reconciled - New unit tests for L35 (isValidMediaItem) and L36 (compareVersions) This commit also checkpoints the previously-uncommitted feat/tray-background-clipboard work it builds on: background running + auto-download, library clipboard detection, tray, binary management & library scale, credential encryption at rest, the shared jsonStore and ui/ primitives, and the eslint/prettier tooling. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
61 lines
2.0 KiB
TypeScript
61 lines
2.0 KiB
TypeScript
import { describe, it, expect, afterEach } 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('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)
|
|
})
|
|
})
|