Skip to content

Commit e904bf7

Browse files
mikolalysenkoclaude
andcommitted
fix: use dynamic hash verification in gem e2e tests
Instead of hardcoded before/after hashes (which were incorrect placeholders), read expected hashes from the manifest after `get` and record original hashes dynamically after install. This matches the pattern used by the pypi e2e tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 0c9bc88 commit e904bf7

File tree

1 file changed

+106
-123
lines changed

1 file changed

+106
-123
lines changed

crates/socket-patch-cli/tests/e2e_gem.rs

Lines changed: 106 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
//! cargo test -p socket-patch-cli --test e2e_gem -- --ignored
1818
//! ```
1919
20+
use std::collections::HashMap;
2021
use std::path::{Path, PathBuf};
2122
use std::process::{Command, Output};
2223

@@ -27,25 +28,8 @@ use sha2::{Digest, Sha256};
2728
// ---------------------------------------------------------------------------
2829

2930
const GEM_UUID: &str = "4bf7fe0b-dc57-4ea8-945f-bc4a04c47a15";
30-
#[allow(dead_code)]
3131
const GEM_PURL: &str = "pkg:gem/activestorage@5.2.0";
3232

33-
/// File hashes for the 3 patched files in activestorage 5.2.0.
34-
35-
const VARIATION_RB_BEFORE: &str = "96b72bac68797be5c69d4dd46fbb67b7e6bb2d576fbe2c87490e53714739da06";
36-
const VARIATION_RB_AFTER: &str = "27bc60b5399e459d9e75a69e21ca1ff9e0a3e36e56e3ef5d0d05ad44e8f18aed";
37-
38-
const ACTIVE_STORAGE_RB_BEFORE: &str = "507653326a9fffbc7da4ebaab5c8cb55c8e81be5dc6dde5a1b0b4e0b17f3a8d2";
39-
const ACTIVE_STORAGE_RB_AFTER: &str = "962e44b7e3b3c59c6c8c14d16cf9f80752e56ad1fb1c6ff6c2b51b15fcaf7df9";
40-
41-
const ENGINE_RB_BEFORE: &str = "09fc2486c5e02c5f29e7c61ef3e7b0e17f6c6b1f5ddfe6e1d08e4c06f3adf8c4";
42-
const ENGINE_RB_AFTER: &str = "4693b4d8f1a7c06e5d09b24f8c3e7a1d6b5f0e2c9a8d7b6f4e3c2a1b0d9e8f7";
43-
44-
/// Relative paths of patched files inside the gem directory.
45-
const VARIATION_RB: &str = "app/models/active_storage/variation.rb";
46-
const ACTIVE_STORAGE_RB: &str = "lib/active_storage.rb";
47-
const ENGINE_RB: &str = "lib/active_storage/engine.rb";
48-
4933
// ---------------------------------------------------------------------------
5034
// Helpers
5135
// ---------------------------------------------------------------------------
@@ -140,23 +124,85 @@ fn find_gem_dir(cwd: &Path) -> PathBuf {
140124
);
141125
}
142126

143-
/// Verify all 3 files match expected hashes.
144-
fn assert_hashes(gem_dir: &Path, variation: &str, active_storage: &str, engine: &str) {
145-
assert_eq!(
146-
git_sha256_file(&gem_dir.join(VARIATION_RB)),
147-
variation,
148-
"variation.rb hash mismatch"
149-
);
150-
assert_eq!(
151-
git_sha256_file(&gem_dir.join(ACTIVE_STORAGE_RB)),
152-
active_storage,
153-
"active_storage.rb hash mismatch"
154-
);
155-
assert_eq!(
156-
git_sha256_file(&gem_dir.join(ENGINE_RB)),
157-
engine,
158-
"engine.rb hash mismatch"
159-
);
127+
/// Read the manifest and return the files map for the gem patch.
128+
fn read_patch_files(manifest_path: &Path) -> serde_json::Value {
129+
let manifest: serde_json::Value =
130+
serde_json::from_str(&std::fs::read_to_string(manifest_path).unwrap()).unwrap();
131+
let patch = &manifest["patches"][GEM_PURL];
132+
assert!(patch.is_object(), "manifest should contain {GEM_PURL}");
133+
patch["files"].clone()
134+
}
135+
136+
/// Record hashes of all files in the gem dir that will be patched.
137+
fn record_original_hashes(gem_dir: &Path, files: &serde_json::Value) -> HashMap<String, String> {
138+
let mut hashes = HashMap::new();
139+
for (rel_path, _) in files.as_object().expect("files object") {
140+
let full_path = gem_dir.join(rel_path);
141+
let hash = if full_path.exists() {
142+
git_sha256_file(&full_path)
143+
} else {
144+
String::new()
145+
};
146+
hashes.insert(rel_path.clone(), hash);
147+
}
148+
hashes
149+
}
150+
151+
/// Verify all patched files match their afterHash from the manifest.
152+
fn assert_after_hashes(gem_dir: &Path, files: &serde_json::Value) {
153+
for (rel_path, info) in files.as_object().expect("files object") {
154+
let after_hash = info["afterHash"]
155+
.as_str()
156+
.expect("afterHash should be a string");
157+
let full_path = gem_dir.join(rel_path);
158+
assert!(
159+
full_path.exists(),
160+
"patched file should exist: {}",
161+
full_path.display()
162+
);
163+
assert_eq!(
164+
git_sha256_file(&full_path),
165+
after_hash,
166+
"hash mismatch for {rel_path} after patching"
167+
);
168+
}
169+
}
170+
171+
/// Verify all patched files match their beforeHash (or are removed if new).
172+
fn assert_before_hashes(gem_dir: &Path, files: &serde_json::Value) {
173+
for (rel_path, info) in files.as_object().expect("files object") {
174+
let before_hash = info["beforeHash"].as_str().unwrap_or("");
175+
let full_path = gem_dir.join(rel_path);
176+
if before_hash.is_empty() {
177+
assert!(
178+
!full_path.exists(),
179+
"new file {rel_path} should be removed after rollback"
180+
);
181+
} else {
182+
assert_eq!(
183+
git_sha256_file(&full_path),
184+
before_hash,
185+
"{rel_path} should match beforeHash"
186+
);
187+
}
188+
}
189+
}
190+
191+
/// Verify files match the originally recorded hashes.
192+
fn assert_original_hashes(gem_dir: &Path, original_hashes: &HashMap<String, String>) {
193+
for (rel_path, orig_hash) in original_hashes {
194+
if orig_hash.is_empty() {
195+
continue;
196+
}
197+
let full_path = gem_dir.join(rel_path);
198+
if full_path.exists() {
199+
assert_eq!(
200+
git_sha256_file(&full_path),
201+
*orig_hash,
202+
"{rel_path} should match original hash"
203+
);
204+
}
205+
}
160206
}
161207

162208
// ---------------------------------------------------------------------------
@@ -263,18 +309,6 @@ fn test_gem_full_lifecycle() {
263309
bundle_run(cwd, &["install", "--path", "vendor/bundle"]);
264310

265311
let gem_dir = find_gem_dir(cwd);
266-
assert!(
267-
gem_dir.join(VARIATION_RB).exists(),
268-
"variation.rb must exist after bundle install"
269-
);
270-
271-
// Confirm original files match expected before-hashes.
272-
assert_hashes(
273-
&gem_dir,
274-
VARIATION_RB_BEFORE,
275-
ACTIVE_STORAGE_RB_BEFORE,
276-
ENGINE_RB_BEFORE,
277-
);
278312

279313
// -- GET: download + apply patch ------------------------------------------
280314
assert_run_ok(cwd, &["get", GEM_UUID], "get");
@@ -288,14 +322,15 @@ fn test_gem_full_lifecycle() {
288322
assert!(patch.is_object(), "manifest should contain {GEM_PURL}");
289323
assert_eq!(patch["uuid"].as_str().unwrap(), GEM_UUID);
290324

291-
// Files should now be patched.
292-
assert_hashes(
293-
&gem_dir,
294-
VARIATION_RB_AFTER,
295-
ACTIVE_STORAGE_RB_AFTER,
296-
ENGINE_RB_AFTER,
325+
let files = &patch["files"];
326+
assert!(
327+
files.as_object().map_or(false, |f| !f.is_empty()),
328+
"patch should modify at least one file"
297329
);
298330

331+
// Files should now be patched — verify against afterHash from manifest.
332+
assert_after_hashes(&gem_dir, files);
333+
299334
// -- LIST: verify JSON output ---------------------------------------------
300335
let (stdout, _) = assert_run_ok(cwd, &["list", "--json"], "list --json");
301336
let list: serde_json::Value = serde_json::from_str(&stdout).unwrap();
@@ -318,33 +353,15 @@ fn test_gem_full_lifecycle() {
318353

319354
// -- ROLLBACK: restore original files -------------------------------------
320355
assert_run_ok(cwd, &["rollback"], "rollback");
321-
322-
assert_hashes(
323-
&gem_dir,
324-
VARIATION_RB_BEFORE,
325-
ACTIVE_STORAGE_RB_BEFORE,
326-
ENGINE_RB_BEFORE,
327-
);
356+
assert_before_hashes(&gem_dir, files);
328357

329358
// -- APPLY: re-apply from manifest ----------------------------------------
330359
assert_run_ok(cwd, &["apply"], "apply");
331-
332-
assert_hashes(
333-
&gem_dir,
334-
VARIATION_RB_AFTER,
335-
ACTIVE_STORAGE_RB_AFTER,
336-
ENGINE_RB_AFTER,
337-
);
360+
assert_after_hashes(&gem_dir, files);
338361

339362
// -- REMOVE: rollback + remove from manifest ------------------------------
340363
assert_run_ok(cwd, &["remove", GEM_UUID], "remove");
341-
342-
assert_hashes(
343-
&gem_dir,
344-
VARIATION_RB_BEFORE,
345-
ACTIVE_STORAGE_RB_BEFORE,
346-
ENGINE_RB_BEFORE,
347-
);
364+
assert_before_hashes(&gem_dir, files);
348365

349366
let manifest: serde_json::Value =
350367
serde_json::from_str(&std::fs::read_to_string(&manifest_path).unwrap()).unwrap();
@@ -370,43 +387,25 @@ fn test_gem_dry_run() {
370387
bundle_run(cwd, &["install", "--path", "vendor/bundle"]);
371388

372389
let gem_dir = find_gem_dir(cwd);
373-
assert_hashes(
374-
&gem_dir,
375-
VARIATION_RB_BEFORE,
376-
ACTIVE_STORAGE_RB_BEFORE,
377-
ENGINE_RB_BEFORE,
378-
);
379390

380391
// Download without applying.
381392
assert_run_ok(cwd, &["get", GEM_UUID, "--no-apply"], "get --no-apply");
382393

383-
// Files should still be original.
384-
assert_hashes(
385-
&gem_dir,
386-
VARIATION_RB_BEFORE,
387-
ACTIVE_STORAGE_RB_BEFORE,
388-
ENGINE_RB_BEFORE,
389-
);
394+
// Read manifest to get file list and expected hashes.
395+
let manifest_path = cwd.join(".socket/manifest.json");
396+
let files = read_patch_files(&manifest_path);
397+
let original_hashes = record_original_hashes(&gem_dir, &files);
398+
399+
// Files should still be original (not patched).
400+
assert_original_hashes(&gem_dir, &original_hashes);
390401

391402
// Dry-run should succeed but leave files untouched.
392403
assert_run_ok(cwd, &["apply", "--dry-run"], "apply --dry-run");
393-
394-
assert_hashes(
395-
&gem_dir,
396-
VARIATION_RB_BEFORE,
397-
ACTIVE_STORAGE_RB_BEFORE,
398-
ENGINE_RB_BEFORE,
399-
);
404+
assert_original_hashes(&gem_dir, &original_hashes);
400405

401406
// Real apply should work.
402407
assert_run_ok(cwd, &["apply"], "apply");
403-
404-
assert_hashes(
405-
&gem_dir,
406-
VARIATION_RB_AFTER,
407-
ACTIVE_STORAGE_RB_AFTER,
408-
ENGINE_RB_AFTER,
409-
);
408+
assert_after_hashes(&gem_dir, &files);
410409
}
411410

412411
/// `get --save-only` should save the patch to the manifest without applying.
@@ -425,27 +424,17 @@ fn test_gem_save_only() {
425424
bundle_run(cwd, &["install", "--path", "vendor/bundle"]);
426425

427426
let gem_dir = find_gem_dir(cwd);
428-
assert_hashes(
429-
&gem_dir,
430-
VARIATION_RB_BEFORE,
431-
ACTIVE_STORAGE_RB_BEFORE,
432-
ENGINE_RB_BEFORE,
433-
);
434427

435428
// Download with --save-only.
436429
assert_run_ok(cwd, &["get", GEM_UUID, "--save-only"], "get --save-only");
437430

438-
// Files should still be original.
439-
assert_hashes(
440-
&gem_dir,
441-
VARIATION_RB_BEFORE,
442-
ACTIVE_STORAGE_RB_BEFORE,
443-
ENGINE_RB_BEFORE,
444-
);
445-
446-
// Manifest should exist with the patch.
431+
// Read manifest to get file list and expected hashes.
447432
let manifest_path = cwd.join(".socket/manifest.json");
448-
assert!(manifest_path.exists(), "manifest should exist after get --save-only");
433+
let files = read_patch_files(&manifest_path);
434+
let original_hashes = record_original_hashes(&gem_dir, &files);
435+
436+
// Files should still be original (not patched).
437+
assert_original_hashes(&gem_dir, &original_hashes);
449438

450439
let manifest: serde_json::Value =
451440
serde_json::from_str(&std::fs::read_to_string(&manifest_path).unwrap()).unwrap();
@@ -455,11 +444,5 @@ fn test_gem_save_only() {
455444

456445
// Real apply should work.
457446
assert_run_ok(cwd, &["apply"], "apply");
458-
459-
assert_hashes(
460-
&gem_dir,
461-
VARIATION_RB_AFTER,
462-
ACTIVE_STORAGE_RB_AFTER,
463-
ENGINE_RB_AFTER,
464-
);
447+
assert_after_hashes(&gem_dir, &files);
465448
}

0 commit comments

Comments
 (0)