Skip to content

Commit 488ad72

Browse files
authored
Merge pull request #639 from ContextualWisdomLab/codex/fix-coverage-py314-lock
fix(ci): regenerate OpenCode review lock for Python 3.14
2 parents 87f2fdb + a818e33 commit 488ad72

7 files changed

Lines changed: 382 additions & 6 deletions

CLAUDE.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,13 @@ e.g.:
9595
uv pip compile --generate-hashes --python-version 3.12 --python-platform x86_64-manylinux_2_28 requirements-bandit-ci.txt -o requirements-bandit-ci-hashes.txt
9696
uv pip compile --generate-hashes --python-version 3.12 --python-platform x86_64-manylinux_2_28 requirements-pip-audit-ci.txt -o requirements-pip-audit-ci-hashes.txt
9797
uv pip compile --generate-hashes --python-version 3.13 --python-platform x86_64-manylinux_2_28 --output-file requirements-strix-ci-hashes.txt requirements-strix-ci.txt
98+
./scripts/ci/compile_opencode_review_lock.sh
9899
```
99100

100-
Note the per-file Python versions differ (bandit/pip-audit: 3.12; strix: 3.13).
101+
Note the per-file Python versions differ (bandit/pip-audit: 3.12; strix: 3.13; OpenCode
102+
review: 3.14). The OpenCode review generator always passes `--upgrade` so an existing output
103+
file cannot preserve hashes from the previous Python target, and records itself as the lock's
104+
repeatable compile command.
101105

102106
## Conventions and gotchas specific to this repo
103107

Lines changed: 144 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,170 @@
1+
# This file was autogenerated by uv via the following command:
2+
# ./scripts/ci/compile_opencode_review_lock.sh
13
attrs==26.1.0 \
2-
--hash=sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309
4+
--hash=sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309 \
5+
--hash=sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32
6+
# via interrogate
37
click==8.4.2 \
8+
--hash=sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6 \
49
--hash=sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76
10+
# via interrogate
511
colorama==0.4.6 \
12+
--hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
613
--hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
14+
# via interrogate
715
coverage==7.14.3 \
16+
--hash=sha256:0096fd7559178f0cc9cf088f2dbd2a02ef85bacaa69732c633517286b4494610 \
17+
--hash=sha256:02c41de2a88011b893050fc9830267d927a50a215f7ad5ec17349db7090ccf26 \
18+
--hash=sha256:0423d64c013057a06e70f070f073cec4b0cbc7d2b27f3c7007292f2ff1d52965 \
19+
--hash=sha256:0ee68f5c34812780f3a7063382c0a9fcbb99985b7ddcdcaa626e4f3fb2e0783a \
20+
--hash=sha256:11a7ec9f97ab950f4c5af62229befc7faf208fdbc0116d3902d7e306cf2c5abd \
21+
--hash=sha256:1551b4caac3e3ec9f2bfcec6bf3776e01c0edbdd2e240431a50ca1a1aac72c27 \
22+
--hash=sha256:16b206e521feb8b7133a45754643dead0538489cf8b783b90cf5f4e3299625fd \
23+
--hash=sha256:1a7563a443f3d53fdeb040ec8c9f7466aed7ca3dc5891aa09d3ca3625fa4387f \
24+
--hash=sha256:1bb93c2aa61d2a5b38f1526546d95cf4132cb681e541a337bf8dfd092be816e5 \
25+
--hash=sha256:1e3b91f9c4740aeb571ecf82e5e8d8e4ab62d34fcb5a5d4e5baa38c6f7d2857c \
26+
--hash=sha256:2415902f385a23dcc4ccd26e0ba803249a169af6a930c003a4c715eeb9a5444e \
27+
--hash=sha256:27d07a46500ba23515b838dbcf52512026af04090755cf6cc64166d88c9b9a1a \
28+
--hash=sha256:2bfc4dd0a912329eccc7484a7d0b2a38032b38c40663b1e1ac595f10c457954b \
29+
--hash=sha256:2e41fd3aab806770008279a93879b0924b16247e09ab537c043d08bbca53b4ab \
30+
--hash=sha256:338b19131ab1a6b767b462bfcbaa692e7ae22f24463e39d49b02a83410ff6b37 \
31+
--hash=sha256:360bec1f58e7243e3405d3bdf7a1a8115aa9b448d54dc7cd6f7b7e0e9406b62e \
32+
--hash=sha256:39e1dbbb6ff2c338e0196a482558a792a1de3aa64261196f5cdb3da016ad9cda \
33+
--hash=sha256:3c68df8e61f1e09633fefc7538297145623957a048534368c9d212782aa5e845 \
34+
--hash=sha256:3d74ff26299c4879ce3a4d826f9d3d4d556fd285fde7bbce3c0ef5a8ab1cec24 \
35+
--hash=sha256:3e5b550a128419373c2f6cec28a244207013ef15f5cbcff6a5ca09d1dfaaf027 \
36+
--hash=sha256:41de778bd41780586e2b04912079c73089ab5d839624e28db3bdb26de638da92 \
37+
--hash=sha256:47968988b367990ae4ab17523790c38cd125e02c6bfd379b6022be2d40bdc38c \
38+
--hash=sha256:4b60ca6d8af70473491a15a343cbabab2e8f9ea66a4376e81c7aa24876a6f977 \
39+
--hash=sha256:4d310baf69a4fbe8a098ce727e4808a34866ac718a6f759ae659cbd3221358bc \
40+
--hash=sha256:526ce9721116af23b1065089f0b75046fe521e7772ab94b641cd66b7a0421889 \
41+
--hash=sha256:583d50d59142f8549470bd6390471d0fe8b8c8d69d6a0f28ac71e05380cef640 \
42+
--hash=sha256:5952f8c1bda2a5347154450379316e6dfa4d934d62ca35f6784451e6f55074fb \
43+
--hash=sha256:5d788e5fd55347eef06ca0732c77d04a264de67e8ff24631270cdff3767a60cf \
44+
--hash=sha256:605ab2b566a22bd94834529d66d295c364aba84afd3e5498285c7a524017b1fc \
45+
--hash=sha256:611e62cb9386096d81b63e0a05330750268617231e7bd598e1fe77482a2c58a5 \
46+
--hash=sha256:6197e5a00183c11a8ce7c6abd18be1a9189fd8399084ffc95196f4f0db4f2137 \
847
--hash=sha256:621e13c6108234d7960aaf5762ab5c3c00f33c30c15af06dcbff0c73bf112727 \
9-
--hash=sha256:92c22e19ce64ca3f2ad751f16f14df1468b4c231bd6af97185063a9c292a0cb3
48+
--hash=sha256:62c7f79db2851c95ef020e5d28b97afde3daf9f7febcd35b53e05638f729063f \
49+
--hash=sha256:64b2055bb6e0dc945af35cdeceb3633e6ed9273475ef3af85592410fd6803803 \
50+
--hash=sha256:68520c90babfa2d560eca6d497921ed3a4f469623bd709733124491b2aa8ef3f \
51+
--hash=sha256:69918344541ed9c8368566c2adc03c0e33d4550d7faa87d1b35e49b6a3286ea9 \
52+
--hash=sha256:6a3693b4153394d265f44fb855fdc80e72403024d4d6f91c4871b334d028e4e0 \
53+
--hash=sha256:74fdd718d88fe144f4579b8747873a07ec3f04cb837d5faec5a25d9e22fa31a8 \
54+
--hash=sha256:7b27c822a8161afbe48e99f1adfb098d270ae7e0f7d7b0555ce110529bdb69cc \
55+
--hash=sha256:7dfe427045520d6abca33687dfef767b4f635015893a1816c5decb12eb72ce18 \
56+
--hash=sha256:7ea52fc08f007bcc494d4bb3df3851e95843d881860ba38fe2c64dc100db5e7d \
57+
--hash=sha256:830c1fca669c572dec37ce9c838224ee45aac5be0f6961edf871e82e49d6537c \
58+
--hash=sha256:8427f370ca67db4c975d2a26acfc0e5783ca0b52444dbc50278ace0f35445949 \
59+
--hash=sha256:878832eaac515b62decfa76965aed558775f86bf1fc8cca76993c0c84ae31aed \
60+
--hash=sha256:8ac012839ff7e396030f1e94e10553a431d14e4de2ab65cb3acb72bbd5628ca2 \
61+
--hash=sha256:8cec0ad652ec57790970d817490105bd917d783c2f7b38d6b58a0ca312e1a336 \
62+
--hash=sha256:8cf0f2509acb4619e2471a1951089054dd58ebea7a912066d2ea56dd4c24ca4a \
63+
--hash=sha256:90f7608aeb5d9b60b523b9fb2a4ee1973867cc4865a3f26fe6c7577073b70205 \
64+
--hash=sha256:92c22e19ce64ca3f2ad751f16f14df1468b4c231bd6af97185063a9c292a0cb3 \
65+
--hash=sha256:96150a9cf3468ea20f0bc5d0e21b3df8972c31480ef90fa7614b773cc6429665 \
66+
--hash=sha256:98a0859b0e98e43e1178a9402e19c8127766b14f7109a374d976e5a62c0e5c73 \
67+
--hash=sha256:9973ef2463f8e6cfb61a6324126bb3e17d67a85f22f58d856e583ea2e3ca6501 \
68+
--hash=sha256:9a3f142070eb7b82fc4085a55d887396f9c4e21250bccebe2ba22502c45b9647 \
69+
--hash=sha256:9be4e7d4c5ca0427889f8f9d614bd630c2be741b1de7699bca3b2b6c0e41003e \
70+
--hash=sha256:a090cbf9521e78ffdb2fcf448b72902afe9f5923ff6a12d5c0d0120200348af9 \
71+
--hash=sha256:a2335ea5fed26af2e831094964fa3f8fae60b45f7e37fcc2d3b615b2add3ad87 \
72+
--hash=sha256:a3c2134809e80fac091bfed18a6991b5a5eb5df5ae32b17ac4f4f99864b73dd7 \
73+
--hash=sha256:a571bd889cd36c5922ce8e42e059f9d37d02301531d11374afa4c87a578625d5 \
74+
--hash=sha256:a574912f3bde4b0619f6e97d01aa590b70998859244793769eb3a6df78ee56d3 \
75+
--hash=sha256:a64caee2193563601dbaaa55fe2dcf597debef04a2f8f1fa8a07aa4bb7ac7a1e \
76+
--hash=sha256:ac082660de8f429ba0ea363595abb838998570b9a7546777c60f413ab902bbde \
77+
--hash=sha256:b3d77f7f196abdef7e01415de1bce09f216189e83e58159cfeef2b92d0464994 \
78+
--hash=sha256:b3ff255799f5a1676c71c1c32ec01fd043aa09d57b3d95764b24992757184784 \
79+
--hash=sha256:b488bd4b23397db62e7a9459129d01ff06a846582a732efd24834b24a6ada498 \
80+
--hash=sha256:b75ee850fc2d7c831e883220c445b035f2224de2ba6103f1e56dbd237ab913f7 \
81+
--hash=sha256:b7f300ac92cd4b570724c8ffbbd0c130fee298d2447f41d5a3abf58976fae1de \
82+
--hash=sha256:beaab199b9e5ceaf5a225e16a9d4df136f2a1eae0a5c20de1e277c8a5225f388 \
83+
--hash=sha256:c02efd507227bde9969cab0db8f48890eb3b5dcad6afac57a4792df4133543ce \
84+
--hash=sha256:c66f9f9d4f1e9712eb9b1de5310f881d4e2188cfcba5065e1a8490f38687f2c4 \
85+
--hash=sha256:c90a7cdd5e380e1ce02f19792e2ac2fbfbf177e35a27e69fd3e873b30d895c0c \
86+
--hash=sha256:c946099774a7699de03cbd0ff0a64e21aed4525eed9d959adde4afe6d15758ef \
87+
--hash=sha256:cc96aa922e21d4bc5d5ed3c915cef27dfcbc13686f47d5e378d647fbfba655a2 \
88+
--hash=sha256:d20a15c622194234161535459affa8f7905830391c9ccfa060d495dbfe3a1c7f \
89+
--hash=sha256:d48400185564042287dc487c1f016a3397f18ab4f4c5d5ec36edc218f7ffa35b \
90+
--hash=sha256:d8e88f335544a47e22ae2e45b344772925ec65166555c958720d5ed971880891 \
91+
--hash=sha256:dc9b4e35e7c3920e925ba7f14886fd5fbe481232754624e832ddba66c7535635 \
92+
--hash=sha256:de76caefc8deabb0dd1678b6a980be97d14c8d87e213ac194dbf8b09e96d63fb \
93+
--hash=sha256:e0bb8a6bc7015efdf8a928753b25da1b9ca2d6f24ef04d2ee0688e486f32aae7 \
94+
--hash=sha256:e343fb086c9cd780b38622fea7c369acd64c1a0724312149b5d769c387a2b1f5 \
95+
--hash=sha256:e4ed44705ca4bead6fc977a8b741f2145608289b33c8a9b42a95d0f15aedbf4d \
96+
--hash=sha256:e574801e1d643561594aa021206c46d80b257e9853087090ba97bed8b0a509d3 \
97+
--hash=sha256:e6230e688c7c3e65cedd41a774eb4ec221adc6bfee13768231015b702d5e4150 \
98+
--hash=sha256:ea3169c7116eb6cdf7608c6c7da9ecfcb3da40688e3a510fac2d1d2bafd6dc35 \
99+
--hash=sha256:eadea7aba74e40adee867a8c0eec17b820b061d308a4b014f7a0e118c2b0aa61 \
100+
--hash=sha256:ed68faa5e85de2f3e400bc3f122e5c82735a58c8bb24b9f63a2215954ba17b2d \
101+
--hash=sha256:f0a47095963cfe054e0df178daca95aec21e680d6076da807c3add28dfe920f7 \
102+
--hash=sha256:f502e948e03e866538048bba081c075caaa62e5bda6ea5b7432e45f587eb462a \
103+
--hash=sha256:f82b6bb7d75a2613e85d07cefa3a8c973d0544a8993337f6e2728e4a1e94c305 \
104+
--hash=sha256:fa9e5c6857a7e80fa22ace5cf3550ae392bbfc322f1d8dd2d2d5a8be38cec027 \
105+
--hash=sha256:fb7e18afb6e903c1a92401a2f0501ac277dca527bb9ca6fe1f691a8a0026a0e8 \
106+
--hash=sha256:fbb8c3a98e779013786ae01d229662aeacbc77100efbd3f2f245219ace5af700
107+
# via
108+
# -r requirements-opencode-review-ci.txt
109+
# pytest-cov
10110
iniconfig==2.3.0 \
111+
--hash=sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730 \
11112
--hash=sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12
113+
# via pytest
12114
interrogate==1.7.0 \
115+
--hash=sha256:a320d6ec644dfd887cc58247a345054fc4d9f981100c45184470068f4b3719b0 \
13116
--hash=sha256:b13ff4dd8403369670e2efe684066de9fcb868ad9d7f2b4095d8112142dc9d12
117+
# via -r requirements-opencode-review-ci.txt
14118
packaging==26.2 \
15-
--hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e
119+
--hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e \
120+
--hash=sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661
121+
# via pytest
16122
pluggy==1.6.0 \
123+
--hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \
17124
--hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746
125+
# via
126+
# pytest
127+
# pytest-cov
18128
py==1.11.0 \
129+
--hash=sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719 \
19130
--hash=sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378
131+
# via interrogate
20132
pygments==2.20.0 \
133+
--hash=sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f \
21134
--hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176
135+
# via pytest
22136
pytest==9.1.1 \
137+
--hash=sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313 \
23138
--hash=sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c
139+
# via
140+
# -r requirements-opencode-review-ci.txt
141+
# pytest-cov
24142
pytest-cov==7.1.0 \
143+
--hash=sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2 \
25144
--hash=sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678
145+
# via -r requirements-opencode-review-ci.txt
26146
tabulate==0.10.0 \
147+
--hash=sha256:e2cfde8f79420f6deeffdeda9aaec3b6bc5abce947655d17ac662b126e48a60d \
27148
--hash=sha256:f0b0622e567335c8fabaaa659f1b33bcb6ddfe2e496071b743aa113f8774f2d3
149+
# via interrogate
28150
uv==0.11.25 \
151+
--hash=sha256:2c1cfe97dce56c997dfa3214bdb8955b7b34cceea7505520185e22ad99c0eb6b \
152+
--hash=sha256:3febca65ec5bc336ddaf7e4f724704f2c894c16839723df14865ee00b4acf38d \
153+
--hash=sha256:41b37e724f41eb4c3794bbdd82ddeebb4b5850d4ada8cccb2906ef9e5aa0f83b \
154+
--hash=sha256:458e731778e7b5cc870710397859c23e766703e7bc0695f23b3eb15080745ba6 \
155+
--hash=sha256:560b0fbaa6356af533923a349658c21d4f410d16e835787d8a05da451d4ee859 \
156+
--hash=sha256:57fbd47e924242fd347d0c209d95711d8ea61db8d8780962d0f30ccde2c854a3 \
157+
--hash=sha256:610650cbaa0a9b18015da39d2c28d736d287a5a124e49296d8fdef5e4022e980 \
158+
--hash=sha256:61ef11d9967a38109e6e8e3d20d1f743fa08033c32bce274d6ccd9a9abb5d305 \
159+
--hash=sha256:69d14ffd0a4b050f8a70f64aacb09b8dfdfb1cb30a6351fb17b48f273f95c58c \
160+
--hash=sha256:79f166cd1b84f855e9d2768221d59b403869648289fd884d58ad4299edfb4d9e \
161+
--hash=sha256:850ba0018ff170c3a9baaf9b5fe8b23393b6b77ee4ea6b2e2315fdb8d7c388f7 \
162+
--hash=sha256:86d4759fec9b46f61944d6e9ef1f5eaa2c5fbe2db5ddb59492d9174b08fcf39c \
163+
--hash=sha256:b180b12237b4e04692491fc6796584a9a8bdf4c7332bd2a769caf096b97885d0 \
29164
--hash=sha256:d2bc05e17ae3e1f232abf93e7dcfb3b68702dfcde34a00c29cbce7e07d1ecbfb \
30-
--hash=sha256:560b0fbaa6356af533923a349658c21d4f410d16e835787d8a05da451d4ee859
165+
--hash=sha256:d6f965a79fc7539a12139ce981caa0cbf7d9d3bd4ea3daadaf174ab4d7fb6e42 \
166+
--hash=sha256:e3480640983e0b8e509eeb67882837e620bdd820f8776948a5f13ebbb4481d04 \
167+
--hash=sha256:f42de9e7d63a28a4fe76a522077813656de38b5acda20b4db63857d260c1ff13 \
168+
--hash=sha256:f7a78fc8d0c5e764e9fa39c99066db47a0bc465b023feed90812e3c0a6b5eb0d \
169+
--hash=sha256:fbff70ae9fa4da9fb6823ae4fdaf77a65c9520e13b6d1d0241ba56e4b121b7aa
170+
# via -r requirements-opencode-review-ci.txt
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
repo_root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd -P)"
5+
cd "$repo_root"
6+
7+
uv pip compile \
8+
--upgrade \
9+
--generate-hashes \
10+
--python-version 3.14 \
11+
--python-platform x86_64-manylinux_2_28 \
12+
--custom-compile-command "./scripts/ci/compile_opencode_review_lock.sh" \
13+
--output-file requirements-opencode-review-ci-hashes.txt \
14+
requirements-opencode-review-ci.txt

scripts/ci/pr_review_merge_scheduler.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -936,6 +936,11 @@ def context_nodes(pr: dict[str, Any]) -> list[dict[str, Any]]:
936936
def is_opencode_context(node: dict[str, Any]) -> bool:
937937
"""Return whether a check or status context belongs to OpenCode Review."""
938938
if node.get("__typename") == "CheckRun":
939+
if (os.environ.get("SCHEDULER_REQUIRED_WORKFLOW_REPOSITORY") or "").strip():
940+
# Central reviews run through repository_dispatch and publish a commit
941+
# status. Organization required-workflow CheckRuns are deliberately
942+
# non-authoritative placeholders and must not suppress that dispatch.
943+
return False
939944
workflow = (
940945
((node.get("checkSuite") or {}).get("workflowRun") or {}).get("workflow")
941946
or {}

tests/test_materialize_base_python_requirements.py

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from __future__ import annotations
22

3+
import runpy
34
import subprocess
5+
import sys
46
from pathlib import Path
57

68
import pytest
@@ -70,3 +72,160 @@ def test_rejects_invalid_base_sha(tmp_path: Path) -> None:
7072
"""Git options and symbolic refs cannot cross the exact-SHA boundary."""
7173
with pytest.raises(ValueError, match="40 hexadecimal"):
7274
materializer.base_hash_locks(tmp_path, "--help")
75+
76+
77+
def test_git_failure_preserves_a_visible_reason(tmp_path: Path) -> None:
78+
"""A failed read-only git command reports its operation and stderr."""
79+
with pytest.raises(RuntimeError, match=r"git rev-parse failed: fatal"):
80+
materializer._git(tmp_path, "rev-parse", "HEAD")
81+
82+
83+
@pytest.mark.parametrize(
84+
("tree_output", "message"),
85+
[
86+
(b"malformed\0", "malformed entry"),
87+
(b"100644 blob\tfile\0", "malformed metadata"),
88+
],
89+
)
90+
def test_rejects_malformed_git_tree_entries(
91+
tmp_path: Path,
92+
monkeypatch: pytest.MonkeyPatch,
93+
tree_output: bytes,
94+
message: str,
95+
) -> None:
96+
"""Malformed git output cannot be interpreted as a trusted lock blob."""
97+
98+
def fake_git(_repo_root: Path, *_args: str) -> bytes:
99+
return tree_output
100+
101+
monkeypatch.setattr(materializer, "_git", fake_git)
102+
103+
with pytest.raises(RuntimeError, match=message):
104+
materializer.base_hash_locks(tmp_path, "a" * 40)
105+
106+
107+
def test_rejects_symlink_output_directory(
108+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
109+
) -> None:
110+
"""A symlink cannot redirect trusted lock materialization outside its context."""
111+
target = tmp_path / "target"
112+
target.mkdir()
113+
output = tmp_path / "output"
114+
output.symlink_to(target, target_is_directory=True)
115+
monkeypatch.setattr(materializer, "base_hash_locks", lambda *_args: [])
116+
117+
with pytest.raises(ValueError, match="must not be a symlink"):
118+
materializer.materialize(tmp_path, "a" * 40, output)
119+
120+
121+
def test_main_reports_each_materialized_lock(
122+
tmp_path: Path,
123+
monkeypatch: pytest.MonkeyPatch,
124+
capsys: pytest.CaptureFixture[str],
125+
) -> None:
126+
"""The CLI identifies the exact trusted source and generated lock name."""
127+
128+
def fake_materialize(
129+
_repo_root: Path, _base_sha: str, _output_dir: Path
130+
) -> list[dict[str, str]]:
131+
return [{"file": "requirements-000.txt", "source": "backend/requirements-hashes.txt"}]
132+
133+
monkeypatch.setattr(materializer, "materialize", fake_materialize)
134+
135+
assert (
136+
materializer.main(
137+
[
138+
"--repo-root",
139+
str(tmp_path),
140+
"--base-sha",
141+
"a" * 40,
142+
"--output-dir",
143+
str(tmp_path / "output"),
144+
]
145+
)
146+
== 0
147+
)
148+
assert (
149+
"Materialized trusted base Python lock backend/requirements-hashes.txt "
150+
"as requirements-000.txt." in capsys.readouterr().out
151+
)
152+
153+
154+
def test_main_reports_when_no_locks_exist(
155+
tmp_path: Path,
156+
monkeypatch: pytest.MonkeyPatch,
157+
capsys: pytest.CaptureFixture[str],
158+
) -> None:
159+
"""The CLI distinguishes an empty trusted base from a failed extraction."""
160+
monkeypatch.setattr(materializer, "materialize", lambda *_args: [])
161+
162+
assert (
163+
materializer.main(
164+
[
165+
"--repo-root",
166+
str(tmp_path),
167+
"--base-sha",
168+
"a" * 40,
169+
"--output-dir",
170+
str(tmp_path / "output"),
171+
]
172+
)
173+
== 0
174+
)
175+
assert "No tracked requirements-hashes.txt files exist" in capsys.readouterr().out
176+
177+
178+
def test_main_fails_with_the_materialization_reason(
179+
tmp_path: Path,
180+
monkeypatch: pytest.MonkeyPatch,
181+
capsys: pytest.CaptureFixture[str],
182+
) -> None:
183+
"""A materialization exception fails closed and remains diagnosable in CI."""
184+
185+
def fail_materialize(_repo_root: Path, _base_sha: str, _output_dir: Path) -> None:
186+
raise OSError("fixture failure")
187+
188+
monkeypatch.setattr(materializer, "materialize", fail_materialize)
189+
190+
assert (
191+
materializer.main(
192+
[
193+
"--repo-root",
194+
str(tmp_path),
195+
"--base-sha",
196+
"a" * 40,
197+
"--output-dir",
198+
str(tmp_path / "output"),
199+
]
200+
)
201+
== 1
202+
)
203+
assert (
204+
"::error::Could not materialize base Python locks: fixture failure"
205+
in capsys.readouterr().err
206+
)
207+
208+
209+
def test_script_entrypoint_exits_through_main(
210+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
211+
) -> None:
212+
"""The executable script propagates the fail-closed CLI status."""
213+
module_path = Path(materializer.__file__)
214+
monkeypatch.setattr(
215+
sys,
216+
"argv",
217+
[
218+
str(module_path),
219+
"--repo-root",
220+
str(tmp_path),
221+
"--base-sha",
222+
"invalid",
223+
"--output-dir",
224+
str(tmp_path / "output"),
225+
],
226+
)
227+
228+
with pytest.raises(SystemExit) as raised:
229+
runpy.run_path(str(module_path), run_name="__main__")
230+
231+
assert raised.value.code == 1

0 commit comments

Comments
 (0)