import { Test, TestingModule } from '@nestjs/testing'; import { TypeOrmModule } from '@nestjs/typeorm'; import { DataSource } from 'typeorm'; import { faker } from '@faker-js/faker'; import { TypeOrmBookingRepository } from '../../src/infrastructure/persistence/typeorm/repositories/typeorm-booking.repository'; import { BookingOrmEntity } from '../../src/infrastructure/persistence/typeorm/entities/booking.orm-entity'; import { ContainerOrmEntity } from '../../src/infrastructure/persistence/typeorm/entities/container.orm-entity'; import { OrganizationOrmEntity } from '../../src/infrastructure/persistence/typeorm/entities/organization.orm-entity'; import { UserOrmEntity } from '../../src/infrastructure/persistence/typeorm/entities/user.orm-entity'; import { RateQuoteOrmEntity } from '../../src/infrastructure/persistence/typeorm/entities/rate-quote.orm-entity'; import { PortOrmEntity } from '../../src/infrastructure/persistence/typeorm/entities/port.orm-entity'; import { CarrierOrmEntity } from '../../src/infrastructure/persistence/typeorm/entities/carrier.orm-entity'; import { Booking } from '../../src/domain/entities/booking.entity'; import { BookingStatus } from '../../src/domain/value-objects/booking-status.vo'; import { BookingNumber } from '../../src/domain/value-objects/booking-number.vo'; describe('TypeOrmBookingRepository (Integration)', () => { let module: TestingModule; let repository: TypeOrmBookingRepository; let dataSource: DataSource; let testOrganization: OrganizationOrmEntity; let testUser: UserOrmEntity; let testCarrier: CarrierOrmEntity; let testOriginPort: PortOrmEntity; let testDestinationPort: PortOrmEntity; let testRateQuote: RateQuoteOrmEntity; beforeAll(async () => { module = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', host: process.env.TEST_DB_HOST || 'localhost', port: parseInt(process.env.TEST_DB_PORT || '5432'), username: process.env.TEST_DB_USER || 'postgres', password: process.env.TEST_DB_PASSWORD || 'postgres', database: process.env.TEST_DB_NAME || 'xpeditis_test', entities: [ BookingOrmEntity, ContainerOrmEntity, OrganizationOrmEntity, UserOrmEntity, RateQuoteOrmEntity, PortOrmEntity, CarrierOrmEntity, ], synchronize: true, // Auto-create schema for tests dropSchema: true, // Clean slate for each test run logging: false, }), TypeOrmModule.forFeature([ BookingOrmEntity, ContainerOrmEntity, OrganizationOrmEntity, UserOrmEntity, RateQuoteOrmEntity, PortOrmEntity, CarrierOrmEntity, ]), ], providers: [TypeOrmBookingRepository], }).compile(); repository = module.get(TypeOrmBookingRepository); dataSource = module.get(DataSource); // Create test data fixtures await createTestFixtures(); }); afterAll(async () => { await dataSource.destroy(); await module.close(); }); afterEach(async () => { // Clean up bookings after each test await dataSource.getRepository(ContainerOrmEntity).delete({}); await dataSource.getRepository(BookingOrmEntity).delete({}); }); async function createTestFixtures() { const orgRepo = dataSource.getRepository(OrganizationOrmEntity); const userRepo = dataSource.getRepository(UserOrmEntity); const carrierRepo = dataSource.getRepository(CarrierOrmEntity); const portRepo = dataSource.getRepository(PortOrmEntity); const rateQuoteRepo = dataSource.getRepository(RateQuoteOrmEntity); // Create organization testOrganization = orgRepo.create({ id: faker.string.uuid(), name: 'Test Freight Forwarder', type: 'freight_forwarder', scac: 'TEFF', address: { street: '123 Test St', city: 'Rotterdam', postalCode: '3000', country: 'NL', }, contactEmail: 'test@example.com', contactPhone: '+31123456789', isActive: true, createdAt: new Date(), updatedAt: new Date(), }); await orgRepo.save(testOrganization); // Create user testUser = userRepo.create({ id: faker.string.uuid(), organizationId: testOrganization.id, email: 'testuser@example.com', passwordHash: 'hashed_password', firstName: 'Test', lastName: 'User', role: 'user', isActive: true, createdAt: new Date(), updatedAt: new Date(), }); await userRepo.save(testUser); // Create carrier testCarrier = carrierRepo.create({ id: faker.string.uuid(), name: 'Test Carrier Line', code: 'TESTCARRIER', scac: 'TSTC', supportsApi: true, isActive: true, createdAt: new Date(), updatedAt: new Date(), }); await carrierRepo.save(testCarrier); // Create ports testOriginPort = portRepo.create({ id: faker.string.uuid(), name: 'Port of Rotterdam', code: 'NLRTM', city: 'Rotterdam', country: 'Netherlands', countryCode: 'NL', timezone: 'Europe/Amsterdam', latitude: 51.9225, longitude: 4.47917, createdAt: new Date(), updatedAt: new Date(), }); await portRepo.save(testOriginPort); testDestinationPort = portRepo.create({ id: faker.string.uuid(), name: 'Port of Shanghai', code: 'CNSHA', city: 'Shanghai', country: 'China', countryCode: 'CN', timezone: 'Asia/Shanghai', latitude: 31.2304, longitude: 121.4737, createdAt: new Date(), updatedAt: new Date(), }); await portRepo.save(testDestinationPort); // Create rate quote testRateQuote = rateQuoteRepo.create({ id: faker.string.uuid(), carrierId: testCarrier.id, originPortId: testOriginPort.id, destinationPortId: testDestinationPort.id, baseFreight: 1500.0, currency: 'USD', surcharges: [], totalAmount: 1500.0, containerType: '40HC', validFrom: new Date(), validUntil: new Date(Date.now() + 86400000 * 30), // 30 days etd: new Date(Date.now() + 86400000 * 7), // 7 days from now eta: new Date(Date.now() + 86400000 * 37), // 37 days from now transitDays: 30, createdAt: new Date(), updatedAt: new Date(), }); await rateQuoteRepo.save(testRateQuote); } function createTestBookingEntity(): Booking { return Booking.create({ id: faker.string.uuid(), bookingNumber: BookingNumber.generate(), userId: testUser.id, organizationId: testOrganization.id, rateQuoteId: testRateQuote.id, status: BookingStatus.create('draft'), shipper: { name: 'Shipper Company Ltd', address: { street: '456 Shipper Ave', city: 'Rotterdam', postalCode: '3001', country: 'NL', }, contactName: 'John Shipper', contactEmail: 'shipper@example.com', contactPhone: '+31987654321', }, consignee: { name: 'Consignee Corp', address: { street: '789 Consignee Rd', city: 'Shanghai', postalCode: '200000', country: 'CN', }, contactName: 'Jane Consignee', contactEmail: 'consignee@example.com', contactPhone: '+86123456789', }, cargoDescription: 'General cargo - electronics', containers: [], specialInstructions: 'Handle with care', createdAt: new Date(), updatedAt: new Date(), }); } describe('save', () => { it('should save a new booking', async () => { const booking = createTestBookingEntity(); const savedBooking = await repository.save(booking); expect(savedBooking.id).toBe(booking.id); expect(savedBooking.bookingNumber.value).toBe(booking.bookingNumber.value); expect(savedBooking.status.value).toBe('draft'); }); it('should update an existing booking', async () => { const booking = createTestBookingEntity(); await repository.save(booking); // Update the booking const updatedBooking = Booking.create({ ...booking, status: BookingStatus.create('pending_confirmation'), cargoDescription: 'Updated cargo description', }); const result = await repository.save(updatedBooking); expect(result.status.value).toBe('pending_confirmation'); expect(result.cargoDescription).toBe('Updated cargo description'); }); }); describe('findById', () => { it('should find a booking by ID', async () => { const booking = createTestBookingEntity(); await repository.save(booking); const found = await repository.findById(booking.id); expect(found).toBeDefined(); expect(found?.id).toBe(booking.id); expect(found?.bookingNumber.value).toBe(booking.bookingNumber.value); }); it('should return null for non-existent ID', async () => { const nonExistentId = faker.string.uuid(); const found = await repository.findById(nonExistentId); expect(found).toBeNull(); }); }); describe('findByBookingNumber', () => { it('should find a booking by booking number', async () => { const booking = createTestBookingEntity(); await repository.save(booking); const found = await repository.findByBookingNumber(booking.bookingNumber); expect(found).toBeDefined(); expect(found?.id).toBe(booking.id); expect(found?.bookingNumber.value).toBe(booking.bookingNumber.value); }); it('should return null for non-existent booking number', async () => { const nonExistentNumber = BookingNumber.generate(); const found = await repository.findByBookingNumber(nonExistentNumber); expect(found).toBeNull(); }); }); describe('findByOrganization', () => { it('should find all bookings for an organization', async () => { const booking1 = createTestBookingEntity(); const booking2 = createTestBookingEntity(); const booking3 = createTestBookingEntity(); await repository.save(booking1); await repository.save(booking2); await repository.save(booking3); const bookings = await repository.findByOrganization(testOrganization.id); expect(bookings).toHaveLength(3); expect(bookings.every((b) => b.organizationId === testOrganization.id)).toBe(true); }); it('should return empty array for organization with no bookings', async () => { const nonExistentOrgId = faker.string.uuid(); const bookings = await repository.findByOrganization(nonExistentOrgId); expect(bookings).toEqual([]); }); }); describe('findByStatus', () => { it('should find bookings by status', async () => { const draftBooking1 = createTestBookingEntity(); const draftBooking2 = createTestBookingEntity(); const confirmedBooking = Booking.create({ ...createTestBookingEntity(), status: BookingStatus.create('confirmed'), }); await repository.save(draftBooking1); await repository.save(draftBooking2); await repository.save(confirmedBooking); const draftBookings = await repository.findByStatus(BookingStatus.create('draft')); const confirmedBookings = await repository.findByStatus(BookingStatus.create('confirmed')); expect(draftBookings).toHaveLength(2); expect(confirmedBookings).toHaveLength(1); expect(draftBookings.every((b) => b.status.value === 'draft')).toBe(true); expect(confirmedBookings.every((b) => b.status.value === 'confirmed')).toBe(true); }); }); describe('delete', () => { it('should delete a booking', async () => { const booking = createTestBookingEntity(); await repository.save(booking); const found = await repository.findById(booking.id); expect(found).toBeDefined(); await repository.delete(booking.id); const deletedBooking = await repository.findById(booking.id); expect(deletedBooking).toBeNull(); }); it('should not throw error when deleting non-existent booking', async () => { const nonExistentId = faker.string.uuid(); await expect(repository.delete(nonExistentId)).resolves.not.toThrow(); }); }); describe('complex scenarios', () => { it('should handle bookings with multiple containers', async () => { const booking = createTestBookingEntity(); // Note: Container handling would be tested separately // This test ensures the booking can be saved without containers first await repository.save(booking); const found = await repository.findById(booking.id); expect(found).toBeDefined(); }); it('should maintain data integrity for nested shipper and consignee', async () => { const booking = createTestBookingEntity(); await repository.save(booking); const found = await repository.findById(booking.id); expect(found?.shipper.name).toBe(booking.shipper.name); expect(found?.shipper.contactEmail).toBe(booking.shipper.contactEmail); expect(found?.consignee.name).toBe(booking.consignee.name); expect(found?.consignee.contactEmail).toBe(booking.consignee.contactEmail); }); }); });