mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 12:18:11 +00:00
131 lines
4.9 KiB
TypeScript
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();
|
|
});
|
|
});
|