# tests/unit/services/test_content_page_service.py """Unit tests for ContentPageService.""" import uuid import pytest from app.exceptions.content_page import ( ContentPageNotFoundException, UnauthorizedContentPageAccessException, ) from app.services.content_page_service import ContentPageService, content_page_service from models.database.content_page import ContentPage @pytest.mark.unit @pytest.mark.cms class TestContentPageServiceGetPageForVendor: """Test suite for ContentPageService.get_page_for_vendor().""" def test_get_platform_page_success(self, db, platform_about_page): """Test getting a platform default page.""" result = content_page_service.get_page_for_vendor( db, slug="about", vendor_id=None ) assert result is not None assert result.id == platform_about_page.id assert result.slug == "about" assert result.vendor_id is None def test_get_vendor_override_returns_vendor_page( self, db, platform_about_page, vendor_about_page, test_vendor ): """Test that vendor-specific override is returned over platform default.""" result = content_page_service.get_page_for_vendor( db, slug="about", vendor_id=test_vendor.id ) assert result is not None assert result.id == vendor_about_page.id assert result.vendor_id == test_vendor.id assert "Our Shop" in result.title def test_get_vendor_fallback_to_platform( self, db, platform_faq_page, test_vendor ): """Test fallback to platform default when vendor has no override.""" result = content_page_service.get_page_for_vendor( db, slug="faq", vendor_id=test_vendor.id ) assert result is not None assert result.id == platform_faq_page.id assert result.vendor_id is None def test_get_page_not_found(self, db): """Test getting non-existent page returns None.""" result = content_page_service.get_page_for_vendor( db, slug="nonexistent", vendor_id=None ) assert result is None def test_get_unpublished_page_excluded(self, db, platform_draft_page): """Test unpublished pages are excluded by default.""" result = content_page_service.get_page_for_vendor( db, slug="draft-page", vendor_id=None ) assert result is None def test_get_unpublished_page_included(self, db, platform_draft_page): """Test unpublished pages are included when requested.""" result = content_page_service.get_page_for_vendor( db, slug="draft-page", vendor_id=None, include_unpublished=True ) assert result is not None assert result.id == platform_draft_page.id assert result.is_published is False @pytest.mark.unit @pytest.mark.cms class TestContentPageServiceListPagesForVendor: """Test suite for ContentPageService.list_pages_for_vendor().""" def test_list_platform_pages(self, db, all_platform_pages): """Test listing all platform pages.""" result = content_page_service.list_pages_for_vendor(db, vendor_id=None) assert len(result) == 5 slugs = {page.slug for page in result} assert "about" in slugs assert "faq" in slugs assert "privacy" in slugs assert "terms" in slugs assert "contact" in slugs def test_list_footer_pages(self, db, all_platform_pages): """Test listing pages marked for footer display.""" result = content_page_service.list_pages_for_vendor( db, vendor_id=None, footer_only=True ) # about, faq, contact have show_in_footer=True assert len(result) == 3 for page in result: assert page.show_in_footer is True def test_list_header_pages(self, db, all_platform_pages): """Test listing pages marked for header display.""" result = content_page_service.list_pages_for_vendor( db, vendor_id=None, header_only=True ) # about, contact have show_in_header=True assert len(result) == 2 for page in result: assert page.show_in_header is True def test_list_legal_pages(self, db, all_platform_pages): """Test listing pages marked for legal/bottom bar display.""" result = content_page_service.list_pages_for_vendor( db, vendor_id=None, legal_only=True ) # privacy, terms have show_in_legal=True assert len(result) == 2 slugs = {page.slug for page in result} assert "privacy" in slugs assert "terms" in slugs for page in result: assert page.show_in_legal is True def test_list_vendor_pages_with_override( self, db, platform_about_page, platform_faq_page, vendor_about_page, test_vendor ): """Test vendor pages merge correctly with platform defaults.""" result = content_page_service.list_pages_for_vendor( db, vendor_id=test_vendor.id ) slugs = {page.slug: page for page in result} # Vendor override should be used for 'about' assert slugs["about"].vendor_id == test_vendor.id # Platform default should be used for 'faq' assert slugs["faq"].vendor_id is None def test_list_pages_sorted_by_display_order(self, db, content_page_factory): """Test pages are sorted by display_order.""" # Create pages with specific display orders page1 = content_page_factory(db, slug="page-c", display_order=3) page2 = content_page_factory(db, slug="page-a", display_order=1) page3 = content_page_factory(db, slug="page-b", display_order=2) result = content_page_service.list_pages_for_vendor(db, vendor_id=None) # Should be sorted by display_order assert result[0].display_order <= result[1].display_order assert result[1].display_order <= result[2].display_order def test_list_excludes_unpublished(self, db, platform_about_page, platform_draft_page): """Test unpublished pages are excluded by default.""" result = content_page_service.list_pages_for_vendor(db, vendor_id=None) slugs = {page.slug for page in result} assert "about" in slugs assert "draft-page" not in slugs def test_list_includes_unpublished(self, db, platform_about_page, platform_draft_page): """Test unpublished pages are included when requested.""" result = content_page_service.list_pages_for_vendor( db, vendor_id=None, include_unpublished=True ) slugs = {page.slug for page in result} assert "about" in slugs assert "draft-page" in slugs @pytest.mark.unit @pytest.mark.cms class TestContentPageServiceCreatePage: """Test suite for ContentPageService.create_page().""" def test_create_platform_page(self, db, test_user): """Test creating a platform default page.""" result = content_page_service.create_page( db, slug="new-page", title="New Page", content="
New content
", vendor_id=None, is_published=True, created_by=test_user.id, ) assert result.id is not None assert result.slug == "new-page" assert result.title == "New Page" assert result.vendor_id is None assert result.is_published is True assert result.created_by == test_user.id def test_create_vendor_page(self, db, test_vendor, test_user): """Test creating a vendor-specific page.""" result = content_page_service.create_page( db, slug="vendor-page", title="Vendor Page", content="Vendor content
", vendor_id=test_vendor.id, is_published=True, created_by=test_user.id, ) assert result.vendor_id == test_vendor.id assert result.slug == "vendor-page" def test_create_page_with_all_navigation_flags(self, db): """Test creating page with all navigation flags set.""" result = content_page_service.create_page( db, slug="all-nav-page", title="All Navigation Page", content="Appears everywhere
", show_in_header=True, show_in_footer=True, # Note: show_in_legal not in create_page params by default ) assert result.show_in_header is True assert result.show_in_footer is True @pytest.mark.unit @pytest.mark.cms class TestContentPageServiceUpdatePage: """Test suite for ContentPageService.update_page().""" def test_update_page_title(self, db, platform_about_page): """Test updating page title.""" result = content_page_service.update_page( db, page_id=platform_about_page.id, title="Updated Title" ) assert result.title == "Updated Title" def test_update_page_navigation_flags(self, db, platform_about_page): """Test updating navigation flags.""" result = content_page_service.update_page( db, page_id=platform_about_page.id, show_in_header=False, show_in_footer=False, ) assert result.show_in_header is False assert result.show_in_footer is False def test_update_page_not_found(self, db): """Test updating non-existent page returns None.""" result = content_page_service.update_page( db, page_id=99999, title="New Title" ) assert result is None def test_update_page_or_raise_not_found(self, db): """Test update_page_or_raise raises exception for non-existent page.""" with pytest.raises(ContentPageNotFoundException) as exc_info: content_page_service.update_page_or_raise( db, page_id=99999, title="New Title" ) assert exc_info.value.error_code == "CONTENT_PAGE_NOT_FOUND" @pytest.mark.unit @pytest.mark.cms class TestContentPageServiceDeletePage: """Test suite for ContentPageService.delete_page().""" def test_delete_page_success(self, db, content_page_factory): """Test deleting a page.""" page = content_page_factory(db, slug="to-delete") page_id = page.id result = content_page_service.delete_page(db, page_id=page_id) db.commit() # Commit the deletion assert result is True # Verify page is deleted deleted = content_page_service.get_page_by_id(db, page_id) assert deleted is None def test_delete_page_not_found(self, db): """Test deleting non-existent page returns False.""" result = content_page_service.delete_page(db, page_id=99999) assert result is False def test_delete_page_or_raise_not_found(self, db): """Test delete_page_or_raise raises exception for non-existent page.""" with pytest.raises(ContentPageNotFoundException): content_page_service.delete_page_or_raise(db, page_id=99999) @pytest.mark.unit @pytest.mark.cms class TestContentPageServiceVendorMethods: """Test suite for vendor-specific methods with ownership checks.""" def test_update_vendor_page_success(self, db, vendor_about_page, test_vendor): """Test updating vendor page with correct ownership.""" result = content_page_service.update_vendor_page( db, page_id=vendor_about_page.id, vendor_id=test_vendor.id, title="Updated Vendor Title", ) assert result.title == "Updated Vendor Title" def test_update_vendor_page_wrong_vendor( self, db, vendor_about_page, other_company ): """Test updating vendor page with wrong vendor raises exception.""" from models.database.vendor import Vendor # Create another vendor unique_id = str(uuid.uuid4())[:8] other_vendor = Vendor( company_id=other_company.id, vendor_code=f"OTHER_{unique_id.upper()}", subdomain=f"other{unique_id.lower()}", name=f"Other Vendor {unique_id}", is_active=True, ) db.add(other_vendor) db.commit() db.refresh(other_vendor) with pytest.raises(UnauthorizedContentPageAccessException) as exc_info: content_page_service.update_vendor_page( db, page_id=vendor_about_page.id, vendor_id=other_vendor.id, title="Unauthorized Update", ) assert exc_info.value.error_code == "CONTENT_PAGE_ACCESS_DENIED" def test_delete_vendor_page_success( self, db, vendor_shipping_page, test_vendor ): """Test deleting vendor page with correct ownership.""" page_id = vendor_shipping_page.id content_page_service.delete_vendor_page( db, page_id=page_id, vendor_id=test_vendor.id ) db.commit() # Commit the deletion # Verify page is deleted deleted = content_page_service.get_page_by_id(db, page_id) assert deleted is None def test_delete_vendor_page_wrong_vendor( self, db, vendor_about_page, other_company ): """Test deleting vendor page with wrong vendor raises exception.""" from models.database.vendor import Vendor # Create another vendor unique_id = str(uuid.uuid4())[:8] other_vendor = Vendor( company_id=other_company.id, vendor_code=f"OTHER2_{unique_id.upper()}", subdomain=f"other2{unique_id.lower()}", name=f"Other Vendor 2 {unique_id}", is_active=True, ) db.add(other_vendor) db.commit() db.refresh(other_vendor) with pytest.raises(UnauthorizedContentPageAccessException): content_page_service.delete_vendor_page( db, page_id=vendor_about_page.id, vendor_id=other_vendor.id ) @pytest.mark.unit @pytest.mark.cms class TestContentPageServiceListAllMethods: """Test suite for list_all methods.""" def test_list_all_platform_pages(self, db, all_platform_pages): """Test listing only platform default pages.""" result = content_page_service.list_all_platform_pages(db) assert len(result) == 5 for page in result: assert page.vendor_id is None def test_list_all_vendor_pages( self, db, vendor_about_page, vendor_shipping_page, test_vendor ): """Test listing only vendor-specific pages.""" result = content_page_service.list_all_vendor_pages(db, vendor_id=test_vendor.id) assert len(result) == 2 for page in result: assert page.vendor_id == test_vendor.id def test_list_all_pages_filtered_by_vendor( self, db, platform_about_page, vendor_about_page, test_vendor ): """Test listing all pages filtered by vendor ID.""" result = content_page_service.list_all_pages(db, vendor_id=test_vendor.id) # Should only include vendor pages, not platform defaults assert len(result) >= 1 for page in result: assert page.vendor_id == test_vendor.id @pytest.mark.unit @pytest.mark.cms class TestContentPageModel: """Test suite for ContentPage model methods.""" def test_is_platform_default_property(self, db, platform_about_page): """Test is_platform_default property.""" assert platform_about_page.is_platform_default is True assert platform_about_page.is_vendor_override is False def test_is_vendor_override_property(self, db, vendor_about_page): """Test is_vendor_override property.""" assert vendor_about_page.is_platform_default is False assert vendor_about_page.is_vendor_override is True def test_to_dict_includes_navigation_flags(self, db, platform_privacy_page): """Test to_dict includes all navigation flags.""" result = platform_privacy_page.to_dict() assert "show_in_footer" in result assert "show_in_header" in result assert "show_in_legal" in result assert result["show_in_legal"] is True def test_to_dict_includes_all_fields(self, db, platform_about_page): """Test to_dict includes all expected fields.""" result = platform_about_page.to_dict() expected_fields = [ "id", "vendor_id", "slug", "title", "content", "content_format", "template", "meta_description", "meta_keywords", "is_published", "published_at", "display_order", "show_in_footer", "show_in_header", "show_in_legal", "is_platform_default", "is_vendor_override", "created_at", "updated_at", "created_by", "updated_by", ] for field in expected_fields: assert field in result, f"Missing field: {field}"