From 7f48fe9b5d300b4a49c663f399caeda5744888fc Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 28 Oct 2026 06:59:04 +0000 Subject: [PATCH 1/3] Start fix for #1770 Assisted-by: Claude Code:claude-opus-5-5 From f1a7fe3b4a081e3a998732340e0cc0dd7f584350 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 28 Oct 2026 07:05:47 +0000 Subject: [PATCH 2/3] Fix nightly bun.lockb takeover tests after #1648 The nightly bun 1.0 and 1.1 e2e legs have failed since vendor started ejecting hosted pins straight from the lockfile (#1648). An offline vendor now refuses up front with offline_eject_unavailable, and the online eject reports no separate takeover-revert event, but the takeover roundtrip test still expected the old behaviour. Assert the new offline refusal and the eject's events. Also point both binary-lock eject runs at the mock patch API (--api-url, --api-token, --org). Without those flags the eject fetched its patch record from the live public API, so the test depended on that service and on the fixture uuid existing there. Assisted-by: Claude Code:claude-opus-5-5 --- .../socket-patch-cli/tests/e2e_bun_lockb.rs | 52 +++++++++++-------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/crates/socket-patch-cli/tests/e2e_bun_lockb.rs b/crates/socket-patch-cli/tests/e2e_bun_lockb.rs index 5473397ac..28f75f3d1 100644 --- a/crates/socket-patch-cli/tests/e2e_bun_lockb.rs +++ b/crates/socket-patch-cli/tests/e2e_bun_lockb.rs @@ -899,10 +899,10 @@ 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 + // 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. Online, the // takeover rebuilds the binary registry record exactly and vendors over // it; `vendor --revert` then gives back the original bytes. After that // (equivalently, after `git checkout -- bun.lockb`) the offline vendor @@ -918,20 +918,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!( @@ -964,6 +958,12 @@ async fn native_binary_hosted_vendored_takeover_roundtrip() { project, &[ "vendor", + "--api-url", + &uri, + "--api-token", + "fake", + "--org", + ORG, "--patch-server-url", &uri, "--vendor-source", @@ -975,11 +975,13 @@ 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}" ); let vendor_lock = fixture.lock(); assert!( @@ -1176,6 +1178,12 @@ async fn binary_shared_bundled_record_hosted_pin_is_managed() { project, &[ "vendor", + "--api-url", + &uri, + "--api-token", + "fake", + "--org", + ORG, "--patch-server-url", &uri, "--vendor-source", @@ -1187,11 +1195,13 @@ 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!( !taken_over From d80f222e474ea23035b869142f96aabe1d25ba42 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 28 Oct 2026 07:09:35 +0000 Subject: [PATCH 3/3] Prove the bun.lockb eject used the mock patch API Check that the online eject fetched its patch record view from the mock server, and that the offline refusal made no request at all, so the test fails if the eject quietly takes another path. Update the comments to describe an eject rather than the old takeover. Assisted-by: Claude Code:claude-opus-5-5 --- .../socket-patch-cli/tests/e2e_bun_lockb.rs | 47 ++++++++++++++++--- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/crates/socket-patch-cli/tests/e2e_bun_lockb.rs b/crates/socket-patch-cli/tests/e2e_bun_lockb.rs index 28f75f3d1..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() { @@ -902,14 +918,16 @@ async fn native_binary_hosted_vendored_takeover_roundtrip() { // 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. Online, the - // takeover rebuilds the binary registry record exactly and vendors over - // it; `vendor --revert` then gives back the original bytes. After that + // 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); @@ -933,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. @@ -954,6 +977,7 @@ 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, &[ @@ -978,11 +1002,15 @@ async fn native_binary_hosted_vendored_takeover_roundtrip() { // 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 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!( !vendor_lock.windows(uri.len()).any(|w| w == uri.as_bytes()), @@ -1121,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() { @@ -1174,6 +1202,7 @@ 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, &[ @@ -1198,11 +1227,15 @@ async fn binary_shared_bundled_record_hosted_pin_is_managed() { // 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 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 .to_string()