Files
edr-platform/apps/edr-passenger-api/src/modules/bookings/booking-identity.util.spec.ts

131 lines
4.9 KiB
TypeScript

import { BadRequestException } from '@nestjs/common';
import { IdDocumentType } from '@prisma/client';
import {
assertIdentitiesNotAlreadyBooked,
resolveIdentityRef,
} from './booking-identity.util';
import { PrismaService } from '../../common/prisma.service';
// ── Fixtures ─────────────────────────────────────────────────────────────────
const SCHEDULE = 'schedule-1';
const RETURN_SCHEDULE = 'schedule-2';
const makePrisma = (clash: any = null) =>
({ bookingSeat: { findFirst: jest.fn().mockResolvedValue(clash) } }) as unknown as PrismaService;
const traveller = (passengerName: string, identityRef: string | null) => ({
passengerName,
identityRef,
});
// ── resolveIdentityRef ───────────────────────────────────────────────────────
describe('resolveIdentityRef', () => {
it('uses the Fayda sub for national-ID travellers', () => {
expect(
resolveIdentityRef({
idDocumentType: IdDocumentType.NATIONAL_ID,
faydaSub: 'psut-abc',
passportNumber: 'P1234567',
}),
).toBe('psut-abc');
});
it('uses the passport number for passport travellers, normalised to upper case', () => {
expect(
resolveIdentityRef({
idDocumentType: IdDocumentType.PASSPORT,
faydaSub: 'psut-abc',
passportNumber: ' p1234567 ',
}),
).toBe('P1234567');
});
it('returns null when there is nothing to key on — children and Fayda-disabled bookings', () => {
expect(resolveIdentityRef({ idDocumentType: IdDocumentType.NATIONAL_ID })).toBeNull();
expect(
resolveIdentityRef({ idDocumentType: IdDocumentType.NATIONAL_ID, faydaSub: ' ' }),
).toBeNull();
expect(resolveIdentityRef({ idDocumentType: IdDocumentType.PASSPORT })).toBeNull();
});
});
// ── assertIdentitiesNotAlreadyBooked ─────────────────────────────────────────
describe('assertIdentitiesNotAlreadyBooked', () => {
it('rejects the same identity used twice inside one payload', async () => {
const prisma = makePrisma();
await expect(
assertIdentitiesNotAlreadyBooked(
prisma,
[traveller('Abebe Kebede', 'psut-abc'), traveller('Sara Ali', 'psut-abc')],
[SCHEDULE],
),
).rejects.toThrow(BadRequestException);
// Rejected before touching the database.
expect(prisma.bookingSeat.findFirst).not.toHaveBeenCalled();
});
it('ignores passengers with no identity — two children never collide with each other', async () => {
const prisma = makePrisma();
await expect(
assertIdentitiesNotAlreadyBooked(
prisma,
[traveller('Child One', null), traveller('Child Two', null)],
[SCHEDULE],
),
).resolves.toBeUndefined();
expect(prisma.bookingSeat.findFirst).not.toHaveBeenCalled();
});
it('queries every leg of the booking, de-duplicated, for active bookings only', async () => {
const prisma = makePrisma();
await assertIdentitiesNotAlreadyBooked(
prisma,
[traveller('Abebe Kebede', 'psut-abc')],
[SCHEDULE, RETURN_SCHEDULE, SCHEDULE, null, undefined],
);
const { where } = (prisma.bookingSeat.findFirst as jest.Mock).mock.calls[0][0];
expect(where.scheduleId).toEqual({ in: [SCHEDULE, RETURN_SCHEDULE] });
expect(where.idDocumentNumber).toEqual({ in: ['psut-abc'] });
expect(where.booking.status.in).toEqual(['DRAFT', 'PENDING_PAYMENT', 'CONFIRMED', 'BOARDED']);
});
it('rejects an identity that already holds a ticket on the departure', async () => {
const prisma = makePrisma({
idDocumentNumber: 'psut-abc',
passengerName: 'Abebe K.',
booking: { bookingRef: 'ABCDEF', status: 'CONFIRMED' },
});
await expect(
assertIdentitiesNotAlreadyBooked(prisma, [traveller('Abebe Kebede', 'psut-abc')], [SCHEDULE]),
).rejects.toThrow(/Abebe Kebede already has a ticket on this train \(booking ABCDEF\)/);
});
it('points an unpaid clash at the booking the traveller still has to settle', async () => {
const prisma = makePrisma({
idDocumentNumber: 'psut-abc',
passengerName: 'Abebe K.',
booking: { bookingRef: 'ABCDEF', status: 'PENDING_PAYMENT' },
});
await expect(
assertIdentitiesNotAlreadyBooked(prisma, [traveller('Abebe Kebede', 'psut-abc')], [SCHEDULE]),
).rejects.toThrow(/already has an unpaid booking \(ABCDEF\)/);
});
it('allows the booking when nothing active matches — a cancelled ticket frees the identity', async () => {
const prisma = makePrisma(null);
await expect(
assertIdentitiesNotAlreadyBooked(
prisma,
[traveller('Abebe Kebede', 'psut-abc'), traveller('Sara Ali', 'P7654321')],
[SCHEDULE],
),
).resolves.toBeUndefined();
});
});