Files
edr-platform/apps/edr-freight-api/src/modules/contracts/contract-expiry.service.spec.ts
Marshal 9a1c8e5603 feat: add hazardous goods declaration feature
- Introduced HazardDeclarationPanel component to display dangerous goods declaration details.
- Updated URL constants to include CLEARANCE_PROCEED endpoint for re-requesting operations.
- Enhanced permissions to include hazardous approval roles for contract approvals.
- Integrated HazardDeclarationPanel into ContractRequestDetailPage and ContractClearanceDetailPage.
- Added proceedToOperation method in bookings service for handling operation re-requests.
- Updated contract forms and schemas to include hazard class and UN number fields.
- Implemented validation for hazardous contracts in the contract creation flow.
- Added expiry notice functionality for contracts nearing validity end.
- Created tests for expiry notice calculations and labels.
- Updated UI components to reflect hazardous cargo information and validation errors.
2026-07-25 21:10:09 +00:00

64 lines
2.4 KiB
TypeScript

import { ContractExpiryService } from './contract-expiry.service';
import type { Contract } from './entities/contract.entity';
/**
* The reminder must warn each customer once, ten days out, and must never let a
* notification failure escape into the scheduler (that would also take out the
* expiry sweep sharing this service).
*/
describe('ContractExpiryService — expiry reminder', () => {
const contract = (over: Partial<Contract> = {}): Contract =>
({
id: 'c-1',
reference: 'CTR-2026-00042',
companyId: 'co-1',
contractValidUntil: new Date('2026-08-10T00:00:00.000Z'),
status: 'CONTRACT_ACTIVE',
...over,
}) as Contract;
let repo: { expireLapsedContracts: jest.Mock; findExpiringInDays: jest.Mock };
let inbox: { notify: jest.Mock };
let service: ContractExpiryService;
beforeEach(() => {
repo = {
expireLapsedContracts: jest.fn().mockResolvedValue(0),
findExpiringInDays: jest.fn().mockResolvedValue([]),
};
inbox = { notify: jest.fn().mockResolvedValue(undefined) };
service = new ContractExpiryService(repo as never, inbox as never);
});
it('asks for the contracts lapsing ten days out', async () => {
await service.remindExpiringContracts();
expect(repo.findExpiringInDays).toHaveBeenCalledWith(10);
});
it('notifies the owning company once, deep-linking the contract list', async () => {
repo.findExpiringInDays.mockResolvedValue([contract()]);
await service.remindExpiringContracts();
expect(inbox.notify).toHaveBeenCalledTimes(1);
const sent = inbox.notify.mock.calls[0][0];
expect(sent.recipients).toEqual({ companyId: 'co-1' });
expect(sent.title).toContain('CTR-2026-00042');
expect(sent.title).toContain('10 days');
expect(sent.link).toBe('/contracts');
expect(sent.data).toMatchObject({ contractId: 'c-1', action: 'CONTRACT_EXPIRING' });
});
it('skips a contract with no owning company (nobody to notify)', async () => {
repo.findExpiringInDays.mockResolvedValue([contract({ companyId: null })]);
await service.remindExpiringContracts();
expect(inbox.notify).not.toHaveBeenCalled();
});
it('swallows a notification failure instead of throwing into the scheduler', async () => {
repo.findExpiringInDays.mockResolvedValue([contract()]);
inbox.notify.mockRejectedValue(new Error('inbox down'));
await expect(service.remindExpiringContracts()).resolves.toBeUndefined();
});
});