@@ -205,20 +205,26 @@ def test_config_keys_extraction_level(tmp_path):
205205 )).analyze ().application
206206
207207 # Config keys should be identical at both levels
208- for art_name in [".env" , "config/settings.yml" , "app.properties" ]:
208+ for art_name in [
209+ ".env" , "config/settings.yml" , "app.properties" ,
210+ "Dockerfile" , "docker-compose.yml" , # deployment-env namespaces (#165)
211+ ]:
209212 l1_keys = sorted ([k .key for k in app_l1 .artifacts [art_name ].config_keys ])
210213 l4_keys = sorted ([k .key for k in app_l4 .artifacts [art_name ].config_keys ])
211214 assert l1_keys == l4_keys
212215
213216
214217def test_config_keys_extraction_full (tmp_path ):
215- """Verify extraction='full' on the three config files."""
218+ """Verify extraction='full' on the config files, including Dockerfile
219+ (newly namespace-eligible as of #165 -- it was "none" before, since
220+ `is_config_eligible` used to skip it outright)."""
216221 app = _app (tmp_path , "extraction_test" )
217222
218- # All three new config files should have extraction='full'
219223 assert app .artifacts [".env" ].extraction == "full"
220224 assert app .artifacts ["config/settings.yml" ].extraction == "full"
221225 assert app .artifacts ["app.properties" ].extraction == "full"
226+ assert app .artifacts ["Dockerfile" ].extraction == "full"
227+ assert app .artifacts ["docker-compose.yml" ].extraction == "full"
222228
223229
224230def _id_suffix (id_ : str ) -> str :
@@ -229,7 +235,8 @@ def _id_suffix(id_: str) -> str:
229235
230236
231237def test_config_uses_full (tmp_path ):
232- """Verify config-use edges at -a 4: all five fixture shapes (#162 Task 5).
238+ """Verify config-use edges at -a 4: all six fixture shapes (#162 Task 5,
239+ #165 shape 6).
233240
234241 Exact-set assertions (the plan's "exact", not a `>=` lower bound): every
235242 `config_uses` edge as `(src-suffix, dst-key, prov)` and every
@@ -238,7 +245,8 @@ def test_config_uses_full(tmp_path):
238245 against the dataflow-tier fixes landed alongside this test -- none of
239246 those fixes' shapes (aliasing, conditional shadowing, module-scope
240247 callers) occur in this fixture, so the counts are unchanged from the
241- pre-fix reviewer probe: 3 resolved, 2 unresolved at -a 4).
248+ pre-fix reviewer probe except for #165's own addition: 4 resolved, 2
249+ unresolved at -a 4).
242250 """
243251 app = Codeanalyzer (AnalysisOptions (
244252 input = FIXTURE , analysis_level = 4 , no_venv = True , cache_dir = tmp_path / "config_uses" ,
@@ -250,11 +258,14 @@ def test_config_uses_full(tmp_path):
250258 # (site is the read INSIDE _read_config, not get_secret_token's call)
251259 # 4. Multi-def unresolved: get_config_multi_def() -> unresolved (two defs)
252260 # 5. Undefined-key: get_missing_config() -> unresolved (key not in .env)
261+ # 6. Deployment-env literal: get_app_mode() -> os.getenv("APP_MODE"),
262+ # binding to the Dockerfile `ENV APP_MODE=production` key (#165).
253263 uses = {(_id_suffix (e .src ), _id_suffix (e .dst ), tuple (e .prov )) for e in app .config_uses }
254264 assert uses == {
255265 ("get_database_url()@7:11" , "DATABASE_URL" , ("literal" ,)),
256266 ("get_api_key()@14:11" , "API_KEY" , ("dataflow" ,)),
257267 ("_read_config(name)@20:11" , "SECRET_API_TOKEN" , ("dataflow" ,)),
268+ ("get_app_mode()@46:11" , "APP_MODE" , ("literal" ,)),
258269 }
259270
260271 unresolved = {(_id_suffix (r .site ), r .reason , r .key ) for r in app .config_reads_unresolved }
@@ -264,6 +275,40 @@ def test_config_uses_full(tmp_path):
264275 }
265276
266277
278+ def test_config_uses_app_mode_resolves_at_l2 (tmp_path ):
279+ """The deployment-env DoD, verified directly (#165): `get_app_mode()`'s
280+ `os.getenv("APP_MODE")` binds to the Dockerfile `ENV APP_MODE=production`
281+ key at `-a 2` already -- a direct literal, same tier as `DATABASE_URL`,
282+ with no dataflow tier needed."""
283+ app = Codeanalyzer (AnalysisOptions (
284+ input = FIXTURE , analysis_level = 2 , no_venv = True , cache_dir = tmp_path / "app_mode_l2" ,
285+ )).analyze ().application
286+ uses = {(_id_suffix (e .src ), _id_suffix (e .dst ), tuple (e .prov )) for e in app .config_uses }
287+ assert ("get_app_mode()@46:11" , "APP_MODE" , ("literal" ,)) in uses
288+
289+
290+ def test_config_keys_deployment_env (tmp_path ):
291+ """Verify Dockerfile ENV/ARG and compose environment-map config keys,
292+ and their dual-mint into namespace "env" alongside the plain namespace
293+ "yaml" dotted path (#165)."""
294+ app = _app (tmp_path , "deployment_env_test" )
295+
296+ df_keys = {(k .key , k .namespace , k .value ) for k in app .artifacts ["Dockerfile" ].config_keys }
297+ assert df_keys == {
298+ ("APP_MODE" , "env" , "production" ),
299+ ("BUILD_REV" , "dockerfile" , None ),
300+ }
301+
302+ compose_keys = {(k .key , k .namespace , k .value ) for k in app .artifacts ["docker-compose.yml" ].config_keys }
303+ assert compose_keys == {
304+ ("services.web.build" , "yaml" , "." ),
305+ ("services.web.environment.COMPOSE_ONLY_KEY" , "yaml" , "x" ),
306+ ("COMPOSE_ONLY_KEY" , "env" , "x" ), # dual-mint alongside the dotted yaml key
307+ }
308+ by = {k .key : k for k in app .artifacts ["docker-compose.yml" ].config_keys }
309+ assert by ["COMPOSE_ONLY_KEY" ].id != by ["services.web.environment.COMPOSE_ONLY_KEY" ].id
310+
311+
267312def test_config_uses_tier_visibility (tmp_path ):
268313 """Verify config-use edges only appear at their appropriate tier (#162):
269314 monotonic growth AND that each tier's own shape is genuinely gated, not
0 commit comments