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
@@ -0,0 +1,57 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { Prisma } from '../generated/prisma';
|
||||
import { claimBatch } from '../src/common/outbox/outbox-claims';
|
||||
import { PrismaModule } from '../src/prisma/prisma.module';
|
||||
import { PrismaService } from '../src/prisma/prisma.service';
|
||||
|
||||
const ROUNDS = 1000;
|
||||
|
||||
/**
|
||||
* "availableAt" is TIMESTAMP(3): a value written from SQL now() (stale
|
||||
* release, retry backoff, requeue) is ROUNDED to milliseconds, so it can lie
|
||||
* up to half a millisecond after the next statement's now(). A claim right
|
||||
* behind such a write must still find the row.
|
||||
*/
|
||||
describe('claimBatch and millisecond rounding (e2e)', () => {
|
||||
const correlationId = `claim-rounding-${Date.now()}-${Math.round(Math.random() * 1e6)}`;
|
||||
let moduleRef: TestingModule;
|
||||
let prisma: PrismaService;
|
||||
|
||||
beforeAll(async () => {
|
||||
moduleRef = await Test.createTestingModule({
|
||||
imports: [PrismaModule],
|
||||
}).compile();
|
||||
await moduleRef.init();
|
||||
prisma = moduleRef.get(PrismaService);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await prisma.domainEvent.deleteMany({ where: { correlationId } });
|
||||
await moduleRef.close();
|
||||
});
|
||||
|
||||
it('claims an entry whose availableAt was just set to now()', async () => {
|
||||
const event = await prisma.domainEvent.create({
|
||||
data: {
|
||||
type: 'TaskDue',
|
||||
aggregateType: 'Task',
|
||||
aggregateId: correlationId,
|
||||
payload: {},
|
||||
correlationId,
|
||||
},
|
||||
});
|
||||
let missed = 0;
|
||||
for (let round = 0; round < ROUNDS; round++) {
|
||||
await prisma.$executeRaw`UPDATE "DomainEvent" SET "status" = 'PENDING', "claimedAt" = NULL, "availableAt" = now() WHERE "id" = ${event.id}`;
|
||||
const claimed = await claimBatch(prisma, 'DomainEvent', {
|
||||
limit: 1,
|
||||
filter: Prisma.sql`"correlationId" = ${correlationId}`,
|
||||
orderBy: 'occurredAt',
|
||||
});
|
||||
if (claimed.length !== 1) {
|
||||
missed++;
|
||||
}
|
||||
}
|
||||
expect(missed).toBe(0);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user