105 lines
3.3 KiB
TypeScript
105 lines
3.3 KiB
TypeScript
import { Logger } from '@nestjs/common';
|
|
import { CsvBookingService } from './csv-booking.service';
|
|
import { CsvBookingStatus } from '@domain/entities/csv-booking.entity';
|
|
|
|
/**
|
|
* Validation d'un virement par un administrateur : le booking passe en PENDING
|
|
* et la demande part chez le transporteur. Un echec de cet email etait avale
|
|
* sans que l'administrateur le sache.
|
|
*/
|
|
|
|
function pendingTransferBooking() {
|
|
const booking = {
|
|
id: 'b-1',
|
|
bookingNumber: 'XPD-2026-AAAAAA',
|
|
userId: 'u-1',
|
|
organizationId: 'org-1',
|
|
carrierName: 'SSC Consolidation',
|
|
carrierEmail: 'booking@ssc.test',
|
|
origin: { getValue: () => 'FRLEH' },
|
|
destination: { getValue: () => 'EGEDK' },
|
|
volumeCBM: 2.4,
|
|
weightKG: 850,
|
|
palletCount: 2,
|
|
priceUSD: 200,
|
|
priceEUR: 180,
|
|
primaryCurrency: 'EUR',
|
|
transitDays: 11,
|
|
containerType: 'LCL',
|
|
status: CsvBookingStatus.PENDING_BANK_TRANSFER as CsvBookingStatus,
|
|
documents: [],
|
|
confirmationToken: 'token',
|
|
requestedAt: new Date('2026-09-01T10:00:00Z'),
|
|
notes: undefined,
|
|
getRouteDescription: () => 'FRLEH → EGEDK',
|
|
isExpired: () => false,
|
|
getPriceInCurrency: () => 180,
|
|
options: {},
|
|
markBankTransferValidated() {
|
|
booking.status = CsvBookingStatus.PENDING;
|
|
},
|
|
};
|
|
return booking;
|
|
}
|
|
|
|
function buildService(options: { emailFails: boolean }) {
|
|
const booking = pendingTransferBooking();
|
|
const csvBookingRepository = {
|
|
findById: jest.fn(async () => booking),
|
|
update: jest.fn(async (b: unknown) => b),
|
|
repository: {
|
|
findOne: jest.fn(async () => ({ bookingNumber: booking.bookingNumber, passwordHash: null })),
|
|
save: jest.fn(async (b: unknown) => b),
|
|
},
|
|
};
|
|
const emailAdapter = {
|
|
sendCsvBookingRequest: jest.fn(async () => {
|
|
if (options.emailFails) throw new Error('550 sender not valid');
|
|
}),
|
|
};
|
|
const notificationRepository = { save: jest.fn(async () => undefined) };
|
|
const unused = {} as never;
|
|
|
|
const service = new CsvBookingService(
|
|
csvBookingRepository as never,
|
|
notificationRepository as never,
|
|
emailAdapter as never,
|
|
unused,
|
|
unused,
|
|
unused,
|
|
unused
|
|
);
|
|
return { service, booking, emailAdapter };
|
|
}
|
|
|
|
describe('CsvBookingService.validateBankTransfer', () => {
|
|
beforeEach(() => {
|
|
jest.spyOn(Logger.prototype, 'log').mockImplementation(() => undefined);
|
|
jest.spyOn(Logger.prototype, 'error').mockImplementation(() => undefined);
|
|
});
|
|
|
|
afterEach(() => jest.restoreAllMocks());
|
|
|
|
it('active le booking et envoie la demande au transporteur', async () => {
|
|
const { service, booking, emailAdapter } = buildService({ emailFails: false });
|
|
|
|
const result = await service.validateBankTransfer('b-1');
|
|
|
|
expect(booking.status).toBe(CsvBookingStatus.PENDING);
|
|
expect(emailAdapter.sendCsvBookingRequest).toHaveBeenCalledWith(
|
|
'booking@ssc.test',
|
|
expect.objectContaining({ bookingNumber: 'XPD-2026-AAAAAA', confirmationToken: 'token' })
|
|
);
|
|
expect(result.carrierEmailSent).toBe(true);
|
|
}, 20000);
|
|
|
|
it("signale l'echec de l'email au lieu de l'avaler, sans annuler la validation", async () => {
|
|
const { service, booking } = buildService({ emailFails: true });
|
|
|
|
const result = await service.validateBankTransfer('b-1');
|
|
|
|
expect(booking.status).toBe(CsvBookingStatus.PENDING);
|
|
expect(result.carrierEmailSent).toBe(false);
|
|
}, 20000);
|
|
});
|