Files
edr-platform/apps/edr-freight-api/src/modules/bookings/booking-lifecycle-notifier.service.spec.ts
2026-08-07 12:42:33 +00:00

114 lines
4.4 KiB
TypeScript

import { BookingLifecycleNotifierService } from './booking-lifecycle-notifier.service';
import type { Booking } from './entities/booking.entity';
import { FREIGHT_PERMS } from '../../seed/freight-permissions.registry';
/**
* Who hears "Operations wants changes" depends on who owns the booking. A
* customs (Path B) booking is created BY GL Ethiopia on the customer's behalf —
* the customer can neither edit nor resubmit it, so the note has to reach the GL
* who made it, not the portal.
*/
describe('BookingLifecycleNotifierService — operation changes requested', () => {
const booking = (over: Partial<Booking> = {}): Booking =>
({
id: 'b-1',
reference: 'BKG-0001',
companyId: 'co-1',
contractId: 'ctr-1',
createdByRole: 'CUSTOMER',
company: { email: 'customer@example.com' },
...over,
}) as Booking;
let notifications: { directSend: jest.Mock };
let inbox: { notify: jest.Mock };
let service: BookingLifecycleNotifierService;
const flush = () => new Promise((resolve) => setImmediate(resolve));
beforeEach(() => {
notifications = { directSend: jest.fn().mockResolvedValue(undefined) };
inbox = { notify: jest.fn().mockResolvedValue(undefined) };
service = new BookingLifecycleNotifierService(
notifications as never,
inbox as never,
{ query: jest.fn().mockResolvedValue([{ phone: '+251900000000' }]) } as never,
);
});
it('sends a GL-created booking back to the GL who created it, not the customer', async () => {
service.operationChangesRequested(
booking({ createdByRole: 'GL_ET', createdByUserId: 'gl-user-1' }),
'Cargo weight does not match the declaration',
);
await flush();
expect(inbox.notify).toHaveBeenCalledTimes(1);
const sent = inbox.notify.mock.calls[0][0];
expect(sent.recipients).toEqual({ userIds: ['gl-user-1'] });
expect(sent.audience).toBe('BACKOFFICE');
expect(sent.body).toContain('Cargo weight does not match the declaration');
// Deep-links the clearance page GL works from, not the portal booking.
expect(sent.link).toBe('/dashboard/contracts/clearance/ctr-1');
// The customer is not told to fix something they cannot touch.
expect(notifications.directSend).not.toHaveBeenCalled();
});
it('still tells the customer when the booking is their own', async () => {
service.operationChangesRequested(booking(), 'Please attach the packing list');
await flush();
const sent = inbox.notify.mock.calls[0][0];
expect(sent.recipients).toEqual({ companyId: 'co-1' });
expect(sent.audience).toBe('PORTAL');
expect(sent.link).toBe('/bookings/b-1');
expect(notifications.directSend).toHaveBeenCalled();
});
it('falls back to the customer when the GL creator is unknown (legacy rows)', async () => {
service.operationChangesRequested(
booking({ createdByRole: 'GL_ET', createdByUserId: null }),
'Fix the declaration',
);
await flush();
expect(inbox.notify.mock.calls[0][0].recipients).toEqual({ companyId: 'co-1' });
});
});
/**
* Staff notifications used to go to every employee in every organization. They
* now target a desk — and the two desks are disjoint: the GL presets hold no
* bookings:view and no intake keys, so intake pings would be noise they cannot
* act on. Both branches run through the same `inAppStaff` helper, which is the
* easy place to lose the distinction again.
*/
describe('BookingLifecycleNotifierService — staff desk targeting', () => {
const booking = () =>
({ id: 'b-1', reference: 'BKG-0001', companyId: 'co-1' }) as Booking;
let inbox: { notify: jest.Mock };
let service: BookingLifecycleNotifierService;
beforeEach(() => {
inbox = { notify: jest.fn().mockResolvedValue(undefined) };
service = new BookingLifecycleNotifierService(
{ directSend: jest.fn().mockResolvedValue(undefined) } as never,
inbox as never,
{ query: jest.fn().mockResolvedValue([]) } as never,
);
});
it('routes intake items to the booking desk and clearance items to the clearance desk', () => {
service.submittedToStaff(booking());
service.clearanceDocsUploadedToStaff(booking());
expect(inbox.notify.mock.calls[0][0].recipients).toEqual({
permissionKeys: [FREIGHT_PERMS.bookings.getNotification],
});
expect(inbox.notify.mock.calls[1][0].recipients).toEqual({
permissionKeys: [FREIGHT_PERMS.bookings.clearanceGetNotification],
});
});
});