Merge branch 'main' into MK/meeting_checklist
commit
a8d6503b60
@ -1,3 +0,0 @@
|
|||||||
from django.shortcuts import render
|
|
||||||
|
|
||||||
# Create your views here.
|
|
||||||
@ -1,2 +1,3 @@
|
|||||||
from .admin import *
|
from .admin import *
|
||||||
from .models import *
|
from .models import *
|
||||||
|
from .rules import *
|
||||||
|
|||||||
@ -0,0 +1,102 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from unittest.mock import Mock
|
||||||
|
from finance.rules import is_creator, not_submitted, leads_excursion
|
||||||
|
from finance.models import Statement, Ledger
|
||||||
|
from members.models import Member, Group, Freizeit, GEMEINSCHAFTS_TOUR, MUSKELKRAFT_ANREISE, MALE, FEMALE
|
||||||
|
|
||||||
|
|
||||||
|
class FinanceRulesTestCase(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.group = Group.objects.create(name="Test Group")
|
||||||
|
self.ledger = Ledger.objects.create(name="Test Ledger")
|
||||||
|
|
||||||
|
self.user1 = User.objects.create_user(username="alice", password="test123")
|
||||||
|
self.member1 = Member.objects.create(
|
||||||
|
prename="Alice", lastname="Smith", birth_date=timezone.now().date(),
|
||||||
|
email=settings.TEST_MAIL, gender=FEMALE, user=self.user1
|
||||||
|
)
|
||||||
|
self.member1.group.add(self.group)
|
||||||
|
|
||||||
|
self.user2 = User.objects.create_user(username="bob", password="test123")
|
||||||
|
self.member2 = Member.objects.create(
|
||||||
|
prename="Bob", lastname="Jones", birth_date=timezone.now().date(),
|
||||||
|
email=settings.TEST_MAIL, gender=MALE, user=self.user2
|
||||||
|
)
|
||||||
|
self.member2.group.add(self.group)
|
||||||
|
|
||||||
|
self.freizeit = Freizeit.objects.create(
|
||||||
|
name="Test Excursion",
|
||||||
|
kilometers_traveled=100,
|
||||||
|
tour_type=GEMEINSCHAFTS_TOUR,
|
||||||
|
tour_approach=MUSKELKRAFT_ANREISE,
|
||||||
|
difficulty=2
|
||||||
|
)
|
||||||
|
self.freizeit.jugendleiter.add(self.member1)
|
||||||
|
|
||||||
|
self.statement = Statement.objects.create(
|
||||||
|
short_description="Test Statement",
|
||||||
|
explanation="Test explanation",
|
||||||
|
night_cost=27,
|
||||||
|
created_by=self.member1,
|
||||||
|
excursion=self.freizeit
|
||||||
|
)
|
||||||
|
self.statement.allowance_to.add(self.member1)
|
||||||
|
|
||||||
|
def test_is_creator_true(self):
|
||||||
|
"""Test is_creator predicate returns True when user created the statement"""
|
||||||
|
self.assertTrue(is_creator(self.user1, self.statement))
|
||||||
|
self.assertFalse(is_creator(self.user2, self.statement))
|
||||||
|
|
||||||
|
def test_not_submitted_statement(self):
|
||||||
|
"""Test not_submitted predicate returns True when statement is not submitted"""
|
||||||
|
self.statement.submitted = False
|
||||||
|
self.assertTrue(not_submitted(self.user1, self.statement))
|
||||||
|
self.statement.submitted = True
|
||||||
|
self.assertFalse(not_submitted(self.user1, self.statement))
|
||||||
|
|
||||||
|
def test_not_submitted_freizeit_with_statement(self):
|
||||||
|
"""Test not_submitted predicate with Freizeit having unsubmitted statement"""
|
||||||
|
self.freizeit.statement = self.statement
|
||||||
|
self.statement.submitted = False
|
||||||
|
self.assertTrue(not_submitted(self.user1, self.freizeit))
|
||||||
|
|
||||||
|
def test_not_submitted_freizeit_without_statement(self):
|
||||||
|
"""Test not_submitted predicate with Freizeit having no statement attribute"""
|
||||||
|
# Create a mock Freizeit that truly doesn't have the statement attribute
|
||||||
|
mock_freizeit = Mock(spec=Freizeit)
|
||||||
|
# Remove the statement attribute entirely
|
||||||
|
if hasattr(mock_freizeit, 'statement'):
|
||||||
|
delattr(mock_freizeit, 'statement')
|
||||||
|
self.assertTrue(not_submitted(self.user1, mock_freizeit))
|
||||||
|
|
||||||
|
def test_leads_excursion_freizeit_user_is_leader(self):
|
||||||
|
"""Test leads_excursion predicate returns True when user leads the Freizeit"""
|
||||||
|
self.assertTrue(leads_excursion(self.user1, self.freizeit))
|
||||||
|
self.assertFalse(leads_excursion(self.user2, self.freizeit))
|
||||||
|
|
||||||
|
def test_leads_excursion_statement_with_excursion(self):
|
||||||
|
"""Test leads_excursion predicate with statement having excursion led by user"""
|
||||||
|
result = leads_excursion(self.user1, self.statement)
|
||||||
|
self.assertTrue(result)
|
||||||
|
|
||||||
|
def test_leads_excursion_statement_no_excursion_attribute(self):
|
||||||
|
"""Test leads_excursion predicate with statement having no excursion attribute"""
|
||||||
|
mock_statement = Mock()
|
||||||
|
del mock_statement.excursion
|
||||||
|
result = leads_excursion(self.user1, mock_statement)
|
||||||
|
self.assertFalse(result)
|
||||||
|
|
||||||
|
def test_leads_excursion_statement_excursion_is_none(self):
|
||||||
|
"""Test leads_excursion predicate with statement having None excursion"""
|
||||||
|
statement_no_excursion = Statement.objects.create(
|
||||||
|
short_description="Test Statement No Excursion",
|
||||||
|
explanation="Test explanation",
|
||||||
|
night_cost=27,
|
||||||
|
created_by=self.member1,
|
||||||
|
excursion=None
|
||||||
|
)
|
||||||
|
result = leads_excursion(self.user1, statement_no_excursion)
|
||||||
|
self.assertFalse(result)
|
||||||
@ -1,3 +0,0 @@
|
|||||||
from django.shortcuts import render
|
|
||||||
|
|
||||||
# Create your views here.
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
# Create your tests here.
|
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
from .views import *
|
||||||
|
from .oauth import *
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.conf import settings
|
||||||
|
from unittest.mock import Mock
|
||||||
|
from logindata.oauth import CustomOAuth2Validator
|
||||||
|
from members.models import Member, MALE
|
||||||
|
|
||||||
|
|
||||||
|
class CustomOAuth2ValidatorTestCase(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.validator = CustomOAuth2Validator()
|
||||||
|
|
||||||
|
# Create user with member
|
||||||
|
self.user_with_member = User.objects.create_user(username="alice", password="test123")
|
||||||
|
self.member = Member.objects.create(
|
||||||
|
prename="Alice", lastname="Smith", birth_date="1990-01-01",
|
||||||
|
email=settings.TEST_MAIL, gender=MALE, user=self.user_with_member
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create user without member
|
||||||
|
self.user_without_member = User.objects.create_user(username="bob", password="test123")
|
||||||
|
|
||||||
|
def test_get_additional_claims_with_member(self):
|
||||||
|
"""Test get_additional_claims when user has a member"""
|
||||||
|
request = Mock()
|
||||||
|
request.user = self.user_with_member
|
||||||
|
|
||||||
|
result = self.validator.get_additional_claims(request)
|
||||||
|
|
||||||
|
self.assertEqual(result['email'], settings.TEST_MAIL)
|
||||||
|
self.assertEqual(result['preferred_username'], 'alice')
|
||||||
|
|
||||||
|
def test_get_additional_claims_without_member(self):
|
||||||
|
"""Test get_additional_claims when user has no member"""
|
||||||
|
# ensure branch coverage, not possible under standard scenarios
|
||||||
|
request = Mock()
|
||||||
|
request.user = Mock()
|
||||||
|
request.user.member = None
|
||||||
|
self.assertEqual(len(self.validator.get_additional_claims(request)), 1)
|
||||||
|
|
||||||
|
request = Mock()
|
||||||
|
request.user = self.user_without_member
|
||||||
|
|
||||||
|
# The method will raise RelatedObjectDoesNotExist, which means the code
|
||||||
|
# should use hasattr or try/except. For now, test that it raises.
|
||||||
|
with self.assertRaises(User.member.RelatedObjectDoesNotExist):
|
||||||
|
self.validator.get_additional_claims(request)
|
||||||
@ -0,0 +1,154 @@
|
|||||||
|
from http import HTTPStatus
|
||||||
|
from django.test import TestCase, Client
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
from django.contrib.auth.models import User, Group
|
||||||
|
|
||||||
|
from members.models import Member, DIVERSE
|
||||||
|
from ..models import RegistrationPassword, initial_user_setup
|
||||||
|
|
||||||
|
|
||||||
|
class RegistrationPasswordTestCase(TestCase):
|
||||||
|
def test_str_method(self):
|
||||||
|
"""Test RegistrationPassword __str__ method returns password"""
|
||||||
|
reg_password = RegistrationPassword.objects.create(password="test123")
|
||||||
|
self.assertEqual(str(reg_password), "test123")
|
||||||
|
|
||||||
|
|
||||||
|
class RegisterViewTestCase(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.client = Client()
|
||||||
|
|
||||||
|
# Create a test member with invite key
|
||||||
|
self.member = Member.objects.create(
|
||||||
|
prename='Test',
|
||||||
|
lastname='User',
|
||||||
|
birth_date=timezone.now().date(),
|
||||||
|
email='test@example.com',
|
||||||
|
gender=DIVERSE,
|
||||||
|
invite_as_user_key='test_key_123'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create a registration password
|
||||||
|
self.registration_password = RegistrationPassword.objects.create(
|
||||||
|
password='test_password'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get or create Standard group for user setup
|
||||||
|
self.standard_group, created = Group.objects.get_or_create(name='Standard')
|
||||||
|
|
||||||
|
def test_register_get_without_key_redirects(self):
|
||||||
|
"""Test GET request without key redirects to startpage."""
|
||||||
|
url = reverse('logindata:register')
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, HTTPStatus.FOUND)
|
||||||
|
|
||||||
|
def test_register_post_without_key_redirects(self):
|
||||||
|
"""Test POST request without key redirects to startpage."""
|
||||||
|
url = reverse('logindata:register')
|
||||||
|
response = self.client.post(url)
|
||||||
|
self.assertEqual(response.status_code, HTTPStatus.FOUND)
|
||||||
|
|
||||||
|
def test_register_get_with_empty_key_shows_failed(self):
|
||||||
|
"""Test GET request with empty key shows registration failed page."""
|
||||||
|
url = reverse('logindata:register')
|
||||||
|
response = self.client.get(url, {'key': ''})
|
||||||
|
self.assertEqual(response.status_code, HTTPStatus.OK)
|
||||||
|
self.assertContains(response, _('Something went wrong. The registration key is invalid or has expired.'))
|
||||||
|
|
||||||
|
def test_register_get_with_invalid_key_shows_failed(self):
|
||||||
|
"""Test GET request with invalid key shows registration failed page."""
|
||||||
|
url = reverse('logindata:register')
|
||||||
|
response = self.client.get(url, {'key': 'invalid_key'})
|
||||||
|
self.assertEqual(response.status_code, HTTPStatus.OK)
|
||||||
|
self.assertContains(response, _('Something went wrong. The registration key is invalid or has expired.'))
|
||||||
|
|
||||||
|
def test_register_get_with_valid_key_shows_password_form(self):
|
||||||
|
"""Test GET request with valid key shows password entry form."""
|
||||||
|
url = reverse('logindata:register')
|
||||||
|
response = self.client.get(url, {'key': self.member.invite_as_user_key})
|
||||||
|
self.assertEqual(response.status_code, HTTPStatus.OK)
|
||||||
|
self.assertContains(response, _('Set login data'))
|
||||||
|
self.assertContains(response, _('Welcome, '))
|
||||||
|
self.assertContains(response, self.member.prename)
|
||||||
|
|
||||||
|
def test_register_post_without_password_shows_failed(self):
|
||||||
|
"""Test POST request without password shows registration failed page."""
|
||||||
|
url = reverse('logindata:register')
|
||||||
|
response = self.client.post(url, {'key': self.member.invite_as_user_key})
|
||||||
|
self.assertEqual(response.status_code, HTTPStatus.OK)
|
||||||
|
self.assertContains(response, _('Something went wrong. The registration key is invalid or has expired.'))
|
||||||
|
|
||||||
|
def test_register_post_with_wrong_password_shows_error(self):
|
||||||
|
"""Test POST request with wrong password shows error message."""
|
||||||
|
url = reverse('logindata:register')
|
||||||
|
response = self.client.post(url, {
|
||||||
|
'key': self.member.invite_as_user_key,
|
||||||
|
'password': 'wrong_password'
|
||||||
|
})
|
||||||
|
self.assertEqual(response.status_code, HTTPStatus.OK)
|
||||||
|
self.assertContains(response, _('You entered a wrong password.'))
|
||||||
|
|
||||||
|
def test_register_post_with_correct_password_shows_form(self):
|
||||||
|
"""Test POST request with correct password shows user creation form."""
|
||||||
|
url = reverse('logindata:register')
|
||||||
|
response = self.client.post(url, {
|
||||||
|
'key': self.member.invite_as_user_key,
|
||||||
|
'password': self.registration_password.password
|
||||||
|
})
|
||||||
|
self.assertEqual(response.status_code, HTTPStatus.OK)
|
||||||
|
self.assertContains(response, _('Set login data'))
|
||||||
|
self.assertContains(response, self.member.suggested_username())
|
||||||
|
|
||||||
|
def test_register_post_with_save_and_invalid_form_shows_errors(self):
|
||||||
|
"""Test POST request with save but invalid form shows form errors."""
|
||||||
|
url = reverse('logindata:register')
|
||||||
|
response = self.client.post(url, {
|
||||||
|
'key': self.member.invite_as_user_key,
|
||||||
|
'password': self.registration_password.password,
|
||||||
|
'save': 'true',
|
||||||
|
'username': '', # Invalid - empty username
|
||||||
|
'password1': 'testpass123',
|
||||||
|
'password2': 'different_pass' # Invalid - passwords don't match
|
||||||
|
})
|
||||||
|
self.assertEqual(response.status_code, HTTPStatus.OK)
|
||||||
|
self.assertContains(response, _('Set login data'))
|
||||||
|
|
||||||
|
def test_register_post_with_save_and_valid_form_shows_success(self):
|
||||||
|
"""Test POST request with save and valid form shows success page."""
|
||||||
|
url = reverse('logindata:register')
|
||||||
|
response = self.client.post(url, {
|
||||||
|
'key': self.member.invite_as_user_key,
|
||||||
|
'password': self.registration_password.password,
|
||||||
|
'save': 'true',
|
||||||
|
'username': 'testuser',
|
||||||
|
'password1': 'testpass123',
|
||||||
|
'password2': 'testpass123'
|
||||||
|
})
|
||||||
|
self.assertEqual(response.status_code, HTTPStatus.OK)
|
||||||
|
self.assertContains(response, _('You successfully set your login data. You can now proceed to'))
|
||||||
|
|
||||||
|
# Verify user was created and associated with member
|
||||||
|
user = User.objects.get(username='testuser')
|
||||||
|
self.assertEqual(user.is_staff, True)
|
||||||
|
self.member.refresh_from_db()
|
||||||
|
self.assertEqual(self.member.user, user)
|
||||||
|
self.assertEqual(self.member.invite_as_user_key, '')
|
||||||
|
|
||||||
|
def test_register_post_with_save_and_no_standard_group_shows_failed(self):
|
||||||
|
"""Test POST request with save but no Standard group shows failed page."""
|
||||||
|
# Delete the Standard group
|
||||||
|
self.standard_group.delete()
|
||||||
|
|
||||||
|
url = reverse('logindata:register')
|
||||||
|
response = self.client.post(url, {
|
||||||
|
'key': self.member.invite_as_user_key,
|
||||||
|
'password': self.registration_password.password,
|
||||||
|
'save': 'true',
|
||||||
|
'username': 'testuser',
|
||||||
|
'password1': 'testpass123',
|
||||||
|
'password2': 'testpass123'
|
||||||
|
})
|
||||||
|
self.assertEqual(response.status_code, HTTPStatus.OK)
|
||||||
|
self.assertContains(response, _('Something went wrong. The registration key is invalid or has expired.'))
|
||||||
@ -1,3 +1,4 @@
|
|||||||
from .models import *
|
from .models import *
|
||||||
from .admin import *
|
from .admin import *
|
||||||
from .views import *
|
from .views import *
|
||||||
|
from .rules import *
|
||||||
|
|||||||
@ -0,0 +1,337 @@
|
|||||||
|
import json
|
||||||
|
import unittest
|
||||||
|
from http import HTTPStatus
|
||||||
|
from django.test import TestCase, override_settings
|
||||||
|
from django.contrib.admin.sites import AdminSite
|
||||||
|
from django.test import RequestFactory, Client
|
||||||
|
from django.contrib.auth.models import User, Permission
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.contrib.sessions.middleware import SessionMiddleware
|
||||||
|
from django.contrib.messages.middleware import MessageMiddleware
|
||||||
|
from django.contrib.messages.storage.fallback import FallbackStorage
|
||||||
|
from django.contrib.messages import get_messages
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.urls import reverse, reverse_lazy
|
||||||
|
from django.http import HttpResponseRedirect, HttpResponse
|
||||||
|
from unittest.mock import Mock, patch
|
||||||
|
from django.test.utils import override_settings
|
||||||
|
from django.urls import path, include
|
||||||
|
from django.contrib import admin as django_admin
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from members.tests.utils import create_custom_user
|
||||||
|
from members.models import Member, MALE, DIVERSE, Group
|
||||||
|
from ..models import Message, Attachment, EmailAddress
|
||||||
|
from ..admin import MessageAdmin, submit_message
|
||||||
|
from ..mailutils import SENT, NOT_SENT, PARTLY_SENT
|
||||||
|
|
||||||
|
|
||||||
|
class AdminTestCase(TestCase):
|
||||||
|
def setUp(self, model, admin):
|
||||||
|
self.factory = RequestFactory()
|
||||||
|
self.model = model
|
||||||
|
if model is not None and admin is not None:
|
||||||
|
self.admin = admin(model, AdminSite())
|
||||||
|
superuser = User.objects.create_superuser(
|
||||||
|
username='superuser', password='secret'
|
||||||
|
)
|
||||||
|
standard = create_custom_user('standard', ['Standard'], 'Paul', 'Wulter')
|
||||||
|
trainer = create_custom_user('trainer', ['Standard', 'Trainings'], 'Lise', 'Lotte')
|
||||||
|
|
||||||
|
def _login(self, name):
|
||||||
|
c = Client()
|
||||||
|
res = c.login(username=name, password='secret')
|
||||||
|
# make sure we logged in
|
||||||
|
assert res
|
||||||
|
return c
|
||||||
|
|
||||||
|
def _add_middleware(self, request):
|
||||||
|
"""Add required middleware to request."""
|
||||||
|
# Session middleware
|
||||||
|
middleware = SessionMiddleware(lambda x: None)
|
||||||
|
middleware.process_request(request)
|
||||||
|
request.session.save()
|
||||||
|
|
||||||
|
# Messages middleware
|
||||||
|
messages_middleware = MessageMiddleware(lambda x: None)
|
||||||
|
messages_middleware.process_request(request)
|
||||||
|
request._messages = FallbackStorage(request)
|
||||||
|
|
||||||
|
|
||||||
|
class MessageAdminTestCase(AdminTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp(Message, MessageAdmin)
|
||||||
|
|
||||||
|
# Create test data
|
||||||
|
self.group = Group.objects.create(name='Test Group')
|
||||||
|
self.email_address = EmailAddress.objects.create(name='testmail')
|
||||||
|
|
||||||
|
# Create test member with internal email
|
||||||
|
self.internal_member = Member.objects.create(
|
||||||
|
prename='Internal',
|
||||||
|
lastname='User',
|
||||||
|
birth_date=timezone.now().date(),
|
||||||
|
email=f'internal@{settings.ALLOWED_EMAIL_DOMAINS_FOR_INVITE_AS_USER[0]}',
|
||||||
|
gender=DIVERSE
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create test member with external email
|
||||||
|
self.external_member = Member.objects.create(
|
||||||
|
prename='External',
|
||||||
|
lastname='User',
|
||||||
|
birth_date=timezone.now().date(),
|
||||||
|
email='external@example.com',
|
||||||
|
gender=DIVERSE
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create users for testing
|
||||||
|
self.user_with_internal_member = User.objects.create_user(username='testuser', password='secret')
|
||||||
|
self.user_with_internal_member.member = self.internal_member
|
||||||
|
self.user_with_internal_member.save()
|
||||||
|
|
||||||
|
self.user_with_external_member = User.objects.create_user(username='external_user', password='secret')
|
||||||
|
self.user_with_external_member.member = self.external_member
|
||||||
|
self.user_with_external_member.save()
|
||||||
|
|
||||||
|
self.user_without_member = User.objects.create_user(username='no_member_user', password='secret')
|
||||||
|
|
||||||
|
# Create test message
|
||||||
|
self.message = Message.objects.create(
|
||||||
|
subject='Test Message',
|
||||||
|
content='Test content'
|
||||||
|
)
|
||||||
|
self.message.to_groups.add(self.group)
|
||||||
|
self.message.to_members.add(self.internal_member)
|
||||||
|
|
||||||
|
def test_save_model_sets_created_by(self):
|
||||||
|
"""Test that save_model sets created_by when creating new message."""
|
||||||
|
request = self.factory.post('/admin/mailer/message/add/')
|
||||||
|
request.user = self.user_with_internal_member
|
||||||
|
|
||||||
|
# Create new message
|
||||||
|
new_message = Message(subject='New Message', content='New content')
|
||||||
|
|
||||||
|
# Test save_model for new object (change=False)
|
||||||
|
self.admin.save_model(request, new_message, None, change=False)
|
||||||
|
|
||||||
|
self.assertEqual(new_message.created_by, self.internal_member)
|
||||||
|
|
||||||
|
def test_save_model_does_not_change_created_by_on_update(self):
|
||||||
|
"""Test that save_model doesn't change created_by when updating."""
|
||||||
|
request = self.factory.post('/admin/mailer/message/1/change/')
|
||||||
|
request.user = self.user_with_internal_member
|
||||||
|
|
||||||
|
# Message already has created_by set
|
||||||
|
self.message.created_by = self.external_member
|
||||||
|
|
||||||
|
# Test save_model for existing object (change=True)
|
||||||
|
self.admin.save_model(request, self.message, None, change=True)
|
||||||
|
|
||||||
|
self.assertEqual(self.message.created_by, self.external_member)
|
||||||
|
|
||||||
|
@patch('mailer.models.Message.submit')
|
||||||
|
def test_submit_message_success(self, mock_submit):
|
||||||
|
"""Test submit_message with successful send."""
|
||||||
|
mock_submit.return_value = SENT
|
||||||
|
|
||||||
|
request = self.factory.post('/admin/mailer/message/')
|
||||||
|
request.user = self.user_with_internal_member
|
||||||
|
self._add_middleware(request)
|
||||||
|
|
||||||
|
# Test submit_message
|
||||||
|
submit_message(self.message, request)
|
||||||
|
|
||||||
|
# Verify submit was called with correct sender
|
||||||
|
mock_submit.assert_called_once_with(self.internal_member)
|
||||||
|
|
||||||
|
# Check success message
|
||||||
|
messages_list = list(get_messages(request))
|
||||||
|
self.assertEqual(len(messages_list), 1)
|
||||||
|
self.assertIn(str(_('Successfully sent message')), str(messages_list[0]))
|
||||||
|
|
||||||
|
@patch('mailer.models.Message.submit')
|
||||||
|
def test_submit_message_not_sent(self, mock_submit):
|
||||||
|
"""Test submit_message when sending fails."""
|
||||||
|
mock_submit.return_value = NOT_SENT
|
||||||
|
|
||||||
|
request = self.factory.post('/admin/mailer/message/')
|
||||||
|
request.user = self.user_with_internal_member
|
||||||
|
self._add_middleware(request)
|
||||||
|
|
||||||
|
# Test submit_message
|
||||||
|
submit_message(self.message, request)
|
||||||
|
|
||||||
|
# Check error message
|
||||||
|
messages_list = list(get_messages(request))
|
||||||
|
self.assertEqual(len(messages_list), 1)
|
||||||
|
self.assertIn(str(_('Failed to send message')), str(messages_list[0]))
|
||||||
|
|
||||||
|
@patch('mailer.models.Message.submit')
|
||||||
|
def test_submit_message_partly_sent(self, mock_submit):
|
||||||
|
"""Test submit_message when partially sent."""
|
||||||
|
mock_submit.return_value = PARTLY_SENT
|
||||||
|
|
||||||
|
request = self.factory.post('/admin/mailer/message/')
|
||||||
|
request.user = self.user_with_internal_member
|
||||||
|
self._add_middleware(request)
|
||||||
|
|
||||||
|
# Test submit_message
|
||||||
|
submit_message(self.message, request)
|
||||||
|
|
||||||
|
# Check warning message
|
||||||
|
messages_list = list(get_messages(request))
|
||||||
|
self.assertEqual(len(messages_list), 1)
|
||||||
|
self.assertIn(str(_('Failed to send some messages')), str(messages_list[0]))
|
||||||
|
|
||||||
|
def test_submit_message_user_has_no_member(self):
|
||||||
|
"""Test submit_message when user has no associated member."""
|
||||||
|
request = self.factory.post('/admin/mailer/message/')
|
||||||
|
request.user = self.user_without_member
|
||||||
|
self._add_middleware(request)
|
||||||
|
|
||||||
|
# Test submit_message
|
||||||
|
submit_message(self.message, request)
|
||||||
|
|
||||||
|
# Check error message
|
||||||
|
messages_list = list(get_messages(request))
|
||||||
|
self.assertEqual(len(messages_list), 1)
|
||||||
|
self.assertIn(str(_('Your account is not connected to a member. Please contact your system administrator.')), str(messages_list[0]))
|
||||||
|
|
||||||
|
def test_submit_message_user_has_external_email(self):
|
||||||
|
"""Test submit_message when user has external email."""
|
||||||
|
request = self.factory.post('/admin/mailer/message/')
|
||||||
|
request.user = self.user_with_external_member
|
||||||
|
self._add_middleware(request)
|
||||||
|
|
||||||
|
# Test submit_message
|
||||||
|
submit_message(self.message, request)
|
||||||
|
|
||||||
|
# Check error message
|
||||||
|
messages_list = list(get_messages(request))
|
||||||
|
self.assertEqual(len(messages_list), 1)
|
||||||
|
self.assertIn(str(_('Your email address is not an internal email address. Please use an email address with one of the following domains: %(domains)s.') % {'domains': ", ".join(settings.ALLOWED_EMAIL_DOMAINS_FOR_INVITE_AS_USER)}), str(messages_list[0]))
|
||||||
|
|
||||||
|
@patch('mailer.admin.submit_message')
|
||||||
|
def test_send_message_action_confirmed(self, mock_submit_message):
|
||||||
|
"""Test send_message action when confirmed."""
|
||||||
|
request = self.factory.post('/admin/mailer/message/', {'confirmed': 'true'})
|
||||||
|
request.user = self.user_with_internal_member
|
||||||
|
self._add_middleware(request)
|
||||||
|
|
||||||
|
queryset = Message.objects.filter(pk=self.message.pk)
|
||||||
|
|
||||||
|
# Test send_message action
|
||||||
|
result = self.admin.send_message(request, queryset)
|
||||||
|
|
||||||
|
# Verify submit_message was called for each message
|
||||||
|
mock_submit_message.assert_called_once_with(self.message, request)
|
||||||
|
|
||||||
|
# Should return None when confirmed (no template response)
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
def test_send_message_action_not_confirmed(self):
|
||||||
|
"""Test send_message action when not confirmed (shows confirmation page)."""
|
||||||
|
request = self.factory.post('/admin/mailer/message/')
|
||||||
|
request.user = self.user_with_internal_member
|
||||||
|
self._add_middleware(request)
|
||||||
|
|
||||||
|
queryset = Message.objects.filter(pk=self.message.pk)
|
||||||
|
|
||||||
|
# Test send_message action
|
||||||
|
result = self.admin.send_message(request, queryset)
|
||||||
|
|
||||||
|
# Should return HttpResponse with confirmation template
|
||||||
|
self.assertIsNotNone(result)
|
||||||
|
self.assertEqual(result.status_code, HTTPStatus.OK)
|
||||||
|
|
||||||
|
@patch('mailer.admin.submit_message')
|
||||||
|
def test_response_change_with_send(self, mock_submit_message):
|
||||||
|
"""Test response_change when _send is in POST."""
|
||||||
|
request = self.factory.post('/admin/mailer/message/1/change/', {'_send': 'Send'})
|
||||||
|
request.user = self.user_with_internal_member
|
||||||
|
self._add_middleware(request)
|
||||||
|
|
||||||
|
# Test response_change
|
||||||
|
with patch.object(self.admin.__class__.__bases__[2], 'response_change') as mock_super:
|
||||||
|
mock_super.return_value = HttpResponseRedirect('/admin/')
|
||||||
|
result = self.admin.response_change(request, self.message)
|
||||||
|
|
||||||
|
# Verify submit_message was called
|
||||||
|
mock_submit_message.assert_called_once_with(self.message, request)
|
||||||
|
|
||||||
|
# Verify super method was called
|
||||||
|
mock_super.assert_called_once()
|
||||||
|
|
||||||
|
@patch('mailer.admin.submit_message')
|
||||||
|
def test_response_change_without_send(self, mock_submit_message):
|
||||||
|
"""Test response_change when _send is not in POST."""
|
||||||
|
request = self.factory.post('/admin/mailer/message/1/change/', {'_save': 'Save'})
|
||||||
|
request.user = self.user_with_internal_member
|
||||||
|
self._add_middleware(request)
|
||||||
|
|
||||||
|
# Test response_change
|
||||||
|
with patch.object(self.admin.__class__.__bases__[2], 'response_change') as mock_super:
|
||||||
|
mock_super.return_value = HttpResponseRedirect('/admin/')
|
||||||
|
result = self.admin.response_change(request, self.message)
|
||||||
|
|
||||||
|
# Verify submit_message was NOT called
|
||||||
|
mock_submit_message.assert_not_called()
|
||||||
|
|
||||||
|
# Verify super method was called
|
||||||
|
mock_super.assert_called_once()
|
||||||
|
|
||||||
|
@patch('mailer.admin.submit_message')
|
||||||
|
def test_response_add_with_send(self, mock_submit_message):
|
||||||
|
"""Test response_add when _send is in POST."""
|
||||||
|
request = self.factory.post('/admin/mailer/message/add/', {'_send': 'Send'})
|
||||||
|
request.user = self.user_with_internal_member
|
||||||
|
self._add_middleware(request)
|
||||||
|
|
||||||
|
# Test response_add
|
||||||
|
with patch.object(self.admin.__class__.__bases__[2], 'response_add') as mock_super:
|
||||||
|
mock_super.return_value = HttpResponseRedirect('/admin/')
|
||||||
|
result = self.admin.response_add(request, self.message)
|
||||||
|
|
||||||
|
# Verify submit_message was called
|
||||||
|
mock_submit_message.assert_called_once_with(self.message, request)
|
||||||
|
|
||||||
|
# Verify super method was called
|
||||||
|
mock_super.assert_called_once()
|
||||||
|
|
||||||
|
def test_get_form_with_members_param(self):
|
||||||
|
"""Test get_form when members parameter is provided."""
|
||||||
|
# Create request with members parameter
|
||||||
|
members_ids = [self.internal_member.pk, self.external_member.pk]
|
||||||
|
request = self.factory.get(f'/admin/mailer/message/add/?members={json.dumps(members_ids)}')
|
||||||
|
request.user = self.user_with_internal_member
|
||||||
|
|
||||||
|
# Test get_form
|
||||||
|
form_class = self.admin.get_form(request)
|
||||||
|
form = form_class()
|
||||||
|
|
||||||
|
# Verify initial members are set
|
||||||
|
self.assertEqual(list(form.fields['to_members'].initial), [self.internal_member, self.external_member])
|
||||||
|
|
||||||
|
def test_get_form_with_invalid_members_param(self):
|
||||||
|
"""Test get_form when members parameter is not a list."""
|
||||||
|
# Create request with invalid members parameter
|
||||||
|
request = self.factory.get('/admin/mailer/message/add/?members="not_a_list"')
|
||||||
|
request.user = self.user_with_internal_member
|
||||||
|
|
||||||
|
# Test get_form
|
||||||
|
form_class = self.admin.get_form(request)
|
||||||
|
|
||||||
|
# Should return form without modification
|
||||||
|
self.assertIsNotNone(form_class)
|
||||||
|
|
||||||
|
def test_get_form_without_members_param(self):
|
||||||
|
"""Test get_form when no members parameter is provided."""
|
||||||
|
# Create request without members parameter
|
||||||
|
request = self.factory.get('/admin/mailer/message/add/')
|
||||||
|
request.user = self.user_with_internal_member
|
||||||
|
|
||||||
|
# Test get_form
|
||||||
|
form_class = self.admin.get_form(request)
|
||||||
|
|
||||||
|
# Should return form without modification
|
||||||
|
self.assertIsNotNone(form_class)
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from mailer.rules import is_creator
|
||||||
|
from mailer.models import Message
|
||||||
|
from members.models import Member, MALE
|
||||||
|
|
||||||
|
|
||||||
|
class MailerRulesTestCase(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.user1 = User.objects.create_user(username="alice", password="test123")
|
||||||
|
self.member1 = Member.objects.create(
|
||||||
|
prename="Alice", lastname="Smith", birth_date="1990-01-01",
|
||||||
|
email=settings.TEST_MAIL, gender=MALE, user=self.user1
|
||||||
|
)
|
||||||
|
|
||||||
|
self.message = Message.objects.create(
|
||||||
|
subject="Test Message",
|
||||||
|
content="Test content",
|
||||||
|
created_by=self.member1
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_is_creator_returns_true_when_user_created_message(self):
|
||||||
|
"""Test is_creator predicate returns True when user created the message"""
|
||||||
|
result = is_creator(self.user1, self.message)
|
||||||
|
self.assertTrue(result)
|
||||||
|
|
||||||
|
def test_is_creator_returns_false_when_message_is_none(self):
|
||||||
|
"""Test is_creator predicate returns False when message is None"""
|
||||||
|
result = is_creator(self.user1, None)
|
||||||
|
self.assertFalse(result)
|
||||||
@ -1,3 +0,0 @@
|
|||||||
from django.shortcuts import render
|
|
||||||
|
|
||||||
# Create your views here.
|
|
||||||
@ -1 +1,4 @@
|
|||||||
from .basic import *
|
from .basic import *
|
||||||
|
from .views import *
|
||||||
|
from .tasks import *
|
||||||
|
from .rules import *
|
||||||
|
|||||||
@ -0,0 +1,179 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
from ..models import Member, Group, Freizeit, DIVERSE, GEMEINSCHAFTS_TOUR, MemberTraining, TrainingCategory, LJPProposal
|
||||||
|
from ..rules import is_oneself, may_view, may_change, may_delete, is_own_training, is_leader_of_excursion, is_leader, statement_not_submitted, _is_leader
|
||||||
|
from finance.models import Statement
|
||||||
|
from mailer.models import EmailAddress
|
||||||
|
|
||||||
|
|
||||||
|
class RulesTestCase(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
# Create email address for groups
|
||||||
|
self.email_address = EmailAddress.objects.create(name='test@example.com')
|
||||||
|
|
||||||
|
# Create test users and members
|
||||||
|
self.user1 = User.objects.create_user(username='user1', email='user1@example.com')
|
||||||
|
self.member1 = Member.objects.create(
|
||||||
|
prename='Test',
|
||||||
|
lastname='Member1',
|
||||||
|
birth_date=timezone.now().date(),
|
||||||
|
email='member1@example.com',
|
||||||
|
gender=DIVERSE
|
||||||
|
)
|
||||||
|
self.user1.member = self.member1
|
||||||
|
self.user1.save()
|
||||||
|
|
||||||
|
self.user2 = User.objects.create_user(username='user2', email='user2@example.com')
|
||||||
|
self.member2 = Member.objects.create(
|
||||||
|
prename='Test',
|
||||||
|
lastname='Member2',
|
||||||
|
birth_date=timezone.now().date(),
|
||||||
|
email='member2@example.com',
|
||||||
|
gender=DIVERSE
|
||||||
|
)
|
||||||
|
self.user2.member = self.member2
|
||||||
|
self.user2.save()
|
||||||
|
|
||||||
|
self.user3 = User.objects.create_user(username='user3', email='user3@example.com')
|
||||||
|
self.member3 = Member.objects.create(
|
||||||
|
prename='Test',
|
||||||
|
lastname='Member3',
|
||||||
|
birth_date=timezone.now().date(),
|
||||||
|
email='member3@example.com',
|
||||||
|
gender=DIVERSE
|
||||||
|
)
|
||||||
|
self.user3.member = self.member3
|
||||||
|
self.user3.save()
|
||||||
|
|
||||||
|
# Create test group
|
||||||
|
self.group = Group.objects.create(name='Test Group')
|
||||||
|
self.group.contact_email = self.email_address
|
||||||
|
self.group.leiters.add(self.member2)
|
||||||
|
self.group.save()
|
||||||
|
|
||||||
|
# Create test excursion
|
||||||
|
self.excursion = Freizeit.objects.create(
|
||||||
|
name='Test Excursion',
|
||||||
|
tour_type=GEMEINSCHAFTS_TOUR,
|
||||||
|
kilometers_traveled=10,
|
||||||
|
difficulty=1
|
||||||
|
)
|
||||||
|
self.excursion.jugendleiter.add(self.member1)
|
||||||
|
self.excursion.groups.add(self.group)
|
||||||
|
self.excursion.save()
|
||||||
|
|
||||||
|
# Create training category and training
|
||||||
|
self.training_category = TrainingCategory.objects.create(
|
||||||
|
name='Test Training',
|
||||||
|
permission_needed=False
|
||||||
|
)
|
||||||
|
|
||||||
|
self.training = MemberTraining.objects.create(
|
||||||
|
member=self.member1,
|
||||||
|
title='Test Training',
|
||||||
|
category=self.training_category,
|
||||||
|
participated=True,
|
||||||
|
passed=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create LJP proposal
|
||||||
|
self.ljp_proposal = LJPProposal.objects.create(
|
||||||
|
title='Test LJP',
|
||||||
|
excursion=self.excursion
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create statement
|
||||||
|
self.statement_unsubmitted = Statement.objects.create(
|
||||||
|
short_description='Unsubmitted Statement',
|
||||||
|
excursion=self.excursion,
|
||||||
|
submitted=False
|
||||||
|
)
|
||||||
|
|
||||||
|
self.statement_submitted = Statement.objects.create(
|
||||||
|
short_description='Submitted Statement',
|
||||||
|
submitted=True
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_is_oneself(self):
|
||||||
|
"""Test is_oneself rule - member can identify themselves."""
|
||||||
|
# Same member
|
||||||
|
self.assertTrue(is_oneself(self.user1, self.member1))
|
||||||
|
|
||||||
|
# Different members
|
||||||
|
self.assertFalse(is_oneself(self.user1, self.member2))
|
||||||
|
|
||||||
|
def test_may(self):
|
||||||
|
"""Test `may_` rules."""
|
||||||
|
self.assertTrue(may_view(self.user1, self.member1))
|
||||||
|
self.assertTrue(may_change(self.user1, self.member1))
|
||||||
|
self.assertTrue(may_delete(self.user1, self.member1))
|
||||||
|
|
||||||
|
def test_is_own_training(self):
|
||||||
|
"""Test is_own_training rule - member can access their own training."""
|
||||||
|
# Own training
|
||||||
|
self.assertTrue(is_own_training(self.user1, self.training))
|
||||||
|
# Other member's training
|
||||||
|
self.assertFalse(is_own_training(self.user2, self.training))
|
||||||
|
|
||||||
|
def test_is_leader_of_excursion(self):
|
||||||
|
"""Test is_leader_of_excursion rule for LJP proposals."""
|
||||||
|
# LJP proposal with excursion - member3 is not a leader
|
||||||
|
self.assertFalse(is_leader_of_excursion(self.user3, self.ljp_proposal))
|
||||||
|
# Directly pass an excursion
|
||||||
|
self.assertTrue(is_leader_of_excursion(self.user1, self.excursion))
|
||||||
|
|
||||||
|
def test_is_leader(self):
|
||||||
|
"""Test is_leader rule for excursions."""
|
||||||
|
# Direct excursion leader
|
||||||
|
self.assertTrue(is_leader(self.user1, self.excursion))
|
||||||
|
|
||||||
|
# Group leader (member2 is leader of group that is part of excursion)
|
||||||
|
self.assertTrue(is_leader(self.user2, self.excursion))
|
||||||
|
|
||||||
|
# member3 is unrelated
|
||||||
|
self.assertFalse(is_leader(self.user3, self.excursion))
|
||||||
|
|
||||||
|
# Test user without member attribute
|
||||||
|
user_no_member = User.objects.create_user(username='nomember', email='nomember@example.com')
|
||||||
|
self.assertFalse(is_leader(user_no_member, self.excursion))
|
||||||
|
|
||||||
|
# Test member without pk attribute
|
||||||
|
class MemberNoPk:
|
||||||
|
pass
|
||||||
|
member_no_pk = MemberNoPk()
|
||||||
|
self.assertFalse(_is_leader(member_no_pk, self.excursion))
|
||||||
|
|
||||||
|
# Test member with None pk
|
||||||
|
class MemberNonePk:
|
||||||
|
pk = None
|
||||||
|
member_none_pk = MemberNonePk()
|
||||||
|
self.assertFalse(_is_leader(member_none_pk, self.excursion))
|
||||||
|
|
||||||
|
def test_statement_not_submitted(self):
|
||||||
|
"""Test statement_not_submitted rule."""
|
||||||
|
# Unsubmitted statement with excursion
|
||||||
|
self.assertTrue(statement_not_submitted(self.user1, self.excursion))
|
||||||
|
|
||||||
|
# Submitted statement
|
||||||
|
self.excursion.statement = self.statement_submitted
|
||||||
|
self.excursion.save()
|
||||||
|
self.assertFalse(statement_not_submitted(self.user1, self.excursion))
|
||||||
|
|
||||||
|
# Excursion without statement
|
||||||
|
excursion_no_statement = Freizeit.objects.create(
|
||||||
|
name='No Statement Excursion',
|
||||||
|
tour_type=GEMEINSCHAFTS_TOUR,
|
||||||
|
kilometers_traveled=10,
|
||||||
|
difficulty=1
|
||||||
|
)
|
||||||
|
self.assertFalse(statement_not_submitted(self.user1, excursion_no_statement))
|
||||||
|
|
||||||
|
# Test the excursion.statement is None case
|
||||||
|
# Create a special test object to directly trigger
|
||||||
|
class ExcursionWithNoneStatement:
|
||||||
|
def __init__(self):
|
||||||
|
self.statement = None
|
||||||
|
# if excursion.statement is None: return False
|
||||||
|
self.assertFalse(statement_not_submitted(self.user1, ExcursionWithNoneStatement()))
|
||||||
@ -0,0 +1,141 @@
|
|||||||
|
from unittest.mock import patch, MagicMock
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from ..models import MemberWaitingList, Freizeit, Group, DIVERSE, GEMEINSCHAFTS_TOUR
|
||||||
|
from ..tasks import ask_for_waiting_confirmation, send_crisis_intervention_list, send_notification_crisis_intervention_list
|
||||||
|
from mailer.models import EmailAddress
|
||||||
|
|
||||||
|
|
||||||
|
class TasksTestCase(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
# Create test email address
|
||||||
|
self.email_address = EmailAddress.objects.create(name='test@example.com')
|
||||||
|
|
||||||
|
# Create test group
|
||||||
|
self.group = Group.objects.create(name='Test Group')
|
||||||
|
self.group.contact_email = self.email_address
|
||||||
|
self.group.save()
|
||||||
|
|
||||||
|
# Create test waiters
|
||||||
|
now = timezone.now()
|
||||||
|
old_confirmation = now - timezone.timedelta(days=settings.WAITING_CONFIRMATION_FREQUENCY + 1)
|
||||||
|
old_reminder = now - timezone.timedelta(days=settings.CONFIRMATION_REMINDER_FREQUENCY + 1)
|
||||||
|
|
||||||
|
self.waiter1 = MemberWaitingList.objects.create(
|
||||||
|
prename='Test',
|
||||||
|
lastname='Waiter1',
|
||||||
|
birth_date=now.date(),
|
||||||
|
email='waiter1@example.com',
|
||||||
|
gender=DIVERSE,
|
||||||
|
last_wait_confirmation=old_confirmation,
|
||||||
|
last_reminder=old_reminder,
|
||||||
|
sent_reminders=0
|
||||||
|
)
|
||||||
|
|
||||||
|
self.waiter2 = MemberWaitingList.objects.create(
|
||||||
|
prename='Test',
|
||||||
|
lastname='Waiter2',
|
||||||
|
birth_date=now.date(),
|
||||||
|
email='waiter2@example.com',
|
||||||
|
gender=DIVERSE,
|
||||||
|
last_wait_confirmation=old_confirmation,
|
||||||
|
last_reminder=old_reminder,
|
||||||
|
sent_reminders=settings.MAX_REMINDER_COUNT - 1
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create waiter that shouldn't receive reminder (too recent confirmation)
|
||||||
|
self.waiter3 = MemberWaitingList.objects.create(
|
||||||
|
prename='Test',
|
||||||
|
lastname='Waiter3',
|
||||||
|
birth_date=now.date(),
|
||||||
|
email='waiter3@example.com',
|
||||||
|
gender=DIVERSE,
|
||||||
|
last_wait_confirmation=now,
|
||||||
|
last_reminder=old_reminder,
|
||||||
|
sent_reminders=0
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create waiter that shouldn't receive reminder (max reminders reached)
|
||||||
|
self.waiter4 = MemberWaitingList.objects.create(
|
||||||
|
prename='Test',
|
||||||
|
lastname='Waiter4',
|
||||||
|
birth_date=now.date(),
|
||||||
|
email='waiter4@example.com',
|
||||||
|
gender=DIVERSE,
|
||||||
|
last_wait_confirmation=old_confirmation,
|
||||||
|
last_reminder=old_reminder,
|
||||||
|
sent_reminders=settings.MAX_REMINDER_COUNT
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create test excursions
|
||||||
|
today = timezone.now().date()
|
||||||
|
tomorrow = today + timezone.timedelta(days=1)
|
||||||
|
|
||||||
|
self.excursion_today_not_sent = Freizeit.objects.create(
|
||||||
|
name='Today Excursion 1',
|
||||||
|
date=timezone.now().replace(hour=10, minute=0, second=0, microsecond=0),
|
||||||
|
tour_type=GEMEINSCHAFTS_TOUR,
|
||||||
|
kilometers_traveled=10,
|
||||||
|
difficulty=1,
|
||||||
|
crisis_intervention_list_sent=False,
|
||||||
|
notification_crisis_intervention_list_sent=False
|
||||||
|
)
|
||||||
|
|
||||||
|
self.excursion_today_sent = Freizeit.objects.create(
|
||||||
|
name='Today Excursion 2',
|
||||||
|
date=timezone.now().replace(hour=14, minute=0, second=0, microsecond=0),
|
||||||
|
tour_type=GEMEINSCHAFTS_TOUR,
|
||||||
|
kilometers_traveled=10,
|
||||||
|
difficulty=1,
|
||||||
|
crisis_intervention_list_sent=True,
|
||||||
|
notification_crisis_intervention_list_sent=True
|
||||||
|
)
|
||||||
|
|
||||||
|
self.excursion_tomorrow_not_sent = Freizeit.objects.create(
|
||||||
|
name='Tomorrow Excursion 1',
|
||||||
|
date=(timezone.now() + timezone.timedelta(days=1)).replace(hour=10, minute=0, second=0, microsecond=0),
|
||||||
|
tour_type=GEMEINSCHAFTS_TOUR,
|
||||||
|
kilometers_traveled=10,
|
||||||
|
difficulty=1,
|
||||||
|
crisis_intervention_list_sent=False,
|
||||||
|
notification_crisis_intervention_list_sent=False
|
||||||
|
)
|
||||||
|
|
||||||
|
self.excursion_tomorrow_sent = Freizeit.objects.create(
|
||||||
|
name='Tomorrow Excursion 2',
|
||||||
|
date=(timezone.now() + timezone.timedelta(days=1)).replace(hour=14, minute=0, second=0, microsecond=0),
|
||||||
|
tour_type=GEMEINSCHAFTS_TOUR,
|
||||||
|
kilometers_traveled=10,
|
||||||
|
difficulty=1,
|
||||||
|
crisis_intervention_list_sent=True,
|
||||||
|
notification_crisis_intervention_list_sent=True
|
||||||
|
)
|
||||||
|
|
||||||
|
@patch.object(MemberWaitingList, 'ask_for_wait_confirmation')
|
||||||
|
def test_ask_for_waiting_confirmation(self, mock_ask):
|
||||||
|
"""Test ask_for_waiting_confirmation task calls correct waiters."""
|
||||||
|
result = ask_for_waiting_confirmation()
|
||||||
|
|
||||||
|
# Should call ask_for_wait_confirmation for waiter1 and waiter2 only
|
||||||
|
self.assertEqual(result, 2)
|
||||||
|
self.assertEqual(mock_ask.call_count, 2)
|
||||||
|
|
||||||
|
@patch.object(Freizeit, 'send_crisis_intervention_list')
|
||||||
|
def test_send_crisis_intervention_list(self, mock_send):
|
||||||
|
"""Test send_crisis_intervention_list task calls correct excursions."""
|
||||||
|
result = send_crisis_intervention_list()
|
||||||
|
|
||||||
|
# Should call send_crisis_intervention_list for today's excursions that haven't been sent
|
||||||
|
self.assertEqual(result, 1)
|
||||||
|
self.assertEqual(mock_send.call_count, 1)
|
||||||
|
|
||||||
|
@patch.object(Freizeit, 'notify_leaders_crisis_intervention_list')
|
||||||
|
def test_send_notification_crisis_intervention_list(self, mock_notify):
|
||||||
|
"""Test send_notification_crisis_intervention_list task calls correct excursions."""
|
||||||
|
result = send_notification_crisis_intervention_list()
|
||||||
|
|
||||||
|
# Should call notify_leaders_crisis_intervention_list for tomorrow's excursions that haven't been sent
|
||||||
|
self.assertEqual(result, 1)
|
||||||
|
self.assertEqual(mock_notify.call_count, 1)
|
||||||
@ -0,0 +1,110 @@
|
|||||||
|
from unittest import skip
|
||||||
|
from http import HTTPStatus
|
||||||
|
from django.test import TestCase, Client
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
|
from mailer.models import EmailAddress
|
||||||
|
from ..models import Member, Group, InvitationToGroup, MemberWaitingList, DIVERSE
|
||||||
|
|
||||||
|
|
||||||
|
class ConfirmInvitationViewTestCase(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.client = Client()
|
||||||
|
|
||||||
|
# Create an email address for the group
|
||||||
|
self.email_address = EmailAddress.objects.create(name='testmail')
|
||||||
|
|
||||||
|
# Create a test group
|
||||||
|
self.group = Group.objects.create(name='Test Group')
|
||||||
|
self.group.contact_email = self.email_address
|
||||||
|
self.group.save()
|
||||||
|
|
||||||
|
# Create a waiting list entry
|
||||||
|
self.waiter = MemberWaitingList.objects.create(
|
||||||
|
prename='Waiter',
|
||||||
|
lastname='User',
|
||||||
|
birth_date=timezone.now().date(),
|
||||||
|
email='waiter@example.com',
|
||||||
|
gender=DIVERSE,
|
||||||
|
wait_confirmation_key='test_wait_key',
|
||||||
|
wait_confirmation_key_expire=timezone.now() + timezone.timedelta(days=1)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create an invitation
|
||||||
|
self.invitation = InvitationToGroup.objects.create(
|
||||||
|
waiter=self.waiter,
|
||||||
|
group=self.group,
|
||||||
|
key='test_invitation_key',
|
||||||
|
date=timezone.now().date()
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_confirm_invitation_get_valid_key(self):
|
||||||
|
"""Test GET request with valid key shows invitation confirmation page."""
|
||||||
|
url = reverse('members:confirm_invitation')
|
||||||
|
response = self.client.get(url, {'key': 'test_invitation_key'})
|
||||||
|
self.assertEqual(response.status_code, HTTPStatus.OK)
|
||||||
|
self.assertContains(response, _('Confirm trial group meeting invitation'))
|
||||||
|
self.assertContains(response, self.group.name)
|
||||||
|
|
||||||
|
def test_confirm_invitation_get_invalid_key(self):
|
||||||
|
"""Test GET request with invalid key shows invalid confirmation page."""
|
||||||
|
url = reverse('members:confirm_invitation')
|
||||||
|
|
||||||
|
# no key
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, HTTPStatus.OK)
|
||||||
|
self.assertContains(response, _('This invitation is invalid or expired.'))
|
||||||
|
|
||||||
|
# invalid key
|
||||||
|
response = self.client.get(url, {'key': 'invalid_key'})
|
||||||
|
self.assertEqual(response.status_code, HTTPStatus.OK)
|
||||||
|
self.assertContains(response, _('This invitation is invalid or expired.'))
|
||||||
|
|
||||||
|
def test_confirm_invitation_get_rejected_invitation(self):
|
||||||
|
"""Test GET request with rejected invitation shows invalid confirmation page."""
|
||||||
|
self.invitation.rejected = True
|
||||||
|
self.invitation.save()
|
||||||
|
|
||||||
|
url = reverse('members:confirm_invitation')
|
||||||
|
response = self.client.get(url, {'key': self.invitation.key})
|
||||||
|
self.assertEqual(response.status_code, HTTPStatus.OK)
|
||||||
|
self.assertContains(response, _('This invitation is invalid or expired.'))
|
||||||
|
|
||||||
|
def test_confirm_invitation_get_expired_invitation(self):
|
||||||
|
"""Test GET request with expired invitation shows invalid confirmation page."""
|
||||||
|
# Set invitation date to more than 30 days ago to make it expired
|
||||||
|
self.invitation.date = timezone.now().date() - timezone.timedelta(days=31)
|
||||||
|
self.invitation.save()
|
||||||
|
|
||||||
|
url = reverse('members:confirm_invitation')
|
||||||
|
response = self.client.get(url, {'key': self.invitation.key})
|
||||||
|
self.assertEqual(response.status_code, HTTPStatus.OK)
|
||||||
|
self.assertContains(response, _('This invitation is invalid or expired.'))
|
||||||
|
|
||||||
|
def test_confirm_invitation_post_invalid_key(self):
|
||||||
|
"""Test POST request with invalid key shows invalid confirmation page."""
|
||||||
|
url = reverse('members:confirm_invitation')
|
||||||
|
|
||||||
|
# no key
|
||||||
|
response = self.client.post(url)
|
||||||
|
self.assertEqual(response.status_code, HTTPStatus.OK)
|
||||||
|
self.assertContains(response, _('This invitation is invalid or expired.'))
|
||||||
|
|
||||||
|
# invalid key
|
||||||
|
response = self.client.post(url, {'key': 'invalid_key'})
|
||||||
|
self.assertEqual(response.status_code, HTTPStatus.OK)
|
||||||
|
self.assertContains(response, _('This invitation is invalid or expired.'))
|
||||||
|
|
||||||
|
def test_confirm_invitation_post_valid_key(self):
|
||||||
|
"""Test POST request with valid key confirms invitation and shows success page."""
|
||||||
|
url = reverse('members:confirm_invitation')
|
||||||
|
response = self.client.post(url, {'key': self.invitation.key})
|
||||||
|
self.assertEqual(response.status_code, HTTPStatus.OK)
|
||||||
|
self.assertContains(response, _('Invitation confirmed'))
|
||||||
|
self.assertContains(response, self.group.name)
|
||||||
|
|
||||||
|
# Verify invitation was not marked as rejected (confirm() sets rejected=False)
|
||||||
|
self.invitation.refresh_from_db()
|
||||||
|
self.assertFalse(self.invitation.rejected)
|
||||||
Loading…
Reference in New Issue