phase-8:server code enhancements.

This commit is contained in:
Kazuma
2026-06-05 22:44:04 -04:00
parent 617703e91d
commit bd373ab69b
15 changed files with 781 additions and 94 deletions
+166
View File
@@ -0,0 +1,166 @@
/**
* Unit tests for MarketCallRepository
* Each test gets its own temp file so tests are fully isolated.
*/
import { test, after } from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync, rmSync, writeFileSync } from 'fs';
import { tmpdir } from 'os';
import { join } from 'path';
import { MarketCallRepository } from '../server/repositories/MarketCallRepository';
// ── Temp-file helpers ─────────────────────────────────────────────────────────
const tmpDirs: string[] = [];
function tempRepo(): MarketCallRepository {
const dir = mkdtempSync(join(tmpdir(), 'mkt-calls-test-'));
const path = join(dir, 'calls.json');
tmpDirs.push(dir);
return new MarketCallRepository(path);
}
after(() => {
for (const dir of tmpDirs) {
rmSync(dir, { recursive: true, force: true });
}
});
// ── 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 when file does not exist', () => {
const repo = tempRepo();
assert.deepEqual(repo.list(), []);
});
test('create() returns call with id, createdAt, and correct fields', () => {
const repo = tempRepo();
const call = repo.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 to disk — list() returns the created call', () => {
const repo = tempRepo();
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', () => {
// Write two calls directly with distinct timestamps to guarantee stable ordering.
const dir = mkdtempSync(join(tmpdir(), 'mkt-order-'));
tmpDirs.push(dir);
const path = join(dir, 'calls.json');
const older = {
id: 'old-id',
title: 'First',
quarter: 'Q1',
date: '2025-01-01',
thesis: 'A',
tickers: [],
snapshot: {},
createdAt: '2025-01-01T00:00:00.000Z',
};
const newer = {
id: 'new-id',
title: 'Second',
quarter: 'Q1',
date: '2025-01-02',
thesis: 'B',
tickers: [],
snapshot: {},
createdAt: '2025-01-02T00:00:00.000Z',
};
writeFileSync(path, JSON.stringify({ calls: [older, newer] }), 'utf8');
const repo = new MarketCallRepository(path);
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 = tempRepo();
const call = repo.create(CALL_INPUT);
const found = repo.get(call.id);
assert.ok(found, 'should find by id');
assert.equal(found!.id, call.id);
});
test('get() returns null for unknown id', () => {
const repo = tempRepo();
assert.equal(repo.get('nonexistent-id'), null);
});
test('delete() removes the call and returns true', () => {
const repo = tempRepo();
const call = repo.create(CALL_INPUT);
const ok = repo.delete(call.id);
assert.equal(ok, true);
assert.equal(repo.list().length, 0);
assert.equal(repo.get(call.id), null);
});
test('delete() returns false for unknown id', () => {
const repo = tempRepo();
assert.equal(repo.delete('no-such-id'), false);
});
test('delete() only removes the targeted call, leaves others intact', () => {
const repo = tempRepo();
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 = tempRepo();
const snapshot = { TLT: { price: 95.5, signal: '✅ Strong Buy' } };
const call = repo.create({ ...CALL_INPUT, snapshot } as any);
const found = repo.get(call.id)!;
assert.deepEqual(found.snapshot, snapshot);
});
test('create() sets default date when not provided', () => {
const repo = tempRepo();
const call = repo.create(CALL_INPUT);
assert.match(call.date, /^\d{4}-\d{2}-\d{2}$/);
});
test('create() uses provided date', () => {
const repo = tempRepo();
const call = repo.create({ ...CALL_INPUT, date: '2025-03-15' });
assert.equal(call.date, '2025-03-15');
});
test('concurrent writes: two rapid creates do not lose data', async () => {
const repo = tempRepo();
// Both writes happen synchronously (writeFileSync), so the second
// always sees the first. This test documents the behaviour.
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, 'both calls should be persisted');
const ids = new Set(list.map((c) => c.id));
assert.ok(ids.has(a.id), 'call A should be present');
assert.ok(ids.has(b.id), 'call B should be present');
});