Files
workdock-platform/backend/workflows/tests/test_notifications.py

353 lines
14 KiB
Python

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())