parent
44354cb681
commit
7ea500ebaa
@ -1 +1,4 @@
|
||||
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