Files
RosterChief/news/tests.py
Bernard Siebens 3e0c63ec36 Add a news app: coach_manager authoring, team tagging, photos, editor release flow
News and NewsPhoto (team-tagged instead of categorised, one photo taggable
as main via a partial unique constraint), gated per club/services/access.py:
any current-season coach_manager, EDITOR, or ADMIN can draft and edit a news
item; only EDITOR/ADMIN can publish it, or edit it once it's live. Publishing
takes a date so it can be scheduled ahead of time rather than only right now.

Authoring/release only for now -- no member-facing reading page or public API
yet, the visibility field (internal/external/both) is there for when those land.
2026-08-03 18:46:37 +02:00

101 lines
3.7 KiB
Python

import datetime
from django.core.files.uploadedfile import SimpleUploadedFile
from django.db import IntegrityError
from django.test import TestCase
from django.utils import timezone
from club.models import Club
from .models import News, NewsPhoto
def make_photo(news_item, *, is_main=False):
image = SimpleUploadedFile("photo.jpg", b"fake-image-bytes", content_type="image/jpeg")
return NewsPhoto.objects.create(news_item=news_item, image=image, is_main=is_main)
class NewsModelTests(TestCase):
def setUp(self):
self.club = Club.objects.create(name="Ajax United", slug="ajax-united")
def test_slug_is_derived_from_title(self):
item = News.objects.create(club=self.club, title="Season Kickoff", body="Body text.")
self.assertEqual(item.slug, "season-kickoff")
def test_slug_is_unique_per_club_not_globally(self):
News.objects.create(club=self.club, title="Season Kickoff", body="First.")
second = News.objects.create(club=self.club, title="Season Kickoff", body="Second.")
self.assertEqual(second.slug, "season-kickoff-2")
def test_two_clubs_can_share_the_same_slug(self):
other_club = Club.objects.create(name="Rival FC", slug="rival-fc")
News.objects.create(club=self.club, title="Season Kickoff", body="First.")
other = News.objects.create(club=other_club, title="Season Kickoff", body="Other club.")
self.assertEqual(other.slug, "season-kickoff")
def test_defaults_to_draft_and_internal(self):
item = News.objects.create(club=self.club, title="Draft item", body="Body.")
self.assertEqual(item.status, News.Status.DRAFT)
self.assertEqual(item.visibility, News.Visibility.INTERNAL)
self.assertIsNone(item.published_at)
def test_publish_defaults_the_publish_date_to_now(self):
item = News.objects.create(club=self.club, title="Item", body="Body.")
item.publish()
self.assertEqual(item.status, News.Status.PUBLISHED)
self.assertIsNotNone(item.published_at)
self.assertFalse(item.is_scheduled)
def test_publish_accepts_a_future_date_and_is_scheduled(self):
item = News.objects.create(club=self.club, title="Item", body="Body.")
future = timezone.now() + datetime.timedelta(days=7)
item.publish(at=future)
self.assertEqual(item.status, News.Status.PUBLISHED)
self.assertEqual(item.published_at, future)
self.assertTrue(item.is_scheduled)
def test_unpublish_clears_the_publish_date(self):
item = News.objects.create(club=self.club, title="Item", body="Body.")
item.publish()
item.unpublish()
self.assertEqual(item.status, News.Status.DRAFT)
self.assertIsNone(item.published_at)
class NewsPhotoModelTests(TestCase):
def setUp(self):
self.club = Club.objects.create(name="Ajax United", slug="ajax-united")
self.item = News.objects.create(club=self.club, title="Match report", body="Body.")
def test_a_second_main_photo_is_rejected_at_the_database_level(self):
make_photo(self.item, is_main=True)
with self.assertRaises(IntegrityError):
make_photo(self.item, is_main=True)
def test_two_non_main_photos_are_fine(self):
make_photo(self.item, is_main=False)
make_photo(self.item, is_main=False)
self.assertEqual(self.item.photos.count(), 2)
def test_two_different_news_items_can_each_have_a_main_photo(self):
other_item = News.objects.create(club=self.club, title="Other item", body="Body.")
make_photo(self.item, is_main=True)
make_photo(other_item, is_main=True)
self.assertEqual(NewsPhoto.objects.filter(is_main=True).count(), 2)