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
7 changes: 7 additions & 0 deletions crates/socket-patch-cli/tests/cli/envelope_helper_copies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const PENDING_PRIVATE_READERS: &[&str] = &[
"in_process_vendor_pnpm_takeover.rs",
"vendor_eject_bun_lockb.rs",
"vendor_jvm_cli.rs",
"apply/in_process_gem_fallback_home.rs",
"e2e_vex_lockfile/cargo.rs",
"in_process_vendor_bun_takeover/vlt.rs",
"scan/scan_paths_e2e.rs",
Expand All @@ -52,6 +53,8 @@ const READER_NAMES: &[&str] = &[
"find_event",
"parse_envelope",
"parse_json_envelope",
"parse_env",
"events_of",
"event_codes",
"event_triples",
"event_purls",
Expand Down Expand Up @@ -141,6 +144,8 @@ fn the_detector_sees_every_former_copy_shape() {
"fn warning_codes(doc: &Value) -> Vec<&str> {",
"fn event_codes(v: &Value) -> Vec<String> {",
"fn event_purls(v: &serde_json::Value, action: &str) -> Vec<String> {",
"fn parse_env(stdout: &str) -> serde_json::Value {",
"fn events_of(v: &serde_json::Value) -> Vec<serde_json::Value> {",
] {
assert!(defines_private_reader(copy), "missed: {copy}");
}
Expand All @@ -149,6 +154,8 @@ fn the_detector_sees_every_former_copy_shape() {
" fn warning_codes(&self) -> Vec<String> {",
"fn events_for_purl(env: &Value) -> Vec<Value> {",
"fn codes_of_lock(lock: &str) -> Vec<String> {",
"fn parse_env_vars(text: &str) -> Vec<(String, String)> {",
"fn events_of_kind(v: &Value, kind: &str) -> Vec<Value> {",
"let codes = codes_in(&env[\"warnings\"]);",
] {
assert!(
Expand Down
6 changes: 3 additions & 3 deletions crates/socket-patch-cli/tests/repair/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ mod vlt_hosted_common;
#[path = "../vlt_hosted_common/vendored.rs"]
mod vlt_vendored;

mod coverage_fix_repair_vendor_predelete;
mod covgap_commands_repair;
mod covgap_commands_repair_vendor;
mod repair_invariants;
mod repair_render;
mod repair_vendor_e2e;
mod repair_vendor_failure_paths;
mod repair_vendor_flavors_e2e;
mod repair_vendor_predelete;

#[path = "../prebuilt_common/mod.rs"]
mod prebuilt_common;
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
//! Coverage-gap tests for `commands/repair.rs`: the human-mode (non-`--json`)
//! output arms that every existing suite drove through `--json`, plus the
//! warn-and-continue housekeeping contracts.
//! `repair`'s render suite (#1089): the human-mode (non-`--json`) output
//! of `commands/repair.rs`, plus the warn-and-continue housekeeping
//! contracts. Behavior is asserted through `--json` codes elsewhere; the
//! sentence checks here are the render checks, one per message family.
//!
//! Ranges pinned:
//! Lines pinned:
//! * the loud `manifest_not_found` / `repair_failed` stderr prints,
//! * the loud "All {artifacts} are present locally." summary,
//! * the `... and N more` truncation of the offline warning (>5 missing)
Expand All @@ -23,6 +24,7 @@
use std::path::{Path, PathBuf};
use std::process::Command;

use crate::common::envelope::{find_event, json_string, parse_json_envelope};
use crate::common::{binary, git_sha256, hermetic_command};

use wiremock::matchers::{method, path};
Expand Down Expand Up @@ -371,7 +373,9 @@ fn repair_removes_orphan_archives_human_mode_prints_relabeled_summary() {
!stdout.contains("unused blob"),
"no archive line may use the blob wording; stdout=\n{stdout}"
);
// The download phase checks blobs only: the referenced one is present.
// The download phase checks blobs only: the referenced one stays, and
// its summary keeps the blob noun.
assert!(socket.join("blobs").join(REFERENCED_HASH).exists());
assert!(
stdout.contains("All blobs are present locally."),
"the referenced blob is present; stdout=\n{stdout}"
Expand Down Expand Up @@ -464,21 +468,12 @@ fn repair_archive_cleanup_failure_warns_and_continues() {
Some(0),
"json: archive-cleanup failure stays non-fatal; stdout=\n{json_stdout}"
);
let v: serde_json::Value = serde_json::from_str(json_stdout.trim()).expect("envelope JSON");
assert_eq!(v["status"], "success");
let events = v["events"].as_array().expect("events array");
let skip = events
.iter()
.find(|e| e["action"] == "skipped" && e["errorCode"] == "cleanup_failed")
.unwrap_or_else(|| {
panic!("json: envelope must record the failed archive cleanup; got events={events:?}")
});
assert!(
skip["reason"]
.as_str()
.unwrap_or("")
.contains("diff cleanup failed"),
"the skip reason must name the failing archive pass; got {skip}"
let v = parse_json_envelope(&json_stdout);
assert_eq!(json_string(&v, "status"), Some("success"), "envelope={v}");
let skip = find_event(&v, "skipped", Some("cleanup_failed"));
assert!(
skip["reason"].as_str().unwrap_or("").starts_with("diff "),
"the skip must be labeled with the failing archive pass; got {skip}"
);
// The packages pass still swept its orphan: `gc` reports it (the
// failed diffs pass counts as empty).
Expand Down
89 changes: 41 additions & 48 deletions crates/socket-patch-cli/tests/repair/repair_vendor_e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
//! empty-wiring revert refusal. Their fixture pair is hand-written, modeled
//! byte-for-byte on real `bundle lock` output (bundler 4.0.15).

use crate::common::envelope::{events, parse_json_envelope};
use crate::common::{binary, git_sha256};

use std::path::{Path, PathBuf};
Expand Down Expand Up @@ -212,14 +213,6 @@ fn vendor_project(root: &Path, mock_uri: &str, extra: &[&str]) -> PathBuf {
tgz
}

fn parse_env(stdout: &str) -> serde_json::Value {
serde_json::from_str(stdout.trim()).unwrap_or_else(|e| panic!("bad JSON ({e}): {stdout}"))
}

fn events_of(v: &serde_json::Value) -> Vec<serde_json::Value> {
v["events"].as_array().cloned().unwrap_or_default()
}

/// 1. Deleted tarball → `repair` rebuilds it byte-identically (installed
/// copy + view-fetched patch content), lockfile and ledger untouched.
#[tokio::test]
Expand All @@ -241,10 +234,10 @@ async fn repair_rebuilds_deleted_vendored_tarball() {

let (code, stdout, stderr) = run_cli(tmp.path(), &mock.uri(), &["repair"]);
assert_eq!(code, 0, "stdout={stdout} stderr={stderr}");
let v = parse_env(&stdout);
let v = parse_json_envelope(&stdout);
assert_eq!(v["summary"]["rebuilt"], 1, "envelope={v}");
assert!(
events_of(&v)
events(&v)
.iter()
.any(|e| e["action"] == "rebuilt" && e["purl"] == PURL),
"envelope={v}"
Expand All @@ -268,7 +261,7 @@ async fn repair_rebuilds_deleted_vendored_tarball() {
// Healthy re-run: nothing to rebuild.
let (code, stdout, _) = run_cli(tmp.path(), &mock.uri(), &["repair"]);
assert_eq!(code, 0);
let v = parse_env(&stdout);
let v = parse_json_envelope(&stdout);
assert!(
v["summary"]["rebuilt"].is_null() || v["summary"]["rebuilt"] == 0,
"healthy ledger rebuilds nothing: {v}"
Expand Down Expand Up @@ -298,7 +291,7 @@ async fn repair_offline_refuses_even_with_installed_tree_and_blobs() {
let before_reqs = mock.received_requests().await.unwrap().len();
let (code, stdout, stderr) = run_cli(tmp.path(), &mock.uri(), &["repair", "--offline"]);
assert_eq!(code, 1, "stdout={stdout} stderr={stderr}");
let v = parse_env(&stdout);
let v = parse_json_envelope(&stdout);
assert_eq!(v["summary"]["failed"], 1, "envelope={v}");
assert!(!tgz.exists(), "no local rebuild");
let after_reqs = mock.received_requests().await.unwrap().len();
Expand Down Expand Up @@ -327,7 +320,7 @@ async fn repair_rebuilds_corrupt_vendored_tarball() {

let (code, stdout, stderr) = run_cli(tmp.path(), &mock.uri(), &["repair"]);
assert_eq!(code, 0, "stdout={stdout} stderr={stderr}");
let v = parse_env(&stdout);
let v = parse_json_envelope(&stdout);
assert_eq!(v["summary"]["rebuilt"], 1, "envelope={v}");
assert_eq!(
std::fs::read(&tgz).unwrap(),
Expand Down Expand Up @@ -367,9 +360,9 @@ async fn repair_keeps_corrupt_artifact_when_no_rebuild_source_exists() {
// nothing to rebuild from.
let (code, stdout, stderr) = run_cli(tmp.path(), &mock.uri(), &["repair", "--offline"]);
assert_eq!(code, 1, "stdout={stdout} stderr={stderr}");
let v = parse_env(&stdout);
let v = parse_json_envelope(&stdout);
assert!(
events_of(&v).iter().any(|e| e["action"] == "failed"
events(&v).iter().any(|e| e["action"] == "failed"
&& e["purl"] == PURL
&& e["error"].as_str().unwrap_or("").contains("--offline")),
"the failure names the purl and the offline cause: {v}"
Expand All @@ -388,9 +381,9 @@ async fn repair_keeps_corrupt_artifact_when_no_rebuild_source_exists() {
std::fs::write(blobs.join(git_sha256(AFTER)), AFTER).unwrap();
let (code, stdout, stderr) = run_cli(tmp.path(), &mock.uri(), &["repair", "--offline"]);
assert_eq!(code, 1, "stdout={stdout} stderr={stderr}");
let v = parse_env(&stdout);
let v = parse_json_envelope(&stdout);
assert!(
events_of(&v).iter().any(|e| e["action"] == "failed"
events(&v).iter().any(|e| e["action"] == "failed"
&& e["purl"] == PURL
&& e["error"].as_str().unwrap_or("").contains("--offline")),
"the failure names the purl and the offline cause: {v}"
Expand Down Expand Up @@ -429,9 +422,9 @@ async fn repair_fails_closed_on_tampered_ledger_sha() {

let (code, stdout, stderr) = run_cli(tmp.path(), &mock.uri(), &["repair"]);
assert_eq!(code, 1, "stdout={stdout} stderr={stderr}");
let env = parse_env(&stdout);
let env = parse_json_envelope(&stdout);
assert!(
events_of(&env)
events(&env)
.iter()
.any(|e| e["action"] == "failed"
&& e["errorCode"] == "vendor_artifact_redownload_failed"),
Expand Down Expand Up @@ -476,16 +469,16 @@ async fn vendor_rerun_is_a_noop_and_repair_recovers_registry_resolution_from_led

let (code, stdout, stderr) = run_cli(tmp.path(), &mock.uri(), &["vendor"]);
assert_eq!(code, 0, "stdout={stdout} stderr={stderr}");
let v = parse_env(&stdout);
let v = parse_json_envelope(&stdout);
assert_eq!(v["status"], "noManifest", "envelope={v}");
assert!(events_of(&v).is_empty(), "envelope={v}");
assert!(events(&v).is_empty(), "envelope={v}");
assert!(!tgz.exists(), "`vendor` never re-vendors from the ledger");

let (code, stdout, stderr) = run_cli(tmp.path(), &mock.uri(), &["repair"]);
assert_eq!(code, 0, "stdout={stdout} stderr={stderr}");
let v = parse_env(&stdout);
let v = parse_json_envelope(&stdout);
assert!(
events_of(&v)
events(&v)
.iter()
.any(|e| e["action"] == "rebuilt" && e["purl"] == PURL),
"the missing artifact is rebuilt from the recovered fetch: {v}"
Expand Down Expand Up @@ -519,7 +512,7 @@ async fn repair_rebuilds_detached_entry_without_manifest() {

let (code, stdout, stderr) = run_cli(tmp.path(), &mock.uri(), &["repair"]);
assert_eq!(code, 0, "stdout={stdout} stderr={stderr}");
let v = parse_env(&stdout);
let v = parse_json_envelope(&stdout);
assert_eq!(v["summary"]["rebuilt"], 1, "envelope={v}");
assert!(tgz.is_file());
assert_socket_dir_lean(tmp.path());
Expand Down Expand Up @@ -580,8 +573,8 @@ async fn repair_reports_missing_ledger_for_nuget_maven_and_hatch_wiring() {

let (code, stdout, stderr) = run_cli(tmp.path(), &mock.uri(), &["repair"]);
assert_eq!(code, 1, "stdout={stdout} stderr={stderr}");
let v = parse_env(&stdout);
let mut missing: Vec<(String, String, String)> = events_of(&v)
let v = parse_json_envelope(&stdout);
let mut missing: Vec<(String, String, String)> = events(&v)
.into_iter()
.filter(|e| e["errorCode"] == "vendor_ledger_missing")
.map(|e| {
Expand Down Expand Up @@ -676,8 +669,8 @@ async fn repair_reports_missing_ledger_instead_of_reconstructing() {
code, 1,
"wholesale={wholesale} stdout={stdout} stderr={stderr}"
);
let v = parse_env(&stdout);
let missing: Vec<_> = events_of(&v)
let v = parse_json_envelope(&stdout);
let missing: Vec<_> = events(&v)
.into_iter()
.filter(|e| e["errorCode"] == "vendor_ledger_missing")
.collect();
Expand Down Expand Up @@ -722,9 +715,9 @@ async fn repair_reports_missing_ledger_instead_of_reconstructing() {
std::fs::write(&state_path, &state1).unwrap();
let (code, stdout, stderr) = run_cli(tmp.path(), &mock.uri(), &["repair"]);
assert_eq!(code, 0, "stdout={stdout} stderr={stderr}");
let v = parse_env(&stdout);
let v = parse_json_envelope(&stdout);
assert!(
!events_of(&v).iter().any(|e| e["action"] == "failed"),
!events(&v).iter().any(|e| e["action"] == "failed"),
"restored ledger repairs clean: {v}"
);
}
Expand All @@ -746,9 +739,9 @@ async fn repair_dry_run_previews_rebuild() {

let (code, stdout, stderr) = run_cli(tmp.path(), &mock.uri(), &["repair", "--dry-run"]);
assert_eq!(code, 0, "stdout={stdout} stderr={stderr}");
let v = parse_env(&stdout);
let v = parse_json_envelope(&stdout);
assert!(
events_of(&v).iter().any(|e| e["action"] == "verified"
events(&v).iter().any(|e| e["action"] == "verified"
&& e["details"]["wouldRedownload"] == true
&& e["purl"] == PURL),
"envelope={v}"
Expand Down Expand Up @@ -968,8 +961,8 @@ async fn revert_of_empty_wiring_gem_entry_fails_loudly() {

let (code, stdout, _) = run_cli(tmp.path(), &mock.uri(), &["vendor", "--revert"]);
assert_eq!(code, 1, "empty-wiring revert must fail: {stdout}");
let v = parse_env(&stdout);
let failed = events_of(&v)
let v = parse_json_envelope(&stdout);
let failed = events(&v)
.into_iter()
.find(|e| e["action"] == "failed" && e["purl"] == GEM_PURL)
.unwrap_or_else(|| panic!("expected a failed event: {v}"));
Expand Down Expand Up @@ -1046,9 +1039,9 @@ async fn repair_gem_dir_tamper_matrix_and_vex_refusal() {
],
);
assert_eq!(code, 1, "arm {i}: tampered dir must not attest: {stdout}");
let venv = parse_env(&stdout);
let venv = parse_json_envelope(&stdout);
assert!(
events_of(&venv)
events(&venv)
.iter()
.any(|e| e["action"] == "skipped" && e["errorCode"] == "vendor_inventory_mismatch"),
"arm {i}: envelope={venv}"
Expand All @@ -1058,9 +1051,9 @@ async fn repair_gem_dir_tamper_matrix_and_vex_refusal() {
// Repair heals: Corrupt → deterministic rebuild of the recorded tree.
let (code, stdout, stderr) = run_cli(tmp.path(), &mock.uri(), &["repair"]);
assert_eq!(code, 0, "arm {i}: stdout={stdout} stderr={stderr}");
let v = parse_env(&stdout);
let v = parse_json_envelope(&stdout);
assert!(
events_of(&v).iter().any(|e| e["action"] == "rebuilt"
events(&v).iter().any(|e| e["action"] == "rebuilt"
&& e["purl"] == GEM_PURL
&& e["details"]["reason"] == "vendor_artifact_corrupt"),
"arm {i}: envelope={v}"
Expand Down Expand Up @@ -1131,7 +1124,7 @@ async fn repair_refuses_changed_service_inventory() {
// Anti-vacuity: the simulated service state is self-consistent.
let (code, stdout, _) = run_cli(tmp.path(), &mock.uri(), &["repair"]);
assert_eq!(code, 0, "{stdout}");
let v = parse_env(&stdout);
let v = parse_json_envelope(&stdout);
assert!(
v["summary"]["rebuilt"].is_null() || v["summary"]["rebuilt"] == 0,
"the simulated service tree must be healthy: {v}"
Expand Down Expand Up @@ -1177,12 +1170,12 @@ async fn repair_warns_on_legacy_gem_entry_without_inventory() {

let (code, stdout, stderr) = run_cli(tmp.path(), &mock.uri(), &["repair"]);
assert_eq!(code, 0, "stdout={stdout} stderr={stderr}");
let v = parse_env(&stdout);
let v = parse_json_envelope(&stdout);
assert!(
v["summary"]["rebuilt"].is_null() || v["summary"]["rebuilt"] == 0,
"legacy entries keep member-only behavior (no rebuild): {v}"
);
let missing: Vec<_> = events_of(&v)
let missing: Vec<_> = events(&v)
.into_iter()
.filter(|e| e["errorCode"] == "vendor_inventory_missing")
.collect();
Expand All @@ -1193,7 +1186,7 @@ async fn repair_warns_on_legacy_gem_entry_without_inventory() {
);
assert_eq!(missing[0]["purl"], GEM_PURL, "envelope={v}");
assert!(
!events_of(&v).iter().any(|e| e["purl"] == CARGO_PURL),
!events(&v).iter().any(|e| e["purl"] == CARGO_PURL),
"the healthy inventory-less cargo entry stays silent: {v}"
);
assert_eq!(
Expand All @@ -1207,9 +1200,9 @@ async fn repair_warns_on_legacy_gem_entry_without_inventory() {
std::fs::remove_dir_all(&cargo_dir).unwrap();
let (code, stdout, _) = run_cli(tmp.path(), &mock.uri(), &["repair"]);
assert_eq!(code, 1, "a missing cargo artifact must fail: {stdout}");
let v = parse_env(&stdout);
let v = parse_json_envelope(&stdout);
assert!(
events_of(&v)
events(&v)
.iter()
.any(|e| e["action"] == "failed" && e["purl"] == CARGO_PURL),
"the cargo entry is live in pass 1: {v}"
Expand All @@ -1235,8 +1228,8 @@ async fn repair_offline_without_sources_fails_loudly() {

let (code, stdout, stderr) = run_cli(tmp.path(), &mock.uri(), &["repair", "--offline"]);
assert_eq!(code, 1, "stdout={stdout} stderr={stderr}");
let v = parse_env(&stdout);
let failed: Vec<_> = events_of(&v)
let v = parse_json_envelope(&stdout);
let failed: Vec<_> = events(&v)
.into_iter()
.filter(|e| e["action"] == "failed")
.collect();
Expand Down Expand Up @@ -1403,7 +1396,7 @@ async fn repair_offline_never_repacks_verified_members() {

let (code, stdout, stderr) = run_cli(tmp.path(), &mock.uri(), &["repair", "--offline"]);
assert_eq!(code, 1, "stdout={stdout} stderr={stderr}");
let v = parse_env(&stdout);
let v = parse_json_envelope(&stdout);
assert_eq!(v["summary"]["failed"], 1, "envelope={v}");
assert_eq!(
std::fs::read(&tgz).unwrap(),
Expand Down Expand Up @@ -1483,7 +1476,7 @@ async fn identity_kept_over_different_service_bytes(symlinked_lock: bool) {
std::fs::remove_file(&tgz).unwrap();
let (code, stdout, stderr) = run_cli(tmp.path(), &mock.uri(), &["repair"]);
assert_eq!(code, 0, "stdout={stdout} stderr={stderr}");
let v = parse_env(&stdout);
let v = parse_json_envelope(&stdout);
assert_eq!(v["summary"]["rebuilt"], 1, "envelope={v}");
assert_eq!(
std::fs::read(&tgz).unwrap(),
Expand Down
Loading
Loading