INITIAL COMMIT
This commit is contained in:
125
tests/test_workouts.py
Normal file
125
tests/test_workouts.py
Normal file
@ -0,0 +1,125 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date
|
||||
|
||||
from garmin_coach_clone.workouts import (
|
||||
build_dummy_cycling_workout,
|
||||
calendar_entry_date,
|
||||
calendar_entry_id,
|
||||
calendar_entry_name,
|
||||
clone_workout_payload,
|
||||
estimate_duration,
|
||||
existing_clone_names,
|
||||
find_generated_calendar_entry,
|
||||
find_generated_workout,
|
||||
generated_calendar_entries,
|
||||
generated_workouts,
|
||||
validate_workout_payload,
|
||||
)
|
||||
|
||||
|
||||
def test_dummy_workout_has_expected_duration_and_steps() -> None:
|
||||
workout = build_dummy_cycling_workout("Dummy")
|
||||
|
||||
assert workout["workoutName"] == "Dummy"
|
||||
assert workout["sportType"]["sportTypeKey"] == "cycling"
|
||||
assert estimate_duration(workout) == 14 * 60
|
||||
assert validate_workout_payload(workout) == []
|
||||
|
||||
|
||||
def test_clone_payload_strips_ids_and_sets_prefix() -> None:
|
||||
source = build_dummy_cycling_workout("Coach Original")
|
||||
source["workoutId"] = 123
|
||||
source["ownerId"] = 456
|
||||
source["workoutSegments"][0]["workoutSteps"][0]["stepId"] = 789
|
||||
|
||||
cloned = clone_workout_payload(source, date(2026, 6, 16), "GCClone")
|
||||
|
||||
assert cloned["workoutName"] == "GCClone 2026-06-16 Coach Original"
|
||||
assert "workoutId" not in cloned
|
||||
assert "ownerId" not in cloned
|
||||
assert "stepId" not in cloned["workoutSegments"][0]["workoutSteps"][0]
|
||||
assert validate_workout_payload(cloned) == []
|
||||
|
||||
|
||||
def test_existing_clone_names_filters_by_prefix_and_date() -> None:
|
||||
names = existing_clone_names(
|
||||
[
|
||||
{"workoutName": "GCClone 2026-06-16 Ride"},
|
||||
{"workoutName": "GCClone 2026-06-17 Ride"},
|
||||
{"workoutName": "Other 2026-06-16 Ride"},
|
||||
],
|
||||
date(2026, 6, 16),
|
||||
"GCClone",
|
||||
)
|
||||
|
||||
assert names == ["GCClone 2026-06-16 Ride"]
|
||||
|
||||
|
||||
def test_generated_workouts_filters_by_prefix() -> None:
|
||||
workouts = [
|
||||
{"workoutId": 1, "workoutName": "GCClone 2026-06-16 Ride"},
|
||||
{"workoutId": 2, "workoutName": "GCClone Probe Dummy 2026-06-16"},
|
||||
{"workoutId": 3, "workoutName": "Real Workout"},
|
||||
]
|
||||
|
||||
assert generated_workouts(workouts, "GCClone") == workouts[:2]
|
||||
assert find_generated_workout(workouts, "2", "GCClone") == workouts[1]
|
||||
assert find_generated_workout(workouts, "3", "GCClone") is None
|
||||
|
||||
|
||||
def test_generated_calendar_entries_handle_nested_workout_names() -> None:
|
||||
generated_entry = {
|
||||
"scheduledWorkoutId": 99,
|
||||
"scheduledDate": "2026-06-16",
|
||||
"workout": {"workoutName": "GCClone 2026-06-16 Ride"},
|
||||
}
|
||||
data = {
|
||||
"calendarItems": [
|
||||
generated_entry,
|
||||
{
|
||||
"scheduledWorkoutId": 100,
|
||||
"scheduledDate": "2026-06-16",
|
||||
"workout": {"workoutName": "Real Workout"},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
entries = generated_calendar_entries(data, "GCClone")
|
||||
|
||||
assert entries == [generated_entry]
|
||||
assert find_generated_calendar_entry(data, "99", "GCClone") == generated_entry
|
||||
assert find_generated_calendar_entry(data, "100", "GCClone") is None
|
||||
assert calendar_entry_id(generated_entry) == "99"
|
||||
assert calendar_entry_date(generated_entry) == "2026-06-16"
|
||||
assert calendar_entry_name(generated_entry) == "GCClone 2026-06-16 Ride"
|
||||
|
||||
|
||||
def test_validate_workout_payload_rejects_missing_segments() -> None:
|
||||
errors = validate_workout_payload(
|
||||
{
|
||||
"workoutName": "Broken",
|
||||
"sportType": {"sportTypeKey": "cycling"},
|
||||
"workoutSegments": [],
|
||||
}
|
||||
)
|
||||
|
||||
assert "workoutSegments must be a non-empty list" in errors
|
||||
|
||||
|
||||
def test_validate_workout_payload_rejects_non_cycling() -> None:
|
||||
workout = build_dummy_cycling_workout("Broken")
|
||||
workout["sportType"] = {"sportTypeKey": "running"}
|
||||
|
||||
errors = validate_workout_payload(workout)
|
||||
|
||||
assert "sportType must be cycling, got running" in errors
|
||||
|
||||
|
||||
def test_validate_workout_payload_rejects_missing_step_target() -> None:
|
||||
workout = build_dummy_cycling_workout("Broken")
|
||||
workout["workoutSegments"][0]["workoutSteps"][0].pop("targetType")
|
||||
|
||||
errors = validate_workout_payload(workout)
|
||||
|
||||
assert "workoutSegments[1].workoutSteps[1].targetType is missing" in errors
|
||||
Reference in New Issue
Block a user