import { DatabaseSync } from 'node:sqlite' import { describe, expect, it } from 'vitest' import type { GiteaIssue } from '../gitea/types.js' import { extractLabelFacts } from '../labels/label-schema.js' import { type CacheDriver, initCache, readIssue, upsertIssue } from './cache-v0.js' /** Adapt node:sqlite's DatabaseSync to the CacheDriver seam (main uses better-sqlite3). */ function memoryDriver(): CacheDriver { const db = new DatabaseSync(':memory:') return { exec: (sql) => db.exec(sql), run: (sql, params = []) => { db.prepare(sql).run(...(params as never[])) }, get: (sql, params = []) => db.prepare(sql).get(...(params as never[])) as Record | undefined, all: (sql, params = []) => db.prepare(sql).all(...(params as never[])) as Record[], } } function issue(over: Partial = {}): GiteaIssue { const labels = over.labels ?? ['est/5d', 'p/1', 'deadline/hard'] return { number: 42, title: 'Monte Carlo engine', body: 'percentile bands', state: 'open', labels, facts: extractLabelFacts(labels), milestone: { id: 7, title: 'P2 — Scheduler', dueOn: '2026-09-01T00:00:00Z' }, assignee: 'christian', assignees: ['christian'], createdAt: '2026-07-08T00:00:00Z', updatedAt: '2026-07-08T01:00:00Z', closedAt: null, url: 'https://gitea/christian/commitea/issues/42', ...over, } } describe('cache-v0', () => { it('mirrors one issue and reads its facts back through extractLabelFacts (acceptance)', () => { const d = memoryDriver() initCache(d) upsertIssue(d, issue({ labels: ['est/5d', 'p/1', 'deadline/hard'] })) const back = readIssue(d, 42)! expect(back.labels).toEqual(['est/5d', 'p/1', 'deadline/hard']) // facts are re-derived on read, not stored expect(back.facts.estimateDays).toBe(5) expect(back.facts.priority).toBe(1) expect(back.facts.hardDeadline).toBe(true) // the rest of the domain shape round-trips expect(back.milestone).toEqual({ id: 7, title: 'P2 — Scheduler', dueOn: '2026-09-01T00:00:00Z' }) expect(back.assignee).toBe('christian') expect(back.state).toBe('open') }) it('re-derives facts from the current labels after a re-reconcile (upsert in place, no dup)', () => { const d = memoryDriver() initCache(d) upsertIssue(d, issue({ labels: ['est/2d', 'p/3'] })) // reconcile again with changed labels + closed upsertIssue(d, issue({ labels: ['est/8d', 'p/1'], state: 'closed', closedAt: '2026-07-09T00:00:00Z' })) expect(d.all('SELECT number FROM issues')).toHaveLength(1) // upsert by number, not a second row const back = readIssue(d, 42)! expect(back.facts.estimateDays).toBe(8) expect(back.facts.priority).toBe(1) expect(back.facts.hardDeadline).toBe(false) // deadline/hard dropped expect(back.state).toBe('closed') expect(back.closedAt).toBe('2026-07-09T00:00:00Z') }) it('reads an issue with no milestone / empty labels', () => { const d = memoryDriver() initCache(d) upsertIssue(d, issue({ number: 9, labels: [], milestone: null, assignee: null, assignees: [] })) const back = readIssue(d, 9)! expect(back.milestone).toBeNull() expect(back.labels).toEqual([]) expect(back.facts.estimateDays).toBeNull() expect(back.assignee).toBeNull() }) it('returns null for an uncached issue', () => { const d = memoryDriver() initCache(d) expect(readIssue(d, 999)).toBeNull() }) })