|
| 1 | +//! Coverage for the `--dry-run` paths across multiple commands. |
| 2 | +//! Each test runs a command with `--dry-run` against a fixture and |
| 3 | +//! asserts the JSON envelope's `dryRun: true` field — covering the |
| 4 | +//! dry-run flag-propagation branches each command's `run` has. |
| 5 | +
|
| 6 | +use std::path::PathBuf; |
| 7 | +use std::process::Command; |
| 8 | + |
| 9 | +fn binary() -> PathBuf { |
| 10 | + env!("CARGO_BIN_EXE_socket-patch").into() |
| 11 | +} |
| 12 | + |
| 13 | +fn make_socket_with_empty_manifest(root: &std::path::Path) { |
| 14 | + let socket = root.join(".socket"); |
| 15 | + std::fs::create_dir_all(&socket).unwrap(); |
| 16 | + std::fs::write( |
| 17 | + socket.join("manifest.json"), |
| 18 | + r#"{"patches":{}}"#, |
| 19 | + ) |
| 20 | + .unwrap(); |
| 21 | + std::fs::create_dir_all(socket.join("blobs")).unwrap(); |
| 22 | +} |
| 23 | + |
| 24 | +/// `apply --dry-run --json` against an empty manifest reports |
| 25 | +/// dryRun:true and success. Covers the dry-run flag propagation |
| 26 | +/// in `commands::apply::run`. |
| 27 | +#[test] |
| 28 | +fn apply_dry_run_empty_manifest_emits_dry_run_envelope() { |
| 29 | + let tmp = tempfile::tempdir().expect("tempdir"); |
| 30 | + make_socket_with_empty_manifest(tmp.path()); |
| 31 | + let out = Command::new(binary()) |
| 32 | + .args(["apply", "--json", "--dry-run"]) |
| 33 | + .current_dir(tmp.path()) |
| 34 | + .env_remove("SOCKET_API_TOKEN") |
| 35 | + .output() |
| 36 | + .expect("run apply"); |
| 37 | + let stdout = String::from_utf8_lossy(&out.stdout); |
| 38 | + let v: serde_json::Value = serde_json::from_str(stdout.trim()) |
| 39 | + .unwrap_or_else(|e| panic!("invalid JSON: {e}\n{stdout}")); |
| 40 | + assert_eq!(v["command"], "apply"); |
| 41 | + assert_eq!(v["dryRun"], true); |
| 42 | +} |
| 43 | + |
| 44 | +/// `repair --dry-run --offline --json`: dry-run with no patches |
| 45 | +/// should succeed with `dryRun:true`. |
| 46 | +#[test] |
| 47 | +fn repair_dry_run_offline_emits_dry_run_envelope() { |
| 48 | + let tmp = tempfile::tempdir().expect("tempdir"); |
| 49 | + make_socket_with_empty_manifest(tmp.path()); |
| 50 | + let out = Command::new(binary()) |
| 51 | + .args(["repair", "--json", "--dry-run", "--offline"]) |
| 52 | + .current_dir(tmp.path()) |
| 53 | + .env_remove("SOCKET_API_TOKEN") |
| 54 | + .output() |
| 55 | + .expect("run repair"); |
| 56 | + let stdout = String::from_utf8_lossy(&out.stdout); |
| 57 | + let v: serde_json::Value = serde_json::from_str(stdout.trim()) |
| 58 | + .unwrap_or_else(|e| panic!("invalid JSON: {e}\n{stdout}")); |
| 59 | + assert_eq!(v["command"], "repair"); |
| 60 | + assert_eq!(v["dryRun"], true); |
| 61 | +} |
| 62 | + |
| 63 | +/// Rollback with no patches in manifest + --json must not crash. |
| 64 | +/// Locks in the manifest-empty-but-valid branch. |
| 65 | +#[test] |
| 66 | +fn rollback_with_empty_manifest_emits_envelope() { |
| 67 | + let tmp = tempfile::tempdir().expect("tempdir"); |
| 68 | + make_socket_with_empty_manifest(tmp.path()); |
| 69 | + let out = Command::new(binary()) |
| 70 | + .args(["rollback", "--json", "--offline"]) |
| 71 | + .current_dir(tmp.path()) |
| 72 | + .env_remove("SOCKET_API_TOKEN") |
| 73 | + .output() |
| 74 | + .expect("run rollback"); |
| 75 | + let stdout = String::from_utf8_lossy(&out.stdout); |
| 76 | + // Should produce SOME envelope JSON without panicking. |
| 77 | + let _: serde_json::Value = serde_json::from_str(stdout.trim()) |
| 78 | + .unwrap_or_else(|e| panic!("invalid JSON: {e}\nstdout:\n{stdout}\nstderr:\n{}", |
| 79 | + String::from_utf8_lossy(&out.stderr))); |
| 80 | +} |
| 81 | + |
| 82 | +/// `remove --json` with no manifest at all: the early-exit |
| 83 | +/// envelope branch with `manifest_not_found` error code. Covered |
| 84 | +/// elsewhere too but a redundant lock is cheap. |
| 85 | +#[test] |
| 86 | +fn remove_with_no_socket_dir_emits_manifest_not_found() { |
| 87 | + let tmp = tempfile::tempdir().expect("tempdir"); |
| 88 | + // NO .socket/ directory at all. |
| 89 | + let out = Command::new(binary()) |
| 90 | + .args([ |
| 91 | + "remove", |
| 92 | + "11111111-1111-4111-8111-111111111111", |
| 93 | + "--json", |
| 94 | + "--yes", |
| 95 | + "--skip-rollback", |
| 96 | + ]) |
| 97 | + .current_dir(tmp.path()) |
| 98 | + .env_remove("SOCKET_API_TOKEN") |
| 99 | + .output() |
| 100 | + .expect("run remove"); |
| 101 | + let stdout = String::from_utf8_lossy(&out.stdout); |
| 102 | + let v: serde_json::Value = serde_json::from_str(stdout.trim()).expect("valid JSON"); |
| 103 | + assert_eq!(v["command"], "remove"); |
| 104 | + let code = v["error"]["code"].as_str().unwrap_or(""); |
| 105 | + assert!( |
| 106 | + code == "manifest_not_found" || code == "not_found", |
| 107 | + "expected manifest_not_found error; got {v}" |
| 108 | + ); |
| 109 | +} |
| 110 | + |
| 111 | +/// `list --json` against an empty manifest emits an empty |
| 112 | +/// `patches` array and status=success. Covers the list-empty path. |
| 113 | +#[test] |
| 114 | +fn list_with_empty_manifest_emits_empty_envelope() { |
| 115 | + let tmp = tempfile::tempdir().expect("tempdir"); |
| 116 | + make_socket_with_empty_manifest(tmp.path()); |
| 117 | + let out = Command::new(binary()) |
| 118 | + .args(["list", "--json"]) |
| 119 | + .current_dir(tmp.path()) |
| 120 | + .env_remove("SOCKET_API_TOKEN") |
| 121 | + .output() |
| 122 | + .expect("run list"); |
| 123 | + let stdout = String::from_utf8_lossy(&out.stdout); |
| 124 | + let v: serde_json::Value = serde_json::from_str(stdout.trim()) |
| 125 | + .unwrap_or_else(|e| panic!("invalid JSON: {e}\n{stdout}")); |
| 126 | + assert_eq!(v["command"], "list"); |
| 127 | + assert_eq!(v["status"], "success"); |
| 128 | +} |
| 129 | + |
| 130 | +/// `--silent` flag suppresses the friendly "no manifest" message |
| 131 | +/// in non-JSON mode for `apply`. Covers the silent-flag short-circuit. |
| 132 | +#[test] |
| 133 | +fn apply_silent_no_manifest_produces_no_output() { |
| 134 | + let tmp = tempfile::tempdir().expect("tempdir"); |
| 135 | + let out = Command::new(binary()) |
| 136 | + .args(["apply", "--silent"]) |
| 137 | + .current_dir(tmp.path()) |
| 138 | + .env_remove("SOCKET_API_TOKEN") |
| 139 | + .output() |
| 140 | + .expect("run apply"); |
| 141 | + assert_eq!(out.status.code(), Some(0)); |
| 142 | + let stdout = String::from_utf8_lossy(&out.stdout); |
| 143 | + assert!(stdout.trim().is_empty(), "silent mode should produce no stdout"); |
| 144 | +} |
0 commit comments