screen-locker/screen_locker/tests/test_duration_threshold_fixture.py
Krzysztof kuhy Rudnicki c63a02f9eb
Some checks are pending
pre-commit / pre-commit (push) Waiting to run
Tests / test (3.10) (push) Waiting to run
Tests / test (3.11) (push) Waiting to run
Tests / test (3.12) (push) Waiting to run
Tests / lock-popup-safety (push) Waiting to run
release-apk / release (APK) (push) Waiting to run
Add sync-status card and duration-threshold fixture
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 18:41:31 +02:00

88 lines
3.3 KiB
Python

"""Cross-language fixture test for the unified workout-duration gate.
The Dart app mirrors these constants in ``lib/models/manual_workout.dart``.
Each language's own round-trip test passes happily while the two sides
disagree, so the only thing that catches a drift is a shared literal both
read -- ``fixtures/duration_threshold.json``. The Dart twin of this file is
``test/models/duration_threshold_fixture_test.dart``; they must stay in step.
"""
from __future__ import annotations
import json
from pathlib import Path
import pytest
from screen_locker._constants import (
MIN_WORKOUT_DURATION_MINUTES,
WORKOUT_DURATION_ACCEPT_MINUTES,
WORKOUT_DURATION_LEEWAY_MINUTES,
)
from screen_locker._manual_workout import ManualWorkoutDraft, validate_manual_workout
FIXTURE = Path(__file__).parent / "fixtures" / "duration_threshold.json"
def _fixture() -> dict[str, object]:
"""Load the shared cross-language threshold fixture."""
return json.loads(FIXTURE.read_text(encoding="utf-8"))
def _draft(duration_minutes: int) -> ManualWorkoutDraft:
"""Build an otherwise-valid draft of exactly ``duration_minutes``."""
end_h, end_m = divmod(12 * 60 + duration_minutes, 60)
return ManualWorkoutDraft(
sport="other",
start_time="12:00",
end_time=f"{end_h:02d}:{end_m:02d}",
location_name="Home",
transport_method="From bed",
cost="0",
rpe=3,
went_well="Everything went smoothly today, no issues at all",
to_improve="Dumbbell bench press needs assistance exercises",
overall_feeling="Felt strong throughout the whole session today",
reservation_phone="none",
techniques_practiced="FBW",
warm_up_minutes="none",
pain_or_injury="none",
activity_type_other="Weightlifting",
activity_details="Dumbbell Lunge, Press, Row and Dumbbell Curl at home",
equipment="Dumbbells",
)
def test_constants_match_shared_fixture() -> None:
"""The Python constants equal the shared literal the Dart side also reads."""
data = _fixture()
assert data["advertised_minutes"] == MIN_WORKOUT_DURATION_MINUTES
assert data["leeway_minutes"] == WORKOUT_DURATION_LEEWAY_MINUTES
assert data["accept_minutes"] == WORKOUT_DURATION_ACCEPT_MINUTES
def test_accept_bar_is_derived_not_hardcoded() -> None:
"""The two bars cannot drift: the accept bar is advertised minus leeway."""
assert (
WORKOUT_DURATION_ACCEPT_MINUTES
== MIN_WORKOUT_DURATION_MINUTES - WORKOUT_DURATION_LEEWAY_MINUTES
)
@pytest.mark.parametrize("case", _fixture()["cases"])
def test_boundary_cases_from_fixture(case: dict[str, object]) -> None:
"""Every shared boundary case decides the same way on the Python side."""
error = validate_manual_workout(_draft(int(case["duration_minutes"])))
assert (error is None) is case["accepted"], (
f"{case['duration_minutes']} min should be "
f"{'accepted' if case['accepted'] else 'rejected'}, got {error!r}"
)
def test_rejection_message_advertises_40_never_the_real_cutoff() -> None:
"""The hidden accept bar must never reach the user's eyes."""
error = validate_manual_workout(_draft(10))
assert error is not None
assert str(MIN_WORKOUT_DURATION_MINUTES) in error
assert str(WORKOUT_DURATION_ACCEPT_MINUTES) not in error