Files
ApiCore/ApiCore-Backend/src/honey-batches/honey-batch-amount-writes.spec.ts
T
Nick SzaunigandClaude Opus 5.5 16cc557462
Backend Image / build-and-push (push) Successful in 4m26s
Frontend Image / build-and-push (push) Successful in 7m19s
Manifests / validate (push) Successful in 17s
feat: T4b/T4c wave 9 - phenology reports, autopilot A4-A10, honey ledger, Telegram delivery
- 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]>
2026-10-14 21:13:23 +02:00

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,
));
});