diff --git a/ApiCore-Backend/test/daily-evaluation.e2e-spec.ts b/ApiCore-Backend/test/daily-evaluation.e2e-spec.ts index 0f35ed1..260951f 100644 --- a/ApiCore-Backend/test/daily-evaluation.e2e-spec.ts +++ b/ApiCore-Backend/test/daily-evaluation.e2e-spec.ts @@ -24,11 +24,13 @@ const NOTHING_NEW = { }; /** - * The job evaluates the whole database. The e2e files share it and run in - * parallel, so the evaluation runs on days in 2011 and 2012: no other file has - * records there (autopilot and reminders own 2001; this file used to share it, - * so its "nothing new" counts picked up their colonies and its phenology step - * wrote season rows for their apiaries), and the events of this file stay its own. Called through + * The job evaluates the whole database, and the expiry step expires EVERY open + * suggestion whose expiresAt lies before the given day. The e2e files share + * the database and run in parallel, so this file runs on days in 1991 and + * 1992: earlier than any date any other file uses (autopilot and reminders + * use 2001, others 2002 and later). A later year would let the expiry step + * expire their open suggestions (and count them in our totals), and an + * "own" year in between would still see 2001's records. Called through * TimeEventsStep.run(day) instead of runCli, which would also write JobRun * rows that test/jobs.e2e-spec.ts counts. */ @@ -68,7 +70,7 @@ describe('daily-evaluation (e2e)', () => { phenologyStep = app.get(PhenologyStep); expiryStep = app.get(SuggestionsExpiryStep); // The real AutopilotStep, but on the colonies of this file only: other - // files share the database and must not be reconciled on a 2011 day. + // files share the database and must not be reconciled on a 1991 day. autopilotStep = new AutopilotStep( { colony: { @@ -109,18 +111,18 @@ describe('daily-evaluation (e2e)', () => { }); ids[`queen:${name}`] = created.id; }; - await queen('aged', { hatchYear: 2009, status: 'ACTIVE' }); - await queen('young', { hatchYear: 2010, status: 'ACTIVE' }); - await queen('reserve', { hatchYear: 2009, status: 'RESERVE' }); + await queen('aged', { hatchYear: 1989, status: 'ACTIVE' }); + await queen('young', { hatchYear: 1990, status: 'ACTIVE' }); + await queen('reserve', { hatchYear: 1989, status: 'RESERVE' }); - // Living, created 2011-04-20 and never measured: varroa due 2011-05-18. + // Living, created 1991-04-20 and never measured: varroa due 1991-05-18. const living = await prisma.colony.create({ data: { code: `DE-E2E-${suffix}-living`, apiaryId: apiary.id, colonyType: 'PRODUCTION_COLONY', currentQueenId: ids['queen:aged'], - createdAt: d('2011-04-20'), + createdAt: d('1991-04-20'), }, }); ids.living = living.id; @@ -130,15 +132,15 @@ describe('daily-evaluation (e2e)', () => { apiaryId: apiary.id, colonyType: 'PRODUCTION_COLONY', status: 'DECEASED', - createdAt: d('2011-04-20'), + createdAt: d('1991-04-20'), }, }); ids.dead = dead.id; - // Inspected 2011-04-28, in the swarm season (every 7 days): the next - // inspection is due 2011-05-05 and overdue on 2011-05-06. + // Inspected 1991-04-28, in the swarm season (every 7 days): the next + // inspection is due 1991-05-05 and overdue on 1991-05-06. for (const colonyId of [living.id, dead.id]) { await prisma.inspection.create({ - data: { colonyId, userId: user.id, date: d('2011-04-28') }, + data: { colonyId, userId: user.id, date: d('1991-04-28') }, }); } @@ -159,18 +161,18 @@ describe('daily-evaluation (e2e)', () => { }); ids[`treatment:${name}`] = created.id; }; - // 2011-05-10 + 7 = 2011-05-17, in the window of 2011-05-20. + // 1991-05-10 + 7 = 1991-05-17, in the window of 1991-05-20. await treatment('ending', { - date: d('2011-05-08'), - endDate: d('2011-05-10'), + date: d('1991-05-08'), + endDate: d('1991-05-10'), withdrawalPeriodDays: 7, }); - // 2011-05-01 + 7 = 2011-05-08, before the window. + // 1991-05-01 + 7 = 1991-05-08, before the window. await treatment('ended-earlier', { - date: d('2011-05-01'), + date: d('1991-05-01'), withdrawalPeriodDays: 7, }); - await treatment('no-withdrawal', { date: d('2011-05-15') }); + await treatment('no-withdrawal', { date: d('1991-05-15') }); const task = async ( name: string, @@ -181,8 +183,8 @@ describe('daily-evaluation (e2e)', () => { }); ids[`task:${name}`] = created.id; }; - await task('open', { dueDate: d('2011-05-18'), status: 'OPEN' }); - await task('done', { dueDate: d('2011-05-18'), status: 'DONE' }); + await task('open', { dueDate: d('1991-05-18'), status: 'OPEN' }); + await task('done', { dueDate: d('1991-05-18'), status: 'DONE' }); }); afterAll(async () => { @@ -224,10 +226,10 @@ describe('daily-evaluation (e2e)', () => { }); it('running the job twice yields exactly one WithdrawalEnded per treatment and day', async () => { - const first = await step.run('2011-05-20'); - const second = await step.run('2011-05-20'); + const first = await step.run('1991-05-20'); + const second = await step.run('1991-05-20'); // The next day the occurrence is still in the window - and still known. - const nextDay = await step.run('2011-05-21'); + const nextDay = await step.run('1991-05-21'); expect(first.WithdrawalEnded).toBeGreaterThanOrEqual(1); expect(second).toEqual(NOTHING_NEW); @@ -236,8 +238,8 @@ describe('daily-evaluation (e2e)', () => { { type: 'WithdrawalEnded', aggregateType: 'Treatment', - payload: { colonyId: ids.living, endedOn: '2011-05-17' }, - dedupeKey: `WithdrawalEnded:${ids['treatment:ending']}:2011-05-17`, + payload: { colonyId: ids.living, endedOn: '1991-05-17' }, + dedupeKey: `WithdrawalEnded:${ids['treatment:ending']}:1991-05-17`, actorUserId: null, }, ]); @@ -280,11 +282,11 @@ describe('daily-evaluation (e2e)', () => { afterEach(async () => { await prisma.notification.deleteMany({ - where: { relatedType: 'ComplianceItem', relatedId: 'tsk-report:2011' }, + where: { relatedType: 'ComplianceItem', relatedId: 'tsk-report:1991' }, }); - await prisma.authorityNotice.deleteMany({ where: { year: 2011 } }); + await prisma.authorityNotice.deleteMany({ where: { year: 1991 } }); await prisma.domainEvent.deleteMany({ - where: { aggregateId: 'tsk:2011' }, + where: { aggregateId: 'tsk:1991' }, }); await prisma.complianceSettings.update({ where: { id: 'singleton' }, @@ -293,25 +295,25 @@ describe('daily-evaluation (e2e)', () => { }); async function expectOneDraftAndNotices(): Promise { - expect(await eventsOf('tsk:2011')).toEqual([ + expect(await eventsOf('tsk:1991')).toEqual([ { type: 'TskReferenceDayReached', aggregateType: 'Compliance', - payload: { year: 2011, referenceDay: '2011-03-03' }, - dedupeKey: 'TskReferenceDayReached:tsk:2011:2011-03-03', + payload: { year: 1991, referenceDay: '1991-03-03' }, + dedupeKey: 'TskReferenceDayReached:tsk:1991:1991-03-03', actorUserId: null, }, ]); const drafts = await prisma.authorityNotice.findMany({ - where: { kind: 'TSK_REPORT', year: 2011 }, + where: { kind: 'TSK_REPORT', year: 1991 }, }); expect(drafts).toHaveLength(1); expect(drafts[0]).toMatchObject({ status: 'DRAFT', - openKey: 'TSK_REPORT:2011', + openKey: 'TSK_REPORT:1991', }); const notifications = await prisma.notification.findMany({ - where: { relatedType: 'ComplianceItem', relatedId: 'tsk-report:2011' }, + where: { relatedType: 'ComplianceItem', relatedId: 'tsk-report:1991' }, select: { userId: true, priority: true, category: true, message: true }, }); // One copy per recipient, written once - and every permitted user that @@ -333,17 +335,17 @@ describe('daily-evaluation (e2e)', () => { category: 'COMPLIANCE', }); expect(row.message).toMatch( - /^TSK-Stichtag 03\.03\.2011: \d+ Völker gezählt\. Bitte prüfen und melden\.$/, + /^TSK-Stichtag 03\.03\.1991: \d+ Völker gezählt\. Bitte prüfen und melden\.$/, ); } } it('daily evaluation twice on the reference day: one event, one draft', async () => { - const first = await step.run('2011-03-03'); - const second = await step.run('2011-03-03'); - await drainDomainEvents(app, ['tsk:2011']); - await step.run('2011-03-04'); - await drainDomainEvents(app, ['tsk:2011']); + const first = await step.run('1991-03-03'); + const second = await step.run('1991-03-03'); + await drainDomainEvents(app, ['tsk:1991']); + await step.run('1991-03-04'); + await drainDomainEvents(app, ['tsk:1991']); expect(first.TskReferenceDayReached).toBe(1); expect(second.TskReferenceDayReached).toBe(0); @@ -351,9 +353,9 @@ describe('daily-evaluation (e2e)', () => { }); it('catches up a missed reference day on the 5th exactly once', async () => { - const catchUp = await step.run('2011-03-05'); - const again = await step.run('2011-03-05'); - await drainDomainEvents(app, ['tsk:2011']); + const catchUp = await step.run('1991-03-05'); + const again = await step.run('1991-03-05'); + await drainDomainEvents(app, ['tsk:1991']); expect(catchUp.TskReferenceDayReached).toBe(1); expect(again.TskReferenceDayReached).toBe(0); @@ -362,14 +364,14 @@ describe('daily-evaluation (e2e)', () => { }); it('records due tasks and varroa checks only for open tasks and living colonies', async () => { - await step.run('2011-05-20'); + await step.run('1991-05-20'); expect(await eventsOf(ids['task:open'])).toEqual([ { type: 'TaskDue', aggregateType: 'Task', - payload: { dueOn: '2011-05-18' }, - dedupeKey: `TaskDue:${ids['task:open']}:2011-05-18`, + payload: { dueOn: '1991-05-18' }, + dedupeKey: `TaskDue:${ids['task:open']}:1991-05-18`, actorUserId: null, }, ]); @@ -379,12 +381,12 @@ describe('daily-evaluation (e2e)', () => { type: 'VarroaMeasurementDue', aggregateType: 'Colony', payload: { - dueOn: '2011-05-18', + dueOn: '1991-05-18', lastMeasuredOn: null, kind: 'INTERVAL', anchor: 'none', }, - dedupeKey: `VarroaMeasurementDue:${ids.living}:2011-05-18`, + dedupeKey: `VarroaMeasurementDue:${ids.living}:1991-05-18`, actorUserId: null, }, ]); @@ -392,9 +394,9 @@ describe('daily-evaluation (e2e)', () => { }); it('InspectionOverdue follows the segment interval', async () => { - await step.run('2011-05-12'); + await step.run('1991-05-12'); - // SCHWARMZEIT since 2011-04-15: 2011-04-28 + 7 = 2011-05-05, not + 21. + // SCHWARMZEIT since 1991-04-15: 1991-04-28 + 7 = 1991-05-05, not + 21. expect( (await eventsOf(ids.living)).filter( (event) => event.type === 'InspectionOverdue', @@ -404,11 +406,11 @@ describe('daily-evaluation (e2e)', () => { type: 'InspectionOverdue', aggregateType: 'Colony', payload: { - dueOn: '2011-05-05', - lastInspectedOn: '2011-04-28', + dueOn: '1991-05-05', + lastInspectedOn: '1991-04-28', firstOfYear: false, }, - dedupeKey: `InspectionOverdue:${ids.living}:2011-05-06`, + dedupeKey: `InspectionOverdue:${ids.living}:1991-05-06`, actorUserId: null, }, ]); @@ -416,50 +418,50 @@ describe('daily-evaluation (e2e)', () => { }); it('records no inspection in WINTERRUHE and the first one the day after FRUEHJAHR begins', async () => { - await step.run('2012-03-10'); + await step.run('1992-03-10'); expect( (await eventsOf(ids.living)).filter((event) => - event.dedupeKey?.endsWith(':2012-03-21'), + event.dedupeKey?.endsWith(':1992-03-21'), ), ).toEqual([]); - // FRUEHJAHR begins 2012-03-20; the colony was last inspected in 2011. - await step.run('2012-03-21'); + // FRUEHJAHR begins 1992-03-20; the colony was last inspected in 1991. + await step.run('1992-03-21'); expect( (await eventsOf(ids.living)).filter((event) => - event.dedupeKey?.endsWith(':2012-03-21'), + event.dedupeKey?.endsWith(':1992-03-21'), ), ).toEqual([ { type: 'InspectionOverdue', aggregateType: 'Colony', payload: { - dueOn: '2012-03-20', - lastInspectedOn: '2011-04-28', + dueOn: '1992-03-20', + lastInspectedOn: '1991-04-28', firstOfYear: true, }, - dedupeKey: `InspectionOverdue:${ids.living}:2012-03-21`, + dedupeKey: `InspectionOverdue:${ids.living}:1992-03-21`, actorUserId: null, }, ]); }); it('reports the current queen of a living colony once she is aged, on any job day of the year', async () => { - await step.run('2011-09-09'); - await step.run('2011-09-10'); + await step.run('1991-09-09'); + await step.run('1991-09-10'); - // One per queen and year: the 2012 run of the test above added its own. + // One per queen and year: the 1992 run of the test above added its own. const events = await eventsOf(ids['queen:aged']); expect(events.map((event) => event.dedupeKey)).toEqual([ - `QueenAged:${ids['queen:aged']}:2011-01-01`, - `QueenAged:${ids['queen:aged']}:2012-01-01`, + `QueenAged:${ids['queen:aged']}:1991-01-01`, + `QueenAged:${ids['queen:aged']}:1992-01-01`, ]); expect(events.slice(0, 1)).toEqual([ { type: 'QueenAged', aggregateType: 'Queen', - payload: { hatchYear: 2009, colonyId: ids.living }, - dedupeKey: `QueenAged:${ids['queen:aged']}:2011-01-01`, + payload: { hatchYear: 1989, colonyId: ids.living }, + dedupeKey: `QueenAged:${ids['queen:aged']}:1991-01-01`, actorUserId: null, }, ]); @@ -468,7 +470,7 @@ describe('daily-evaluation (e2e)', () => { }); describe('phenology step', () => { - const DAY = '2011-04-30'; + const DAY = '1991-04-30'; let weatherApiaryId = ''; beforeAll(async () => { @@ -483,8 +485,8 @@ describe('daily-evaluation (e2e)', () => { // 10 degrees every day from January 1st to April 29th: GTS 965. const days: Prisma.ApiaryWeatherDayCreateManyInput[] = []; for ( - let t = d('2011-01-01').getTime(); - t <= d('2011-04-29').getTime(); + let t = d('1991-01-01').getTime(); + t <= d('1991-04-29').getTime(); t += 86_400_000 ) { days.push({ @@ -499,7 +501,7 @@ describe('daily-evaluation (e2e)', () => { data: { apiaryId: apiary.id, phase: 'HOLUNDER', - date: d('2011-04-29'), + date: d('1991-04-29'), userId: ids.user, }, }); @@ -512,8 +514,8 @@ describe('daily-evaluation (e2e)', () => { await prisma.phenologyReport.deleteMany({ where: { apiaryId: weatherApiaryId }, }); - // Rows of 2011 would feed the season of the other tests of this file. - await prisma.apiaryPhenology.deleteMany({ where: { year: 2011 } }); + // Rows of 1991 would feed the season of the other tests of this file. + await prisma.apiaryPhenology.deleteMany({ where: { year: 1991 } }); await prisma.apiary.deleteMany({ where: { id: weatherApiaryId } }); }); @@ -532,7 +534,7 @@ describe('daily-evaluation (e2e)', () => { expect(Number(result.phenologyApiaries)).toBeGreaterThanOrEqual(2); const rows = await prisma.apiaryPhenology.findMany({ - where: { apiaryId: { in: [ids.apiary, weatherApiaryId] }, year: 2011 }, + where: { apiaryId: { in: [ids.apiary, weatherApiaryId] }, year: 1991 }, orderBy: { apiaryId: 'asc' }, }); expect(rows).toHaveLength(2); @@ -547,7 +549,7 @@ describe('daily-evaluation (e2e)', () => { // A report for HOLUNDER beats GTS and calendar. expect(byApiary.get(weatherApiaryId)).toMatchObject({ gts: 965, - gtsThroughDay: d('2011-04-29'), + gtsThroughDay: d('1991-04-29'), estimatedPhase: 'APFEL', reportedPhase: 'HOLUNDER', effectivePhase: 'HOLUNDER', @@ -581,25 +583,25 @@ describe('daily-evaluation (e2e)', () => { }); describe('autopilot and suggestions-expiry steps', () => { - /** Living colony, inspected 2011-05-03 and measured 2011-05-10. */ + /** Living colony, inspected 1991-05-03 and measured 1991-05-10. */ async function lateColony(name: string): Promise { const colony = await prisma.colony.create({ data: { code: `DE-E2E-${suffix}-ap-${name}`, apiaryId: ids.apiary, colonyType: 'PRODUCTION_COLONY', - createdAt: d('2011-04-20'), + createdAt: d('1991-04-20'), }, }); autopilotColonyIds.push(colony.id); await prisma.inspection.create({ - data: { colonyId: colony.id, userId: ids.user, date: d('2011-05-03') }, + data: { colonyId: colony.id, userId: ids.user, date: d('1991-05-03') }, }); await prisma.varroaMeasurement.create({ data: { colonyId: colony.id, userId: ids.user, - date: d('2011-05-10'), + date: d('1991-05-10'), method: 'NATURAL_MITE_FALL', }, }); @@ -615,10 +617,10 @@ describe('daily-evaluation (e2e)', () => { it('running autopilot and expiry twice yields the same suggestion counts', async () => { const colonyId = await lateColony('twice'); - const first = await autopilotStep.run('2011-05-12'); - const firstExpiry = await expiryStep.run('2011-05-12'); - const second = await autopilotStep.run('2011-05-12'); - const secondExpiry = await expiryStep.run('2011-05-12'); + const first = await autopilotStep.run('1991-05-12'); + const firstExpiry = await expiryStep.run('1991-05-12'); + const second = await autopilotStep.run('1991-05-12'); + const secondExpiry = await expiryStep.run('1991-05-12'); expect(first).toMatchObject({ autopilotColonies: 1, @@ -637,13 +639,13 @@ describe('daily-evaluation (e2e)', () => { it('an OPEN suggestion past expiresAt becomes EXPIRED', async () => { const colonyId = await lateColony('expires'); - await autopilotStep.run('2011-05-12'); + await autopilotStep.run('1991-05-12'); - // expiresAt = 2011-06-11 - expect(await expiryStep.run('2011-06-11')).toEqual({ + // expiresAt = 1991-06-11 + expect(await expiryStep.run('1991-06-11')).toEqual({ suggestionsExpired: 0, }); - expect(await expiryStep.run('2011-06-12')).toEqual({ + expect(await expiryStep.run('1991-06-12')).toEqual({ suggestionsExpired: expect.any(Number) as number, }); @@ -653,10 +655,10 @@ describe('daily-evaluation (e2e)', () => { it('an expired INSPECT suggestion of the same anchor is proposed again on the next sync (dedupeKey was released)', async () => { const colonyId = await lateColony('again'); - await autopilotStep.run('2011-05-12'); - await expiryStep.run('2011-06-12'); + await autopilotStep.run('1991-05-12'); + await expiryStep.run('1991-06-12'); - const result = await autopilotStep.run('2011-06-12'); + const result = await autopilotStep.run('1991-06-12'); expect(result.suggestionsOpened).toBeGreaterThanOrEqual(1); expect((await inspectSuggestions(colonyId)).map((s) => s.status)).toEqual( @@ -668,13 +670,13 @@ describe('daily-evaluation (e2e)', () => { const colonyId = await lateColony('overdue20'); await prisma.inspection.updateMany({ where: { colonyId }, - data: { date: d('2011-04-10') }, + data: { date: d('1991-04-10') }, }); - await step.run('2011-05-12'); - await autopilotStep.run('2011-05-12'); + await step.run('1991-05-12'); + await autopilotStep.run('1991-05-12'); - // Due 2011-04-17: the event of 04-18 is outside the 7-day catch-up. + // Due 1991-04-17: the event of 04-18 is outside the 7-day catch-up. expect( await prisma.domainEvent.count({ where: { aggregateId: colonyId, type: 'InspectionOverdue' }, @@ -682,19 +684,19 @@ describe('daily-evaluation (e2e)', () => { ).toBe(0); const [suggestion] = await inspectSuggestions(colonyId); expect(suggestion).toMatchObject({ status: 'OPEN', priority: 'HIGH' }); - expect(suggestion.dueDate?.toISOString().slice(0, 10)).toBe('2011-04-17'); + expect(suggestion.dueDate?.toISOString().slice(0, 10)).toBe('1991-04-17'); }); it('a RESOLVED autopilot suggestion whose condition returns is proposed again', async () => { const colonyId = await lateColony('returns'); - await autopilotStep.run('2011-05-12'); + await autopilotStep.run('1991-05-12'); const fresh = await prisma.inspection.create({ - data: { colonyId, userId: ids.user, date: d('2011-05-12') }, + data: { colonyId, userId: ids.user, date: d('1991-05-12') }, }); - const resolved = await autopilotStep.run('2011-05-12'); + const resolved = await autopilotStep.run('1991-05-12'); await prisma.inspection.delete({ where: { id: fresh.id } }); - const returned = await autopilotStep.run('2011-05-12'); + const returned = await autopilotStep.run('1991-05-12'); expect(resolved.suggestionsResolved).toBeGreaterThanOrEqual(1); expect(returned.suggestionsOpened).toBeGreaterThanOrEqual(1); diff --git a/ApiCore-Backend/test/jobs.e2e-spec.ts b/ApiCore-Backend/test/jobs.e2e-spec.ts index 26e0bf9..89f13bd 100644 --- a/ApiCore-Backend/test/jobs.e2e-spec.ts +++ b/ApiCore-Backend/test/jobs.e2e-spec.ts @@ -39,8 +39,13 @@ describe('Jobs (e2e)', () => { }); afterAll(async () => { + // Only the jobs this file runs: other files (rekey, app-backup, voice) + // record their own runs in the same window and read them back. const runs = await prisma.jobRun.findMany({ - where: { startedAt: { gte: testStart } }, + where: { + job: { in: ['cleanup', 'daily-evaluation', 'task-templates'] }, + startedAt: { gte: testStart }, + }, select: { id: true }, }); const runIds = [...jobRunIds, ...runs.map((run) => run.id)];