import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication, ValidationPipe } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from '../src/app.module'; import { getRepositoryToken } from '@nestjs/typeorm'; import { User, UserRole } from '../src/modules/users/entities/user.entity'; import { Store } from '../src/modules/stores/entities/store.entity'; import { StoreUser, StoreUserRole } from '../src/modules/stores/entities/store-user.entity'; import { IaProvider } from '../src/modules/admin/entities/ia-provider.entity'; import { Promotion, PromotionType } from '../src/modules/admin/entities/promotion.entity'; import { CreditPackage } from '../src/modules/credits/entities/credit-package.entity'; import { ProductSubmission } from '../src/modules/feedback/entities/product-submission.entity'; import { Referral, ReferralStatus } from '../src/modules/referrals/entities/referral.entity'; import { JwtService } from '@nestjs/jwt'; import { Repository } from 'typeorm'; describe('AdminController (e2e)', () => { let app: INestApplication; let jwtService: JwtService; let userRepository: Repository; let storeRepository: Repository; let storeUserRepository: Repository; let providerRepository: Repository; let promotionRepository: Repository; let packageRepository: Repository; let productSubmissionRepository: Repository; let referralRepository: Repository; let superAdminUser: User; let adminUser: User; let moderatorUser: User; let viewerUser: User; let regularUser: User; let testStore: Store; let testProvider: IaProvider; let testPackage: CreditPackage; let superAdminToken: string; let adminToken: string; let moderatorToken: string; let viewerToken: string; let regularToken: string; beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); app.useGlobalPipes(new ValidationPipe({ whitelist: true })); await app.init(); jwtService = moduleFixture.get(JwtService); userRepository = moduleFixture.get(getRepositoryToken(User)); storeRepository = moduleFixture.get(getRepositoryToken(Store)); storeUserRepository = moduleFixture.get(getRepositoryToken(StoreUser)); providerRepository = moduleFixture.get(getRepositoryToken(IaProvider)); promotionRepository = moduleFixture.get(getRepositoryToken(Promotion)); packageRepository = moduleFixture.get(getRepositoryToken(CreditPackage)); productSubmissionRepository = moduleFixture.get(getRepositoryToken(ProductSubmission)); referralRepository = moduleFixture.get(getRepositoryToken(Referral)); // Create test users with different roles superAdminUser = await userRepository.save({ phone: '+521111111111', name: 'Super Admin Test', role: UserRole.SUPER_ADMIN, }); adminUser = await userRepository.save({ phone: '+522222222222', name: 'Admin Test', role: UserRole.ADMIN, }); moderatorUser = await userRepository.save({ phone: '+523333333333', name: 'Moderator Test', role: UserRole.MODERATOR, }); viewerUser = await userRepository.save({ phone: '+524444444444', name: 'Viewer Test', role: UserRole.VIEWER, }); regularUser = await userRepository.save({ phone: '+525555555555', name: 'Regular User Test', role: UserRole.USER, }); // Create test store testStore = await storeRepository.save({ name: 'Test Admin Store', ownerId: regularUser.id, }); await storeUserRepository.save({ storeId: testStore.id, userId: regularUser.id, role: StoreUserRole.OWNER, }); // Create test provider testProvider = await providerRepository.save({ name: 'Test Provider', code: 'test-provider', costPerFrame: 0.01, costPerToken: 0.00001, isActive: true, isDefault: false, }); // Create test package testPackage = await packageRepository.save({ name: 'Test Package', credits: 100, priceMXN: 99.00, isActive: true, }); // Generate tokens superAdminToken = jwtService.sign({ sub: superAdminUser.id, phone: superAdminUser.phone, role: superAdminUser.role }); adminToken = jwtService.sign({ sub: adminUser.id, phone: adminUser.phone, role: adminUser.role }); moderatorToken = jwtService.sign({ sub: moderatorUser.id, phone: moderatorUser.phone, role: moderatorUser.role }); viewerToken = jwtService.sign({ sub: viewerUser.id, phone: viewerUser.phone, role: viewerUser.role }); regularToken = jwtService.sign({ sub: regularUser.id, phone: regularUser.phone, role: regularUser.role }); }); afterAll(async () => { // Cleanup await promotionRepository.delete({}); await providerRepository.delete({ id: testProvider.id }); await storeUserRepository.delete({ storeId: testStore.id }); await storeRepository.delete({ id: testStore.id }); await userRepository.delete({ id: superAdminUser.id }); await userRepository.delete({ id: adminUser.id }); await userRepository.delete({ id: moderatorUser.id }); await userRepository.delete({ id: viewerUser.id }); await userRepository.delete({ id: regularUser.id }); await app.close(); }); describe('/admin/dashboard (GET)', () => { it('should return dashboard metrics for VIEWER+', async () => { const response = await request(app.getHttpServer()) .get('/admin/dashboard') .set('Authorization', `Bearer ${viewerToken}`) .expect(200); expect(response.body.metrics).toBeDefined(); expect(response.body.metrics.users).toBeDefined(); expect(response.body.metrics.stores).toBeDefined(); expect(response.body.metrics.revenue).toBeDefined(); }); it('should reject regular users', async () => { await request(app.getHttpServer()) .get('/admin/dashboard') .set('Authorization', `Bearer ${regularToken}`) .expect(403); }); it('should reject without auth', async () => { await request(app.getHttpServer()) .get('/admin/dashboard') .expect(401); }); }); describe('/admin/providers (GET)', () => { it('should return providers for ADMIN+', async () => { const response = await request(app.getHttpServer()) .get('/admin/providers') .set('Authorization', `Bearer ${adminToken}`) .expect(200); expect(response.body.providers).toBeDefined(); expect(Array.isArray(response.body.providers)).toBe(true); }); it('should reject MODERATOR', async () => { await request(app.getHttpServer()) .get('/admin/providers') .set('Authorization', `Bearer ${moderatorToken}`) .expect(403); }); }); describe('/admin/providers/:id (PATCH)', () => { it('should update provider for SUPER_ADMIN only', async () => { const response = await request(app.getHttpServer()) .patch(`/admin/providers/${testProvider.id}`) .set('Authorization', `Bearer ${superAdminToken}`) .send({ costPerFrame: 0.02 }) .expect(200); expect(response.body.message).toBe('Proveedor actualizado exitosamente'); expect(Number(response.body.provider.costPerFrame)).toBe(0.02); }); it('should reject ADMIN', async () => { await request(app.getHttpServer()) .patch(`/admin/providers/${testProvider.id}`) .set('Authorization', `Bearer ${adminToken}`) .send({ costPerFrame: 0.03 }) .expect(403); }); }); describe('/admin/packages (POST)', () => { let createdPackageId: string; it('should create package for ADMIN+', async () => { const response = await request(app.getHttpServer()) .post('/admin/packages') .set('Authorization', `Bearer ${adminToken}`) .send({ name: 'New Admin Package', credits: 500, priceMxn: 299.00, }) .expect(201); expect(response.body.message).toBe('Paquete creado exitosamente'); expect(response.body.package.name).toBe('New Admin Package'); createdPackageId = response.body.package.id; }); afterAll(async () => { if (createdPackageId) { await packageRepository.delete({ id: createdPackageId }); } }); }); describe('/admin/promotions (POST)', () => { let createdPromotionId: string; it('should create promotion for ADMIN+', async () => { const startsAt = new Date(); const endsAt = new Date(); endsAt.setMonth(endsAt.getMonth() + 1); const response = await request(app.getHttpServer()) .post('/admin/promotions') .set('Authorization', `Bearer ${adminToken}`) .send({ name: 'Test Promo', code: 'TESTPROMO2024', type: PromotionType.PERCENTAGE, value: 20, startsAt: startsAt.toISOString(), endsAt: endsAt.toISOString(), }) .expect(201); expect(response.body.message).toBe('Promocion creada exitosamente'); expect(response.body.promotion.code).toBe('TESTPROMO2024'); createdPromotionId = response.body.promotion.id; }); it('should reject duplicate promotion code', async () => { const startsAt = new Date(); const endsAt = new Date(); endsAt.setMonth(endsAt.getMonth() + 1); await request(app.getHttpServer()) .post('/admin/promotions') .set('Authorization', `Bearer ${adminToken}`) .send({ name: 'Duplicate Promo', code: 'TESTPROMO2024', type: PromotionType.PERCENTAGE, value: 10, startsAt: startsAt.toISOString(), endsAt: endsAt.toISOString(), }) .expect(400); }); afterAll(async () => { if (createdPromotionId) { await promotionRepository.delete({ id: createdPromotionId }); } }); }); describe('/admin/products/pending (GET)', () => { it('should return pending products for MODERATOR+', async () => { const response = await request(app.getHttpServer()) .get('/admin/products/pending') .set('Authorization', `Bearer ${moderatorToken}`) .expect(200); expect(response.body.products).toBeDefined(); expect(Array.isArray(response.body.products)).toBe(true); expect(response.body.total).toBeDefined(); expect(response.body.page).toBeDefined(); }); it('should reject VIEWER', async () => { await request(app.getHttpServer()) .get('/admin/products/pending') .set('Authorization', `Bearer ${viewerToken}`) .expect(403); }); }); describe('/admin/products/:id/approve (POST)', () => { let testSubmission: ProductSubmission; beforeAll(async () => { testSubmission = await productSubmissionRepository.save({ userId: regularUser.id, storeId: testStore.id, name: 'Test Product for Approval', category: 'Test', status: 'PENDING' as any, }); }); it('should approve product for MODERATOR+', async () => { const response = await request(app.getHttpServer()) .post(`/admin/products/${testSubmission.id}/approve`) .set('Authorization', `Bearer ${moderatorToken}`) .send({ notes: 'Looks good' }) .expect(201); expect(response.body.message).toBe('Producto aprobado exitosamente'); expect(response.body.product.status).toBe('APPROVED'); }); afterAll(async () => { await productSubmissionRepository.delete({ id: testSubmission.id }); }); }); describe('/admin/referrals/fraud-holds (GET)', () => { it('should return fraud-hold referrals for MODERATOR+', async () => { const response = await request(app.getHttpServer()) .get('/admin/referrals/fraud-holds') .set('Authorization', `Bearer ${moderatorToken}`) .expect(200); expect(response.body.referrals).toBeDefined(); expect(Array.isArray(response.body.referrals)).toBe(true); }); }); describe('/admin/referrals/:id/approve (POST)', () => { let testReferral: Referral; beforeAll(async () => { testReferral = await referralRepository.save({ referrerId: adminUser.id, referredId: regularUser.id, referralCode: 'TESTFRAUD123', status: ReferralStatus.PENDING, fraudHold: true, fraudReason: 'Suspicious activity', }); }); it('should approve fraud-hold referral for MODERATOR+', async () => { const response = await request(app.getHttpServer()) .post(`/admin/referrals/${testReferral.id}/approve`) .set('Authorization', `Bearer ${moderatorToken}`) .send({ notes: 'Verified legitimate' }) .expect(201); expect(response.body.message).toBe('Referido aprobado exitosamente'); expect(response.body.referral.fraudHold).toBe(false); }); afterAll(async () => { await referralRepository.delete({ id: testReferral.id }); }); }); describe('Role Hierarchy Tests', () => { it('SUPER_ADMIN should access all endpoints', async () => { // Dashboard (VIEWER+) await request(app.getHttpServer()) .get('/admin/dashboard') .set('Authorization', `Bearer ${superAdminToken}`) .expect(200); // Providers (ADMIN+) await request(app.getHttpServer()) .get('/admin/providers') .set('Authorization', `Bearer ${superAdminToken}`) .expect(200); // Products pending (MODERATOR+) await request(app.getHttpServer()) .get('/admin/products/pending') .set('Authorization', `Bearer ${superAdminToken}`) .expect(200); }); it('ADMIN should access MODERATOR and VIEWER endpoints', async () => { // Dashboard (VIEWER+) await request(app.getHttpServer()) .get('/admin/dashboard') .set('Authorization', `Bearer ${adminToken}`) .expect(200); // Products pending (MODERATOR+) await request(app.getHttpServer()) .get('/admin/products/pending') .set('Authorization', `Bearer ${adminToken}`) .expect(200); }); it('MODERATOR should access VIEWER endpoints but not ADMIN', async () => { // Dashboard (VIEWER+) await request(app.getHttpServer()) .get('/admin/dashboard') .set('Authorization', `Bearer ${moderatorToken}`) .expect(200); // Providers (ADMIN+) - should be rejected await request(app.getHttpServer()) .get('/admin/providers') .set('Authorization', `Bearer ${moderatorToken}`) .expect(403); }); }); });