jest.mock('../database/db', () => { const mockDb = jest.fn(); return { db: mockDb, logActivity: jest.fn(), }; }); jest.mock('../utils/logger', () => ({ info: jest.fn(), warn: jest.fn(), error: jest.fn(), })); const { db } = require('../database/db'); const { getPublicSitePayload, clearPublicSiteCache } = require('../services/publicSiteService'); const { sanitizeCss } = require('../utils/cssSanitizer'); const buildPublicSiteRows = (overrides = {}) => ([ { setting_key: 'general_public_site_enabled', setting_value: JSON.stringify(overrides.enabled ?? true) }, { setting_key: 'general_public_site_html', setting_value: JSON.stringify(overrides.html ?? '

{{company_name}}

') }, { setting_key: 'general_public_site_custom_css', setting_value: JSON.stringify(overrides.css ?? "body { color: red; }") } ]); const buildBrandingRows = (overrides = {}) => ([ { setting_key: 'branding_company_name', setting_value: JSON.stringify(overrides.companyName ?? 'Willow & Pine Studio') }, { setting_key: 'branding_company_tagline', setting_value: JSON.stringify(overrides.companyTagline ?? 'Stories told in colour and light.') }, { setting_key: 'branding_support_email', setting_value: JSON.stringify(overrides.supportEmail ?? 'hello@example.com') }, { setting_key: 'branding_logo_url', setting_value: JSON.stringify(overrides.logoUrl ?? '/uploads/logos/logo.png') }, { setting_key: 'branding_footer_text', setting_value: JSON.stringify(overrides.footerText ?? 'Crafted with care for every celebration.') }, { setting_key: 'theme_config', setting_value: JSON.stringify(overrides.themeConfig ?? { primaryColor: '#2563eb', accentColor: '#1d4ed8', backgroundColor: '#f8fafc', textColor: '#0f172a' }) } ]); describe('publicSiteService', () => { beforeEach(() => { clearPublicSiteCache(); jest.clearAllMocks(); }); it('sanitizes stored HTML by stripping script tags', async () => { const publicSiteRows = buildPublicSiteRows({ html: '

{{company_name}}

' }); const brandingRows = buildBrandingRows(); db.mockImplementationOnce(() => ({ whereIn: () => Promise.resolve(publicSiteRows) })); db.mockImplementationOnce(() => ({ whereIn: () => Promise.resolve(brandingRows) })); const payload = await getPublicSitePayload({ bypassCache: true }); expect(payload.enabled).toBe(true); expect(payload.html).toContain('

Willow & Pine Studio

'); expect(payload.html).not.toContain(' { const publicSiteRows = buildPublicSiteRows({ css: "body { color: blue; } @import url('https://malicious.example/style.css'); div { background: url(\"javascript:alert(1)\"); }" }); const brandingRows = buildBrandingRows(); db.mockImplementationOnce(() => ({ whereIn: () => Promise.resolve(publicSiteRows) })); db.mockImplementationOnce(() => ({ whereIn: () => Promise.resolve(brandingRows) })); const payload = await getPublicSitePayload({ bypassCache: true }); expect(payload.css).toContain('body { color: blue; }'); expect(payload.css).not.toContain('@import'); expect(payload.css).not.toContain('javascript:'); // Client-side util should match server sanitization expectations const clientSanitized = sanitizeCss(publicSiteRows[2].setting_value ? JSON.parse(publicSiteRows[2].setting_value) : ''); expect(clientSanitized).not.toContain('@import'); expect(clientSanitized).not.toContain('javascript:'); }); it('injects branding tokens into the rendered payload', async () => { const publicSiteRows = buildPublicSiteRows({ html: '

{{company_name}}

{{company_tagline}}

Get in touch
' }); const brandingRows = buildBrandingRows({ companyName: 'Aurora Collective', companyTagline: 'Modern photography for timeless celebrations.', supportEmail: 'studio@aurora.co', logoUrl: '/uploads/logos/aurora.png', themeConfig: { primaryColor: '#5C8762', accentColor: '#1d4ed8', backgroundColor: '#fafafa', textColor: '#171717' } }); db.mockImplementationOnce(() => ({ whereIn: () => Promise.resolve(publicSiteRows) })); db.mockImplementationOnce(() => ({ whereIn: () => Promise.resolve(brandingRows) })); const payload = await getPublicSitePayload({ bypassCache: true }); expect(payload.html).toContain('Aurora Collective'); expect(payload.html).toContain('Modern photography for timeless celebrations.'); expect(payload.html).toContain('studio@aurora.co'); expect(payload.branding.logoUrl).toBe('/uploads/logos/aurora.png'); expect(payload.branding.colors.primary).toBe('#5C8762'); }); });