miinventario-v2/apps/backend/test/admin.e2e-spec.ts
rckrdmrd 1a53b5c4d3 [MIINVENTARIO] feat: Initial commit - Sistema de inventario con análisis de video IA
- Backend NestJS con módulos de autenticación, inventario, créditos
- Frontend React con dashboard y componentes UI
- Base de datos PostgreSQL con migraciones
- Tests E2E configurados
- Configuración de Docker y deployment

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 02:25:48 -06:00

433 lines
14 KiB
TypeScript

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<User>;
let storeRepository: Repository<Store>;
let storeUserRepository: Repository<StoreUser>;
let providerRepository: Repository<IaProvider>;
let promotionRepository: Repository<Promotion>;
let packageRepository: Repository<CreditPackage>;
let productSubmissionRepository: Repository<ProductSubmission>;
let referralRepository: Repository<Referral>;
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>(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);
});
});
});