commands.rs
1 use std::path::Path; 2 use std::str::FromStr; 3 use std::{env, thread, time}; 4 5 use radicle::git; 6 use radicle::node; 7 use radicle::node::address::Store as _; 8 use radicle::node::routing::Store as _; 9 use radicle::node::Handle as _; 10 use radicle::node::{Alias, DEFAULT_TIMEOUT}; 11 use radicle::prelude::Id; 12 use radicle::profile::Home; 13 use radicle::storage::{ReadRepository, ReadStorage}; 14 use radicle::test::fixtures; 15 16 use radicle_cli_test::TestFormula; 17 use radicle_node::service::tracking::{Policy, Scope}; 18 use radicle_node::service::Event; 19 use radicle_node::test::environment::{Config, Environment}; 20 #[allow(unused_imports)] 21 use radicle_node::test::logger; 22 23 /// Seed used in tests. 24 const RAD_SEED: &str = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; 25 26 /// Run a CLI test file. 27 fn test<'a>( 28 test: impl AsRef<Path>, 29 cwd: impl AsRef<Path>, 30 home: Option<&Home>, 31 envs: impl IntoIterator<Item = (&'a str, &'a str)>, 32 ) -> Result<(), Box<dyn std::error::Error>> { 33 let tmp = tempfile::tempdir().unwrap(); 34 let home = if let Some(home) = home { 35 home.path().to_path_buf() 36 } else { 37 tmp.path().to_path_buf() 38 }; 39 40 formula(cwd.as_ref(), test)? 41 .env("RAD_HOME", home.to_string_lossy()) 42 .envs(envs) 43 .run()?; 44 45 Ok(()) 46 } 47 48 fn formula(root: &Path, test: impl AsRef<Path>) -> Result<TestFormula, Box<dyn std::error::Error>> { 49 let mut formula = TestFormula::new(root.to_path_buf()); 50 let base = Path::new(env!("CARGO_MANIFEST_DIR")); 51 52 formula 53 .env("GIT_AUTHOR_DATE", "1671125284") 54 .env("GIT_AUTHOR_EMAIL", "radicle@localhost") 55 .env("GIT_AUTHOR_NAME", "radicle") 56 .env("GIT_COMMITTER_DATE", "1671125284") 57 .env("GIT_COMMITTER_EMAIL", "radicle@localhost") 58 .env("GIT_COMMITTER_NAME", "radicle") 59 .env("RAD_PASSPHRASE", "radicle") 60 .env("RAD_SEED", RAD_SEED) 61 .env("RAD_RNG_SEED", "0") 62 .env("EDITOR", "true") 63 .env("TZ", "UTC") 64 .env("LANG", "C") 65 .env("USER", "alice") 66 .env(radicle_cob::git::RAD_COMMIT_TIME, "1671125284") 67 .envs(git::env::GIT_DEFAULT_CONFIG) 68 .build(&[ 69 ("radicle-remote-helper", "git-remote-rad"), 70 ("radicle-cli", "rad"), 71 ]) 72 .file(base.join(test))?; 73 74 Ok(formula) 75 } 76 77 #[test] 78 fn rad_auth() { 79 test("examples/rad-auth.md", Path::new("."), None, []).unwrap(); 80 } 81 82 #[test] 83 fn rad_issue() { 84 let mut environment = Environment::new(); 85 let profile = environment.profile("alice"); 86 let home = &profile.home; 87 let working = environment.tmp().join("working"); 88 89 // Setup a test repository. 90 fixtures::repository(&working); 91 92 test("examples/rad-init.md", &working, Some(home), []).unwrap(); 93 test("examples/rad-issue.md", &working, Some(home), []).unwrap(); 94 } 95 96 #[test] 97 fn rad_cob() { 98 let mut environment = Environment::new(); 99 let profile = environment.profile("alice"); 100 let home = &profile.home; 101 let working = environment.tmp().join("working"); 102 103 // Setup a test repository. 104 fixtures::repository(&working); 105 106 test("examples/rad-init.md", &working, Some(home), []).unwrap(); 107 test("examples/rad-cob.md", &working, Some(home), []).unwrap(); 108 } 109 110 #[test] 111 fn rad_label() { 112 let mut environment = Environment::new(); 113 let profile = environment.profile("alice"); 114 let home = &profile.home; 115 let working = environment.tmp().join("working"); 116 117 // Setup a test repository. 118 fixtures::repository(&working); 119 120 test("examples/rad-init.md", &working, Some(home), []).unwrap(); 121 test("examples/rad-issue.md", &working, Some(home), []).unwrap(); 122 test("examples/rad-label.md", &working, Some(home), []).unwrap(); 123 } 124 125 #[test] 126 fn rad_init() { 127 let mut environment = Environment::new(); 128 let profile = environment.profile("alice"); 129 let working = tempfile::tempdir().unwrap(); 130 131 // Setup a test repository. 132 fixtures::repository(working.path()); 133 134 test( 135 "examples/rad-init.md", 136 working.path(), 137 Some(&profile.home), 138 [], 139 ) 140 .unwrap(); 141 } 142 143 #[test] 144 fn rad_init_no_git() { 145 let mut environment = Environment::new(); 146 let profile = environment.profile("alice"); 147 let working = tempfile::tempdir().unwrap(); 148 149 test( 150 "examples/rad-init-no-git.md", 151 working.path(), 152 Some(&profile.home), 153 [], 154 ) 155 .unwrap(); 156 } 157 158 #[test] 159 fn rad_inspect() { 160 let mut environment = Environment::new(); 161 let profile = environment.profile("alice"); 162 let working = tempfile::tempdir().unwrap(); 163 164 // Setup a test repository. 165 fixtures::repository(working.path()); 166 167 test( 168 "examples/rad-init.md", 169 working.path(), 170 Some(&profile.home), 171 [], 172 ) 173 .unwrap(); 174 175 test( 176 "examples/rad-inspect.md", 177 working.path(), 178 Some(&profile.home), 179 [], 180 ) 181 .unwrap(); 182 183 test("examples/rad-inspect-noauth.md", working.path(), None, []).unwrap(); 184 } 185 186 #[test] 187 fn rad_checkout() { 188 let mut environment = Environment::new(); 189 let profile = environment.profile("alice"); 190 let working = tempfile::tempdir().unwrap(); 191 let copy = tempfile::tempdir().unwrap(); 192 193 // Setup a test repository. 194 fixtures::repository(working.path()); 195 196 test( 197 "examples/rad-init.md", 198 working.path(), 199 Some(&profile.home), 200 [], 201 ) 202 .unwrap(); 203 204 test( 205 "examples/rad-checkout.md", 206 copy.path(), 207 Some(&profile.home), 208 [], 209 ) 210 .unwrap(); 211 212 if cfg!(target_os = "linux") { 213 test( 214 "examples/rad-checkout-repo-config-linux.md", 215 copy.path(), 216 Some(&profile.home), 217 [], 218 ) 219 .unwrap(); 220 } else if cfg!(target_os = "macos") { 221 test( 222 "examples/rad-checkout-repo-config-macos.md", 223 copy.path(), 224 Some(&profile.home), 225 [], 226 ) 227 .unwrap(); 228 } 229 } 230 231 #[test] 232 fn rad_id() { 233 let mut environment = Environment::new(); 234 let profile = environment.profile("alice"); 235 let working = tempfile::tempdir().unwrap(); 236 let home = &profile.home; 237 238 // Setup a test repository. 239 fixtures::repository(working.path()); 240 241 test("examples/rad-init.md", working.path(), Some(home), []).unwrap(); 242 test("examples/rad-id.md", working.path(), Some(home), []).unwrap(); 243 } 244 245 #[test] 246 fn rad_id_multi_delegate() { 247 let mut environment = Environment::new(); 248 let alice = environment.node(Config::test(Alias::new("alice"))); 249 let bob = environment.node(Config::test(Alias::new("bob"))); 250 let working = tempfile::tempdir().unwrap(); 251 let working = working.path(); 252 let acme = Id::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap(); 253 254 // Setup a test repository. 255 fixtures::repository(working.join("alice")); 256 257 test( 258 "examples/rad-init.md", 259 working.join("alice"), 260 Some(&alice.home), 261 [], 262 ) 263 .unwrap(); 264 265 let mut alice = alice.spawn(); 266 let mut bob = bob.spawn(); 267 268 bob.handle.track_repo(acme, Scope::All).unwrap(); 269 alice.connect(&bob).converge([&bob]); 270 271 test( 272 "examples/rad-clone.md", 273 working.join("bob"), 274 Some(&bob.home), 275 [], 276 ) 277 .unwrap(); 278 279 // TODO: Have formula with two connected nodes and a tracked project. 280 281 formula(&environment.tmp(), "examples/rad-id-multi-delegate.md") 282 .unwrap() 283 .home( 284 "alice", 285 working.join("alice"), 286 [("RAD_HOME", alice.home.path().display())], 287 ) 288 .home( 289 "bob", 290 working.join("bob"), 291 [("RAD_HOME", bob.home.path().display())], 292 ) 293 .run() 294 .unwrap(); 295 } 296 297 #[test] 298 fn rad_id_conflict() { 299 let mut environment = Environment::new(); 300 let alice = environment.node(Config::test(Alias::new("alice"))); 301 let bob = environment.node(Config::test(Alias::new("bob"))); 302 let working = tempfile::tempdir().unwrap(); 303 let working = working.path(); 304 let acme = Id::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap(); 305 306 // Setup a test repository. 307 fixtures::repository(working.join("alice")); 308 309 test( 310 "examples/rad-init.md", 311 working.join("alice"), 312 Some(&alice.home), 313 [], 314 ) 315 .unwrap(); 316 317 let mut alice = alice.spawn(); 318 let mut bob = bob.spawn(); 319 320 bob.handle.track_repo(acme, Scope::All).unwrap(); 321 alice.connect(&bob).converge([&bob]); 322 323 test( 324 "examples/rad-clone.md", 325 working.join("bob"), 326 Some(&bob.home), 327 [], 328 ) 329 .unwrap(); 330 331 formula(&environment.tmp(), "examples/rad-id-conflict.md") 332 .unwrap() 333 .home( 334 "alice", 335 working.join("alice"), 336 [("RAD_HOME", alice.home.path().display())], 337 ) 338 .home( 339 "bob", 340 working.join("bob"), 341 [("RAD_HOME", bob.home.path().display())], 342 ) 343 .run() 344 .unwrap(); 345 } 346 347 #[test] 348 fn rad_node_connect() { 349 let mut environment = Environment::new(); 350 let alice = environment.node(Config::test(Alias::new("alice"))); 351 let bob = environment.node(Config::test(Alias::new("bob"))); 352 let working = tempfile::tempdir().unwrap(); 353 let alice = alice.spawn(); 354 let bob = bob.spawn(); 355 356 alice 357 .rad( 358 "node", 359 &["connect", format!("{}@{}", bob.id, bob.addr).as_str()], 360 working.path(), 361 ) 362 .unwrap(); 363 364 let sessions = alice.handle.sessions().unwrap(); 365 let session = sessions.first().unwrap(); 366 367 assert_eq!(session.nid, bob.id); 368 assert_eq!(session.addr, bob.addr.into()); 369 assert!(session.state.is_connected()); 370 } 371 372 #[test] 373 fn rad_node() { 374 let mut environment = Environment::new(); 375 let alice = environment.node(Config::test(Alias::new("alice"))); 376 let working = tempfile::tempdir().unwrap(); 377 let alice = alice.spawn(); 378 379 fixtures::repository(working.path().join("alice")); 380 381 test( 382 "examples/rad-init-sync.md", 383 &working.path().join("alice"), 384 Some(&alice.home), 385 [], 386 ) 387 .unwrap(); 388 389 test( 390 "examples/rad-node.md", 391 working.path().join("alice"), 392 Some(&alice.home), 393 [], 394 ) 395 .unwrap(); 396 } 397 398 #[test] 399 fn rad_patch() { 400 let mut environment = Environment::new(); 401 let profile = environment.profile("alice"); 402 let working = tempfile::tempdir().unwrap(); 403 let home = &profile.home; 404 405 // Setup a test repository. 406 fixtures::repository(working.path()); 407 408 test("examples/rad-init.md", working.path(), Some(home), []).unwrap(); 409 test("examples/rad-issue.md", working.path(), Some(home), []).unwrap(); 410 test("examples/rad-patch.md", working.path(), Some(home), []).unwrap(); 411 } 412 413 #[test] 414 fn rad_patch_checkout() { 415 let mut environment = Environment::new(); 416 let profile = environment.profile("alice"); 417 let working = tempfile::tempdir().unwrap(); 418 let home = &profile.home; 419 420 // Setup a test repository. 421 fixtures::repository(working.path()); 422 423 test("examples/rad-init.md", working.path(), Some(home), []).unwrap(); 424 test( 425 "examples/rad-patch-checkout.md", 426 working.path(), 427 Some(home), 428 [], 429 ) 430 .unwrap(); 431 } 432 433 #[test] 434 fn rad_patch_update() { 435 let mut environment = Environment::new(); 436 let profile = environment.profile("alice"); 437 let working = tempfile::tempdir().unwrap(); 438 let home = &profile.home; 439 440 // Setup a test repository. 441 fixtures::repository(working.path()); 442 443 test("examples/rad-init.md", working.path(), Some(home), []).unwrap(); 444 test( 445 "examples/rad-patch-update.md", 446 working.path(), 447 Some(home), 448 [], 449 ) 450 .unwrap(); 451 } 452 453 #[test] 454 #[cfg(not(target_os = "macos"))] 455 fn rad_patch_ahead_behind() { 456 use std::fs; 457 458 let mut environment = Environment::new(); 459 let profile = environment.profile("alice"); 460 let working = tempfile::tempdir().unwrap(); 461 let home = &profile.home; 462 463 // Setup a test repository. 464 fixtures::repository(working.path()); 465 466 fs::write(working.path().join("CONTRIBUTORS"), "Alice Jones\n").unwrap(); 467 468 test("examples/rad-init.md", working.path(), Some(home), []).unwrap(); 469 test( 470 "examples/rad-patch-ahead-behind.md", 471 working.path(), 472 Some(home), 473 [], 474 ) 475 .unwrap(); 476 } 477 478 #[test] 479 fn rad_patch_draft() { 480 let mut environment = Environment::new(); 481 let profile = environment.profile("alice"); 482 let working = tempfile::tempdir().unwrap(); 483 let home = &profile.home; 484 485 // Setup a test repository. 486 fixtures::repository(working.path()); 487 488 test("examples/rad-init.md", working.path(), Some(home), []).unwrap(); 489 test( 490 "examples/rad-patch-draft.md", 491 working.path(), 492 Some(home), 493 [], 494 ) 495 .unwrap(); 496 } 497 498 #[test] 499 fn rad_patch_via_push() { 500 let mut environment = Environment::new(); 501 let profile = environment.profile("alice"); 502 let working = tempfile::tempdir().unwrap(); 503 let home = &profile.home; 504 505 // Setup a test repository. 506 fixtures::repository(working.path()); 507 508 test("examples/rad-init.md", working.path(), Some(home), []).unwrap(); 509 test( 510 "examples/rad-patch-via-push.md", 511 working.path(), 512 Some(home), 513 [], 514 ) 515 .unwrap(); 516 } 517 518 #[test] 519 #[cfg(not(target_os = "macos"))] 520 fn rad_review_by_hunk() { 521 let mut environment = Environment::new(); 522 let profile = environment.profile("alice"); 523 let working = tempfile::tempdir().unwrap(); 524 let home = &profile.home; 525 526 // Setup a test repository. 527 fixtures::repository(working.path()); 528 529 test("examples/rad-init.md", working.path(), Some(home), []).unwrap(); 530 test( 531 "examples/rad-review-by-hunk.md", 532 working.path(), 533 Some(home), 534 [], 535 ) 536 .unwrap(); 537 } 538 539 #[test] 540 fn rad_rm() { 541 let mut environment = Environment::new(); 542 let profile = environment.profile("alice"); 543 let working = tempfile::tempdir().unwrap(); 544 let home = &profile.home; 545 546 // Setup a test repository. 547 fixtures::repository(working.path()); 548 549 test("examples/rad-init.md", working.path(), Some(home), []).unwrap(); 550 test("examples/rad-rm.md", working.path(), Some(home), []).unwrap(); 551 } 552 553 #[test] 554 fn rad_track() { 555 let mut environment = Environment::new(); 556 let alice = environment.node(Config::test(Alias::new("alice"))); 557 let working = tempfile::tempdir().unwrap(); 558 let alice = alice.spawn(); 559 560 test( 561 "examples/rad-track.md", 562 working.path(), 563 Some(&alice.home), 564 [], 565 ) 566 .unwrap(); 567 } 568 569 #[test] 570 fn rad_clone() { 571 let mut environment = Environment::new(); 572 let mut alice = environment.node(Config::test(Alias::new("alice"))); 573 let bob = environment.node(Config::test(Alias::new("bob"))); 574 let working = environment.tmp().join("working"); 575 576 // Setup a test project. 577 let acme = alice.project("heartwood", "Radicle Heartwood Protocol & Stack"); 578 579 let mut alice = alice.spawn(); 580 let mut bob = bob.spawn(); 581 // Prevent Alice from fetching Bob's fork, as we're not testing that and it may cause errors. 582 alice.handle.track_repo(acme, Scope::Trusted).unwrap(); 583 584 bob.connect(&alice).converge([&alice]); 585 586 test("examples/rad-clone.md", working, Some(&bob.home), []).unwrap(); 587 } 588 589 #[test] 590 fn rad_clone_all() { 591 let mut environment = Environment::new(); 592 let mut alice = environment.node(Config::test(Alias::new("alice"))); 593 let bob = environment.node(Config::test(Alias::new("bob"))); 594 let eve = environment.node(Config::test(Alias::new("eve"))); 595 let working = environment.tmp().join("working"); 596 597 // Setup a test project. 598 let acme = alice.project("heartwood", "Radicle Heartwood Protocol & Stack"); 599 600 let mut alice = alice.spawn(); 601 let mut bob = bob.spawn(); 602 let mut eve = eve.spawn(); 603 604 alice.handle.track_repo(acme, Scope::All).unwrap(); 605 bob.connect(&alice).converge([&alice]); 606 eve.connect(&alice).converge([&alice]); 607 608 test( 609 "examples/rad-clone.md", 610 working.join("bob"), 611 Some(&bob.home), 612 [], 613 ) 614 .unwrap(); 615 bob.has_inventory_of(&acme, &alice.id); 616 alice.has_inventory_of(&acme, &bob.id); 617 618 test( 619 "examples/rad-clone-all.md", 620 working.join("eve"), 621 Some(&eve.home), 622 [], 623 ) 624 .unwrap(); 625 eve.has_inventory_of(&acme, &bob.id); 626 } 627 628 #[test] 629 fn rad_clone_connect() { 630 let mut environment = Environment::new(); 631 let working = environment.tmp().join("working"); 632 let alice = environment.node(Config::test(Alias::new("alice"))); 633 let bob = environment.node(Config::test(Alias::new("bob"))); 634 let mut eve = environment.node(Config::test(Alias::new("eve"))); 635 let acme = Id::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap(); 636 let now = localtime::LocalTime::now().as_secs(); 637 638 fixtures::repository(working.join("acme")); 639 640 test( 641 "examples/rad-init.md", 642 working.join("acme"), 643 Some(&alice.home), 644 [], 645 ) 646 .unwrap(); 647 648 let mut alice = alice.spawn(); 649 let mut bob = bob.spawn(); 650 651 // Let Eve know about Alice and Bob having the repo. 652 eve.routing.insert([&acme], alice.id, now).unwrap(); 653 eve.routing.insert([&acme], bob.id, now).unwrap(); 654 eve.addresses 655 .insert( 656 &alice.id, 657 node::Features::SEED, 658 Alias::new("alice"), 659 0, 660 now, 661 [node::KnownAddress::new( 662 node::Address::from(alice.addr), 663 node::address::Source::Imported, 664 )], 665 ) 666 .unwrap(); 667 eve.addresses 668 .insert( 669 &bob.id, 670 node::Features::SEED, 671 Alias::new("bob"), 672 0, 673 now, 674 [node::KnownAddress::new( 675 node::Address::from(bob.addr), 676 node::address::Source::Imported, 677 )], 678 ) 679 .unwrap(); 680 eve.config.peers = node::config::PeerConfig::Static; 681 682 let eve = eve.spawn(); 683 684 alice.handle.track_repo(acme, Scope::Trusted).unwrap(); 685 bob.handle.track_repo(acme, Scope::Trusted).unwrap(); 686 alice.connect(&bob); 687 bob.routes_to(&[(acme, alice.id)]); 688 eve.routes_to(&[(acme, alice.id), (acme, bob.id)]); 689 alice.routes_to(&[(acme, alice.id), (acme, bob.id)]); 690 691 test( 692 "examples/rad-clone-connect.md", 693 working.join("acme"), 694 Some(&eve.home), 695 [], 696 ) 697 .unwrap(); 698 } 699 700 #[test] 701 fn rad_sync_without_node() { 702 let mut environment = Environment::new(); 703 let alice = environment.node(Config::test(Alias::new("alice"))); 704 let bob = environment.node(Config::test(Alias::new("bob"))); 705 let mut eve = environment.node(Config::test(Alias::new("eve"))); 706 707 let rid = Id::from_urn("rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5").unwrap(); 708 eve.tracking.track_repo(&rid, Scope::All).unwrap(); 709 710 formula(&environment.tmp(), "examples/rad-sync-without-node.md") 711 .unwrap() 712 .home( 713 "alice", 714 alice.home.path(), 715 [("RAD_HOME", alice.home.path().display())], 716 ) 717 .home( 718 "bob", 719 bob.home.path(), 720 [("RAD_HOME", bob.home.path().display())], 721 ) 722 .home( 723 "eve", 724 eve.home.path(), 725 [("RAD_HOME", eve.home.path().display())], 726 ) 727 .run() 728 .unwrap(); 729 } 730 731 #[test] 732 fn rad_self() { 733 let mut environment = Environment::new(); 734 let alice = environment.node(Config::test(Alias::new("alice"))); 735 let working = environment.tmp().join("working"); 736 737 test("examples/rad-self.md", working, Some(&alice.home), []).unwrap(); 738 } 739 740 #[test] 741 fn rad_clone_unknown() { 742 let mut environment = Environment::new(); 743 let alice = environment.node(Config::test(Alias::new("alice"))); 744 let working = environment.tmp().join("working"); 745 746 let alice = alice.spawn(); 747 748 test( 749 "examples/rad-clone-unknown.md", 750 working, 751 Some(&alice.home), 752 [], 753 ) 754 .unwrap(); 755 } 756 757 #[test] 758 fn rad_init_sync_and_clone() { 759 let mut environment = Environment::new(); 760 let alice = environment.node(Config::test(Alias::new("alice"))); 761 let bob = environment.node(Config::test(Alias::new("bob"))); 762 let working = environment.tmp().join("working"); 763 764 let alice = alice.spawn(); 765 let mut bob = bob.spawn(); 766 767 bob.connect(&alice); 768 769 fixtures::repository(working.join("alice")); 770 771 // Necessary for now, if we don't want the new inventry announcement to be considered stale 772 // for Bob. 773 // TODO: Find a way to advance internal clocks instead. 774 thread::sleep(time::Duration::from_millis(3)); 775 776 // Alice initializes a repo after her node has started, and after bob has connected to it. 777 test( 778 "examples/rad-init-sync.md", 779 &working.join("alice"), 780 Some(&alice.home), 781 [], 782 ) 783 .unwrap(); 784 785 // Wait for bob to get any updates to the routing table. 786 bob.converge([&alice]); 787 788 test( 789 "examples/rad-clone.md", 790 working.join("bob"), 791 Some(&bob.home), 792 [], 793 ) 794 .unwrap(); 795 } 796 797 #[test] 798 fn rad_fetch() { 799 let mut environment = Environment::new(); 800 let working = environment.tmp().join("working"); 801 let alice = environment.node(Config::test(Alias::new("alice"))); 802 let bob = environment.node(Config::test(Alias::new("bob"))); 803 804 let mut alice = alice.spawn(); 805 let bob = bob.spawn(); 806 807 alice.connect(&bob); 808 fixtures::repository(working.join("alice")); 809 810 // Alice initializes a repo after her node has started, and after bob has connected to it. 811 test( 812 "examples/rad-init-sync.md", 813 &working.join("alice"), 814 Some(&alice.home), 815 [], 816 ) 817 .unwrap(); 818 819 // Wait for bob to get any updates to the routing table. 820 bob.converge([&alice]); 821 822 test( 823 "examples/rad-fetch.md", 824 working.join("bob"), 825 Some(&bob.home), 826 [], 827 ) 828 .unwrap(); 829 } 830 831 #[test] 832 fn rad_fork() { 833 let mut environment = Environment::new(); 834 let working = environment.tmp().join("working"); 835 let alice = environment.node(Config::test(Alias::new("alice"))); 836 let bob = environment.node(Config::test(Alias::new("bob"))); 837 838 let mut alice = alice.spawn(); 839 let bob = bob.spawn(); 840 841 alice.connect(&bob); 842 fixtures::repository(working.join("alice")); 843 844 // Alice initializes a repo after her node has started, and after bob has connected to it. 845 test( 846 "examples/rad-init-sync.md", 847 &working.join("alice"), 848 Some(&alice.home), 849 [], 850 ) 851 .unwrap(); 852 853 // Wait for bob to get any updates to the routing table. 854 bob.converge([&alice]); 855 856 test( 857 "examples/rad-fetch.md", 858 working.join("bob"), 859 Some(&bob.home), 860 [], 861 ) 862 .unwrap(); 863 test( 864 "examples/rad-fork.md", 865 working.join("bob"), 866 Some(&bob.home), 867 [], 868 ) 869 .unwrap(); 870 } 871 872 #[test] 873 // User tries to clone; no seeds are available, but user has the repo locally. 874 fn test_clone_without_seeds() { 875 let mut environment = Environment::new(); 876 let mut alice = environment.node(Config::test(Alias::new("alice"))); 877 let working = environment.tmp().join("working"); 878 let rid = alice.project("heartwood", "Radicle Heartwood Protocol & Stack"); 879 let mut alice = alice.spawn(); 880 let seeds = alice.handle.seeds(rid).unwrap(); 881 let connected = seeds.connected().collect::<Vec<_>>(); 882 883 assert!(connected.is_empty()); 884 885 alice 886 .rad("clone", &[rid.to_string().as_str()], working.as_path()) 887 .unwrap(); 888 889 alice 890 .rad("inspect", &[], working.join("heartwood").as_path()) 891 .unwrap(); 892 } 893 894 #[test] 895 fn test_cob_replication() { 896 let mut environment = Environment::new(); 897 let working = tempfile::tempdir().unwrap(); 898 let mut alice = environment.node(Config::test(Alias::new("alice"))); 899 let bob = environment.node(Config::test(Alias::new("bob"))); 900 901 let rid = alice.project("heartwood", ""); 902 903 let mut alice = alice.spawn(); 904 let mut bob = bob.spawn(); 905 let events = alice.handle.events(); 906 907 alice.handle.track_node(bob.id, None).unwrap(); 908 alice.connect(&bob); 909 910 bob.routes_to(&[(rid, alice.id)]); 911 bob.rad("clone", &[rid.to_string().as_str()], working.path()) 912 .unwrap(); 913 914 // Wait for Alice to fetch the clone refs. 915 events 916 .wait( 917 |e| matches!(e, Event::RefsFetched { .. }).then_some(()), 918 time::Duration::from_secs(6), 919 ) 920 .unwrap(); 921 922 let bob_repo = bob.storage.repository(rid).unwrap(); 923 let mut bob_issues = radicle::cob::issue::Issues::open(&bob_repo).unwrap(); 924 let issue = bob_issues 925 .create( 926 "Something's fishy", 927 "I don't know what it is", 928 &[], 929 &[], 930 [], 931 &bob.signer, 932 ) 933 .unwrap(); 934 log::debug!(target: "test", "Issue {} created", issue.id()); 935 936 // Make sure that Bob's issue refs announcement has a different timestamp than his fork's 937 // announcement, otherwise Alice will consider it stale. 938 thread::sleep(time::Duration::from_millis(3)); 939 940 bob.handle.announce_refs(rid).unwrap(); 941 942 // Wait for Alice to fetch the issue refs. 943 events 944 .iter() 945 .find(|e| matches!(e, Event::RefsFetched { .. })) 946 .unwrap(); 947 948 let alice_repo = alice.storage.repository(rid).unwrap(); 949 let alice_issues = radicle::cob::issue::Issues::open(&alice_repo).unwrap(); 950 let alice_issue = alice_issues.get(issue.id()).unwrap().unwrap(); 951 952 assert_eq!(alice_issue.title(), "Something's fishy"); 953 } 954 955 #[test] 956 fn test_cob_deletion() { 957 let mut environment = Environment::new(); 958 let working = tempfile::tempdir().unwrap(); 959 let mut alice = environment.node(Config::test(Alias::new("alice"))); 960 let bob = environment.node(Config::test(Alias::new("bob"))); 961 962 let rid = alice.project("heartwood", ""); 963 964 let mut alice = alice.spawn(); 965 let mut bob = bob.spawn(); 966 967 alice.handle.track_repo(rid, Scope::All).unwrap(); 968 bob.handle.track_repo(rid, Scope::All).unwrap(); 969 alice.connect(&bob); 970 bob.routes_to(&[(rid, alice.id)]); 971 972 let alice_repo = alice.storage.repository(rid).unwrap(); 973 let mut alice_issues = radicle::cob::issue::Issues::open(&alice_repo).unwrap(); 974 let issue = alice_issues 975 .create( 976 "Something's fishy", 977 "I don't know what it is", 978 &[], 979 &[], 980 [], 981 &alice.signer, 982 ) 983 .unwrap(); 984 let issue_id = issue.id(); 985 log::debug!(target: "test", "Issue {} created", issue_id); 986 987 bob.rad("clone", &[rid.to_string().as_str()], working.path()) 988 .unwrap(); 989 990 let bob_repo = bob.storage.repository(rid).unwrap(); 991 let bob_issues = radicle::cob::issue::Issues::open(&bob_repo).unwrap(); 992 assert!(bob_issues.get(issue_id).unwrap().is_some()); 993 994 let alice_issues = radicle::cob::issue::Issues::open(&alice_repo).unwrap(); 995 alice_issues.remove(issue_id, &alice.signer).unwrap(); 996 997 log::debug!(target: "test", "Removing issue.."); 998 999 radicle::assert_matches!( 1000 bob.handle.fetch(rid, alice.id, DEFAULT_TIMEOUT).unwrap(), 1001 radicle::node::FetchResult::Success { .. } 1002 ); 1003 let bob_repo = bob.storage.repository(rid).unwrap(); 1004 let bob_issues = radicle::cob::issue::Issues::open(&bob_repo).unwrap(); 1005 assert!(bob_issues.get(issue_id).unwrap().is_none()); 1006 } 1007 1008 #[test] 1009 fn rad_sync() { 1010 let mut environment = Environment::new(); 1011 let working = environment.tmp().join("working"); 1012 let alice = environment.node(Config::test(Alias::new("alice"))); 1013 let bob = environment.node(Config::test(Alias::new("bob"))); 1014 let eve = environment.node(Config::test(Alias::new("eve"))); 1015 let acme = Id::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap(); 1016 1017 fixtures::repository(working.join("acme")); 1018 1019 test( 1020 "examples/rad-init.md", 1021 working.join("acme"), 1022 Some(&alice.home), 1023 [], 1024 ) 1025 .unwrap(); 1026 1027 let mut alice = alice.spawn(); 1028 let mut bob = bob.spawn(); 1029 let mut eve = eve.spawn(); 1030 1031 bob.handle.track_repo(acme, Scope::All).unwrap(); 1032 eve.handle.track_repo(acme, Scope::All).unwrap(); 1033 1034 alice.connect(&bob); 1035 eve.connect(&alice); 1036 1037 bob.routes_to(&[(acme, alice.id)]); 1038 eve.routes_to(&[(acme, alice.id)]); 1039 alice.routes_to(&[(acme, alice.id), (acme, eve.id), (acme, bob.id)]); 1040 1041 test( 1042 "examples/rad-sync.md", 1043 working.join("acme"), 1044 Some(&alice.home), 1045 [], 1046 ) 1047 .unwrap(); 1048 } 1049 1050 #[test] 1051 // 1052 // alice -- seed -- bob 1053 // 1054 fn test_replication_via_seed() { 1055 let mut environment = Environment::new(); 1056 let alice = environment.node(Config::test(Alias::new("alice"))); 1057 let bob = environment.node(Config::test(Alias::new("bob"))); 1058 let seed = environment.node(Config { 1059 policy: Policy::Track, 1060 scope: Scope::All, 1061 ..Config::test(Alias::new("seed")) 1062 }); 1063 let working = environment.tmp().join("working"); 1064 let rid = Id::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap(); 1065 1066 let mut alice = alice.spawn(); 1067 let mut bob = bob.spawn(); 1068 let seed = seed.spawn(); 1069 1070 alice.connect(&seed); 1071 bob.connect(&seed); 1072 1073 // Enough time for the next inventory from Seed to not be considered stale by Bob. 1074 thread::sleep(time::Duration::from_millis(3)); 1075 1076 alice.routes_to(&[]); 1077 seed.routes_to(&[]); 1078 bob.routes_to(&[]); 1079 1080 // Initialize a repo as Alice. 1081 fixtures::repository(working.join("alice")); 1082 alice 1083 .rad( 1084 "init", 1085 &[ 1086 "--name", 1087 "heartwood", 1088 "--description", 1089 "Radicle Heartwood Protocol & Stack", 1090 "--default-branch", 1091 "master", 1092 "--public", 1093 ], 1094 working.join("alice"), 1095 ) 1096 .unwrap(); 1097 1098 alice 1099 .rad("track", &[&bob.id.to_human()], working.join("alice")) 1100 .unwrap(); 1101 1102 alice.routes_to(&[(rid, alice.id), (rid, seed.id)]); 1103 seed.routes_to(&[(rid, alice.id), (rid, seed.id)]); 1104 bob.routes_to(&[(rid, alice.id), (rid, seed.id)]); 1105 1106 let seed_events = seed.handle.events(); 1107 let alice_events = alice.handle.events(); 1108 1109 bob.rad("clone", &[rid.to_string().as_str()], working.join("bob")) 1110 .unwrap(); 1111 1112 alice.routes_to(&[(rid, alice.id), (rid, seed.id), (rid, bob.id)]); 1113 seed.routes_to(&[(rid, alice.id), (rid, seed.id), (rid, bob.id)]); 1114 bob.routes_to(&[(rid, alice.id), (rid, seed.id), (rid, bob.id)]); 1115 1116 seed_events 1117 .iter() 1118 .any(|e| matches!(e, Event::RefsFetched { remote, .. } if remote == bob.id)); 1119 alice_events 1120 .iter() 1121 .any(|e| matches!(e, Event::RefsFetched { remote, .. } if remote == seed.id)); 1122 1123 seed.storage 1124 .repository(rid) 1125 .unwrap() 1126 .remote(&bob.id) 1127 .unwrap(); 1128 1129 // Seed should send Bob's ref announcement to Alice, after the fetch. 1130 alice 1131 .storage 1132 .repository(rid) 1133 .unwrap() 1134 .remote(&bob.id) 1135 .unwrap(); 1136 } 1137 1138 #[test] 1139 fn rad_remote() { 1140 let mut environment = Environment::new(); 1141 let alice = environment.node(Config::test(Alias::new("alice"))); 1142 let bob = environment.node(Config::test(Alias::new("bob"))); 1143 let working = environment.tmp().join("working"); 1144 let home = alice.home.clone(); 1145 let rid = Id::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap(); 1146 // Setup a test repository. 1147 fixtures::repository(working.join("alice")); 1148 1149 test( 1150 "examples/rad-init.md", 1151 working.join("alice"), 1152 Some(&home), 1153 [], 1154 ) 1155 .unwrap(); 1156 1157 let mut alice = alice.spawn(); 1158 let mut bob = bob.spawn(); 1159 alice 1160 .handle 1161 .track_node(bob.id, Some(Alias::new("bob"))) 1162 .unwrap(); 1163 1164 bob.connect(&alice); 1165 bob.routes_to(&[(rid, alice.id)]); 1166 bob.rad("clone", &[rid.to_string().as_str()], &working) 1167 .unwrap(); 1168 1169 alice.has_inventory_of(&rid, &bob.id); 1170 1171 test( 1172 "examples/rad-remote.md", 1173 working.join("alice"), 1174 Some(&home), 1175 [], 1176 ) 1177 .unwrap(); 1178 } 1179 1180 #[test] 1181 fn rad_merge_via_push() { 1182 let mut environment = Environment::new(); 1183 let alice = environment.node(Config::test(Alias::new("alice"))); 1184 let working = environment.tmp().join("working"); 1185 1186 fixtures::repository(working.join("alice")); 1187 1188 test( 1189 "examples/rad-init.md", 1190 working.join("alice"), 1191 Some(&alice.home), 1192 [], 1193 ) 1194 .unwrap(); 1195 1196 let alice = alice.spawn(); 1197 1198 test( 1199 "examples/rad-merge-via-push.md", 1200 working.join("alice"), 1201 Some(&alice.home), 1202 [], 1203 ) 1204 .unwrap(); 1205 } 1206 1207 #[test] 1208 fn rad_merge_after_update() { 1209 let mut environment = Environment::new(); 1210 let alice = environment.node(Config::test(Alias::new("alice"))); 1211 let working = environment.tmp().join("working"); 1212 1213 fixtures::repository(working.join("alice")); 1214 1215 test( 1216 "examples/rad-init.md", 1217 working.join("alice"), 1218 Some(&alice.home), 1219 [], 1220 ) 1221 .unwrap(); 1222 1223 let alice = alice.spawn(); 1224 1225 test( 1226 "examples/rad-merge-after-update.md", 1227 working.join("alice"), 1228 Some(&alice.home), 1229 [], 1230 ) 1231 .unwrap(); 1232 } 1233 1234 #[test] 1235 fn rad_merge_no_ff() { 1236 let mut environment = Environment::new(); 1237 let alice = environment.node(Config::test(Alias::new("alice"))); 1238 let working = environment.tmp().join("working"); 1239 1240 fixtures::repository(working.join("alice")); 1241 1242 test( 1243 "examples/rad-init.md", 1244 working.join("alice"), 1245 Some(&alice.home), 1246 [], 1247 ) 1248 .unwrap(); 1249 1250 test( 1251 "examples/rad-merge-no-ff.md", 1252 working.join("alice"), 1253 Some(&alice.home), 1254 [], 1255 ) 1256 .unwrap(); 1257 } 1258 1259 #[test] 1260 fn rad_patch_pull_update() { 1261 let mut environment = Environment::new(); 1262 let alice = environment.node(Config::test(Alias::new("alice"))); 1263 let bob = environment.node(Config::test(Alias::new("bob"))); 1264 let working = environment.tmp().join("working"); 1265 1266 fixtures::repository(working.join("alice")); 1267 1268 let alice = alice.spawn(); 1269 let mut bob = bob.spawn(); 1270 1271 bob.connect(&alice).converge([&alice]); 1272 1273 formula(&environment.tmp(), "examples/rad-patch-pull-update.md") 1274 .unwrap() 1275 .home( 1276 "alice", 1277 working.join("alice"), 1278 [("RAD_HOME", alice.home.path().display())], 1279 ) 1280 .home( 1281 "bob", 1282 bob.home.path(), 1283 [("RAD_HOME", bob.home.path().display())], 1284 ) 1285 .run() 1286 .unwrap(); 1287 } 1288 1289 #[test] 1290 fn rad_init_private() { 1291 let mut environment = Environment::new(); 1292 let alice = environment.node(Config::test(Alias::new("alice"))); 1293 let working = environment.tmp().join("working"); 1294 1295 fixtures::repository(working.join("alice")); 1296 1297 test( 1298 "examples/rad-init-private.md", 1299 working.join("alice"), 1300 Some(&alice.home), 1301 [], 1302 ) 1303 .unwrap(); 1304 } 1305 1306 #[test] 1307 fn rad_init_private_clone() { 1308 let mut environment = Environment::new(); 1309 let alice = environment.node(Config::test(Alias::new("alice"))); 1310 let bob = environment.node(Config::test(Alias::new("bob"))); 1311 let working = environment.tmp().join("working"); 1312 1313 fixtures::repository(working.join("alice")); 1314 1315 let alice = alice.spawn(); 1316 let mut bob = bob.spawn(); 1317 1318 test( 1319 "examples/rad-init-private.md", 1320 working.join("alice"), 1321 Some(&alice.home), 1322 [], 1323 ) 1324 .unwrap(); 1325 1326 bob.connect(&alice).converge([&alice]); 1327 1328 formula(&environment.tmp(), "examples/rad-init-private-clone.md") 1329 .unwrap() 1330 .home( 1331 "alice", 1332 working.join("alice"), 1333 [("RAD_HOME", alice.home.path().display())], 1334 ) 1335 .home( 1336 "bob", 1337 bob.home.path(), 1338 [("RAD_HOME", bob.home.path().display())], 1339 ) 1340 .run() 1341 .unwrap(); 1342 } 1343 1344 #[test] 1345 fn rad_publish() { 1346 let mut environment = Environment::new(); 1347 let alice = environment.node(Config::test(Alias::new("alice"))); 1348 let working = environment.tmp().join("working"); 1349 1350 fixtures::repository(working.join("alice")); 1351 1352 test( 1353 "examples/rad-init-private.md", 1354 working.join("alice"), 1355 Some(&alice.home), 1356 [], 1357 ) 1358 .unwrap(); 1359 1360 test( 1361 "examples/rad-publish.md", 1362 working.join("alice"), 1363 Some(&alice.home), 1364 [], 1365 ) 1366 .unwrap(); 1367 } 1368 1369 #[test] 1370 fn framework_home() { 1371 let mut environment = Environment::new(); 1372 let alice = environment.node(Config::test(Alias::new("alice"))); 1373 let bob = environment.node(Config::test(Alias::new("bob"))); 1374 1375 formula(&environment.tmp(), "examples/framework/home.md") 1376 .unwrap() 1377 .home( 1378 "alice", 1379 alice.home.path(), 1380 [("RAD_HOME", alice.home.path().display())], 1381 ) 1382 .home( 1383 "bob", 1384 bob.home.path(), 1385 [("RAD_HOME", bob.home.path().display())], 1386 ) 1387 .run() 1388 .unwrap(); 1389 } 1390 1391 #[test] 1392 fn git_push_diverge() { 1393 let mut environment = Environment::new(); 1394 let alice = environment.node(Config::test(Alias::new("alice"))); 1395 let bob = environment.node(Config::test(Alias::new("bob"))); 1396 let working = environment.tmp().join("working"); 1397 1398 fixtures::repository(working.join("alice")); 1399 1400 test( 1401 "examples/rad-init.md", 1402 working.join("alice"), 1403 Some(&alice.home), 1404 [], 1405 ) 1406 .unwrap(); 1407 1408 let alice = alice.spawn(); 1409 let mut bob = bob.spawn(); 1410 1411 bob.connect(&alice).converge([&alice]); 1412 1413 test( 1414 "examples/rad-clone.md", 1415 working.join("bob"), 1416 Some(&bob.home), 1417 [], 1418 ) 1419 .unwrap(); 1420 1421 formula(&environment.tmp(), "examples/git/git-push-diverge.md") 1422 .unwrap() 1423 .home( 1424 "alice", 1425 working.join("alice"), 1426 [("RAD_HOME", alice.home.path().display())], 1427 ) 1428 .home( 1429 "bob", 1430 working.join("bob").join("heartwood"), 1431 [("RAD_HOME", bob.home.path().display())], 1432 ) 1433 .run() 1434 .unwrap(); 1435 } 1436 1437 #[test] 1438 fn git_push_and_pull() { 1439 let mut environment = Environment::new(); 1440 let alice = environment.node(Config::test(Alias::new("alice"))); 1441 let bob = environment.node(Config::test(Alias::new("bob"))); 1442 let working = environment.tmp().join("working"); 1443 1444 fixtures::repository(working.join("alice")); 1445 1446 test( 1447 "examples/rad-init.md", 1448 working.join("alice"), 1449 Some(&alice.home), 1450 [], 1451 ) 1452 .unwrap(); 1453 1454 let alice = alice.spawn(); 1455 let mut bob = bob.spawn(); 1456 1457 bob.connect(&alice).converge([&alice]); 1458 1459 test( 1460 "examples/rad-clone.md", 1461 &working.join("bob"), 1462 Some(&bob.home), 1463 [], 1464 ) 1465 .unwrap(); 1466 test( 1467 "examples/git/git-push.md", 1468 &working.join("alice"), 1469 Some(&alice.home), 1470 [], 1471 ) 1472 .unwrap(); 1473 test( 1474 "examples/git/git-pull.md", 1475 &working.join("bob"), 1476 Some(&bob.home), 1477 [], 1478 ) 1479 .unwrap(); 1480 test( 1481 "examples/git/git-push-delete.md", 1482 &working.join("alice"), 1483 Some(&alice.home), 1484 [], 1485 ) 1486 .unwrap(); 1487 } 1488 1489 #[test] 1490 fn rad_workflow() { 1491 let mut environment = Environment::new(); 1492 let alice = environment.node(Config::test(Alias::new("alice"))); 1493 let bob = environment.node(Config::test(Alias::new("bob"))); 1494 let working = environment.tmp().join("working"); 1495 1496 fixtures::repository(working.join("alice")); 1497 1498 test( 1499 "examples/workflow/1-new-project.md", 1500 &working.join("alice"), 1501 Some(&alice.home), 1502 [], 1503 ) 1504 .unwrap(); 1505 1506 let alice = alice.spawn(); 1507 let mut bob = bob.spawn(); 1508 1509 bob.connect(&alice).converge([&alice]); 1510 1511 test( 1512 "examples/workflow/2-cloning.md", 1513 &working.join("bob"), 1514 Some(&bob.home), 1515 [], 1516 ) 1517 .unwrap(); 1518 1519 test( 1520 "examples/workflow/3-issues.md", 1521 &working.join("bob").join("heartwood"), 1522 Some(&bob.home), 1523 [], 1524 ) 1525 .unwrap(); 1526 1527 test( 1528 "examples/workflow/4-patching-contributor.md", 1529 &working.join("bob").join("heartwood"), 1530 Some(&bob.home), 1531 [], 1532 ) 1533 .unwrap(); 1534 1535 test( 1536 "examples/workflow/5-patching-maintainer.md", 1537 &working.join("alice"), 1538 Some(&alice.home), 1539 [], 1540 ) 1541 .unwrap(); 1542 1543 test( 1544 "examples/workflow/6-pulling-contributor.md", 1545 &working.join("bob").join("heartwood"), 1546 Some(&bob.home), 1547 [], 1548 ) 1549 .unwrap(); 1550 }