import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication, ValidationPipe } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from '../src/app.module'; import { DataSource } from 'typeorm'; describe('Children (e2e)', () => { let app: INestApplication; let dataSource: DataSource; // Test user and auth const testUser = { email: `test-children-${Date.now()}@example.com`, password: 'SecurePass123!', name: 'Test Parent', deviceInfo: { deviceId: 'test-device-children', platform: 'ios', model: 'iPhone 14', osVersion: '17.0', }, }; let accessToken: string; let userId: string; let familyId: string; let childId: string; beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); app.useGlobalPipes( new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true, }), ); await app.init(); dataSource = app.get(DataSource); // Register user and get auth token const registerRes = await request(app.getHttpServer()) .post('/api/v1/auth/register') .send(testUser); accessToken = registerRes.body.data.tokens.accessToken; userId = registerRes.body.data.user.id; familyId = registerRes.body.data.family.id; }); afterAll(async () => { // Cleanup if (childId) { await dataSource.query('DELETE FROM children WHERE id = $1', [childId]); } if (userId) { await dataSource.query('DELETE FROM refresh_tokens WHERE user_id = $1', [ userId, ]); await dataSource.query('DELETE FROM device_registry WHERE user_id = $1', [ userId, ]); await dataSource.query('DELETE FROM family_members WHERE user_id = $1', [ userId, ]); } if (familyId) { await dataSource.query('DELETE FROM families WHERE id = $1', [familyId]); } if (userId) { await dataSource.query('DELETE FROM users WHERE id = $1', [userId]); } await app.close(); }); describe('POST /api/v1/children', () => { it('should create a child', () => { return request(app.getHttpServer()) .post(`/api/v1/children?familyId=${familyId}`) .set('Authorization', `Bearer ${accessToken}`) .send({ name: 'Emma', birthDate: '2023-06-15', gender: 'female', }) .expect(201) .expect((res) => { expect(res.body.success).toBe(true); expect(res.body.data).toHaveProperty('child'); expect(res.body.data.child.name).toBe('Emma'); expect(res.body.data.child.gender).toBe('female'); expect(res.body.data.child.id).toMatch(/^chd_/); expect(res.body.data.child.familyId).toBe(familyId); // Store for subsequent tests childId = res.body.data.child.id; }); }); it('should require authentication', () => { return request(app.getHttpServer()) .post(`/api/v1/children?familyId=${familyId}`) .send({ name: 'Test Child', birthDate: '2023-01-01', }) .expect(401); }); it('should require familyId query parameter', () => { return request(app.getHttpServer()) .post('/api/v1/children') .set('Authorization', `Bearer ${accessToken}`) .send({ name: 'Test Child', birthDate: '2023-01-01', }) .expect(201) .expect((res) => { expect(res.body.success).toBe(false); expect(res.body.error.code).toBe('VALIDATION_ERROR'); }); }); it('should validate required fields', () => { return request(app.getHttpServer()) .post(`/api/v1/children?familyId=${familyId}`) .set('Authorization', `Bearer ${accessToken}`) .send({ name: 'Test Child', // Missing birthDate }) .expect(400); }); it('should validate birthDate format', () => { return request(app.getHttpServer()) .post(`/api/v1/children?familyId=${familyId}`) .set('Authorization', `Bearer ${accessToken}`) .send({ name: 'Test Child', birthDate: 'invalid-date', }) .expect(400); }); }); describe('GET /api/v1/children', () => { it('should get all children for a family', () => { return request(app.getHttpServer()) .get(`/api/v1/children?familyId=${familyId}`) .set('Authorization', `Bearer ${accessToken}`) .expect(200) .expect((res) => { expect(res.body.success).toBe(true); expect(res.body.data).toHaveProperty('children'); expect(Array.isArray(res.body.data.children)).toBe(true); expect(res.body.data.children.length).toBeGreaterThan(0); expect(res.body.data.children[0].name).toBe('Emma'); }); }); it('should get all children across all families when no familyId provided', () => { return request(app.getHttpServer()) .get('/api/v1/children') .set('Authorization', `Bearer ${accessToken}`) .expect(200) .expect((res) => { expect(res.body.success).toBe(true); expect(res.body.data).toHaveProperty('children'); expect(Array.isArray(res.body.data.children)).toBe(true); }); }); it('should require authentication', () => { return request(app.getHttpServer()).get('/api/v1/children').expect(401); }); }); describe('GET /api/v1/children/:id', () => { it('should get a specific child', () => { return request(app.getHttpServer()) .get(`/api/v1/children/${childId}`) .set('Authorization', `Bearer ${accessToken}`) .expect(200) .expect((res) => { expect(res.body.success).toBe(true); expect(res.body.data).toHaveProperty('child'); expect(res.body.data.child.id).toBe(childId); expect(res.body.data.child.name).toBe('Emma'); }); }); it('should return 404 for non-existent child', () => { return request(app.getHttpServer()) .get('/api/v1/children/chd_nonexistent') .set('Authorization', `Bearer ${accessToken}`) .expect(404); }); it('should require authentication', () => { return request(app.getHttpServer()) .get(`/api/v1/children/${childId}`) .expect(401); }); }); describe('GET /api/v1/children/:id/age', () => { it('should get child age', () => { return request(app.getHttpServer()) .get(`/api/v1/children/${childId}/age`) .set('Authorization', `Bearer ${accessToken}`) .expect(200) .expect((res) => { expect(res.body.success).toBe(true); expect(res.body.data).toHaveProperty('ageInMonths'); expect(res.body.data).toHaveProperty('ageInYears'); expect(res.body.data).toHaveProperty('remainingMonths'); expect(typeof res.body.data.ageInMonths).toBe('number'); }); }); it('should return 404 for non-existent child', () => { return request(app.getHttpServer()) .get('/api/v1/children/chd_nonexistent/age') .set('Authorization', `Bearer ${accessToken}`) .expect(404); }); }); describe('PATCH /api/v1/children/:id', () => { it('should update a child', () => { return request(app.getHttpServer()) .patch(`/api/v1/children/${childId}`) .set('Authorization', `Bearer ${accessToken}`) .send({ name: 'Emma Updated', medicalInfo: { allergies: ['peanuts'], }, }) .expect(200) .expect((res) => { expect(res.body.success).toBe(true); expect(res.body.data.child.name).toBe('Emma Updated'); expect(res.body.data.child.medicalInfo.allergies).toEqual([ 'peanuts', ]); }); }); it('should return 404 for non-existent child', () => { return request(app.getHttpServer()) .patch('/api/v1/children/chd_nonexistent') .set('Authorization', `Bearer ${accessToken}`) .send({ name: 'Test', }) .expect(404); }); it('should require authentication', () => { return request(app.getHttpServer()) .patch(`/api/v1/children/${childId}`) .send({ name: 'Test', }) .expect(401); }); }); describe('DELETE /api/v1/children/:id', () => { it('should soft delete a child', async () => { await request(app.getHttpServer()) .delete(`/api/v1/children/${childId}`) .set('Authorization', `Bearer ${accessToken}`) .expect(200) .expect((res) => { expect(res.body.success).toBe(true); expect(res.body.message).toBe('Child deleted successfully'); }); // Verify child is soft deleted (not visible in list) await request(app.getHttpServer()) .get(`/api/v1/children?familyId=${familyId}`) .set('Authorization', `Bearer ${accessToken}`) .expect(200) .expect((res) => { const deletedChild = res.body.data.children.find( (c: any) => c.id === childId, ); expect(deletedChild).toBeUndefined(); }); }); it('should return 404 for non-existent child', () => { return request(app.getHttpServer()) .delete('/api/v1/children/chd_nonexistent') .set('Authorization', `Bearer ${accessToken}`) .expect(404); }); it('should require authentication', () => { return request(app.getHttpServer()) .delete(`/api/v1/children/${childId}`) .expect(401); }); }); });