@@ -103,30 +103,26 @@ def _install_compliance_catalog_test_cache() -> None:
103103 import prowler .lib .check .compliance_models as compliance_models
104104 from prowler .lib .check .models import CheckMetadata
105105
106- framework_cache : dict [str , dict ] = {}
107- checks_cache : dict [str , dict ] = {}
108- path_cache : dict [str , object ] = {}
109-
110106 original_bulk_frameworks = (
111107 compliance_models .get_bulk_compliance_frameworks_universal
112108 )
113109 original_get_bulk = CheckMetadata .get_bulk
114110 original_load = compliance_models .load_compliance_framework_universal
115111
116112 def cached_bulk_frameworks (provider ):
117- if provider not in framework_cache :
118- framework_cache [provider ] = original_bulk_frameworks (provider )
119- return framework_cache [provider ]
113+ if provider not in _COMPLIANCE_FRAMEWORK_CACHE :
114+ _COMPLIANCE_FRAMEWORK_CACHE [provider ] = original_bulk_frameworks (provider )
115+ return _COMPLIANCE_FRAMEWORK_CACHE [provider ]
120116
121117 def cached_get_bulk (provider ):
122- if provider not in checks_cache :
123- checks_cache [provider ] = original_get_bulk (provider )
124- return checks_cache [provider ]
118+ if provider not in _COMPLIANCE_CHECKS_CACHE :
119+ _COMPLIANCE_CHECKS_CACHE [provider ] = original_get_bulk (provider )
120+ return _COMPLIANCE_CHECKS_CACHE [provider ]
125121
126122 def cached_load (path ):
127- if path not in path_cache :
128- path_cache [path ] = original_load (path )
129- return path_cache [path ]
123+ if path not in _COMPLIANCE_PATH_CACHE :
124+ _COMPLIANCE_PATH_CACHE [path ] = original_load (path )
125+ return _COMPLIANCE_PATH_CACHE [path ]
130126
131127 compliance_models .get_bulk_compliance_frameworks_universal = cached_bulk_frameworks
132128 compliance_models .load_compliance_framework_universal = cached_load
@@ -139,9 +135,38 @@ def cached_load(path):
139135 api_compliance .get_bulk_compliance_frameworks_universal = cached_bulk_frameworks
140136
141137
138+ # Module-scoped so the ``_compliance_cache_guard`` fixture below can reset them.
139+ # Keeping them out of ``_install_compliance_catalog_test_cache``'s local scope is
140+ # what makes the caches resettable between tests; the wrappers above close over
141+ # these names, and the original loaders stay referenced so patched behaviour is
142+ # still honoured.
143+ _COMPLIANCE_FRAMEWORK_CACHE : dict [str , dict ] = {}
144+ _COMPLIANCE_CHECKS_CACHE : dict [str , dict ] = {}
145+ _COMPLIANCE_PATH_CACHE : dict [str , object ] = {}
146+
147+
142148_install_compliance_catalog_test_cache ()
143149
144150
151+ @pytest .fixture (autouse = True )
152+ def _compliance_cache_guard (request ):
153+ """Reset the compliance catalog caches after any test that used ``monkeypatch``.
154+
155+ The session-wide caches in ``_install_compliance_catalog_test_cache`` let the
156+ read-only, parametrized compliance tests parse the ~100 catalog JSONs once
157+ instead of dozens of times. A test that swaps a loader (or mutates a returned
158+ object) could otherwise leak that state into later tests through the shared
159+ dicts. Using ``monkeypatch`` as the opt-in signal keeps the full speed-up for
160+ catalog-reading tests while giving patching tests a clean slate afterwards;
161+ the next test simply repopulates the caches from disk.
162+ """
163+ yield
164+ if "monkeypatch" in request .fixturenames :
165+ _COMPLIANCE_FRAMEWORK_CACHE .clear ()
166+ _COMPLIANCE_CHECKS_CACHE .clear ()
167+ _COMPLIANCE_PATH_CACHE .clear ()
168+
169+
145170def today_after_n_days (n_days : int ) -> str :
146171 return datetime .strftime (
147172 datetime .today ().date () + timedelta (days = n_days ), "%Y-%m-%d"
0 commit comments