Files
Sith/com/tests/test_api.py
T
2026-09-07 13:32:13 +02:00

167 lines
6.0 KiB
Python

from datetime import timedelta
from pathlib import Path
from typing import TYPE_CHECKING
import pytest
from aemark import markdown
from django.conf import settings
from django.contrib.auth.models import Permission
from django.test import Client, TestCase
from django.urls import reverse
from django.utils.timezone import now
from model_bakery import baker, seq
from pytest_django.asserts import assertNumQueries
from com.ics_calendar import IcsCalendar
from com.models import News, NewsDate
from core.models import User
if TYPE_CHECKING:
from django.http import HttpResponse
def accel_redirect_to_file(response: HttpResponse) -> Path | None:
redirect = Path(response.headers.get("X-Accel-Redirect", ""))
if not redirect.is_relative_to(Path("/") / settings.MEDIA_ROOT.stem):
return None
return settings.MEDIA_ROOT / redirect.relative_to(
Path("/") / settings.MEDIA_ROOT.stem
)
@pytest.mark.django_db
class TestInternalCalendar:
@pytest.fixture(autouse=True)
def clear_cache(self):
IcsCalendar._INTERNAL_CALENDAR.unlink(missing_ok=True)
def test_fetch_success(self, client: Client):
response = client.get(reverse("api:calendar_internal"))
assert response.status_code == 200
out_file = accel_redirect_to_file(response)
assert out_file is not None
assert out_file.exists()
@pytest.mark.django_db
class TestModerateNews:
@pytest.mark.parametrize("news_is_published", [True, False])
def test_moderation_ok(self, client: Client, news_is_published: bool): # noqa FBT
user = baker.make(
User, user_permissions=[Permission.objects.get(codename="moderate_news")]
)
# The API call should work even if the news is initially moderated.
# In the latter case, the result should be a noop, rather than an error.
news = baker.make(News, is_published=news_is_published)
initial_moderator = news.moderator
client.force_login(user)
response = client.patch(
reverse("api:moderate_news", kwargs={"news_id": news.id})
)
# if it wasn't moderated, it should now be moderated and the moderator should
# be the user that made the request.
# If it was already moderated, it should be a no-op, but not an error
assert response.status_code == 200
news.refresh_from_db()
assert news.is_published
if not news_is_published:
assert news.moderator == user
else:
assert news.moderator == initial_moderator
def test_moderation_forbidden(self, client: Client):
user = baker.make(User)
news = baker.make(News, is_published=False)
client.force_login(user)
response = client.patch(
reverse("api:moderate_news", kwargs={"news_id": news.id})
)
assert response.status_code == 403
news.refresh_from_db()
assert not news.is_published
@pytest.mark.django_db
class TestDeleteNews:
def test_delete_news_ok(self, client: Client):
user = baker.make(
User, user_permissions=[Permission.objects.get(codename="delete_news")]
)
news = baker.make(News)
client.force_login(user)
response = client.delete(
reverse("api:delete_news", kwargs={"news_id": news.id})
)
assert response.status_code == 200
assert not News.objects.filter(id=news.id).exists()
def test_delete_news_forbidden(self, client: Client):
user = baker.make(User)
news = baker.make(News)
client.force_login(user)
response = client.delete(
reverse("api:delete_news", kwargs={"news_id": news.id})
)
assert response.status_code == 403
assert News.objects.filter(id=news.id).exists()
class TestFetchNewsDates(TestCase):
@classmethod
def setUpTestData(cls):
News.objects.all().delete()
cls.dates = baker.make(
NewsDate,
_quantity=5,
_bulk_create=True,
start_date=seq(value=now(), increment_by=timedelta(days=1)),
end_date=seq(
value=now() + timedelta(hours=2), increment_by=timedelta(days=1)
),
news=iter(
baker.make(News, is_published=True, _quantity=5, _bulk_create=True)
),
)
cls.dates.append(
baker.make(
NewsDate,
start_date=now() + timedelta(days=2, hours=1),
end_date=now() + timedelta(days=2, hours=5),
news=baker.make(News, is_published=True),
)
)
cls.dates.sort(key=lambda d: d.start_date)
def test_num_queries(self):
with assertNumQueries(2):
self.client.get(reverse("api:fetch_news_dates"))
def test_html_format(self):
"""Test that when the summary is asked in html, the summary is in html."""
summary_1 = "# First event\nThere is something happening.\n"
self.dates[0].news.summary = summary_1
self.dates[0].news.save()
summary_2 = (
"# Second event\n"
"There is something happening **for real**.\n"
"Everything is [here](https://youtu.be/dQw4w9WgXcQ)\n"
)
self.dates[1].news.summary = summary_2
self.dates[1].news.save()
response = self.client.get(
reverse("api:fetch_news_dates") + "?page_size=2&text_format=html"
)
assert response.status_code == 200
dates = response.json()["results"]
assert dates[0]["news"]["summary"] == markdown(summary_1)
assert dates[1]["news"]["summary"] == markdown(summary_2)
def test_fetch(self):
after = (now() + timedelta(days=1)).isoformat()
response = self.client.get(
reverse("api:fetch_news_dates", query={"page_size": 3, "after": after})
)
assert response.status_code == 200
dates = response.json()["results"]
assert [d["id"] for d in dates] == [d.id for d in self.dates[1:4]]