from django.contrib.auth.models import User from django.test import TestCase from exercise.models import Exercise from generator.models import UserPreference from generator.services.exercise_selector import ExerciseSelector from registered_user.models import RegisteredUser class TestSidePairIntegrity(TestCase): def setUp(self): django_user = User.objects.create_user( username='side_pair_user', password='testpass123', ) registered_user = RegisteredUser.objects.create( user=django_user, first_name='Side', last_name='Pair', ) self.preference = UserPreference.objects.create( registered_user=registered_user, days_per_week=4, fitness_level=2, ) self.selector = ExerciseSelector(self.preference) def test_orphan_left_is_removed_and_replaced(self): left_only = Exercise.objects.create( name='Single Arm Row Left', side='Left', is_reps=True, is_duration=False, is_weight=True, movement_patterns='upper pull - horizontal, upper pull', muscle_groups='lats,upper back,biceps', difficulty_level='intermediate', ) filler_a = Exercise.objects.create( name='Chest Supported Row', side='', is_reps=True, is_duration=False, is_weight=True, movement_patterns='upper pull - horizontal, upper pull', muscle_groups='lats,upper back,biceps', difficulty_level='intermediate', ) filler_b = Exercise.objects.create( name='Face Pull', side='', is_reps=True, is_duration=False, is_weight=True, movement_patterns='upper pull, rear delt', muscle_groups='upper back,deltoids', difficulty_level='intermediate', ) selected = [left_only] base_qs = Exercise.objects.filter(pk__in=[left_only.pk, filler_a.pk, filler_b.pk]) enforced = self.selector._ensure_side_pair_integrity(selected, base_qs, count=1) self.assertEqual(len(enforced), 1) self.assertNotEqual(enforced[0].pk, left_only.pk) self.assertIn( enforced[0].pk, {filler_a.pk, filler_b.pk}, 'Orphan left-side movement should be replaced by a non-sided filler.', ) def test_left_right_pair_is_preserved(self): left_ex = Exercise.objects.create( name='Single Arm Press Left', side='Left', is_reps=True, is_duration=False, is_weight=True, movement_patterns='upper push - vertical, upper push', muscle_groups='deltoids,triceps', difficulty_level='intermediate', ) right_ex = Exercise.objects.create( name='Single Arm Press Right', side='Right', is_reps=True, is_duration=False, is_weight=True, movement_patterns='upper push - vertical, upper push', muscle_groups='deltoids,triceps', difficulty_level='intermediate', ) enforced = self.selector._ensure_side_pair_integrity( [left_ex, right_ex], Exercise.objects.filter(pk__in=[left_ex.pk, right_ex.pk]), count=2, ) enforced_ids = {ex.pk for ex in enforced} self.assertEqual(enforced_ids, {left_ex.pk, right_ex.pk}) def test_left_arm_right_arm_pair_is_preserved(self): left_ex = Exercise.objects.create( name='Single Arm Row', side='left_arm', is_reps=True, is_duration=False, is_weight=True, movement_patterns='upper pull - horizontal, upper pull', muscle_groups='lats,upper back,biceps', difficulty_level='intermediate', ) right_ex = Exercise.objects.create( name='Single Arm Row', side='right_arm', is_reps=True, is_duration=False, is_weight=True, movement_patterns='upper pull - horizontal, upper pull', muscle_groups='lats,upper back,biceps', difficulty_level='intermediate', ) paired = self.selector._pair_sided_exercises( [left_ex], Exercise.objects.filter(pk__in=[left_ex.pk, right_ex.pk]), ) paired_ids = {ex.pk for ex in paired} self.assertEqual(paired_ids, {left_ex.pk, right_ex.pk}) def test_orphan_left_arm_is_removed(self): left_ex = Exercise.objects.create( name='Single Arm Row', side='left_arm', is_reps=True, is_duration=False, is_weight=True, movement_patterns='upper pull - horizontal, upper pull', muscle_groups='lats,upper back,biceps', difficulty_level='intermediate', ) filler = Exercise.objects.create( name='Inverted Row', side='', is_reps=True, is_duration=False, is_weight=False, movement_patterns='upper pull - horizontal, upper pull', muscle_groups='lats,upper back,biceps', difficulty_level='intermediate', ) enforced = self.selector._ensure_side_pair_integrity( [left_ex], Exercise.objects.filter(pk__in=[left_ex.pk, filler.pk]), count=1, ) self.assertEqual(len(enforced), 1) self.assertEqual(enforced[0].pk, filler.pk) def test_try_hard_fetch_adds_opposite_side_partner_from_global_db(self): left_ex = Exercise.objects.create( name='Single Arm Lateral Raise Left', side='Left', is_reps=True, is_duration=False, is_weight=True, movement_patterns='upper push', muscle_groups='deltoids', difficulty_level='intermediate', ) right_ex = Exercise.objects.create( name='Single Arm Lateral Raise Right', side='Right', is_reps=True, is_duration=False, is_weight=True, movement_patterns='upper push', muscle_groups='deltoids', difficulty_level='intermediate', ) filler = Exercise.objects.create( name='Shoulder Tap', side='', is_reps=True, is_duration=False, is_weight=False, movement_patterns='upper push', muscle_groups='deltoids,core', difficulty_level='intermediate', ) # base_qs intentionally does not include right_ex to validate global fallback. base_qs = Exercise.objects.filter(pk__in=[left_ex.pk, filler.pk]) enforced = self.selector._ensure_side_pair_integrity( [left_ex, filler], base_qs, count=2, ) enforced_ids = {ex.pk for ex in enforced} self.assertIn(left_ex.pk, enforced_ids) self.assertIn(right_ex.pk, enforced_ids) self.assertNotIn(filler.pk, enforced_ids)