Implements T01-T04: - tools.py: green-commit-only-equivalent tool surface (read/write/edit/ glob/grep + git status/diff/log/add+commit), path-traversal guarded. - openrouter_client.py: own minimal chat-completions client — llm-connect's OpenRouterAdapter takes a single prompt string and never surfaces tool_calls, so it can't drive a multi-turn tool-calling loop without a breaking change to its frozen Core ABC. llm-connect stays an optional dependency (pyproject.toml), not load-bearing. - loop.py: plan -> tool call -> observe -> repeat, budget- and turn-bounded, tool errors reported back to the model instead of crashing the loop. - credentials.py: own OpenBao AppRole/ambient-token acquisition, per glas-harness ADR-002 (Option B) — glas-harness does not broker this. - runner.py/hub.py: commit-verified success criterion + State Hub progress/token reporting, mirroring rein-aharness's model. 26 tests, all mocked at the httpx/subprocess boundary — no real OpenRouter or OpenBao calls made. T05 (Forgejo repo creation) stays open, deferred to the operator. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
54 lines
1.8 KiB
Python
54 lines
1.8 KiB
Python
from unittest.mock import patch
|
|
|
|
from rein_openweights.credentials import resolve_openrouter_api_key
|
|
|
|
|
|
def test_explicit_env_var_short_circuits(monkeypatch):
|
|
monkeypatch.setenv("OPENROUTER_API_KEY", "sk-explicit")
|
|
assert resolve_openrouter_api_key() == "sk-explicit"
|
|
|
|
|
|
def test_falls_back_to_bao_kv_when_no_approle(monkeypatch):
|
|
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
|
|
monkeypatch.delenv("REIN_OPENWEIGHTS_APPROLE_DIR", raising=False)
|
|
|
|
with patch("rein_openweights.credentials._bao", return_value="sk-from-vault") as bao:
|
|
key = resolve_openrouter_api_key()
|
|
|
|
assert key == "sk-from-vault"
|
|
bao.assert_called_once()
|
|
assert bao.call_args.args[0] == "kv"
|
|
|
|
|
|
def test_approle_lane_exchanges_token_before_kv_read(monkeypatch, tmp_path):
|
|
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
|
|
role_id = tmp_path / "role_id"
|
|
secret_id = tmp_path / "secret_id"
|
|
role_id.write_text("role-123")
|
|
secret_id.write_text("secret-456")
|
|
monkeypatch.setenv("REIN_OPENWEIGHTS_APPROLE_DIR", str(tmp_path))
|
|
|
|
calls = []
|
|
|
|
def fake_bao(*args, env=None):
|
|
calls.append(args)
|
|
if args[0] == "write":
|
|
return "vault-token"
|
|
return "sk-from-vault"
|
|
|
|
with patch("rein_openweights.credentials._bao", side_effect=fake_bao):
|
|
key = resolve_openrouter_api_key()
|
|
|
|
assert key == "sk-from-vault"
|
|
assert calls[0][0] == "write"
|
|
assert calls[1][0] == "kv"
|
|
|
|
|
|
def test_returns_none_when_bao_fails(monkeypatch):
|
|
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
|
|
monkeypatch.delenv("REIN_OPENWEIGHTS_APPROLE_DIR", raising=False)
|
|
|
|
from rein_openweights.credentials import CredentialError
|
|
|
|
with patch("rein_openweights.credentials._bao", side_effect=CredentialError("boom")):
|
|
assert resolve_openrouter_api_key() is None
|