from unittest.mock import patch from django.contrib.auth import get_user_model from django.test import TestCase from workflows.branding import get_company_email_domain from workflows.models import FormConditionalRuleConfig, FormCustomFieldConfig, FormFieldConfig, FormSectionConfig, OnboardingRequest class OnboardingFlowTests(TestCase): def setUp(self): self.company_domain = get_company_email_domain() user_model = get_user_model() self.user = user_model.objects.create_user( username='onboard_user', password='secret123', email=f'requester@{self.company_domain}', first_name='Mia', last_name='Beispiel', ) self.client.force_login(self.user) @patch('workflows.views.process_onboarding_request.delay') def test_onboarding_submit_persists_and_enqueues_task(self, mock_delay): payload = { 'first_name': 'Max', 'last_name': 'Mustermann', 'gender': 'herr', 'job_title': 'Consultant', 'department': 'IT-Service', 'work_email': f'max.mustermann@{self.company_domain}', 'contract_start': '2026-11-01', 'employment_type': 'unbefristet', 'group_mailboxes_required_choice': 'nein', 'additional_hardware_needed_choice': 'nein', 'additional_software_needed_choice': 'nein', 'additional_access_needed_choice': 'nein', 'successor_required_choice': 'nein', 'inherit_phone_number_choice': 'nein', 'agreement_confirm': 'on', } response = self.client.post('/onboarding/new/', payload, HTTP_HOST='localhost') self.assertEqual(response.status_code, 302) self.assertIn('/onboarding/new/?saved=1&id=', response['Location']) obj = OnboardingRequest.objects.get(work_email=f'max.mustermann@{self.company_domain}') self.assertEqual(obj.full_name, 'Max Mustermann') self.assertEqual(obj.onboarded_by_email, f'requester@{self.company_domain}') self.assertEqual(obj.onboarded_by_name, 'Mia Beispiel') mock_delay.assert_called_once_with(obj.id) @patch('workflows.views.process_onboarding_request.delay') def test_hidden_non_locked_field_does_not_block_submission(self, mock_delay): FormFieldConfig.objects.update_or_create( form_type='onboarding', field_name='department', defaults={'is_visible': False}, ) payload = { 'first_name': 'Nora', 'last_name': 'Neutral', 'gender': 'frau', 'job_title': 'Consultant', 'work_email': f'nora.neutral@{self.company_domain}', 'contract_start': '2026-11-01', 'employment_type': 'unbefristet', 'group_mailboxes_required_choice': 'nein', 'additional_hardware_needed_choice': 'nein', 'additional_software_needed_choice': 'nein', 'additional_access_needed_choice': 'nein', 'successor_required_choice': 'nein', 'inherit_phone_number_choice': 'nein', 'agreement_confirm': 'on', } response = self.client.post('/onboarding/new/', payload, HTTP_HOST='localhost') self.assertEqual(response.status_code, 302) obj = OnboardingRequest.objects.get(work_email=f'nora.neutral@{self.company_domain}') self.assertEqual(obj.department, '') mock_delay.assert_called_once_with(obj.id) @patch('workflows.views.process_onboarding_request.delay') def test_required_override_blocks_submission_when_field_is_missing(self, mock_delay): FormFieldConfig.objects.update_or_create( form_type='onboarding', field_name='job_title', defaults={'is_required': True}, ) payload = { 'first_name': 'Lina', 'last_name': 'Leer', 'gender': 'frau', 'department': 'IT-Service', 'work_email': f'lina.leer@{self.company_domain}', 'contract_start': '2026-11-01', 'employment_type': 'unbefristet', 'group_mailboxes_required_choice': 'nein', 'additional_hardware_needed_choice': 'nein', 'additional_software_needed_choice': 'nein', 'additional_access_needed_choice': 'nein', 'successor_required_choice': 'nein', 'inherit_phone_number_choice': 'nein', 'agreement_confirm': 'on', } response = self.client.post('/onboarding/new/', payload, HTTP_HOST='localhost') self.assertEqual(response.status_code, 200) self.assertContains(response, 'Dieses Feld ist zwingend erforderlich.') self.assertFalse(OnboardingRequest.objects.filter(work_email=f'lina.leer@{self.company_domain}').exists()) mock_delay.assert_not_called() @patch('workflows.views.process_onboarding_request.delay') def test_hidden_itsetup_section_is_removed_from_form_and_submission(self, mock_delay): FormSectionConfig.objects.update_or_create( form_type='onboarding', section_key='itsetup', defaults={'is_visible': False}, ) response = self.client.get('/onboarding/new/', HTTP_HOST='localhost') self.assertEqual(response.status_code, 200) self.assertNotContains(response, '3. IT-Setup') payload = { 'first_name': 'Nora', 'last_name': 'Neutral', 'gender': 'frau', 'job_title': 'Consultant', 'department': 'IT-Service', 'work_email': f'nora.section@{self.company_domain}', 'contract_start': '2026-11-01', 'employment_type': 'unbefristet', 'group_mailboxes_required_choice': 'nein', 'agreement_confirm': 'on', } submit_response = self.client.post('/onboarding/new/', payload, HTTP_HOST='localhost') self.assertEqual(submit_response.status_code, 302) self.assertTrue(OnboardingRequest.objects.filter(work_email=f'nora.section@{self.company_domain}').exists()) mock_delay.assert_called_once() def test_onboarding_page_renders_conditional_rules_payload(self): response = self.client.get('/onboarding/new/', HTTP_HOST='localhost') html = response.content.decode('utf-8') self.assertEqual(response.status_code, 200) self.assertIn('id="onboarding-conditional-rules"', html) self.assertIn('business-card-box', html) self.assertIn('employment-end-box', html) self.assertIn('data-conditional-target="business-card-box"', html) self.assertIn('data-conditional-target="phone-box"', html) def test_onboarding_page_uses_stored_conditional_rule_config(self): FormConditionalRuleConfig.objects.update_or_create( form_type='onboarding', target_key='employment-end-box', defaults={ 'is_active': True, 'clauses': [{'field': 'employment_type', 'operator': 'equals', 'value': 'unbefristet'}], }, ) response = self.client.get('/onboarding/new/', HTTP_HOST='localhost') html = response.content.decode('utf-8') self.assertEqual(response.status_code, 200) self.assertIn('employment-end-box', html) self.assertIn('"value": "unbefristet"', html) def test_onboarding_custom_field_uses_combined_order(self): FormCustomFieldConfig.objects.create( form_type='onboarding', field_key='office_location', section_key='stammdaten', sort_order=1, field_type='text', is_active=True, label='Bürostandort', ) FormFieldConfig.objects.update_or_create( form_type='onboarding', field_name='gender', defaults={'sort_order': 2, 'page_key': 'stammdaten'}, ) response = self.client.get('/onboarding/new/', HTTP_HOST='localhost') html = response.content.decode('utf-8') self.assertLess(html.index('Bürostandort'), html.index('Anrede')) @patch('workflows.views.process_onboarding_request.delay') def test_onboarding_custom_field_is_rendered_and_saved(self, mock_delay): FormCustomFieldConfig.objects.create( form_type='onboarding', field_key='office_location', section_key='stammdaten', sort_order=0, field_type='text', is_active=True, is_required=True, label='Bürostandort', ) response = self.client.get('/onboarding/new/', HTTP_HOST='localhost') self.assertContains(response, 'Bürostandort') payload = { 'first_name': 'Mara', 'last_name': 'Muster', 'gender': 'frau', 'job_title': 'Consultant', 'department': 'IT-Service', 'work_email': f'mara.muster@{self.company_domain}', 'contract_start': '2026-11-01', 'employment_type': 'unbefristet', 'group_mailboxes_required_choice': 'nein', 'additional_hardware_needed_choice': 'nein', 'additional_software_needed_choice': 'nein', 'additional_access_needed_choice': 'nein', 'successor_required_choice': 'nein', 'inherit_phone_number_choice': 'nein', 'custom__office_location': 'Berlin Mitte', 'agreement_confirm': 'on', } submit_response = self.client.post('/onboarding/new/', payload, HTTP_HOST='localhost') self.assertEqual(submit_response.status_code, 302) obj = OnboardingRequest.objects.get(work_email=f'mara.muster@{self.company_domain}') self.assertEqual(obj.custom_field_values, {'office_location': 'Berlin Mitte'}) mock_delay.assert_called_once_with(obj.id) @patch('workflows.views.process_onboarding_request.delay') def test_hidden_required_custom_field_does_not_block_submission(self, mock_delay): FormCustomFieldConfig.objects.create( form_type='onboarding', field_key='visitor_badge_name', section_key='stammdaten', sort_order=0, field_type='text', is_active=True, is_required=True, label='Besucherausweis', ) FormConditionalRuleConfig.objects.update_or_create( form_type='onboarding', target_key='custom__visitor_badge_name', defaults={ 'is_active': True, 'clauses': [{'field': 'order_business_cards', 'operator': 'checked', 'value': True}], }, ) response = self.client.get('/onboarding/new/', HTTP_HOST='localhost') html = response.content.decode('utf-8') self.assertIn('custom__visitor_badge_name', html) self.assertIn('"custom__visitor_badge_name"', html) payload = { 'first_name': 'Lea', 'last_name': 'Leicht', 'gender': 'frau', 'job_title': 'Consultant', 'department': 'IT-Service', 'work_email': f'lea.leicht@{self.company_domain}', 'contract_start': '2026-11-01', 'employment_type': 'unbefristet', 'group_mailboxes_required_choice': 'nein', 'additional_hardware_needed_choice': 'nein', 'additional_software_needed_choice': 'nein', 'additional_access_needed_choice': 'nein', 'successor_required_choice': 'nein', 'inherit_phone_number_choice': 'nein', 'agreement_confirm': 'on', } submit_response = self.client.post('/onboarding/new/', payload, HTTP_HOST='localhost') self.assertEqual(submit_response.status_code, 302) obj = OnboardingRequest.objects.get(work_email=f'lea.leicht@{self.company_domain}') self.assertEqual(obj.custom_field_values, {'visitor_badge_name': ''}) mock_delay.assert_called_once_with(obj.id) @patch('workflows.views.process_onboarding_request.delay') def test_visible_required_custom_field_blocks_submission(self, mock_delay): FormCustomFieldConfig.objects.create( form_type='onboarding', field_key='visitor_badge_name', section_key='stammdaten', sort_order=0, field_type='text', is_active=True, is_required=True, label='Besucherausweis', ) FormConditionalRuleConfig.objects.update_or_create( form_type='onboarding', target_key='custom__visitor_badge_name', defaults={ 'is_active': True, 'clauses': [{'field': 'order_business_cards', 'operator': 'checked', 'value': True}], }, ) payload = { 'first_name': 'Lia', 'last_name': 'Laut', 'gender': 'frau', 'job_title': 'Consultant', 'department': 'IT-Service', 'work_email': f'lia.laut@{self.company_domain}', 'contract_start': '2026-11-01', 'employment_type': 'unbefristet', 'order_business_cards': 'on', 'business_card_name': 'Lia Laut', 'business_card_title': 'Consultant', 'business_card_email': f'lia.laut@{self.company_domain}', 'business_card_phone': '030 123456', 'group_mailboxes_required_choice': 'nein', 'additional_hardware_needed_choice': 'nein', 'additional_software_needed_choice': 'nein', 'additional_access_needed_choice': 'nein', 'successor_required_choice': 'nein', 'inherit_phone_number_choice': 'nein', 'agreement_confirm': 'on', } response = self.client.post('/onboarding/new/', payload, HTTP_HOST='localhost') self.assertEqual(response.status_code, 200) self.assertContains(response, 'Besucherausweis') self.assertFalse(OnboardingRequest.objects.filter(work_email=f'lia.laut@{self.company_domain}').exists()) mock_delay.assert_not_called()