Skip to content

Commit 55ac20a

Browse files
committed
test(artifacts): wire deployment-env config keys into the manifests_app e2e fixture
Dockerfile gains ARG BUILD_REV + ENV APP_MODE=production; docker-compose.yml gains an environment map with COMPOSE_ONLY_KEY; pkg/config_reader.py gains a sixth shape (get_app_mode -> os.getenv("APP_MODE")) that binds to the Dockerfile-defined key. Extends the exact-set config_uses/config_reads_ unresolved assertions, the extraction='full' and level-invariance checks, and adds a dedicated -a 2 literal-tier check plus a Dockerfile/compose config-keys assertion.
1 parent b5b2763 commit 55ac20a

4 files changed

Lines changed: 62 additions & 6 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
11
FROM python:3.12-slim
2+
3+
ARG BUILD_REV
4+
ENV APP_MODE=production
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
services:
22
web:
33
build: .
4+
environment:
5+
COMPOSE_ONLY_KEY: x

test/fixtures/whole_applications/manifests_app/pkg/config_reader.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Config-use e2e fixture for all five detection shapes (#162 Task 5)."""
1+
"""Config-use e2e fixture for all six detection shapes (#162 Task 5, #165)."""
22
import os
33

44

@@ -38,3 +38,9 @@ def get_config_multi_def(use_debug):
3838
# Should appear in config_reads_unresolved with reason: "undefined-key"
3939
def get_missing_config():
4040
return os.getenv("NOT_DEFINED_ANYWHERE")
41+
42+
43+
# Shape 6: Deployment-env, reads APP_MODE from the Dockerfile ENV directive
44+
# (#165). Direct literal, resolves at -a 2 like shape 1.
45+
def get_app_mode():
46+
return os.getenv("APP_MODE")

test/test_artifacts_end_to_end.py

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -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

214217
def 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

224230
def _id_suffix(id_: str) -> str:
@@ -229,7 +235,8 @@ def _id_suffix(id_: str) -> str:
229235

230236

231237
def 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+
267312
def 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

Comments
 (0)