120 lines
4.7 KiB
TypeScript
120 lines
4.7 KiB
TypeScript
import { ExecutionContext, INestApplication } from '@nestjs/common';
|
|
import { Test } from '@nestjs/testing';
|
|
import { ConfigService } from '@nestjs/config';
|
|
import request from 'supertest';
|
|
import { Subscription } from '@domain/entities/subscription.entity';
|
|
import { SubscriptionPlan } from '@domain/value-objects/subscription-plan.vo';
|
|
import { ShipmentLimitExceededException } from '@domain/exceptions/shipment-limit-exceeded.exception';
|
|
import { CreateCsvBookingDto } from '../dto/csv-booking.dto';
|
|
import { SubscriptionStatus } from '@domain/value-objects/subscription-status.vo';
|
|
import { CsvBookingsController } from './csv-bookings.controller';
|
|
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
|
|
import { CsvBookingService } from '../services/csv-booking.service';
|
|
import { SubscriptionService } from '../services/subscription.service';
|
|
import { SHIPMENT_COUNTER_PORT } from '@domain/ports/out/shipment-counter.port';
|
|
import { ORGANIZATION_REPOSITORY } from '@domain/ports/out/organization.repository';
|
|
|
|
describe('CSV booking HTTP security', () => {
|
|
let app: INestApplication;
|
|
let subscription: Subscription;
|
|
const countPaidShipmentsForOrganizationInYear = jest.fn().mockResolvedValue(0);
|
|
const createBooking = jest.fn(async () => ({ id: 'booking' }));
|
|
const getUserBookings = jest.fn(async () => ({ bookings: [] }));
|
|
beforeAll(async () => {
|
|
const module = await Test.createTestingModule({
|
|
controllers: [CsvBookingsController],
|
|
providers: [
|
|
{ provide: CsvBookingService, useValue: { createBooking, getUserBookings } },
|
|
{
|
|
provide: SubscriptionService,
|
|
useValue: {
|
|
getOrCreateSubscription: async () => subscription,
|
|
},
|
|
},
|
|
{ provide: ConfigService, useValue: {} },
|
|
{ provide: SHIPMENT_COUNTER_PORT, useValue: { countPaidShipmentsForOrganizationInYear } },
|
|
{ provide: ORGANIZATION_REPOSITORY, useValue: {} },
|
|
],
|
|
})
|
|
.overrideGuard(JwtAuthGuard)
|
|
.useValue({
|
|
canActivate: (context: ExecutionContext) => {
|
|
const req = context.switchToHttp().getRequest();
|
|
req.user = {
|
|
id: 'user',
|
|
organizationId: 'org',
|
|
role: req.headers['x-test-role'] || 'USER',
|
|
};
|
|
return true;
|
|
},
|
|
})
|
|
.compile();
|
|
app = module.createNestApplication({ logger: false });
|
|
await app.init();
|
|
await app.listen(0, '127.0.0.1');
|
|
});
|
|
afterAll(async () => {
|
|
await app?.close();
|
|
});
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
subscription = Subscription.create({
|
|
id: 'sub',
|
|
organizationId: 'org',
|
|
plan: SubscriptionPlan.gold(),
|
|
});
|
|
countPaidShipmentsForOrganizationInYear.mockResolvedValue(0);
|
|
});
|
|
|
|
it('rejects VIEWER mutations before invoking the booking service', async () => {
|
|
await request(app.getHttpServer())
|
|
.post('/csv-bookings')
|
|
.set('x-test-role', 'VIEWER')
|
|
.attach('documents', Buffer.from('document'), 'test.pdf')
|
|
.expect(403);
|
|
expect(createBooking).not.toHaveBeenCalled();
|
|
});
|
|
it('preserves VIEWER reads', async () => {
|
|
await request(app.getHttpServer())
|
|
.get('/csv-bookings')
|
|
.set('x-test-role', 'VIEWER')
|
|
.expect(200);
|
|
expect(getUserBookings).toHaveBeenCalled();
|
|
});
|
|
it('rejects organization-wide reads for an ordinary member', async () => {
|
|
await request(app.getHttpServer()).get('/csv-bookings/organization/all').expect(403);
|
|
});
|
|
it('rejects oversized documents before invoking the service', async () => {
|
|
await request(app.getHttpServer())
|
|
.post('/csv-bookings')
|
|
.attach('documents', Buffer.alloc(10 * 1024 * 1024 + 1), 'large.pdf')
|
|
.expect(413);
|
|
expect(createBooking).not.toHaveBeenCalled();
|
|
});
|
|
it('applies the Bronze quota after a paid subscription is suspended', async () => {
|
|
subscription = subscription.updateStatus(SubscriptionStatus.create('UNPAID'));
|
|
countPaidShipmentsForOrganizationInYear.mockResolvedValue(
|
|
SubscriptionPlan.bronze().maxShipmentsPerYear
|
|
);
|
|
await expect(
|
|
app
|
|
.get(CsvBookingsController)
|
|
.createBooking({} as CreateCsvBookingDto, [{} as Express.Multer.File], {
|
|
user: { id: 'user', organizationId: 'org', role: 'USER' },
|
|
})
|
|
).rejects.toBeInstanceOf(ShipmentLimitExceededException);
|
|
expect(createBooking).not.toHaveBeenCalled();
|
|
expect(countPaidShipmentsForOrganizationInYear).toHaveBeenCalledWith(
|
|
'org',
|
|
new Date().getFullYear()
|
|
);
|
|
});
|
|
it('preserves permitted uploads', async () => {
|
|
await request(app.getHttpServer())
|
|
.post('/csv-bookings')
|
|
.attach('documents', Buffer.from('document'), 'test.pdf')
|
|
.expect(201);
|
|
expect(createBooking).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|