godcrm/backend/tests/fitness-analytics.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

207 lines
7.2 KiB
JavaScript

// @vitest-environment node
/**
* ADR-025: Fitness Analytics 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';
import { dbRun } from '../database/connection.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 spaceId;
/**
* Helper to create test user and space
*/
const TEST_UID = 99100;
async function createTestUserAndSpace() {
await dbRun(`
INSERT INTO users (id, email, password_hash, name, encryption_key_encrypted)
VALUES ($1, $2, 'hash', 'Test User', 'encrypted_key')
ON CONFLICT (id) DO NOTHING
`, [TEST_UID, `fitness-analytics-${Date.now()}@test.com`]);
const result = await dbRun(`
INSERT INTO spaces (owner_id, name, type)
VALUES ($1, 'Test Space Analytics', 'personal')
`, [TEST_UID]);
return result.lastInsertRowid;
}
/**
* Helper to seed workout data for analytics tests
*/
async function seedAnalyticsData(spaceId) {
// Create workouts over the past week (relative to today so streak/week filters work)
const today = new Date();
const makeDate = (daysAgo) => {
const d = new Date(today);
d.setDate(d.getDate() - daysAgo);
return d.toISOString().split('T')[0] + ' 08:00:00';
};
const dates = [
makeDate(7), // Day 1
makeDate(5), // Day 3
makeDate(3), // Day 5
makeDate(2), // Day 6
makeDate(0), // Day 8 (today)
];
// Clean up any previous fitness data to avoid PK conflicts with SERIAL
await dbRun(`DELETE FROM fitness_sets`);
await dbRun(`DELETE FROM fitness_workouts`);
for (let i = 0; i < dates.length; i++) {
const workoutResult = await dbRun(`
INSERT INTO fitness_workouts (space_id, title, started_at, ended_at)
VALUES (?, ?, ?, ?)
`, [spaceId, `Workout ${i + 1}`, dates[i], dates[i].replace('08:00:00', '09:00:00')]);
const workoutId = workoutResult.lastInsertRowid;
// Add sets with increasing weights (for PR detection)
await dbRun(`
INSERT INTO fitness_sets (workout_id, exercise_name, set_index, weight_kg, reps, is_pr)
VALUES (?, 'Bench Press', 1, ?, 5, ?)
`, [workoutId, 60 + i * 10, i === dates.length - 1 ? true : false]); // Last one is PR
await dbRun(`
INSERT INTO fitness_sets (workout_id, exercise_name, set_index, weight_kg, reps)
VALUES (?, 'Squat', 1, ?, 5)
`, [workoutId, 80 + i * 10]);
}
}
beforeEach(async () => {
await setupTestDatabase();
app = express();
app.use(express.json());
spaceId = await createTestUserAndSpace();
await seedAnalyticsData(spaceId);
const fitnessRoutes = (await import('../routes/v3/fitness.js')).default;
app.use('/api/v3/fitness', fitnessRoutes);
});
afterEach(async () => {
await cleanupTestDatabase();
});
describe('Fitness Analytics API', () => {
describe('GET /api/v3/fitness/analytics/summary', () => {
test('should return workout summary stats', async () => {
const response = await request(app)
.get('/api/v3/fitness/analytics/summary')
.query({ space_id: spaceId });
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(response.body.data.total_workouts).toBe(5);
expect(response.body.data.total_sets).toBe(10);
expect(response.body.data.total_volume).toBeGreaterThan(0);
expect(response.body.data.total_prs).toBeGreaterThanOrEqual(1);
});
});
describe('GET /api/v3/fitness/analytics/volume', () => {
test('should return volume by day', async () => {
const response = await request(app)
.get('/api/v3/fitness/analytics/volume')
.query({ space_id: spaceId, period: 'week' });
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(Array.isArray(response.body.data)).toBe(true);
expect(response.body.data.length).toBeGreaterThan(0);
expect(response.body.data[0]).toHaveProperty('date');
expect(response.body.data[0]).toHaveProperty('volume');
});
test('should return volume by exercise', async () => {
const response = await request(app)
.get('/api/v3/fitness/analytics/volume')
.query({ space_id: spaceId, group_by: 'exercise' });
expect(response.status).toBe(200);
expect(response.body.data.some(d => d.exercise === 'Bench Press')).toBe(true);
expect(response.body.data.some(d => d.exercise === 'Squat')).toBe(true);
});
});
describe('GET /api/v3/fitness/analytics/prs', () => {
test('should return personal records', async () => {
const response = await request(app)
.get('/api/v3/fitness/analytics/prs')
.query({ space_id: spaceId });
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);
expect(response.body.data[0]).toHaveProperty('exercise_name');
expect(response.body.data[0]).toHaveProperty('weight_kg');
});
});
describe('GET /api/v3/fitness/analytics/streak', () => {
test('should return current workout streak', async () => {
const response = await request(app)
.get('/api/v3/fitness/analytics/streak')
.query({ space_id: spaceId });
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(response.body.data.current_streak).toBeGreaterThanOrEqual(0);
expect(response.body.data.longest_streak).toBeGreaterThanOrEqual(0);
expect(response.body.data.workouts_this_week).toBeGreaterThan(0);
});
});
describe('GET /api/v3/fitness/analytics/muscle-volume', () => {
test('should return volume by muscle group', async () => {
// First add exercise with muscle mapping
await dbRun(`
INSERT INTO fitness_exercises (name, primary_muscle, equipment)
VALUES ('Bench Press', 'chest', 'barbell')
`);
await dbRun(`
INSERT INTO fitness_exercises (name, primary_muscle, equipment)
VALUES ('Squat', 'quadriceps', 'barbell')
`);
const response = await request(app)
.get('/api/v3/fitness/analytics/muscle-volume')
.query({ space_id: spaceId });
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(Array.isArray(response.body.data)).toBe(true);
});
});
describe('GET /api/v3/fitness/analytics/exercise/:exerciseName', () => {
test('should return exercise-specific analytics', async () => {
const response = await request(app)
.get('/api/v3/fitness/analytics/exercise/Bench Press')
.query({ space_id: spaceId });
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(response.body.data.exercise_name).toBe('Bench Press');
expect(response.body.data.total_sets).toBeGreaterThan(0);
expect(response.body.data.max_weight).toBeGreaterThan(0);
expect(response.body.data.estimated_1rm).toBeGreaterThan(0);
});
});
});