diff --git a/crates/socket-patch-cli/tests/e2e_bun_lockb.rs b/crates/socket-patch-cli/tests/e2e_bun_lockb.rs index 5473397ac..53d776b26 100644 --- a/crates/socket-patch-cli/tests/e2e_bun_lockb.rs +++ b/crates/socket-patch-cli/tests/e2e_bun_lockb.rs @@ -216,6 +216,22 @@ fn scan(project: &Path, server: &MockServer, mode: &str, extra: &[&str]) -> Valu cli(project, &args) } +/// How many patch-record views the mock API has served: a vendor eject +/// reads each hosted pin's record through this endpoint. +async fn record_views(server: &MockServer) -> usize { + server + .received_requests() + .await + .unwrap_or_default() + .iter() + .filter(|r| { + r.url + .path() + .starts_with(&format!("/v0/orgs/{ORG}/patches/view/")) + }) + .count() +} + fn snapshot(root: &Path) -> BTreeMap> { fn walk(root: &Path, dir: &Path, result: &mut BTreeMap>) { for entry in std::fs::read_dir(dir).unwrap() { @@ -899,17 +915,19 @@ async fn native_binary_hosted_vendored_takeover_roundtrip() { ); std::fs::rename(&modules, project.join("node_modules")).unwrap(); - // Hosted -> vendored: v5 restores a hosted pin's upstream entry before - // vendoring over it, re-resolving the registry record from the npm - // registry — which an OFFLINE takeover cannot do, so it is REFUSED (dry - // and wet alike, nothing written) with the checkout remedy. Online, the - // takeover rebuilds the binary registry record exactly and vendors over - // it; `vendor --revert` then gives back the original bytes. After that + // Hosted -> vendored: v5 ejects a hosted pin from its patch record and + // the npm registry's upstream entry, neither of which an OFFLINE run can + // fetch, so it is REFUSED up front (dry and wet alike, nothing written) + // with the re-run-online remedy and no request at all. Online, the eject + // reads the pin's record from the patch API and vendors it in place of + // the hosted entry, recording the registry record exactly; + // `vendor --revert` then gives back the original bytes. After that // (equivalently, after `git checkout -- bun.lockb`) the offline vendor // proceeds, with a truthful dry run and exact rerun state. fixture.stage(); let uri = server.uri(); let before = snapshot(project); + let requests = server.received_requests().await.unwrap_or_default().len(); for extra in [&["--dry-run"][..], &[][..]] { let mut args = vec!["vendor", "--offline", "--patch-server-url", &uri]; args.extend_from_slice(extra); @@ -918,20 +936,14 @@ async fn native_binary_hosted_vendored_takeover_roundtrip() { code, 1, "vendor {extra:?} over a binary hosted pin: {refused}" ); - let failed = refused["events"] - .as_array() - .and_then(|events| { - events - .iter() - .find(|e| e["errorCode"] == "redirect_revert_failed") - }) - .unwrap_or_else(|| panic!("expected redirect_revert_failed: {refused}")); - assert_eq!(failed["purl"], PURL, "{refused}"); + assert_eq!( + refused["error"]["code"], "offline_eject_unavailable", + "{refused}" + ); assert!( - failed["error"].as_str().is_some_and(|e| { - e.contains("cannot vendor over the live hosted pin") - && e.contains("git checkout -- bun.lockb") - }), + refused["error"]["message"] + .as_str() + .is_some_and(|e| e.contains("re-run without --offline")), "{refused}" ); assert_eq!( @@ -939,6 +951,11 @@ async fn native_binary_hosted_vendored_takeover_roundtrip() { before, "refused vendor {extra:?} wrote nothing" ); + assert_eq!( + server.received_requests().await.unwrap_or_default().len(), + requests, + "refused vendor {extra:?} made no request" + ); } // The npm registry's version document for minimist@1.2.2 (the public // registry's values, which the original lock pins), served locally. @@ -960,10 +977,17 @@ async fn native_binary_hosted_vendored_takeover_roundtrip() { "integrity": integrity}}))) .mount(&server) .await; + let views = record_views(&server).await; let taken_over = cli_env( project, &[ "vendor", + "--api-url", + &uri, + "--api-token", + "fake", + "--org", + ORG, "--patch-server-url", &uri, "--vendor-source", @@ -975,11 +999,17 @@ async fn native_binary_hosted_vendored_takeover_roundtrip() { taken_over["summary"]["applied"], 1, "online vendor over the binary hosted pin: {taken_over}" ); + // An eject vendors the pin's own patch record in place of the hosted + // entry; there is no separate takeover-revert step to report. assert!( - taken_over["events"].as_array().is_some_and(|events| events + taken_over["events"].as_array().is_some_and(|events| !events .iter() .any(|e| e["errorCode"] == "vendor_takeover_reverted_redirect")), - "the takeover is reported: {taken_over}" + "the eject reports no takeover revert: {taken_over}" + ); + assert!( + record_views(&server).await > views, + "the eject read the pin's record from the mock API: {taken_over}" ); let vendor_lock = fixture.lock(); assert!( @@ -1119,8 +1149,8 @@ async fn native_binary_hosted_vendored_takeover_roundtrip() { /// tarball. The hosted scan wires that record for the regular install /// (warning that the bundled copy stays unpatched); the pin it wrote must /// then be listed and unwound like any other: `list` succeeds, the online -/// hosted → vendored takeover restores the registry record and vendors over -/// it, and `vendor --revert` gives back the pre-hosted bytes exactly. +/// hosted → vendored eject vendors the pin's patch record over it, and +/// `vendor --revert` gives back the pre-hosted bytes exactly. #[tokio::test(flavor = "multi_thread")] #[serial_test::serial] async fn binary_shared_bundled_record_hosted_pin_is_managed() { @@ -1172,10 +1202,17 @@ async fn binary_shared_bundled_record_hosted_pin_is_managed() { .mount(&server) .await; fixture.stage(); + let views = record_views(&server).await; let taken_over = cli_env( project, &[ "vendor", + "--api-url", + &uri, + "--api-token", + "fake", + "--org", + ORG, "--patch-server-url", &uri, "--vendor-source", @@ -1187,11 +1224,17 @@ async fn binary_shared_bundled_record_hosted_pin_is_managed() { taken_over["summary"]["applied"], 1, "online vendor over the shared hosted pin: {taken_over}" ); + // An eject vendors the pin's own patch record in place of the hosted + // entry; there is no separate takeover-revert step to report. assert!( - taken_over["events"].as_array().is_some_and(|events| events + taken_over["events"].as_array().is_some_and(|events| !events .iter() .any(|e| e["errorCode"] == "vendor_takeover_reverted_redirect")), - "the takeover is reported: {taken_over}" + "the eject reports no takeover revert: {taken_over}" + ); + assert!( + record_views(&server).await > views, + "the eject read the pin's record from the mock API: {taken_over}" ); assert!( !taken_over