snapshot: preserve role-aware notification preferences and operational alerts
This commit is contained in:
@@ -3,6 +3,7 @@ from django.test import Client, TestCase
|
||||
from django.utils import timezone
|
||||
|
||||
from workflows.models import UserProfile
|
||||
from workflows.roles import ROLE_PLATFORM_OWNER, assign_user_role
|
||||
from workflows.totp import generate_totp_token
|
||||
|
||||
|
||||
@@ -32,6 +33,55 @@ class AccountUISmokeTests(TestCase):
|
||||
def test_user_profile_is_created_automatically(self):
|
||||
self.assertTrue(UserProfile.objects.filter(user=self.user).exists())
|
||||
|
||||
def test_notification_preferences_can_be_updated(self):
|
||||
response = self.client.post(
|
||||
'/account/',
|
||||
{
|
||||
'account_form': 'notification_preferences',
|
||||
'onboarding_success': 'on',
|
||||
'onboarding_failure': '',
|
||||
'offboarding_success': '',
|
||||
'offboarding_failure': 'on',
|
||||
},
|
||||
HTTP_HOST='localhost',
|
||||
follow=True,
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
profile = UserProfile.objects.get(user=self.user)
|
||||
self.assertEqual(
|
||||
profile.notification_preferences,
|
||||
{
|
||||
'onboarding_success': True,
|
||||
'onboarding_failure': False,
|
||||
'offboarding_success': False,
|
||||
'offboarding_failure': True,
|
||||
'backup_success': True,
|
||||
'backup_failure': True,
|
||||
'welcome_email_success': False,
|
||||
'welcome_email_failure': False,
|
||||
'trial_alerts': True,
|
||||
'system_alerts': True,
|
||||
},
|
||||
)
|
||||
|
||||
def test_staff_account_notifications_hide_admin_only_categories(self):
|
||||
response = self.client.get('/account/', HTTP_HOST='localhost')
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertNotContains(response, 'Backup erfolgreich')
|
||||
self.assertNotContains(response, 'Trial-Hinweise')
|
||||
self.assertNotContains(response, 'System-Hinweise')
|
||||
self.assertContains(response, 'Welcome E-Mail erfolgreich')
|
||||
|
||||
def test_platform_owner_sees_all_notification_categories(self):
|
||||
assign_user_role(self.user, ROLE_PLATFORM_OWNER)
|
||||
response = self.client.get('/account/', HTTP_HOST='localhost')
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, 'Backup erfolgreich')
|
||||
self.assertContains(response, 'Trial-Hinweise')
|
||||
self.assertContains(response, 'System-Hinweise')
|
||||
|
||||
def test_account_profile_details_can_be_updated(self):
|
||||
response = self.client.post(
|
||||
'/account/',
|
||||
@@ -106,23 +156,26 @@ class AccountUISmokeTests(TestCase):
|
||||
{'username': 'profile-user', 'password': 'secret-12345'},
|
||||
HTTP_HOST='localhost',
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(response['Location'], '/accounts/login/totp/')
|
||||
|
||||
response = client.get('/accounts/login/totp/', HTTP_HOST='localhost')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, 'TOTP-Code')
|
||||
self.assertContains(response, 'Recovery-Code verwenden')
|
||||
|
||||
token = generate_totp_token(profile.totp_secret, int(timezone.now().timestamp()))
|
||||
response = client.post(
|
||||
'/accounts/login/',
|
||||
{'username': 'profile-user', 'password': 'secret-12345', 'otp_code': token},
|
||||
'/accounts/login/totp/',
|
||||
{'otp_code': token},
|
||||
HTTP_HOST='localhost',
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
client = Client()
|
||||
response = client.post(
|
||||
'/accounts/login/',
|
||||
{'username': 'profile-user', 'password': 'secret-12345', 'recovery_code': 'ABCDE-12345'},
|
||||
HTTP_HOST='localhost',
|
||||
)
|
||||
first_step = client.post('/accounts/login/', {'username': 'profile-user', 'password': 'secret-12345'}, HTTP_HOST='localhost')
|
||||
self.assertEqual(first_step.status_code, 302)
|
||||
response = client.post('/accounts/login/totp/', {'recovery_code': 'ABCDE-12345'}, HTTP_HOST='localhost')
|
||||
self.assertEqual(response.status_code, 302)
|
||||
profile.refresh_from_db()
|
||||
self.assertEqual(profile.totp_recovery_codes, [])
|
||||
|
||||
@@ -6,6 +6,7 @@ from django.contrib.auth import get_user_model
|
||||
from django.test import TestCase, override_settings
|
||||
from django.utils import timezone
|
||||
|
||||
from workflows.branding import get_company_email_domain
|
||||
from workflows.models import EmployeeProfile, NotificationTemplate, OffboardingRequest, OnboardingRequest, ScheduledWelcomeEmail
|
||||
from workflows.tasks import process_onboarding_request, send_scheduled_welcome_email
|
||||
|
||||
@@ -13,11 +14,12 @@ from workflows.tasks import process_onboarding_request, send_scheduled_welcome_e
|
||||
@override_settings(PDF_OUTPUT_DIR=Path('/tmp/onoff_test_pdfs'))
|
||||
class BilingualSmokeTests(TestCase):
|
||||
def setUp(self):
|
||||
self.company_domain = get_company_email_domain()
|
||||
user_model = get_user_model()
|
||||
self.user = user_model.objects.create_user(
|
||||
username='bilingual_user',
|
||||
password='secret123',
|
||||
email='requester@tub.co',
|
||||
email=f'requester@{self.company_domain}',
|
||||
first_name='Mia',
|
||||
last_name='Beispiel',
|
||||
)
|
||||
@@ -28,7 +30,7 @@ class BilingualSmokeTests(TestCase):
|
||||
last_name='Beispiel',
|
||||
department='IT-Service',
|
||||
job_title='Engineer',
|
||||
work_email='lara.beispiel@tub.co',
|
||||
work_email=f'lara.beispiel@{self.company_domain}',
|
||||
)
|
||||
|
||||
@patch('workflows.views.process_onboarding_request.delay')
|
||||
@@ -39,7 +41,7 @@ class BilingualSmokeTests(TestCase):
|
||||
'gender': 'herr',
|
||||
'job_title': 'Consultant',
|
||||
'department': 'IT-Service',
|
||||
'work_email': 'max.mustermann@tub.co',
|
||||
'work_email': f'max.mustermann@{self.company_domain}',
|
||||
'contract_start': '2026-11-01',
|
||||
'employment_type': 'unbefristet',
|
||||
'group_mailboxes_required_choice': 'nein',
|
||||
@@ -54,7 +56,7 @@ class BilingualSmokeTests(TestCase):
|
||||
response = self.client.post('/onboarding/new/', payload, HTTP_HOST='localhost', HTTP_ACCEPT_LANGUAGE='en')
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
obj = OnboardingRequest.objects.get(work_email='max.mustermann@tub.co')
|
||||
obj = OnboardingRequest.objects.get(work_email=f'max.mustermann@{self.company_domain}')
|
||||
self.assertEqual(obj.preferred_language, 'en')
|
||||
mock_delay.assert_called_once_with(obj.id)
|
||||
|
||||
@@ -66,7 +68,7 @@ class BilingualSmokeTests(TestCase):
|
||||
'gender': 'frau',
|
||||
'job_title': 'Consultant',
|
||||
'department': 'IT-Service',
|
||||
'work_email': 'erika.muster@tub.co',
|
||||
'work_email': f'erika.muster@{self.company_domain}',
|
||||
'contract_start': '2026-11-02',
|
||||
'employment_type': 'unbefristet',
|
||||
'group_mailboxes_required_choice': 'nein',
|
||||
@@ -81,7 +83,7 @@ class BilingualSmokeTests(TestCase):
|
||||
response = self.client.post('/onboarding/new/', payload, HTTP_HOST='localhost', HTTP_ACCEPT_LANGUAGE='de')
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
obj = OnboardingRequest.objects.get(work_email='erika.muster@tub.co')
|
||||
obj = OnboardingRequest.objects.get(work_email=f'erika.muster@{self.company_domain}')
|
||||
self.assertEqual(obj.preferred_language, 'de')
|
||||
mock_delay.assert_called_once_with(obj.id)
|
||||
|
||||
@@ -140,10 +142,10 @@ class BilingualSmokeTests(TestCase):
|
||||
gender='herr',
|
||||
job_title='Engineer',
|
||||
department='IT-Service',
|
||||
work_email='english.person@tub.co',
|
||||
work_email=f'english.person@{self.company_domain}',
|
||||
contract_start=date(2026, 11, 1),
|
||||
employment_type='unbefristet',
|
||||
onboarded_by_email='requester@tub.co',
|
||||
onboarded_by_email=f'requester@{self.company_domain}',
|
||||
onboarded_by_name='Mia Beispiel',
|
||||
agreement='accepted',
|
||||
preferred_language='en',
|
||||
@@ -172,16 +174,16 @@ class BilingualSmokeTests(TestCase):
|
||||
gender='frau',
|
||||
job_title='Manager',
|
||||
department='IT-Service',
|
||||
work_email='welcome.person@tub.co',
|
||||
work_email=f'welcome.person@{self.company_domain}',
|
||||
contract_start=date(2026, 11, 1),
|
||||
employment_type='unbefristet',
|
||||
onboarded_by_email='requester@tub.co',
|
||||
onboarded_by_email=f'requester@{self.company_domain}',
|
||||
agreement='accepted',
|
||||
preferred_language='en',
|
||||
)
|
||||
scheduled = ScheduledWelcomeEmail.objects.create(
|
||||
onboarding_request=onboarding,
|
||||
recipient_email='welcome.person@tub.co',
|
||||
recipient_email=f'welcome.person@{self.company_domain}',
|
||||
send_at=timezone.now(),
|
||||
status='scheduled',
|
||||
)
|
||||
|
||||
352
backend/workflows/tests/test_notifications.py
Normal file
352
backend/workflows/tests/test_notifications.py
Normal file
@@ -0,0 +1,352 @@
|
||||
from pathlib import Path
|
||||
from datetime import date
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import Client, TestCase, override_settings
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
|
||||
from workflows.models import OffboardingRequest, OnboardingRequest, ScheduledWelcomeEmail, UserNotification, UserProfile
|
||||
from workflows.roles import ROLE_PLATFORM_OWNER, assign_user_role
|
||||
from workflows.tasks import process_offboarding_request, process_onboarding_request, send_scheduled_welcome_email
|
||||
|
||||
|
||||
@override_settings(PDF_OUTPUT_DIR=Path('/tmp/onoff_test_pdfs'))
|
||||
class NotificationFlowTests(TestCase):
|
||||
def setUp(self):
|
||||
user_model = get_user_model()
|
||||
self.requester = user_model.objects.create_user(
|
||||
username='notify_user',
|
||||
password='secret123',
|
||||
email='requester@workdock.de',
|
||||
first_name='Nina',
|
||||
last_name='Requester',
|
||||
)
|
||||
|
||||
@patch('workflows.tasks._apply_notification_rules')
|
||||
@patch('workflows.tasks._schedule_welcome_email')
|
||||
@patch('workflows.tasks.upload_to_nextcloud')
|
||||
@patch('workflows.tasks._send_templated_email')
|
||||
@patch('workflows.tasks._generate_onboarding_pdf')
|
||||
def test_onboarding_success_creates_success_notification(
|
||||
self,
|
||||
mock_generate_pdf,
|
||||
mock_send_templated_email,
|
||||
mock_upload,
|
||||
mock_schedule,
|
||||
mock_rules,
|
||||
):
|
||||
pdf_path = Path('/tmp/onoff_test_pdfs/onboarding_letter_Nina_Notify.pdf')
|
||||
pdf_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
pdf_path.write_bytes(b'%PDF-1.4\n%test\n')
|
||||
mock_generate_pdf.return_value = pdf_path
|
||||
request_obj = OnboardingRequest.objects.create(
|
||||
full_name='Nina Notify',
|
||||
gender='frau',
|
||||
job_title='Engineer',
|
||||
department='IT',
|
||||
work_email='nina.notify@workdock.de',
|
||||
contract_start=date(2026, 11, 1),
|
||||
employment_type='unbefristet',
|
||||
onboarded_by_email=self.requester.email,
|
||||
onboarded_by_name='Nina Requester',
|
||||
agreement='accepted',
|
||||
)
|
||||
|
||||
process_onboarding_request(request_obj.id)
|
||||
|
||||
notification = UserNotification.objects.get(user=self.requester)
|
||||
self.assertEqual(notification.level, UserNotification.LEVEL_SUCCESS)
|
||||
self.assertIn('Onboarding abgeschlossen', notification.title)
|
||||
self.assertEqual(notification.link_url, '/requests/')
|
||||
mock_upload.assert_called_once_with(pdf_path, pdf_path.name)
|
||||
|
||||
@patch('workflows.tasks._generate_onboarding_pdf', side_effect=RuntimeError('PDF kaputt'))
|
||||
def test_onboarding_failure_creates_error_notification(self, mock_generate_pdf):
|
||||
request_obj = OnboardingRequest.objects.create(
|
||||
full_name='Lara Broken',
|
||||
gender='frau',
|
||||
job_title='Engineer',
|
||||
department='IT',
|
||||
work_email='lara.broken@workdock.de',
|
||||
contract_start=date(2026, 11, 1),
|
||||
employment_type='unbefristet',
|
||||
onboarded_by_email=self.requester.email,
|
||||
onboarded_by_name='Nina Requester',
|
||||
agreement='accepted',
|
||||
)
|
||||
|
||||
with self.assertRaises(RuntimeError):
|
||||
process_onboarding_request(request_obj.id)
|
||||
|
||||
notification = UserNotification.objects.get(user=self.requester)
|
||||
self.assertEqual(notification.level, UserNotification.LEVEL_ERROR)
|
||||
self.assertIn('Onboarding fehlgeschlagen', notification.title)
|
||||
self.assertIn('PDF kaputt', notification.body)
|
||||
|
||||
@patch('workflows.tasks._apply_notification_rules')
|
||||
@patch('workflows.tasks.upload_to_nextcloud')
|
||||
@patch('workflows.tasks._send_templated_email')
|
||||
@patch('workflows.tasks._generate_offboarding_pdf')
|
||||
def test_offboarding_success_creates_success_notification(
|
||||
self,
|
||||
mock_generate_pdf,
|
||||
mock_send_templated_email,
|
||||
mock_upload,
|
||||
mock_rules,
|
||||
):
|
||||
pdf_path = Path('/tmp/onoff_test_pdfs/offboarding_letter_Nina_Notify.pdf')
|
||||
pdf_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
pdf_path.write_bytes(b'%PDF-1.4\n%test\n')
|
||||
mock_generate_pdf.return_value = pdf_path
|
||||
request_obj = OffboardingRequest.objects.create(
|
||||
full_name='Nina Notify',
|
||||
work_email='nina.notify@workdock.de',
|
||||
department='IT',
|
||||
job_title='Engineer',
|
||||
last_working_day=date(2026, 12, 31),
|
||||
requested_by_email=self.requester.email,
|
||||
requested_by_name='Nina Requester',
|
||||
)
|
||||
|
||||
process_offboarding_request(request_obj.id)
|
||||
|
||||
notification = UserNotification.objects.get(user=self.requester)
|
||||
self.assertEqual(notification.level, UserNotification.LEVEL_SUCCESS)
|
||||
self.assertIn('Offboarding abgeschlossen', notification.title)
|
||||
self.assertEqual(notification.link_url, '/requests/')
|
||||
mock_upload.assert_called_once_with(pdf_path, pdf_path.name)
|
||||
|
||||
@patch('workflows.tasks._apply_notification_rules')
|
||||
@patch('workflows.tasks._schedule_welcome_email')
|
||||
@patch('workflows.tasks.upload_to_nextcloud')
|
||||
@patch('workflows.tasks._send_templated_email')
|
||||
@patch('workflows.tasks._generate_onboarding_pdf')
|
||||
def test_onboarding_success_notification_respects_user_preferences(
|
||||
self,
|
||||
mock_generate_pdf,
|
||||
mock_send_templated_email,
|
||||
mock_upload,
|
||||
mock_schedule,
|
||||
mock_rules,
|
||||
):
|
||||
profile, _ = UserProfile.objects.get_or_create(user=self.requester)
|
||||
profile.notification_preferences = {
|
||||
'onboarding_success': False,
|
||||
'onboarding_failure': True,
|
||||
'offboarding_success': True,
|
||||
'offboarding_failure': True,
|
||||
}
|
||||
profile.save(update_fields=['notification_preferences', 'updated_at'])
|
||||
pdf_path = Path('/tmp/onoff_test_pdfs/onboarding_letter_Pref_Off.pdf')
|
||||
pdf_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
pdf_path.write_bytes(b'%PDF-1.4\n%test\n')
|
||||
mock_generate_pdf.return_value = pdf_path
|
||||
request_obj = OnboardingRequest.objects.create(
|
||||
full_name='Pref Off',
|
||||
gender='frau',
|
||||
job_title='Engineer',
|
||||
department='IT',
|
||||
work_email='pref.off@workdock.de',
|
||||
contract_start=date(2026, 11, 1),
|
||||
employment_type='unbefristet',
|
||||
onboarded_by_email=self.requester.email,
|
||||
onboarded_by_name='Nina Requester',
|
||||
agreement='accepted',
|
||||
)
|
||||
|
||||
process_onboarding_request(request_obj.id)
|
||||
|
||||
self.assertFalse(UserNotification.objects.filter(user=self.requester).exists())
|
||||
|
||||
|
||||
class NotificationHeaderTests(TestCase):
|
||||
def setUp(self):
|
||||
user_model = get_user_model()
|
||||
self.user = user_model.objects.create_user(
|
||||
username='notify_header',
|
||||
password='secret123',
|
||||
email='notify.header@workdock.de',
|
||||
)
|
||||
self.client.force_login(self.user)
|
||||
|
||||
def test_mark_notification_read_marks_single_entry(self):
|
||||
notification = UserNotification.objects.create(
|
||||
user=self.user,
|
||||
title='Backup fehlgeschlagen',
|
||||
body='Bitte prüfen.',
|
||||
level=UserNotification.LEVEL_ERROR,
|
||||
link_url='/requests/',
|
||||
)
|
||||
|
||||
response = self.client.post(
|
||||
reverse('mark_notification_read', args=[notification.id]),
|
||||
{'next': '/'},
|
||||
HTTP_HOST='localhost',
|
||||
)
|
||||
|
||||
notification.refresh_from_db()
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(response['Location'], '/')
|
||||
self.assertIsNotNone(notification.read_at)
|
||||
|
||||
def test_mark_all_notifications_read_marks_unread_items(self):
|
||||
first = UserNotification.objects.create(user=self.user, title='Erfolg', level=UserNotification.LEVEL_SUCCESS)
|
||||
second = UserNotification.objects.create(user=self.user, title='Fehler', level=UserNotification.LEVEL_ERROR)
|
||||
|
||||
response = self.client.post(
|
||||
reverse('mark_all_notifications_read'),
|
||||
{'next': '/requests/'},
|
||||
HTTP_HOST='localhost',
|
||||
)
|
||||
|
||||
first.refresh_from_db()
|
||||
second.refresh_from_db()
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(response['Location'], '/requests/')
|
||||
self.assertIsNotNone(first.read_at)
|
||||
self.assertIsNotNone(second.read_at)
|
||||
|
||||
|
||||
class OperationalNotificationTests(TestCase):
|
||||
def setUp(self):
|
||||
user_model = get_user_model()
|
||||
self.user = user_model.objects.create_user(
|
||||
username='ops_notify',
|
||||
password='secret123',
|
||||
email='ops.notify@workdock.de',
|
||||
)
|
||||
assign_user_role(self.user, ROLE_PLATFORM_OWNER)
|
||||
self.client = Client(HTTP_HOST='localhost')
|
||||
self.client.force_login(self.user)
|
||||
|
||||
@patch('workflows.views.create_backup_bundle')
|
||||
def test_backup_success_creates_notification(self, mock_create_backup_bundle):
|
||||
mock_create_backup_bundle.return_value = {'name': 'backup_20260327_101010', 'path': '/tmp/backup'}
|
||||
|
||||
response = self.client.post(reverse('create_backup_from_admin'))
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
notification = UserNotification.objects.get(user=self.user)
|
||||
self.assertEqual(notification.level, UserNotification.LEVEL_SUCCESS)
|
||||
self.assertIn('Backup erstellt', notification.title)
|
||||
|
||||
def test_backup_success_respects_preferences(self):
|
||||
profile = UserProfile.objects.get(user=self.user)
|
||||
profile.notification_preferences = {
|
||||
'onboarding_success': True,
|
||||
'onboarding_failure': True,
|
||||
'offboarding_success': True,
|
||||
'offboarding_failure': True,
|
||||
'backup_success': False,
|
||||
'backup_failure': True,
|
||||
'trial_alerts': True,
|
||||
'system_alerts': True,
|
||||
}
|
||||
profile.save(update_fields=['notification_preferences', 'updated_at'])
|
||||
with patch('workflows.views.create_backup_bundle', return_value={'name': 'backup_20260327_111111', 'path': '/tmp/backup'}):
|
||||
response = self.client.post(reverse('create_backup_from_admin'))
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertFalse(UserNotification.objects.filter(user=self.user).exists())
|
||||
|
||||
def test_trial_warning_creates_notification(self):
|
||||
response = self.client.post(
|
||||
reverse('save_portal_trial_config'),
|
||||
{
|
||||
'is_trial_mode': 'on',
|
||||
'trial_started_at': '2026-03-27T10:00',
|
||||
'trial_expires_at': '2026-03-30T10:00',
|
||||
'restrict_production_integrations': 'on',
|
||||
'auto_cleanup_enabled': 'on',
|
||||
'trial_banner_text': 'Trial läuft',
|
||||
'trial_banner_text_en': 'Trial running',
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
notification = UserNotification.objects.get(user=self.user)
|
||||
self.assertEqual(notification.level, UserNotification.LEVEL_WARNING)
|
||||
self.assertIn('Trial läuft bald ab', notification.title)
|
||||
|
||||
|
||||
@override_settings(PDF_OUTPUT_DIR=Path('/tmp/onoff_test_pdfs'))
|
||||
class WelcomeEmailNotificationTests(TestCase):
|
||||
def setUp(self):
|
||||
user_model = get_user_model()
|
||||
self.requester = user_model.objects.create_user(
|
||||
username='welcome_notify_user',
|
||||
password='secret123',
|
||||
email='welcome.requester@workdock.de',
|
||||
)
|
||||
self.onboarding = OnboardingRequest.objects.create(
|
||||
full_name='Welcome Notify',
|
||||
gender='frau',
|
||||
job_title='Engineer',
|
||||
department='IT',
|
||||
work_email='welcome.notify@workdock.de',
|
||||
contract_start=date(2026, 11, 1),
|
||||
employment_type='unbefristet',
|
||||
onboarded_by_email=self.requester.email,
|
||||
onboarded_by_name='Welcome Requester',
|
||||
agreement='accepted',
|
||||
)
|
||||
|
||||
@patch('workflows.tasks._send_templated_email')
|
||||
def test_welcome_email_success_creates_notification(self, mock_send_templated_email):
|
||||
scheduled = ScheduledWelcomeEmail.objects.create(
|
||||
onboarding_request=self.onboarding,
|
||||
recipient_email='welcome.notify@workdock.de',
|
||||
send_at=timezone.now() - timezone.timedelta(minutes=1),
|
||||
status='scheduled',
|
||||
)
|
||||
|
||||
send_scheduled_welcome_email(scheduled.id, True)
|
||||
|
||||
notification = UserNotification.objects.get(user=self.requester)
|
||||
self.assertEqual(notification.level, UserNotification.LEVEL_SUCCESS)
|
||||
self.assertIn('Welcome E-Mail gesendet', notification.title)
|
||||
|
||||
@patch('workflows.tasks._send_templated_email', side_effect=RuntimeError('SMTP broken'))
|
||||
def test_welcome_email_failure_creates_notification(self, mock_send_templated_email):
|
||||
scheduled = ScheduledWelcomeEmail.objects.create(
|
||||
onboarding_request=self.onboarding,
|
||||
recipient_email='welcome.notify@workdock.de',
|
||||
send_at=timezone.now() - timezone.timedelta(minutes=1),
|
||||
status='scheduled',
|
||||
)
|
||||
|
||||
with self.assertRaises(RuntimeError):
|
||||
send_scheduled_welcome_email(scheduled.id, True)
|
||||
|
||||
notification = UserNotification.objects.get(user=self.requester)
|
||||
self.assertEqual(notification.level, UserNotification.LEVEL_ERROR)
|
||||
self.assertIn('Welcome E-Mail fehlgeschlagen', notification.title)
|
||||
|
||||
@patch('workflows.tasks._send_templated_email')
|
||||
def test_welcome_email_success_respects_preferences(self, mock_send_templated_email):
|
||||
profile, _ = UserProfile.objects.get_or_create(user=self.requester)
|
||||
profile.notification_preferences = {
|
||||
'onboarding_success': True,
|
||||
'onboarding_failure': True,
|
||||
'offboarding_success': True,
|
||||
'offboarding_failure': True,
|
||||
'backup_success': True,
|
||||
'backup_failure': True,
|
||||
'welcome_email_success': False,
|
||||
'welcome_email_failure': True,
|
||||
'trial_alerts': True,
|
||||
'system_alerts': True,
|
||||
}
|
||||
profile.save(update_fields=['notification_preferences', 'updated_at'])
|
||||
scheduled = ScheduledWelcomeEmail.objects.create(
|
||||
onboarding_request=self.onboarding,
|
||||
recipient_email='welcome.notify@workdock.de',
|
||||
send_at=timezone.now() - timezone.timedelta(minutes=1),
|
||||
status='scheduled',
|
||||
)
|
||||
|
||||
send_scheduled_welcome_email(scheduled.id, True)
|
||||
|
||||
self.assertFalse(UserNotification.objects.filter(user=self.requester).exists())
|
||||
@@ -3,16 +3,18 @@ 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 EmployeeProfile, OffboardingRequest
|
||||
|
||||
|
||||
class OffboardingFlowTests(TestCase):
|
||||
def setUp(self):
|
||||
self.company_domain = get_company_email_domain()
|
||||
user_model = get_user_model()
|
||||
self.user = user_model.objects.create_user(
|
||||
username='offboard_user',
|
||||
password='secret123',
|
||||
email='operator@tub.co',
|
||||
email=f'operator@{self.company_domain}',
|
||||
first_name='Nina',
|
||||
last_name='Admin',
|
||||
)
|
||||
@@ -23,7 +25,7 @@ class OffboardingFlowTests(TestCase):
|
||||
last_name='Beispiel',
|
||||
department='IT-Service',
|
||||
job_title='Engineer',
|
||||
work_email='lara.beispiel@tub.co',
|
||||
work_email=f'lara.beispiel@{self.company_domain}',
|
||||
)
|
||||
|
||||
def test_offboarding_prefill_from_profile(self):
|
||||
@@ -32,7 +34,7 @@ class OffboardingFlowTests(TestCase):
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn('value="Lara Beispiel"', html)
|
||||
self.assertIn('value="lara.beispiel@tub.co"', html)
|
||||
self.assertIn(f'value="lara.beispiel@{self.company_domain}"', html)
|
||||
self.assertIn('value="Engineer"', html)
|
||||
|
||||
@patch('workflows.views.process_offboarding_request.delay')
|
||||
@@ -54,6 +56,6 @@ class OffboardingFlowTests(TestCase):
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
obj = OffboardingRequest.objects.get(work_email=self.profile.work_email)
|
||||
self.assertEqual(obj.requested_by_email, 'operator@tub.co')
|
||||
self.assertEqual(obj.requested_by_email, f'operator@{self.company_domain}')
|
||||
self.assertEqual(obj.requested_by_name, 'Nina Admin')
|
||||
mock_delay.assert_called_once_with(obj.id)
|
||||
|
||||
@@ -3,16 +3,18 @@ from unittest.mock import patch
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import TestCase
|
||||
|
||||
from workflows.models import OnboardingRequest
|
||||
from workflows.branding import get_company_email_domain
|
||||
from workflows.models import FormFieldConfig, 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='requester@workdock.de',
|
||||
email=f'requester@{self.company_domain}',
|
||||
first_name='Mia',
|
||||
last_name='Beispiel',
|
||||
)
|
||||
@@ -26,7 +28,7 @@ class OnboardingFlowTests(TestCase):
|
||||
'gender': 'herr',
|
||||
'job_title': 'Consultant',
|
||||
'department': 'IT-Service',
|
||||
'work_email': 'max.mustermann@workdock.de',
|
||||
'work_email': f'max.mustermann@{self.company_domain}',
|
||||
'contract_start': '2026-11-01',
|
||||
'employment_type': 'unbefristet',
|
||||
'group_mailboxes_required_choice': 'nein',
|
||||
@@ -43,8 +45,70 @@ class OnboardingFlowTests(TestCase):
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertIn('/onboarding/new/?saved=1&id=', response['Location'])
|
||||
|
||||
obj = OnboardingRequest.objects.get(work_email='max.mustermann@workdock.de')
|
||||
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, 'requester@workdock.de')
|
||||
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()
|
||||
|
||||
Reference in New Issue
Block a user