Created new eslint.config.mjs with flat config:
- Migrated from .eslintrc.js to eslint.config.mjs
- Added globals package for Node.js and Jest globals
- Configured TypeScript parser and plugins
- Maintained all existing rules and Prettier integration
ESLint now running successfully with v9 flat config.
Note: 39 unused variable warnings found - these are minor code
quality issues that can be addressed in a separate cleanup PR.
🤖 Generated with Claude Code
318 lines
9.7 KiB
TypeScript
318 lines
9.7 KiB
TypeScript
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);
|
|
});
|
|
});
|
|
});
|