- 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>
203 lines
7.4 KiB
TypeScript
203 lines
7.4 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 } 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 { InventoryItem } from '../src/modules/inventory/entities/inventory-item.entity';
|
|
import { Correction } from '../src/modules/feedback/entities/correction.entity';
|
|
import { JwtService } from '@nestjs/jwt';
|
|
import { Repository } from 'typeorm';
|
|
|
|
describe('FeedbackController (e2e)', () => {
|
|
let app: INestApplication;
|
|
let jwtService: JwtService;
|
|
let userRepository: Repository<User>;
|
|
let storeRepository: Repository<Store>;
|
|
let storeUserRepository: Repository<StoreUser>;
|
|
let inventoryRepository: Repository<InventoryItem>;
|
|
let correctionRepository: Repository<Correction>;
|
|
|
|
let testUser: User;
|
|
let testStore: Store;
|
|
let testItem: InventoryItem;
|
|
let authToken: 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));
|
|
inventoryRepository = moduleFixture.get(getRepositoryToken(InventoryItem));
|
|
correctionRepository = moduleFixture.get(getRepositoryToken(Correction));
|
|
|
|
// Create test user
|
|
testUser = await userRepository.save({
|
|
phone: '+521234567890',
|
|
name: 'Test Feedback User',
|
|
});
|
|
|
|
// Create test store
|
|
testStore = await storeRepository.save({
|
|
name: 'Test Feedback Store',
|
|
ownerId: testUser.id,
|
|
});
|
|
|
|
// Create store-user relationship
|
|
await storeUserRepository.save({
|
|
storeId: testStore.id,
|
|
userId: testUser.id,
|
|
role: StoreUserRole.OWNER,
|
|
});
|
|
|
|
// Create test inventory item
|
|
testItem = await inventoryRepository.save({
|
|
storeId: testStore.id,
|
|
name: 'Test Product',
|
|
quantity: 10,
|
|
category: 'Test Category',
|
|
});
|
|
|
|
// Generate auth token
|
|
authToken = jwtService.sign({ sub: testUser.id, phone: testUser.phone });
|
|
});
|
|
|
|
afterAll(async () => {
|
|
// Cleanup
|
|
await correctionRepository.delete({ inventoryItemId: testItem.id });
|
|
await inventoryRepository.delete({ id: testItem.id });
|
|
await storeUserRepository.delete({ storeId: testStore.id });
|
|
await storeRepository.delete({ id: testStore.id });
|
|
await userRepository.delete({ id: testUser.id });
|
|
await app.close();
|
|
});
|
|
|
|
describe('/stores/:storeId/inventory/:itemId/correct-quantity (PATCH)', () => {
|
|
it('should correct item quantity', async () => {
|
|
const response = await request(app.getHttpServer())
|
|
.patch(`/stores/${testStore.id}/inventory/${testItem.id}/correct-quantity`)
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send({ quantity: 15, reason: 'Conteo incorrecto' })
|
|
.expect(200);
|
|
|
|
expect(response.body.message).toBe('Cantidad corregida exitosamente');
|
|
expect(response.body.correction.type).toBe('QUANTITY');
|
|
expect(response.body.correction.previousValue.quantity).toBe(10);
|
|
expect(response.body.correction.newValue.quantity).toBe(15);
|
|
expect(response.body.item.quantity).toBe(15);
|
|
});
|
|
|
|
it('should reject invalid quantity', async () => {
|
|
await request(app.getHttpServer())
|
|
.patch(`/stores/${testStore.id}/inventory/${testItem.id}/correct-quantity`)
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send({ quantity: -5 })
|
|
.expect(400);
|
|
});
|
|
|
|
it('should reject without auth', async () => {
|
|
await request(app.getHttpServer())
|
|
.patch(`/stores/${testStore.id}/inventory/${testItem.id}/correct-quantity`)
|
|
.send({ quantity: 20 })
|
|
.expect(401);
|
|
});
|
|
});
|
|
|
|
describe('/stores/:storeId/inventory/:itemId/correct-sku (PATCH)', () => {
|
|
it('should correct item SKU/name', async () => {
|
|
const response = await request(app.getHttpServer())
|
|
.patch(`/stores/${testStore.id}/inventory/${testItem.id}/correct-sku`)
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send({
|
|
name: 'Corrected Product Name',
|
|
category: 'New Category',
|
|
reason: 'Nombre incorrecto',
|
|
})
|
|
.expect(200);
|
|
|
|
expect(response.body.message).toBe('Nombre/SKU corregido exitosamente');
|
|
expect(response.body.correction.type).toBe('SKU');
|
|
expect(response.body.item.name).toBe('Corrected Product Name');
|
|
});
|
|
|
|
it('should reject empty name', async () => {
|
|
await request(app.getHttpServer())
|
|
.patch(`/stores/${testStore.id}/inventory/${testItem.id}/correct-sku`)
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send({ name: '' })
|
|
.expect(400);
|
|
});
|
|
});
|
|
|
|
describe('/stores/:storeId/inventory/:itemId/confirm (POST)', () => {
|
|
it('should confirm item as correct', async () => {
|
|
const response = await request(app.getHttpServer())
|
|
.post(`/stores/${testStore.id}/inventory/${testItem.id}/confirm`)
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.expect(201);
|
|
|
|
expect(response.body.message).toBe('Item confirmado exitosamente');
|
|
expect(response.body.correction.type).toBe('CONFIRMATION');
|
|
});
|
|
});
|
|
|
|
describe('/stores/:storeId/inventory/:itemId/history (GET)', () => {
|
|
it('should return correction history', async () => {
|
|
const response = await request(app.getHttpServer())
|
|
.get(`/stores/${testStore.id}/inventory/${testItem.id}/history`)
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.expect(200);
|
|
|
|
expect(response.body.corrections).toBeDefined();
|
|
expect(Array.isArray(response.body.corrections)).toBe(true);
|
|
expect(response.body.corrections.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should return 404 for non-existent item', async () => {
|
|
await request(app.getHttpServer())
|
|
.get(`/stores/${testStore.id}/inventory/00000000-0000-0000-0000-000000000000/history`)
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.expect(404);
|
|
});
|
|
});
|
|
|
|
describe('/products/submit (POST)', () => {
|
|
it('should submit new product', async () => {
|
|
const response = await request(app.getHttpServer())
|
|
.post('/products/submit')
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send({
|
|
storeId: testStore.id,
|
|
name: 'New Product Submission',
|
|
category: 'Beverages',
|
|
})
|
|
.expect(201);
|
|
|
|
expect(response.body.message).toBe('Producto enviado para revision');
|
|
expect(response.body.submission.status).toBe('PENDING');
|
|
});
|
|
});
|
|
|
|
describe('/products/search (GET)', () => {
|
|
it('should search products', async () => {
|
|
const response = await request(app.getHttpServer())
|
|
.get('/products/search?q=test')
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.expect(200);
|
|
|
|
expect(response.body.products).toBeDefined();
|
|
expect(Array.isArray(response.body.products)).toBe(true);
|
|
});
|
|
});
|
|
});
|