Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,13 @@ e.g.:
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
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
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
./scripts/ci/compile_opencode_review_lock.sh
```

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

## Conventions and gotchas specific to this repo

Expand Down
148 changes: 144 additions & 4 deletions requirements-opencode-review-ci-hashes.txt
Original file line number Diff line number Diff line change
@@ -1,30 +1,170 @@
# This file was autogenerated by uv via the following command:
# ./scripts/ci/compile_opencode_review_lock.sh
attrs==26.1.0 \
--hash=sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309
--hash=sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309 \
--hash=sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32
# via interrogate
click==8.4.2 \
--hash=sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6 \
--hash=sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76
# via interrogate
colorama==0.4.6 \
--hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
--hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
# via interrogate
coverage==7.14.3 \
--hash=sha256:0096fd7559178f0cc9cf088f2dbd2a02ef85bacaa69732c633517286b4494610 \
--hash=sha256:02c41de2a88011b893050fc9830267d927a50a215f7ad5ec17349db7090ccf26 \
--hash=sha256:0423d64c013057a06e70f070f073cec4b0cbc7d2b27f3c7007292f2ff1d52965 \
--hash=sha256:0ee68f5c34812780f3a7063382c0a9fcbb99985b7ddcdcaa626e4f3fb2e0783a \
--hash=sha256:11a7ec9f97ab950f4c5af62229befc7faf208fdbc0116d3902d7e306cf2c5abd \
--hash=sha256:1551b4caac3e3ec9f2bfcec6bf3776e01c0edbdd2e240431a50ca1a1aac72c27 \
--hash=sha256:16b206e521feb8b7133a45754643dead0538489cf8b783b90cf5f4e3299625fd \
--hash=sha256:1a7563a443f3d53fdeb040ec8c9f7466aed7ca3dc5891aa09d3ca3625fa4387f \
--hash=sha256:1bb93c2aa61d2a5b38f1526546d95cf4132cb681e541a337bf8dfd092be816e5 \
--hash=sha256:1e3b91f9c4740aeb571ecf82e5e8d8e4ab62d34fcb5a5d4e5baa38c6f7d2857c \
--hash=sha256:2415902f385a23dcc4ccd26e0ba803249a169af6a930c003a4c715eeb9a5444e \
--hash=sha256:27d07a46500ba23515b838dbcf52512026af04090755cf6cc64166d88c9b9a1a \
--hash=sha256:2bfc4dd0a912329eccc7484a7d0b2a38032b38c40663b1e1ac595f10c457954b \
--hash=sha256:2e41fd3aab806770008279a93879b0924b16247e09ab537c043d08bbca53b4ab \
--hash=sha256:338b19131ab1a6b767b462bfcbaa692e7ae22f24463e39d49b02a83410ff6b37 \
--hash=sha256:360bec1f58e7243e3405d3bdf7a1a8115aa9b448d54dc7cd6f7b7e0e9406b62e \
--hash=sha256:39e1dbbb6ff2c338e0196a482558a792a1de3aa64261196f5cdb3da016ad9cda \
--hash=sha256:3c68df8e61f1e09633fefc7538297145623957a048534368c9d212782aa5e845 \
--hash=sha256:3d74ff26299c4879ce3a4d826f9d3d4d556fd285fde7bbce3c0ef5a8ab1cec24 \
--hash=sha256:3e5b550a128419373c2f6cec28a244207013ef15f5cbcff6a5ca09d1dfaaf027 \
--hash=sha256:41de778bd41780586e2b04912079c73089ab5d839624e28db3bdb26de638da92 \
--hash=sha256:47968988b367990ae4ab17523790c38cd125e02c6bfd379b6022be2d40bdc38c \
--hash=sha256:4b60ca6d8af70473491a15a343cbabab2e8f9ea66a4376e81c7aa24876a6f977 \
--hash=sha256:4d310baf69a4fbe8a098ce727e4808a34866ac718a6f759ae659cbd3221358bc \
--hash=sha256:526ce9721116af23b1065089f0b75046fe521e7772ab94b641cd66b7a0421889 \
--hash=sha256:583d50d59142f8549470bd6390471d0fe8b8c8d69d6a0f28ac71e05380cef640 \
--hash=sha256:5952f8c1bda2a5347154450379316e6dfa4d934d62ca35f6784451e6f55074fb \
--hash=sha256:5d788e5fd55347eef06ca0732c77d04a264de67e8ff24631270cdff3767a60cf \
--hash=sha256:605ab2b566a22bd94834529d66d295c364aba84afd3e5498285c7a524017b1fc \
--hash=sha256:611e62cb9386096d81b63e0a05330750268617231e7bd598e1fe77482a2c58a5 \
--hash=sha256:6197e5a00183c11a8ce7c6abd18be1a9189fd8399084ffc95196f4f0db4f2137 \
--hash=sha256:621e13c6108234d7960aaf5762ab5c3c00f33c30c15af06dcbff0c73bf112727 \
--hash=sha256:92c22e19ce64ca3f2ad751f16f14df1468b4c231bd6af97185063a9c292a0cb3
--hash=sha256:62c7f79db2851c95ef020e5d28b97afde3daf9f7febcd35b53e05638f729063f \
--hash=sha256:64b2055bb6e0dc945af35cdeceb3633e6ed9273475ef3af85592410fd6803803 \
--hash=sha256:68520c90babfa2d560eca6d497921ed3a4f469623bd709733124491b2aa8ef3f \
--hash=sha256:69918344541ed9c8368566c2adc03c0e33d4550d7faa87d1b35e49b6a3286ea9 \
--hash=sha256:6a3693b4153394d265f44fb855fdc80e72403024d4d6f91c4871b334d028e4e0 \
--hash=sha256:74fdd718d88fe144f4579b8747873a07ec3f04cb837d5faec5a25d9e22fa31a8 \
--hash=sha256:7b27c822a8161afbe48e99f1adfb098d270ae7e0f7d7b0555ce110529bdb69cc \
--hash=sha256:7dfe427045520d6abca33687dfef767b4f635015893a1816c5decb12eb72ce18 \
--hash=sha256:7ea52fc08f007bcc494d4bb3df3851e95843d881860ba38fe2c64dc100db5e7d \
--hash=sha256:830c1fca669c572dec37ce9c838224ee45aac5be0f6961edf871e82e49d6537c \
--hash=sha256:8427f370ca67db4c975d2a26acfc0e5783ca0b52444dbc50278ace0f35445949 \
--hash=sha256:878832eaac515b62decfa76965aed558775f86bf1fc8cca76993c0c84ae31aed \
--hash=sha256:8ac012839ff7e396030f1e94e10553a431d14e4de2ab65cb3acb72bbd5628ca2 \
--hash=sha256:8cec0ad652ec57790970d817490105bd917d783c2f7b38d6b58a0ca312e1a336 \
--hash=sha256:8cf0f2509acb4619e2471a1951089054dd58ebea7a912066d2ea56dd4c24ca4a \
--hash=sha256:90f7608aeb5d9b60b523b9fb2a4ee1973867cc4865a3f26fe6c7577073b70205 \
--hash=sha256:92c22e19ce64ca3f2ad751f16f14df1468b4c231bd6af97185063a9c292a0cb3 \
--hash=sha256:96150a9cf3468ea20f0bc5d0e21b3df8972c31480ef90fa7614b773cc6429665 \
--hash=sha256:98a0859b0e98e43e1178a9402e19c8127766b14f7109a374d976e5a62c0e5c73 \
--hash=sha256:9973ef2463f8e6cfb61a6324126bb3e17d67a85f22f58d856e583ea2e3ca6501 \
--hash=sha256:9a3f142070eb7b82fc4085a55d887396f9c4e21250bccebe2ba22502c45b9647 \
--hash=sha256:9be4e7d4c5ca0427889f8f9d614bd630c2be741b1de7699bca3b2b6c0e41003e \
--hash=sha256:a090cbf9521e78ffdb2fcf448b72902afe9f5923ff6a12d5c0d0120200348af9 \
--hash=sha256:a2335ea5fed26af2e831094964fa3f8fae60b45f7e37fcc2d3b615b2add3ad87 \
--hash=sha256:a3c2134809e80fac091bfed18a6991b5a5eb5df5ae32b17ac4f4f99864b73dd7 \
--hash=sha256:a571bd889cd36c5922ce8e42e059f9d37d02301531d11374afa4c87a578625d5 \
--hash=sha256:a574912f3bde4b0619f6e97d01aa590b70998859244793769eb3a6df78ee56d3 \
--hash=sha256:a64caee2193563601dbaaa55fe2dcf597debef04a2f8f1fa8a07aa4bb7ac7a1e \
--hash=sha256:ac082660de8f429ba0ea363595abb838998570b9a7546777c60f413ab902bbde \
--hash=sha256:b3d77f7f196abdef7e01415de1bce09f216189e83e58159cfeef2b92d0464994 \
--hash=sha256:b3ff255799f5a1676c71c1c32ec01fd043aa09d57b3d95764b24992757184784 \
--hash=sha256:b488bd4b23397db62e7a9459129d01ff06a846582a732efd24834b24a6ada498 \
--hash=sha256:b75ee850fc2d7c831e883220c445b035f2224de2ba6103f1e56dbd237ab913f7 \
--hash=sha256:b7f300ac92cd4b570724c8ffbbd0c130fee298d2447f41d5a3abf58976fae1de \
--hash=sha256:beaab199b9e5ceaf5a225e16a9d4df136f2a1eae0a5c20de1e277c8a5225f388 \
--hash=sha256:c02efd507227bde9969cab0db8f48890eb3b5dcad6afac57a4792df4133543ce \
--hash=sha256:c66f9f9d4f1e9712eb9b1de5310f881d4e2188cfcba5065e1a8490f38687f2c4 \
--hash=sha256:c90a7cdd5e380e1ce02f19792e2ac2fbfbf177e35a27e69fd3e873b30d895c0c \
--hash=sha256:c946099774a7699de03cbd0ff0a64e21aed4525eed9d959adde4afe6d15758ef \
--hash=sha256:cc96aa922e21d4bc5d5ed3c915cef27dfcbc13686f47d5e378d647fbfba655a2 \
--hash=sha256:d20a15c622194234161535459affa8f7905830391c9ccfa060d495dbfe3a1c7f \
--hash=sha256:d48400185564042287dc487c1f016a3397f18ab4f4c5d5ec36edc218f7ffa35b \
--hash=sha256:d8e88f335544a47e22ae2e45b344772925ec65166555c958720d5ed971880891 \
--hash=sha256:dc9b4e35e7c3920e925ba7f14886fd5fbe481232754624e832ddba66c7535635 \
--hash=sha256:de76caefc8deabb0dd1678b6a980be97d14c8d87e213ac194dbf8b09e96d63fb \
--hash=sha256:e0bb8a6bc7015efdf8a928753b25da1b9ca2d6f24ef04d2ee0688e486f32aae7 \
--hash=sha256:e343fb086c9cd780b38622fea7c369acd64c1a0724312149b5d769c387a2b1f5 \
--hash=sha256:e4ed44705ca4bead6fc977a8b741f2145608289b33c8a9b42a95d0f15aedbf4d \
--hash=sha256:e574801e1d643561594aa021206c46d80b257e9853087090ba97bed8b0a509d3 \
--hash=sha256:e6230e688c7c3e65cedd41a774eb4ec221adc6bfee13768231015b702d5e4150 \
--hash=sha256:ea3169c7116eb6cdf7608c6c7da9ecfcb3da40688e3a510fac2d1d2bafd6dc35 \
--hash=sha256:eadea7aba74e40adee867a8c0eec17b820b061d308a4b014f7a0e118c2b0aa61 \
--hash=sha256:ed68faa5e85de2f3e400bc3f122e5c82735a58c8bb24b9f63a2215954ba17b2d \
--hash=sha256:f0a47095963cfe054e0df178daca95aec21e680d6076da807c3add28dfe920f7 \
--hash=sha256:f502e948e03e866538048bba081c075caaa62e5bda6ea5b7432e45f587eb462a \
--hash=sha256:f82b6bb7d75a2613e85d07cefa3a8c973d0544a8993337f6e2728e4a1e94c305 \
--hash=sha256:fa9e5c6857a7e80fa22ace5cf3550ae392bbfc322f1d8dd2d2d5a8be38cec027 \
--hash=sha256:fb7e18afb6e903c1a92401a2f0501ac277dca527bb9ca6fe1f691a8a0026a0e8 \
--hash=sha256:fbb8c3a98e779013786ae01d229662aeacbc77100efbd3f2f245219ace5af700
# via
# -r requirements-opencode-review-ci.txt
# pytest-cov
iniconfig==2.3.0 \
--hash=sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730 \
--hash=sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12
# via pytest
interrogate==1.7.0 \
--hash=sha256:a320d6ec644dfd887cc58247a345054fc4d9f981100c45184470068f4b3719b0 \
--hash=sha256:b13ff4dd8403369670e2efe684066de9fcb868ad9d7f2b4095d8112142dc9d12
# via -r requirements-opencode-review-ci.txt
packaging==26.2 \
--hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e
--hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e \
--hash=sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661
# via pytest
pluggy==1.6.0 \
--hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \
--hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746
# via
# pytest
# pytest-cov
py==1.11.0 \
--hash=sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719 \
--hash=sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378
# via interrogate
pygments==2.20.0 \
--hash=sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f \
--hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176
# via pytest
pytest==9.1.1 \
--hash=sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313 \
--hash=sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c
# via
# -r requirements-opencode-review-ci.txt
# pytest-cov
pytest-cov==7.1.0 \
--hash=sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2 \
--hash=sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678
# via -r requirements-opencode-review-ci.txt
tabulate==0.10.0 \
--hash=sha256:e2cfde8f79420f6deeffdeda9aaec3b6bc5abce947655d17ac662b126e48a60d \
--hash=sha256:f0b0622e567335c8fabaaa659f1b33bcb6ddfe2e496071b743aa113f8774f2d3
# via interrogate
uv==0.11.25 \
--hash=sha256:2c1cfe97dce56c997dfa3214bdb8955b7b34cceea7505520185e22ad99c0eb6b \
--hash=sha256:3febca65ec5bc336ddaf7e4f724704f2c894c16839723df14865ee00b4acf38d \
--hash=sha256:41b37e724f41eb4c3794bbdd82ddeebb4b5850d4ada8cccb2906ef9e5aa0f83b \
--hash=sha256:458e731778e7b5cc870710397859c23e766703e7bc0695f23b3eb15080745ba6 \
--hash=sha256:560b0fbaa6356af533923a349658c21d4f410d16e835787d8a05da451d4ee859 \
--hash=sha256:57fbd47e924242fd347d0c209d95711d8ea61db8d8780962d0f30ccde2c854a3 \
--hash=sha256:610650cbaa0a9b18015da39d2c28d736d287a5a124e49296d8fdef5e4022e980 \
--hash=sha256:61ef11d9967a38109e6e8e3d20d1f743fa08033c32bce274d6ccd9a9abb5d305 \
--hash=sha256:69d14ffd0a4b050f8a70f64aacb09b8dfdfb1cb30a6351fb17b48f273f95c58c \
--hash=sha256:79f166cd1b84f855e9d2768221d59b403869648289fd884d58ad4299edfb4d9e \
--hash=sha256:850ba0018ff170c3a9baaf9b5fe8b23393b6b77ee4ea6b2e2315fdb8d7c388f7 \
--hash=sha256:86d4759fec9b46f61944d6e9ef1f5eaa2c5fbe2db5ddb59492d9174b08fcf39c \
--hash=sha256:b180b12237b4e04692491fc6796584a9a8bdf4c7332bd2a769caf096b97885d0 \
--hash=sha256:d2bc05e17ae3e1f232abf93e7dcfb3b68702dfcde34a00c29cbce7e07d1ecbfb \
--hash=sha256:560b0fbaa6356af533923a349658c21d4f410d16e835787d8a05da451d4ee859
--hash=sha256:d6f965a79fc7539a12139ce981caa0cbf7d9d3bd4ea3daadaf174ab4d7fb6e42 \
--hash=sha256:e3480640983e0b8e509eeb67882837e620bdd820f8776948a5f13ebbb4481d04 \
--hash=sha256:f42de9e7d63a28a4fe76a522077813656de38b5acda20b4db63857d260c1ff13 \
--hash=sha256:f7a78fc8d0c5e764e9fa39c99066db47a0bc465b023feed90812e3c0a6b5eb0d \
--hash=sha256:fbff70ae9fa4da9fb6823ae4fdaf77a65c9520e13b6d1d0241ba56e4b121b7aa
# via -r requirements-opencode-review-ci.txt
14 changes: 14 additions & 0 deletions scripts/ci/compile_opencode_review_lock.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env bash
set -euo pipefail

repo_root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd -P)"
cd "$repo_root"

uv pip compile \
--upgrade \
--generate-hashes \
--python-version 3.14 \
--python-platform x86_64-manylinux_2_28 \
--custom-compile-command "./scripts/ci/compile_opencode_review_lock.sh" \
--output-file requirements-opencode-review-ci-hashes.txt \
requirements-opencode-review-ci.txt
5 changes: 5 additions & 0 deletions scripts/ci/pr_review_merge_scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -936,6 +936,11 @@ def context_nodes(pr: dict[str, Any]) -> list[dict[str, Any]]:
def is_opencode_context(node: dict[str, Any]) -> bool:
"""Return whether a check or status context belongs to OpenCode Review."""
if node.get("__typename") == "CheckRun":
if (os.environ.get("SCHEDULER_REQUIRED_WORKFLOW_REPOSITORY") or "").strip():
# Central reviews run through repository_dispatch and publish a commit
# status. Organization required-workflow CheckRuns are deliberately
# non-authoritative placeholders and must not suppress that dispatch.
return False
workflow = (
((node.get("checkSuite") or {}).get("workflowRun") or {}).get("workflow")
or {}
Expand Down
159 changes: 159 additions & 0 deletions tests/test_materialize_base_python_requirements.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from __future__ import annotations

import runpy
import subprocess
import sys
from pathlib import Path

import pytest
Expand Down Expand Up @@ -70,3 +72,160 @@ def test_rejects_invalid_base_sha(tmp_path: Path) -> None:
"""Git options and symbolic refs cannot cross the exact-SHA boundary."""
with pytest.raises(ValueError, match="40 hexadecimal"):
materializer.base_hash_locks(tmp_path, "--help")


def test_git_failure_preserves_a_visible_reason(tmp_path: Path) -> None:
"""A failed read-only git command reports its operation and stderr."""
with pytest.raises(RuntimeError, match=r"git rev-parse failed: fatal"):
materializer._git(tmp_path, "rev-parse", "HEAD")


@pytest.mark.parametrize(
("tree_output", "message"),
[
(b"malformed\0", "malformed entry"),
(b"100644 blob\tfile\0", "malformed metadata"),
],
)
def test_rejects_malformed_git_tree_entries(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
tree_output: bytes,
message: str,
) -> None:
"""Malformed git output cannot be interpreted as a trusted lock blob."""

def fake_git(_repo_root: Path, *_args: str) -> bytes:
return tree_output

monkeypatch.setattr(materializer, "_git", fake_git)

with pytest.raises(RuntimeError, match=message):
materializer.base_hash_locks(tmp_path, "a" * 40)


def test_rejects_symlink_output_directory(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""A symlink cannot redirect trusted lock materialization outside its context."""
target = tmp_path / "target"
target.mkdir()
output = tmp_path / "output"
output.symlink_to(target, target_is_directory=True)
monkeypatch.setattr(materializer, "base_hash_locks", lambda *_args: [])

with pytest.raises(ValueError, match="must not be a symlink"):
materializer.materialize(tmp_path, "a" * 40, output)


def test_main_reports_each_materialized_lock(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture[str],
) -> None:
"""The CLI identifies the exact trusted source and generated lock name."""

def fake_materialize(
_repo_root: Path, _base_sha: str, _output_dir: Path
) -> list[dict[str, str]]:
return [{"file": "requirements-000.txt", "source": "backend/requirements-hashes.txt"}]

monkeypatch.setattr(materializer, "materialize", fake_materialize)

assert (
materializer.main(
[
"--repo-root",
str(tmp_path),
"--base-sha",
"a" * 40,
"--output-dir",
str(tmp_path / "output"),
]
)
== 0
)
assert (
"Materialized trusted base Python lock backend/requirements-hashes.txt "
"as requirements-000.txt." in capsys.readouterr().out
)


def test_main_reports_when_no_locks_exist(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture[str],
) -> None:
"""The CLI distinguishes an empty trusted base from a failed extraction."""
monkeypatch.setattr(materializer, "materialize", lambda *_args: [])

assert (
materializer.main(
[
"--repo-root",
str(tmp_path),
"--base-sha",
"a" * 40,
"--output-dir",
str(tmp_path / "output"),
]
)
== 0
)
assert "No tracked requirements-hashes.txt files exist" in capsys.readouterr().out


def test_main_fails_with_the_materialization_reason(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture[str],
) -> None:
"""A materialization exception fails closed and remains diagnosable in CI."""

def fail_materialize(_repo_root: Path, _base_sha: str, _output_dir: Path) -> None:
raise OSError("fixture failure")

monkeypatch.setattr(materializer, "materialize", fail_materialize)

assert (
materializer.main(
[
"--repo-root",
str(tmp_path),
"--base-sha",
"a" * 40,
"--output-dir",
str(tmp_path / "output"),
]
)
== 1
)
assert (
"::error::Could not materialize base Python locks: fixture failure"
in capsys.readouterr().err
)


def test_script_entrypoint_exits_through_main(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""The executable script propagates the fail-closed CLI status."""
module_path = Path(materializer.__file__)
monkeypatch.setattr(
sys,
"argv",
[
str(module_path),
"--repo-root",
str(tmp_path),
"--base-sha",
"invalid",
"--output-dir",
str(tmp_path / "output"),
],
)

with pytest.raises(SystemExit) as raised:
runpy.run_path(str(module_path), run_name="__main__")

assert raised.value.code == 1
Loading
Loading