import { Logger } from '@nestjs/common'; import type { PrismaService } from '../prisma/prisma.service'; import { JobPartialFailure } from '../system/job-run.service'; import { AutopilotStep } from './autopilot.step'; import type { AutopilotService } from './autopilot.service'; describe('AutopilotStep', () => { const tx = { tx: true }; let findMany: jest.Mock; let transaction: jest.Mock; let syncColonyIn: jest.Mock; let preloadFor: jest.Mock; const PRELOAD = { configs: {}, seasons: new Map() }; let step: AutopilotStep; beforeEach(() => { findMany = jest.fn().mockResolvedValue([ { id: 'c1', apiaryId: 'a1' }, { id: 'c2', apiaryId: 'a1' }, { id: 'c3', apiaryId: 'a2' }, ]); transaction = jest.fn((work: (client: unknown) => unknown) => work(tx)); syncColonyIn = jest .fn() .mockResolvedValue({ opened: 1, refreshed: 0, resolved: 2 }); preloadFor = jest.fn().mockResolvedValue(PRELOAD); step = new AutopilotStep( { colony: { findMany }, $transaction: transaction, } as unknown as PrismaService, { syncColonyIn, preloadFor } as unknown as AutopilotService, ); }); afterEach(() => jest.restoreAllMocks()); it('is the autopilot step', () => { expect(step.name).toBe('autopilot'); }); it('syncs every living colony in a transaction of its own and sums up', async () => { const result = await step.run('2026-05-12'); expect(findMany).toHaveBeenCalledWith( expect.objectContaining({ where: { status: { notIn: ['DISSOLVED', 'SOLD', 'DECEASED', 'MERGED'] }, }, orderBy: { id: 'asc' }, }), ); expect(transaction).toHaveBeenCalledTimes(3); expect(syncColonyIn).toHaveBeenCalledWith(tx, 'c1', '2026-05-12', PRELOAD); expect(syncColonyIn).toHaveBeenCalledWith(tx, 'c3', '2026-05-12', PRELOAD); expect(result).toEqual({ autopilotColonies: 3, suggestionsOpened: 3, suggestionsResolved: 6, }); }); it('reads rules and seasons once for all colonies', async () => { await step.run('2026-05-12'); expect(preloadFor).toHaveBeenCalledTimes(1); expect(preloadFor).toHaveBeenCalledWith( expect.anything(), ['a1', 'a2'], '2026-05-12', ); }); it('a failing colony does not stop the others; the step fails with the counters', async () => { jest.spyOn(Logger.prototype, 'error').mockImplementation(() => undefined); syncColonyIn .mockRejectedValueOnce(new Error('boom')) .mockResolvedValueOnce({ opened: 1, refreshed: 0, resolved: 0 }); const failure = await step.run('2026-05-12').then( () => null, (error: unknown) => error, ); expect(failure).toBeInstanceOf(JobPartialFailure); expect(failure).toMatchObject({ result: { autopilotColonies: 2, suggestionsOpened: 2, suggestionsResolved: 2, }, failedSteps: ['1 colony'], }); }); });