Governed substrate for autonomous agents: scoped identity (passports), audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
221 lines
9.4 KiB
JavaScript
221 lines
9.4 KiB
JavaScript
// Test: Fitness Module Database Schema
|
|
// ADR-025: Fitness Module - LiftShift Clone
|
|
import { describe, test, expect, beforeEach, afterEach } from 'vitest';
|
|
import { setupTestDatabase, cleanupTestDatabase } from './helpers/test-db.js';
|
|
import { dbGet, dbAll, dbRun } from '../database/connection.js';
|
|
|
|
// Skip dev user creation in tests (avoids NeoMetal dependency)
|
|
process.env.SKIP_DEV_USER = 'true';
|
|
|
|
const TEST_USER_ID = 99001;
|
|
const TEST_SPACE_ID = 99001;
|
|
const TEST_EXERCISE_ID = 99001;
|
|
const TEST_WORKOUT_ID = 99001;
|
|
|
|
// Helper to create test user with all required fields
|
|
async function createTestUser(id = TEST_USER_ID) {
|
|
await dbRun(`
|
|
INSERT INTO users (id, email, password_hash, name, encryption_key_encrypted)
|
|
VALUES ($1, $2, 'hash', 'Test User', 'test_encrypted_key')
|
|
ON CONFLICT (id) DO NOTHING
|
|
`, [id, `fitness-schema-test${id}@test.com`]);
|
|
}
|
|
|
|
// Helper to create test space
|
|
async function createTestSpace(id = TEST_SPACE_ID, ownerId = TEST_USER_ID) {
|
|
await createTestUser(ownerId);
|
|
await dbRun(`
|
|
INSERT INTO spaces (id, name, owner_id, type)
|
|
VALUES ($1, 'Test Space', $2, 'personal')
|
|
ON CONFLICT (id) DO NOTHING
|
|
`, [id, ownerId]);
|
|
}
|
|
|
|
// Cleanup helper
|
|
async function cleanupFitnessTestData() {
|
|
await dbRun(`DELETE FROM fitness_sets WHERE workout_id = $1`, [TEST_WORKOUT_ID]);
|
|
await dbRun(`DELETE FROM fitness_workouts WHERE space_id = $1`, [TEST_SPACE_ID]);
|
|
await dbRun(`DELETE FROM fitness_exercises WHERE name IN ('Bench Press', 'Custom Exercise', 'Squat') AND (space_id = $1 OR space_id IS NULL)`, [TEST_SPACE_ID]);
|
|
await dbRun(`DELETE FROM spaces WHERE id = $1`, [TEST_SPACE_ID]);
|
|
await dbRun(`DELETE FROM users WHERE id = $1`, [TEST_USER_ID]);
|
|
}
|
|
|
|
// Helper: get column names for a table (PostgreSQL)
|
|
async function getColumnNames(tableName) {
|
|
const rows = await dbAll(
|
|
`SELECT column_name as name FROM information_schema.columns WHERE table_name = $1 ORDER BY ordinal_position`,
|
|
[tableName]
|
|
);
|
|
return rows.map(r => r.name);
|
|
}
|
|
|
|
describe('Fitness Database Schema', () => {
|
|
beforeEach(async () => {
|
|
await setupTestDatabase();
|
|
await cleanupFitnessTestData();
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await cleanupFitnessTestData();
|
|
await cleanupTestDatabase();
|
|
});
|
|
|
|
describe('fitness_exercises table', () => {
|
|
test('should exist with all required columns', async () => {
|
|
const columnNames = await getColumnNames('fitness_exercises');
|
|
|
|
expect(columnNames.length).toBeGreaterThan(0);
|
|
|
|
expect(columnNames).toContain('id');
|
|
expect(columnNames).toContain('space_id');
|
|
expect(columnNames).toContain('name');
|
|
expect(columnNames).toContain('equipment');
|
|
expect(columnNames).toContain('primary_muscle');
|
|
expect(columnNames).toContain('secondary_muscle');
|
|
expect(columnNames).toContain('category');
|
|
expect(columnNames).toContain('created_at');
|
|
});
|
|
|
|
test('should allow inserting global exercise (space_id = NULL)', async () => {
|
|
await dbRun(`
|
|
INSERT INTO fitness_exercises (name, equipment, primary_muscle, category)
|
|
VALUES ($1, $2, $3, $4)
|
|
`, ['Bench Press', 'barbell', 'Chest', 'compound']);
|
|
|
|
const exercise = await dbGet(`SELECT * FROM fitness_exercises WHERE name = $1`, ['Bench Press']);
|
|
|
|
expect(exercise).toBeDefined();
|
|
expect(exercise.name).toBe('Bench Press');
|
|
expect(exercise.space_id).toBeNull();
|
|
expect(exercise.primary_muscle).toBe('Chest');
|
|
|
|
// Cleanup
|
|
await dbRun(`DELETE FROM fitness_exercises WHERE name = 'Bench Press' AND space_id IS NULL`);
|
|
});
|
|
|
|
test('should allow inserting user-specific exercise (space_id set)', async () => {
|
|
await createTestSpace();
|
|
|
|
await dbRun(`
|
|
INSERT INTO fitness_exercises (space_id, name, equipment, primary_muscle, category)
|
|
VALUES ($1, $2, $3, $4, $5)
|
|
`, [TEST_SPACE_ID, 'Custom Exercise', 'bodyweight', 'Core', 'isolation']);
|
|
|
|
const exercise = await dbGet(`SELECT * FROM fitness_exercises WHERE name = $1`, ['Custom Exercise']);
|
|
|
|
expect(exercise).toBeDefined();
|
|
expect(exercise.space_id).toBe(TEST_SPACE_ID);
|
|
});
|
|
});
|
|
|
|
describe('fitness_workouts table', () => {
|
|
test('should exist with all required columns', async () => {
|
|
const columnNames = await getColumnNames('fitness_workouts');
|
|
|
|
expect(columnNames.length).toBeGreaterThan(0);
|
|
|
|
expect(columnNames).toContain('id');
|
|
expect(columnNames).toContain('space_id');
|
|
expect(columnNames).toContain('title');
|
|
expect(columnNames).toContain('description');
|
|
expect(columnNames).toContain('started_at');
|
|
expect(columnNames).toContain('ended_at');
|
|
expect(columnNames).toContain('notes');
|
|
expect(columnNames).toContain('source');
|
|
expect(columnNames).toContain('created_at');
|
|
expect(columnNames).toContain('updated_at');
|
|
});
|
|
|
|
test('should allow inserting workout', async () => {
|
|
await createTestSpace();
|
|
|
|
await dbRun(`
|
|
INSERT INTO fitness_workouts (id, space_id, title, started_at, source)
|
|
VALUES ($1, $2, $3, $4, $5)
|
|
`, [TEST_WORKOUT_ID, TEST_SPACE_ID, 'Morning Workout', '2026-01-17 08:00:00', 'manual']);
|
|
|
|
const workout = await dbGet(`SELECT * FROM fitness_workouts WHERE title = $1`, ['Morning Workout']);
|
|
|
|
expect(workout).toBeDefined();
|
|
expect(workout.space_id).toBe(TEST_SPACE_ID);
|
|
expect(workout.source).toBe('manual');
|
|
});
|
|
});
|
|
|
|
describe('fitness_sets table', () => {
|
|
test('should exist with all required columns', async () => {
|
|
const columnNames = await getColumnNames('fitness_sets');
|
|
|
|
expect(columnNames.length).toBeGreaterThan(0);
|
|
|
|
expect(columnNames).toContain('id');
|
|
expect(columnNames).toContain('workout_id');
|
|
expect(columnNames).toContain('exercise_id');
|
|
expect(columnNames).toContain('exercise_name');
|
|
expect(columnNames).toContain('set_index');
|
|
expect(columnNames).toContain('set_type');
|
|
expect(columnNames).toContain('weight_kg');
|
|
expect(columnNames).toContain('reps');
|
|
expect(columnNames).toContain('rpe');
|
|
expect(columnNames).toContain('distance_km');
|
|
expect(columnNames).toContain('duration_seconds');
|
|
expect(columnNames).toContain('is_pr');
|
|
expect(columnNames).toContain('created_at');
|
|
});
|
|
|
|
test('should allow inserting set with exercise reference', async () => {
|
|
await createTestSpace();
|
|
await dbRun(`INSERT INTO fitness_exercises (id, name, primary_muscle) VALUES ($1, 'Squat', 'Quads') ON CONFLICT (id) DO NOTHING`, [TEST_EXERCISE_ID]);
|
|
await dbRun(`INSERT INTO fitness_workouts (id, space_id, title, started_at) VALUES ($1, $2, 'Leg Day', '2026-01-17 08:00:00')`, [TEST_WORKOUT_ID, TEST_SPACE_ID]);
|
|
|
|
await dbRun(`
|
|
INSERT INTO fitness_sets (workout_id, exercise_id, exercise_name, set_index, weight_kg, reps, is_pr)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
`, [TEST_WORKOUT_ID, TEST_EXERCISE_ID, 'Squat', 1, 100, 5, 1]);
|
|
|
|
const set = await dbGet(`SELECT * FROM fitness_sets WHERE workout_id = $1`, [TEST_WORKOUT_ID]);
|
|
|
|
expect(set).toBeDefined();
|
|
expect(set.exercise_name).toBe('Squat');
|
|
expect(Number(set.weight_kg)).toBe(100);
|
|
expect(set.reps).toBe(5);
|
|
expect(set.is_pr).toBeTruthy();
|
|
});
|
|
|
|
test('should cascade delete sets when workout is deleted', async () => {
|
|
await createTestSpace();
|
|
await dbRun(`INSERT INTO fitness_workouts (id, space_id, title, started_at) VALUES ($1, $2, 'Test Workout', '2026-01-17 08:00:00')`, [TEST_WORKOUT_ID, TEST_SPACE_ID]);
|
|
await dbRun(`INSERT INTO fitness_sets (workout_id, exercise_name, set_index, weight_kg, reps) VALUES ($1, 'Bench', 1, 80, 8)`, [TEST_WORKOUT_ID]);
|
|
await dbRun(`INSERT INTO fitness_sets (workout_id, exercise_name, set_index, weight_kg, reps) VALUES ($1, 'Bench', 2, 80, 7)`, [TEST_WORKOUT_ID]);
|
|
|
|
// Verify sets exist
|
|
const setsBefore = await dbAll(`SELECT * FROM fitness_sets WHERE workout_id = $1`, [TEST_WORKOUT_ID]);
|
|
expect(setsBefore.length).toBe(2);
|
|
|
|
// Delete workout
|
|
await dbRun(`DELETE FROM fitness_workouts WHERE id = $1`, [TEST_WORKOUT_ID]);
|
|
|
|
// Verify sets are deleted
|
|
const setsAfter = await dbAll(`SELECT * FROM fitness_sets WHERE workout_id = $1`, [TEST_WORKOUT_ID]);
|
|
expect(setsAfter.length).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('fitness_workout_sets view', () => {
|
|
test('should return joined workout and set data', async () => {
|
|
await createTestSpace();
|
|
await dbRun(`INSERT INTO fitness_workouts (id, space_id, title, started_at, ended_at, description) VALUES ($1, $2, 'Push Day', '2026-01-17 08:00:00', '2026-01-17 09:00:00', 'Chest and Triceps')`, [TEST_WORKOUT_ID, TEST_SPACE_ID]);
|
|
await dbRun(`INSERT INTO fitness_sets (workout_id, exercise_name, set_index, set_type, weight_kg, reps, is_pr) VALUES ($1, 'Bench Press', 1, 'warmup', 60, 10, false)`, [TEST_WORKOUT_ID]);
|
|
await dbRun(`INSERT INTO fitness_sets (workout_id, exercise_name, set_index, set_type, weight_kg, reps, is_pr) VALUES ($1, 'Bench Press', 2, 'normal', 100, 5, true)`, [TEST_WORKOUT_ID]);
|
|
|
|
const rows = await dbAll(`SELECT * FROM fitness_workout_sets WHERE workout_id = $1 ORDER BY set_index`, [TEST_WORKOUT_ID]);
|
|
|
|
expect(rows.length).toBe(2);
|
|
expect(rows[0].workout_title).toBe('Push Day');
|
|
expect(rows[0].exercise_name).toBe('Bench Press');
|
|
expect(rows[0].set_type).toBe('warmup');
|
|
expect(rows[1].set_type).toBe('normal');
|
|
expect(rows[1].is_pr).toBeTruthy();
|
|
});
|
|
});
|
|
});
|