import { readdirSync, readFileSync } from 'node:fs'; import { join, relative, sep } from 'node:path'; // Side effects run through the domain-event backbone. runAfterCommit and the // direct wrappers stay only for the legacy paths that have no event yet // (ARCHITECTURE assumption 11). A new caller must record an event instead. const SRC = join(__dirname, '..'); const DEFINITION = 'common/after-commit.ts'; function productionSources(dir: string): string[] { return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => { const path = join(dir, entry.name); if (entry.isDirectory()) { return productionSources(path); } return entry.name.endsWith('.ts') && !entry.name.endsWith('.spec.ts') ? [path] : []; }); } function withoutComments(source: string): string { return source.replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, ''); } function filesCalling(call: string): string[] { return productionSources(SRC) .filter((file) => withoutComments(readFileSync(file, 'utf8')).includes(call), ) .map((file) => relative(SRC, file).split(sep).join('/')) .filter((file) => file !== DEFINITION) .map((file) => `src/${file}`) .sort(); } describe('legacy after-commit paths', () => { it('runAfterCommit is only used by the remaining legacy paths', () => { expect(filesCalling('runAfterCommit(')).toEqual([ 'src/automation/automation.service.ts', 'src/webhooks/webhooks.service.ts', ]); }); it('no module enqueues a calendar entry directly any more', () => { expect(filesCalling('calendarSync.enqueue(')).toEqual([]); }); it('only the modules without an event still dispatch webhooks directly', () => { expect(filesCalling('webhooks.dispatch(')).toEqual([ 'src/colonies/colonies.service.ts', 'src/queens/queens.service.ts', 'src/storage/storage.service.ts', ]); }); it('apiaries.service.ts has no after-commit side effects', () => { for (const call of [ 'runAfterCommit(', 'webhooks.dispatch(', 'calendarSync.enqueue(', ]) { expect(filesCalling(call)).not.toContain( 'src/apiaries/apiaries.service.ts', ); } }); it('no longer defines the removed post-commit automation wrappers (only SWARM_MOOD_HIGH and STOCK_ITEM_LOW keep one)', () => { const removed = [ 'evaluateInspection', 'evaluateVarroaMeasurement', 'evaluateTreatment', 'evaluateFeeding', 'evaluateColonyHealth', 'evaluateRawMaterialLow', ].map( (name) => [ name, new RegExp( String.raw`\b(?:public\s+|private\s+)?async\s+${name}\s*\(`, ), ] as const, ); const definitions = productionSources(SRC).flatMap((file) => { const source = withoutComments(readFileSync(file, 'utf8')); return removed .filter(([, pattern]) => pattern.test(source)) .map(([name]) => name); }); expect(definitions).toEqual([]); }); });