import { ConfigService } from "@nestjs/config"; import { DataSource } from "typeorm"; import { EimsConfig } from "../../config/eims.config"; import { EimsAutoSubmitService } from "./eims-auto-submit.service"; import { EimsInvoiceRegistrationService } from "./eims-invoice-registration.service"; import { EimsInvoiceStatus } from "./eims-registration.types"; import { eimsConfig } from "./eims-test-fixtures"; const INVOICE_ID = "11111111-1111-4111-8111-111111111111"; /** * `query` is answered by shape: the first call is the system-state guard, the second is the * candidate lookup. Keeps the fake honest about the order the service actually asks in. */ const build = ( opts: { cfg?: Partial; state?: { in_flight_invoice_id?: string | null; blocked_reason?: string | null }; candidate?: { id: string; invoiceNumber: string } | null; register?: jest.Mock; } = {}, ) => { const register = opts.register ?? jest.fn().mockResolvedValue({ eimsStatus: EimsInvoiceStatus.Registered, eimsIrn: "IRN-1" }); const query = jest.fn().mockImplementation((sql: string) => { if (sql.includes("eims_system_state")) { return Promise.resolve( opts.state ? [{ in_flight_invoice_id: null, blocked_reason: null, ...opts.state }] : [], ); } return Promise.resolve(opts.candidate === undefined ? [] : opts.candidate ? [opts.candidate] : []); }); const service = new EimsAutoSubmitService( { query } as unknown as DataSource, { get: () => eimsConfig({ autoSubmit: true, ...opts.cfg }) } as unknown as ConfigService, { registerInvoiceWithEims: register } as unknown as EimsInvoiceRegistrationService, ); return { service, register, query }; }; const candidate = { id: INVOICE_ID, invoiceNumber: "INV-20260807-00006" }; describe("EimsAutoSubmitService.tick", () => { it("files the oldest eligible invoice through the registration service", async () => { const { service, register } = build({ candidate }); await service.tick(); expect(register).toHaveBeenCalledTimes(1); expect(register).toHaveBeenCalledWith(INVOICE_ID); }); it("files nothing when EIMS_AUTO_SUBMIT is off", async () => { const { service, register, query } = build({ cfg: { autoSubmit: false }, candidate }); await service.tick(); expect(register).not.toHaveBeenCalled(); expect(query).not.toHaveBeenCalled(); }); it("files nothing when EIMS itself is disabled, even with auto-submit on", async () => { const { service, register, query } = build({ cfg: { enabled: false }, candidate }); await service.tick(); expect(register).not.toHaveBeenCalled(); expect(query).not.toHaveBeenCalled(); }); it("does not submit while another submission is in flight", async () => { const { service, register } = build({ state: { in_flight_invoice_id: "22222222-2222-4222-8222-222222222222" }, candidate, }); await service.tick(); expect(register).not.toHaveBeenCalled(); }); it("does not submit while the system number is blocked", async () => { const { service, register } = build({ state: { blocked_reason: "never acknowledged" }, candidate, }); await service.tick(); expect(register).not.toHaveBeenCalled(); }); it("does nothing when no invoice is eligible", async () => { const { service, register } = build({ candidate: null }); await service.tick(); expect(register).not.toHaveBeenCalled(); }); it("asks only for NOT_SUBMITTED invoices, so UNKNOWN and FAILED are never retried", async () => { const { service, query } = build({ candidate }); await service.tick(); const [sql, params] = query.mock.calls.find(([s]: [string]) => s.includes("freight.invoices"))!; expect(sql).toContain("i.eims_status = $1"); expect(params[0]).toBe(EimsInvoiceStatus.NotSubmitted); expect(sql).toContain("i.issued_at IS NOT NULL"); }); it("survives a filing failure so the job keeps running", async () => { const register = jest.fn().mockRejectedValue(new Error("EIMS register failed (406)")); const { service } = build({ candidate, register }); await expect(service.tick()).resolves.toBeUndefined(); expect(register).toHaveBeenCalledTimes(1); }); it("does not start a second tick while one is still filing", async () => { let release: () => void = () => {}; const register = jest.fn().mockImplementation( () => new Promise((resolve) => (release = () => resolve({ eimsStatus: "REGISTERED" }))), ); const { service } = build({ candidate, register }); const first = service.tick(); await new Promise((r) => setImmediate(r)); await service.tick(); // overlapping tick, must be a no-op expect(register).toHaveBeenCalledTimes(1); release(); await first; }); });