mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 07:38:10 +00:00
162 lines
6.6 KiB
TypeScript
162 lines
6.6 KiB
TypeScript
import { Test, TestingModule } from '@nestjs/testing';
|
|
import { INestApplication, ValidationPipe } from '@nestjs/common';
|
|
import request from 'supertest';
|
|
import { AppModule } from '../../app.module';
|
|
import { PrismaService } from '../../common/prisma.service';
|
|
|
|
describe('Payments E2E', () => {
|
|
let app: INestApplication;
|
|
let prisma: PrismaService;
|
|
let authToken: string;
|
|
let bookingId: string;
|
|
|
|
beforeAll(async () => {
|
|
const moduleFixture: TestingModule = await Test.createTestingModule({
|
|
imports: [AppModule],
|
|
}).compile();
|
|
|
|
app = moduleFixture.createNestApplication();
|
|
app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true }));
|
|
await app.init();
|
|
|
|
prisma = app.get<PrismaService>(PrismaService);
|
|
|
|
const testUser = await prisma.user.create({
|
|
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 } });
|
|
|
|
await prisma.walletAccount.create({ data: { passengerId: passenger.id, balanceMinor: 100000, currency: 'ETB' } });
|
|
|
|
authToken = 'mock-jwt-token';
|
|
|
|
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 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: { coachNumber: 'TEST-C1', label: 'A', seatClassId: seatClass.id, mode: 'seat', totalUnits: 10 },
|
|
});
|
|
|
|
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, scheduleId: schedule.id, status: 'PENDING_PAYMENT', totalMinor: 50000, currency: 'ETB' },
|
|
});
|
|
|
|
await prisma.bookingSeat.create({ data: { bookingId: booking.id, seatId: seat.id, passengerName: 'Test Passenger' } });
|
|
|
|
bookingId = booking.id;
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await prisma.$transaction([
|
|
prisma.bookingSeat.deleteMany(),
|
|
prisma.paymentIntent.deleteMany(),
|
|
prisma.booking.deleteMany(),
|
|
prisma.coachAssignment.deleteMany(),
|
|
prisma.seat.deleteMany(),
|
|
prisma.coach.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({ where: { email: 'payment-test@example.com' } }),
|
|
]);
|
|
await app.close();
|
|
});
|
|
|
|
describe('POST /payments/initiate', () => {
|
|
it('should initiate wallet payment successfully', async () => {
|
|
const response = await request(app.getHttpServer())
|
|
.post('/payments/initiate')
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send({ bookingId, method: 'WALLET' })
|
|
.expect(201);
|
|
expect(response.body.intentId).toBeDefined();
|
|
expect(response.body.status).toBe('SUCCEEDED');
|
|
});
|
|
|
|
it('should return 400 for invalid payment method', async () => {
|
|
await request(app.getHttpServer())
|
|
.post('/payments/initiate')
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send({ bookingId, method: 'INVALID_METHOD' })
|
|
.expect(400);
|
|
});
|
|
|
|
it('should return 404 for non-existent booking', async () => {
|
|
await request(app.getHttpServer())
|
|
.post('/payments/initiate')
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send({ bookingId: 'non-existent-id', method: 'WALLET' })
|
|
.expect(404);
|
|
});
|
|
});
|
|
|
|
describe('GET /payments/intents/:bookingId', () => {
|
|
it('should get payment intent status', async () => {
|
|
const response = await request(app.getHttpServer())
|
|
.get(`/payments/intents/${bookingId}`)
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.expect(200);
|
|
expect(response.body.intentId).toBeDefined();
|
|
expect(response.body.status).toBeDefined();
|
|
});
|
|
|
|
it('should return 404 for non-existent intent', async () => {
|
|
await request(app.getHttpServer())
|
|
.get('/payments/intents/non-existent-booking')
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.expect(404);
|
|
});
|
|
});
|
|
|
|
describe('Webhook endpoints', () => {
|
|
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' })
|
|
.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' })
|
|
.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' })
|
|
.expect(200);
|
|
});
|
|
|
|
it('should handle Card webhook', async () => {
|
|
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) })
|
|
.expect(200);
|
|
});
|
|
});
|
|
});
|