Add validated editorial content schema
This commit is contained in:
1
apps/content/tests/__init__.py
Normal file
1
apps/content/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
0
apps/content/tests/integration/__init__.py
Normal file
0
apps/content/tests/integration/__init__.py
Normal file
106
apps/content/tests/integration/test_models.py
Normal file
106
apps/content/tests/integration/test_models.py
Normal file
@@ -0,0 +1,106 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user