- Phenology reports API with corrections and revokes; recompute per apiary via PhenologyRecomputeRequested, allowBackward for corrections; season changes are announced in the morning digest (changes since the previous digest, each once) - Season autopilot A4-A10 with per-apiary sync (Apiary before Colony locks) and WinterCheckRecorded - Honey batch ledger at runtime: initial amount, BOTTLING bookings, manual movements, one gram rounding, untracked bottlings never book - Telegram delivery: planning, Berlin delivery times incl. DST, quiet hours, permission re-check and stale skip at send time, rate limits, cleanup of old deliveries - ARCHITECTURE.md: assumptions 47-50 and updated sections 11.x Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
50 lines
1.7 KiB
TypeScript
50 lines
1.7 KiB
TypeScript
import { readdirSync, readFileSync } from 'node:fs';
|
|
import { join, relative, sep } from 'node:path';
|
|
|
|
// HoneyBatch.amount is the running rest of the movement book (spec 7.7): only
|
|
// HoneyBatchLedgerService writes it. A second writer would break the
|
|
// invariant amount = initialAmount + sum(quantity) / 1000.
|
|
//
|
|
// The regexes see an `amount` inside the call itself. A spread such as
|
|
// `data: { ...dto }` is invisible to them; honey-batches.service.spec.ts
|
|
// covers that case.
|
|
const SRC = join(__dirname, '..');
|
|
const LEDGER = 'src/honey-batches/honey-batch-ledger.service.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 writersOf(...patterns: RegExp[]): string[] {
|
|
return productionSources(SRC)
|
|
.filter((file) => {
|
|
const source = readFileSync(file, 'utf8');
|
|
return patterns.some((pattern) => pattern.test(source));
|
|
})
|
|
.map((file) => `src/${relative(SRC, file).split(sep).join('/')}`)
|
|
.sort();
|
|
}
|
|
|
|
describe('honey batch amount writes', () => {
|
|
it('honey batch amount is written only by the ledger', () =>
|
|
expect(
|
|
writersOf(
|
|
/honeyBatch\.(update|updateMany|upsert)\([^)]*amount/s,
|
|
/UPDATE "HoneyBatch"[^;]*"amount"/,
|
|
),
|
|
).toEqual([LEDGER]));
|
|
|
|
it('the guard sees a write: the ledger itself is found', () =>
|
|
expect(writersOf(/honeyBatch\.update\(\{[^)]*data: \{ amount/s)).toContain(
|
|
LEDGER,
|
|
));
|
|
});
|