Codebase hardening: 102 fixes across 35+ files
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>
This commit is contained in:
136
generator/tests/test_workout_generation_modules.py
Normal file
136
generator/tests/test_workout_generation_modules.py
Normal file
@@ -0,0 +1,136 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user