Deep audit identified 106 findings; 102 fixed, 4 deferred. Covers 8 areas: - Settings & deploy: env-gated DEBUG/SECRET_KEY, HTTPS headers, gunicorn, celery worker - Auth (registered_user): password write_only, request.data fixes, transaction safety, proper HTTP status codes - Workout app: IDOR protection, get_object_or_404, prefetch_related N+1 fixes, transaction.atomic - Video/scripts: path traversal sanitization, HLS trigger guard, auth on cache wipe - Models (exercise/equipment/muscle/superset): null-safe __str__, stable IDs, prefetch support - Generator views: helper for registered_user lookup, logger.exception, bulk_update, transaction wrapping - Generator core (rules/selector/generator): push-pull ratio, type affinity normalization, modality checks, side-pair exact match, word-boundary regex, equipment cache clearing - Generator services (plan_builder/analyzer/normalizer): transaction.atomic, muscle cache, bulk_update, glutes classification fix Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
137 lines
4.5 KiB
Python
137 lines
4.5 KiB
Python
from django.test import SimpleTestCase
|
|
|
|
from generator.services.workout_generation.entry_rules import (
|
|
apply_rep_volume_floor,
|
|
pick_reps_for_exercise,
|
|
working_rest_seconds,
|
|
)
|
|
from generator.services.workout_generation.focus import (
|
|
focus_key_for_exercise,
|
|
has_duplicate_focus,
|
|
)
|
|
from generator.services.workout_generation.modality import (
|
|
clamp_duration_bias,
|
|
plan_superset_modalities,
|
|
)
|
|
from generator.services.workout_generation.pattern_planning import (
|
|
merge_pattern_preferences,
|
|
rotated_muscle_subset,
|
|
working_position_label,
|
|
)
|
|
from generator.services.workout_generation.recovery import is_recovery_exercise
|
|
from generator.services.workout_generation.scaling import apply_fitness_scaling
|
|
from generator.services.workout_generation.section_builders import (
|
|
build_duration_entries,
|
|
build_section_superset,
|
|
section_exercise_count,
|
|
)
|
|
|
|
|
|
class _Rng:
|
|
def __init__(self, randint_values=None):
|
|
self._randint_values = list(randint_values or [])
|
|
|
|
def randint(self, low, high):
|
|
if self._randint_values:
|
|
return self._randint_values.pop(0)
|
|
return low
|
|
|
|
def shuffle(self, arr):
|
|
# Deterministic for tests.
|
|
return None
|
|
|
|
|
|
class _Ex:
|
|
def __init__(self, **kwargs):
|
|
self.__dict__.update(kwargs)
|
|
|
|
|
|
class TestWorkoutGenerationModules(SimpleTestCase):
|
|
def test_section_count_and_duration_entries(self):
|
|
rng = _Rng([6, 27, 31])
|
|
self.assertEqual(section_exercise_count('warmup', 1, rng=rng), 6)
|
|
|
|
exercises = [_Ex(name='A'), _Ex(name='B')]
|
|
entries = build_duration_entries(
|
|
exercises,
|
|
duration_min=20,
|
|
duration_max=40,
|
|
min_duration=20,
|
|
duration_multiple=5,
|
|
rng=rng,
|
|
)
|
|
self.assertEqual(entries[0]['duration'], 25)
|
|
self.assertEqual(entries[1]['duration'], 30)
|
|
section = build_section_superset('Warm Up', entries)
|
|
self.assertEqual(section['name'], 'Warm Up')
|
|
self.assertEqual(section['rounds'], 1)
|
|
|
|
def test_scaling_and_rest_floor(self):
|
|
params = {
|
|
'rep_min': 4,
|
|
'rep_max': 10,
|
|
'rounds': (3, 4),
|
|
'rest_between_rounds': 60,
|
|
}
|
|
scaling = {
|
|
1: {'rep_min_mult': 1.1, 'rep_max_mult': 1.2, 'rounds_adj': -1, 'rest_adj': 15},
|
|
2: {'rep_min_mult': 1.0, 'rep_max_mult': 1.0, 'rounds_adj': 0, 'rest_adj': 0},
|
|
}
|
|
out = apply_fitness_scaling(
|
|
params,
|
|
fitness_level=1,
|
|
scaling_config=scaling,
|
|
min_reps=6,
|
|
min_reps_strength=1,
|
|
is_strength=True,
|
|
)
|
|
self.assertGreaterEqual(out['rep_min'], 5)
|
|
self.assertEqual(working_rest_seconds(-5, 0), 15)
|
|
|
|
def test_modality_helpers(self):
|
|
self.assertEqual(clamp_duration_bias(0.9, (0.2, 0.6)), 0.6)
|
|
modalities = plan_superset_modalities(
|
|
num_supersets=4,
|
|
duration_bias=0.5,
|
|
duration_bias_range=(0.25, 0.5),
|
|
is_strength_workout=False,
|
|
rng=_Rng(),
|
|
)
|
|
self.assertEqual(len(modalities), 4)
|
|
self.assertTrue(any(modalities))
|
|
|
|
def test_pattern_and_focus_helpers(self):
|
|
self.assertEqual(working_position_label(0, 3), 'early')
|
|
self.assertEqual(working_position_label(1, 3), 'middle')
|
|
self.assertEqual(working_position_label(2, 3), 'late')
|
|
self.assertEqual(
|
|
merge_pattern_preferences(['upper pull', 'core'], ['core', 'lunge']),
|
|
['core'],
|
|
)
|
|
self.assertEqual(
|
|
rotated_muscle_subset(['a', 'b', 'c'], 1),
|
|
['b', 'c', 'a'],
|
|
)
|
|
|
|
curl_a = _Ex(name='Alternating Bicep Curls', movement_patterns='upper pull')
|
|
curl_b = _Ex(name='Bicep Curls', movement_patterns='upper pull')
|
|
self.assertEqual(focus_key_for_exercise(curl_a), 'bicep_curl')
|
|
self.assertTrue(has_duplicate_focus([curl_a, curl_b]))
|
|
|
|
def test_recovery_and_rep_selection(self):
|
|
stretch = _Ex(name='Supine Pec Stretch - T', movement_patterns='mobility - static')
|
|
self.assertTrue(is_recovery_exercise(stretch))
|
|
|
|
ex = _Ex(exercise_tier='primary')
|
|
reps = pick_reps_for_exercise(
|
|
ex,
|
|
{'rep_min': 8, 'rep_max': 12},
|
|
{'primary': (3, 6)},
|
|
rng=_Rng([5]),
|
|
)
|
|
self.assertEqual(reps, 5)
|
|
|
|
entries = [{'reps': 3}, {'duration': 30}]
|
|
apply_rep_volume_floor(entries, rounds=3, min_volume=12)
|
|
self.assertEqual(entries[0]['reps'], 4)
|