import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication, ValidationPipe } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import * as request from 'supertest'; import { PrismaService } from '../../src/infrastructure/persistence/prisma/prisma.service'; import { AppModule } from '../../src/app.module'; import { Platform } from '../../src/domain/enums/platform.enum'; import { Platform as PrismaPlatform } from '@prisma/client'; describe('VersionController (e2e)', () => { let app: INestApplication; let prisma: PrismaService; beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [ ConfigModule.forRoot({ isGlobal: true, envFilePath: '.env.test', }), AppModule, ], }).compile(); app = moduleFixture.createNestApplication(); // Apply same middleware as main.ts app.useGlobalPipes( new ValidationPipe({ whitelist: true, transform: true, forbidNonWhitelisted: true, }), ); const apiPrefix = process.env.API_PREFIX || 'api/v1'; app.setGlobalPrefix(apiPrefix); await app.init(); prisma = moduleFixture.get(PrismaService); await prisma.$connect(); }); afterAll(async () => { await prisma.$disconnect(); await app.close(); }); beforeEach(async () => { await prisma.appVersion.deleteMany({}); }); const apiPrefix = process.env.API_PREFIX || 'api/v1'; describe('/version (POST)', () => { it('should create new Android version', () => { return request(app.getHttpServer()) .post(`/${apiPrefix}/version`) .send({ platform: 'android', versionCode: 100, versionName: '1.0.0', buildNumber: '100', downloadUrl: 'https://example.com/app.apk', fileSize: '10485760', fileSha256: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', changelog: 'Initial release with basic features', minOsVersion: '5.0', isEnabled: true, isForceUpdate: false, createdBy: 'admin', }) .expect(201) .expect((res) => { expect(res.body.platform).toBe('android'); expect(res.body.versionCode).toBe(100); expect(res.body.versionName).toBe('1.0.0'); }); }); it('should create new iOS version', () => { return request(app.getHttpServer()) .post(`/${apiPrefix}/version`) .send({ platform: 'ios', versionCode: 100, versionName: '1.0.0', buildNumber: '100', downloadUrl: 'https://example.com/app.ipa', fileSize: '15728640', fileSha256: '0000000000000000000000000000000000000000000000000000000000000000', changelog: 'iOS initial release', minOsVersion: '14.0', isEnabled: true, isForceUpdate: false, createdBy: 'admin', }) .expect(201) .expect((res) => { expect(res.body.platform).toBe('ios'); expect(res.body.versionCode).toBe(100); }); }); it('should reject invalid version code', () => { return request(app.getHttpServer()) .post(`/${apiPrefix}/version`) .send({ platform: 'android', versionCode: -1, versionName: '1.0.0', buildNumber: '100', downloadUrl: 'https://example.com/app.apk', fileSize: '10485760', fileSha256: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', changelog: 'Invalid version', minOsVersion: '5.0', isEnabled: true, isForceUpdate: false, createdBy: 'admin', }) .expect(400); }); it('should reject invalid version name format', () => { return request(app.getHttpServer()) .post(`/${apiPrefix}/version`) .send({ platform: 'android', versionCode: 100, versionName: '1.0', buildNumber: '100', downloadUrl: 'https://example.com/app.apk', fileSize: '10485760', fileSha256: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', changelog: 'Invalid version name', minOsVersion: '5.0', isEnabled: true, isForceUpdate: false, createdBy: 'admin', }) .expect(400); }); it('should reject invalid download URL', () => { return request(app.getHttpServer()) .post(`/${apiPrefix}/version`) .send({ platform: 'android', versionCode: 100, versionName: '1.0.0', buildNumber: '100', downloadUrl: 'not-a-url', fileSize: '10485760', fileSha256: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', changelog: 'Invalid URL', minOsVersion: '5.0', isEnabled: true, isForceUpdate: false, createdBy: 'admin', }) .expect(400); }); it('should reject invalid SHA256', () => { return request(app.getHttpServer()) .post(`/${apiPrefix}/version`) .send({ platform: 'android', versionCode: 100, versionName: '1.0.0', buildNumber: '100', downloadUrl: 'https://example.com/app.apk', fileSize: '10485760', fileSha256: 'invalid-sha256', changelog: 'Invalid SHA256', minOsVersion: '5.0', isEnabled: true, isForceUpdate: false, createdBy: 'admin', }) .expect(400); }); }); describe('/version/check-update (GET)', () => { beforeEach(async () => { // Create test versions await prisma.appVersion.create({ data: { id: 'test-v1', platform: PrismaPlatform.ANDROID, versionCode: 100, versionName: '1.0.0', buildNumber: '100', downloadUrl: 'https://example.com/app-v1.apk', fileSize: 10485760n, fileSha256: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', changelog: 'Version 1.0.0', minOsVersion: '5.0', isEnabled: true, isForceUpdate: false, releaseDate: null, createdBy: 'admin', createdAt: new Date(), updatedAt: new Date(), }, }); await prisma.appVersion.create({ data: { id: 'test-v2', platform: PrismaPlatform.ANDROID, versionCode: 200, versionName: '2.0.0', buildNumber: '200', downloadUrl: 'https://example.com/app-v2.apk', fileSize: 20971520n, fileSha256: '0000000000000000000000000000000000000000000000000000000000000000', changelog: 'Version 2.0.0', minOsVersion: '6.0', isEnabled: true, isForceUpdate: false, releaseDate: null, createdBy: 'admin', createdAt: new Date(), updatedAt: new Date(), }, }); }); it('should return update available for older version', () => { return request(app.getHttpServer()) .get(`/${apiPrefix}/version/check-update`) .query({ platform: 'android', currentVersionCode: 100, }) .expect(200) .expect((res) => { expect(res.body.hasUpdate).toBe(true); expect(res.body.latestVersion).toBeDefined(); expect(res.body.latestVersion.versionCode).toBe(200); expect(res.body.isForceUpdate).toBe(false); }); }); it('should return no update for latest version', () => { return request(app.getHttpServer()) .get(`/${apiPrefix}/version/check-update`) .query({ platform: 'android', currentVersionCode: 200, }) .expect(200) .expect((res) => { expect(res.body.hasUpdate).toBe(false); expect(res.body.latestVersion).toBeNull(); }); }); it('should return force update flag when enabled', async () => { // Update version to force update await prisma.appVersion.update({ where: { id: 'test-v2' }, data: { isForceUpdate: true }, }); return request(app.getHttpServer()) .get(`/${apiPrefix}/version/check-update`) .query({ platform: 'android', currentVersionCode: 100, }) .expect(200) .expect((res) => { expect(res.body.hasUpdate).toBe(true); expect(res.body.isForceUpdate).toBe(true); }); }); it('should reject invalid platform', () => { return request(app.getHttpServer()) .get(`/${apiPrefix}/version/check-update`) .query({ platform: 'invalid', currentVersionCode: 100, }) .expect(400); }); it('should reject invalid version code', () => { return request(app.getHttpServer()) .get(`/${apiPrefix}/version/check-update`) .query({ platform: 'android', currentVersionCode: -1, }) .expect(400); }); }); describe('/version/:platform/latest (GET)', () => { beforeEach(async () => { await prisma.appVersion.create({ data: { id: 'test-latest', platform: PrismaPlatform.ANDROID, versionCode: 300, versionName: '3.0.0', buildNumber: '300', downloadUrl: 'https://example.com/app-v3.apk', fileSize: 31457280n, fileSha256: '1111111111111111111111111111111111111111111111111111111111111111', changelog: 'Latest version', minOsVersion: '7.0', isEnabled: true, isForceUpdate: false, releaseDate: null, createdBy: 'admin', createdAt: new Date(), updatedAt: new Date(), }, }); }); it('should get latest version for platform', () => { return request(app.getHttpServer()) .get(`/${apiPrefix}/version/android/latest`) .expect(200) .expect((res) => { expect(res.body.platform).toBe('android'); expect(res.body.versionCode).toBe(300); expect(res.body.versionName).toBe('3.0.0'); }); }); it('should return 404 for platform with no versions', () => { return request(app.getHttpServer()) .get(`/${apiPrefix}/version/ios/latest`) .expect(404); }); }); });