107 lines
5.2 KiB
Python
107 lines
5.2 KiB
Python
import io
|
|
|
|
import pytest
|
|
from django.core.exceptions import ValidationError
|
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
|
from django.db import IntegrityError, transaction
|
|
from PIL import Image
|
|
|
|
from apps.content.models import EditorialPage, MediaAsset, PageRevision, PageSection, SiteIdentity, SiteRevision
|
|
|
|
|
|
pytestmark = [pytest.mark.django_db, pytest.mark.integration]
|
|
|
|
|
|
def test_single_site_and_fixed_page_slugs():
|
|
SiteIdentity.objects.create()
|
|
with pytest.raises(IntegrityError), transaction.atomic():
|
|
SiteIdentity.objects.create()
|
|
|
|
page = EditorialPage(slug="home", page_kind="sections")
|
|
page.full_clean()
|
|
page.save()
|
|
with pytest.raises(ValidationError):
|
|
EditorialPage(slug="other", page_kind="sections").full_clean()
|
|
with pytest.raises(ValidationError):
|
|
EditorialPage(slug="events", page_kind="sections").full_clean()
|
|
|
|
|
|
def test_site_revision_rejects_unsafe_text_and_contact_links():
|
|
site = SiteIdentity.objects.create()
|
|
navigation = [{"route_id": route, "label": route, "position": index, "visible": True} for index, route in enumerate(("home", "about", "events", "blog"))]
|
|
revision = SiteRevision(site=site, number=1, official_name="<script>alert(1)</script>", short_name="ACE", navigation=navigation)
|
|
with pytest.raises(ValidationError):
|
|
revision.full_clean()
|
|
|
|
revision.official_name = "Association"
|
|
revision.contacts = [{"kind": "telegram", "label": "Join", "url": "javascript:alert(1)", "position": 0, "visible": True}]
|
|
with pytest.raises(ValidationError):
|
|
revision.full_clean()
|
|
revision.contacts = [{"kind": ["telegram"], "label": "Join", "url": "https://t.me/example", "position": 0, "visible": True}]
|
|
with pytest.raises(ValidationError):
|
|
revision.full_clean()
|
|
revision.contacts = [{"kind": "telegram", "label": "Join", "url": "https://t.me/example", "position": 0, "visible": True}]
|
|
revision.full_clean()
|
|
|
|
|
|
def test_section_registry_and_plain_text():
|
|
page = EditorialPage.objects.create(slug="home", page_kind="sections")
|
|
revision = PageRevision.objects.create(page=page, number=1, title="Home", seo_title="Home", seo_description="About us")
|
|
section = PageSection(revision=revision, key="hero", section_type="hero", position=0, heading="Welcome", body="<img src=x onerror=alert(1)>")
|
|
with pytest.raises(ValidationError):
|
|
section.full_clean()
|
|
section.body = "Plain introduction"
|
|
section.full_clean()
|
|
section.image_alt = "Orphan alt"
|
|
with pytest.raises(ValidationError):
|
|
section.full_clean()
|
|
section.image_alt = ""
|
|
section.primary_action = {"label": "Go", "route_id": "javascript:alert(1)"}
|
|
with pytest.raises(ValidationError):
|
|
section.full_clean()
|
|
section.primary_action = {"label": "Go", "route_id": ["about"]}
|
|
with pytest.raises(ValidationError):
|
|
section.full_clean()
|
|
with pytest.raises(ValidationError):
|
|
PageSection(revision=revision, key="mission", section_type="mission", position=1, heading="Mission").full_clean()
|
|
|
|
|
|
def test_revision_numbers_and_section_keys_are_unique():
|
|
page = EditorialPage.objects.create(slug="about", page_kind="sections")
|
|
PageRevision.objects.create(page=page, number=1, title="About", seo_title="About", seo_description="Association")
|
|
with pytest.raises(IntegrityError), transaction.atomic():
|
|
PageRevision.objects.create(page=page, number=1, title="Again", seo_title="Again", seo_description="Association")
|
|
|
|
|
|
def test_section_entries_reject_markup_and_excess():
|
|
page = EditorialPage.objects.create(slug="about", page_kind="sections")
|
|
revision = PageRevision.objects.create(page=page, number=1, title="About", seo_title="About", seo_description="Association")
|
|
section = PageSection(revision=revision, key="faq", section_type="faq", position=1, heading="Questions", entries=[{"title": "Question", "text": "<script>alert(1)</script>"}])
|
|
with pytest.raises(ValidationError):
|
|
section.full_clean()
|
|
section.entries = [{"title": f"Question {index}", "text": "Answer"} for index in range(21)]
|
|
with pytest.raises(ValidationError):
|
|
section.full_clean()
|
|
|
|
|
|
def test_media_rejects_invalid_signature_and_oversize(settings, tmp_path):
|
|
settings.MEDIA_ROOT = tmp_path
|
|
bad = MediaAsset(file=SimpleUploadedFile("fake.png", b"not an image", content_type="image/png"), alt="Logo", source="owner", license="owned")
|
|
with pytest.raises(ValidationError):
|
|
bad.full_clean()
|
|
|
|
buffer = io.BytesIO()
|
|
Image.new("RGB", (10, 10)).save(buffer, format="PNG")
|
|
good = MediaAsset(file=SimpleUploadedFile("logo.png", buffer.getvalue(), content_type="image/png"), alt="Association logo", source="owner", license="owned")
|
|
good.full_clean()
|
|
good.save()
|
|
assert good.width == 10 and good.height == 10
|
|
|
|
huge = MediaAsset(file=SimpleUploadedFile("huge.png", b"x" * (5 * 1024 * 1024 + 1), content_type="image/png"), alt="Huge", source="owner", license="owned")
|
|
with pytest.raises(ValidationError):
|
|
huge.full_clean()
|
|
|
|
mismatch = MediaAsset(file=SimpleUploadedFile("logo.jpg", buffer.getvalue(), content_type="image/jpeg"), alt="Logo", source="owner", license="owned")
|
|
with pytest.raises(ValidationError):
|
|
mismatch.full_clean()
|