11//! Real-vlt mode migrations (suite `migration`, DESIGN §8.3, §4.10).
22//!
3- //! Hosted ⇄ vendored takeovers (both drivers), dry-run parity, scoped and
4- //! unscoped unwinds, agent-mode interplay (r-tests T21), package-manager
5- //! switches (T37) and the vlt upgrade (T19, `SOCKET_PATCH_VLT_E2E_UPGRADE_JS`)
6- //! against the REAL vlt under test, every terminal state proven by a fresh
7- //! checkout's locked install. Each leg is `vlt_pinned_matrix_migration_<leg>`
3+ //! Hosted ⇄ vendored migrations (scan takeovers, the `vendor` eject),
4+ //! dry-run parity, scoped and unscoped unwinds, agent-mode interplay
5+ //! (r-tests T21), package-manager switches (T37) and the vlt upgrade (T19,
6+ //! `SOCKET_PATCH_VLT_E2E_UPGRADE_JS`) against the REAL vlt under test,
7+ //! every terminal state proven by a fresh checkout's locked install. Each leg is `vlt_pinned_matrix_migration_<leg>`
88//! and prints one `VLT-LEG` line.
99
1010#[ path = "common/rollback_json.rs" ]
@@ -111,7 +111,8 @@ fn api_upstream(fx: &Fixture, dir: &Path, head: &[&str], extra: &[&str]) -> Sock
111111}
112112
113113/// `vendor` over a (possibly) hosted project: no `--offline`, since the
114- /// takeover restores the hosted pin's upstream entry first.
114+ /// eject fetches the hosted pin's record and restores its upstream entry
115+ /// first.
115116fn vendor_upstream ( fx : & Fixture , dir : & Path , extra : & [ & str ] ) -> SocketOut {
116117 api_upstream ( fx, dir, & [ "vendor" ] , extra)
117118}
@@ -286,10 +287,12 @@ async fn vlt_pinned_matrix_migration_vendored_then_hosted() {
286287 fx. leg . ran ( ) ;
287288}
288289
289- /// Hosted → vendored through `scan --mode vendored` and through `vendor`
290- /// (a staged manifest): the takeover restores the hosted pin's upstream
291- /// entry first, the lock is vendored, fresh checkouts are patched, and
292- /// `vendor --revert` restores the pristine (upstream) lock.
290+ /// Hosted → vendored through `scan --mode vendored` (a per-purl takeover)
291+ /// and through `vendor` (a staged manifest, but a hosted pin is in scope,
292+ /// so `vendor` ejects the lockfile's hosted pin instead): either way the
293+ /// hosted pin's upstream entry is restored first, the lock is vendored,
294+ /// fresh checkouts are patched, and `vendor --revert` restores the
295+ /// pristine (upstream) lock.
293296#[ tokio:: test( flavor = "multi_thread" ) ]
294297#[ ignore = "real vlt: SOCKET_PATCH_VLT_E2E_JS" ]
295298async fn vlt_pinned_matrix_migration_hosted_then_vendored ( ) {
@@ -302,17 +305,22 @@ async fn vlt_pinned_matrix_migration_hosted_then_vendored() {
302305 fx. vlt_ok ( & fx. proj , & fx. leg . locked_install_args ( ) ) ;
303306 for driver in [ "scan" , "vendor" ] {
304307 let dir = copy_project ( & fx, & fx. proj , & format ! ( "drv-{driver}" ) ) ;
305- let out = if driver == "scan" {
306- vendored_scan ( & fx, & dir, & [ ] )
307- } else {
308+ let eject = driver == "vendor" ;
309+ let out = if eject {
308310 stage_manifest ( & dir, & [ fx. t ( ) ] ) ;
309- vendor_upstream ( & fx, & dir, & [ ] )
311+ let manifest = std:: fs:: read ( dir. join ( ".socket/manifest.json" ) ) . unwrap ( ) ;
312+ let out = vendor_upstream ( & fx, & dir, & [ ] ) ;
313+ assert_eq ! (
314+ std:: fs:: read( dir. join( ".socket/manifest.json" ) ) . unwrap( ) ,
315+ manifest,
316+ "{driver}: the eject leaves the manifest untouched: {out}"
317+ ) ;
318+ out
319+ } else {
320+ vendored_scan ( & fx, & dir, & [ ] )
310321 } ;
311322 assert_eq ! ( out. code, 0 , "{driver}: {out}" ) ;
312- assert ! (
313- has_code( & out. json( ) , "vendor_takeover_reverted_redirect" ) ,
314- "{driver}: {out}"
315- ) ;
323+ assert_vendored_over_hosted ( eject, driver, & out) ;
316324 assert_pure_vendored ( & dir, fx. t ( ) ) ;
317325 assert_fresh (
318326 & fx,
@@ -332,15 +340,35 @@ async fn vlt_pinned_matrix_migration_hosted_then_vendored() {
332340 fx. leg . ran ( ) ;
333341}
334342
343+ /// The wet hosted → vendored advisories. `scan --mode vendored` announces
344+ /// its per-purl takeover; plain `vendor` with a hosted pin in scope
345+ /// (`eject`) runs the eject instead, which announces no takeover. Both
346+ /// heal the warm vlt store and ask for a `vlt install`.
347+ fn assert_vendored_over_hosted ( eject : bool , tag : & str , out : & SocketOut ) {
348+ let doc = out. json ( ) ;
349+ assert_eq ! (
350+ has_code( & doc, "vendor_takeover_reverted_redirect" ) ,
351+ !eject,
352+ "{tag}: only the scan takeover announces a takeover: {out}"
353+ ) ;
354+ // The eject reports a failed restore as a run error (exit 1); the scan
355+ // takeover reports it per event, so check it there.
356+ assert ! ( !has_code( & doc, "redirect_revert_failed" ) , "{tag}: {out}" ) ;
357+ assert ! (
358+ has_code( & doc, "redirect_vlt_reinstall_required" ) ,
359+ "{tag}: the warm vlt store is healed: {out}"
360+ ) ;
361+ }
362+
335363fn vendor_revert_all ( dir : & Path ) -> SocketOut {
336364 vendor_cmd ( dir, & [ "--revert" ] )
337365}
338366
339367// ── 3. dry-run parity ─────────────────────────────────────────────────────
340368
341- /// Over a live hosted redirect `vendor --dry-run` previews the takeover
342- /// and `scan --mode vendored --dry-run` says `would_vendor`; over a live
343- /// vendored state `scan --mode hosted --dry-run` previews
369+ /// Over a live hosted redirect `vendor --dry-run` previews the eject of
370+ /// the hosted pin (`eject_planned`) and `scan --mode vendored --dry-run`
371+ /// says `would_vendor`; over a live vendored state `scan --mode hosted --dry-run` previews
344372/// `redirect_would_revert_vendored`. No preview writes a byte, and the wet
345373/// runs then do what was previewed.
346374#[ tokio:: test( flavor = "multi_thread" ) ]
@@ -357,10 +385,20 @@ async fn vlt_pinned_matrix_migration_dry_run_parity() {
357385 let before = project_bytes ( & fx. proj ) ;
358386 let out = vendor_upstream ( & fx, & fx. proj , & [ "--dry-run" ] ) ;
359387 assert_eq ! ( out. code, 0 , "{out}" ) ;
388+ let doc = out. json ( ) ;
389+ let planned: Vec < & Value > = doc[ "events" ]
390+ . as_array ( )
391+ . map ( |evs| {
392+ evs. iter ( )
393+ . filter ( |e| e[ "errorCode" ] == "eject_planned" )
394+ . collect ( )
395+ } )
396+ . unwrap_or_default ( ) ;
360397 assert ! (
361- has_code ( & out . json ( ) , "vendor_would_revert_redirect" ) ,
362- "{out}"
398+ planned . len ( ) == 1 && planned [ 0 ] [ "purl" ] == fx . t ( ) . purl ( ) ,
399+ "one eject planned, for the hosted pin only: {out}"
363400 ) ;
401+ assert ! ( !has_code( & doc, "vendor_would_revert_redirect" ) , "{out}" ) ;
364402 assert ! ( !out. stdout. contains( "vendor_lock_entry_not_found" ) , "{out}" ) ;
365403 let out = vendored_scan ( & fx, & fx. proj , & [ "--dry-run" ] ) ;
366404 assert_eq ! ( out. code, 0 , "{out}" ) ;
@@ -382,10 +420,7 @@ async fn vlt_pinned_matrix_migration_dry_run_parity() {
382420 ) ;
383421 let out = vendor_upstream ( & fx, & fx. proj , & [ ] ) ;
384422 assert_eq ! ( out. code, 0 , "{out}" ) ;
385- assert ! (
386- has_code( & out. json( ) , "vendor_takeover_reverted_redirect" ) ,
387- "{out}"
388- ) ;
423+ assert_vendored_over_hosted ( true , "vendor" , & out) ;
389424 assert_pure_vendored ( & fx. proj , fx. t ( ) ) ;
390425 let before = project_bytes ( & fx. proj ) ;
391426 let out = hosted_scan ( & fx, & fx. proj , & [ "--dry-run" ] ) ;
0 commit comments