from unittest import mock from django.contrib.auth import get_user_model from django.core.management import call_command from django.test import TestCase, override_settings from django.utils import timezone from .markdown import render_markdown from .models import GuidePage, allowed_audiences User = get_user_model() def make_user(email, role): return User.objects.create_user( email=email, password="pass1234", role=role, full_name=email.split("@")[0], is_staff=True, ) def make_page(path, content="# Heading\n\nBody text", title=None): return GuidePage.objects.create( path=path, title=title or path.rsplit("/", 1)[-1], audience=GuidePage.map_audience(path), content=content, updated_at=timezone.now(), ) class GuideAudienceFilterTests(TestCase): def setUp(self): self.shared = make_page("user-guides/index", "# User Guides\n\nWelcome.", title="Landing Page") self.buyer = make_page("user-guides/buyer/getting-started", title="Getting Started Guide") self.buyer2 = make_page("user-guides/buyer/dashboard", title="Dashboard Guide") self.ops = make_page("user-guides/ops/batches", title="Ops Batches Guide") self.admin = make_page("user-guides/admin/overview", title="Admin Overview Guide") self.buyer_user = make_user("buyer@ixg.local", "buyer") self.ops_user = make_user("ops@ixg.local", "ops") self.admin_user = make_user("admin@ixg.local", "admin") def test_buyer_index_shows_shared_and_buyer_only(self): self.client.force_login(self.buyer_user) resp = self.client.get("/portal/help/") self.assertEqual(resp.status_code, 200) content = resp.content.decode() self.assertContains(resp, self.shared.title) self.assertContains(resp, self.buyer.title) self.assertContains(resp, self.buyer2.title) self.assertNotContains(resp, self.ops.title) self.assertNotContains(resp, self.admin.title) def test_ops_index_shows_shared_and_ops_only(self): self.client.force_login(self.ops_user) resp = self.client.get("/ops/help/") self.assertEqual(resp.status_code, 200) content = resp.content.decode() self.assertContains(resp, self.shared.title) self.assertContains(resp, self.ops.title) self.assertNotContains(resp, self.buyer.title) self.assertNotContains(resp, self.admin.title) def test_admin_index_on_portal_lists_buyer_guides_only(self): """Portal scoping wins over role: the buyer portal shows buyer guides.""" self.client.force_login(self.admin_user) resp = self.client.get("/portal/help/") self.assertEqual(resp.status_code, 200) self.assertContains(resp, self.shared.title) self.assertContains(resp, self.buyer.title) self.assertContains(resp, self.buyer2.title) self.assertNotContains(resp, self.ops.title) self.assertNotContains(resp, self.admin.title) def test_admin_index_on_ops_lists_ops_and_admin_guides(self): self.client.force_login(self.admin_user) resp = self.client.get("/ops/help/") self.assertEqual(resp.status_code, 200) self.assertContains(resp, self.shared.title) self.assertContains(resp, self.ops.title) self.assertContains(resp, self.admin.title) self.assertNotContains(resp, self.buyer.title) def test_superuser_follows_the_same_portal_scoping(self): superuser = make_user("root@ixg.local", "admin") superuser.is_superuser = True superuser.save(update_fields=["is_superuser"]) self.client.force_login(superuser) resp = self.client.get("/portal/help/") self.assertNotContains(resp, self.ops.title) resp = self.client.get("/ops/help/") self.assertContains(resp, self.admin.title) def test_ops_only_user_is_refused_at_the_portal_gate(self): """Only buyer/admin roles reach /portal/ at all (BuyerRequiredMixin). So portal scoping here matters for exactly two kinds of viewer: real buyers, and admins who can reach both portals. """ self.client.force_login(self.ops_user) self.assertEqual(self.client.get("/portal/help/").status_code, 403) def test_portal_audiences_helper(self): """The scoping rule itself, independent of view wiring.""" A = GuidePage.Audience self.assertEqual( allowed_audiences("portal", role="buyer"), {A.SHARED, A.BUYER}, ) self.assertEqual( allowed_audiences("portal", role="admin"), {A.SHARED, A.BUYER}, msg="admin does not gain ops/admin guides inside the buyer portal", ) self.assertEqual( allowed_audiences("ops", role="ops"), {A.SHARED, A.OPS}, ) self.assertEqual( allowed_audiences("ops", role="admin"), {A.SHARED, A.OPS, A.ADMIN}, ) self.assertEqual( allowed_audiences("ops", role="buyer", is_superuser=True), {A.SHARED, A.OPS, A.ADMIN}, msg="superuser is treated like the admin role", ) def test_admin_cannot_reach_ops_guide_through_portal(self): self.client.force_login(self.admin_user) resp = self.client.get("/portal/help/ops/batches/") self.assertEqual(resp.status_code, 404) resp = self.client.get("/portal/help/admin/overview/") self.assertEqual(resp.status_code, 404) # ...but the same guides are reachable through the ops help centre self.assertEqual(self.client.get("/ops/help/ops/batches/").status_code, 200) self.assertEqual(self.client.get("/ops/help/admin/overview/").status_code, 200) def test_buyer_cannot_access_ops_or_admin_guide(self): self.client.force_login(self.buyer_user) resp = self.client.get("/portal/help/ops/batches/") self.assertEqual(resp.status_code, 404) resp = self.client.get("/portal/help/admin/overview/") self.assertEqual(resp.status_code, 404) def test_ops_cannot_access_buyer_guide(self): self.client.force_login(self.ops_user) resp = self.client.get("/ops/help/buyer/getting-started/") self.assertEqual(resp.status_code, 404) def test_buyer_can_access_buyer_guide(self): self.client.force_login(self.buyer_user) resp = self.client.get("/portal/help/buyer/getting-started/") self.assertEqual(resp.status_code, 200) self.assertContains(resp, "alert('x') world", "user-guides/buyer/test", "/portal/help/", ) self.assertNotIn("