import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication, ValidationPipe } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import * as request from 'supertest'; import { Repository } from 'typeorm'; import { getRepositoryToken } from '@nestjs/typeorm'; import { AppModule } from '../src/app.module'; import { RawEvent } from '../src/entities/raw-event.entity'; import { Device } from '../src/entities/device.entity'; import { UserProfile } from '../src/entities/user-profile.entity'; import { UserIdentity } from '../src/entities/user-identity.entity'; import { ValidatedEvent } from '../src/entities/validated-event.entity'; import { InventoryDevice } from '../src/entities/inventory-device.entity'; describe('Full Integration Tests (e2e)', () => { let app: INestApplication; let rawEventRepository: Repository; let deviceRepository: Repository; let userProfileRepository: Repository; let userIdentityRepository: Repository; let validatedEventRepository: Repository; let inventoryDeviceRepository: Repository; const testDatabaseUrl = process.env.TEST_DATABASE_URL || 'postgresql://meteor_test:meteor_test_pass@localhost:5433/meteor_test'; beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); app.useGlobalPipes( new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true, }), ); app.enableCors({ origin: true, credentials: true, }); await app.init(); // 获取仓库 rawEventRepository = moduleFixture.get>( getRepositoryToken(RawEvent), ); deviceRepository = moduleFixture.get>( getRepositoryToken(Device), ); userProfileRepository = moduleFixture.get>( getRepositoryToken(UserProfile), ); userIdentityRepository = moduleFixture.get>( getRepositoryToken(UserIdentity), ); validatedEventRepository = moduleFixture.get>( getRepositoryToken(ValidatedEvent), ); inventoryDeviceRepository = moduleFixture.get>( getRepositoryToken(InventoryDevice), ); }); afterAll(async () => { await app.close(); }); beforeEach(async () => { // 清理测试数据 await validatedEventRepository.delete({}); await rawEventRepository.delete({}); await deviceRepository.delete({}); await inventoryDeviceRepository.delete({}); await userIdentityRepository.delete({}); await userProfileRepository.delete({}); }); describe('Gallery Full Workflow Integration', () => { it('should handle complete gallery workflow from registration to event display', async () => { // 1. 用户注册 const registrationData = { email: 'integration-test@meteor.dev', password: 'TestPassword123', displayName: 'Integration Test User', }; const registerResponse = await request(app.getHttpServer()) .post('/api/v1/auth/register-email') .send(registrationData) .expect(201); // 2. 用户登录 const loginResponse = await request(app.getHttpServer()) .post('/api/v1/auth/login-email') .send({ email: registrationData.email, password: registrationData.password, }) .expect(200); const { accessToken, userId } = loginResponse.body; expect(accessToken).toBeDefined(); expect(userId).toBeDefined(); // 3. 创建和注册设备 const hardwareId = 'INTEGRATION_TEST_DEVICE_001'; // 先创建库存设备 const inventoryDevice = inventoryDeviceRepository.create({ hardwareId, isClaimed: false, }); await inventoryDeviceRepository.save(inventoryDevice); // 注册设备到用户 const deviceResponse = await request(app.getHttpServer()) .post('/api/v1/devices/register') .set('Authorization', `Bearer ${accessToken}`) .send({ hardwareId }) .expect(201); const device = deviceResponse.body.device; expect(device.id).toBeDefined(); // 4. 上传事件文件 const eventData = { eventType: 'meteor', eventTimestamp: new Date().toISOString(), metadata: { deviceId: device.id, location: 'Integration Test Location', confidence: 0.95, }, }; const uploadResponse = await request(app.getHttpServer()) .post('/api/v1/events/upload') .set('Authorization', `Bearer ${accessToken}`) .attach('file', Buffer.from('fake image data'), 'integration-test.jpg') .field('eventData', JSON.stringify(eventData)) .expect(202); const { rawEventId } = uploadResponse.body; expect(rawEventId).toBeDefined(); // 5. 模拟处理服务完成 - 创建验证事件 const validatedEvent = validatedEventRepository.create({ rawEventId, deviceId: device.id, userProfileId: userId, mediaUrl: `https://test-bucket.s3.amazonaws.com/events/${device.id}/${rawEventId}.jpg`, eventType: eventData.eventType, capturedAt: new Date(eventData.eventTimestamp), isValid: true, validationScore: '0.95', fileSize: '1024', fileType: 'image/jpeg', originalFilename: 'integration-test.jpg', metadata: eventData.metadata, }); await validatedEventRepository.save(validatedEvent); // 6. 获取验证事件列表 (Gallery API) const eventsResponse = await request(app.getHttpServer()) .get('/api/v1/events') .set('Authorization', `Bearer ${accessToken}`) .expect(200); const { data: events, nextCursor } = eventsResponse.body; expect(events).toHaveLength(1); expect(events[0].id).toBe(validatedEvent.id); expect(events[0].eventType).toBe('meteor'); expect(events[0].mediaUrl).toBe(validatedEvent.mediaUrl); expect(events[0].metadata.location).toBe('Integration Test Location'); expect(nextCursor).toBeNull(); // 7. 测试分页功能 // 创建更多测试事件 const additionalEvents = []; for (let i = 0; i < 25; i++) { const additionalEvent = validatedEventRepository.create({ rawEventId: `additional-${i}`, deviceId: device.id, userProfileId: userId, mediaUrl: `https://test-bucket.s3.amazonaws.com/events/${device.id}/additional-${i}.jpg`, eventType: i % 2 === 0 ? 'meteor' : 'satellite', capturedAt: new Date(Date.now() - i * 60000), isValid: true, validationScore: '0.90', metadata: { location: `Location ${i}` }, }); additionalEvents.push(additionalEvent); } await validatedEventRepository.save(additionalEvents); // 测试分页查询 const paginatedResponse = await request(app.getHttpServer()) .get('/api/v1/events?limit=10') .set('Authorization', `Bearer ${accessToken}`) .expect(200); expect(paginatedResponse.body.data).toHaveLength(10); expect(paginatedResponse.body.nextCursor).toBeTruthy(); // 使用cursor获取下一页 const nextPageResponse = await request(app.getHttpServer()) .get( `/api/v1/events?limit=10&cursor=${paginatedResponse.body.nextCursor}`, ) .set('Authorization', `Bearer ${accessToken}`) .expect(200); expect(nextPageResponse.body.data).toHaveLength(10); // 确保没有重复的事件 const firstPageIds = paginatedResponse.body.data.map((e: any) => e.id); const secondPageIds = nextPageResponse.body.data.map((e: any) => e.id); const intersection = firstPageIds.filter((id: string) => secondPageIds.includes(id), ); expect(intersection).toHaveLength(0); }); it('should handle user isolation correctly', async () => { // 创建两个用户 const user1Data = { email: 'user1@meteor.dev', password: 'Password123', displayName: 'User 1', }; const user2Data = { email: 'user2@meteor.dev', password: 'Password123', displayName: 'User 2', }; // 注册用户1 await request(app.getHttpServer()) .post('/api/v1/auth/register-email') .send(user1Data) .expect(201); const user1Login = await request(app.getHttpServer()) .post('/api/v1/auth/login-email') .send({ email: user1Data.email, password: user1Data.password }) .expect(200); // 注册用户2 await request(app.getHttpServer()) .post('/api/v1/auth/register-email') .send(user2Data) .expect(201); const user2Login = await request(app.getHttpServer()) .post('/api/v1/auth/login-email') .send({ email: user2Data.email, password: user2Data.password }) .expect(200); // 为每个用户创建设备 const device1HardwareId = 'USER1_DEVICE'; const device2HardwareId = 'USER2_DEVICE'; await inventoryDeviceRepository.save([ { hardwareId: device1HardwareId, isClaimed: false }, { hardwareId: device2HardwareId, isClaimed: false }, ]); const device1Response = await request(app.getHttpServer()) .post('/api/v1/devices/register') .set('Authorization', `Bearer ${user1Login.body.accessToken}`) .send({ hardwareId: device1HardwareId }) .expect(201); const device2Response = await request(app.getHttpServer()) .post('/api/v1/devices/register') .set('Authorization', `Bearer ${user2Login.body.accessToken}`) .send({ hardwareId: device2HardwareId }) .expect(201); // 为每个用户创建验证事件 const user1Event = validatedEventRepository.create({ rawEventId: 'user1-event', deviceId: device1Response.body.device.id, userProfileId: user1Login.body.userId, mediaUrl: 'https://test-bucket.s3.amazonaws.com/user1-event.jpg', eventType: 'meteor', capturedAt: new Date(), isValid: true, metadata: { location: 'User 1 Location' }, }); const user2Event = validatedEventRepository.create({ rawEventId: 'user2-event', deviceId: device2Response.body.device.id, userProfileId: user2Login.body.userId, mediaUrl: 'https://test-bucket.s3.amazonaws.com/user2-event.jpg', eventType: 'satellite', capturedAt: new Date(), isValid: true, metadata: { location: 'User 2 Location' }, }); await validatedEventRepository.save([user1Event, user2Event]); // 用户1只能看到自己的事件 const user1Events = await request(app.getHttpServer()) .get('/api/v1/events') .set('Authorization', `Bearer ${user1Login.body.accessToken}`) .expect(200); expect(user1Events.body.data).toHaveLength(1); expect(user1Events.body.data[0].eventType).toBe('meteor'); expect(user1Events.body.data[0].metadata.location).toBe( 'User 1 Location', ); // 用户2只能看到自己的事件 const user2Events = await request(app.getHttpServer()) .get('/api/v1/events') .set('Authorization', `Bearer ${user2Login.body.accessToken}`) .expect(200); expect(user2Events.body.data).toHaveLength(1); expect(user2Events.body.data[0].eventType).toBe('satellite'); expect(user2Events.body.data[0].metadata.location).toBe( 'User 2 Location', ); }); it('should handle API rate limiting and error conditions', async () => { // 注册和登录用户 const userData = { email: 'ratelimit-test@meteor.dev', password: 'Password123', displayName: 'Rate Limit Test', }; await request(app.getHttpServer()) .post('/api/v1/auth/register-email') .send(userData) .expect(201); const loginResponse = await request(app.getHttpServer()) .post('/api/v1/auth/login-email') .send({ email: userData.email, password: userData.password }) .expect(200); const { accessToken } = loginResponse.body; // 测试无效的查询参数 await request(app.getHttpServer()) .get('/api/v1/events?limit=0') .set('Authorization', `Bearer ${accessToken}`) .expect(400); await request(app.getHttpServer()) .get('/api/v1/events?limit=1001') .set('Authorization', `Bearer ${accessToken}`) .expect(400); // 测试无效的cursor await request(app.getHttpServer()) .get('/api/v1/events?cursor=invalid-cursor-format') .set('Authorization', `Bearer ${accessToken}`) .expect(500); // 测试无效的token await request(app.getHttpServer()) .get('/api/v1/events') .set('Authorization', 'Bearer invalid-token') .expect(401); // 测试过期的token(这里简化处理) await request(app.getHttpServer()) .get('/api/v1/events') .set( 'Authorization', 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.expired', ) .expect(401); }); }); describe('Performance and Load Testing', () => { it('should handle concurrent requests efficiently', async () => { // 创建测试用户 const userData = { email: 'perf-test@meteor.dev', password: 'Password123', displayName: 'Performance Test', }; await request(app.getHttpServer()) .post('/api/v1/auth/register-email') .send(userData) .expect(201); const loginResponse = await request(app.getHttpServer()) .post('/api/v1/auth/login-email') .send({ email: userData.email, password: userData.password }) .expect(200); const { accessToken, userId } = loginResponse.body; // 创建设备 const hardwareId = 'PERF_TEST_DEVICE'; await inventoryDeviceRepository.save({ hardwareId, isClaimed: false }); const deviceResponse = await request(app.getHttpServer()) .post('/api/v1/devices/register') .set('Authorization', `Bearer ${accessToken}`) .send({ hardwareId }) .expect(201); const device = deviceResponse.body.device; // 创建大量测试事件 const events = Array.from({ length: 100 }, (_, i) => validatedEventRepository.create({ rawEventId: `perf-event-${i}`, deviceId: device.id, userProfileId: userId, mediaUrl: `https://test-bucket.s3.amazonaws.com/perf-${i}.jpg`, eventType: i % 3 === 0 ? 'meteor' : i % 3 === 1 ? 'satellite' : 'airplane', capturedAt: new Date(Date.now() - i * 1000), isValid: true, metadata: { location: `Performance Location ${i}` }, }), ); await validatedEventRepository.save(events); // 并发请求测试 const concurrentRequests = Array.from({ length: 10 }, () => request(app.getHttpServer()) .get('/api/v1/events?limit=20') .set('Authorization', `Bearer ${accessToken}`) .expect(200), ); const results = await Promise.all(concurrentRequests); // 验证所有请求都成功返回 results.forEach((result) => { expect(result.body.data).toHaveLength(20); expect(result.body.nextCursor).toBeTruthy(); }); // 测试响应时间(简单检查) const startTime = Date.now(); await request(app.getHttpServer()) .get('/api/v1/events?limit=50') .set('Authorization', `Bearer ${accessToken}`) .expect(200); const responseTime = Date.now() - startTime; // 响应时间应该在合理范围内(< 1秒) expect(responseTime).toBeLessThan(1000); }); }); });