feat: T4 wave 3 - notifications per user, suggestions API, varroa bands, split/queen events
- Schema III: per-user notifications (broadcast fan-out migration, category, link, requiredPermissions filter), notification preferences and settings endpoints, calendar withdrawal switch, occupiedFrames - Suggestions API: list/summary with permission visibility and paging, accept/dismiss/reopen incl. bulk, SuggestionView on all responses - Varroa traffic light by season and method, young colonies, treatment effect window, sample-based percentage; shared withdrawal-window helper on Berlin calendar days; GET /varroa-measurements/thresholds - ColonySplit and QueenAssigned domain events with calendar and webhook handlers - Outbox claims compare against now() rounded to timestamp(3); fixes the flaky e2e claims of rows written with SQL now() Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
d99a697739
commit
f468dc5f75
87 files changed
+6022
-510
No files matched your search
@@ -35,6 +35,7 @@ describe('Colonies (e2e)', () => {
|
||||
let apiaryBId: string;
|
||||
let colonyId: string;
|
||||
let childColonyId: string;
|
||||
const splitColonyIds: string[] = [];
|
||||
let colonyToMergeId: string;
|
||||
let healthColonyId: string;
|
||||
let raceColonyId: string;
|
||||
@@ -83,6 +84,7 @@ describe('Colonies (e2e)', () => {
|
||||
colonyToMergeId,
|
||||
healthColonyId,
|
||||
raceColonyId,
|
||||
...splitColonyIds,
|
||||
].filter(Boolean);
|
||||
// Rule tasks are written without a user - their audit entry and
|
||||
// TaskCreated are not covered by the userId cleanup below.
|
||||
@@ -190,6 +192,55 @@ describe('Colonies (e2e)', () => {
|
||||
expect((history.body as ColonyHistoryBody).lineageAsSource).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('records ColonySplit with the given day and queen source, and refuses a future day', async () => {
|
||||
await request(app.getHttpServer())
|
||||
.post(`/api/v1/colonies/${colonyId}/split`)
|
||||
.set('Authorization', `Bearer ${beekeeperToken}`)
|
||||
.send({
|
||||
code: `V-${Date.now()}-F`,
|
||||
colonyType: 'NUCLEUS_COLONY',
|
||||
date: '2999-01-01',
|
||||
})
|
||||
.expect(400);
|
||||
await request(app.getHttpServer())
|
||||
.post(`/api/v1/colonies/${colonyId}/split`)
|
||||
.set('Authorization', `Bearer ${beekeeperToken}`)
|
||||
.send({
|
||||
code: `V-${Date.now()}-T`,
|
||||
colonyType: 'NUCLEUS_COLONY',
|
||||
date: '2026-05-05T23:30:00+02:00',
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
const response = await request(app.getHttpServer())
|
||||
.post(`/api/v1/colonies/${colonyId}/split`)
|
||||
.set('Authorization', `Bearer ${beekeeperToken}`)
|
||||
.send({
|
||||
code: `V-${Date.now()}-B`,
|
||||
colonyType: 'NUCLEUS_COLONY',
|
||||
date: '2026-05-05',
|
||||
queenSource: 'QUEEN_CELL',
|
||||
})
|
||||
.expect(201);
|
||||
const splitId = (response.body as ColonyBody).id;
|
||||
splitColonyIds.push(splitId);
|
||||
|
||||
const lineage = await prisma.colonyLineage.findFirstOrThrow({
|
||||
where: { toColonyId: splitId },
|
||||
});
|
||||
expect(lineage.queenSource).toBe('QUEEN_CELL');
|
||||
expect(lineage.date.toISOString().slice(0, 10)).toBe('2026-05-05');
|
||||
const event = await prisma.domainEvent.findFirstOrThrow({
|
||||
where: { type: 'ColonySplit', aggregateId: splitId },
|
||||
});
|
||||
expect(event.payload).toEqual({
|
||||
parentColonyId: colonyId,
|
||||
lineageId: lineage.id,
|
||||
queenSource: 'QUEEN_CELL',
|
||||
splitOn: '2026-05-05',
|
||||
});
|
||||
});
|
||||
|
||||
it('merges another colony into the surviving one', async () => {
|
||||
const otherColony = await request(app.getHttpServer())
|
||||
.post('/api/v1/colonies')
|
||||
|
||||
Reference in new issue
Block a user