diff --git a/core/tests.py b/core/tests.py index ac39d1b..e1a2214 100644 --- a/core/tests.py +++ b/core/tests.py @@ -3542,4 +3542,47 @@ class ManagerSalariedAbsenceExclusionTests(TestCase): def test_absenceedit_supervisor_excludes_fixed(self): from core.forms import AbsenceEditForm qs = AbsenceEditForm(user=self.sup).fields['worker'].queryset + self.assertIn(self.daily, qs) self.assertNotIn(self.mgr, qs) + + def test_absence_log_post_rejects_manager_id(self): + """Behavioral guard (matches Task 3's POST-level rigor): the + ModelMultipleChoiceField re-validates submitted worker ids against + its excluded-fixed queryset. POSTing a manager's id must be rejected + with a `workers` form error and create ZERO Absence rows for the + manager — which also prevents the underlying bug this feature exists + to stop: a paid manager-absence auto-creating a Bonus + PayrollAdjustment at daily_rate. A daily worker POSTed with the SAME + payload succeeds, proving the rejection is specific to the manager + (pay_type='fixed') and not a broken payload.""" + self.client.force_login(self.admin) + + # --- Manager id is rejected --- + resp = self.client.post(reverse('absence_log'), data={ + 'date': '2026-05-14', + 'reason': 'sick', + 'workers': [self.mgr.id], + 'notes': 'should be rejected', + }) + # Form re-rendered (not a redirect to success/confirm flow). + self.assertEqual(resp.status_code, 200) + self.assertIn('form', resp.context) + self.assertIn('workers', resp.context['form'].errors) + # No Absence row created for the manager — and therefore no + # auto-created Bonus PayrollAdjustment leaking at daily_rate. + self.assertEqual(Absence.objects.filter(worker=self.mgr).count(), 0) + self.assertEqual( + PayrollAdjustment.objects.filter(worker=self.mgr).count(), 0) + + # --- Positive control: daily worker, identical payload, succeeds --- + resp2 = self.client.post(reverse('absence_log'), data={ + 'date': '2026-05-14', + 'reason': 'sick', + 'workers': [self.daily.id], + 'notes': 'legit daily absence', + }) + # No conflicts → immediate create + redirect to the absence list. + self.assertRedirects( + resp2, reverse('absence_list'), fetch_redirect_response=False) + self.assertEqual(Absence.objects.filter(worker=self.daily).count(), 1) + self.assertEqual(Absence.objects.filter(worker=self.mgr).count(), 0)