Some checks failed
CI/CD Pipeline / Discord Notification (Failure) (push) Blocked by required conditions
CI/CD Pipeline / Integration Tests (push) Blocked by required conditions
CI/CD Pipeline / Deployment Summary (push) Blocked by required conditions
CI/CD Pipeline / Deploy to Portainer (push) Blocked by required conditions
CI/CD Pipeline / Discord Notification (Success) (push) Blocked by required conditions
CI/CD Pipeline / Backend - Build, Test & Push (push) Failing after 1m20s
CI/CD Pipeline / Frontend - Build, Test & Push (push) Has been cancelled
363 lines
13 KiB
TypeScript
363 lines
13 KiB
TypeScript
/**
|
|
* Carrier Portal E2E Tests
|
|
*
|
|
* Tests the complete carrier portal workflow including:
|
|
* - Account creation
|
|
* - Authentication
|
|
* - Dashboard access
|
|
* - Booking management
|
|
*/
|
|
|
|
import { Test, TestingModule } from '@nestjs/testing';
|
|
import { INestApplication, ValidationPipe } from '@nestjs/common';
|
|
import request from 'supertest';
|
|
import { AppModule } from '../src/app.module';
|
|
|
|
describe('Carrier Portal (e2e)', () => {
|
|
let app: INestApplication;
|
|
let carrierAccessToken: string;
|
|
let _carrierId: string;
|
|
let bookingId: string;
|
|
|
|
beforeAll(async () => {
|
|
const moduleFixture: TestingModule = await Test.createTestingModule({
|
|
imports: [AppModule],
|
|
}).compile();
|
|
|
|
app = moduleFixture.createNestApplication();
|
|
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
|
|
await app.init();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await app.close();
|
|
});
|
|
|
|
describe('Authentication', () => {
|
|
describe('POST /api/v1/carrier-auth/login', () => {
|
|
it('should login with valid credentials', () => {
|
|
return request(app.getHttpServer())
|
|
.post('/api/v1/carrier-auth/login')
|
|
.send({
|
|
email: 'test.carrier@example.com',
|
|
password: 'ValidPassword123!',
|
|
})
|
|
.expect(200)
|
|
.expect((res: any) => {
|
|
expect(res.body).toHaveProperty('accessToken');
|
|
expect(res.body).toHaveProperty('refreshToken');
|
|
expect(res.body).toHaveProperty('carrier');
|
|
expect(res.body.carrier).toHaveProperty('id');
|
|
expect(res.body.carrier).toHaveProperty('companyName');
|
|
expect(res.body.carrier).toHaveProperty('email');
|
|
|
|
// Save tokens for subsequent tests
|
|
carrierAccessToken = res.body.accessToken;
|
|
_carrierId = res.body.carrier.id;
|
|
});
|
|
});
|
|
|
|
it('should return 401 for invalid credentials', () => {
|
|
return request(app.getHttpServer())
|
|
.post('/api/v1/carrier-auth/login')
|
|
.send({
|
|
email: 'test.carrier@example.com',
|
|
password: 'WrongPassword',
|
|
})
|
|
.expect(401);
|
|
});
|
|
|
|
it('should return 400 for invalid email format', () => {
|
|
return request(app.getHttpServer())
|
|
.post('/api/v1/carrier-auth/login')
|
|
.send({
|
|
email: 'invalid-email',
|
|
password: 'Password123!',
|
|
})
|
|
.expect(400);
|
|
});
|
|
|
|
it('should return 400 for missing required fields', () => {
|
|
return request(app.getHttpServer())
|
|
.post('/api/v1/carrier-auth/login')
|
|
.send({
|
|
email: 'test@example.com',
|
|
})
|
|
.expect(400);
|
|
});
|
|
});
|
|
|
|
describe('POST /api/v1/carrier-auth/verify-auto-login', () => {
|
|
it('should verify valid auto-login token', async () => {
|
|
// This would require generating a valid auto-login token first
|
|
// For now, we'll test with an invalid token to verify error handling
|
|
return request(app.getHttpServer())
|
|
.post('/api/v1/carrier-auth/verify-auto-login')
|
|
.send({
|
|
token: 'invalid-token',
|
|
})
|
|
.expect(401);
|
|
});
|
|
});
|
|
|
|
describe('GET /api/v1/carrier-auth/me', () => {
|
|
it('should get carrier profile with valid token', () => {
|
|
return request(app.getHttpServer())
|
|
.get('/api/v1/carrier-auth/me')
|
|
.set('Authorization', `Bearer ${carrierAccessToken}`)
|
|
.expect(200)
|
|
.expect((res: any) => {
|
|
expect(res.body).toHaveProperty('id');
|
|
expect(res.body).toHaveProperty('companyName');
|
|
expect(res.body).toHaveProperty('email');
|
|
expect(res.body).toHaveProperty('isVerified');
|
|
expect(res.body).toHaveProperty('totalBookingsAccepted');
|
|
});
|
|
});
|
|
|
|
it('should return 401 without auth token', () => {
|
|
return request(app.getHttpServer()).get('/api/v1/carrier-auth/me').expect(401);
|
|
});
|
|
});
|
|
|
|
describe('PATCH /api/v1/carrier-auth/change-password', () => {
|
|
it('should change password with valid credentials', () => {
|
|
return request(app.getHttpServer())
|
|
.patch('/api/v1/carrier-auth/change-password')
|
|
.set('Authorization', `Bearer ${carrierAccessToken}`)
|
|
.send({
|
|
oldPassword: 'ValidPassword123!',
|
|
newPassword: 'NewValidPassword123!',
|
|
})
|
|
.expect(200);
|
|
});
|
|
|
|
it('should return 401 for invalid old password', () => {
|
|
return request(app.getHttpServer())
|
|
.patch('/api/v1/carrier-auth/change-password')
|
|
.set('Authorization', `Bearer ${carrierAccessToken}`)
|
|
.send({
|
|
oldPassword: 'WrongOldPassword',
|
|
newPassword: 'NewValidPassword123!',
|
|
})
|
|
.expect(401);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Dashboard', () => {
|
|
describe('GET /api/v1/carrier-dashboard/stats', () => {
|
|
it('should get dashboard statistics', () => {
|
|
return request(app.getHttpServer())
|
|
.get('/api/v1/carrier-dashboard/stats')
|
|
.set('Authorization', `Bearer ${carrierAccessToken}`)
|
|
.expect(200)
|
|
.expect((res: any) => {
|
|
expect(res.body).toHaveProperty('totalBookings');
|
|
expect(res.body).toHaveProperty('pendingBookings');
|
|
expect(res.body).toHaveProperty('acceptedBookings');
|
|
expect(res.body).toHaveProperty('rejectedBookings');
|
|
expect(res.body).toHaveProperty('acceptanceRate');
|
|
expect(res.body).toHaveProperty('totalRevenue');
|
|
expect(res.body.totalRevenue).toHaveProperty('usd');
|
|
expect(res.body.totalRevenue).toHaveProperty('eur');
|
|
expect(res.body).toHaveProperty('recentActivities');
|
|
expect(Array.isArray(res.body.recentActivities)).toBe(true);
|
|
});
|
|
});
|
|
|
|
it('should return 401 without auth token', () => {
|
|
return request(app.getHttpServer()).get('/api/v1/carrier-dashboard/stats').expect(401);
|
|
});
|
|
});
|
|
|
|
describe('GET /api/v1/carrier-dashboard/bookings', () => {
|
|
it('should get paginated bookings list', () => {
|
|
return request(app.getHttpServer())
|
|
.get('/api/v1/carrier-dashboard/bookings')
|
|
.set('Authorization', `Bearer ${carrierAccessToken}`)
|
|
.query({ page: 1, limit: 10 })
|
|
.expect(200)
|
|
.expect((res: any) => {
|
|
expect(res.body).toHaveProperty('data');
|
|
expect(res.body).toHaveProperty('total');
|
|
expect(res.body).toHaveProperty('page', 1);
|
|
expect(res.body).toHaveProperty('limit', 10);
|
|
expect(Array.isArray(res.body.data)).toBe(true);
|
|
|
|
if (res.body.data.length > 0) {
|
|
bookingId = res.body.data[0].id;
|
|
const booking = res.body.data[0];
|
|
expect(booking).toHaveProperty('id');
|
|
expect(booking).toHaveProperty('origin');
|
|
expect(booking).toHaveProperty('destination');
|
|
expect(booking).toHaveProperty('status');
|
|
expect(booking).toHaveProperty('priceUsd');
|
|
expect(booking).toHaveProperty('transitDays');
|
|
}
|
|
});
|
|
});
|
|
|
|
it('should filter bookings by status', () => {
|
|
return request(app.getHttpServer())
|
|
.get('/api/v1/carrier-dashboard/bookings')
|
|
.set('Authorization', `Bearer ${carrierAccessToken}`)
|
|
.query({ status: 'PENDING' })
|
|
.expect(200)
|
|
.expect((res: any) => {
|
|
expect(res.body).toHaveProperty('data');
|
|
// All bookings should have PENDING status
|
|
res.body.data.forEach((booking: any) => {
|
|
expect(booking.status).toBe('PENDING');
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('GET /api/v1/carrier-dashboard/bookings/:id', () => {
|
|
it('should get booking details', async () => {
|
|
if (!bookingId) {
|
|
return; // Skip if no bookings available
|
|
}
|
|
|
|
await request(app.getHttpServer())
|
|
.get(`/api/v1/carrier-dashboard/bookings/${bookingId}`)
|
|
.set('Authorization', `Bearer ${carrierAccessToken}`)
|
|
.expect(200)
|
|
.expect((res: any) => {
|
|
expect(res.body).toHaveProperty('id', bookingId);
|
|
expect(res.body).toHaveProperty('origin');
|
|
expect(res.body).toHaveProperty('destination');
|
|
expect(res.body).toHaveProperty('volumeCBM');
|
|
expect(res.body).toHaveProperty('weightKG');
|
|
expect(res.body).toHaveProperty('priceUSD');
|
|
expect(res.body).toHaveProperty('status');
|
|
expect(res.body).toHaveProperty('documents');
|
|
expect(res.body).toHaveProperty('carrierViewedAt');
|
|
});
|
|
});
|
|
|
|
it('should return 404 for non-existent booking', () => {
|
|
return request(app.getHttpServer())
|
|
.get('/api/v1/carrier-dashboard/bookings/non-existent-id')
|
|
.set('Authorization', `Bearer ${carrierAccessToken}`)
|
|
.expect(404);
|
|
});
|
|
|
|
it('should return 401 without auth token', () => {
|
|
return request(app.getHttpServer())
|
|
.get(`/api/v1/carrier-dashboard/bookings/${bookingId || 'test-id'}`)
|
|
.expect(401);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Booking Actions', () => {
|
|
describe('POST /api/v1/carrier-dashboard/bookings/:id/accept', () => {
|
|
it('should accept a pending booking', async () => {
|
|
if (!bookingId) {
|
|
return; // Skip if no bookings available
|
|
}
|
|
|
|
await request(app.getHttpServer())
|
|
.post(`/api/v1/carrier-dashboard/bookings/${bookingId}/accept`)
|
|
.set('Authorization', `Bearer ${carrierAccessToken}`)
|
|
.send({
|
|
notes: 'Accepted - ready to proceed',
|
|
})
|
|
.expect(200);
|
|
});
|
|
|
|
it('should return 401 without auth token', () => {
|
|
return request(app.getHttpServer())
|
|
.post(`/api/v1/carrier-dashboard/bookings/test-id/accept`)
|
|
.send({ notes: 'Test' })
|
|
.expect(401);
|
|
});
|
|
});
|
|
|
|
describe('POST /api/v1/carrier-dashboard/bookings/:id/reject', () => {
|
|
it('should reject a pending booking with reason', async () => {
|
|
if (!bookingId) {
|
|
return; // Skip if no bookings available
|
|
}
|
|
|
|
await request(app.getHttpServer())
|
|
.post(`/api/v1/carrier-dashboard/bookings/${bookingId}/reject`)
|
|
.set('Authorization', `Bearer ${carrierAccessToken}`)
|
|
.send({
|
|
reason: 'Capacity not available',
|
|
notes: 'Cannot accommodate this shipment at this time',
|
|
})
|
|
.expect(200);
|
|
});
|
|
|
|
it('should return 400 without rejection reason', async () => {
|
|
if (!bookingId) {
|
|
return; // Skip if no bookings available
|
|
}
|
|
|
|
await request(app.getHttpServer())
|
|
.post(`/api/v1/carrier-dashboard/bookings/${bookingId}/reject`)
|
|
.set('Authorization', `Bearer ${carrierAccessToken}`)
|
|
.send({})
|
|
.expect(400);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Documents', () => {
|
|
describe('GET /api/v1/carrier-dashboard/bookings/:bookingId/documents/:documentId/download', () => {
|
|
it('should download document with valid access', async () => {
|
|
if (!bookingId) {
|
|
return; // Skip if no bookings available
|
|
}
|
|
|
|
// First get the booking details to find a document ID
|
|
const res = await request(app.getHttpServer())
|
|
.get(`/api/v1/carrier-dashboard/bookings/${bookingId}`)
|
|
.set('Authorization', `Bearer ${carrierAccessToken}`)
|
|
.expect(200);
|
|
|
|
if (res.body.documents && res.body.documents.length > 0) {
|
|
const documentId = res.body.documents[0].id;
|
|
|
|
await request(app.getHttpServer())
|
|
.get(`/api/v1/carrier-dashboard/bookings/${bookingId}/documents/${documentId}/download`)
|
|
.set('Authorization', `Bearer ${carrierAccessToken}`)
|
|
.expect(200);
|
|
}
|
|
});
|
|
|
|
it('should return 403 for unauthorized access to document', () => {
|
|
return request(app.getHttpServer())
|
|
.get('/api/v1/carrier-dashboard/bookings/other-booking/documents/test-doc/download')
|
|
.set('Authorization', `Bearer ${carrierAccessToken}`)
|
|
.expect(403);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Password Reset', () => {
|
|
describe('POST /api/v1/carrier-auth/request-password-reset', () => {
|
|
it('should request password reset for existing carrier', () => {
|
|
return request(app.getHttpServer())
|
|
.post('/api/v1/carrier-auth/request-password-reset')
|
|
.send({
|
|
email: 'test.carrier@example.com',
|
|
})
|
|
.expect(200);
|
|
});
|
|
|
|
it('should return 401 for non-existent carrier (security)', () => {
|
|
return request(app.getHttpServer())
|
|
.post('/api/v1/carrier-auth/request-password-reset')
|
|
.send({
|
|
email: 'nonexistent@example.com',
|
|
})
|
|
.expect(401);
|
|
});
|
|
});
|
|
});
|
|
});
|