godcrm/backend/tests/fitness-api.test.js
GOD CRM Release f89e074dd1
Some checks failed
CI / Lint / Typecheck / Test / Build (push) Has been cancelled
CI / PostgreSQL Integration Tests (push) Has been cancelled
GOD CRM — public scrubbed snapshot
Governed substrate for autonomous agents: scoped identity (passports),
audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
2026-08-10 04:01:45 +03:00

447 lines
14 KiB
JavaScript

/**
* ADR-025: Fitness Module API Tests
* TDD - Test First Approach
*/
import { describe, test, expect, beforeEach, afterEach } from 'vitest';
import request from 'supertest';
import express from 'express';
import { setupTestDatabase, cleanupTestDatabase } from './helpers/test-db.js';
// Set test mode
process.env.TEST_MODE = 'true';
process.env.JWT_SECRET = 'test-secret-key-for-fitness';
process.env.MASTER_ENCRYPTION_KEY = 'test-master-key-32-characters!!';
process.env.SKIP_DEV_USER = 'true';
let app;
let authToken;
let spaceId;
/**
* Helper to setup test user and get auth token
*/
async function setupAuthenticatedUser() {
const authRoutes = (await import('../routes/v3/auth.js')).default;
app.use('/api/v3/auth', authRoutes);
// Register user with unique email to avoid conflicts
const registerRes = await request(app)
.post('/api/v3/auth/register')
.send({ email: `fitness-${Date.now()}@test.com`, password: 'TestPass123!', name: 'Fitness User' });
authToken = registerRes.body.data?.accessToken;
// Get user's personal space
const spacesRoutes = (await import('../routes/v3/spaces.js')).default;
app.use('/api/v3/spaces', spacesRoutes);
const spacesRes = await request(app)
.get('/api/v3/spaces')
.set('Authorization', `Bearer ${authToken}`);
// Find personal space
const personalSpace = spacesRes.body.data?.find(s => s.type === 'personal');
spaceId = personalSpace?.id || 1;
return { authToken, spaceId };
}
beforeEach(async () => {
await setupTestDatabase();
app = express();
app.use(express.json());
// Setup fitness routes
const fitnessRoutes = (await import('../routes/v3/fitness.js')).default;
app.use('/api/v3/fitness', fitnessRoutes);
await setupAuthenticatedUser();
});
afterEach(async () => {
await cleanupTestDatabase();
});
describe('Fitness API - Workouts CRUD', () => {
describe('POST /api/v3/fitness/workouts', () => {
test('should create a new workout', async () => {
const response = await request(app)
.post('/api/v3/fitness/workouts')
.set('Authorization', `Bearer ${authToken}`)
.send({
space_id: spaceId,
title: 'Morning Push Day',
started_at: '2026-01-17T08:00:00Z',
notes: 'Feeling strong today'
});
expect(response.status).toBe(201);
expect(response.body.success).toBe(true);
expect(response.body.data.id).toBeDefined();
expect(response.body.data.title).toBe('Morning Push Day');
expect(response.body.data.space_id).toBe(spaceId);
});
test('should require space_id', async () => {
const response = await request(app)
.post('/api/v3/fitness/workouts')
.set('Authorization', `Bearer ${authToken}`)
.send({
title: 'No Space Workout',
started_at: '2026-01-17T08:00:00Z'
});
expect(response.status).toBe(400);
expect(response.body.success).toBe(false);
expect(response.body.error.code).toBe('VALIDATION_ERROR');
});
test('should require started_at', async () => {
const response = await request(app)
.post('/api/v3/fitness/workouts')
.set('Authorization', `Bearer ${authToken}`)
.send({
space_id: spaceId,
title: 'No Date Workout'
});
expect(response.status).toBe(400);
expect(response.body.success).toBe(false);
});
});
describe('GET /api/v3/fitness/workouts', () => {
test('should list workouts for space', async () => {
// Create workout first
await request(app)
.post('/api/v3/fitness/workouts')
.set('Authorization', `Bearer ${authToken}`)
.send({
space_id: spaceId,
title: 'Test Workout',
started_at: '2026-01-17T08:00:00Z'
});
const response = await request(app)
.get('/api/v3/fitness/workouts')
.query({ space_id: spaceId })
.set('Authorization', `Bearer ${authToken}`);
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(Array.isArray(response.body.data)).toBe(true);
expect(response.body.data.length).toBeGreaterThanOrEqual(1);
});
test('should return empty array for space with no workouts', async () => {
const response = await request(app)
.get('/api/v3/fitness/workouts')
.query({ space_id: 9999 })
.set('Authorization', `Bearer ${authToken}`);
expect(response.status).toBe(200);
expect(response.body.data).toEqual([]);
});
});
describe('GET /api/v3/fitness/workouts/:id', () => {
test('should get single workout with sets', async () => {
// Create workout
const createRes = await request(app)
.post('/api/v3/fitness/workouts')
.set('Authorization', `Bearer ${authToken}`)
.send({
space_id: spaceId,
title: 'Detailed Workout',
started_at: '2026-01-17T08:00:00Z'
});
const workoutId = createRes.body.data.id;
// Add a set
await request(app)
.post(`/api/v3/fitness/workouts/${workoutId}/sets`)
.set('Authorization', `Bearer ${authToken}`)
.send({
exercise_name: 'Bench Press',
set_index: 1,
weight_kg: 80,
reps: 10
});
const response = await request(app)
.get(`/api/v3/fitness/workouts/${workoutId}`)
.set('Authorization', `Bearer ${authToken}`);
expect(response.status).toBe(200);
expect(response.body.data.id).toBe(workoutId);
expect(response.body.data.title).toBe('Detailed Workout');
expect(Array.isArray(response.body.data.sets)).toBe(true);
expect(response.body.data.sets.length).toBe(1);
expect(response.body.data.sets[0].exercise_name).toBe('Bench Press');
});
test('should return 404 for non-existent workout', async () => {
const response = await request(app)
.get('/api/v3/fitness/workouts/99999')
.set('Authorization', `Bearer ${authToken}`);
expect(response.status).toBe(404);
expect(response.body.success).toBe(false);
});
});
describe('PUT /api/v3/fitness/workouts/:id', () => {
test('should update workout', async () => {
// Create workout
const createRes = await request(app)
.post('/api/v3/fitness/workouts')
.set('Authorization', `Bearer ${authToken}`)
.send({
space_id: spaceId,
title: 'Original Title',
started_at: '2026-01-17T08:00:00Z'
});
const workoutId = createRes.body.data.id;
const response = await request(app)
.put(`/api/v3/fitness/workouts/${workoutId}`)
.set('Authorization', `Bearer ${authToken}`)
.send({
title: 'Updated Title',
ended_at: '2026-01-17T09:30:00Z',
notes: 'Great session!'
});
expect(response.status).toBe(200);
expect(response.body.data.title).toBe('Updated Title');
expect(response.body.data.notes).toBe('Great session!');
});
});
describe('DELETE /api/v3/fitness/workouts/:id', () => {
test('should delete workout and its sets', async () => {
// Create workout with set
const createRes = await request(app)
.post('/api/v3/fitness/workouts')
.set('Authorization', `Bearer ${authToken}`)
.send({
space_id: spaceId,
title: 'To Delete',
started_at: '2026-01-17T08:00:00Z'
});
const workoutId = createRes.body.data.id;
// Add set
await request(app)
.post(`/api/v3/fitness/workouts/${workoutId}/sets`)
.set('Authorization', `Bearer ${authToken}`)
.send({
exercise_name: 'Squat',
set_index: 1,
weight_kg: 100,
reps: 5
});
// Delete workout
const deleteRes = await request(app)
.delete(`/api/v3/fitness/workouts/${workoutId}`)
.set('Authorization', `Bearer ${authToken}`);
expect(deleteRes.status).toBe(200);
expect(deleteRes.body.success).toBe(true);
// Verify workout is gone
const getRes = await request(app)
.get(`/api/v3/fitness/workouts/${workoutId}`)
.set('Authorization', `Bearer ${authToken}`);
expect(getRes.status).toBe(404);
});
});
});
describe('Fitness API - Sets CRUD', () => {
let workoutId;
beforeEach(async () => {
// Create a workout for set tests
const createRes = await request(app)
.post('/api/v3/fitness/workouts')
.set('Authorization', `Bearer ${authToken}`)
.send({
space_id: spaceId,
title: 'Set Test Workout',
started_at: '2026-01-17T08:00:00Z'
});
workoutId = createRes.body.data?.id;
});
describe('POST /api/v3/fitness/workouts/:workoutId/sets', () => {
test('should create a new set', async () => {
const response = await request(app)
.post(`/api/v3/fitness/workouts/${workoutId}/sets`)
.set('Authorization', `Bearer ${authToken}`)
.send({
exercise_name: 'Deadlift',
set_index: 1,
set_type: 'normal',
weight_kg: 140,
reps: 5,
rpe: 8
});
expect(response.status).toBe(201);
expect(response.body.success).toBe(true);
expect(response.body.data.id).toBeDefined();
expect(response.body.data.exercise_name).toBe('Deadlift');
expect(Number(response.body.data.weight_kg)).toBe(140);
expect(response.body.data.reps).toBe(5);
});
test('should allow creating warmup set', async () => {
const response = await request(app)
.post(`/api/v3/fitness/workouts/${workoutId}/sets`)
.set('Authorization', `Bearer ${authToken}`)
.send({
exercise_name: 'Bench Press',
set_index: 1,
set_type: 'warmup',
weight_kg: 40,
reps: 15
});
expect(response.status).toBe(201);
expect(response.body.data.set_type).toBe('warmup');
});
test('should require exercise_name', async () => {
const response = await request(app)
.post(`/api/v3/fitness/workouts/${workoutId}/sets`)
.set('Authorization', `Bearer ${authToken}`)
.send({
set_index: 1,
weight_kg: 80,
reps: 10
});
expect(response.status).toBe(400);
expect(response.body.error.code).toBe('VALIDATION_ERROR');
});
});
describe('PUT /api/v3/fitness/sets/:id', () => {
test('should update a set', async () => {
// Create set
const createRes = await request(app)
.post(`/api/v3/fitness/workouts/${workoutId}/sets`)
.set('Authorization', `Bearer ${authToken}`)
.send({
exercise_name: 'Squat',
set_index: 1,
weight_kg: 100,
reps: 8
});
const setId = createRes.body.data.id;
// Update set
const response = await request(app)
.put(`/api/v3/fitness/sets/${setId}`)
.set('Authorization', `Bearer ${authToken}`)
.send({
weight_kg: 105,
reps: 10,
is_pr: true
});
expect(response.status).toBe(200);
expect(Number(response.body.data.weight_kg)).toBe(105);
expect(response.body.data.reps).toBe(10);
expect(response.body.data.is_pr).toBe(true);
});
});
describe('DELETE /api/v3/fitness/sets/:id', () => {
test('should delete a set', async () => {
// Create set
const createRes = await request(app)
.post(`/api/v3/fitness/workouts/${workoutId}/sets`)
.set('Authorization', `Bearer ${authToken}`)
.send({
exercise_name: 'Pull-up',
set_index: 1,
reps: 12
});
const setId = createRes.body.data.id;
// Delete set
const response = await request(app)
.delete(`/api/v3/fitness/sets/${setId}`)
.set('Authorization', `Bearer ${authToken}`);
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
});
});
});
describe('Fitness API - Exercises Catalog', () => {
describe('GET /api/v3/fitness/exercises', () => {
test('should list exercises', async () => {
const response = await request(app)
.get('/api/v3/fitness/exercises')
.set('Authorization', `Bearer ${authToken}`);
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(Array.isArray(response.body.data)).toBe(true);
});
test('should search exercises by name', async () => {
// First create an exercise with unique name
const uniqueName = `Barbell Bench Press ${Date.now()}`;
await request(app)
.post('/api/v3/fitness/exercises')
.set('Authorization', `Bearer ${authToken}`)
.send({
name: uniqueName,
primary_muscle: 'chest',
equipment: 'barbell'
});
const response = await request(app)
.get('/api/v3/fitness/exercises')
.query({ search: 'bench' })
.set('Authorization', `Bearer ${authToken}`);
expect(response.status).toBe(200);
expect(response.body.data.some(e => e.name.toLowerCase().includes('bench'))).toBe(true);
});
});
describe('POST /api/v3/fitness/exercises', () => {
test('should create custom exercise', async () => {
const uniqueName = `My Custom Exercise ${Date.now()}`;
const response = await request(app)
.post('/api/v3/fitness/exercises')
.set('Authorization', `Bearer ${authToken}`)
.send({
space_id: spaceId,
name: uniqueName,
primary_muscle: 'back',
equipment: 'cable'
});
expect(response.status).toBe(201);
expect(response.body.data.name).toBe(uniqueName);
expect(response.body.data.space_id).toBe(spaceId);
});
});
});