/** * Unit tests for MarketCallRepository (SQLite-backed). * Each test gets its own in-memory database so tests are fully isolated. */ import { test } from 'node:test'; import assert from 'node:assert/strict'; import BetterSqlite3 from 'better-sqlite3'; import { MarketCallRepository } from '../server/repositories/MarketCallRepository'; // ── Helpers ─────────────────────────────────────────────────────────────────── const DDL = ` CREATE TABLE IF NOT EXISTS market_calls ( id TEXT PRIMARY KEY, title TEXT NOT NULL, quarter TEXT NOT NULL, date TEXT NOT NULL, thesis TEXT NOT NULL, tickers TEXT NOT NULL, snapshot TEXT NOT NULL, created_at TEXT NOT NULL ); `; function makeRepo(): MarketCallRepository { const db = new BetterSqlite3(':memory:'); db.pragma('journal_mode = WAL'); db.exec(DDL); return new MarketCallRepository(db); } // ── Fixtures ────────────────────────────────────────────────────────────────── const CALL_INPUT = { title: 'Rate pivot play', quarter: 'Q3 2025', thesis: 'Fed cuts expected — rotate into duration and growth.', tickers: ['TLT', 'QQQ'], }; // ── Tests ───────────────────────────────────────────────────────────────────── test('list() returns empty array on fresh db', () => { assert.deepEqual(makeRepo().list(), []); }); test('create() returns call with id, createdAt, and correct fields', () => { const call = makeRepo().create(CALL_INPUT); assert.ok(call.id, 'id should be set'); assert.ok(call.createdAt, 'createdAt should be set'); assert.equal(call.title, CALL_INPUT.title); assert.equal(call.quarter, CALL_INPUT.quarter); assert.equal(call.thesis, CALL_INPUT.thesis); assert.deepEqual(call.tickers, CALL_INPUT.tickers); }); test('create() persists — list() returns the created call', () => { const repo = makeRepo(); repo.create(CALL_INPUT); assert.equal(repo.list().length, 1); assert.equal(repo.list()[0].title, CALL_INPUT.title); }); test('list() returns calls newest-first', () => { const repo = makeRepo(); const db = (repo as any).db as BetterSqlite3.Database; // Insert two rows with distinct created_at values directly db.prepare( `INSERT INTO market_calls (id, title, quarter, date, thesis, tickers, snapshot, created_at) VALUES (?,?,?,?,?,?,?,?)`, ).run('old-id', 'First', 'Q1', '2025-01-01', 'A', '[]', '{}', '2025-01-01T00:00:00.000Z'); db.prepare( `INSERT INTO market_calls (id, title, quarter, date, thesis, tickers, snapshot, created_at) VALUES (?,?,?,?,?,?,?,?)`, ).run('new-id', 'Second', 'Q1', '2025-01-02', 'B', '[]', '{}', '2025-01-02T00:00:00.000Z'); const list = repo.list(); assert.equal(list[0].id, 'new-id', 'newer call should be first'); assert.equal(list[1].id, 'old-id', 'older call should be second'); }); test('get() returns the call by id', () => { const repo = makeRepo(); const call = repo.create(CALL_INPUT); const found = repo.get(call.id); assert.ok(found); assert.equal(found!.id, call.id); }); test('get() returns null for unknown id', () => { assert.equal(makeRepo().get('no-such-id'), null); }); test('delete() removes the call and returns true', () => { const repo = makeRepo(); const call = repo.create(CALL_INPUT); assert.equal(repo.delete(call.id), true); assert.equal(repo.list().length, 0); assert.equal(repo.get(call.id), null); }); test('delete() returns false for unknown id', () => { assert.equal(makeRepo().delete('no-such-id'), false); }); test('delete() only removes the targeted call', () => { const repo = makeRepo(); const a = repo.create({ ...CALL_INPUT, title: 'Keep me' }); const b = repo.create({ ...CALL_INPUT, title: 'Delete me' }); repo.delete(b.id); const list = repo.list(); assert.equal(list.length, 1); assert.equal(list[0].id, a.id); }); test('create() stores snapshot when provided', () => { const repo = makeRepo(); const snapshot = { TLT: { price: 95.5, signal: '✅ Strong Buy' } }; const call = repo.create({ ...CALL_INPUT, snapshot } as any); assert.deepEqual(repo.get(call.id)!.snapshot, snapshot); }); test('create() sets default date when not provided', () => { const call = makeRepo().create(CALL_INPUT); assert.match(call.date, /^\d{4}-\d{2}-\d{2}$/); }); test('create() uses provided date', () => { const call = makeRepo().create({ ...CALL_INPUT, date: '2025-03-15' }); assert.equal(call.date, '2025-03-15'); }); test('concurrent writes: two rapid creates both persist (SQLite WAL is concurrency-safe)', () => { const repo = makeRepo(); const a = repo.create({ ...CALL_INPUT, title: 'A' }); const b = repo.create({ ...CALL_INPUT, title: 'B' }); const list = repo.list(); assert.equal(list.length, 2); const ids = new Set(list.map((c) => c.id)); assert.ok(ids.has(a.id)); assert.ok(ids.has(b.id)); });