// Checks for the firm catalog sync in lib/firm-catalog.ts, against a stand-in // master on a local port and a throwaway database — never the live DB or the // real master. // // npm run check import { after, test } from 'node:test'; import assert from 'node:assert/strict'; import { mkdtempSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { registerHooks } from 'node:module'; import { createServer } from 'node:http'; import { once } from 'node:events'; // Resolve the extensionless local imports used by Next, without a build. function resolveTypes(specifier, context, nextResolve) { try { return nextResolve(specifier, context); } catch (err) { if (err.code !== 'ERR_MODULE_NOT_FOUND' || !specifier.startsWith('.')) throw err; return nextResolve(`${specifier}.ts`, context); } } registerHooks({ resolve: resolveTypes }); const directory = mkdtempSync(join(tmpdir(), 'autofirmer-catalog-')); process.env.AUTOFIRMER_DB = join(directory, 'test.sqlite'); after(() => rmSync(directory, { recursive: true, force: true })); const { getSetting, setSetting } = await import('../lib/db.ts'); const { syncCatalog, getCachedCatalog, getKeptRuleIdsForFirm, CATALOG_PARSER_VERSION } = await import('../lib/firm-catalog.ts'); const firm = (id, name, platform) => ({ id, name, platform, keptAccountRules: [], updatedAt: '2026-10-26T19:00:00.000Z', accountTypes: [{ id: `${id}-50k`, firmId: id, label: '50k', prefix: `${id.toUpperCase()}-`, updatedAt: '2026-10-26T19:00:00.000Z', accountSize: 50000, profitTarget: 3000, consistency: 0, minDayPnL: 0, minTradingDays: 0, maxLoss: 3000, maxPositionSize: 0, withdrawalStages: [], }], }); const apex = firm('apex', 'Apex', 'tradovate'); const vest = firm('vest', 'VestMarkets', 'vest'); // The master's catalog, answering ?since= as app/api/firms/catalog does, and // the `since` each sync sent (null = a full fetch). let served = { version: 41, updatedAt: '2026-10-26T19:20:00.000Z', firms: [apex, vest] }; const sinceSent = []; const server = createServer((req, res) => { const since = new URL(req.url, 'http://master').searchParams.get('since'); sinceSent.push(since); res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify(since !== null && Number(since) === served.version ? { version: served.version, updatedAt: served.updatedAt, unchanged: true } : served)); }); server.listen(0, '127.0.0.1'); await once(server, 'listening'); after(() => server.close()); const master = `http://127.0.0.1:${server.address().port}`; /** This machine's cache as some build stored it, read afresh as after a restart. */ function storedCache(payload) { setSetting('master_dashboard_url', master); setSetting('catalog_source_url', master); setSetting('catalog_version', String(payload.version)); setSetting('catalog_payload', JSON.stringify(payload)); globalThis.__firmCatalog = undefined; sinceSent.length = 0; } const names = () => getCachedCatalog().map((f) => f.name); test('a cache a pre-Vest build stored is fetched in full by a build that knows Vest', async (t) => { t.mock.method(console, 'log', () => {}); // Parser 2 skipped the Vest firm and said nothing of it; the master's // catalog hasn't changed since, so ?since= would answer "unchanged". storedCache({ version: 41, updatedAt: served.updatedAt, firms: [apex], parser: 2 }); assert.equal((await syncCatalog()).ok, true); assert.deepEqual(sinceSent, [null]); assert.deepEqual(names(), ['Apex', 'VestMarkets']); // Current from then on: the next sync asks ?since= again. await syncCatalog(); assert.deepEqual(sinceSent, [null, '41']); }); test('a skipped platform is named in the cache, and a build that knows it fetches in full', async (t) => { t.mock.method(console, 'log', () => {}); t.mock.method(console, 'warn', () => {}); // The master adds a firm on a platform this build doesn't know. served = { version: 42, updatedAt: '2026-10-27T15:00:00.000Z', firms: [apex, vest, firm('px', 'PX Firm', 'projectx')] }; storedCache({ version: 41, updatedAt: '2026-10-26T19:20:00.000Z', firms: [apex, vest], parser: CATALOG_PARSER_VERSION }); await syncCatalog(); assert.deepEqual(names(), ['Apex', 'VestMarkets']); assert.deepEqual(JSON.parse(getSetting('catalog_payload')).skippedPlatforms, ['projectx']); // While this build still doesn't know it, syncs stay incremental, and the // cache still vouches for the kept rule sets of the firms it holds. globalThis.__firmCatalog = undefined; sinceSent.length = 0; await syncCatalog(); assert.deepEqual(sinceSent, ['42']); assert.deepEqual(getKeptRuleIdsForFirm('apex'), []); // A build that knows the platform it skipped (Vest here) fetches in full. storedCache({ version: 42, updatedAt: served.updatedAt, firms: [apex], parser: CATALOG_PARSER_VERSION, skippedPlatforms: ['vest'] }); await syncCatalog(); assert.deepEqual(sinceSent, [null]); assert.ok(names().includes('VestMarkets')); });