char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
7325e8c
main
1# [ path = "common/registry.rs" ] 2mod fixture; 3 4use avril_runtime::{ 5 caps::{ Capabilities , Category , Initial }, 6 registry::{ 7PackageStore , 8 npm:: resolution::{ NpmResolver , NpmRootRequest , ResolutionMode }, 9}, 10}; 11use fixture:: Registry ; 12use futures:: executor:: block_on; 13use std:: collections:: BTreeMap ; 14use std:: sync:: Arc ; 15 16# [ test ] 17fn frozen_replay_accepts_the_canonical_locked_graph () { 18let server =Registry :: new (); 19 server. publish_package ( "util" , "1.0.0" , serde_json:: json!({}), & []); 20 server. publish_package ( 21"app" , 22"1.0.0" , 23 serde_json:: json!({ "dependencies" : { "util" : "^1" }}), 24& [], 25); 26let root = tempfile:: tempdir (). unwrap (); 27let app =NpmRootRequest { 28request : "npm:app@1" . into (), 29context : BTreeMap :: new (), 30}; 31let warm = server. resolver ( root. path ()); 32let expected =block_on ( warm. prepare ( std:: slice:: from_ref ( & app))). unwrap (); 33 warm. store (). save (). unwrap (); 34 35let frozen = server. frozen_resolver ( root. path ()); 36let lock_before = std:: fs:: read ( root. path (). join ( "avril.lock" )). unwrap (); 37let replay =block_on ( frozen. prepare ( & [ app])). unwrap (); 38assert_eq! ( replay. roots , expected. roots ); 39 frozen. store (). save (). unwrap (); 40assert_eq! ( 41 std:: fs:: read ( root. path (). join ( "avril.lock" )). unwrap (), 42 lock_before 43); 44} 45 46# [ test ] 47fn locked_roots_restore_the_recorded_peer_context () { 48let server =Registry :: new (); 49for versionin [ "1.0.0" , "1.1.0" ] { 50 server. publish_package ( "host" , version, serde_json:: json!({}), & []); 51} 52 server. publish_package ( 53"plugin" , 54"1.0.0" , 55 serde_json:: json!({ "peerDependencies" : { "host" : "^1" }}), 56& [], 57); 58let root = tempfile:: tempdir (). unwrap (); 59let request = |version : & str |NpmRootRequest { 60request : "npm:plugin@1" . into (), 61context : BTreeMap :: from ([( "host" . into (), format! ( "{}|host|{version}" , server. url ))]), 62}; 63let warm = server. resolver ( root. path ()); 64let first =block_on ( warm. prepare ( & [ request ( "1.0.0" )])). unwrap (); 65let second =block_on ( warm. prepare ( & [ request ( "1.1.0" )])). unwrap (); 66assert_ne! ( first. roots . values (). next (), second. roots . values (). next ()); 67 warm. store (). save (). unwrap (); 68let valid = server. frozen_resolver ( root. path ()); 69block_on ( valid. prepare ( & [ request ( "1.0.0" )])). unwrap (); 70let mut lock = fixture:: read_lock ( root. path ()); 71let first_key = first. roots . keys (). next (). unwrap (); 72let second_instance = second. roots . values (). next (). unwrap (); 73 lock[ "npm" ][ "roots" ][ first_key] = serde_json:: json!( second_instance); 74 std:: fs:: write ( root. path (). join ( "avril.lock" ), lock. to_string ()). unwrap (); 75 76let cold = server. frozen_resolver ( root. path ()); 77let replay =block_on ( cold. prepare ( & [ request ( "1.0.0" )])). unwrap (); 78assert_eq! ( replay. roots [ first_key], * second_instance); 79let plugin =block_on ( cold. resolve_request ( "npm:plugin@1" , ResolutionMode :: Import )). unwrap (); 80let host = cold 81. resolve_from_file ( "host" , & plugin. path , ResolutionMode :: Import ) 82. unwrap (); 83assert! ( host. path . to_string_lossy (). contains ( "/host/1.1.0/" )); 84} 85 86# [ test ] 87fn locked_dependency_edges_override_request_selections () { 88let server =Registry :: new (); 89 server. publish_package ( "util" , "1.0.0" , serde_json:: json!({}), & []); 90 server. publish_package ( 91"app" , 92"1.0.0" , 93 serde_json:: json!({ "dependencies" : { "util" : "^1" }, "imports" : { "#renamed" : "renamed" }}), 94& [], 95); 96let root = tempfile:: tempdir (). unwrap (); 97let app = ||NpmRootRequest { 98request : "npm:app@1" . into (), 99context : BTreeMap :: new (), 100}; 101let warm = server. resolver ( root. path ()); 102block_on ( warm. prepare ( & [ app ()])). unwrap (); 103 warm. store (). save (). unwrap (); 104 server. publish_package ( "util" , "2.0.0" , serde_json:: json!({}), & []); 105let expanded = server. resolver ( root. path ()); 106let newer =block_on ( expanded. prepare ( & [ NpmRootRequest { 107request : "npm:util@2.0.0" . into (), 108context : BTreeMap :: new (), 109}])) 110. unwrap (); 111 expanded. store (). save (). unwrap (); 112let valid = server. frozen_resolver ( root. path ()); 113block_on ( valid. prepare ( & [ app ()])). unwrap (); 114let mut lock = fixture:: read_lock ( root. path ()); 115assert_eq! ( lock[ "npm" ][ "specifiers" ][ "npm:util@^1" ], "1.0.0" ); 116let instances = lock[ "npm" ][ "instances" ]. as_object_mut (). unwrap (); 117let app_pin = instances 118. values_mut () 119. find ( |pin| pin[ "package" ]. as_str (). unwrap (). ends_with ( "|app|1.0.0" )) 120. unwrap (); 121 app_pin[ "deps" ][ "util" ] = serde_json:: json!( newer. roots . values (). next (). unwrap ()); 122 app_pin[ "deps" ][ "renamed" ] = app_pin[ "deps" ][ "util" ]. clone (); 123 std:: fs:: write ( root. path (). join ( "avril.lock" ), lock. to_string ()). unwrap (); 124 125let cold = server. frozen_resolver ( root. path ()); 126block_on ( cold. prepare ( & [ app ()])). unwrap (); 127let app =block_on ( cold. resolve_request ( "npm:app@1" , ResolutionMode :: Import )). unwrap (); 128let util = cold 129. resolve_from_file ( "util" , & app. path , ResolutionMode :: Import ) 130. unwrap (); 131assert! ( util. path . to_string_lossy (). contains ( "/util/2.0.0/" )); 132for aliasin [ "renamed" , "#renamed" ] { 133assert_eq! ( 134 cold. resolve_from_file ( alias, & app. path , ResolutionMode :: Import ) 135. unwrap () 136. path , 137 util. path 138); 139} 140} 141 142# [ test ] 143fn replay_loads_only_root_metadata_until_a_dependency_is_visited () { 144let server =Registry :: new (); 145 server. publish_package ( "util" , "1.0.0" , serde_json:: json!({}), & []); 146 server. publish_package ( 147"app" , 148"1.0.0" , 149 serde_json:: json!({ "dependencies" : { "util" : "^1" }}), 150& [( "index.js" , "export default 1;" )], 151); 152let root = tempfile:: tempdir (). unwrap (); 153let app =NpmRootRequest { 154request : "npm:app@1" . into (), 155context : BTreeMap :: new (), 156}; 157let warm = server. resolver ( root. path ()); 158block_on ( warm. prepare ( std:: slice:: from_ref ( & app))). unwrap (); 159 warm. store (). save (). unwrap (); 160 161let app_module =block_on ( warm. resolve_request ( "npm:app@1" , ResolutionMode :: Import )). unwrap (); 162let util = warm 163. resolve_from_file ( "util" , & app_module. path , ResolutionMode :: Import ) 164. unwrap (); 165 std:: fs:: write ( util. path . with_file_name ( "package.json" ), "not json" ). unwrap (); 166 167let frozen = server. frozen_resolver ( root. path ()); 168block_on ( frozen. prepare ( & [ app])). unwrap (); 169let app_module =block_on ( frozen. resolve_request ( "npm:app@1" , ResolutionMode :: Import )). unwrap (); 170assert_eq! ( 171 frozen. npm_source ( & app_module. path ). unwrap (), 172"export default 1;" 173); 174let error = frozen 175. resolve_from_file ( "util" , & app_module. path , ResolutionMode :: Import ) 176. unwrap_err () 177. to_string (); 178assert! ( error. contains ( "package.json" ), "{error}" ); 179assert! ( 180 frozen 181. audit () 182. unwrap_err () 183. to_string () 184. contains ( "package.json" ) 185); 186} 187 188# [ test ] 189fn locked_graph_can_omit_a_manifest_dependency () { 190let server =Registry :: new (); 191 server. publish_package ( "util" , "1.0.0" , serde_json:: json!({}), & []); 192 server. publish_package ( 193"app" , 194"1.0.0" , 195 serde_json:: json!({ "dependencies" : { "util" : "^1" }}), 196& [], 197); 198let root = tempfile:: tempdir (). unwrap (); 199let warm = server. resolver ( root. path ()); 200block_on ( warm. resolve_request ( "npm:app@1" , ResolutionMode :: Import )). unwrap (); 201 warm. store (). save (). unwrap (); 202let mut lock = fixture:: read_lock ( root. path ()); 203let app_pin = lock[ "npm" ][ "instances" ] 204. as_object_mut () 205. unwrap () 206. values_mut () 207. find ( |pin| pin[ "package" ]. as_str (). unwrap (). ends_with ( "|app|1.0.0" )) 208. unwrap (); 209 app_pin[ "deps" ]. as_object_mut (). unwrap (). remove ( "util" ); 210 std:: fs:: write ( root. path (). join ( "avril.lock" ), lock. to_string ()). unwrap (); 211let frozen = server. frozen_resolver ( root. path ()); 212let app =block_on ( frozen. resolve_request ( "npm:app@1" , ResolutionMode :: Import )). unwrap (); 213let error = frozen 214. resolve_from_file ( "util" , & app. path , ResolutionMode :: Import ) 215. unwrap_err (); 216assert! ( 217 error. to_string (). contains ( "not a declared dependency" ), 218"{error}" 219); 220} 221 222# [ test ] 223fn frozen_replay_restores_missing_storage_before_sync_require () { 224let server =Registry :: new (); 225 server. publish_package ( 226"util" , 227"1.0.0" , 228 serde_json:: json!({}), 229& [( "index.js" , "export default 1;" )], 230); 231 server. publish_package ( 232"app" , 233"1.0.0" , 234 serde_json:: json!({ "dependencies" : { "util" : "^1" }}), 235& [], 236); 237let root = tempfile:: tempdir (). unwrap (); 238let warm = server. resolver ( root. path ()); 239let app =block_on ( warm. resolve_request ( "npm:app@1" , ResolutionMode :: Import )). unwrap (); 240let util = warm 241. resolve_from_file ( "util" , & app. path , ResolutionMode :: Import ) 242. unwrap (); 243 warm. store (). save (). unwrap (); 244assert! ( 245 fixture:: read_lock ( root. path ())[ "npm" ][ "specifiers" ] 246. get ( "npm:util@1.0.0" ) 247. is_none () 248); 249let before = std:: fs:: read ( root. path (). join ( "avril.lock" )). unwrap (); 250let requests = server. requests . lock (). unwrap (). len (); 251 std:: fs:: remove_dir_all ( util. path . parent (). unwrap ()). unwrap (); 252let frozen = server. frozen_resolver ( root. path ()); 253let app =block_on ( frozen. resolve_request ( "npm:app@1" , ResolutionMode :: Import )). unwrap (); 254let util = frozen 255. resolve_from_file ( "util" , & app. path , ResolutionMode :: Require ) 256. unwrap (); 257assert_eq! ( frozen. npm_source ( & util. path ). unwrap (), "export default 1;" ); 258 frozen. store (). save (). unwrap (); 259assert_eq! ( 260 std:: fs:: read ( root. path (). join ( "avril.lock" )). unwrap (), 261 before 262); 263assert_eq! ( server. requests . lock (). unwrap (). len (), requests); 264} 265 266# [ test ] 267fn frozen_replay_restores_backing_storage_for_existing_peer_instances () { 268let server =Registry :: new (); 269 server. publish_package ( 270"app" , 271"1.0.0" , 272 serde_json:: json!({ "dependencies" : { "app" : "1.0.0" }}), 273& [( "index.js" , "export default 1;" )], 274); 275let root = tempfile:: tempdir (). unwrap (); 276let warm = server. resolver ( root. path ()); 277let app =block_on ( warm. resolve_request ( "npm:app@1" , ResolutionMode :: Import )). unwrap (); 278let backing =block_on ( warm. store (). npm_acquire ( "npm:app@1" )). unwrap (); 279assert_ne! ( app. path . parent (). unwrap (), backing. dir ); 280 warm. store (). save (). unwrap (); 281 std:: fs:: remove_dir_all ( & backing. dir ). unwrap (); 282let frozen = server. frozen_resolver ( root. path ()); 283let replay =block_on ( frozen. resolve_request ( "npm:app@1" , ResolutionMode :: Import )). unwrap (); 284assert_eq! ( replay. path , app. path ); 285assert_eq! ( 286 frozen. npm_source ( & replay. path ). unwrap (), 287"export default 1;" 288); 289 frozen. audit (). unwrap (); 290} 291 292# [ test ] 293fn frozen_replay_requires_the_recorded_contextual_root () { 294let server =Registry :: new (); 295 server. publish_package ( "app" , "1.0.0" , serde_json:: json!({}), & []); 296let root = tempfile:: tempdir (). unwrap (); 297let warm = server. resolver ( root. path ()); 298block_on ( warm. resolve_request ( "npm:app@1" , ResolutionMode :: Import )). unwrap (); 299 warm. store (). save (). unwrap (); 300let mut lock = fixture:: read_lock ( root. path ()); 301 lock[ "npm" ][ "roots" ] = serde_json:: json!({}); 302 std:: fs:: write ( root. path (). join ( "avril.lock" ), lock. to_string ()). unwrap (); 303let frozen = server. frozen_resolver ( root. path ()); 304let error =block_on ( frozen. resolve_request ( "npm:app@1" , ResolutionMode :: Import )). unwrap_err (); 305assert! ( 306 error 307. to_string () 308. contains ( "Frozen lockfile is missing npm:app@1" ), 309"{error}" 310); 311} 312 313# [ test ] 314fn replay_preserves_different_root_versions_of_the_same_package () { 315let server =Registry :: new (); 316for versionin [ "1.0.0" , "2.0.0" ] { 317 server. publish_package ( "app" , version, serde_json:: json!({}), & []); 318} 319let root = tempfile:: tempdir (). unwrap (); 320let requests: Vec < _ > =[ "npm:app@1" , "npm:app@2" ] 321. into_iter () 322. map ( |request|NpmRootRequest { 323request : request. to_owned (), 324context : BTreeMap :: new (), 325}) 326. collect (); 327let warm = server. resolver ( root. path ()); 328let expected =block_on ( warm. prepare ( & requests)). unwrap (); 329 warm. store (). save (). unwrap (); 330let frozen = server. frozen_resolver ( root. path ()); 331assert_eq! ( 332block_on ( frozen. prepare ( & requests)). unwrap (). roots , 333 expected. roots 334); 335for ( request, version) in [( "npm:app@1" , "1.0.0" ), ( "npm:app@2" , "2.0.0" )] { 336let module =block_on ( frozen. resolve_request ( request, ResolutionMode :: Import )). unwrap (); 337assert_eq! ( 338 frozen. describe_instance ( & module. instance ). unwrap (). version , 339 version 340); 341} 342} 343 344# [ test ] 345fn warm_replay_rejects_foreign_archive_provenance () { 346let server =Registry :: new (); 347 server. publish_package ( "app" , "1.0.0" , serde_json:: json!({}), & []); 348let root = tempfile:: tempdir (). unwrap (); 349let warm = server. resolver ( root. path ()); 350block_on ( warm. resolve_request ( "npm:app@1" , ResolutionMode :: Import )). unwrap (); 351 warm. store (). save (). unwrap (); 352let mut lock = fixture:: read_lock ( root. path ()); 353let pin = lock[ "npm" ][ "packages" ] 354. as_object_mut () 355. unwrap () 356. values_mut () 357. next () 358. unwrap (); 359 pin[ "tarball" ] = serde_json:: json!( "https://unrelated.invalid/app.tgz" ); 360 std:: fs:: write ( root. path (). join ( "avril.lock" ), lock. to_string ()). unwrap (); 361let error =PackageStore :: new ( fixture:: runtime_handle (), server. options ( root. path ())) 362. err () 363. expect ( "foreign archive provenance must fail" ); 364assert! ( 365matches! ( 366 error, 367 avril_runtime:: registry:: RegistryError :: Npm ( 368 avril_runtime:: registry:: npm:: NpmError :: NpmTarballEscapesRegistry { ..} 369) 370), 371"{error}" 372); 373} 374 375# [ test ] 376fn frozen_replay_rechecks_live_source_authority () { 377let server =Registry :: new (); 378 server. publish_package ( "guarded" , "1.0.0" , serde_json:: json!({}), & []); 379let root = tempfile:: tempdir (). unwrap (); 380let version_url =format! ( "{}guarded/1.0.0/" , server. url ); 381let mut initial =Initial :: new ( false ); 382 initial 383. grant ( Category :: JsExec , false , & version_url, root. path ()) 384. unwrap (); 385let capabilities =Capabilities :: new ( initial, root. path (). to_owned ()); 386let store =PackageStore :: new ( fixture:: runtime_handle (), server. options ( root. path ())) 387. unwrap () 388. with_capabilities ( capabilities. clone ()); 389let warm =NpmResolver :: new ( Arc :: new ( store)); 390let request =NpmRootRequest { 391request : "npm:guarded@1" . into (), 392context : BTreeMap :: new (), 393}; 394block_on ( warm. prepare ( std:: slice:: from_ref ( & request))). unwrap (); 395 warm. store (). save (). unwrap (); 396 397 capabilities 398. pledge ( vec! [( Category :: JsExec , None , vec! [ version_url])]) 399. unwrap (); 400let mut options = server. options ( root. path ()); 401 options. frozen =true ; 402 options. offline =true ; 403let replay =NpmResolver :: new ( Arc :: new ( 404PackageStore :: new ( fixture:: runtime_handle (), options) 405. unwrap () 406. with_capabilities ( capabilities), 407)); 408let error =block_on ( replay. prepare ( & [ request])) 409. unwrap_err () 410. to_string (); 411assert! ( error. contains ( "PermissionDenied" ), "{error}" ); 412} 413 414# [ test ] 415fn frozen_replay_rejects_invalid_graph_structure () { 416let server =Registry :: new (); 417 server. publish_package ( "util" , "1.0.0" , serde_json:: json!({}), & []); 418 server. publish_package ( 419"app" , 420"1.0.0" , 421 serde_json:: json!({ "dependencies" : { "util" : "^1" }}), 422& [], 423); 424let root = tempfile:: tempdir (). unwrap (); 425let app =NpmRootRequest { 426request : "npm:app@1" . into (), 427context : BTreeMap :: new (), 428}; 429let warm = server. resolver ( root. path ()); 430block_on ( warm. prepare ( std:: slice:: from_ref ( & app))). unwrap (); 431 warm. store (). save (). unwrap (); 432let pristine = fixture:: read_lock ( root. path ()); 433 434let mut missing_instance = pristine. clone (); 435let instances = missing_instance[ "npm" ][ "instances" ] 436. as_object_mut () 437. unwrap (); 438let dependency_instance = instances 439. iter () 440. find ( |( _, pin) | pin[ "package" ]. as_str (). unwrap (). ends_with ( "|util|1.0.0" )) 441. map ( |( id, _) | id. clone ()) 442. unwrap (); 443 instances. remove ( & dependency_instance); 444 445let mut missing_edge = pristine. clone (); 446let app_pin = missing_edge[ "npm" ][ "instances" ] 447. as_object_mut () 448. unwrap () 449. values_mut () 450. find ( |pin| pin[ "package" ]. as_str (). unwrap (). ends_with ( "|app|1.0.0" )) 451. unwrap (); 452 app_pin[ "deps" ][ "util" ] = serde_json:: json!( "p-0000000000000000" ); 453 454let mut missing_package = pristine. clone (); 455let package = missing_package[ "npm" ][ "instances" ] 456. as_object () 457. unwrap () 458. values () 459. find ( |pin| pin[ "package" ]. as_str (). unwrap (). ends_with ( "|util|1.0.0" )) 460. unwrap ()[ "package" ] 461. as_str () 462. unwrap () 463. to_owned (); 464 missing_package[ "npm" ][ "packages" ] 465. as_object_mut () 466. unwrap () 467. remove ( & package); 468 469for damagedin [ missing_instance, missing_edge, missing_package] { 470 std:: fs:: write ( root. path (). join ( "avril.lock" ), damaged. to_string ()). unwrap (); 471let mut options = server. options ( root. path ()); 472 options. frozen =true ; 473 options. offline =true ; 474let error =match PackageStore :: new ( fixture:: runtime_handle (), options) { 475Ok ( store) =>{ 476let cold =NpmResolver :: new ( Arc :: new ( store)); 477block_on ( cold. prepare ( std:: slice:: from_ref ( & app))) 478. unwrap_err () 479. to_string () 480} 481Err ( error) => error. to_string (), 482}; 483assert! ( error. contains ( "Lockfile is missing npm" ), "{error}" ); 484} 485 486let alias = package. replacen ( "http://" , "HTTP://" , 1 ); 487let noncanonical = pristine. to_string (). replace ( & package, & alias); 488 std:: fs:: write ( root. path (). join ( "avril.lock" ), noncanonical). unwrap (); 489let error =PackageStore :: new ( fixture:: runtime_handle (), server. options ( root. path ())) 490. err () 491. expect ( "distinct package identities must not alias the same storage" ); 492assert! ( 493matches! ( 494 error, 495 avril_runtime:: registry:: RegistryError :: Npm ( 496 avril_runtime:: registry:: npm:: NpmError :: InvalidNpmLockIdentity 497) 498), 499"{error}" 500); 501}