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

176 lines
6.0 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('Presence API (E2E)', () => {
let app: INestApplication;
let prisma: PrismaService;
// Mock JWT token for testing (in real scenario, generate from auth service)
const mockJwtToken = 'test-jwt-token';
const mockUserId = BigInt(12345);
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
})
// Override JWT guard for testing
.overrideGuard(JwtAuthGuard)
.useValue({
canActivate: (context: ExecutionContext) => {
const req = context.switchToHttp().getRequest();
req.user = { userId: mockUserId.toString() };
return true;
},
})
.compile();
app = moduleFixture.createNestApplication();
// Apply same configuration as main.ts
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/presence/heartbeat', () => {
it('should record heartbeat successfully', async () => {
const response = await request(app.getHttpServer())
.post('/api/v1/presence/heartbeat')
.set('Authorization', `Bearer ${mockJwtToken}`)
.send({
installId: 'test-install-id-12345',
appVersion: '1.0.0',
clientTs: Date.now(),
})
.expect(201);
expect(response.body).toHaveProperty('ok', true);
expect(response.body).toHaveProperty('serverTs');
expect(typeof response.body.serverTs).toBe('number');
});
it('should reject heartbeat without authentication', async () => {
// This test depends on whether we mock the guard or not
// With mocked guard always returning true, this will pass
// In real scenario, this should return 401
});
it('should validate installId format', async () => {
// Test with non-string installId to trigger validation error
const response = await request(app.getHttpServer())
.post('/api/v1/presence/heartbeat')
.set('Authorization', `Bearer ${mockJwtToken}`)
.send({
installId: 12345, // Invalid: not a string
appVersion: '1.0.0',
clientTs: Date.now(),
})
.expect(400);
expect(response.body.statusCode).toBe(400);
});
});
describe('GET /api/v1/presence/online-count', () => {
it('should return online count', async () => {
const response = await request(app.getHttpServer())
.get('/api/v1/presence/online-count')
.set('Authorization', `Bearer ${mockJwtToken}`)
.expect(200);
expect(response.body).toHaveProperty('count');
expect(typeof response.body.count).toBe('number');
expect(response.body).toHaveProperty('windowSeconds');
expect(response.body.windowSeconds).toBe(180);
expect(response.body).toHaveProperty('queriedAt');
});
});
describe('GET /api/v1/presence/online-history', () => {
it('should return online history', async () => {
const startTime = new Date(Date.now() - 3600000).toISOString(); // 1 hour ago
const endTime = new Date().toISOString();
const response = await request(app.getHttpServer())
.get('/api/v1/presence/online-history')
.query({
startTime,
endTime,
interval: '5m',
})
.set('Authorization', `Bearer ${mockJwtToken}`)
.expect(200);
expect(response.body).toHaveProperty('data');
expect(Array.isArray(response.body.data)).toBe(true);
expect(response.body).toHaveProperty('interval', '5m');
expect(response.body).toHaveProperty('startTime');
expect(response.body).toHaveProperty('endTime');
expect(response.body).toHaveProperty('total');
expect(response.body).toHaveProperty('summary');
});
it('should validate startTime format', async () => {
const response = await request(app.getHttpServer())
.get('/api/v1/presence/online-history')
.query({
startTime: 'invalid-date',
endTime: new Date().toISOString(),
})
.set('Authorization', `Bearer ${mockJwtToken}`)
.expect(400);
expect(response.body.statusCode).toBe(400);
});
it('should validate interval enum', async () => {
const response = await request(app.getHttpServer())
.get('/api/v1/presence/online-history')
.query({
startTime: new Date(Date.now() - 3600000).toISOString(),
endTime: new Date().toISOString(),
interval: '10m', // Invalid interval
})
.set('Authorization', `Bearer ${mockJwtToken}`)
.expect(400);
expect(response.body.statusCode).toBe(400);
});
it('should use default interval when not provided', async () => {
const startTime = new Date(Date.now() - 3600000).toISOString();
const endTime = new Date().toISOString();
const response = await request(app.getHttpServer())
.get('/api/v1/presence/online-history')
.query({
startTime,
endTime,
})
.set('Authorization', `Bearer ${mockJwtToken}`)
.expect(200);
expect(response.body.interval).toBe('5m');
});
});
});