import { Test, TestingModule } from '@nestjs/testing'; import { SeatsService } from './seats.service'; import { PrismaService } from '../../common/prisma.service'; import { ConflictException } from '@nestjs/common'; import { SegmentsService } from '../segments/segments.service'; import { SystemConfigService } from '../system-config/system-config.service'; import { AuditService } from '../../common/audit.service'; import { SmsClientService } from '../notifications/sms-client.service'; import { JourneyDirection } from './seats.dto'; describe('SeatsService - Auto Assign', () => { let service: SeatsService; let prisma: PrismaService; let segmentsService: SegmentsService; const mockPrisma = { seat: { findMany: jest.fn(), updateMany: jest.fn(), }, tripStopTime: { findMany: jest.fn(), }, trainSchedule: { findUnique: jest.fn(), }, seatBlock: { findMany: jest.fn(), }, }; const mockSegmentsService = { getSeatAvailabilityMap: jest.fn(), }; const mockSystemConfigService = { getNumber: jest.fn().mockResolvedValue(30), }; const mockAuditService = { log: jest.fn(), }; const mockSmsClientService = { send: jest.fn(), }; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ SeatsService, { provide: PrismaService, useValue: mockPrisma }, { provide: SegmentsService, useValue: mockSegmentsService }, { provide: SystemConfigService, useValue: mockSystemConfigService }, { provide: AuditService, useValue: mockAuditService }, { provide: SmsClientService, useValue: mockSmsClientService }, ], }).compile(); service = module.get(SeatsService); prisma = module.get(PrismaService); segmentsService = module.get(SegmentsService); jest.clearAllMocks(); mockPrisma.trainSchedule.findUnique.mockResolvedValue({ originStationId: 'origin-station', destinationStationId: 'destination-station', }); mockPrisma.tripStopTime.findMany.mockResolvedValue([ { stationId: 'origin-station', sequence: 0 }, { stationId: 'destination-station', sequence: 1 }, ]); mockPrisma.seatBlock.findMany.mockResolvedValue([]); mockSegmentsService.getSeatAvailabilityMap.mockResolvedValue(new Map()); }); describe('assertNoRouteSeatConflict', () => { it('rejects overlapping origin-destination seat reuse on the same schedule', async () => { mockPrisma.tripStopTime.findMany.mockResolvedValue([ { stationId: 'seb', sequence: 1 }, { stationId: 'ada', sequence: 2 }, { stationId: 'dd', sequence: 3 }, ]); mockSegmentsService.getSeatAvailabilityMap.mockResolvedValue(new Map([['seat-1', 'BOOKED']])); mockPrisma.seat.findMany.mockResolvedValue([ { id: 'seat-1', seatNumber: '12', coach: { number: 'C1' } }, ]); await expect(service.assertNoRouteSeatConflict({ scheduleId: 'sched-1', seatIds: ['seat-1'], originStationId: 'seb', destinationStationId: 'dd', journeyDirection: JourneyDirection.ONE_WAY, })).rejects.toThrow(ConflictException); }); it('allows the same seat number to be reused on non-overlapping legs', async () => { mockPrisma.tripStopTime.findMany.mockResolvedValue([ { stationId: 'seb', sequence: 1 }, { stationId: 'ada', sequence: 2 }, { stationId: 'dd', sequence: 3 }, ]); mockSegmentsService.getSeatAvailabilityMap.mockResolvedValue(new Map()); await expect(service.assertNoRouteSeatConflict({ scheduleId: 'sched-1', seatIds: ['seat-1'], originStationId: 'seb', destinationStationId: 'ada', journeyDirection: JourneyDirection.ONE_WAY, })).resolves.toBeUndefined(); }); }); describe('autoAssignSeats', () => { it('should assign contiguous seats in same row', async () => { const mockSeats = [ { id: 'seat-1', coachId: 'coach-1', row: 1, col: 'A' }, { id: 'seat-2', coachId: 'coach-1', row: 1, col: 'B' }, { id: 'seat-3', coachId: 'coach-1', row: 1, col: 'C' }, { id: 'seat-4', coachId: 'coach-1', row: 2, col: 'A' }, ]; mockPrisma.seat.findMany.mockResolvedValue(mockSeats); const result = await service.autoAssignSeats('trip-1', 2, 'ECONOMY_REGULAR'); expect(result).toHaveLength(2); expect(result).toEqual(['seat-1', 'seat-2']); }); it('should throw error if not enough seats available', async () => { mockPrisma.seat.findMany.mockResolvedValue([ { id: 'seat-1', coachId: 'coach-1', row: 1, col: 'A' }, ]); await expect( service.autoAssignSeats('trip-1', 3, 'ECONOMY_REGULAR'), ).rejects.toThrow(ConflictException); }); it('should respect eligibility filter', async () => { const mockSeats = [ { id: 'seat-1', coachId: 'coach-1', row: 1, col: 'A', eligibility: 'ACCESSIBLE' }, { id: 'seat-2', coachId: 'coach-1', row: 1, col: 'B', eligibility: 'ACCESSIBLE' }, ]; mockPrisma.seat.findMany.mockResolvedValue(mockSeats); const result = await service.autoAssignSeats('trip-1', 2, 'ECONOMY_REGULAR'); expect(result).toHaveLength(2); }); it('should assign single seat', async () => { const mockSeats = [ { id: 'seat-1', coachId: 'coach-1', row: 1, col: 'A' }, ]; mockPrisma.seat.findMany.mockResolvedValue(mockSeats); const result = await service.autoAssignSeats('trip-1', 1, 'ECONOMY_REGULAR'); expect(result).toEqual(['seat-1']); }); }); });