feat: add Doris Barbell v1 scaffold

This commit is contained in:
Fizzlepoof
2026-05-21 23:32:10 +00:00
parent d5cfdbe8b5
commit 9dc5a9a8a3
21 changed files with 2175 additions and 0 deletions

View File

@@ -0,0 +1,411 @@
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': 0,
'measurement_entries': 0,
'routines': 0,
'weight_entries': 0,
'workout_sessions': 0,
}
john = next(user for user in payload['users'] if user['id'] == 'john')
assert john['measurement_system'] == 'imperial'
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) == 1
assert payload[0]['name'] == 'Bench Press'
summary = client.get('/api/dashboard/summary').json()
assert summary['counts']['exercise_templates'] == 1
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) == 1
assert payload[0]['name'] == 'Push Day Placeholder'
summary = client.get('/api/dashboard/summary').json()
assert summary['counts']['routines'] == 1
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 'Recent workouts' in body
assert 'Current body metrics' in body
assert 'Log weight' in body
assert 'Add exercise template' in body
assert 'Build routine draft' in body
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
assert 'Leg Day Draft' in page.text
routines = client.get('/api/routines').json()
assert routines[0]['exercises'][0]['exercise_name'] == 'Leg Press'
assert routines[0]['exercises'][1]['notes'] == 'Slow negative'