rwadurian/backend/services/presence-service/test/e2e/analytics.e2e-spec.ts

199 lines
6.2 KiB
TypeScript

import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication, ValidationPipe, ExecutionContext } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from '../../src/app.module';
import { GlobalExceptionFilter } from '../../src/shared/filters/global-exception.filter';
import { PrismaService } from '../../src/infrastructure/persistence/prisma/prisma.service';
import { JwtAuthGuard } from '../../src/shared/guards/jwt-auth.guard';
describe('Analytics API (E2E)', () => {
let app: INestApplication;
let prisma: PrismaService;
const mockJwtToken = 'test-jwt-token';
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
})
.overrideGuard(JwtAuthGuard)
.useValue({
canActivate: (context: ExecutionContext) => {
const req = context.switchToHttp().getRequest();
req.user = { userId: '12345' };
return true;
},
})
.compile();
app = moduleFixture.createNestApplication();
app.useGlobalFilters(new GlobalExceptionFilter());
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
transform: true,
forbidNonWhitelisted: true,
}),
);
app.setGlobalPrefix('api/v1');
await app.init();
prisma = moduleFixture.get<PrismaService>(PrismaService);
});
afterAll(async () => {
await app.close();
});
describe('POST /api/v1/analytics/events', () => {
it('should accept batch events', async () => {
const response = await request(app.getHttpServer())
.post('/api/v1/analytics/events')
.send({
events: [
{
installId: 'test-install-id-12345',
eventName: 'app_session_start',
clientTs: Math.floor(Date.now() / 1000),
properties: {
os: 'iOS',
osVersion: '17.0',
appVersion: '1.0.0',
},
},
],
})
.expect(201);
expect(response.body).toHaveProperty('accepted');
expect(response.body.accepted).toBeGreaterThanOrEqual(0);
});
it('should accept multiple events in batch', async () => {
const response = await request(app.getHttpServer())
.post('/api/v1/analytics/events')
.send({
events: [
{
installId: 'test-install-id-12345',
eventName: 'app_session_start',
clientTs: Math.floor(Date.now() / 1000),
properties: { os: 'iOS' },
},
{
installId: 'test-install-id-12345',
eventName: 'presence_heartbeat',
clientTs: Math.floor(Date.now() / 1000),
properties: { os: 'iOS' },
},
{
installId: 'test-install-id-12345',
eventName: 'app_session_end',
clientTs: Math.floor(Date.now() / 1000),
properties: { os: 'iOS' },
},
],
})
.expect(201);
expect(response.body.accepted).toBeGreaterThanOrEqual(0);
});
it('should validate event name format', async () => {
const response = await request(app.getHttpServer())
.post('/api/v1/analytics/events')
.send({
events: [
{
installId: 'test-install-id-12345',
eventName: '123_invalid', // Invalid: starts with number
clientTs: Math.floor(Date.now() / 1000),
},
],
});
// May return 201 with failed count or 400 depending on implementation
// Check that validation occurs
});
it('should validate installId format', async () => {
const response = await request(app.getHttpServer())
.post('/api/v1/analytics/events')
.send({
events: [
{
installId: 'short', // Invalid: too short
eventName: 'app_session_start',
clientTs: Math.floor(Date.now() / 1000),
},
],
});
// May return 201 with failed count or 400 depending on implementation
});
it('should handle empty events array', async () => {
const response = await request(app.getHttpServer())
.post('/api/v1/analytics/events')
.send({
events: [],
})
.expect(201);
expect(response.body.accepted).toBe(0);
});
});
describe('GET /api/v1/analytics/dau', () => {
it('should return DAU statistics', async () => {
const startDate = '2025-01-01';
const endDate = '2025-01-15';
const response = await request(app.getHttpServer())
.get('/api/v1/analytics/dau')
.query({ startDate, endDate })
.set('Authorization', `Bearer ${mockJwtToken}`)
.expect(200);
expect(response.body).toHaveProperty('data');
expect(Array.isArray(response.body.data)).toBe(true);
expect(response.body).toHaveProperty('total');
});
it('should validate date format', async () => {
const response = await request(app.getHttpServer())
.get('/api/v1/analytics/dau')
.query({
startDate: 'invalid-date',
endDate: '2025-01-15',
})
.set('Authorization', `Bearer ${mockJwtToken}`)
.expect(400);
expect(response.body.statusCode).toBe(400);
});
it('should require startDate parameter', async () => {
const response = await request(app.getHttpServer())
.get('/api/v1/analytics/dau')
.query({ endDate: '2025-01-15' })
.set('Authorization', `Bearer ${mockJwtToken}`)
.expect(400);
expect(response.body.statusCode).toBe(400);
});
it('should require endDate parameter', async () => {
const response = await request(app.getHttpServer())
.get('/api/v1/analytics/dau')
.query({ startDate: '2025-01-01' })
.set('Authorization', `Bearer ${mockJwtToken}`)
.expect(400);
expect(response.body.statusCode).toBe(400);
});
});
});