Files
truenas-stacks/home/doris-barbell/tests/test_app.py
Fizzlepoof 64e7ac82cf
Some checks failed
secret-guardrails / gitleaks (push) Has been cancelled
secret-guardrails / artifact-secret-scan (push) Has been cancelled
Simplify Doris Barbell dashboard
2026-05-28 20:49:23 +00:00

704 lines
25 KiB
Python

import json
import re
from pathlib import Path
from fastapi.testclient import TestClient
from app.main import create_app
def make_client(tmp_path: Path) -> TestClient:
app = create_app(state_dir=tmp_path)
return TestClient(app)
def test_health_endpoint_reports_ok(tmp_path: Path) -> None:
client = make_client(tmp_path)
response = client.get('/api/health')
assert response.status_code == 200
assert response.json() == {'status': 'ok'}
def test_dashboard_summary_seeds_john_and_manndra(tmp_path: Path) -> None:
client = make_client(tmp_path)
response = client.get('/api/dashboard/summary')
assert response.status_code == 200
payload = response.json()
user_ids = [user['id'] for user in payload['users']]
assert user_ids == ['john', 'manndra']
assert payload['counts'] == {
'exercise_templates': 31,
'measurement_entries': 0,
'routines': 5,
'weight_entries': 0,
'workout_sessions': 0,
}
john = next(user for user in payload['users'] if user['id'] == 'john')
assert john['measurement_system'] == 'imperial'
routine_names = [routine['name'] for routine in payload['routines']]
assert routine_names == [
'Monday: Chest',
'Tuesday: Back',
'Wednesday: Arms',
'Thursday: Shoulders',
'Friday: Legs',
]
assert payload['routines'][0]['exercises'][0] == {
'exercise_name': 'Bench Press',
'target_sets': 3,
'target_reps': '10',
}
assert '3 sets of 10 reps' in payload['routines'][0]['notes']
assert 'Weights are still TBD' in payload['routines'][0]['notes']
shoulders = next(routine for routine in payload['routines'] if routine['name'] == 'Thursday: Shoulders')
assert shoulders['exercises'][6] == {
'exercise_name': '7-7-7 Shoulder Press',
'target_sets': 1,
'target_reps': '7-7-7',
}
assert shoulders['exercises'][7] == {
'exercise_name': 'Resistance Band Punch-Out Training',
'target_sets': 3,
'target_reps': '10',
}
assert 'resistance band punch-out training' in shoulders['notes']
template_names = [template['name'] for template in payload['exercise_templates']]
assert template_names == [
'Resistance Band Crunches',
'Calf Raises',
'Deadlifts',
'Step Ups',
'Leg Extensions',
]
def test_updating_profile_and_logging_weight_computes_bmi(tmp_path: Path) -> None:
client = make_client(tmp_path)
profile_response = client.put(
'/api/profile/john',
json={
'display_name': 'John',
'height_inches': 68.5,
},
)
assert profile_response.status_code == 200
weight_response = client.post(
'/api/profile/john/weight',
json={
'recorded_at': '2026-05-21T08:30:00',
'weight_lbs': 220,
'body_fat_percent': 24.5,
},
)
assert weight_response.status_code == 201
saved_entry = weight_response.json()
assert saved_entry['user_id'] == 'john'
assert saved_entry['weight_lbs'] == 220
assert saved_entry['body_fat_percent'] == 24.5
assert round(saved_entry['bmi'], 1) == 33.0
summary = client.get('/api/dashboard/summary').json()
john = next(user for user in summary['users'] if user['id'] == 'john')
assert john['current_weight_lbs'] == 220
assert round(john['current_bmi'], 1) == 33.0
def test_logging_measurements_tracks_latest_imperial_body_metrics(tmp_path: Path) -> None:
client = make_client(tmp_path)
response = client.post(
'/api/profile/john/measurements',
json={
'recorded_at': '2026-05-21T08:45:00',
'waist_inches': 42,
'hip_inches': 41,
'chest_inches': 46,
'neck_inches': 17,
'notes': 'Post-op baseline',
},
)
assert response.status_code == 201
saved_entry = response.json()
assert saved_entry['user_id'] == 'john'
assert saved_entry['waist_inches'] == 42
assert saved_entry['hip_inches'] == 41
assert saved_entry['chest_inches'] == 46
assert saved_entry['neck_inches'] == 17
summary = client.get('/api/dashboard/summary').json()
john = next(user for user in summary['users'] if user['id'] == 'john')
assert summary['counts']['measurement_entries'] == 1
assert john['latest_measurements'] == {
'recorded_at': '2026-05-21T08:45:00',
'waist_inches': 42,
'hip_inches': 41,
'chest_inches': 46,
'neck_inches': 17,
}
def test_can_create_and_list_exercise_templates(tmp_path: Path) -> None:
client = make_client(tmp_path)
create_response = client.post(
'/api/exercises/templates',
json={
'name': 'Bench Press',
'primary_muscle': 'chest',
'equipment': 'barbell',
'notes': 'Flat bench barbell press.',
},
)
assert create_response.status_code == 201
saved_template = create_response.json()
assert saved_template['name'] == 'Bench Press'
assert saved_template['equipment'] == 'barbell'
list_response = client.get('/api/exercises/templates')
assert list_response.status_code == 200
payload = list_response.json()
assert len(payload) == 32
assert any(
item['name'] == 'Bench Press' and item['notes'] == 'Flat bench barbell press.'
for item in payload
)
summary = client.get('/api/dashboard/summary').json()
assert summary['counts']['exercise_templates'] == 32
def test_can_create_routine_before_real_program_arrives(tmp_path: Path) -> None:
client = make_client(tmp_path)
client.post(
'/api/exercises/templates',
json={
'name': 'Bench Press',
'primary_muscle': 'chest',
'equipment': 'barbell',
},
)
client.post(
'/api/exercises/templates',
json={
'name': 'Incline Dumbbell Press',
'primary_muscle': 'chest',
'equipment': 'dumbbells',
},
)
create_response = client.post(
'/api/routines',
json={
'user_id': 'john',
'name': 'Push Day Placeholder',
'notes': 'Temporary until John sends the real split.',
'exercises': [
{'exercise_name': 'Bench Press', 'target_sets': 3, 'target_reps': '8-10'},
{'exercise_name': 'Incline Dumbbell Press', 'target_sets': 3, 'target_reps': '10-12'},
],
},
)
assert create_response.status_code == 201
saved_routine = create_response.json()
assert saved_routine['user_id'] == 'john'
assert saved_routine['name'] == 'Push Day Placeholder'
assert len(saved_routine['exercises']) == 2
list_response = client.get('/api/routines')
assert list_response.status_code == 200
payload = list_response.json()
assert len(payload) == 6
assert any(routine['name'] == 'Push Day Placeholder' for routine in payload)
summary = client.get('/api/dashboard/summary').json()
assert summary['counts']['routines'] == 6
def test_history_endpoints_return_newest_first(tmp_path: Path) -> None:
client = make_client(tmp_path)
client.post(
'/api/profile/john/weight',
json={'recorded_at': '2026-05-20T08:30:00', 'weight_lbs': 222},
)
client.post(
'/api/profile/john/weight',
json={'recorded_at': '2026-05-21T08:30:00', 'weight_lbs': 220},
)
client.post(
'/api/profile/john/measurements',
json={'recorded_at': '2026-05-20T08:45:00', 'waist_inches': 43},
)
client.post(
'/api/profile/john/measurements',
json={'recorded_at': '2026-05-21T08:45:00', 'waist_inches': 42},
)
client.post(
'/api/workouts',
json={
'user_id': 'john',
'performed_at': '2026-05-20T09:15:00',
'routine_name': 'Old Workout',
'exercises': [{'exercise_name': 'Bench Press', 'sets': [{'reps': 8, 'weight_lbs': 135}]}],
},
)
client.post(
'/api/workouts',
json={
'user_id': 'john',
'performed_at': '2026-05-21T09:15:00',
'routine_name': 'New Workout',
'exercises': [{'exercise_name': 'Bench Press', 'sets': [{'reps': 8, 'weight_lbs': 145}]}],
},
)
weight_history = client.get('/api/history/john/weights')
measurement_history = client.get('/api/history/john/measurements')
workout_history = client.get('/api/history/john/workouts')
assert weight_history.status_code == 200
assert measurement_history.status_code == 200
assert workout_history.status_code == 200
assert [entry['weight_lbs'] for entry in weight_history.json()] == [220, 222]
assert [entry['waist_inches'] for entry in measurement_history.json()] == [42, 43]
assert [entry['routine_name'] for entry in workout_history.json()] == ['New Workout', 'Old Workout']
def test_logging_structured_workout_updates_recent_activity(tmp_path: Path) -> None:
client = make_client(tmp_path)
response = client.post(
'/api/workouts',
json={
'user_id': 'john',
'performed_at': '2026-05-21T09:15:00',
'routine_name': 'Push Day',
'notes': 'First pass',
'exercises': [
{
'exercise_name': 'Bench Press',
'sets': [
{'reps': 8, 'weight_lbs': 135, 'rpe': 7.5, 'rest_seconds': 120},
{'reps': 8, 'weight_lbs': 145, 'rpe': 8.0, 'rest_seconds': 120},
],
}
],
},
)
assert response.status_code == 201
workout = response.json()
assert workout['user_id'] == 'john'
assert workout['routine_name'] == 'Push Day'
assert workout['exercise_count'] == 1
assert workout['set_count'] == 2
assert workout['total_volume_lbs'] == 2240
summary = client.get('/api/dashboard/summary').json()
assert summary['counts']['workout_sessions'] == 1
assert summary['recent_workouts'][0]['routine_name'] == 'Push Day'
assert summary['recent_workouts'][0]['total_volume_lbs'] == 2240
def test_progression_endpoint_summarizes_exercise_bests(tmp_path: Path) -> None:
client = make_client(tmp_path)
client.post(
'/api/workouts',
json={
'user_id': 'john',
'performed_at': '2026-05-20T09:15:00',
'routine_name': 'Push Day',
'exercises': [
{
'exercise_name': 'Bench Press',
'sets': [
{'reps': 8, 'weight_lbs': 135},
{'reps': 5, 'weight_lbs': 155},
],
}
],
},
)
client.post(
'/api/workouts',
json={
'user_id': 'john',
'performed_at': '2026-05-22T09:15:00',
'routine_name': 'Push Day',
'exercises': [
{
'exercise_name': 'Bench Press',
'sets': [
{'reps': 3, 'weight_lbs': 175},
],
},
{
'exercise_name': 'Incline Dumbbell Press',
'sets': [
{'reps': 10, 'weight_lbs': 55},
],
},
],
},
)
response = client.get('/api/progression/john/exercises')
assert response.status_code == 200
payload = response.json()
assert payload[0]['exercise_name'] == 'Bench Press'
assert payload[0]['sessions'] == 2
assert payload[0]['max_weight_lbs'] == 175
assert payload[0]['best_set_estimated_1rm'] == 192.5
assert payload[0]['last_performed_at'] == '2026-05-22T09:15:00'
assert payload[1]['exercise_name'] == 'Incline Dumbbell Press'
assert payload[1]['sessions'] == 1
def test_homepage_renders_core_sections_and_forms(tmp_path: Path) -> None:
client = make_client(tmp_path)
response = client.get('/')
assert response.status_code == 200
body = response.text
assert 'Doris Barbell' in body
assert 'Log lifts, body weight, and routines.' in body
assert 'Recent workouts' in body
assert 'Current body metrics' in body
assert 'Log weight' in body
assert 'Add exercise template' in body
assert 'Current routine board' in body
assert 'Monday: Chest' in body
assert 'Doris family directory' not in body
assert 'V1 focus' not in body
assert 'Next likely build' not in body
def test_seeded_routine_can_render_prefilled_log_sheet(tmp_path: Path) -> None:
client = make_client(tmp_path)
summary = client.get('/api/dashboard/summary').json()
shoulders = next(routine for routine in summary['routines'] if routine['name'] == 'Thursday: Shoulders')
response = client.get(f"/routines/{shoulders['id']}/log")
assert response.status_code == 200
body = response.text
assert 'Thursday: Shoulders' in body
assert 'Shoulder Press' in body
assert 'Save shoulder day workout' in body
assert 'name="exercise_0_name"' in body
assert '<option value="Shoulder Press" selected>' in body
assert 'value="Upright Row"' in body
assert 'name="exercise_0_set_0_reps"' in body
assert 'name="exercise_0_set_0_weight_lbs"' in body
assert 'placeholder="10"' in body
assert 'name="exercise_0_set_0_reps" min="1" step="1" placeholder="10"' in body
assert re.search(r'name="performed_at" value="\d{4}-\d{2}-\d{2}T\d{2}:\d{2}"', body)
def test_seeded_routine_log_submission_creates_structured_workout(tmp_path: Path) -> None:
client = make_client(tmp_path)
summary = client.get('/api/dashboard/summary').json()
shoulders = next(routine for routine in summary['routines'] if routine['name'] == 'Thursday: Shoulders')
response = client.post(
'/workouts/routine-log',
data={
'user_id': 'john',
'routine_id': shoulders['id'],
'performed_at': '2026-05-28T12:00:00',
'routine_name': shoulders['name'],
'notes': 'Shoulder day at the gym.',
'exercise_count': '2',
'exercise_0_name': 'Shoulder Press',
'exercise_0_set_count': '3',
'exercise_0_set_0_reps': '10',
'exercise_0_set_0_weight_lbs': '70',
'exercise_0_set_1_reps': '10',
'exercise_0_set_1_weight_lbs': '80',
'exercise_0_set_2_reps': '8',
'exercise_0_set_2_weight_lbs': '90',
'exercise_1_name': 'Upright Row',
'exercise_1_set_count': '3',
'exercise_1_set_0_reps': '10',
'exercise_1_set_0_weight_lbs': '35',
'exercise_1_set_1_reps': '10',
'exercise_1_set_1_weight_lbs': '35',
'exercise_1_set_2_reps': '8',
'exercise_1_set_2_weight_lbs': '40',
},
follow_redirects=False,
)
assert response.status_code == 303
assert response.headers['location'] == '/'
workout_history = client.get('/api/history/john/workouts')
assert workout_history.status_code == 200
payload = workout_history.json()
assert payload[0]['routine_name'] == 'Thursday: Shoulders'
assert payload[0]['exercise_count'] == 2
assert payload[0]['set_count'] == 6
assert payload[0]['total_volume_lbs'] == 3240
assert payload[0]['exercises'][0]['exercise_name'] == 'Shoulder Press'
assert payload[0]['exercises'][0]['sets'][2] == {'reps': 8, 'weight_lbs': 90.0}
assert payload[0]['exercises'][1]['exercise_name'] == 'Upright Row'
def test_existing_state_gets_seeded_routine_backfill(tmp_path: Path) -> None:
state_path = tmp_path / 'state.json'
state_path.write_text(
json.dumps(
{
'users': {
'john': {
'id': 'john',
'display_name': 'John',
'is_stub': False,
'measurement_system': 'imperial',
'height_inches': 68.5,
'goal_weight_lbs': None,
'notes': 'Legacy state',
},
'manndra': {
'id': 'manndra',
'display_name': 'Manndra',
'is_stub': True,
'measurement_system': 'imperial',
'height_inches': None,
'goal_weight_lbs': None,
'notes': 'Legacy stub',
},
},
'exercise_templates': [],
'routines': [],
'weight_entries': [],
'measurement_entries': [],
'workout_sessions': [],
}
)
)
client = make_client(tmp_path)
payload = client.get('/api/dashboard/summary').json()
assert payload['counts']['exercise_templates'] == 31
assert payload['counts']['routines'] == 5
assert payload['routines'][0]['name'] == 'Monday: Chest'
assert payload['routines'][0]['exercises'][0]['target_sets'] == 3
assert payload['routines'][0]['exercises'][0]['target_reps'] == '10'
def test_existing_seeded_routines_upgrade_prescriptions_to_three_by_ten(tmp_path: Path) -> None:
state_path = tmp_path / 'state.json'
state_path.write_text(
json.dumps(
{
'users': {
'john': {
'id': 'john',
'display_name': 'John',
'is_stub': False,
'measurement_system': 'imperial',
'height_inches': 68.5,
'goal_weight_lbs': None,
'notes': 'Legacy state',
},
'manndra': {
'id': 'manndra',
'display_name': 'Manndra',
'is_stub': True,
'measurement_system': 'imperial',
'height_inches': None,
'goal_weight_lbs': None,
'notes': 'Legacy stub',
},
},
'exercise_templates': [],
'routines': [
{
'id': 'seed-routine-01',
'user_id': 'john',
'name': 'Monday: Chest',
'notes': 'Imported from John\'s current routine. Set and rep targets were not provided yet.',
'exercises': [
{'exercise_name': 'Bench Press', 'target_sets': 1, 'target_reps': 'TBD'},
{'exercise_name': 'Incline Bench Press', 'target_sets': 1, 'target_reps': 'TBD'},
],
}
],
'weight_entries': [],
'measurement_entries': [],
'workout_sessions': [],
}
)
)
client = make_client(tmp_path)
payload = client.get('/api/dashboard/summary').json()
monday = next(routine for routine in payload['routines'] if routine['name'] == 'Monday: Chest')
assert monday['exercises'][0]['target_sets'] == 3
assert monday['exercises'][0]['target_reps'] == '10'
assert 'Weights are still TBD' in monday['notes']
def test_existing_seeded_shoulders_routine_gets_777_and_punch_out_corrections(tmp_path: Path) -> None:
state_path = tmp_path / 'state.json'
state_path.write_text(
json.dumps(
{
'users': {
'john': {
'id': 'john',
'display_name': 'John',
'is_stub': False,
'measurement_system': 'imperial',
'height_inches': 68.5,
'goal_weight_lbs': None,
'notes': 'Legacy state',
},
'manndra': {
'id': 'manndra',
'display_name': 'Manndra',
'is_stub': True,
'measurement_system': 'imperial',
'height_inches': None,
'goal_weight_lbs': None,
'notes': 'Legacy stub',
},
},
'exercise_templates': [
{
'id': 'seed-template-25',
'name': 'Resistance Band Punch-Out Holds',
'primary_muscle': 'abs',
'equipment': 'band',
'notes': None,
}
],
'routines': [
{
'id': 'seed-routine-04',
'user_id': 'john',
'name': 'Thursday: Shoulders',
'notes': "Imported from John's current routine. Default prescription is 3 sets of 10 reps. Weights are still TBD. Includes resistance band punch-out ab holds.",
'exercises': [
{'exercise_name': 'Shoulder Press', 'target_sets': 3, 'target_reps': '10'},
{'exercise_name': 'Arnold Press', 'target_sets': 3, 'target_reps': '10'},
{'exercise_name': 'Side Lateral Raise', 'target_sets': 3, 'target_reps': '10'},
{'exercise_name': 'Front Lateral Raise', 'target_sets': 3, 'target_reps': '10'},
{'exercise_name': 'Upright Row', 'target_sets': 3, 'target_reps': '10'},
{'exercise_name': 'Shoulder Shrugs', 'target_sets': 3, 'target_reps': '10'},
{'exercise_name': '7-7-7 Shoulder Press', 'target_sets': 3, 'target_reps': '10'},
{'exercise_name': 'Resistance Band Punch-Out Holds', 'target_sets': 3, 'target_reps': '10'},
],
}
],
'weight_entries': [],
'measurement_entries': [],
'workout_sessions': [],
}
)
)
client = make_client(tmp_path)
payload = client.get('/api/dashboard/summary').json()
shoulders = next(routine for routine in payload['routines'] if routine['name'] == 'Thursday: Shoulders')
assert shoulders['exercises'][6] == {
'exercise_name': '7-7-7 Shoulder Press',
'target_sets': 1,
'target_reps': '7-7-7',
}
assert shoulders['exercises'][7] == {
'exercise_name': 'Resistance Band Punch-Out Training',
'target_sets': 3,
'target_reps': '10',
}
assert 'resistance band punch-out training' in shoulders['notes']
def test_weight_form_submission_redirects_and_updates_dashboard(tmp_path: Path) -> None:
client = make_client(tmp_path)
response = client.post(
'/log/weight',
data={
'user_id': 'john',
'recorded_at': '2026-05-22T06:15:00',
'weight_lbs': '218.5',
'body_fat_percent': '23.7',
'notes': 'Pre-gym weigh-in',
},
follow_redirects=False,
)
assert response.status_code == 303
assert response.headers['location'] == '/'
summary = client.get('/api/dashboard/summary').json()
john = next(user for user in summary['users'] if user['id'] == 'john')
assert john['current_weight_lbs'] == 218.5
history = client.get('/api/history/john/weights').json()
assert history[0]['notes'] == 'Pre-gym weigh-in'
def test_template_and_routine_form_submissions_redirect_and_render_entries(tmp_path: Path) -> None:
client = make_client(tmp_path)
template_response = client.post(
'/templates',
data={
'name': 'Leg Press',
'primary_muscle': 'quads',
'equipment': 'machine',
'notes': 'Plate-loaded sled.',
},
follow_redirects=False,
)
assert template_response.status_code == 303
assert template_response.headers['location'] == '/'
routine_response = client.post(
'/routines',
data={
'user_id': 'john',
'name': 'Leg Day Draft',
'notes': 'Temporary until John sends the real split.',
'exercise_lines': 'Leg Press|4|10-12\nHamstring Curl|3|10-12|Slow negative',
},
follow_redirects=False,
)
assert routine_response.status_code == 303
assert routine_response.headers['location'] == '/'
page = client.get('/')
assert page.status_code == 200
assert 'Leg Press' in page.text
routines = client.get('/api/routines').json()
saved_routine = next(routine for routine in routines if routine['name'] == 'Leg Day Draft')
assert saved_routine['exercises'][0]['exercise_name'] == 'Leg Press'
assert saved_routine['exercises'][1]['notes'] == 'Slow negative'