2020from django .core .management import call_command as django_call_command
2121from django .core .management .base import CommandError
2222from django .db import connection
23- from django .test import SimpleTestCase , TestCase , override_settings
23+ from django .test import RequestFactory , SimpleTestCase , TestCase , override_settings
24+ from django .utils import timezone
2425from wagtail .models import GroupPagePermission , Locale , Page , Revision , Site
2526from wagtail .permission_policies .pages import PagePermissionPolicy
2627
2728from content .models import (
2829 PlacePage ,
2930 PlacesIndexPage ,
31+ PreviewSnapshot ,
3032 StaticIndexPage ,
3133 StaticPage ,
3234 TagPage ,
@@ -623,22 +625,22 @@ def test_index_page_urls(self):
623625 # No frontend listing for static pages: not routable, no "View live".
624626 self .assertIsNone (self .static_index .url )
625627
626- def test_preview_disabled (self ):
628+ def test_index_preview_disabled (self ):
627629 # No Wagtail template exists; previewing raised TemplateDoesNotExist.
628- for page in (
629- self .tag ,
630- self .place ,
631- self .static ,
632- self .tags_index ,
633- self .places_index ,
634- self .static_index ,
635- ):
630+ # Content pages preview headlessly instead (PreviewTests); index
631+ # pages have nothing to preview.
632+ for page in (self .tags_index , self .places_index , self .static_index ):
636633 self .assertEqual (page .preview_modes , [])
637634 self .assertFalse (page .is_previewable ())
638635
636+ def test_content_pages_are_previewable (self ):
637+ for page in (self .tag , self .place , self .static ):
638+ self .assertTrue (page .is_previewable ())
639+
639640 def test_admin_editor_shows_frontend_live_url (self ):
640641 # The page editor (where Preview/"View live" 500'd) renders, offers
641- # no preview panel, and links "View live" at the frontend URL.
642+ # the headless preview panel, and links "View live" at the frontend
643+ # URL.
642644 self .tag .save_revision (clean = False ).publish ()
643645 get_user_model ().objects .create_superuser (
644646 username = "root@districtr.org" ,
@@ -652,7 +654,7 @@ def test_admin_editor_shows_frontend_live_url(self):
652654 self .assertEqual (response .status_code , 200 )
653655 html = response .content .decode ()
654656 self .assertIn ("https://beta.districtr.org/portal/fair-maps" , html )
655- self .assertNotIn ('data-side-panel-toggle="preview"' , html )
657+ self .assertIn ('data-side-panel-toggle="preview"' , html )
656658
657659
658660# ---------------------------------------------------------------------------
@@ -1175,3 +1177,49 @@ def test_json_report(self):
11751177 )
11761178 self .assertTrue (entry ["text_ok" ])
11771179 self .assertEqual (entry ["docs" ]["published" ]["output_blocks" ]["plan_gallery" ], 1 )
1180+
1181+
1182+ @override_settings (FRONTEND_URL = "https://beta.districtr.org" )
1183+ class PreviewTests (TestCase ):
1184+ """Headless preview: serve_preview parks a serialized snapshot and
1185+ redirects to the frontend, which fetches it back by token."""
1186+
1187+ @classmethod
1188+ def setUpTestData (cls ):
1189+ en = Locale .objects .get (language_code = "en" )
1190+ static_index = StaticIndexPage .objects .get (locale = en )
1191+ cls .draft = StaticPage (title = "Draft About" , slug = "about-draft" , live = False )
1192+ static_index .add_child (instance = cls .draft )
1193+
1194+ def test_serve_preview_snapshots_and_redirects (self ):
1195+ response = self .draft .serve_preview (RequestFactory ().get ("/" ), "frontend" )
1196+ self .assertEqual (response .status_code , 302 )
1197+ prefix = "https://beta.districtr.org/preview/"
1198+ self .assertTrue (response ["Location" ].startswith (prefix ))
1199+
1200+ token = response ["Location" ].removeprefix (prefix )
1201+ snapshot = PreviewSnapshot .objects .get (token = token )
1202+ self .assertEqual (snapshot .data ["type" ], "static" )
1203+ self .assertEqual (snapshot .data ["content" ]["title" ], "Draft About" )
1204+ self .assertEqual (snapshot .data ["available_languages" ], ["en" ])
1205+
1206+ def test_preview_endpoint_serves_fresh_and_404s_expired (self ):
1207+ response = self .draft .serve_preview (RequestFactory ().get ("/" ), "frontend" )
1208+ token = response ["Location" ].rsplit ("/" , 1 )[- 1 ]
1209+ url = f"/api/content/preview/{ token } "
1210+
1211+ payload = self .client .get (url ).json ()
1212+ self .assertEqual (payload ["content" ]["slug" ], "about-draft" )
1213+
1214+ PreviewSnapshot .objects .filter (token = token ).update (
1215+ created_at = timezone .now () - PreviewSnapshot .TTL * 2
1216+ )
1217+ self .assertEqual (self .client .get (url ).status_code , 404 )
1218+
1219+ def test_prune_on_mint_drops_stale_snapshots (self ):
1220+ stale = PreviewSnapshot .objects .create (data = {})
1221+ PreviewSnapshot .objects .filter (token = stale .token ).update (
1222+ created_at = timezone .now () - PreviewSnapshot .TTL * 2
1223+ )
1224+ self .draft .serve_preview (RequestFactory ().get ("/" ), "frontend" )
1225+ self .assertFalse (PreviewSnapshot .objects .filter (token = stale .token ).exists ())
0 commit comments