import datetime from django.contrib.auth import get_user_model from django.test import TestCase, override_settings from django.urls import reverse from club.models import Club, ClubMembership, Season from members.models import Family, FamilyMembership, Member from .models import PushSubscription from .services.icons import render_fallback_icon User = get_user_model() def make_club(**kwargs): return Club.objects.create(name="Ajax United", slug="ajax-united", secondary_color="#e4002b", **kwargs) @override_settings(ROSTERCHIEF_BASE_DOMAIN="rosterchief.app", ALLOWED_HOSTS=["rosterchief.app", "ajax-united.rosterchief.app", "testserver"]) class MobileShellTests(TestCase): """The app shell (base.html) plus the PWA plumbing every screen sits on.""" @classmethod def setUpTestData(cls): cls.club = make_club() Season.objects.create(club=cls.club, start_date=datetime.date(2025, 8, 1), end_date=datetime.date(2026, 5, 31)) cls.user = User.objects.create_user(email="parent@example.com", password="pw-secret-123") cls.member = Member.objects.create(first_name="Lars", last_name="Bakker", email="parent@example.com", user=cls.user) ClubMembership.objects.create(club=cls.club, member=cls.member, season=Season.objects.first()) def _get(self, url_name, **kwargs): return self.client.get(reverse(f"mobile:{url_name}", kwargs=kwargs), HTTP_HOST="ajax-united.rosterchief.app") def test_home_requires_login(self): response = self._get("home") self.assertEqual(response.status_code, 302) def test_home_renders_the_app_shell_when_signed_in(self): self.client.force_login(self.user) response = self._get("home") self.assertEqual(response.status_code, 200) self.assertContains(response, self.club.name) self.assertContains(response, 'rel="manifest"') def test_manifest_names_the_club_and_points_at_its_icon(self): response = self._get("manifest") self.assertEqual(response["Content-Type"], "application/manifest+json") self.assertEqual(response.json()["name"], self.club.name) self.assertIn("icon", response.json()["icons"][0]["src"]) def test_icon_falls_back_to_a_rendered_png_without_a_logo(self): response = self._get("icon", size=192) self.assertEqual(response.status_code, 200) self.assertEqual(response["Content-Type"], "image/png") def test_service_worker_is_served_at_the_app_scope(self): response = self.client.get("/app/sw.js", HTTP_HOST="ajax-united.rosterchief.app") self.assertEqual(response.status_code, 200) self.assertIn("javascript", response["Content-Type"]) self.assertIn("addEventListener", response.content.decode()) def test_person_switcher_lists_managed_children_alongside_me(self): family = Family.objects.create(name="Bakker") FamilyMembership.objects.create(family=family, member=self.member, role=FamilyMembership.FamilyRole.PARENT) child = Member.objects.create(first_name="Noor", last_name="Bakker") FamilyMembership.objects.create(family=family, member=child, role=FamilyMembership.FamilyRole.CHILD) ClubMembership.objects.create(club=self.club, member=child, season=Season.objects.first()) self.client.force_login(self.user) response = self._get("home") self.assertContains(response, "Noor") def test_scope_person_switches_via_the_as_query_param(self): family = Family.objects.create(name="Bakker") FamilyMembership.objects.create(family=family, member=self.member, role=FamilyMembership.FamilyRole.PARENT) child = Member.objects.create(first_name="Noor", last_name="Bakker") FamilyMembership.objects.create(family=family, member=child, role=FamilyMembership.FamilyRole.CHILD) ClubMembership.objects.create(club=self.club, member=child, season=Season.objects.first()) self.client.force_login(self.user) response = self.client.get(reverse("mobile:home") + f"?as={child.pk}", HTTP_HOST="ajax-united.rosterchief.app") self.assertEqual(response.context["scope_person"], child) @override_settings( VAPID_PRIVATE_KEY="", ROSTERCHIEF_BASE_DOMAIN="rosterchief.app", ALLOWED_HOSTS=["rosterchief.app", "ajax-united.rosterchief.app", "testserver"], ) class PushSubscribeViewTests(TestCase): @classmethod def setUpTestData(cls): cls.club = make_club() cls.user = User.objects.create_user(email="parent@example.com", password="pw-secret-123") cls.member = Member.objects.create(first_name="Lars", last_name="Bakker", user=cls.user) def test_subscribing_creates_a_push_subscription_row(self): self.client.force_login(self.user) response = self.client.post( reverse("mobile:push_subscribe"), data={"endpoint": "https://push.example.com/abc", "keys": {"p256dh": "key1", "auth": "key2"}}, content_type="application/json", HTTP_HOST="ajax-united.rosterchief.app", ) self.assertEqual(response.status_code, 200) self.assertTrue(PushSubscription.objects.filter(member=self.member, endpoint="https://push.example.com/abc").exists()) def test_resubscribing_the_same_endpoint_updates_rather_than_duplicates(self): self.client.force_login(self.user) PushSubscription.objects.create(club=self.club, member=self.member, endpoint="https://push.example.com/abc", p256dh="old", auth="old") self.client.post( reverse("mobile:push_subscribe"), data={"endpoint": "https://push.example.com/abc", "keys": {"p256dh": "new", "auth": "new"}}, content_type="application/json", HTTP_HOST="ajax-united.rosterchief.app", ) self.assertEqual(PushSubscription.objects.filter(endpoint="https://push.example.com/abc").count(), 1) self.assertEqual(PushSubscription.objects.get(endpoint="https://push.example.com/abc").p256dh, "new") def test_anonymous_cannot_subscribe(self): response = self.client.post( reverse("mobile:push_subscribe"), data={"endpoint": "https://push.example.com/abc", "keys": {"p256dh": "key1", "auth": "key2"}}, content_type="application/json", HTTP_HOST="ajax-united.rosterchief.app", ) self.assertEqual(response.status_code, 302) class RenderFallbackIconTests(TestCase): def test_renders_a_png_without_a_logo(self): club = make_club() png_bytes = render_fallback_icon(club, size=64) self.assertTrue(png_bytes.startswith(b"\x89PNG"))