"""Tests for the secondary marketing pages (A-2.8).""" import os import sys sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) os.environ.setdefault('SQLALCHEMY_DATABASE_URI', 'sqlite:///:memory:') os.environ.setdefault('SECRET_KEY', 'test-secret-key') from src.app import app # noqa: E402 # === /tarifs === def test_tarifs_route_returns_200(): client = app.test_client() response = client.get('/tarifs') assert response.status_code == 200, "GET /tarifs must return 200" def test_tarifs_extends_marketing_base(): client = app.test_client() body = client.get('/tarifs').data.decode('utf-8') assert '' in body assert 'lang="fr-CA"' in body assert '/static/css/marketing.css' in body assert '/static/js/alpine.min.js' in body def test_tarifs_has_h1_with_anchor(): client = app.test_client() body = client.get('/tarifs').data.decode('utf-8') assert 'page-title' in body assert '' in body assert 'scope="col"' in body assert 'scope="row"' in body # 8 row keywords for kw in ['Hébergement', 'GPU', 'Volume audio', 'Utilisateurs', 'Diarisation', 'Mistral 7B local', 'Q&R', 'Délai']: assert kw in body, f"Missing matrix row keyword: {kw}" def test_tarifs_pricing_faq_5_questions(): client = app.test_client() body = client.get('/tarifs').data.decode('utf-8') assert 'tarifs-faq-title' in body for i in range(1, 6): assert f'tarifs-faq-panel-{i}' in body, f"Missing tarifs FAQ panel {i}" # Alpine accordion bindings assert body.count('x-data="{ open: false }"') >= 5 # Each accordion button has focus-visible (WCAG 2.4.7/2.4.11) assert 'focus-visible:outline-2' in body def test_tarifs_uses_oqlf_typography(): client = app.test_client() body = client.get('/tarifs').data.decode('utf-8') assert 'TPS 5 %' in body assert 'TVQ 9,975 %' in body assert 'Loi 25' in body # No double-escape assert '&nbsp;' not in body, "Pricing macro / safe filter regression" # === /fonctionnalites === def test_fonctionnalites_route_returns_200(): client = app.test_client() response = client.get('/fonctionnalites') assert response.status_code == 200 def test_fonctionnalites_extends_marketing_base(): client = app.test_client() body = client.get('/fonctionnalites').data.decode('utf-8') assert '' in body assert 'lang="fr-CA"' in body assert '/static/css/marketing.css' in body def test_fonctionnalites_h1_present(): client = app.test_client() body = client.get('/fonctionnalites').data.decode('utf-8') assert 'page-title' in body assert 'rester' in body or 'restant chez soi' in body def test_fonctionnalites_renders_6_bento_cards(): client = app.test_client() body = client.get('/fonctionnalites').data.decode('utf-8') assert 'features-title' in body # 6 watermark numbers for n in ['01', '02', '03', '04', '05', '06']: assert f'>{n}<' in body # 6 feature anchors for kw in ['WhisperX', 'Diarisation', 'Mistral 7B', 'RAG local', 'DOCX, PDF, SRT', 'Outlook, Teams']: assert kw in body def test_fonctionnalites_export_formats_section(): client = app.test_client() body = client.get('/fonctionnalites').data.decode('utf-8') assert 'exports-title' in body for ext in ['DOCX', 'PDF', 'SRT', 'VTT', 'TXT', 'JSON', 'MD']: # Each format has its own card with the .ext as a heading assert f'>{ext}<' in body, f"Missing export format card: {ext}" def test_fonctionnalites_integrations_grid(): client = app.test_client() body = client.get('/fonctionnalites').data.decode('utf-8') assert 'integrations-title' in body for name in ['Microsoft Word', 'Microsoft Outlook', 'Microsoft Teams', 'Notion', 'Obsidian', 'Zapier', 'Make', 'n8n']: assert name in body, f"Missing integration: {name}" # Trademark disclaimer assert 'marques de leurs propriétaires' in body or 'marques de leurs propriétaires' in body def test_fonctionnalites_tech_specs_6_items(): client = app.test_client() body = client.get('/fonctionnalites').data.decode('utf-8') assert 'specs-title' in body for spec_keyword in ['Modèle ASR', 'pyannote', 'Mistral 7B', 'Flask', 'WAV, MP3', 'québécois']: assert spec_keyword in body, f"Missing tech spec keyword: {spec_keyword}" def test_fonctionnalites_uses_oqlf_typography(): client = app.test_client() body = client.get('/fonctionnalites').data.decode('utf-8') # NBSP entities assert '95 %+' in body, "WhisperX precision NBSP entity" assert 'GPU 8 Go RTX' not in body # Bento card calls don't use 8 Go RTX (that's pricing) assert 'Q&R' in body, "French Q&R (not Q&A)" # No double-escape assert '&nbsp;' not in body # === Cross-page checks === def test_secondary_pages_in_main_nav(): """Header nav links to /tarifs and /fonctionnalites — verify both pages now respond.""" client = app.test_client() for url in ['/tarifs', '/fonctionnalites']: # Each page should self-link in its own nav (consistency) body = client.get(url).data.decode('utf-8') assert 'href="/tarifs"' in body and 'href="/fonctionnalites"' in body, \ f"Page {url} must include nav links to both new pages" def test_secondary_pages_have_canonical_meta(): """Both pages must have canonical URL + OG metadata via base.html.""" client = app.test_client() for url in ['/tarifs', '/fonctionnalites']: body = client.get(url).data.decode('utf-8') assert 'rel="canonical"' in body assert 'og:type' in body assert 'twitter:card' in body