forge-mvc-testing 1.0.0rc2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,15 @@
1
+ """forge-mvc-testing — infrastructure de test partagée pour Forge (dev only).
2
+
3
+ Fournit `FakeRequest` et, via un plugin pytest (point d'entrée `pytest11`,
4
+ voir `forge_mvc_testing.plugin`), les fixtures partagées : configuration du
5
+ noyau, nettoyage entre tests, et `fake_request`.
6
+
7
+ Ce paquet n'est JAMAIS une dépendance runtime ; il n'est installé qu'en
8
+ développement (ADR-041).
9
+ """
10
+ from __future__ import annotations
11
+
12
+ from forge_mvc_testing.fake_request import FakeRequest
13
+
14
+ __all__ = ["FakeRequest"]
15
+ __version__ = "1.0.0rc2"
@@ -0,0 +1,128 @@
1
+ """Requête HTTP simulée pour les tests unitaires de contrôleurs Forge.
2
+
3
+ Portée depuis `tests/fake_request.py` vers le paquet de test-support
4
+ `forge-mvc-testing` (ADR-041), pour que les tests de chaque paquet opt-in y
5
+ accèdent sans dépendre du `tests/` racine.
6
+ """
7
+
8
+
9
+ class FakeRequest:
10
+ """
11
+ Requête HTTP simulée pour les tests unitaires de contrôleurs.
12
+
13
+ Usage :
14
+ req = FakeRequest("GET", "/clients")
15
+ req = FakeRequest("POST", "/clients", body={"Nom": "Dupont"})
16
+ req = FakeRequest("POST", "/api/sync", json_body={"ids": [1, 2]})
17
+ req = FakeRequest("GET", "/clients", session_id="abc123")
18
+
19
+ La fixture `fake_request` (plugin pytest) retourne la classe directement.
20
+ """
21
+
22
+ def __init__(self, method="GET", path="/", *,
23
+ body=None, json_body=None, params=None,
24
+ session_id=None, ip="127.0.0.1", headers=None, files=None):
25
+ self.original_method = method.upper()
26
+ self.method = self.original_method
27
+ self.path = path
28
+ self.ip = ip
29
+ self.route_params = {}
30
+ self.params = {k: [v] for k, v in (params or {}).items()}
31
+
32
+ # body formulaire (format parse_qs : valeurs en liste)
33
+ self.body = {k: [v] for k, v in (body or {}).items()}
34
+ self.json_body = json_body or {}
35
+ self.files = files or {}
36
+ self._apply_method_override()
37
+
38
+ # session injectée via le cookie
39
+ self.headers = _FakeHeaders(session_id, headers=headers)
40
+
41
+ def _apply_method_override(self):
42
+ if self.original_method != "POST":
43
+ return
44
+ value = self.body.get("_method", [None])
45
+ override = value[0] if isinstance(value, list) and value else value
46
+ if override and str(override).upper() in {"PUT", "PATCH", "DELETE"}:
47
+ self.method = str(override).upper()
48
+
49
+ # ── Convention d'inspection — miroir des accesseurs de `Request` ────────
50
+ #
51
+ # Ajouté par STARTER-BONJOUR-FORGE-001 / API-INSPECTABLE-OBJECTS-CONVENTION-001
52
+ # pour que `FakeRequest` se comporte comme `core.http.request.Request`
53
+ # dans les tests qui exercent les contrôleurs typés.
54
+
55
+ def query(self, key, default=None):
56
+ values = self.params.get(key)
57
+ if not values:
58
+ return default
59
+ return values[0]
60
+
61
+ def header(self, name, default=None):
62
+ return self.headers.get(name, default)
63
+
64
+ def form(self, key, default=None):
65
+ values = self.body.get(key)
66
+ if not values:
67
+ return default
68
+ if isinstance(values, list):
69
+ return values[0] if values else default
70
+ return values
71
+
72
+ def json(self, key, default=None):
73
+ body = self.json_body
74
+ if not isinstance(body, dict):
75
+ return default
76
+ return body.get(key, default)
77
+
78
+ def file(self, key, default=None):
79
+ return self.files.get(key, default)
80
+
81
+ def route(self, key, default=None):
82
+ return self.route_params.get(key, default)
83
+
84
+ @property
85
+ def data(self):
86
+ """Mini-version de `Request.data` pour les tests de contrôleurs.
87
+
88
+ Reproduit les clés canoniques (method, path, ip, params,
89
+ route_params, headers, body, json_body, files) mais sans le
90
+ masquage avancé (les tests ne devraient pas avoir besoin de
91
+ passer par FakeRequest pour vérifier le masquage — `Request`
92
+ réel est utilisé pour ça)."""
93
+ headers_view = {}
94
+ for key in list(getattr(self.headers, "_headers", {})):
95
+ headers_view[key] = self.headers.get(key)
96
+ return {
97
+ "method": self.method,
98
+ "original_method": self.original_method,
99
+ "path": self.path,
100
+ "ip": self.ip,
101
+ "params": dict(self.params),
102
+ "route_params": dict(self.route_params),
103
+ "headers": headers_view,
104
+ "body": dict(self.body),
105
+ "json_body": dict(self.json_body) if isinstance(self.json_body, dict) else self.json_body,
106
+ "files": {k: {"filename": getattr(f, "filename", None),
107
+ "size": getattr(f, "size", None),
108
+ "content_type": getattr(f, "content_type", None)}
109
+ for k, f in self.files.items()},
110
+ }
111
+
112
+
113
+ class _FakeHeaders:
114
+ """Simule http.client.HTTPMessage pour get("Cookie") et get("Content-Type")."""
115
+
116
+ def __init__(self, session_id=None, headers=None):
117
+ self._session_id = session_id
118
+ self._headers = dict(headers or {})
119
+ self._headers_lower = {key.lower(): value for key, value in self._headers.items()}
120
+
121
+ def get(self, name, default: str | None = ""):
122
+ if name in self._headers:
123
+ return self._headers[name]
124
+ if name.lower() in self._headers_lower:
125
+ return self._headers_lower[name.lower()]
126
+ if name == "Cookie" and self._session_id:
127
+ return f"__Host-session_id={self._session_id}"
128
+ return default
@@ -0,0 +1,108 @@
1
+ """Plugin pytest de Forge (test-support, ADR-041).
2
+
3
+ Chargé automatiquement via le point d'entrée `pytest11` dès que
4
+ `forge-mvc-testing` est installé dans l'environnement de test. Fournit les
5
+ fixtures partagées autrefois dans `tests/conftest.py` : configuration du noyau,
6
+ nettoyage entre tests, et `fake_request`.
7
+
8
+ Réservé au développement : ce paquet n'est jamais une dépendance runtime, donc
9
+ ces fixtures (notamment l'autouse `configure_forge_kernel`) n'affectent pas les
10
+ suites de tests des utilisateurs de Forge.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import pytest
15
+
16
+ import core.forge as forge
17
+
18
+ from forge_mvc_testing.fake_request import FakeRequest
19
+
20
+
21
+ @pytest.fixture(scope="session", autouse=True)
22
+ def configure_forge_kernel(tmp_path_factory):
23
+ """Configure le noyau Forge pour tous les tests — vues et SQL dans tmp_path."""
24
+ views_dir = tmp_path_factory.mktemp("views")
25
+ sql_dir = tmp_path_factory.mktemp("sql")
26
+ # ADR-060 : le cœur ne porte plus de config de connexion BDD ; un backend la
27
+ # lit dans l'environnement. Les tests qui touchent la base posent eux-mêmes
28
+ # les variables DB_* (voir test_sqlite_adapter_001).
29
+ forge.configure(
30
+ app_name="TestForge",
31
+ app_env="dev",
32
+ views_dir=str(views_dir),
33
+ sql_dir=str(sql_dir),
34
+ )
35
+ # ADR-032 : UPLOAD_ROOT est lu depuis l'environnement par forge-mvc-files.
36
+ # Filet de sécurité : un défaut pointant vers un tmp, pour qu'aucun test ne
37
+ # puisse écrire dans le storage réel du dépôt même sans isolation propre.
38
+ mp = pytest.MonkeyPatch()
39
+ mp.setenv("UPLOAD_ROOT", str(tmp_path_factory.mktemp("uploads")))
40
+ yield
41
+ mp.undo()
42
+
43
+
44
+ @pytest.fixture(autouse=True)
45
+ def clear_sessions():
46
+ """Vide le store de sessions entre chaque test."""
47
+ from core.sessions.manager import get_session_store
48
+
49
+ def _purge() -> None:
50
+ # `purge_all` est une capacité réservée aux tests, portée par
51
+ # MemorySessionStore et hors du protocole SessionStore : on l'appelle
52
+ # uniquement si le store configuré l'expose.
53
+ store = get_session_store()
54
+ purge = getattr(store, "purge_all", None)
55
+ if callable(purge):
56
+ purge()
57
+
58
+ _purge()
59
+ yield
60
+ _purge()
61
+
62
+
63
+ @pytest.fixture(autouse=True)
64
+ def clear_rate_limits():
65
+ """Vide les compteurs de tentatives, anti-replay et échecs d'audit entre tests.
66
+
67
+ Compatible core-only : si `forge_mvc_mfa` n'est pas installé, le purge
68
+ anti-replay devient un no-op.
69
+ """
70
+ from core.auth.rate_limit import purge_all_attempts
71
+ from core.auth.audit import reset_audit_failure_count
72
+
73
+ try:
74
+ from forge_mvc_mfa.totp_replay import purge_all as _purge_replay
75
+ except ImportError:
76
+ def _purge_replay(): # type: ignore[misc]
77
+ """No-op : forge-mvc-mfa n'est pas installé."""
78
+ return None
79
+
80
+ purge_all_attempts()
81
+ _purge_replay()
82
+ reset_audit_failure_count()
83
+ yield
84
+ purge_all_attempts()
85
+ _purge_replay()
86
+ reset_audit_failure_count()
87
+
88
+
89
+ @pytest.fixture(autouse=True)
90
+ def clear_upload_rate_limits():
91
+ """Vide le compteur d'uploads entre chaque test.
92
+
93
+ Le rate-limit d'upload vit dans `forge_mvc_files` (ADR-019) ; absent → no-op.
94
+ """
95
+ try:
96
+ from forge_mvc_files import rate_limit as _rl
97
+ except ImportError:
98
+ yield
99
+ return
100
+ _rl._compteurs.clear()
101
+ yield
102
+ _rl._compteurs.clear()
103
+
104
+
105
+ @pytest.fixture
106
+ def fake_request():
107
+ """Retourne la classe `FakeRequest` pour construire des requêtes simulées."""
108
+ return FakeRequest
File without changes
@@ -0,0 +1,72 @@
1
+ Metadata-Version: 2.4
2
+ Name: forge-mvc-testing
3
+ Version: 1.0.0rc2
4
+ Summary: Forge : infrastructure de test partagée (FakeRequest + plugin pytest) pour tester Forge et ses opt-ins.
5
+ Author: Roger Lequette
6
+ License-Expression: LicenseRef-Forge-Proprietary
7
+ Project-URL: Homepage, https://github.com/caucrogeGit/Forge
8
+ Project-URL: Repository, https://github.com/caucrogeGit/Forge
9
+ Keywords: python,mvc,forge,testing,pytest
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Framework :: Pytest
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Requires-Python: >=3.12
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: forge-mvc<2,>=1.0.0rc2
20
+ Requires-Dist: pytest>=7
21
+ Dynamic: license-file
22
+
23
+ # forge-mvc-testing
24
+
25
+ Infrastructure de test partagée pour le framework Forge (ADR-041).
26
+
27
+ Ce paquet réservé au développement fournit de quoi tester Forge, ses opt-ins
28
+ officiels, et les opt-ins ou applications développés par des tiers :
29
+
30
+ - `FakeRequest` : une fausse requête HTTP pour tester contrôleurs et helpers sans serveur ;
31
+ - un plugin pytest (chargé automatiquement à l'installation) dont les fixtures
32
+ autouse configurent le noyau Forge et réinitialisent l'état partagé entre
33
+ tests (compteurs de tentatives, anti-replay, échecs d'audit).
34
+
35
+ ## Statut
36
+
37
+ `forge-mvc-testing` est en `Development Status :: 4 - Beta`, aligné sur la
38
+ version du cœur. C'est un outil de **développement** : une application ne
39
+ l'importe jamais à l'exécution.
40
+
41
+ ## Installation
42
+
43
+ ```bash
44
+ pip install --pre forge-mvc-testing
45
+ ```
46
+
47
+ En général comme dépendance de développement, par exemple dans
48
+ `requirements-dev.txt` :
49
+
50
+ ```
51
+ forge-mvc-testing
52
+ ```
53
+
54
+ ## Utilisation
55
+
56
+ ```python
57
+ from forge_mvc_testing import FakeRequest
58
+
59
+ def test_mon_controleur():
60
+ request = FakeRequest(method="GET", query={"page": "2"})
61
+ response = MonControleur.index(request)
62
+ assert response.status == 200
63
+ ```
64
+
65
+ Le plugin pytest s'active dès que le paquet est installé : ses fixtures autouse
66
+ préparent le noyau Forge et nettoient l'état partagé avant chaque test, sans
67
+ configuration.
68
+
69
+ ## Licence
70
+
71
+ Distribué sous licence propriétaire Forge (`LicenseRef-Forge-Proprietary`),
72
+ comme le reste du framework. Voir `LICENSE`.
@@ -0,0 +1,10 @@
1
+ forge_mvc_testing/__init__.py,sha256=QQa7_b8Uk3104OhXsDtPD3czYcBpK75yTQjS9QNVSos,532
2
+ forge_mvc_testing/fake_request.py,sha256=0T8p7HtGV4cAspwCXMpkJ1U6ZzIww5xBDWmF8esUpoI,5000
3
+ forge_mvc_testing/plugin.py,sha256=hmc8IFr2kj_-m2Mk8Pzw9Fi40BWu6UmwRec_1Vvjprc,3558
4
+ forge_mvc_testing/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ forge_mvc_testing-1.0.0rc2.dist-info/licenses/LICENSE,sha256=kT7JQI9x59JeUwpyiFmqyM6hwLhDzdPS0Wczb5iY3G0,1599
6
+ forge_mvc_testing-1.0.0rc2.dist-info/METADATA,sha256=d4euMDkgeca6Qydk4f41wtMTm2WPdg8mQZdO4MUWNPk,2361
7
+ forge_mvc_testing-1.0.0rc2.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
8
+ forge_mvc_testing-1.0.0rc2.dist-info/entry_points.txt,sha256=aTJtxAa3vA1xJ12PjiSEtjKta0HmRIO-uQHbBXyIypk,56
9
+ forge_mvc_testing-1.0.0rc2.dist-info/top_level.txt,sha256=aSejGBDuvxByKSpU7NM5UoElp8zaglVNI1HBRURevo8,18
10
+ forge_mvc_testing-1.0.0rc2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [pytest11]
2
+ forge_mvc_testing = forge_mvc_testing.plugin
@@ -0,0 +1,36 @@
1
+ Forge — Licence propriétaire / source disponible
2
+
3
+ Copyright (c) Roger Lequette.
4
+ Tous droits réservés.
5
+
6
+ Le code source de Forge est mis à disposition uniquement pour lecture, étude,
7
+ évaluation et usage éducatif personnel.
8
+
9
+ Sauf autorisation écrite préalable de Roger Lequette, il est interdit de :
10
+
11
+ - utiliser Forge dans un cadre professionnel, commercial ou institutionnel ;
12
+ - utiliser Forge pour une prestation client ;
13
+ - intégrer Forge dans un produit, service, SaaS, application vendue ou solution
14
+ déployée pour un tiers ;
15
+ - redistribuer Forge, modifié ou non ;
16
+ - publier une version modifiée de Forge ;
17
+ - vendre, louer, sous-licencier ou exploiter commercialement Forge ;
18
+ - supprimer ou modifier les mentions de copyright et de licence.
19
+
20
+ Les usages autorisés sans autorisation écrite préalable sont limités à :
21
+
22
+ - lire le code ;
23
+ - étudier son fonctionnement ;
24
+ - tester Forge à titre personnel ;
25
+ - l'utiliser dans un cadre éducatif personnel ou pédagogique non commercial ;
26
+ - évaluer le framework avant une éventuelle demande d'autorisation.
27
+
28
+ Toute utilisation non explicitement autorisée par cette licence nécessite une
29
+ autorisation écrite préalable de Roger Lequette.
30
+
31
+ Cette licence pourra évoluer ultérieurement. Toute version publiée de Forge
32
+ reste soumise à la licence présente dans son dépôt au moment de sa récupération.
33
+
34
+ LE LOGICIEL EST FOURNI « TEL QUEL », SANS GARANTIE D'AUCUNE SORTE, EXPRESSE OU
35
+ IMPLICITE. EN AUCUN CAS LE DÉTENTEUR DU COPYRIGHT NE POURRA ÊTRE TENU
36
+ RESPONSABLE DE TOUT DOMMAGE DÉCOULANT DE L'UTILISATION DE CE LOGICIEL.
@@ -0,0 +1 @@
1
+ forge_mvc_testing