chore: initial snapshot of tubco people portal

This commit is contained in:
Md Bayazid Bostame
2026-03-19 10:22:20 +01:00
commit 9fe3c2ea82
81 changed files with 8698 additions and 0 deletions

View File

View File

@@ -0,0 +1,27 @@
from django.test import TestCase, override_settings
from workflows.models import WorkflowConfig
from workflows.services import is_email_test_mode
class EmailModeOverrideTests(TestCase):
@override_settings(EMAIL_TEST_MODE=False)
def test_uses_env_when_no_override(self):
config, _ = WorkflowConfig.objects.get_or_create(name='Default')
config.email_test_mode_override = None
config.save(update_fields=['email_test_mode_override'])
self.assertEqual(is_email_test_mode(), False)
@override_settings(EMAIL_TEST_MODE=False)
def test_true_override_enables_test_mode(self):
config, _ = WorkflowConfig.objects.get_or_create(name='Default')
config.email_test_mode_override = True
config.save(update_fields=['email_test_mode_override'])
self.assertEqual(is_email_test_mode(), True)
@override_settings(EMAIL_TEST_MODE=True)
def test_false_override_disables_test_mode(self):
config, _ = WorkflowConfig.objects.get_or_create(name='Default')
config.email_test_mode_override = False
config.save(update_fields=['email_test_mode_override'])
self.assertEqual(is_email_test_mode(), False)

View File

@@ -0,0 +1,43 @@
from unittest.mock import MagicMock, patch
from django.test import TestCase
from workflows.models import WorkflowConfig
from workflows.emailing import send_system_email
class EmailingFallbackTests(TestCase):
@patch('workflows.emailing.EmailMessage')
@patch('workflows.emailing.get_connection')
def test_uses_workflowconfig_smtp_when_no_active_system_config(self, mock_get_connection, mock_email_message):
WorkflowConfig.objects.update_or_create(
name='Default',
defaults={
'smtp_server': 'mx.tub.co',
'smtp_port': 465,
'email_account': 'onboarding@tub.co',
'email_password': 'secret',
'smtp_use_ssl': True,
'smtp_use_tls': False,
},
)
fake_connection = object()
mock_get_connection.return_value = fake_connection
msg_instance = MagicMock()
mock_email_message.return_value = msg_instance
send_system_email(
subject='x',
body='y',
to=['target@example.com'],
)
self.assertEqual(mock_get_connection.call_count, 1)
kwargs = mock_get_connection.call_args.kwargs
self.assertEqual(kwargs['host'], 'mx.tub.co')
self.assertEqual(kwargs['port'], 465)
self.assertEqual(kwargs['username'], 'onboarding@tub.co')
self.assertEqual(kwargs['password'], 'secret')
self.assertEqual(kwargs['use_ssl'], True)
self.assertEqual(kwargs['use_tls'], False)

View File

@@ -0,0 +1,96 @@
import json
from django.contrib.auth import get_user_model
from django.test import TestCase
from workflows.models import FormFieldConfig, FormOption
class FormBuilderAdminTests(TestCase):
def setUp(self):
user_model = get_user_model()
self.staff = user_model.objects.create_user(
username='builder_admin',
password='secret123',
email='builder_admin@tub.co',
is_staff=True,
)
self.user = user_model.objects.create_user(
username='builder_user',
password='secret123',
email='builder_user@tub.co',
)
def test_staff_can_open_form_builder(self):
self.client.force_login(self.staff)
response = self.client.get('/admin-tools/form-builder/?form_type=onboarding', HTTP_HOST='localhost')
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Form Builder')
self.assertContains(response, '1. Stammdaten')
def test_non_staff_cannot_open_form_builder(self):
self.client.force_login(self.user)
response = self.client.get('/admin-tools/form-builder/?form_type=onboarding', HTTP_HOST='localhost')
self.assertEqual(response.status_code, 302)
def test_save_order_updates_sort_order_and_step(self):
self.client.force_login(self.staff)
self.client.get('/admin-tools/form-builder/?form_type=onboarding', HTTP_HOST='localhost')
payload = {
'form_type': 'onboarding',
'columns': {
'stammdaten': ['department', 'full_name'],
'vertrag': ['contract_start'],
'itsetup': [],
'abschluss': [],
},
}
response = self.client.post(
'/admin-tools/form-builder/save-order/',
data=json.dumps(payload),
content_type='application/json',
HTTP_HOST='localhost',
)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json()['ok'], True)
department = FormFieldConfig.objects.get(form_type='onboarding', field_name='department')
full_name = FormFieldConfig.objects.get(form_type='onboarding', field_name='full_name')
contract_start = FormFieldConfig.objects.get(form_type='onboarding', field_name='contract_start')
self.assertEqual(department.sort_order, 0)
self.assertEqual(department.page_key, 'stammdaten')
self.assertEqual(full_name.sort_order, 1)
self.assertEqual(full_name.page_key, 'stammdaten')
self.assertEqual(contract_start.sort_order, 2)
self.assertEqual(contract_start.page_key, 'vertrag')
def test_save_order_requires_staff(self):
self.client.force_login(self.user)
response = self.client.post(
'/admin-tools/form-builder/save-order/',
data=json.dumps({'form_type': 'onboarding', 'columns': {}}),
content_type='application/json',
HTTP_HOST='localhost',
)
self.assertEqual(response.status_code, 302)
def test_staff_can_add_option_item_without_django_admin(self):
self.client.force_login(self.staff)
response = self.client.post(
'/admin-tools/form-builder/?form_type=onboarding&option_category=device',
data={
'builder_action': 'add_option',
'category': 'device',
'label': 'Tablet',
'value': 'Tablet',
},
HTTP_HOST='localhost',
)
self.assertEqual(response.status_code, 302)
self.assertTrue(FormOption.objects.filter(category='device', label='Tablet').exists())

View File

@@ -0,0 +1,73 @@
from pathlib import Path
from unittest.mock import patch
from django.test import TestCase, override_settings
from workflows.models import WorkflowConfig
from workflows.services import upload_to_nextcloud
class NextcloudServiceTests(TestCase):
@override_settings(NEXTCLOUD_ENABLED=False)
@patch('workflows.services.requests.put')
def test_upload_returns_false_when_disabled(self, mock_put):
result = upload_to_nextcloud(Path('/tmp/nonexistent.txt'), 'x.txt')
self.assertFalse(result)
mock_put.assert_not_called()
@override_settings(
NEXTCLOUD_ENABLED=True,
NEXTCLOUD_BASE_URL='https://cloud.example/remote.php/dav/files/test-user',
NEXTCLOUD_DIRECTORY='Onboarding',
NEXTCLOUD_USERNAME='u',
NEXTCLOUD_PASSWORD='p',
)
@patch('workflows.services.requests.put')
def test_upload_calls_webdav_and_accepts_201(self, mock_put):
temp_file = Path('/tmp/nextcloud_mock_upload.txt')
temp_file.write_text('hello', encoding='utf-8')
mock_put.return_value.status_code = 201
try:
result = upload_to_nextcloud(temp_file, 'target.txt')
finally:
temp_file.unlink(missing_ok=True)
self.assertTrue(result)
self.assertEqual(mock_put.call_count, 1)
called_url = mock_put.call_args.kwargs['url'] if 'url' in mock_put.call_args.kwargs else mock_put.call_args.args[0]
self.assertTrue(called_url.endswith('/Onboarding/target.txt'))
@override_settings(
NEXTCLOUD_ENABLED=True,
NEXTCLOUD_BASE_URL='https://cloud.example/remote.php/dav/files/env-user',
NEXTCLOUD_DIRECTORY='EnvFolder',
NEXTCLOUD_USERNAME='env-user',
NEXTCLOUD_PASSWORD='env-pass',
)
@patch('workflows.services.requests.put')
def test_upload_prefers_workflowconfig_overrides(self, mock_put):
WorkflowConfig.objects.update_or_create(
name='Default',
defaults={
'nextcloud_enabled_override': True,
'nextcloud_base_url_override': 'https://cloud.example/remote.php/dav/files/admin-user',
'nextcloud_directory_override': 'AdminFolder',
'nextcloud_username_override': 'admin-user',
'nextcloud_password_override': 'admin-pass',
},
)
temp_file = Path('/tmp/nextcloud_override_upload.txt')
temp_file.write_text('hello', encoding='utf-8')
mock_put.return_value.status_code = 201
try:
result = upload_to_nextcloud(temp_file, 'override.txt')
finally:
temp_file.unlink(missing_ok=True)
self.assertTrue(result)
called_url = mock_put.call_args.kwargs['url'] if 'url' in mock_put.call_args.kwargs else mock_put.call_args.args[0]
self.assertTrue(called_url.endswith('/AdminFolder/override.txt'))
auth = mock_put.call_args.kwargs.get('auth')
self.assertEqual(auth, ('admin-user', 'admin-pass'))

View File

@@ -0,0 +1,59 @@
from unittest.mock import patch
from django.contrib.auth import get_user_model
from django.test import TestCase
from workflows.models import EmployeeProfile, OffboardingRequest
class OffboardingFlowTests(TestCase):
def setUp(self):
user_model = get_user_model()
self.user = user_model.objects.create_user(
username='offboard_user',
password='secret123',
email='operator@tub.co',
first_name='Nina',
last_name='Admin',
)
self.client.force_login(self.user)
self.profile = EmployeeProfile.objects.create(
full_name='Lara Beispiel',
first_name='Lara',
last_name='Beispiel',
department='IT-Service',
job_title='Engineer',
work_email='lara.beispiel@tub.co',
)
def test_offboarding_prefill_from_profile(self):
response = self.client.get(f'/offboarding/new/?profile={self.profile.id}', HTTP_HOST='localhost')
html = response.content.decode('utf-8')
self.assertEqual(response.status_code, 200)
self.assertIn('value="Lara Beispiel"', html)
self.assertIn('value="lara.beispiel@tub.co"', html)
self.assertIn('value="Engineer"', html)
@patch('workflows.views.process_offboarding_request.delay')
def test_offboarding_submit_uses_logged_in_user_email(self, mock_delay):
payload = {
'full_name': self.profile.full_name,
'work_email': self.profile.work_email,
'department': self.profile.department,
'job_title': self.profile.job_title,
'last_working_day': '2026-12-31',
'notes': 'Bitte Accounts sperren.',
}
response = self.client.post(
f'/offboarding/new/?profile={self.profile.id}',
payload,
HTTP_HOST='localhost',
)
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_name, 'Nina Admin')
mock_delay.assert_called_once_with(obj.id)

View File

@@ -0,0 +1,50 @@
from unittest.mock import patch
from django.contrib.auth import get_user_model
from django.test import TestCase
from workflows.models import OnboardingRequest
class OnboardingFlowTests(TestCase):
def setUp(self):
user_model = get_user_model()
self.user = user_model.objects.create_user(
username='onboard_user',
password='secret123',
email='requester@tub.co',
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': 'max.mustermann@tub.co',
'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='max.mustermann@tub.co')
self.assertEqual(obj.full_name, 'Max Mustermann')
self.assertEqual(obj.onboarded_by_email, 'requester@tub.co')
self.assertEqual(obj.onboarded_by_name, 'Mia Beispiel')
mock_delay.assert_called_once_with(obj.id)

View File

@@ -0,0 +1,18 @@
from pathlib import Path
from django.test import SimpleTestCase
from workflows.tasks import _generate_content_pdf
class PdfSmokeTests(SimpleTestCase):
def test_generate_content_pdf_creates_nonempty_file(self):
output_pdf = Path('/tmp/pdf_smoke_output.pdf')
html = '<html><body><h1>PDF Smoke</h1><p>This is a smoke test.</p></body></html>'
try:
_generate_content_pdf(html, output_pdf)
self.assertTrue(output_pdf.exists())
self.assertGreater(output_pdf.stat().st_size, 100)
finally:
output_pdf.unlink(missing_ok=True)

View File

@@ -0,0 +1,107 @@
from pathlib import Path
from datetime import date
from unittest.mock import patch
from django.test import TestCase, override_settings
from workflows.models import NotificationRule, OnboardingRequest, WorkflowConfig
from workflows.tasks import process_onboarding_request
@override_settings(PDF_OUTPUT_DIR=Path('/tmp/onoff_test_pdfs'))
class TaskEmailRoutingTests(TestCase):
def setUp(self):
WorkflowConfig.objects.update_or_create(
name='Default',
defaults={
'it_onboarding_email': 'it@tub.co',
'general_info_email': 'ingo@tub.co',
'business_card_email': 'kommunikation@tub.co',
'hr_works_email': 'dittrich@tub.co',
'key_notification_email': 'minuth@tub.co',
},
)
self.request_obj = OnboardingRequest.objects.create(
full_name='Nina Routing',
gender='frau',
job_title='Manager',
department='IT-Service',
work_email='nina.routing@tub.co',
contract_start=date(2026, 11, 1),
employment_type='unbefristet',
order_business_cards=True,
business_card_name='Nina Routing',
business_card_title='Manager',
business_card_email='nina.routing@tub.co',
business_card_phone='030 123456',
needed_devices='Laptop\nSchlüssel',
needed_accesses='HR Works',
onboarded_by_email='requester@tub.co',
agreement='accepted',
)
@patch('workflows.tasks.upload_to_nextcloud')
@patch('workflows.tasks._send_templated_email')
@patch('workflows.tasks._generate_onboarding_pdf')
def test_onboarding_email_routing_conditions(
self,
mock_generate_pdf,
mock_send_templated_email,
mock_upload,
):
pdf_path = Path('/tmp/onoff_test_pdfs/onboarding_letter_Nina_Routing.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
process_onboarding_request(self.request_obj.id)
template_keys = [call.kwargs['template_key'] for call in mock_send_templated_email.call_args_list]
self.assertIn('onboarding_it', template_keys)
self.assertIn('onboarding_general_info', template_keys)
self.assertIn('onboarding_business_card', template_keys)
self.assertIn('onboarding_hr_works', template_keys)
self.assertIn('onboarding_key', template_keys)
self.assertIn('onboarding_reference', template_keys)
self.assertEqual(len(template_keys), 6)
mock_upload.assert_called_once_with(pdf_path, pdf_path.name)
@patch('workflows.tasks.upload_to_nextcloud')
@patch('workflows.tasks._send_templated_email')
@patch('workflows.tasks._generate_onboarding_pdf')
def test_onboarding_additional_notification_rule(
self,
mock_generate_pdf,
mock_send_templated_email,
mock_upload,
):
NotificationRule.objects.create(
name='Extra Schluessel Regel',
event_type='onboarding',
field_name='needed_devices',
operator='contains',
expected_value='Schlüssel',
recipients='extra.recipient@tub.co',
template_key='onboarding_key',
include_pdf_attachment=True,
sort_order=1,
is_active=True,
)
pdf_path = Path('/tmp/onoff_test_pdfs/onboarding_letter_Nina_Routing.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
process_onboarding_request(self.request_obj.id)
matching_calls = [
call for call in mock_send_templated_email.call_args_list
if call.kwargs.get('template_key') == 'onboarding_key'
and call.kwargs.get('to') == ['extra.recipient@tub.co']
]
self.assertEqual(len(matching_calls), 1)
self.assertEqual(matching_calls[0].kwargs.get('attachments'), [pdf_path])
mock_upload.assert_called_once_with(pdf_path, pdf_path.name)

View File

@@ -0,0 +1,162 @@
from datetime import date
from pathlib import Path
from unittest.mock import patch
from django.contrib.auth import get_user_model
from django.test import TestCase, override_settings
from django.utils import timezone
from workflows.models import NotificationTemplate, OnboardingRequest, ScheduledWelcomeEmail, WorkflowConfig
from workflows.tasks import process_onboarding_request, send_scheduled_welcome_email
@override_settings(PDF_OUTPUT_DIR=Path('/tmp/onoff_test_pdfs'))
class WelcomeEmailScheduleTests(TestCase):
def setUp(self):
WorkflowConfig.objects.update_or_create(
name='Default',
defaults={
'welcome_email_delay_days': 9,
'welcome_sender_email': 'admin.sender@tub.co',
'welcome_include_pdf': False,
},
)
self.onboarding = OnboardingRequest.objects.create(
full_name='Welcome Person',
gender='frau',
job_title='Manager',
department='IT-Service',
work_email='welcome.person@tub.co',
contract_start=date(2026, 11, 1),
employment_type='unbefristet',
onboarded_by_email='requester@tub.co',
agreement='accepted',
)
@patch('workflows.tasks.upload_to_nextcloud')
@patch('workflows.tasks.send_scheduled_welcome_email.apply_async')
@patch('workflows.tasks._send_templated_email')
@patch('workflows.tasks._generate_onboarding_pdf')
def test_onboarding_creates_scheduled_welcome_email(
self,
mock_generate_pdf,
mock_send_templated_email,
mock_welcome_apply_async,
mock_upload,
):
pdf_path = Path('/tmp/onoff_test_pdfs/onboarding_letter_Welcome_Person.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
mock_welcome_apply_async.return_value.id = 'celery-welcome-1'
process_onboarding_request(self.onboarding.id)
scheduled = ScheduledWelcomeEmail.objects.get(onboarding_request_id=self.onboarding.id)
self.assertEqual(scheduled.recipient_email, 'welcome.person@tub.co')
self.assertEqual(scheduled.status, 'scheduled')
self.assertEqual(scheduled.celery_task_id, 'celery-welcome-1')
delay = scheduled.send_at - timezone.now()
self.assertGreaterEqual(delay.days, 8)
@patch('workflows.views.send_scheduled_welcome_email.delay')
def test_admin_can_trigger_welcome_email_now(self, mock_delay):
scheduled = ScheduledWelcomeEmail.objects.create(
onboarding_request=self.onboarding,
recipient_email='welcome.person@tub.co',
send_at=timezone.now(),
status='scheduled',
)
user_model = get_user_model()
admin = user_model.objects.create_user(
username='welcome_admin',
password='secret123',
email='welcome_admin@tub.co',
is_staff=True,
is_superuser=True,
)
self.client.force_login(admin)
mock_delay.return_value.id = 'forced-welcome-1'
response = self.client.post(
f'/admin-tools/welcome-emails/{scheduled.id}/trigger-now/',
HTTP_HOST='localhost',
)
self.assertEqual(response.status_code, 302)
scheduled.refresh_from_db()
self.assertEqual(scheduled.celery_task_id, 'forced-welcome-1')
mock_delay.assert_called_once_with(scheduled.id, True)
@patch('workflows.views.send_scheduled_welcome_email.apply_async')
def test_admin_can_pause_resume_and_cancel(self, mock_apply_async):
scheduled = ScheduledWelcomeEmail.objects.create(
onboarding_request=self.onboarding,
recipient_email='welcome.person@tub.co',
send_at=timezone.now(),
status='scheduled',
celery_task_id='task-abc',
)
mock_apply_async.return_value.id = 'resumed-task-1'
user_model = get_user_model()
admin = user_model.objects.create_user(
username='welcome_admin_2',
password='secret123',
email='welcome_admin_2@tub.co',
is_staff=True,
is_superuser=True,
)
self.client.force_login(admin)
pause_response = self.client.post(
f'/admin-tools/welcome-emails/{scheduled.id}/pause/',
HTTP_HOST='localhost',
)
self.assertEqual(pause_response.status_code, 302)
scheduled.refresh_from_db()
self.assertEqual(scheduled.status, 'paused')
resume_response = self.client.post(
f'/admin-tools/welcome-emails/{scheduled.id}/resume/',
HTTP_HOST='localhost',
)
self.assertEqual(resume_response.status_code, 302)
scheduled.refresh_from_db()
self.assertEqual(scheduled.status, 'scheduled')
self.assertEqual(scheduled.celery_task_id, 'resumed-task-1')
cancel_response = self.client.post(
f'/admin-tools/welcome-emails/{scheduled.id}/cancel/',
HTTP_HOST='localhost',
)
self.assertEqual(cancel_response.status_code, 302)
scheduled.refresh_from_db()
self.assertEqual(scheduled.status, 'cancelled')
@patch('workflows.tasks._send_templated_email')
def test_scheduled_welcome_uses_sender_override_without_pdf_if_disabled(self, mock_send_templated):
NotificationTemplate.objects.update_or_create(
key='onboarding_welcome',
defaults={
'subject_template': 'Welcome {{ FULL_NAME }}',
'body_template': 'Body {{ EMAIL }}',
'is_active': True,
},
)
scheduled = ScheduledWelcomeEmail.objects.create(
onboarding_request=self.onboarding,
recipient_email='welcome.person@tub.co',
send_at=timezone.now(),
status='scheduled',
)
self.onboarding.generated_pdf_path = '/tmp/onoff_test_pdfs/should_not_attach.pdf'
self.onboarding.save(update_fields=['generated_pdf_path'])
send_scheduled_welcome_email(scheduled.id, True)
mock_send_templated.assert_called_once()
kwargs = mock_send_templated.call_args.kwargs
self.assertEqual(kwargs.get('attachments'), [])
self.assertEqual(kwargs.get('from_email'), 'admin.sender@tub.co')