[DTP-Worker 20260731_023000] Auto exec · session 20260731_023000
CI / Contraintes NON-NÉGOCIABLES (CLAUDE.md) (push) Has been cancelled
CI / Validation JSON (schémas Faisabilité) (push) Has been cancelled
CI / Qualité documentaire (liens + 4Big) (push) Has been cancelled
CI / Reproductibilité des artefacts out/ (build == commité) (push) Has been cancelled
CI / Fraîcheur matrice de régression (run == commité) (push) Has been cancelled
CI / Intégrité du câblage CI (gate agrège tout · gates statiques verrouillés) (push) Has been cancelled
CI / Publiciste · parser + schéma + generator (unittest) (push) Has been cancelled
CI / RBAC · 50 rôles + schéma (unittest) (push) Has been cancelled
CI / Faisabilité · générateur 4 volets + round-trip (unittest) (push) Has been cancelled
CI / RBAC · fixtures ERPNext (Role + Custom DocPerm) (push) Has been cancelled
CI / RBAC · plan User Permission (row-level) (push) Has been cancelled
CI / RBAC · Role Profile (bundles par portail) (push) Has been cancelled
CI / RBAC · run-book d'application unifié (agrégat 3 volets) (push) Has been cancelled
CI / Faisabilité · dossier bancable trilingue FR/EN/ES (push) Has been cancelled
CI / CRM · workflow vente ERPNext (lead → CONFOTUR) (push) Has been cancelled
CI / CRM · DocType porteur OTO Dossier Vente (push) Has been cancelled
CI / CRM · barème commissions vendeurs (push) Has been cancelled
CI / Fiscal · e-CF DGII (Compupar) (push) Has been cancelled
CI / Frontend · Workspaces 5 portails rôle (push) Has been cancelled
CI / Legal · DocType CONFOTUR Application (push) Has been cancelled
CI / QA · Audit 5D conformité (push) Has been cancelled
CI / SEO · mots-clés trilingues + schema.org + hreflang (push) Has been cancelled
CI / Chat OTOIA · montage par portail (Custom Block) (push) Has been cancelled
CI / QA · Audit 4Big (95+/100 sur 100% deliverables) (push) Has been cancelled
CI / Démo · Scénarios (run-sheet P07 banquier / P05 client) (push) Has been cancelled
CI / QA · Matrice de régression exhaustive (Sprint 8) (push) Has been cancelled
CI / DevOps · Run-book de déploiement VPS unifié (Sprint 8) (push) Has been cancelled
CI / QA · Matrice d'acceptation / traçabilité MVP (Sprint 8) (push) Has been cancelled
CI / Mobile · config app Expo/EAS (navigation par rôle) (push) Has been cancelled
CI / E2E baseline Playwright (manuel) (push) Has been cancelled
CI / Gate qualité (agrégat) (push) Has been cancelled

This commit is contained in:
Claude Code DTP Worker
2026-07-31 02:45:00 +00:00
parent 9403e58f67
commit 1d21a3788d
27 changed files with 1523 additions and 43 deletions
@@ -0,0 +1,154 @@
"""Tests du générateur de config app mobile (Sprint 5 · Mobile).
Stdlib pur (unittest) — aucune dépendance pip. Couvre :
- conformité au schéma + les 12 invariants du CLI (chemin nominal) ;
- la cross-cohérence RBAC ↔ portails ↔ seo (onglets, rôles, labels/icônes, langues) ;
- l'anti-invention (#6) par injections négatives (identifiant de store fabriqué,
rôles désynchronisés de la surface RBAC, langue inventée, token de marque altéré).
"""
from __future__ import annotations
import copy
import json
import os
import sys
import unittest
_HERE = os.path.dirname(os.path.abspath(__file__))
_ROOT = os.path.normpath(os.path.join(_HERE, ".."))
sys.path.insert(0, _ROOT)
import app_config_gen as gen # noqa: E402
from mobilelib import builder, deps # noqa: E402
def build():
return gen._build_bundle()
class TestNominal(unittest.TestCase):
def setUp(self):
self.bundle = build()
def test_build_valide(self):
self.assertEqual(gen._validate_bundle(self.bundle), [])
def test_schema_seul(self):
schema = deps.load_json(deps.SCHEMA_PATH)
self.assertEqual(deps.validate(self.bundle, schema), [])
def test_onglets_portails_metier_dans_ordre(self):
contract = deps.load_contract()
spec = deps.load_spec()
keys = [n["portail"] for n in self.bundle["role_navigation"]]
self.assertEqual(keys, spec["portail_order"])
self.assertEqual(set(keys), set(contract["portails_business"]))
def test_roles_allowed_egal_surface_rbac(self):
"""Ancrage sécurité : un onglet ne cible QUE les rôles RBAC de son portail."""
contract = deps.load_contract()
roles_map = deps.roles_by_portail(contract)
for n in self.bundle["role_navigation"]:
self.assertEqual(n["roles_allowed"], sorted(roles_map[n["portail"]]))
self.assertEqual(n["n_roles"], len(n["roles_allowed"]))
self.assertTrue(n["roles_allowed"], f"{n['portail']} sans rôle")
def test_labels_icones_depuis_portails_spec(self):
pspec = deps.load_portails_spec()
labels = {p["key"]: p["label"] for p in pspec["portails"]}
icons = {p["key"]: p["icon"] for p in pspec["portails"]}
for n in self.bundle["role_navigation"]:
self.assertEqual(n["label"], labels[n["portail"]])
self.assertEqual(n["icon"], icons[n["portail"]])
def test_langues_depuis_seo(self):
langs, default_lang = deps.load_langs()
app = self.bundle["app_config"]["expo"]
self.assertEqual(sorted(app["locales"].keys()), sorted(langs))
self.assertEqual(app["extra"]["defaultLocale"], default_lang)
self.assertEqual(self.bundle["manifest"]["langues"]["liste"], list(langs))
def test_tokens_marque_4(self):
app = self.bundle["app_config"]["expo"]
self.assertEqual(app["backgroundColor"], deps.branding.COLOR_BG)
self.assertEqual(app["splash"]["backgroundColor"], deps.branding.COLOR_BG)
self.assertEqual(app["primaryColor"], deps.branding.COLOR_ACCENT)
self.assertEqual(app["userInterfaceStyle"], "dark")
def test_devises_usd_dop(self):
app = self.bundle["app_config"]["expo"]
self.assertEqual(
app["extra"]["devises"],
[deps.branding.DEVISE_PRIMAIRE, deps.branding.DEVISE_SECONDAIRE],
)
def test_identifiants_store_null(self):
"""Anti-invention : chaque champ a_confirmer reste null (jamais fabriqué)."""
spec = deps.load_spec()
for entry in spec["a_confirmer"]:
found, value = gen._resolve_path(self.bundle, entry["champ"])
self.assertTrue(found, f"champ absent: {entry['champ']}")
self.assertIsNone(value, f"{entry['champ']} devrait être null")
self.assertEqual(self.bundle["manifest"]["identifiants_statut"], "a_confirmer")
def test_expo_sdk_54(self):
spec = deps.load_spec()
self.assertEqual(
self.bundle["app_config"]["expo"]["extra"]["expoSdkMajor"],
spec["app"]["expo_sdk_major"],
)
def test_eas_profils_et_submit_null(self):
spec = deps.load_spec()
eas = self.bundle["eas_build"]
self.assertEqual(sorted(eas["build"].keys()), sorted(spec["eas"]["build_profiles"]))
sub = eas["submit"]["production"]
self.assertIsNone(sub["ios"]["appleId"])
self.assertIsNone(sub["android"]["serviceAccountKeyPath"])
def test_determinisme(self):
a = json.dumps(build(), sort_keys=True, ensure_ascii=False)
b = json.dumps(build(), sort_keys=True, ensure_ascii=False)
self.assertEqual(a, b)
class TestAntiInvention(unittest.TestCase):
"""Injections négatives : chaque garde-fou doit MORDRE (invariant → erreur)."""
def setUp(self):
self.bundle = build()
def test_identifiant_fabrique_detecte(self):
bad = copy.deepcopy(self.bundle)
bad["app_config"]["expo"]["ios"]["bundleIdentifier"] = "com.oto.inventé"
errs = gen._validate_bundle(bad)
self.assertTrue(any("null" in e for e in errs), errs)
def test_roles_desync_detecte(self):
bad = copy.deepcopy(self.bundle)
bad["role_navigation"][0]["roles_allowed"] = ["OTO Rôle Inventé"]
errs = gen._validate_bundle(bad)
self.assertTrue(any("roles_allowed" in e for e in errs), errs)
def test_langue_inventee_detecte(self):
bad = copy.deepcopy(self.bundle)
bad["app_config"]["expo"]["locales"]["de"] = None
errs = gen._validate_bundle(bad)
self.assertTrue(any("locales" in e for e in errs), errs)
def test_token_marque_altere_detecte(self):
bad = copy.deepcopy(self.bundle)
bad["app_config"]["expo"]["primaryColor"] = "#ffffff"
errs = gen._validate_bundle(bad)
self.assertTrue(any("primaryColor" in e for e in errs), errs)
def test_ordre_onglets_altere_detecte(self):
bad = copy.deepcopy(self.bundle)
bad["role_navigation"].reverse()
errs = gen._validate_bundle(bad)
self.assertTrue(any("ordre des onglets" in e for e in errs), errs)
if __name__ == "__main__":
unittest.main()