char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
688e96e
main
1# [ path = "common/registry.rs" ] 2mod fixture; 3 4# [ path = "common/http.rs" ] 5mod http_fixture; 6 7use http_fixture::{ Http , Response }; 8 9use avril_runtime:: caps::{ Capabilities , Category , Initial }; 10use avril_runtime:: registry::{ Lockfile , PackageStore , checksum}; 11use fixture:: Registry ; 12use futures:: executor:: block_on; 13 14fn read_lock ( path : & std:: path:: Path ) ->Lockfile { 15let bytes = std:: fs:: read ( path). unwrap (); 16let source = std:: str:: from_utf8 ( & bytes). unwrap (); 17 serde_json:: from_value ( serde_fjson:: parse ( source). unwrap ()). unwrap () 18} 19 20# [ test ] 21fn redirects_pin_the_final_url_and_only_authorize_the_requested_url () { 22let registry =Registry :: new (); 23let http =Http :: new (); 24let target =Http :: new (); 25 http. redirect ( "mod.ts" , & target. name ( "nested/final.ts" )); 26 target. source ( "nested/final.ts" , "export const value = 1;" ); 27 target. source ( "nested/dep.ts" , "export {};" ); 28let dir = tempfile:: tempdir (). unwrap (); 29let options = registry. options ( dir. path ()); 30let name = http. name ( "mod.ts" ); 31let mut initial =Initial :: new ( true ); 32 initial 33. grant ( Category :: JsExec , false , & name, dir. path ()) 34. unwrap (); 35 initial 36. grant ( Category :: JsExec , true , target. url . as_str (), dir. path ()) 37. unwrap (); 38let caps =Capabilities :: new ( initial, dir. path (). to_owned ()); 39let store =PackageStore :: new ( fixture:: runtime_handle (), options. clone ()) 40. unwrap () 41. with_capabilities ( caps. clone ()); 42assert! ( store. remote_base ( & name). is_err ()); 43let ( a, b) =block_on ( futures:: future:: join ( 44 store. remote_source ( & name), 45 store. remote_source ( & name), 46)); 47assert_eq! ( a. unwrap (), "export const value = 1;" ); 48assert_eq! ( b. unwrap (), "export const value = 1;" ); 49assert_eq! ( 50 store. remote_base ( & name). unwrap (), 51 target. name ( "nested/final.ts" ) 52); 53assert! ( 54block_on ( store. remote_source ( & target. name ( "nested/dep.ts" ))) 55. unwrap_err () 56. to_string () 57. contains ( "PermissionDenied" ) 58); 59assert_eq! ( http. requests . lock (). unwrap (). len (), 1 ); 60assert_eq! ( target. requests . lock (). unwrap (). len (), 1 ); 61 store. save (). unwrap (); 62let lock =read_lock ( & options. lock ); 63assert_eq! ( 64 lock. remote [ & name]. checksum , 65checksum ( b"export const value = 1;" ) 66); 67assert_eq! ( lock. remote [ & name]. url , target. name ( "nested/final.ts" )); 68 caps. pledge ( vec! [( Category :: JsExec , Some ( vec! []), vec! [])]) 69. unwrap (); 70assert! ( 71block_on ( store. remote_source ( & name)) 72. unwrap_err () 73. to_string () 74. contains ( "PermissionDenied" ) 75); 76let mut offline = options; 77 offline. offline =true ; 78 offline. frozen =true ; 79let denied =PackageStore :: new ( fixture:: runtime_handle (), offline. clone ()) 80. unwrap () 81. with_capabilities ( caps); 82assert! ( 83block_on ( denied. remote_source ( & name)) 84. unwrap_err () 85. to_string () 86. contains ( "PermissionDenied" ) 87); 88let store =PackageStore :: new ( fixture:: runtime_handle (), offline). unwrap (); 89block_on ( store. remote_source ( & name)). unwrap (); 90assert_eq! ( 91 store. remote_base ( & name). unwrap (), 92 target. name ( "nested/final.ts" ) 93); 94assert_eq! ( http. requests . lock (). unwrap (). len (), 1 ); 95} 96 97# [ test ] 98fn updates_refresh_once_and_preserve_other_projects_content_pins () { 99let registry =Registry :: new (); 100let http =Http :: new (); 101 http. source ( "mod.js" , "export const value = 1;" ); 102let dir = tempfile:: tempdir (). unwrap (); 103let options = registry. options ( dir. path ()); 104let name = http. name ( "mod.js" ); 105let original =PackageStore :: new ( fixture:: runtime_handle (), options. clone ()). unwrap (); 106block_on ( original. remote_source ( & name)). unwrap (); 107 original. save (). unwrap (); 108 http. source ( "mod.js" , "export const value = 2;" ); 109let stable =PackageStore :: new ( fixture:: runtime_handle (), options. clone ()). unwrap (); 110assert_eq! ( 111block_on ( stable. remote_source ( & name)). unwrap (), 112"export const value = 1;" 113); 114let mut other = options. clone (); 115 other. lock = dir. path (). join ( "other.lock" ); 116 other. update =true ; 117let update =PackageStore :: new ( fixture:: runtime_handle (), other. clone ()). unwrap (); 118let ( a, b) =block_on ( futures:: future:: join ( 119 update. remote_source ( & name), 120 update. remote_source ( & name), 121)); 122assert_eq! ( a. unwrap (), "export const value = 2;" ); 123assert_eq! ( b. unwrap (), "export const value = 2;" ); 124 http. source ( "mod.js" , "export const value = 3;" ); 125assert_eq! ( 126block_on ( update. remote_source ( & name)). unwrap (), 127"export const value = 2;" 128); 129 update. save (). unwrap (); 130assert_eq! ( http. requests . lock (). unwrap (). len (), 2 ); 131let mut offline = options; 132 offline. offline =true ; 133 offline. frozen =true ; 134let store =PackageStore :: new ( fixture:: runtime_handle (), offline). unwrap (); 135assert_eq! ( 136block_on ( store. remote_source ( & name)). unwrap (), 137"export const value = 1;" 138); 139 other. update =false ; 140 other. lock = dir. path (). join ( "unlocked.lock" ); 141let store =PackageStore :: new ( fixture:: runtime_handle (), other). unwrap (); 142assert_eq! ( 143block_on ( store. remote_source ( & name)). unwrap (), 144"export const value = 2;" 145); 146} 147 148# [ test ] 149fn vendored_sources_need_only_the_lock_and_vendor_directory () { 150let registry =Registry :: new (); 151let http =Http :: new (); 152 http. redirect ( "mod.ts?version=1" , "/nested/final.ts" ); 153 http. source ( "nested/final.ts" , "export {};" ); 154let dir = tempfile:: tempdir (). unwrap (); 155let mut options = registry. options ( dir. path ()); 156let name = http. name ( "mod.ts?version=1" ); 157let store =PackageStore :: new ( fixture:: runtime_handle (), options. clone ()). unwrap (); 158block_on ( store. remote_source ( & name)). unwrap (); 159 store. save (). unwrap (); 160let vendor = dir. path (). join ( "vendor" ); 161 store. vendor ( & vendor). unwrap (); 162let relative = store. vendor_relative ( & name). unwrap (); 163assert! ( relative. starts_with ( "http" )); 164assert_eq! ( relative. extension (). unwrap (), "ts" ); 165assert_eq! ( std:: fs:: read ( vendor. join ( relative)). unwrap (), b"export {};" ); 166 options. vendor =Some ( vendor); 167 options. cache = dir. path (). join ( "empty-cache" ); 168 options. offline =true ; 169 options. frozen =true ; 170let store =PackageStore :: new ( fixture:: runtime_handle (), options). unwrap (); 171assert_eq! ( block_on ( store. remote_source ( & name)). unwrap (), "export {};" ); 172assert_eq! ( 173 store. remote_base ( & name). unwrap (), 174 http. name ( "nested/final.ts" ) 175); 176assert_eq! ( http. requests . lock (). unwrap (). len (), 2 ); 177} 178 179# [ test ] 180fn graph_saves_merge_filter_conflict_and_prune_remote_entries () { 181let registry =Registry :: new (); 182let http =Http :: new (); 183 http. source ( "a.js" , "export const value = 1;" ); 184 http. source ( "b.js" , "export {};" ); 185let dir = tempfile:: tempdir (). unwrap (); 186let options = registry. options ( dir. path ()); 187let a = http. name ( "a.js" ); 188let b = http. name ( "b.js" ); 189let first =PackageStore :: new ( fixture:: runtime_handle (), options. clone ()). unwrap (); 190let second =PackageStore :: new ( fixture:: runtime_handle (), options. clone ()). unwrap (); 191block_on ( first. remote_source ( & a)). unwrap (); 192block_on ( first. remote_source ( & b)). unwrap (); 193 first 194. save_graph ( & Default :: default (), & [ a. clone ()]. into ()) 195. unwrap (); 196assert_eq! ( read_lock ( & options. lock ). remote . len (), 1 ); 197block_on ( second. remote_source ( & b)). unwrap (); 198 second. save (). unwrap (); 199assert_eq! ( read_lock ( & options. lock ). remote . len (), 2 ); 200let stale =PackageStore :: new ( fixture:: runtime_handle (), options. clone ()). unwrap (); 201block_on ( stale. remote_source ( & a)). unwrap (); 202let mut updated = options. clone (); 203 updated. update =true ; 204 http. source ( "a.js" , "export const value = 2;" ); 205let update =PackageStore :: new ( fixture:: runtime_handle (), updated). unwrap (); 206block_on ( update. remote_source ( & a)). unwrap (); 207 update. save (). unwrap (); 208assert! ( 209 stale 210. save () 211. unwrap_err () 212. to_string () 213. contains ( "changed concurrently" ) 214); 215 stale 216. save_graph ( & Default :: default (), & [ b. clone ()]. into ()) 217. unwrap (); 218let prune =PackageStore :: new ( fixture:: runtime_handle (), options. clone ()). unwrap (); 219block_on ( prune. remote_source ( & b)). unwrap (); 220 prune. prune (). unwrap (); 221let lock =read_lock ( & options. lock ); 222assert_eq! ( lock. remote . len (), 1 ); 223assert! ( lock. remote . contains_key ( & b)); 224} 225 226# [ test ] 227fn rejects_html_credentials_unsupported_redirects_and_redirect_loops () { 228let registry =Registry :: new (); 229let http =Http :: new (); 230let dir = tempfile:: tempdir (). unwrap (); 231let store =PackageStore :: new ( fixture:: runtime_handle (), registry. options ( dir. path ())). unwrap (); 232 http. source ( "html.js" , "<!DOCTYPE html><html></html>" ); 233 http. source ( "mime.js" , "export {};" ); 234 http. routes 235. lock () 236. unwrap () 237. get_mut ( "/mime.js" ) 238. unwrap () 239. headers ="Content-Type: TEXT/HTML; charset=utf-8\r\n" . into (); 240 http. redirect ( "loop.js" , "/loop.js" ); 241 http. redirect ( "file.js" , "file:///tmp/module.js" ); 242 http. redirect ( 243"credentials.js" , 244& http 245. name ( "secret.js" ) 246. replace ( "http://" , "http://user:pass@" ), 247); 248 http. source ( "secret.js" , "export {};" ); 249for ( path, message) in [ 250( "html.js" , "HTML" ), 251( "mime.js" , "HTML" ), 252( "loop.js" , "Too many" ), 253( "file.js" , "HTTP(S)" ), 254( "credentials.js" , "credentials" ), 255] { 256assert! ( 257block_on ( store. remote_source ( & http. name ( path))) 258. unwrap_err () 259. to_string () 260. contains ( message), 261"{path}" 262); 263} 264assert! ( !http. requests . lock (). unwrap (). contains ( & "/secret.js" . into ())); 265assert_eq! ( 266 http. requests 267. lock () 268. unwrap () 269. iter () 270. filter ( |path| path. as_str () =="/loop.js" ) 271. count (), 27211 273); 274let credentialed = http 275. name ( "secret.js" ) 276. replace ( "http://" , "http://user:pass@" ); 277assert! ( 278block_on ( store. remote_source ( & credentialed)) 279. unwrap_err () 280. to_string () 281. contains ( "credentials" ) 282); 283assert! ( block_on ( store. remote_source ( "file:///tmp/mod.js" )). is_err ()); 284} 285 286# [ test ] 287fn rejects_revocation_during_download_and_missing_frozen_pins () { 288let registry =Registry :: new (); 289let http =Http :: new (); 290 http. source ( "mod.js" , "export {};" ); 291let dir = tempfile:: tempdir (). unwrap (); 292let options = registry. options ( dir. path ()); 293let name = http. name ( "mod.js" ); 294let mut initial =Initial :: new ( true ); 295 initial 296. grant ( Category :: JsExec , false , & name, dir. path ()) 297. unwrap (); 298let caps =Capabilities :: new ( initial, dir. path (). to_owned ()); 299let store =PackageStore :: new ( fixture:: runtime_handle (), options. clone ()) 300. unwrap () 301. with_capabilities ( caps. clone ()); 302* http. hook . lock (). unwrap () =Some ( Box :: new ( move ||{ 303 caps. pledge ( vec! [( Category :: JsExec , Some ( vec! []), vec! [])]) 304. unwrap (); 305})); 306assert! ( 307block_on ( store. remote_source ( & name)) 308. unwrap_err () 309. to_string () 310. contains ( "PermissionDenied" ) 311); 312 store. save (). unwrap (); 313assert! ( !options. lock . exists ()); 314let lock =format! ( 315"version: 1\njsrRegistry: \"{}\"\nspecifiers: {{}}\npackages: {{}}\n" , 316 registry. url 317); 318 std:: fs:: write ( & options. lock , lock). unwrap (); 319let mut frozen = options; 320 frozen. frozen =true ; 321let store =PackageStore :: new ( fixture:: runtime_handle (), frozen). unwrap (); 322assert! ( 323block_on ( store. remote_source ( & name)) 324. unwrap_err () 325. to_string () 326. contains ( "Frozen" ) 327); 328assert_eq! ( http. requests . lock (). unwrap (). len (), 1 ); 329} 330 331# [ test ] 332fn pinned_downloads_reject_changed_final_urls_even_with_identical_bytes () { 333let registry =Registry :: new (); 334let http =Http :: new (); 335 http. redirect ( "mod.js" , "/a.js" ); 336 http. source ( "a.js" , "export {};" ); 337 http. source ( "b.js" , "export {};" ); 338let dir = tempfile:: tempdir (). unwrap (); 339let mut options = registry. options ( dir. path ()); 340let name = http. name ( "mod.js" ); 341let store =PackageStore :: new ( fixture:: runtime_handle (), options. clone ()). unwrap (); 342block_on ( store. remote_source ( & name)). unwrap (); 343 store. save (). unwrap (); 344 options. cache = dir. path (). join ( "empty-cache" ); 345 http. redirect ( "mod.js" , "/b.js" ); 346let store =PackageStore :: new ( fixture:: runtime_handle (), options. clone ()). unwrap (); 347assert! ( 348block_on ( store. remote_source ( & name)) 349. unwrap_err () 350. to_string () 351. contains ( "final URL mismatch" ) 352); 353 http. redirect ( "mod.js" , "/a.js" ); 354 http. source ( "a.js" , "export const changed = true;" ); 355let store =PackageStore :: new ( fixture:: runtime_handle (), options). unwrap (); 356assert! ( 357block_on ( store. remote_source ( & name)) 358. unwrap_err () 359. to_string () 360. contains ( "integrity mismatch" ) 361); 362} 363 364# [ test ] 365fn registry_prefix_sources_still_require_the_manifest () { 366let registry =Registry :: new (); 367 registry. package ( 368"@test/pkg" , 369"1.0.0" , 370& [( "mod.ts" , "export {};" )], 371& [( "." , "./mod.ts" )], 372); 373let dir = tempfile:: tempdir (). unwrap (); 374let options = registry. options ( dir. path ()); 375let store =PackageStore :: new ( fixture:: runtime_handle (), options. clone ()). unwrap (); 376let name =block_on ( store. resolve ( "jsr:@test/pkg@1" )). unwrap (); 377assert_eq! ( block_on ( store. remote_source ( & name)). unwrap (), "export {};" ); 378 store. save (). unwrap (); 379assert! ( read_lock ( & options. lock ). remote . is_empty ()); 380assert! ( 381 registry 382. requests 383. lock () 384. unwrap () 385. contains ( & "/@test/pkg/1.0.0_meta.json" . into ()) 386); 387let private = registry 388. url 389. join ( "@test/pkg/1.0.0/private.ts" ) 390. unwrap () 391. to_string (); 392assert! ( 393block_on ( store. remote_source ( & private)) 394. unwrap_err () 395. to_string () 396. contains ( "manifest" ) 397); 398} 399 400# [ test ] 401fn rejects_oversized_and_non_utf8_sources () { 402let registry =Registry :: new (); 403let http =Http :: new (); 404let dir = tempfile:: tempdir (). unwrap (); 405let store =PackageStore :: new ( fixture:: runtime_handle (), registry. options ( dir. path ())). unwrap (); 406for ( path, body, message) in [ 407( 408"large.js" , 409vec! [ b' ' ; avril_runtime:: registry:: MAX_ARTIFACT_SIZE +1 ], 410"too large" , 411), 412( "invalid.js" , vec! [ 255 ], "UTF-8" ), 413] { 414 http. routes . lock (). unwrap (). insert ( 415format! ( "/{path}" ), 416Response { 417status : 200 , 418headers : String :: new (), 419 body, 420}, 421); 422assert! ( 423block_on ( store. remote_source ( & http. name ( path))) 424. unwrap_err () 425. to_string () 426. contains ( message) 427); 428} 429} 430 431# [ test ] 432fn corrupted_cached_http_source_fails_integrity_verification () { 433let registry =Registry :: new (); 434let http =Http :: new (); 435 http. source ( "mod.js" , "export {};" ); 436let dir = tempfile:: tempdir (). unwrap (); 437let options = registry. options ( dir. path ()); 438let name = http. name ( "mod.js" ); 439let store =PackageStore :: new ( fixture:: runtime_handle (), options. clone ()). unwrap (); 440block_on ( store. remote_source ( & name)). unwrap (); 441 store. save (). unwrap (); 442let hash =read_lock ( & options. lock ). remote [ & name]. checksum . clone (); 443 std:: fs:: write ( options. cache . join ( "http/blobs" ). join ( hash), b"tampered" ). unwrap (); 444let store =PackageStore :: new ( fixture:: runtime_handle (), options). unwrap (); 445assert! ( 446block_on ( store. remote_source ( & name)) 447. unwrap_err () 448. to_string () 449. contains ( "integrity mismatch" ) 450); 451} 452 453# [ test ] 454fn missing_unpinned_blobs_are_reacquired_without_enforcing_stale_metadata () { 455let registry =Registry :: new (); 456let http =Http :: new (); 457let dir = tempfile:: tempdir (). unwrap (); 458let options = registry. options ( dir. path ()); 459let name = http. name ( "mod.ts" ); 460 http. source ( "mod.ts" , "export const value = 1;" ); 461let store =PackageStore :: new ( fixture:: runtime_handle (), options. clone ()). unwrap (); 462block_on ( store. remote_source ( & name)). unwrap (); 463 std:: fs:: remove_dir_all ( options. cache . join ( "http/blobs" )). unwrap (); 464 http. source ( "mod.ts" , "export const value = 2;" ); 465let store =PackageStore :: new ( fixture:: runtime_handle (), options). unwrap (); 466assert_eq! ( 467block_on ( store. remote_source ( & name)). unwrap (), 468"export const value = 2;" 469); 470}