feat(marketing): A-2.8b /conformite + /contact standalone pages
- /conformite page: extends base.html, page H1 with cosmic orb header, 4 pillar cards on white (mirrors landing's Conformite section content with same hedges 'Mappe' 'concue avec' 'Compatible'), 3 Loi 25 article detail cards (art. 3.3 EFVP, art. 3.5 Audit trail, art. 14 Consentement) with grad-bg article-number badges, AGPL v3 transparency CTA section with external links to Gitea + gnu.org (rel=noopener), generic CTA section - /contact page: extends base.html, 3 method cards (email, phone tel:link, postal address with <address>), 6 pre-filled mailto subject shortcuts with focus-visible WCAG 2.2 AA, pre-launch disclaimer that online form ships at launch (B-2.x). NO <form> tag - mailto only - POST returns 405 until B-2.x adds the form handler. - routes.py: add /conformite and /contact routes; preserves existing landing/tarifs/fonctionnalites views and TESTIMONIALS/FAQ data. - tests: append 13 new tests to test_marketing_secondary_pages.py covering routes 200, single H1, 4 pillars + Loi 25 articles + AGPL externals on /conformite, 3 contact methods + 6 shortcuts + 405 on POST + pre-launch note + OQLF typography on /contact. - Apply established WCAG 2.2 AA, FlexiHub, OQLF, LPC art. 219 disciplines. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -93,3 +93,17 @@ def fonctionnalites():
|
||||
plus full integrations list and supported export formats.
|
||||
"""
|
||||
return render_template('marketing/fonctionnalites.html')
|
||||
|
||||
|
||||
@marketing_bp.route('/conformite')
|
||||
def conformite():
|
||||
"""Standalone compliance page — Loi 25, LGGRI, AGPL, EFVP details."""
|
||||
return render_template('marketing/conformite.html')
|
||||
|
||||
|
||||
@marketing_bp.route('/contact', methods=['GET'])
|
||||
def contact():
|
||||
"""Contact page — pre-launch: mailto-only form (no backend submit yet).
|
||||
POST handler will be added in B-2.x once form-handling + Turnstile are wired.
|
||||
"""
|
||||
return render_template('marketing/contact.html')
|
||||
|
||||
@@ -2303,6 +2303,9 @@
|
||||
.text-\[clamp\(2\.25rem\,4vw\,3\.5rem\)\] {
|
||||
font-size: clamp(2.25rem, 4vw, 3.5rem);
|
||||
}
|
||||
.text-\[clamp\(2rem\,3vw\,2\.5rem\)\] {
|
||||
font-size: clamp(2rem, 3vw, 2.5rem);
|
||||
}
|
||||
.text-\[clamp\(2rem\,3vw\,2\.75rem\)\] {
|
||||
font-size: clamp(2rem, 3vw, 2.75rem);
|
||||
}
|
||||
@@ -3049,6 +3052,13 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:border-brand-b1\/30 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
border-color: color-mix(in oklab, #0062ff 30%, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:border-yellow-500\/30 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
@@ -3336,6 +3346,13 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:bg-white {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
background-color: var(--color-white);
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:bg-white\/20 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
@@ -4154,6 +4171,11 @@
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
.md\:items-start {
|
||||
@media (width >= 48rem) {
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
.md\:gap-4 {
|
||||
@media (width >= 48rem) {
|
||||
gap: calc(var(--spacing) * 4);
|
||||
|
||||
147
templates/marketing/conformite.html
Normal file
147
templates/marketing/conformite.html
Normal file
@@ -0,0 +1,147 @@
|
||||
{% extends 'marketing/base.html' %}
|
||||
|
||||
{% block title %}Conformité DictIA — Loi 25 (LPRPSP), LGGRI, AGPL v3, audit trail{% endblock %}
|
||||
{% block description %}DictIA mappe son architecture aux exigences Loi 25 (LPRPSP), au cadre IA du secteur public québécois (LGGRI), avec hébergement OVH Beauharnois et code source AGPL v3 vérifiable.{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{# ===== HEADER ===== #}
|
||||
<section class="bg-brand-navy text-white py-20 overflow-hidden relative" aria-labelledby="page-title">
|
||||
<div class="absolute top-1/3 left-1/4 w-[500px] h-[500px] rounded-full pointer-events-none" aria-hidden="true"
|
||||
style="background: radial-gradient(circle, rgba(0,200,150,0.07) 0%, transparent 60%); filter: blur(60px);"></div>
|
||||
<div class="relative max-w-[820px] mx-auto px-6 text-center">
|
||||
<p class="eyebrow grad-text mb-4">CONFORMITÉ — FORTERESSE QUÉBÉCOISE</p>
|
||||
<h1 id="page-title" class="text-[clamp(2.25rem,4vw,3.5rem)] font-black mb-4">
|
||||
Architecture <span class="grad-text">conçue avec</span> les exigences professionnelles québécoises.
|
||||
</h1>
|
||||
<p class="text-lg text-white/80">
|
||||
Détails techniques, EFVP type, modèles de déclaration CAI : disponibles sur demande à <a href="mailto:info@dictia.ca" class="grad-text font-semibold hover:underline">info@dictia.ca</a>.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{# ===== 4 PILLARS (same as landing's Conformité section, content-identical for SEO single source of truth on /conformite landing page) ===== #}
|
||||
<section class="bg-white py-20" aria-labelledby="pillars-title">
|
||||
<div class="max-w-[1200px] mx-auto px-6">
|
||||
<div class="text-center max-w-2xl mx-auto mb-12">
|
||||
<p class="eyebrow grad-text mb-4">QUATRE PILIERS</p>
|
||||
<h2 id="pillars-title" class="text-[clamp(2rem,3vw,2.75rem)] font-black mb-4 text-brand-navy">
|
||||
Pourquoi la conformité est <span class="grad-text">structurelle</span>, pas optionnelle.
|
||||
</h2>
|
||||
</div>
|
||||
<div class="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{% for card in [
|
||||
{
|
||||
'icon': '🍁',
|
||||
'title': 'Hébergement OVH Beauharnois',
|
||||
'desc': 'Centre de données opéré par OVHcloud Canada en territoire québécois. Conformité documentée selon les services (ISO 27001, SOC 2 selon le périmètre). Détails sur demande.'
|
||||
},
|
||||
{
|
||||
'icon': '⚖️',
|
||||
'title': 'Mappé Loi 25 (LPRPSP)',
|
||||
'desc': 'Audit trail art. 3.5, EFVP préparée art. 3.3, registre des consentements art. 14. Modèles de déclaration CAI fournis.'
|
||||
},
|
||||
{
|
||||
'icon': '🏛️',
|
||||
'title': 'Compatible Cadre IA secteur public',
|
||||
'desc': 'DictIA est conçu pour s\'inscrire dans le cadre de gestion des systèmes d\'IA du secteur public québécois (LGGRI). Documentation détaillée sur demande.'
|
||||
},
|
||||
{
|
||||
'icon': '🔓',
|
||||
'title': 'Code source AGPL v3 vérifiable',
|
||||
'desc': 'Architecture entièrement auditable sur <a href="https://gitea.innova-ai.ca/Innova-AI/dictia-public" target="_blank" rel="noopener" class="underline hover:text-brand-navy">Gitea public</a>. Aucune boîte noire. Vos auditeurs peuvent examiner chaque ligne.'
|
||||
}
|
||||
] %}
|
||||
<article class="bg-brand-bg p-6 rounded-[14px] border border-brand-border">
|
||||
<div class="w-10 h-10 grad-bg rounded-[0.5rem] mb-4 flex items-center justify-center text-lg shadow-cta" aria-hidden="true">{{ card.icon }}</div>
|
||||
<h3 class="text-lg font-bold mb-2 text-brand-navy">{{ card.title | safe }}</h3>
|
||||
<p class="text-sm text-brand-navy/80 leading-relaxed">{{ card.desc | safe }}</p>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{# ===== LOI 25 DETAIL ===== #}
|
||||
<section class="bg-brand-bg py-20" aria-labelledby="loi25-title">
|
||||
<div class="max-w-[1060px] mx-auto px-6">
|
||||
<div class="text-center max-w-2xl mx-auto mb-12">
|
||||
<p class="eyebrow grad-text mb-4">LOI 25 (LPRPSP)</p>
|
||||
<h2 id="loi25-title" class="text-[clamp(2rem,3vw,2.75rem)] font-black mb-4 text-brand-navy">
|
||||
Trois articles centraux que DictIA adresse par construction.
|
||||
</h2>
|
||||
<p class="text-base text-brand-navy/80">
|
||||
La <em>Loi sur la protection des renseignements personnels dans le secteur privé</em> (LPRPSP, communément appelée « Loi 25 ») impose une discipline stricte sur les données biométriques et confidentielles. Les voix capturées en réunion en font partie. Voici comment notre architecture y répond.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-6">
|
||||
{% for art in [
|
||||
{
|
||||
'num': 'Art. 3.3',
|
||||
'title': 'Évaluation des facteurs relatifs à la vie privée (EFVP)',
|
||||
'desc': 'Tout déploiement de DictIA dans un cabinet ou un organisme public déclenche une EFVP. Nous fournissons un modèle pré-rempli pour la voix professionnelle (catégories de données, finalités, mesures de sécurité, durée de conservation, transferts) — à compléter avec votre responsable de la protection des renseignements personnels (RPRP).'
|
||||
},
|
||||
{
|
||||
'num': 'Art. 3.5',
|
||||
'title': 'Audit trail intégré',
|
||||
'desc': 'Chaque enregistrement, écoute, export, partage ou suppression est journalisé : utilisateur, IP, date/heure UTC, action. Le journal est consultable par votre RPRP et exportable pour audits CAI ou ordres professionnels. Aucun moyen de désactiver le journal côté client.'
|
||||
},
|
||||
{
|
||||
'num': 'Art. 14',
|
||||
'title': 'Consentement explicite et tracé',
|
||||
'desc': 'Avant tout enregistrement, DictIA exige une confirmation que les participants ont consenti à l\'enregistrement et à la transcription IA. Le consentement est tracé dans le journal d\'audit. Vous pouvez configurer une demande de consentement automatique en début de session.'
|
||||
}
|
||||
] %}
|
||||
<article class="bg-white p-6 rounded-[14px] border border-brand-border">
|
||||
<div class="flex flex-col md:flex-row md:items-start gap-4">
|
||||
<div class="flex-shrink-0">
|
||||
<span class="inline-block grad-bg text-white text-xs font-black px-3 py-1.5 rounded-md">{{ art.num | safe }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-lg font-bold mb-2 text-brand-navy">{{ art.title | safe }}</h3>
|
||||
<p class="text-sm text-brand-navy/80 leading-relaxed">{{ art.desc | safe }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{# ===== AGPL TRANSPARENCY ===== #}
|
||||
<section class="bg-brand-navy text-white py-20" aria-labelledby="agpl-title">
|
||||
<div class="max-w-[820px] mx-auto px-6 text-center">
|
||||
<p class="eyebrow grad-text mb-4">AGPL V3 — TRANSPARENCE</p>
|
||||
<h2 id="agpl-title" class="text-[clamp(2rem,3vw,2.75rem)] font-black mb-4">
|
||||
Code <span class="grad-text">vérifiable ligne par ligne</span>.
|
||||
</h2>
|
||||
<p class="text-base text-white/80 mb-8">
|
||||
DictIA est publié sous licence <strong>GNU AGPL v3</strong>. Conséquence pratique : tout fork hébergé doit publier ses modifications sous la même licence. Vos auditeurs internes ou un tiers de confiance peuvent inspecter chaque ligne — modèle ML, pipeline audio, gestion d'identité, journal d'audit, exports. Aucune boîte noire propriétaire.
|
||||
</p>
|
||||
<div class="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
{% from 'macros/button.html' import button %}
|
||||
{{ button('Code source sur Gitea', href='https://gitea.innova-ai.ca/Innova-AI/dictia-public', variant='primary', size='lg', icon='↗', target='_blank', rel='noopener') }}
|
||||
{{ button('Comprendre AGPL v3', href='https://www.gnu.org/licenses/agpl-3.0.fr.html', variant='ghost', size='lg', target='_blank', rel='noopener') }}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{# ===== CTA ===== #}
|
||||
<section class="bg-brand-bg py-20" aria-labelledby="conformite-cta-title">
|
||||
<div class="max-w-[820px] mx-auto px-6 text-center">
|
||||
<h2 id="conformite-cta-title" class="text-[clamp(2rem,3vw,2.75rem)] font-black mb-6 text-brand-navy">
|
||||
Une <span class="grad-text">question de conformité</span> ?
|
||||
</h2>
|
||||
<p class="text-lg text-brand-navy/80 mb-8">
|
||||
Nous accompagnons votre RPRP, votre comptable d'ordre ou votre service juridique dans l'évaluation. Modèle d'EFVP, registre de consentements et exemple de déclaration CAI sur demande.
|
||||
</p>
|
||||
<div class="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
{% from 'macros/button.html' import button %}
|
||||
{{ button('Demander un dossier conformité', href='mailto:info@dictia.ca?subject=Demande%20dossier%20conformit%C3%A9', variant='primary', size='lg', icon='✉️') }}
|
||||
{{ button('Voir les forfaits', href='/tarifs', variant='secondary', size='lg') }}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{% endblock %}
|
||||
107
templates/marketing/contact.html
Normal file
107
templates/marketing/contact.html
Normal file
@@ -0,0 +1,107 @@
|
||||
{% extends 'marketing/base.html' %}
|
||||
|
||||
{% block title %}Contact DictIA — info@dictia.ca, (581) 996-8471, Inverness QC{% endblock %}
|
||||
{% block description %}Joignez DictIA Inc. à info@dictia.ca ou (581) 996-8471. Bureau au 77 ch. de la Seigneurie, Inverness QC. Réponse sous 2 jours ouvrables.{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{# ===== HEADER ===== #}
|
||||
<section class="bg-brand-navy text-white py-20" aria-labelledby="page-title">
|
||||
<div class="max-w-[820px] mx-auto px-6 text-center">
|
||||
<p class="eyebrow grad-text mb-4">CONTACT</p>
|
||||
<h1 id="page-title" class="text-[clamp(2.25rem,4vw,3.5rem)] font-black mb-4">
|
||||
Parlons <span class="grad-text">de votre projet</span>.
|
||||
</h1>
|
||||
<p class="text-lg text-white/80">
|
||||
Réponse sous 2 jours ouvrables. Pour les urgences techniques des clients existants, voyez la console DictIA → Support.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{# ===== CONTACT METHODS ===== #}
|
||||
<section class="bg-brand-bg py-20" aria-labelledby="methods-title">
|
||||
<div class="max-w-[1060px] mx-auto px-6">
|
||||
<h2 id="methods-title" class="sr-only">Trois manières de nous joindre</h2>
|
||||
<div class="grid md:grid-cols-3 gap-6">
|
||||
|
||||
{# Email card #}
|
||||
<article class="bg-white p-8 rounded-[18px] border border-brand-border flex flex-col">
|
||||
<div class="w-12 h-12 grad-bg rounded-[0.5rem] mb-4 flex items-center justify-center text-2xl shadow-cta" aria-hidden="true">✉️</div>
|
||||
<h3 class="text-lg font-bold mb-2 text-brand-navy">Courriel</h3>
|
||||
<p class="text-sm text-brand-navy/80 mb-4 leading-relaxed flex-grow">
|
||||
Privilégiez le courriel pour : pré-inscription, devis, démonstration, dossier de conformité, partenariats.
|
||||
</p>
|
||||
<a href="mailto:info@dictia.ca" class="grad-text font-semibold text-base hover:underline">info@dictia.ca</a>
|
||||
</article>
|
||||
|
||||
{# Phone card #}
|
||||
<article class="bg-white p-8 rounded-[18px] border border-brand-border flex flex-col">
|
||||
<div class="w-12 h-12 grad-bg rounded-[0.5rem] mb-4 flex items-center justify-center text-2xl shadow-cta" aria-hidden="true">☎️</div>
|
||||
<h3 class="text-lg font-bold mb-2 text-brand-navy">Téléphone</h3>
|
||||
<p class="text-sm text-brand-navy/80 mb-4 leading-relaxed flex-grow">
|
||||
Du lundi au vendredi, 9 h à 17 h (heure de l'Est). Laissez un message en dehors de ces heures.
|
||||
</p>
|
||||
<a href="tel:+15819968471" class="grad-text font-semibold text-base hover:underline">(581) 996-8471</a>
|
||||
</article>
|
||||
|
||||
{# Mailing address card #}
|
||||
<article class="bg-white p-8 rounded-[18px] border border-brand-border flex flex-col">
|
||||
<div class="w-12 h-12 grad-bg rounded-[0.5rem] mb-4 flex items-center justify-center text-2xl shadow-cta" aria-hidden="true">📬</div>
|
||||
<h3 class="text-lg font-bold mb-2 text-brand-navy">Bureau</h3>
|
||||
<p class="text-sm text-brand-navy/80 mb-4 leading-relaxed flex-grow">
|
||||
Sur rendez-vous uniquement. Visites en personne pour démonstrations on-premise et déploiements DictIA 16 corporatifs.
|
||||
</p>
|
||||
<address class="not-italic text-sm text-brand-navy/80 leading-relaxed">
|
||||
77 ch. de la Seigneurie<br>
|
||||
Inverness QC G0S 1K0
|
||||
</address>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{# ===== USE-CASE SHORTCUTS ===== #}
|
||||
<section class="bg-white py-20" aria-labelledby="shortcuts-title">
|
||||
<div class="max-w-[820px] mx-auto px-6">
|
||||
<div class="text-center mb-10">
|
||||
<p class="eyebrow grad-text mb-4">RACCOURCIS</p>
|
||||
<h2 id="shortcuts-title" class="text-[clamp(2rem,3vw,2.5rem)] font-black mb-4 text-brand-navy">
|
||||
Un sujet précis ?
|
||||
</h2>
|
||||
<p class="text-base text-brand-navy/80">
|
||||
Pré-remplissez le sujet du courriel selon votre besoin :
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid sm:grid-cols-2 gap-3">
|
||||
{% for shortcut in [
|
||||
{'label': 'Pré-inscription DictIA', 'subject': 'Pr%C3%A9-inscription%20DictIA', 'icon': '🎯'},
|
||||
{'label': 'Devis multi-sites', 'subject': 'Devis%20multi-sites', 'icon': '🏢'},
|
||||
{'label': 'Demande de démonstration', 'subject': 'Demande%20de%20d%C3%A9monstration', 'icon': '📺'},
|
||||
{'label': 'Dossier conformité Loi 25', 'subject': 'Dossier%20conformit%C3%A9%20Loi%2025', 'icon': '⚖️'},
|
||||
{'label': 'Partenariat / intégration', 'subject': 'Partenariat%20/%20int%C3%A9gration', 'icon': '🤝'},
|
||||
{'label': 'Question média / presse', 'subject': 'Question%20m%C3%A9dia', 'icon': '📰'}
|
||||
] %}
|
||||
<a href="mailto:info@dictia.ca?subject={{ shortcut.subject }}"
|
||||
class="flex items-center gap-3 bg-brand-bg p-4 rounded-[14px] border border-brand-border hover:bg-white hover:border-brand-b1/30 transition-colors focus-visible:outline-2 focus-visible:outline-brand-b1 focus-visible:outline-offset-2">
|
||||
<span class="text-2xl flex-shrink-0" aria-hidden="true">{{ shortcut.icon }}</span>
|
||||
<span class="text-sm font-semibold text-brand-navy">{{ shortcut.label | safe }}</span>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{# ===== PRE-LAUNCH NOTE ===== #}
|
||||
<section class="bg-brand-bg py-12" aria-labelledby="prelaunch-note-title">
|
||||
<div class="max-w-[820px] mx-auto px-6 text-center">
|
||||
<h2 id="prelaunch-note-title" class="text-base font-bold text-brand-navy/80 mb-2">
|
||||
Formulaire en ligne : bientôt disponible
|
||||
</h2>
|
||||
<p class="text-sm text-brand-navy/70">
|
||||
Le formulaire de contact en ligne ouvrira au lancement (printemps 2026). D'ici là, le courriel reste le canal officiel.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{% endblock %}
|
||||
@@ -176,3 +176,143 @@ def test_secondary_pages_have_canonical_meta():
|
||||
assert 'rel="canonical"' in body
|
||||
assert 'og:type' in body
|
||||
assert 'twitter:card' in body
|
||||
|
||||
|
||||
# === /conformite ===
|
||||
|
||||
def test_conformite_route_returns_200():
|
||||
client = app.test_client()
|
||||
response = client.get('/conformite')
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_conformite_extends_base_with_h1():
|
||||
client = app.test_client()
|
||||
body = client.get('/conformite').data.decode('utf-8')
|
||||
assert '<!DOCTYPE html>' in body
|
||||
assert 'lang="fr-CA"' in body
|
||||
assert 'page-title' in body
|
||||
assert body.count('<h1') == 1, "Must have exactly one H1"
|
||||
|
||||
|
||||
def test_conformite_4_pillars_present():
|
||||
client = app.test_client()
|
||||
body = client.get('/conformite').data.decode('utf-8')
|
||||
assert 'pillars-title' in body
|
||||
# 4 pillars (same anchors as landing's conformité section)
|
||||
pillar_keywords = ['OVH Beauharnois', 'LPRPSP', 'LGGRI', 'AGPL']
|
||||
for kw in pillar_keywords:
|
||||
assert kw in body, f"Conformité pillar missing: {kw}"
|
||||
# Soft hedges (LPC art. 219)
|
||||
assert 'Mapp' in body, "Must use 'Mappé' (not 'Certifié')"
|
||||
assert 'selon le périmètre' in body or 'selon le périmètre' in body, \
|
||||
"SOC 2 must be hedged"
|
||||
|
||||
|
||||
def test_conformite_loi25_3_articles():
|
||||
client = app.test_client()
|
||||
body = client.get('/conformite').data.decode('utf-8')
|
||||
assert 'loi25-title' in body
|
||||
# 3 LPRPSP articles
|
||||
for art in ['Art. 3.3', 'Art. 3.5', 'Art. 14']:
|
||||
assert art in body, f"Conformité missing article: {art}"
|
||||
# Article topics
|
||||
for topic in ['EFVP', 'Audit trail', 'Consentement']:
|
||||
assert topic in body, f"Conformité missing Loi 25 topic: {topic}"
|
||||
|
||||
|
||||
def test_conformite_agpl_section_with_external_links():
|
||||
client = app.test_client()
|
||||
body = client.get('/conformite').data.decode('utf-8')
|
||||
assert 'agpl-title' in body
|
||||
assert 'AGPL' in body
|
||||
# External Gitea link with rel=noopener
|
||||
assert 'gitea.innova-ai.ca/Innova-AI/dictia-public' in body
|
||||
assert 'rel="noopener"' in body
|
||||
# Link to GNU AGPL v3 official text
|
||||
assert 'gnu.org/licenses/agpl-3.0' in body
|
||||
|
||||
|
||||
def test_conformite_uses_oqlf_typography():
|
||||
client = app.test_client()
|
||||
body = client.get('/conformite').data.decode('utf-8')
|
||||
assert 'Loi 25' in body
|
||||
assert 'AGPL v3' in body
|
||||
assert 'art. 3.5' in body or 'Art. 3.5' in body
|
||||
assert '&nbsp;' not in body, "Macro | safe regression"
|
||||
|
||||
|
||||
# === /contact ===
|
||||
|
||||
def test_contact_route_returns_200():
|
||||
client = app.test_client()
|
||||
response = client.get('/contact')
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_contact_route_does_not_accept_post_yet():
|
||||
"""POST handler is B-2.x territory — for now, /contact is GET-only."""
|
||||
client = app.test_client()
|
||||
response = client.post('/contact', data={'name': 'test'})
|
||||
assert response.status_code == 405, "POST must return 405 until B-2.x form handler ships"
|
||||
|
||||
|
||||
def test_contact_extends_base_with_h1():
|
||||
client = app.test_client()
|
||||
body = client.get('/contact').data.decode('utf-8')
|
||||
assert '<!DOCTYPE html>' in body
|
||||
assert 'page-title' in body
|
||||
assert body.count('<h1') == 1
|
||||
|
||||
|
||||
def test_contact_3_methods_email_phone_address():
|
||||
client = app.test_client()
|
||||
body = client.get('/contact').data.decode('utf-8')
|
||||
assert 'methods-title' in body
|
||||
# Email
|
||||
assert 'href="mailto:info@dictia.ca"' in body
|
||||
# Phone (tel: link)
|
||||
assert 'href="tel:+15819968471"' in body
|
||||
assert '(581) 996-8471' in body
|
||||
# Address (postal code)
|
||||
assert 'G0S 1K0' in body
|
||||
assert 'Inverness' in body
|
||||
assert '<address' in body
|
||||
|
||||
|
||||
def test_contact_6_subject_shortcuts():
|
||||
"""Pre-filled mailto subject shortcuts for common requests."""
|
||||
client = app.test_client()
|
||||
body = client.get('/contact').data.decode('utf-8')
|
||||
assert 'shortcuts-title' in body
|
||||
# 6 shortcut anchors with mailto + subject
|
||||
expected_subjects = [
|
||||
'Pr%C3%A9-inscription%20DictIA',
|
||||
'Devis%20multi-sites',
|
||||
'Demande%20de%20d%C3%A9monstration',
|
||||
'Dossier%20conformit%C3%A9%20Loi%2025',
|
||||
'Partenariat',
|
||||
'Question%20m%C3%A9dia',
|
||||
]
|
||||
for subj in expected_subjects:
|
||||
assert subj in body, f"Missing mailto subject shortcut: {subj}"
|
||||
# Each shortcut is a focusable link with focus-visible
|
||||
assert 'focus-visible:outline-2' in body
|
||||
assert 'focus-visible:outline-brand-b1' in body
|
||||
|
||||
|
||||
def test_contact_pre_launch_form_disclaimer():
|
||||
"""Until B-2.x ships the form handler, the page must clearly disclose mailto-only."""
|
||||
client = app.test_client()
|
||||
body = client.get('/contact').data.decode('utf-8')
|
||||
assert 'Formulaire en ligne' in body
|
||||
assert 'lancement' in body or 'printemps' in body, \
|
||||
"Must indicate online form is coming at launch"
|
||||
|
||||
|
||||
def test_contact_uses_oqlf_typography():
|
||||
client = app.test_client()
|
||||
body = client.get('/contact').data.decode('utf-8')
|
||||
assert '2 jours' in body
|
||||
assert '9 h' in body
|
||||
assert '&nbsp;' not in body
|
||||
|
||||
Reference in New Issue
Block a user