diff --git a/crates/socket-patch-cli/tests/cli/envelope_helper_copies.rs b/crates/socket-patch-cli/tests/cli/envelope_helper_copies.rs index afcb7d85e..24feded82 100644 --- a/crates/socket-patch-cli/tests/cli/envelope_helper_copies.rs +++ b/crates/socket-patch-cli/tests/cli/envelope_helper_copies.rs @@ -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", @@ -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", @@ -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 {", "fn event_purls(v: &serde_json::Value, action: &str) -> Vec {", + "fn parse_env(stdout: &str) -> serde_json::Value {", + "fn events_of(v: &serde_json::Value) -> Vec {", ] { assert!(defines_private_reader(copy), "missed: {copy}"); } @@ -149,6 +154,8 @@ fn the_detector_sees_every_former_copy_shape() { " fn warning_codes(&self) -> Vec {", "fn events_for_purl(env: &Value) -> Vec {", "fn codes_of_lock(lock: &str) -> Vec {", + "fn parse_env_vars(text: &str) -> Vec<(String, String)> {", + "fn events_of_kind(v: &Value, kind: &str) -> Vec {", "let codes = codes_in(&env[\"warnings\"]);", ] { assert!( diff --git a/crates/socket-patch-cli/tests/repair/main.rs b/crates/socket-patch-cli/tests/repair/main.rs index 41f066184..0ed1bf197 100644 --- a/crates/socket-patch-cli/tests/repair/main.rs +++ b/crates/socket-patch-cli/tests/repair/main.rs @@ -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; diff --git a/crates/socket-patch-cli/tests/repair/covgap_commands_repair.rs b/crates/socket-patch-cli/tests/repair/repair_render.rs similarity index 96% rename from crates/socket-patch-cli/tests/repair/covgap_commands_repair.rs rename to crates/socket-patch-cli/tests/repair/repair_render.rs index 200321737..a8b11985d 100644 --- a/crates/socket-patch-cli/tests/repair/covgap_commands_repair.rs +++ b/crates/socket-patch-cli/tests/repair/repair_render.rs @@ -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) @@ -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}; @@ -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}" @@ -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). diff --git a/crates/socket-patch-cli/tests/repair/repair_vendor_e2e.rs b/crates/socket-patch-cli/tests/repair/repair_vendor_e2e.rs index 4a5551f05..da29a8212 100644 --- a/crates/socket-patch-cli/tests/repair/repair_vendor_e2e.rs +++ b/crates/socket-patch-cli/tests/repair/repair_vendor_e2e.rs @@ -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}; @@ -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 { - 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] @@ -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}" @@ -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}" @@ -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(); @@ -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(), @@ -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}" @@ -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}" @@ -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"), @@ -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}" @@ -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()); @@ -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| { @@ -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(); @@ -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}" ); } @@ -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}" @@ -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}")); @@ -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}" @@ -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}" @@ -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}" @@ -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(); @@ -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!( @@ -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}" @@ -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(); @@ -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(), @@ -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(), diff --git a/crates/socket-patch-cli/tests/repair/covgap_commands_repair_vendor.rs b/crates/socket-patch-cli/tests/repair/repair_vendor_failure_paths.rs similarity index 93% rename from crates/socket-patch-cli/tests/repair/covgap_commands_repair_vendor.rs rename to crates/socket-patch-cli/tests/repair/repair_vendor_failure_paths.rs index 534f45ed4..a8e8bda66 100644 --- a/crates/socket-patch-cli/tests/repair/covgap_commands_repair_vendor.rs +++ b/crates/socket-patch-cli/tests/repair/repair_vendor_failure_paths.rs @@ -1,11 +1,18 @@ -//! Coverage-gap tests for `repair`'s vendored-artifact phase +//! Failure paths of `repair`'s vendored-artifact phase //! (`commands/vendored_backend/repair.rs`): record resolution (corrupt //! ledger, dropped/moved-on manifest records, API-recovered records), the //! tampered-state health arms (StaleUuid / Unverifiable), the re-vendor's //! failure arms and detail selection, `--ecosystems` scoping, and the -//! human (non-`--json`) output lines.//! -//! Fixtures and helpers mirror `repair_vendor_e2e.rs` (this suite owns its -//! own copies). +//! human (non-`--json`) output lines. +//! +//! The `--json` tests read the envelope through `common::envelope` and +//! check `errorCode`s, events and bytes on disk (#1089). A message is read +//! only for what it must name: a flag, a patch uuid, the file to fix, or, +//! where several arms share one `errorCode`, the phrase that says which +//! arm fired (see [`ARM_MOVED_ON`] and its siblings). +//! +//! Fixtures mirror `repair_vendor_e2e.rs` (this suite owns its own +//! copies); envelope reads go through `common::envelope`. //! //! A vendored run (`scan --mode vendored`) is manifest-free: every ledger entry is //! `detached` with its record embedded and `.socket/manifest.json` is never @@ -15,6 +22,7 @@ use std::path::{Path, PathBuf}; +use crate::common::envelope::{events, parse_json_envelope}; use crate::common::{binary, git_sha256, hermetic_command}; use wiremock::matchers::{method, path}; @@ -408,13 +416,13 @@ fn vendor_gem_project(root: &Path, mock_uri: &str, expected_after: &[u8]) -> Pat copy } -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 { - v["events"].as_array().cloned().unwrap_or_default() -} +// `vendor_uuid_mismatch` is shared by the moved-on manifest record and the +// StaleUuid health arm, and `vendor_artifact_unrepairable` by six arms. +// Until those arms get codes of their own, these phrases are what tell +// the tests which arm fired. +const ARM_MOVED_ON: &str = "moved on"; +const ARM_STALE_UUID: &str = "re-vendor is pending"; +const ARM_BAD_IDENTITY: &str = "ledger package identity"; fn read_state(root: &Path) -> serde_json::Value { serde_json::from_str(&std::fs::read_to_string(root.join(".socket/vendor/state.json")).unwrap()) @@ -506,12 +514,12 @@ async fn repair_fails_loudly_on_corrupt_vendor_state() { 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 v = parse_json_envelope(&stdout); assert_eq!(v["status"], "partialFailure", "envelope={v}"); assert!( - events_of(&v).iter().any(|e| e["action"] == "failed" - && e["errorCode"] == "vendor_state_unreadable" - && e["error"].as_str().unwrap_or("").contains("corrupt")), + events(&v) + .iter() + .any(|e| e["action"] == "failed" && e["errorCode"] == "vendor_state_unreadable"), "envelope={v}" ); assert_eq!( @@ -553,9 +561,9 @@ async fn repair_skips_entry_dropped_from_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!( - !events_of(&v).iter().any(|e| e["purl"] == PURL), + !events(&v).iter().any(|e| e["purl"] == PURL), "a manifest-dropped entry is the reconcile's call, not repair's: {v}" ); assert!( @@ -593,12 +601,16 @@ async fn repair_skips_when_manifest_uuid_moved_on() { 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"] == "skipped" + events(&v).iter().any(|e| e["action"] == "skipped" && e["purl"] == PURL && e["errorCode"] == "vendor_uuid_mismatch" - && e["reason"].as_str().unwrap_or("").contains("moved on")), + && e["reason"].as_str().unwrap_or("").contains(ARM_MOVED_ON) + && e["reason"] + .as_str() + .unwrap_or("") + .contains("socket-patch vendor")), "envelope={v}" ); assert_eq!( @@ -644,11 +656,12 @@ async fn repair_prefers_the_moved_on_manifest_over_an_embedded_record() { 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"] == "skipped" + events(&v).iter().any(|e| e["action"] == "skipped" && e["purl"] == PURL - && e["errorCode"] == "vendor_uuid_mismatch"), + && e["errorCode"] == "vendor_uuid_mismatch" + && e["reason"].as_str().unwrap_or("").contains(ARM_MOVED_ON)), "envelope={v}" ); assert_eq!( @@ -680,19 +693,18 @@ async fn repair_uses_the_embedded_record_without_manifest() { 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["errorCode"] == "vendor_artifact_redownload_failed" && e["error"].as_str().unwrap_or("").contains("--offline")), "envelope={v}" ); assert!( - !events_of(&v).iter().any(|e| e["error"] - .as_str() - .unwrap_or("") - .contains("no manifest record")), + !events(&v) + .iter() + .any(|e| e["errorCode"] == "vendor_artifact_unrepairable"), "the embedded record must stand in for the missing manifest: {v}" ); } @@ -721,16 +733,16 @@ async fn repair_recovers_record_by_uuid_without_manifest_then_fails_offline() { // Online: the (None, None) arm fetches the record by uuid and rebuilds. 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), "envelope={v}" ); // Vendored-phase events say so, as in every envelope. assert!( - events_of(&v) + events(&v) .iter() .all(|e| e["details"]["mode"] == "vendored"), "envelope={v}" @@ -745,15 +757,12 @@ async fn repair_recovers_record_by_uuid_without_manifest_then_fails_offline() { std::fs::remove_file(&tgz).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["errorCode"] == "vendor_artifact_unrepairable" - && e["error"] - .as_str() - .unwrap_or("") - .contains("no manifest record for patch")), + && e["error"].as_str().unwrap_or("").contains(UUID)), "envelope={v}" ); } @@ -762,8 +771,8 @@ async fn repair_recovers_record_by_uuid_without_manifest_then_fails_offline() { /// `state.json`'s artifact.path edited to a DIFFERENT (valid) uuid dir /// while entry.uuid still matches the record: the health check reports -/// StaleUuid and repair skips with "a re-vendor is pending" — never -/// rebuilds at the stale path. +/// StaleUuid and repair skips with [`ARM_STALE_UUID`] — never rebuilds at +/// the stale path. #[tokio::test] async fn repair_skips_stale_uuid_artifact_path() { let mock = MockServer::start().await; @@ -785,15 +794,12 @@ async fn repair_skips_stale_uuid_artifact_path() { 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"] == "skipped" + events(&v).iter().any(|e| e["action"] == "skipped" && e["purl"] == PURL && e["errorCode"] == "vendor_uuid_mismatch" - && e["reason"] - .as_str() - .unwrap_or("") - .contains("re-vendor is pending")), + && e["reason"].as_str().unwrap_or("").contains(ARM_STALE_UUID)), "envelope={v}" ); assert_eq!( @@ -833,12 +839,12 @@ async fn repair_fails_closed_on_unsafe_ledger_artifact_path() { 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 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["errorCode"] == "vendor_artifact_unrepairable" - && e["error"].as_str().unwrap_or("").contains("fix state.json")), + && e["error"].as_str().unwrap_or("").contains("state.json")), "envelope={v}" ); assert_eq!( @@ -881,9 +887,9 @@ async fn repair_rebuilds_via_ledger_recovered_registry_fetch() { 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), "envelope={v}" @@ -922,9 +928,9 @@ async fn repair_redownload_ignores_old_registry_failure() { let ledger_before = std::fs::read(tmp.path().join(".socket/vendor/state.json")).unwrap(); let (code, stdout, stderr) = run_cli(tmp.path(), &mock.uri(), &["repair"]); assert_eq!(code, 0, "{stdout} {stderr}"); - let v = parse_env(&stdout); + let v = parse_json_envelope(&stdout); assert!( - events_of(&v) + events(&v) .iter() .any(|e| e["purl"] == PURL && e["details"]["redownloaded"] == true), "{v}" @@ -959,9 +965,9 @@ async fn repair_redownload_leaves_missing_lockfile_missing() { let ledger_before = std::fs::read(tmp.path().join(".socket/vendor/state.json")).unwrap(); let (code, stdout, stderr) = run_cli(tmp.path(), &mock.uri(), &["repair"]); assert_eq!(code, 0, "{stdout} {stderr}"); - let v = parse_env(&stdout); + let v = parse_json_envelope(&stdout); assert!( - events_of(&v) + events(&v) .iter() .any(|e| e["purl"] == PURL && e["details"]["redownloaded"] == true), "{v}" @@ -997,9 +1003,9 @@ async fn repair_redownload_does_not_require_installed_file() { let ledger_before = std::fs::read(tmp.path().join(".socket/vendor/state.json")).unwrap(); let (code, stdout, stderr) = run_cli(tmp.path(), &mock.uri(), &["repair"]); assert_eq!(code, 0, "{stdout} {stderr}"); - let v = parse_env(&stdout); + let v = parse_json_envelope(&stdout); assert!( - events_of(&v) + events(&v) .iter() .any(|e| e["purl"] == PURL && e["details"]["redownloaded"] == true), "{v}" @@ -1077,15 +1083,15 @@ async fn repair_rebuilds_qualified_ledger_key_from_installed_copy() { 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"] == GEM_PURL_QUALIFIED), "the installed copy must drive the rebuild of the qualified-keyed entry: {v}" ); assert!( - !events_of(&v).iter().any(|e| e["action"] == "failed" + !events(&v).iter().any(|e| e["action"] == "failed" || e["errorCode"] .as_str() .is_some_and(|c| c.starts_with("vendor_fetch"))), @@ -1153,9 +1159,9 @@ async fn repair_restores_crashed_set_aside_leftover_before_classifying() { let (code, stdout, stderr) = run_cli(tmp.path(), &mock.uri(), &["repair", "--offline"]); 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"), "the restored bytes are healthy — nothing to rebuild, nothing failed: {v}" ); assert_eq!( @@ -1188,9 +1194,9 @@ async fn repair_redownloads_gem_without_registry_checksum() { let ledger_before = std::fs::read(tmp.path().join(".socket/vendor/state.json")).unwrap(); let (code, stdout, stderr) = run_cli(tmp.path(), &mock.uri(), &["repair"]); assert_eq!(code, 0, "{stdout} {stderr}"); - let v = parse_env(&stdout); + let v = parse_json_envelope(&stdout); assert!( - events_of(&v) + events(&v) .iter() .any(|e| e["purl"] == GEM_PURL && e["details"]["redownloaded"] == true), "{v}" @@ -1230,9 +1236,9 @@ async fn repair_redownload_ignores_drifted_installed_variant() { let ledger_before = std::fs::read(tmp.path().join(".socket/vendor/state.json")).unwrap(); let (code, stdout, stderr) = run_cli(tmp.path(), &mock.uri(), &["repair"]); assert_eq!(code, 0, "{stdout} {stderr}"); - let v = parse_env(&stdout); + let v = parse_json_envelope(&stdout); assert!( - events_of(&v) + events(&v) .iter() .any(|e| e["purl"] == GEM_PURL && e["details"]["redownloaded"] == true), "{v}" @@ -1265,9 +1271,9 @@ async fn repair_ecosystems_scope_skips_out_of_scope_entries() { let (code, stdout, stderr) = run_cli(tmp.path(), &mock.uri(), &["repair", "--ecosystems", "gem"]); 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["purl"] == PURL), + !events(&v).iter().any(|e| e["purl"] == PURL), "an out-of-scope entry produces no events: {v}" ); assert!( @@ -1278,9 +1284,9 @@ async fn repair_ecosystems_scope_skips_out_of_scope_entries() { let (code, stdout, stderr) = run_cli(tmp.path(), &mock.uri(), &["repair", "--ecosystems", "npm"]); 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), "envelope={v}" @@ -1365,9 +1371,9 @@ async fn repair_redownload_preserves_platform_locked_artifact() { let ledger_before = std::fs::read(tmp.path().join(".socket/vendor/state.json")).unwrap(); let (code, stdout, stderr) = run_cli(tmp.path(), &mock.uri(), &["repair"]); assert_eq!(code, 0, "{stdout} {stderr}"); - let v = parse_env(&stdout); + let v = parse_json_envelope(&stdout); assert!( - events_of(&v) + events(&v) .iter() .any(|e| e["purl"] == PURL && e["details"]["redownloaded"] == true), "{v}" @@ -1403,8 +1409,8 @@ async fn repair_tampered_base_purl_surfaces_precise_unverifiable_detail() { 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 failed = events_of(&v) + let v = parse_json_envelope(&stdout); + let failed = events(&v) .into_iter() .find(|e| { e["action"] == "failed" @@ -1416,7 +1422,7 @@ async fn repair_tampered_base_purl_surfaces_precise_unverifiable_detail() { failed["error"] .as_str() .unwrap_or("") - .contains("ledger package identity"), + .contains(ARM_BAD_IDENTITY), "the missing source is named: {failed}" ); assert!( @@ -1462,10 +1468,10 @@ async fn repair_recovers_multiple_records_by_uuid_sharing_one_client() { 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); for purl in [PURL, GEM_PURL] { assert!( - events_of(&v) + events(&v) .iter() .any(|e| e["action"] == "rebuilt" && e["purl"] == purl), "both records must be recovered by uuid and rebuilt: {v}" @@ -1559,15 +1565,12 @@ async fn repair_no_backend_for_purl_restores_set_aside_bytes() { &[("DENO_DIR", deno_home.path().to_str().unwrap())], ); 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"] == JSR_PURL && e["errorCode"] == "vendor_artifact_unrepairable" - && e["error"] - .as_str() - .unwrap_or("") - .contains("ledger package identity is invalid or unsupported")), + && e["error"].as_str().unwrap_or("").contains(ARM_BAD_IDENTITY)), "envelope={v}" ); assert_eq!( @@ -1594,15 +1597,12 @@ async fn repair_no_backend_for_purl_restores_set_aside_bytes() { &[("DENO_DIR", deno_home.path().to_str().unwrap())], ); 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"] == JSR_PURL && e["errorCode"] == "vendor_artifact_unrepairable" - && e["error"] - .as_str() - .unwrap_or("") - .contains("ledger package identity is invalid or unsupported")), + && e["error"].as_str().unwrap_or("").contains(ARM_BAD_IDENTITY)), "envelope={v}" ); assert!( @@ -1649,9 +1649,9 @@ async fn repair_set_aside_blocked_rename_degrades_to_removal() { // Restore perms first so the tempdir always cleans up. std::fs::set_permissions(&eco_dir, std::fs::Permissions::from_mode(0o755)).unwrap(); 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 blocked set-aside must not block the rebuild: {v}" @@ -1733,9 +1733,9 @@ async fn repair_refuses_changed_inventory_with_readonly_ledger() { let (code, stdout, stderr) = run_cli(tmp.path(), &mock.uri(), &["repair"]); writable_vendor_dir(tmp.path()); assert_eq!(code, 1, "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"] == "failed" && e["errorCode"] == "vendor_artifact_redownload_failed"), diff --git a/crates/socket-patch-cli/tests/repair/repair_vendor_flavors_e2e.rs b/crates/socket-patch-cli/tests/repair/repair_vendor_flavors_e2e.rs index 7149fb5a1..865abd666 100644 --- a/crates/socket-patch-cli/tests/repair/repair_vendor_flavors_e2e.rs +++ b/crates/socket-patch-cli/tests/repair/repair_vendor_flavors_e2e.rs @@ -21,6 +21,7 @@ //! lockfile, so vendoring proceeds offline from the installed copy + the //! view-fetched patch content. +use crate::common::envelope::{events, parse_json_envelope}; use crate::common::git_sha256; use std::path::{Path, PathBuf}; @@ -437,14 +438,6 @@ fn vendor_project(root: &Path, mock_uri: &str, flavor: Flavor) -> 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 { - v["events"].as_array().cloned().unwrap_or_default() -} - /// Assert the flavor's install wiring survived: the post-repair lockfile still /// references the vendored tarball. fn assert_wiring_intact(root: &Path, flavor: Flavor) { @@ -473,10 +466,10 @@ async fn deleted_tarball_rebuilds(flavor: Flavor) { let (code, stdout, stderr) = run_cli(tmp.path(), &mock.uri(), &["repair"]); assert_eq!(code, 0, "{}: stdout={stdout} stderr={stderr}", flavor.tag()); - let v = parse_env(&stdout); + let v = parse_json_envelope(&stdout); assert_eq!(v["summary"]["rebuilt"], 1, "{}: envelope={v}", flavor.tag()); assert!( - events_of(&v) + events(&v) .iter() .any(|e| e["action"] == "rebuilt" && e["purl"] == PURL), "{}: envelope={v}", @@ -569,7 +562,7 @@ async fn corrupt_tarball_rebuilds(flavor: Flavor) { let (code, stdout, stderr) = run_cli(tmp.path(), &mock.uri(), &["repair"]); assert_eq!(code, 0, "{}: stdout={stdout} stderr={stderr}", flavor.tag()); - let v = parse_env(&stdout); + let v = parse_json_envelope(&stdout); assert_eq!(v["summary"]["rebuilt"], 1, "{}: envelope={v}", flavor.tag()); assert_eq!( std::fs::read(&tgz).unwrap(), @@ -621,13 +614,13 @@ async fn repair_skips_an_entry_with_an_unknown_flavor() { let (code, stdout, stderr) = run_cli(tmp.path(), &mock.uri(), &["repair"]); assert_eq!(code, 0, "stdout={stdout} stderr={stderr}"); - let env = parse_env(&stdout); + let env = parse_json_envelope(&stdout); assert!( - !events_of(&env).iter().any(|e| e["action"] == "rebuilt"), + !events(&env).iter().any(|e| e["action"] == "rebuilt"), "{env}" ); assert!( - events_of(&env).iter().any(|e| e["action"] == "skipped" + events(&env).iter().any(|e| e["action"] == "skipped" && e["purl"] == PURL && e["errorCode"] == "vendor_wiring_unknown_revert_blocked"), "{env}" @@ -660,9 +653,9 @@ async fn tampered_ledger_fails_closed(flavor: Flavor) { let (code, stdout, stderr) = run_cli(tmp.path(), &mock.uri(), &["repair"]); assert_eq!(code, 1, "{}: stdout={stdout} stderr={stderr}", flavor.tag()); - 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"), @@ -709,9 +702,9 @@ async fn ledger_gone_is_reported(flavor: Flavor) { let (code, stdout, stderr) = run_cli(tmp.path(), &mock.uri(), &["repair"]); assert_eq!(code, 1, "{}: stdout={stdout} stderr={stderr}", flavor.tag()); - 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["errorCode"] == "vendor_ledger_missing" && e["uuid"] == UUID), "{}: envelope={v}", diff --git a/crates/socket-patch-cli/tests/repair/coverage_fix_repair_vendor_predelete.rs b/crates/socket-patch-cli/tests/repair/repair_vendor_predelete.rs similarity index 96% rename from crates/socket-patch-cli/tests/repair/coverage_fix_repair_vendor_predelete.rs rename to crates/socket-patch-cli/tests/repair/repair_vendor_predelete.rs index 58bf83cff..104f03e79 100644 --- a/crates/socket-patch-cli/tests/repair/coverage_fix_repair_vendor_predelete.rs +++ b/crates/socket-patch-cli/tests/repair/repair_vendor_predelete.rs @@ -6,6 +6,7 @@ //! forensic bytes must come back. Gem fixtures modeled on //! repair_vendor_e2e's. +use crate::common::envelope::{events, parse_json_envelope}; use crate::common::{binary, git_sha256, hermetic_command}; use std::path::{Path, PathBuf}; @@ -156,14 +157,6 @@ fn run_cli(root: &Path, mock_uri: &str, argv: &[&str]) -> (i32, String, String) ) } -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 { - v["events"].as_array().cloned().unwrap_or_default() -} - /// `scan --mode vendored --yes` the gem fixture; returns the vendored copy dir. fn vendor_gem_project(root: &Path, mock_uri: &str) -> PathBuf { let (code, stdout, stderr) = run_cli(root, mock_uri, &["scan", "--mode", "vendored", "--yes"]); @@ -208,9 +201,9 @@ async fn repair_keeps_corrupt_forensic_bytes_when_rebuild_dispatch_refuses() { mock.reset().await; 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 v = parse_json_envelope(&stdout); assert!( - events_of(&v) + events(&v) .iter() .any(|e| e["action"] == "failed" && e["purl"] == GEM_PURL), "envelope={v}" diff --git a/crates/socket-patch-cli/tests/repair_vendor_flavors_e2e/vlt.rs b/crates/socket-patch-cli/tests/repair_vendor_flavors_e2e/vlt.rs index 7a3770e45..7ce0b9331 100644 --- a/crates/socket-patch-cli/tests/repair_vendor_flavors_e2e/vlt.rs +++ b/crates/socket-patch-cli/tests/repair_vendor_flavors_e2e/vlt.rs @@ -24,10 +24,8 @@ use std::path::{Path, PathBuf}; -use super::{ - common, events_of, mount_patch_api, parse_env, run_cli, AFTER, BEFORE, DEP, DEP_VERSION, PURL, - UUID, -}; +use super::{common, mount_patch_api, run_cli, AFTER, BEFORE, DEP, DEP_VERSION, PURL, UUID}; +use crate::common::envelope::{events, parse_json_envelope}; #[derive(Clone, Copy, Debug)] enum VltLock { @@ -99,11 +97,11 @@ fn state(root: &Path) -> serde_json::Value { fn repair(root: &Path, uri: &str) -> serde_json::Value { let (code, stdout, stderr) = run_cli(root, uri, &["repair"]); assert_eq!(code, 0, "repair: {stdout}\n{stderr}"); - parse_env(&stdout) + parse_json_envelope(&stdout) } fn rebuilt(v: &serde_json::Value) -> bool { - events_of(v) + events(v) .iter() .any(|e| e["action"] == "rebuilt" && e["purl"] == PURL) } @@ -181,7 +179,7 @@ async fn vlt_repair_rebuilds_over_a_planted_file() { std::fs::create_dir_all(dir.join("node_modules")).unwrap(); std::fs::write(dir.join("node_modules/evil.js"), b"planted\n").unwrap(); let (_, stdout, _) = run_cli(tmp.path(), &mock.uri(), &["repair", "--dry-run"]); - let dry = parse_env(&stdout); + let dry = parse_json_envelope(&stdout); assert!( dry.to_string().contains("vendor_artifact_corrupt"), "{lock:?}: {dry}" @@ -205,9 +203,9 @@ async fn vlt_repair_reports_a_missing_ledger() { std::fs::remove_file(tmp.path().join(".socket/vendor/state.json")).unwrap(); let (code, stdout, stderr) = run_cli(tmp.path(), &mock.uri(), &["repair"]); assert_eq!(code, 1, "{lock:?}: {stdout}\n{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["errorCode"] == "vendor_ledger_missing" && e["uuid"] == UUID), "{lock:?}: {v}" @@ -362,9 +360,9 @@ async fn vlt_lock_only_reference_is_kept_and_judged() { std::fs::remove_file(tmp.path().join(".socket/vendor/state.json")).unwrap(); let (code, stdout, stderr) = run_cli(tmp.path(), &mock.uri(), &["repair"]); assert_eq!(code, 1, "{lock:?}: {stdout}\n{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["uuid"] == UUID && e["errorCode"] == "vendor_ledger_missing"), "{lock:?}: the lock-only reference is found and reported: {v}" @@ -409,9 +407,9 @@ async fn vlt_wiring_files_are_untouched_by_a_ledger_less_repair() { let (code, stdout, stderr) = run_cli(root, &mock.uri(), &["repair"]); assert_eq!(code, 1, "{stdout}\n{stderr}"); - let v = parse_env(&stdout); + let v = parse_json_envelope(&stdout); assert!( - events_of(&v) + events(&v) .iter() .any(|e| e["action"] == "failed" && e["errorCode"] == "vendor_ledger_missing"), "{v}"