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="", 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="") 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": ""}]) 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()