Refactor business logic for train,schedule,coach,seat and search modules

This commit is contained in:
Roba Boru
2026-05-22 14:47:38 +03:00
parent 9151110fd8
commit 096c717bfa
48 changed files with 2254 additions and 2865 deletions

View File

@@ -21,110 +21,44 @@ describe('Payments E2E', () => {
prisma = app.get<PrismaService>(PrismaService);
// Create test user and authenticate
const testUser = await prisma.user.create({
data: {
email: 'payment-test@example.com',
phone: '+251911111111',
fullName: 'Payment Test User',
passwordHash: '$2b$10$abcdefghijklmnopqrstuvwxyz', // Mock hash
role: 'PASSENGER',
},
data: { email: 'payment-test@example.com', phone: '+251911111112', fullName: 'Payment Test User', passwordHash: '$2b$10$abcdefghijklmnopqrstuvwxyz', role: 'PASSENGER' },
});
const passenger = await prisma.passenger.create({
data: {
userId: testUser.id,
},
});
const passenger = await prisma.passenger.create({ data: { userId: testUser.id } });
// Create wallet for test user
await prisma.walletAccount.create({
data: {
passengerId: passenger.id,
balanceMinor: 100000, // 1000 ETB
currency: 'ETB',
},
});
await prisma.walletAccount.create({ data: { passengerId: passenger.id, balanceMinor: 100000, currency: 'ETB' } });
// Mock JWT token (in real test, call /auth/login)
authToken = 'mock-jwt-token';
// Create test booking
const station1 = await prisma.station.create({
data: {
code: 'TEST1',
name: 'Test Station 1',
city: 'Test City',
lat: 9.0,
lng: 38.0,
},
const station1 = await prisma.station.create({ data: { code: 'TST1', name: 'Test Station 1', city: 'Test City', lat: 9.0, lng: 38.0 } });
const station2 = await prisma.station.create({ data: { code: 'TST2', name: 'Test Station 2', city: 'Test City 2', lat: 9.5, lng: 38.5 } });
const train = await prisma.train.create({ data: { number: 'TEST-001', name: 'Test Train' } });
const schedule = await prisma.trainSchedule.create({
data: { trainId: train.id, originStationId: station1.id, destinationStationId: station2.id, departureAt: new Date(Date.now() + 86400000), arrivalAt: new Date(Date.now() + 90000000), durationMinutes: 60 },
});
const station2 = await prisma.station.create({
data: {
code: 'TEST2',
name: 'Test Station 2',
city: 'Test City 2',
lat: 9.5,
lng: 38.5,
},
});
const service = await prisma.trainService.create({
data: {
number: 'TEST-001',
name: 'Test Service',
},
});
const trip = await prisma.trip.create({
data: {
serviceId: service.id,
originStationId: station1.id,
destinationStationId: station2.id,
departureAt: new Date(Date.now() + 86400000),
arrivalAt: new Date(Date.now() + 90000000),
durationMinutes: 60,
},
const seatClass = await prisma.seatClass.upsert({
where: { name: 'Economy Regular' },
update: {},
create: { name: 'Economy Regular', description: 'Standard economy seating', basePrice: 45000, isActive: true },
});
const coach = await prisma.coach.create({
data: {
tripId: trip.id,
label: 'A',
serviceClass: 'ECONOMY_REGULAR',
},
data: { coachNumber: 'TEST-C1', label: 'A', seatClassId: seatClass.id, mode: 'seat', totalUnits: 10 },
});
const seat = await prisma.seat.create({
data: {
coachId: coach.id,
row: 1,
col: 'A',
label: '1A',
status: 'AVAILABLE',
},
});
await prisma.coachAssignment.create({ data: { scheduleId: schedule.id, coachId: coach.id, positionNumber: 1 } });
const seat = await prisma.seat.create({ data: { coachId: coach.id, row: 1, col: 'A', label: '1A', status: 'AVAILABLE' } });
const booking = await prisma.booking.create({
data: {
bookingRef: 'TEST-BOOK-001',
passengerId: passenger.id,
tripId: trip.id,
status: 'PENDING_PAYMENT',
totalMinor: 50000, // 500 ETB
currency: 'ETB',
},
data: { bookingRef: 'TEST-BOOK-001', passengerId: passenger.id, scheduleId: schedule.id, status: 'PENDING_PAYMENT', totalMinor: 50000, currency: 'ETB' },
});
await prisma.bookingSeat.create({
data: {
bookingId: booking.id,
seatId: seat.id,
passengerName: 'Test Passenger',
},
});
await prisma.bookingSeat.create({ data: { bookingId: booking.id, seatId: seat.id, passengerName: 'Test Passenger' } });
bookingId = booking.id;
});
@@ -134,15 +68,16 @@ describe('Payments E2E', () => {
prisma.bookingSeat.deleteMany(),
prisma.paymentIntent.deleteMany(),
prisma.booking.deleteMany(),
prisma.coachAssignment.deleteMany(),
prisma.seat.deleteMany(),
prisma.coach.deleteMany(),
prisma.trip.deleteMany(),
prisma.trainService.deleteMany(),
prisma.station.deleteMany(),
prisma.trainSchedule.deleteMany(),
prisma.train.deleteMany(),
prisma.station.deleteMany({ where: { code: { in: ['TST1', 'TST2'] } } }),
prisma.walletLedgerEntry.deleteMany(),
prisma.walletAccount.deleteMany(),
prisma.passenger.deleteMany(),
prisma.user.deleteMany(),
prisma.user.deleteMany({ where: { email: 'payment-test@example.com' } }),
]);
await app.close();
});
@@ -152,12 +87,8 @@ describe('Payments E2E', () => {
const response = await request(app.getHttpServer())
.post('/payments/initiate')
.set('Authorization', `Bearer ${authToken}`)
.send({
bookingId,
method: 'WALLET',
})
.send({ bookingId, method: 'WALLET' })
.expect(201);
expect(response.body.intentId).toBeDefined();
expect(response.body.status).toBe('SUCCEEDED');
});
@@ -166,10 +97,7 @@ describe('Payments E2E', () => {
await request(app.getHttpServer())
.post('/payments/initiate')
.set('Authorization', `Bearer ${authToken}`)
.send({
bookingId,
method: 'INVALID_METHOD',
})
.send({ bookingId, method: 'INVALID_METHOD' })
.expect(400);
});
@@ -177,10 +105,7 @@ describe('Payments E2E', () => {
await request(app.getHttpServer())
.post('/payments/initiate')
.set('Authorization', `Bearer ${authToken}`)
.send({
bookingId: 'non-existent-id',
method: 'WALLET',
})
.send({ bookingId: 'non-existent-id', method: 'WALLET' })
.expect(404);
});
});
@@ -191,7 +116,6 @@ describe('Payments E2E', () => {
.get(`/payments/intents/${bookingId}`)
.set('Authorization', `Bearer ${authToken}`)
.expect(200);
expect(response.body.intentId).toBeDefined();
expect(response.body.status).toBeDefined();
});
@@ -208,38 +132,21 @@ describe('Payments E2E', () => {
it('should handle Telebirr webhook', async () => {
await request(app.getHttpServer())
.post('/payments/webhooks/telebirr')
.send({
merch_order_id: 'TEST-ORDER-123',
payment_order_id: 'PAY-123',
trade_status: 'Completed',
sign: 'mock-signature',
})
.send({ merch_order_id: 'TEST-ORDER-123', payment_order_id: 'PAY-123', trade_status: 'Completed', sign: 'mock-signature' })
.expect(200);
});
it('should handle CBE Birr webhook', async () => {
await request(app.getHttpServer())
.post('/payments/webhooks/cbe-birr')
.send({
merchantId: 'TEST-MERCHANT',
merchantOrderId: 'TEST-ORDER-123',
orderId: 'CBE-ORDER-123',
status: 'SUCCESS',
signature: 'mock-signature',
})
.send({ merchantId: 'TEST-MERCHANT', merchantOrderId: 'TEST-ORDER-123', orderId: 'CBE-ORDER-123', status: 'SUCCESS', signature: 'mock-signature' })
.expect(200);
});
it('should handle eBirr webhook', async () => {
await request(app.getHttpServer())
.post('/payments/webhooks/ebirr')
.send({
merchantCode: 'TEST-MERCHANT',
orderNo: 'TEST-ORDER-123',
tradeStatus: 'TRADE_SUCCESS',
timestamp: Date.now(),
sign: 'mock-signature',
})
.send({ merchantCode: 'TEST-MERCHANT', orderNo: 'TEST-ORDER-123', tradeStatus: 'TRADE_SUCCESS', timestamp: Date.now(), sign: 'mock-signature' })
.expect(200);
});
@@ -247,23 +154,7 @@ describe('Payments E2E', () => {
await request(app.getHttpServer())
.post('/payments/webhooks/card')
.set('stripe-signature', 'mock-signature')
.send({
id: 'evt_123',
type: 'payment_intent.succeeded',
data: {
object: {
id: 'pi_123',
status: 'succeeded',
amount: 50000,
currency: 'ETB',
metadata: {
merchantOrderId: 'TEST-ORDER-123',
bookingRef: 'TEST-BOOK-001',
},
},
},
created: Math.floor(Date.now() / 1000),
})
.send({ id: 'evt_123', type: 'payment_intent.succeeded', data: { object: { id: 'pi_123', status: 'succeeded', amount: 50000, currency: 'ETB', metadata: { merchantOrderId: 'TEST-ORDER-123', bookingRef: 'TEST-BOOK-001' } } }, created: Math.floor(Date.now() / 1000) })
.expect(200);
});
});

View File

@@ -18,7 +18,7 @@ describe('PaymentsService', () => {
let ticketsService: TicketsService;
let eventEmitter: EventEmitter2;
const mockPrisma = {
const mockPrisma: Record<string, any> = {
booking: {
findUnique: jest.fn(),
update: jest.fn(),
@@ -44,7 +44,7 @@ describe('PaymentsService', () => {
loyaltyLedgerEntry: {
create: jest.fn(),
},
$transaction: jest.fn((callback) => callback(mockPrisma)),
$transaction: jest.fn((callback: (tx: any) => any) => callback(mockPrisma)),
};
const mockSeatsService = {