- Veterinary medicine catalogue, lots and append-only movement ledger (rest always computed; corrections as counter-bookings; inactive medicines take no new stock; unit must match on lot moves). - Documents of medicine lots, authority notices and restrictions need the domain right on top of documents:* (403 before any lookup). - Label particulars: pure checkLabel with six mandatory gaps and four warnings, AFM font metrics, Berlin-day dates, geometric reading order for split addresses, new master-data fields with row-locked updates. - Herd events ColonyCreated, ColonyMoved, ApiaryChanged and TskReferenceDayReached (ids only, never forwarded to webhooks); CSV import emits ColonyCreated with audit. - ARCHITECTURE.md §12 Compliance (T6), Annahmen 59–70. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
95 lines
3.0 KiB
TypeScript
95 lines
3.0 KiB
TypeScript
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([]);
|
|
});
|
|
});
|