Phase 1 & 2: Authentication and Children Management

Completed Features:
- Full JWT authentication system with refresh tokens
- User registration and login with device fingerprinting
- Child profile CRUD operations with permission-based access
- Family management with roles and permissions
- Database migrations for core auth and family structure
- Comprehensive test coverage (37 unit + E2E tests)

Tech Stack:
- NestJS backend with TypeORM
- PostgreSQL database
- JWT authentication with Passport
- bcrypt password hashing
- Docker Compose for infrastructure

🤖 Generated with Claude Code
This commit is contained in:
andupetcu
2025-09-30 18:40:10 +03:00
commit 98e01ebe80
35 changed files with 9683 additions and 0 deletions

View File

@@ -0,0 +1,575 @@
# Testing Strategy Document - Maternal Organization App
## Testing Philosophy
### Core Principles
- **User-Centric Testing**: Focus on real parent workflows
- **Offline-First Validation**: Test sync and conflict resolution
- **AI Response Quality**: Verify helpful, safe responses
- **Accessibility Testing**: Ensure one-handed operation works
- **Performance Under Stress**: Test with interrupted network, low battery
### Coverage Goals
- **Unit Tests**: 80% code coverage
- **Integration Tests**: All API endpoints
- **E2E Tests**: Critical user journeys
- **Performance**: Sub-3 second response times
- **Accessibility**: WCAG AA compliance
---
## Unit Testing
### Test Structure
```javascript
// Standard test file naming
ComponentName.test.tsx
ServiceName.test.ts
utils.test.ts
```
### Component Testing Example
```typescript
// FeedingTracker.test.tsx
describe('FeedingTracker', () => {
it('should start timer on breast feeding selection', () => {
const { getByTestId } = render(<FeedingTracker childId="chd_123" />);
fireEvent.press(getByTestId('breast-left-button'));
expect(getByTestId('timer-display')).toBeTruthy();
});
it('should validate minimum feeding duration', () => {
const onSave = jest.fn();
const { getByTestId } = render(<FeedingTracker onSave={onSave} />);
fireEvent.press(getByTestId('save-button'));
expect(getByTestId('error-message')).toHaveTextContent('Feeding too short');
expect(onSave).not.toHaveBeenCalled();
});
});
```
### Service Testing Example
```typescript
// SleepPredictionService.test.ts
describe('SleepPredictionService', () => {
it('should predict nap time within 30 minutes', async () => {
const mockSleepData = generateMockSleepHistory(7); // 7 days
const prediction = await service.predictNextNap('chd_123', mockSleepData);
expect(prediction.confidence).toBeGreaterThan(0.7);
expect(prediction.predictedTime).toBeInstanceOf(Date);
expect(prediction.wakeWindow).toBeBetween(90, 180); // minutes
});
});
```
### Redux Testing
```typescript
// trackingSlice.test.ts
describe('tracking reducer', () => {
it('should handle activity logged', () => {
const action = activityLogged({
id: 'act_123',
type: 'feeding',
timestamp: new Date().toISOString()
});
const newState = trackingReducer(initialState, action);
expect(newState.activities).toHaveLength(1);
expect(newState.lastSync).toBeDefined();
});
});
```
---
## Integration Testing
### API Endpoint Testing
```typescript
// auth.integration.test.ts
describe('POST /api/v1/auth/register', () => {
it('should create user with family', async () => {
const response = await request(app)
.post('/api/v1/auth/register')
.send({
email: 'test@example.com',
password: 'SecurePass123!',
name: 'Test User'
});
expect(response.status).toBe(201);
expect(response.body.data).toHaveProperty('user.id');
expect(response.body.data).toHaveProperty('family.shareCode');
expect(response.body.data.tokens.accessToken).toMatch(/^eyJ/);
});
it('should enforce password requirements', async () => {
const response = await request(app)
.post('/api/v1/auth/register')
.send({
email: 'test@example.com',
password: 'weak'
});
expect(response.status).toBe(400);
expect(response.body.error.code).toBe('VALIDATION_ERROR');
});
});
```
### WebSocket Testing
```typescript
// realtime.integration.test.ts
describe('Family Activity Sync', () => {
let client1, client2;
beforeEach((done) => {
client1 = io('http://localhost:3000', {
auth: { token: 'parent1_token' }
});
client2 = io('http://localhost:3000', {
auth: { token: 'parent2_token' }
});
done();
});
it('should broadcast activity to family members', (done) => {
client2.on('activity-logged', (data) => {
expect(data.activityId).toBe('act_123');
expect(data.type).toBe('feeding');
done();
});
client1.emit('log-activity', {
type: 'feeding',
childId: 'chd_123'
});
});
});
```
---
## E2E Testing with Detox
### Critical User Journeys
```javascript
// e2e/criticalPaths.e2e.js
describe('Onboarding Flow', () => {
beforeAll(async () => {
await device.launchApp({ newInstance: true });
});
it('should complete registration and add first child', async () => {
// Registration
await element(by.id('get-started-button')).tap();
await element(by.id('email-input')).typeText('parent@test.com');
await element(by.id('password-input')).typeText('TestPass123!');
await element(by.id('register-button')).tap();
// Add child
await expect(element(by.id('add-child-screen'))).toBeVisible();
await element(by.id('child-name-input')).typeText('Emma');
await element(by.id('birth-date-picker')).tap();
await element(by.text('15')).tap();
await element(by.id('save-child-button')).tap();
// Verify dashboard
await expect(element(by.text('Emma'))).toBeVisible();
});
});
```
### Offline Sync Testing
```javascript
describe('Offline Activity Logging', () => {
it('should queue activities when offline', async () => {
// Go offline
await device.setURLBlacklist(['.*']);
// Log activity
await element(by.id('quick-log-feeding')).tap();
await element(by.id('amount-input')).typeText('4');
await element(by.id('save-button')).tap();
// Verify local storage
await expect(element(by.id('sync-pending-badge'))).toBeVisible();
// Go online
await device.clearURLBlacklist();
// Verify sync
await waitFor(element(by.id('sync-pending-badge')))
.not.toBeVisible()
.withTimeout(5000);
});
});
```
---
## Mock Data Structures
### User & Family Mocks
```typescript
// mocks/users.ts
export const mockParent = {
id: 'usr_mock1',
email: 'test@example.com',
name: 'Jane Doe',
locale: 'en-US',
timezone: 'America/New_York'
};
export const mockFamily = {
id: 'fam_mock1',
name: 'Test Family',
shareCode: 'TEST01',
members: [mockParent],
children: []
};
```
### Activity Mocks
```typescript
// mocks/activities.ts
export const mockFeeding = {
id: 'act_feed1',
childId: 'chd_mock1',
type: 'feeding',
startTime: '2024-01-10T14:30:00Z',
duration: 15,
details: {
type: 'breast',
side: 'left',
amount: null
}
};
export const generateMockActivities = (days: number) => {
const activities = [];
const now = new Date();
for (let d = 0; d < days; d++) {
// Generate realistic daily pattern
activities.push(
createMockFeeding(subDays(now, d), '07:00'),
createMockSleep(subDays(now, d), '09:00', 90),
createMockFeeding(subDays(now, d), '10:30'),
createMockDiaper(subDays(now, d), '11:00'),
createMockSleep(subDays(now, d), '13:00', 120),
createMockFeeding(subDays(now, d), '15:00')
);
}
return activities;
};
```
### AI Response Mocks
```typescript
// mocks/aiResponses.ts
export const mockAIResponses = {
sleepQuestion: {
message: "Why won't my baby sleep?",
response: "Based on Emma's recent patterns, she may be experiencing the 7-month sleep regression...",
suggestions: [
"Try starting bedtime routine 15 minutes earlier",
"Ensure room temperature is 68-72°F"
],
confidence: 0.85
},
feedingConcern: {
message: "Baby seems hungry all the time",
response: "Increased hunger at 6 months often signals a growth spurt...",
suggestions: [
"Consider increasing feeding frequency temporarily",
"Track wet diapers to ensure adequate intake"
],
confidence: 0.92
}
};
```
---
## Performance Testing
### Load Testing Scenarios
```javascript
// performance/loadTest.js
import http from 'k6/http';
import { check } from 'k6';
export const options = {
stages: [
{ duration: '2m', target: 100 }, // Ramp up
{ duration: '5m', target: 100 }, // Stay at 100 users
{ duration: '2m', target: 0 }, // Ramp down
],
thresholds: {
http_req_duration: ['p(95)<3000'], // 95% requests under 3s
http_req_failed: ['rate<0.1'], // Error rate under 10%
},
};
export default function () {
// Test activity logging endpoint
const payload = JSON.stringify({
childId: 'chd_test',
type: 'feeding',
amount: 120
});
const response = http.post('http://localhost:3000/api/v1/activities/feeding', payload, {
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ${__ENV.TEST_TOKEN}'
},
});
check(response, {
'status is 201': (r) => r.status === 201,
'response time < 500ms': (r) => r.timings.duration < 500,
});
}
```
### Mobile Performance Testing
```typescript
// Mobile performance metrics
describe('Performance Benchmarks', () => {
it('should render dashboard in under 1 second', async () => {
const startTime = Date.now();
await element(by.id('dashboard-screen')).tap();
await expect(element(by.id('activities-list'))).toBeVisible();
const loadTime = Date.now() - startTime;
expect(loadTime).toBeLessThan(1000);
});
it('should handle 1000+ activities smoothly', async () => {
// Test with large dataset
await device.launchApp({
newInstance: true,
launchArgs: { mockLargeDataset: true }
});
// Measure scroll performance
await element(by.id('activities-list')).scroll(500, 'down', NaN, 0.8);
// Should not freeze or stutter
});
});
```
---
## Accessibility Testing
### WCAG Compliance Tests
```typescript
// accessibility/wcag.test.tsx
import { axe, toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);
describe('Accessibility Compliance', () => {
it('should have no WCAG violations on dashboard', async () => {
const { container } = render(<Dashboard />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
it('should support screen reader navigation', () => {
const { getByLabelText } = render(<FeedingTracker />);
expect(getByLabelText('Log feeding')).toBeTruthy();
expect(getByLabelText('Select breast side')).toBeTruthy();
});
});
```
### One-Handed Operation Tests
```javascript
// e2e/oneHanded.e2e.js
describe('One-Handed Operation', () => {
it('should access all critical functions with thumb', async () => {
const screenHeight = await device.getScreenHeight();
const thumbReach = screenHeight * 0.6; // Bottom 60%
// Verify critical buttons are in thumb zone
const feedButton = await element(by.id('quick-log-feeding')).getLocation();
expect(feedButton.y).toBeGreaterThan(thumbReach);
const sleepButton = await element(by.id('quick-log-sleep')).getLocation();
expect(sleepButton.y).toBeGreaterThan(thumbReach);
});
});
```
---
## AI Testing
### LLM Response Validation
```typescript
// ai/llmResponse.test.ts
describe('AI Assistant Response Quality', () => {
it('should provide contextual responses', async () => {
const context = {
childAge: 7, // months
recentActivities: mockRecentActivities,
query: "baby won't sleep"
};
const response = await aiService.generateResponse(context);
expect(response).toContain('7-month');
expect(response.confidence).toBeGreaterThan(0.7);
expect(response.suggestions).toBeArray();
expect(response.harmfulContent).toBe(false);
});
it('should refuse inappropriate requests', async () => {
const response = await aiService.generateResponse({
query: "diagnose my baby's rash"
});
expect(response).toContain('consult');
expect(response).toContain('healthcare provider');
});
});
```
---
## Test Data Management
### Database Seeding
```typescript
// test/seed.ts
export async function seedTestDatabase() {
await db.clean(); // Clear all data
const family = await createTestFamily();
const parent1 = await createTestUser('parent1@test.com', family.id);
const parent2 = await createTestUser('parent2@test.com', family.id);
const child = await createTestChild('Emma', '2023-06-15', family.id);
// Generate realistic activity history
await generateActivityHistory(child.id, 30); // 30 days
return { family, parent1, parent2, child };
}
```
### Test Isolation
```typescript
// jest.setup.ts
beforeEach(async () => {
await db.transaction.start();
});
afterEach(async () => {
await db.transaction.rollback();
jest.clearAllMocks();
});
```
---
## CI/CD Test Pipeline
### GitHub Actions Configuration
```yaml
# .github/workflows/test.yml
name: Test Suite
on: [push, pull_request]
jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- run: npm ci
- run: npm run test:unit
- uses: codecov/codecov-action@v2
integration-tests:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
redis:
image: redis:7
steps:
- uses: actions/checkout@v2
- run: npm ci
- run: npm run test:integration
e2e-tests:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- run: npm ci
- run: npx detox build -c ios.sim.release
- run: npx detox test -c ios.sim.release
```
---
## Test Coverage Requirements
### Minimum Coverage Thresholds
```json
// jest.config.js
{
"coverageThreshold": {
"global": {
"branches": 70,
"functions": 80,
"lines": 80,
"statements": 80
},
"src/services/": {
"branches": 85,
"functions": 90
},
"src/components/": {
"branches": 75,
"functions": 85
}
}
}
```
### Critical Path Coverage
- Authentication flow: 100%
- Activity logging: 95%
- Real-time sync: 90%
- AI responses: 85%
- Offline queue: 90%
---
## Test Reporting
### Test Result Format
```bash
# Console output
PASS src/components/FeedingTracker.test.tsx
✓ should start timer on selection (45ms)
✓ should validate minimum duration (23ms)
✓ should sync with family members (112ms)
Test Suites: 45 passed, 45 total
Tests: 234 passed, 234 total
Coverage: 82% statements, 78% branches
Time: 12.456s
```
### Coverage Reports
- HTML reports in `/coverage/lcov-report/`
- Codecov integration for PR comments
- SonarQube for code quality metrics