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
12 changes: 8 additions & 4 deletions audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
from cli_audit.logging_config import setup_logging # noqa: E402
# Split file support (Phase 2.1)
from cli_audit.upstream_cache import ( # noqa: E402
UpstreamVersion, UpstreamCache,
UpstreamVersion,
load_upstream_cache, write_upstream_cache, get_upstream_cache_path,
update_cached_upstream,
)
Expand Down Expand Up @@ -1025,9 +1025,12 @@ def cmd_update_baseline(args: argparse.Namespace) -> int:

print(f"# Collecting upstream versions for {total} tools...", file=sys.stderr)

# Collect upstream versions only
upstream_cache = UpstreamCache()
# Collect upstream versions only. Merge into the existing baseline so a
# tool-scoped run (or a transient collection failure) never drops the
# entries that were not collected this run (issue #125).
upstream_cache = load_upstream_cache()
completed = 0
collected = 0

with ThreadPoolExecutor(max_workers=min(MAX_WORKERS, total)) as executor:
future_to_tool = {}
Expand All @@ -1048,6 +1051,7 @@ def cmd_update_baseline(args: argparse.Namespace) -> int:
)
upstream_cache.versions[tool.name] = version
completed += 1
collected += 1

display = latest_num or latest_tag or "n/a"
print(f"# [{completed}/{total}] {tool.name}: {display}", file=sys.stderr)
Expand All @@ -1059,7 +1063,7 @@ def cmd_update_baseline(args: argparse.Namespace) -> int:
write_upstream_cache(upstream_cache)
print("", file=sys.stderr)
print(f"✓ Upstream baseline updated: {get_upstream_cache_path()}", file=sys.stderr)
print(f"✓ Collected {len(upstream_cache.versions)} versions", file=sys.stderr)
print(f"✓ Collected {collected}/{total} versions", file=sys.stderr)

# Report rate limit
rate_limit = get_github_rate_limit()
Expand Down
54 changes: 54 additions & 0 deletions tests/test_update_fixes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1260,3 +1260,57 @@ def test_refreshes_installed_but_preserves_latest(self, tmp_path):
assert entry["installed_version"] not in ("", "0.0.1"), "installed must be refreshed"
# directional status: real git (e.g. 2.x) < 999.0.0 -> OUTDATED
assert entry["status"] == "OUTDATED"


@skip_on_windows
class TestUpdateBaselineMerges:
"""A tool-scoped `--update-baseline <tool>` must MERGE the collected entry
into the existing committed baseline, never replace the whole file with only
the requested tool (issue #125: `--update-baseline vault` dropped every other
entry). Failed collections must likewise leave the existing entry intact."""

def _seed_baseline(self, path):
path.write_text(json.dumps({
"__meta__": {"baseline_updated_at": "2026-01-01T00:00:00Z",
"schema_version": 2, "source": "test"},
"versions": {
"keepme": {"latest_tag": "v1.0.0", "latest_version": "1.0.0",
"latest_url": "", "tool_url": "", "upstream_method": "gh"},
"ripgrep": {"latest_tag": "v0.0.1", "latest_version": "0.0.1",
"latest_url": "", "tool_url": "", "upstream_method": "gh"},
},
}))

def test_single_tool_update_preserves_other_entries(self, tmp_path, monkeypatch):
import argparse
import audit
baseline = tmp_path / "upstream_versions.json"
self._seed_baseline(baseline)
monkeypatch.setenv("CLI_AUDIT_UPSTREAM_FILE", str(baseline))

with patch.object(audit, "collect_latest_version", return_value=("v9.9.9", "9.9.9")), \
patch.object(audit, "get_github_rate_limit", return_value=None):
rc = audit.cmd_update_baseline(argparse.Namespace(tools=["ripgrep"]))

assert rc == 0
data = json.loads(baseline.read_text())
assert data["versions"]["ripgrep"]["latest_version"] == "9.9.9"
assert "keepme" in data["versions"], "unrelated entries must survive a tool-scoped run"
assert data["versions"]["keepme"]["latest_version"] == "1.0.0"

def test_failed_collection_keeps_existing_entry(self, tmp_path, monkeypatch):
import argparse
import audit
baseline = tmp_path / "upstream_versions.json"
self._seed_baseline(baseline)
monkeypatch.setenv("CLI_AUDIT_UPSTREAM_FILE", str(baseline))

with patch.object(audit, "collect_latest_version", side_effect=RuntimeError("network down")), \
patch.object(audit, "get_github_rate_limit", return_value=None):
rc = audit.cmd_update_baseline(argparse.Namespace(tools=["ripgrep"]))

assert rc == 0
data = json.loads(baseline.read_text())
# transient failure must not delete the committed entry
assert data["versions"]["ripgrep"]["latest_version"] == "0.0.1"
assert "keepme" in data["versions"]
Loading