upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/tests/ngit
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2024-09-09 09:40:06 +0100
committerDanConwayDev <DanConwayDev@protonmail.com>2024-09-09 09:40:19 +0100
commit9a9b13a11868fe58fa0390938a39483bf1f3cc9a (patch)
tree3aa80ebfaacac233f022ebc85b6c20a7793bbc7c /tests/ngit
parentd2d0eeb72912809a00f09fafdae4e827a34d0e26 (diff)
test: refactor into binary subdirs
in prep for splitting git_remote_nostr tests
Diffstat (limited to 'tests/ngit')
-rw-r--r--tests/ngit/init.rs706
-rw-r--r--tests/ngit/list.rs1549
-rw-r--r--tests/ngit/login.rs1168
-rw-r--r--tests/ngit/main.rs6
-rw-r--r--tests/ngit/pull.rs615
-rw-r--r--tests/ngit/push.rs531
-rw-r--r--tests/ngit/send.rs1901
7 files changed, 6476 insertions, 0 deletions
diff --git a/tests/ngit/init.rs b/tests/ngit/init.rs
new file mode 100644
index 0000000..c8390e3
--- /dev/null
+++ b/tests/ngit/init.rs
@@ -0,0 +1,706 @@
1use anyhow::Result;
2use nostr_sdk::Kind;
3use serial_test::serial;
4use test_utils::{git::GitTestRepo, *};
5
6fn expect_msgs_first(p: &mut CliTester) -> Result<()> {
7 p.expect("searching for profile...\r\n")?;
8 p.expect("logged in as fred\r\n")?;
9 // // p.expect("searching for existing claims on repository...\r\n")?;
10 p.expect("publishing repostory reference...\r\n")?;
11 Ok(())
12}
13
14fn expect_msgs_after(p: &mut CliTester) -> Result<()> {
15 p.expect_after_whitespace("maintainers.yaml created. commit and push.\r\n")?;
16 p.expect(
17 "this optional file helps in identifying who the maintainers are over time through the commit history\r\n",
18 )?;
19 Ok(())
20}
21
22fn get_cli_args() -> Vec<&'static str> {
23 vec![
24 "--nsec",
25 TEST_KEY_1_NSEC,
26 "--password",
27 TEST_PASSWORD,
28 "--disable-cli-spinners",
29 "init",
30 "--title",
31 "example-name",
32 "--identifier",
33 "example-identifier",
34 "--description",
35 "example-description",
36 "--web",
37 "https://exampleproject.xyz",
38 "https://gitworkshop.dev/123",
39 "--relays",
40 "ws://localhost:8055",
41 "ws://localhost:8056",
42 "--clone-url",
43 "https://git.myhosting.com/my-repo.git",
44 "--earliest-unique-commit",
45 "9ee507fc4357d7ee16a5d8901bedcd103f23c17d",
46 "--other-maintainers",
47 TEST_KEY_1_NPUB,
48 ]
49}
50
51mod when_repo_not_previously_claimed {
52 use super::*;
53
54 mod when_repo_relays_specified_as_arguments {
55 use futures::join;
56 use test_utils::relay::Relay;
57
58 use super::*;
59
60 fn prep_git_repo() -> Result<GitTestRepo> {
61 let test_repo = GitTestRepo::without_repo_in_git_config();
62 test_repo.populate()?;
63 test_repo.add_remote("origin", "https://localhost:1000")?;
64 Ok(test_repo)
65 }
66
67 fn cli_tester_init(git_repo: &GitTestRepo) -> CliTester {
68 CliTester::new_from_dir(&git_repo.dir, get_cli_args())
69 }
70
71 async fn prep_run_init() -> Result<(
72 Relay<'static>,
73 Relay<'static>,
74 Relay<'static>,
75 Relay<'static>,
76 Relay<'static>,
77 Relay<'static>,
78 )> {
79 let git_repo = prep_git_repo()?;
80 // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57)
81 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
82 Relay::new(
83 8051,
84 None,
85 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
86 relay.respond_events(
87 client_id,
88 &subscription_id,
89 &vec![
90 generate_test_key_1_metadata_event("fred"),
91 generate_test_key_1_relay_list_event(),
92 ],
93 )?;
94 Ok(())
95 }),
96 ),
97 Relay::new(8052, None, None),
98 Relay::new(8053, None, None),
99 Relay::new(8055, None, None),
100 Relay::new(8056, None, None),
101 Relay::new(8057, None, None),
102 );
103
104 // // check relay had the right number of events
105 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
106 let mut p = cli_tester_init(&git_repo);
107 p.expect_end_eventually()?;
108 for p in [51, 52, 53, 55, 56, 57] {
109 relay::shutdown_relay(8000 + p)?;
110 }
111 Ok(())
112 });
113
114 // launch relay
115 let _ = join!(
116 r51.listen_until_close(),
117 r52.listen_until_close(),
118 r53.listen_until_close(),
119 r55.listen_until_close(),
120 r56.listen_until_close(),
121 r57.listen_until_close(),
122 );
123 cli_tester_handle.join().unwrap()?;
124 Ok((r51, r52, r53, r55, r56, r57))
125 }
126
127 mod sent_to_correct_relays {
128
129 use super::*;
130
131 #[tokio::test]
132 #[serial]
133 async fn only_1_repository_kind_event_sent_to_user_relays() -> Result<()> {
134 let (_, _, r53, r55, _, _) = prep_run_init().await?;
135 for relay in [&r53, &r55] {
136 assert_eq!(
137 relay
138 .events
139 .iter()
140 .filter(|e| e.kind.eq(&Kind::GitRepoAnnouncement))
141 .count(),
142 1,
143 );
144 }
145 Ok(())
146 }
147
148 #[tokio::test]
149 #[serial]
150 async fn only_1_repository_kind_event_sent_to_specified_repo_relays() -> Result<()> {
151 let (_, _, _, r55, r56, _) = prep_run_init().await?;
152 for relay in [&r55, &r56] {
153 assert_eq!(
154 relay
155 .events
156 .iter()
157 .filter(|e| e.kind.eq(&Kind::GitRepoAnnouncement))
158 .count(),
159 1,
160 );
161 }
162 Ok(())
163 }
164
165 #[tokio::test]
166 #[serial]
167 async fn only_1_repository_kind_event_sent_to_fallback_relays() -> Result<()> {
168 let (r51, r52, _, _, _, _) = prep_run_init().await?;
169 for relay in [&r51, &r52] {
170 assert_eq!(
171 relay
172 .events
173 .iter()
174 .filter(|e| e.kind.eq(&Kind::GitRepoAnnouncement))
175 .count(),
176 1,
177 );
178 }
179 Ok(())
180 }
181
182 #[tokio::test]
183 #[serial]
184 async fn only_1_repository_kind_event_sent_to_blaster_relays() -> Result<()> {
185 let (_, _, _, _, _, r57) = prep_run_init().await?;
186 assert_eq!(
187 r57.events
188 .iter()
189 .filter(|e| e.kind.eq(&Kind::GitRepoAnnouncement))
190 .count(),
191 1,
192 );
193 Ok(())
194 }
195 }
196
197 mod yaml_file {
198 use std::{fs, io::Read};
199
200 use super::*;
201
202 async fn async_run_test() -> Result<()> {
203 let git_repo = prep_git_repo()?;
204 // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57)
205 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
206 Relay::new(
207 8051,
208 None,
209 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
210 relay.respond_events(
211 client_id,
212 &subscription_id,
213 &vec![
214 generate_test_key_1_metadata_event("fred"),
215 generate_test_key_1_relay_list_event(),
216 ],
217 )?;
218 Ok(())
219 }),
220 ),
221 Relay::new(8052, None, None),
222 Relay::new(8053, None, None),
223 Relay::new(8055, None, None),
224 Relay::new(8056, None, None),
225 Relay::new(8057, None, None),
226 );
227
228 // // check relay had the right number of events
229 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
230 let mut p = cli_tester_init(&git_repo);
231 p.expect_end_eventually()?;
232
233 let yaml_path = git_repo.dir.join("maintainers.yaml");
234
235 assert!(yaml_path.exists());
236
237 let mut file = fs::File::open(yaml_path).expect("no such file");
238 let mut file_contents = "".to_string();
239 let _ = file.read_to_string(&mut file_contents);
240
241 for p in [51, 52, 53, 55, 56, 57] {
242 relay::shutdown_relay(8000 + p)?;
243 }
244 assert_eq!(
245 file_contents,
246 format!(
247 "\
248 identifier: example-identifier\n\
249 maintainers:\n\
250 - {TEST_KEY_1_NPUB}\n\
251 relays:\n\
252 - ws://localhost:8055\n\
253 - ws://localhost:8056\n\
254 "
255 ),
256 );
257 Ok(())
258 });
259
260 // launch relay
261 let _ = join!(
262 r51.listen_until_close(),
263 r52.listen_until_close(),
264 r53.listen_until_close(),
265 r55.listen_until_close(),
266 r56.listen_until_close(),
267 r57.listen_until_close(),
268 );
269 cli_tester_handle.join().unwrap()?;
270 Ok(())
271 }
272
273 #[tokio::test]
274 #[serial]
275 async fn contains_identifier_maintainers_and_relays() -> Result<()> {
276 async_run_test().await
277 }
278 mod updates_existing_with_missing_identifier {
279 use std::io::Write;
280
281 use super::*;
282 async fn async_run_test() -> Result<()> {
283 let git_repo = prep_git_repo()?;
284 // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57)
285 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
286 Relay::new(
287 8051,
288 None,
289 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
290 relay.respond_events(
291 client_id,
292 &subscription_id,
293 &vec![
294 generate_test_key_1_metadata_event("fred"),
295 generate_test_key_1_relay_list_event(),
296 ],
297 )?;
298 Ok(())
299 }),
300 ),
301 Relay::new(8052, None, None),
302 Relay::new(8053, None, None),
303 Relay::new(8055, None, None),
304 Relay::new(8056, None, None),
305 Relay::new(8057, None, None),
306 );
307
308 // // check relay had the right number of events
309 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
310 let yaml_path = git_repo.dir.join("maintainers.yaml");
311 let mut file = std::fs::File::create(&yaml_path)
312 .expect("cannot create maintainers.yaml file");
313 write!(
314 file,
315 "\
316 maintainers:\n\
317 - {TEST_KEY_1_NPUB}\n\
318 relays:\n\
319 - ws://localhost:8055\n\
320 - ws://localhost:8056\n\
321 "
322 )?;
323
324 let mut p = cli_tester_init(&git_repo);
325 p.expect_end_eventually()?;
326
327 assert!(yaml_path.exists());
328
329 let mut file = fs::File::open(yaml_path).expect("no such file");
330 let mut file_contents = "".to_string();
331 let _ = file.read_to_string(&mut file_contents);
332
333 for p in [51, 52, 53, 55, 56, 57] {
334 relay::shutdown_relay(8000 + p)?;
335 }
336 assert_eq!(
337 file_contents,
338 format!(
339 "\
340 identifier: example-identifier\n\
341 maintainers:\n\
342 - {TEST_KEY_1_NPUB}\n\
343 relays:\n\
344 - ws://localhost:8055\n\
345 - ws://localhost:8056\n\
346 "
347 ),
348 );
349 Ok(())
350 });
351
352 // launch relay
353 let _ = join!(
354 r51.listen_until_close(),
355 r52.listen_until_close(),
356 r53.listen_until_close(),
357 r55.listen_until_close(),
358 r56.listen_until_close(),
359 r57.listen_until_close(),
360 );
361 cli_tester_handle.join().unwrap()?;
362 Ok(())
363 }
364
365 #[tokio::test]
366 #[serial]
367 async fn adds_missing_identifier() -> Result<()> {
368 async_run_test().await
369 }
370 }
371 }
372
373 mod git_config_updated {
374
375 use nostr::nips::nip01::Coordinate;
376 use nostr_sdk::ToBech32;
377
378 use super::*;
379
380 async fn async_run_test() -> Result<()> {
381 let git_repo = prep_git_repo()?;
382 // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57)
383 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
384 Relay::new(
385 8051,
386 None,
387 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
388 relay.respond_events(
389 client_id,
390 &subscription_id,
391 &vec![
392 generate_test_key_1_metadata_event("fred"),
393 generate_test_key_1_relay_list_event(),
394 ],
395 )?;
396 Ok(())
397 }),
398 ),
399 Relay::new(8052, None, None),
400 Relay::new(8053, None, None),
401 Relay::new(8055, None, None),
402 Relay::new(8056, None, None),
403 Relay::new(8057, None, None),
404 );
405
406 // // check relay had the right number of events
407 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
408 let mut p = cli_tester_init(&git_repo);
409 p.expect_end_eventually()?;
410 for p in [51, 52, 53, 55, 56, 57] {
411 relay::shutdown_relay(8000 + p)?;
412 }
413 assert_eq!(
414 git_repo
415 .git_repo
416 .config()?
417 .get_entry("nostr.repo")?
418 .value()
419 .unwrap(),
420 Coordinate {
421 kind: nostr_sdk::Kind::GitRepoAnnouncement,
422 identifier: "example-identifier".to_string(),
423 public_key: TEST_KEY_1_KEYS.public_key(),
424 relays: vec![],
425 }
426 .to_bech32()?,
427 );
428
429 Ok(())
430 });
431
432 // launch relay
433 let _ = join!(
434 r51.listen_until_close(),
435 r52.listen_until_close(),
436 r53.listen_until_close(),
437 r55.listen_until_close(),
438 r56.listen_until_close(),
439 r57.listen_until_close(),
440 );
441 cli_tester_handle.join().unwrap()?;
442 Ok(())
443 }
444
445 #[tokio::test]
446 #[serial]
447 async fn with_nostr_repo_set_to_user_and_identifer_naddr() -> Result<()> {
448 async_run_test().await?;
449 Ok(())
450 }
451 }
452
453 mod tags_as_specified_in_args {
454 use super::*;
455
456 #[tokio::test]
457 #[serial]
458 async fn d_replaceable_event_identifier() -> Result<()> {
459 let (_, _, r53, r55, r56, r57) = prep_run_init().await?;
460 for relay in [&r53, &r55, &r56, &r57] {
461 let event: &nostr::Event = relay
462 .events
463 .iter()
464 .find(|e| e.kind.eq(&Kind::GitRepoAnnouncement))
465 .unwrap();
466
467 assert!(
468 event.tags.iter().any(
469 |t| t.as_vec()[0].eq("d") && t.as_vec()[1].eq("example-identifier")
470 )
471 );
472 }
473 Ok(())
474 }
475
476 #[tokio::test]
477 #[serial]
478 async fn earliest_unique_commit_as_reference_with_euc_marker() -> Result<()> {
479 let (_, _, r53, r55, r56, r57) = prep_run_init().await?;
480 for relay in [&r53, &r55, &r56, &r57] {
481 let event: &nostr::Event = relay
482 .events
483 .iter()
484 .find(|e| e.kind.eq(&Kind::GitRepoAnnouncement))
485 .unwrap();
486
487 assert!(event.tags.iter().any(|t| t.as_vec()[0].eq("r")
488 && t.as_vec()[1].eq("9ee507fc4357d7ee16a5d8901bedcd103f23c17d")
489 && t.as_vec()[2].eq("euc")));
490 }
491 Ok(())
492 }
493
494 #[tokio::test]
495 #[serial]
496 async fn name() -> Result<()> {
497 let (_, _, r53, r55, r56, r57) = prep_run_init().await?;
498 for relay in [&r53, &r55, &r56, &r57] {
499 let event: &nostr::Event = relay
500 .events
501 .iter()
502 .find(|e| e.kind.eq(&Kind::GitRepoAnnouncement))
503 .unwrap();
504
505 assert!(
506 event
507 .tags
508 .iter()
509 .any(|t| t.as_vec()[0].eq("name") && t.as_vec()[1].eq("example-name"))
510 );
511 }
512 Ok(())
513 }
514
515 #[tokio::test]
516 #[serial]
517 async fn alt() -> Result<()> {
518 let (_, _, r53, r55, r56, r57) = prep_run_init().await?;
519 for relay in [&r53, &r55, &r56, &r57] {
520 let event: &nostr::Event = relay
521 .events
522 .iter()
523 .find(|e| e.kind.eq(&Kind::GitRepoAnnouncement))
524 .unwrap();
525
526 assert!(event.tags.iter().any(|t| t.as_vec()[0].eq("alt")
527 && t.as_vec()[1].eq("git repository: example-name")));
528 }
529 Ok(())
530 }
531
532 #[tokio::test]
533 #[serial]
534 async fn description() -> Result<()> {
535 let (_, _, r53, r55, r56, r57) = prep_run_init().await?;
536 for relay in [&r53, &r55, &r56, &r57] {
537 let event: &nostr::Event = relay
538 .events
539 .iter()
540 .find(|e| e.kind.eq(&Kind::GitRepoAnnouncement))
541 .unwrap();
542
543 assert!(event.tags.iter().any(|t| t.as_vec()[0].eq("description")
544 && t.as_vec()[1].eq("example-description")));
545 }
546 Ok(())
547 }
548
549 #[tokio::test]
550 #[serial]
551 async fn git_server() -> Result<()> {
552 let (_, _, r53, r55, r56, r57) = prep_run_init().await?;
553 for relay in [&r53, &r55, &r56, &r57] {
554 let event: &nostr::Event = relay
555 .events
556 .iter()
557 .find(|e| e.kind.eq(&Kind::GitRepoAnnouncement))
558 .unwrap();
559
560 assert!(
561 event.tags.iter().any(|t| t.as_vec()[0].eq("clone")
562 && t.as_vec()[1].eq("https://git.myhosting.com/my-repo.git")) /* todo check it defaults to origin */
563 );
564 }
565 Ok(())
566 }
567
568 #[tokio::test]
569 #[serial]
570 async fn relays() -> Result<()> {
571 let (_, _, r53, r55, r56, r57) = prep_run_init().await?;
572 for relay in [&r53, &r55, &r56, &r57] {
573 let event: &nostr::Event = relay
574 .events
575 .iter()
576 .find(|e| e.kind.eq(&Kind::GitRepoAnnouncement))
577 .unwrap();
578 let relays_tag = event
579 .tags
580 .iter()
581 .find(|t| t.as_vec()[0].eq("relays"))
582 .unwrap()
583 .as_vec();
584 assert_eq!(relays_tag[1], "ws://localhost:8055",);
585 assert_eq!(relays_tag[2], "ws://localhost:8056",);
586 }
587 Ok(())
588 }
589
590 #[tokio::test]
591 #[serial]
592 async fn web() -> Result<()> {
593 let (_, _, r53, r55, r56, r57) = prep_run_init().await?;
594 for relay in [&r53, &r55, &r56, &r57] {
595 let event: &nostr::Event = relay
596 .events
597 .iter()
598 .find(|e| e.kind.eq(&Kind::GitRepoAnnouncement))
599 .unwrap();
600 let web_tag = event
601 .tags
602 .iter()
603 .find(|t| t.as_vec()[0].eq("web"))
604 .unwrap()
605 .as_vec();
606 assert_eq!(web_tag[1], "https://exampleproject.xyz",);
607 assert_eq!(web_tag[2], "https://gitworkshop.dev/123",);
608 }
609 Ok(())
610 }
611
612 #[tokio::test]
613 #[serial]
614 async fn maintainers() -> Result<()> {
615 let (_, _, r53, r55, r56, r57) = prep_run_init().await?;
616 for relay in [&r53, &r55, &r56, &r57] {
617 let event: &nostr::Event = relay
618 .events
619 .iter()
620 .find(|e| e.kind.eq(&Kind::GitRepoAnnouncement))
621 .unwrap();
622 let maintainers_tag = event
623 .tags
624 .iter()
625 .find(|t| t.as_vec()[0].eq("maintainers"))
626 .unwrap()
627 .as_vec();
628 assert_eq!(maintainers_tag[1], TEST_KEY_1_KEYS.public_key().to_string());
629 }
630 Ok(())
631 }
632 }
633
634 mod cli_ouput {
635 use super::*;
636
637 #[tokio::test]
638 #[serial]
639 async fn check_cli_output() -> Result<()> {
640 let git_repo = prep_git_repo()?;
641
642 // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57)
643 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
644 Relay::new(
645 8051,
646 None,
647 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
648 relay.respond_events(
649 client_id,
650 &subscription_id,
651 &vec![
652 generate_test_key_1_metadata_event("fred"),
653 generate_test_key_1_relay_list_event(),
654 ],
655 )?;
656 Ok(())
657 }),
658 ),
659 Relay::new(8052, None, None),
660 Relay::new(8053, None, None),
661 Relay::new(8055, None, None),
662 Relay::new(8056, None, None),
663 Relay::new(8057, None, None),
664 );
665
666 // // check relay had the right number of events
667 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
668 let mut p = cli_tester_init(&git_repo);
669 expect_msgs_first(&mut p)?;
670 relay::expect_send_with_progress(
671 &mut p,
672 vec![
673 (" [my-relay] [repo-relay] ws://localhost:8055", true, ""),
674 (" [my-relay] ws://localhost:8053", true, ""),
675 (" [repo-relay] ws://localhost:8056", true, ""),
676 (" [default] ws://localhost:8051", true, ""),
677 (" [default] ws://localhost:8052", true, ""),
678 (" [default] ws://localhost:8057", true, ""),
679 ],
680 1,
681 )?;
682 expect_msgs_after(&mut p)?;
683 p.expect_end()?;
684 for p in [51, 52, 53, 55, 56, 57] {
685 relay::shutdown_relay(8000 + p)?;
686 }
687 Ok(())
688 });
689
690 // launch relay
691 let _ = join!(
692 r51.listen_until_close(),
693 r52.listen_until_close(),
694 r53.listen_until_close(),
695 r55.listen_until_close(),
696 r56.listen_until_close(),
697 r57.listen_until_close(),
698 );
699 cli_tester_handle.join().unwrap()?;
700 Ok(())
701 }
702 }
703 }
704 // TODO: cli caputuring input
705}
706// TODO: when_updating_existing_repoistory correct defaults are used
diff --git a/tests/ngit/list.rs b/tests/ngit/list.rs
new file mode 100644
index 0000000..c145fa4
--- /dev/null
+++ b/tests/ngit/list.rs
@@ -0,0 +1,1549 @@
1use anyhow::Result;
2use futures::join;
3use serial_test::serial;
4use test_utils::{git::GitTestRepo, relay::Relay, *};
5
6async fn prep_proposals_repo_and_repo_with_proposal_pulled_and_checkedout(
7 proposal_number: u16,
8) -> Result<(GitTestRepo, GitTestRepo)> {
9 // fallback (51,52) user write (53, 55) repo (55, 56)
10 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
11 Relay::new(8051, None, None),
12 Relay::new(8052, None, None),
13 Relay::new(8053, None, None),
14 Relay::new(8055, None, None),
15 Relay::new(8056, None, None),
16 );
17
18 r51.events.push(generate_test_key_1_relay_list_event());
19 r51.events.push(generate_test_key_1_metadata_event("fred"));
20 r51.events.push(generate_repo_ref_event());
21
22 r55.events.push(generate_repo_ref_event());
23 r55.events.push(generate_test_key_1_metadata_event("fred"));
24 r55.events.push(generate_test_key_1_relay_list_event());
25
26 let cli_tester_handle = std::thread::spawn(move || -> Result<(GitTestRepo, GitTestRepo)> {
27 let (originating_repo, test_repo) =
28 create_proposals_and_repo_with_proposal_pulled_and_checkedout(proposal_number)?;
29
30 for p in [51, 52, 53, 55, 56] {
31 relay::shutdown_relay(8000 + p)?;
32 }
33 Ok((originating_repo, test_repo))
34 });
35
36 // launch relay
37 let _ = join!(
38 r51.listen_until_close(),
39 r52.listen_until_close(),
40 r53.listen_until_close(),
41 r55.listen_until_close(),
42 r56.listen_until_close(),
43 );
44 let res = cli_tester_handle.join().unwrap()?;
45
46 Ok(res)
47}
48
49mod cannot_find_repo_event {
50 use super::*;
51 mod cli_prompts {
52 use nostr::{nips::nip01::Coordinate, ToBech32};
53
54 use super::*;
55 async fn run_async_repo_event_ref_needed(invalid_input: bool, naddr: bool) -> Result<()> {
56 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
57 Relay::new(8051, None, None),
58 Relay::new(8052, None, None),
59 Relay::new(8053, None, None),
60 Relay::new(8055, None, None),
61 Relay::new(8056, None, None),
62 );
63
64 r51.events.push(generate_test_key_1_relay_list_event());
65 r51.events.push(generate_test_key_1_metadata_event("fred"));
66
67 r55.events.push(generate_test_key_1_relay_list_event());
68 r55.events.push(generate_test_key_1_metadata_event("fred"));
69
70 let repo_event = generate_repo_ref_event();
71 r56.events.push(repo_event.clone());
72
73 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
74 let test_repo = GitTestRepo::without_repo_in_git_config();
75 test_repo.populate()?;
76 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]);
77 p.expect(
78 "hint: https://gitworkshop.dev/repos lists repositories and their naddr\r\n",
79 )?;
80 if invalid_input {
81 let mut input = p.expect_input("repository naddr")?;
82 input.succeeds_with("dfgvfvfzadvd")?;
83 p.expect("not a valid naddr\r\n")?;
84 let _ = p.expect_input("repository naddr")?;
85 p.exit()?;
86 }
87 if naddr {
88 let mut input = p.expect_input("repository naddr")?;
89 input.succeeds_with(
90 &Coordinate {
91 kind: nostr::Kind::GitRepoAnnouncement,
92 public_key: TEST_KEY_1_KEYS.public_key(),
93 identifier: repo_event.identifier().unwrap().to_string(),
94 relays: vec!["ws://localhost:8056".to_string()],
95 }
96 .to_bech32()?,
97 )?;
98 p.expect("fetching updates...\r\n")?;
99 p.expect_eventually("\r\n")?; // some updates listed here
100 p.expect_end_with("no proposals found... create one? try `ngit send`\r\n")?;
101 }
102
103 for p in [51, 52, 53, 55, 56] {
104 relay::shutdown_relay(8000 + p)?;
105 }
106 Ok(())
107 });
108
109 // launch relay
110 let _ = join!(
111 r51.listen_until_close(),
112 r52.listen_until_close(),
113 r53.listen_until_close(),
114 r55.listen_until_close(),
115 r56.listen_until_close(),
116 );
117 cli_tester_handle.join().unwrap()?;
118 Ok(())
119 }
120
121 #[tokio::test]
122 #[serial]
123 async fn warns_not_valid_input_and_asks_again() -> Result<()> {
124 run_async_repo_event_ref_needed(true, false).await
125 }
126
127 #[tokio::test]
128 #[serial]
129 async fn finds_based_on_naddr_on_embeded_relay() -> Result<()> {
130 run_async_repo_event_ref_needed(false, true).await
131 }
132 }
133}
134mod when_main_branch_is_uptodate {
135 use super::*;
136
137 mod when_proposal_branch_doesnt_exist {
138 use super::*;
139
140 mod when_main_is_checked_out {
141 use super::*;
142
143 mod when_first_proposal_selected {
144 use super::*;
145
146 // TODO: test when other proposals with the same name but from other
147 // repositories are present on relays
148
149 mod cli_prompts {
150 use super::*;
151 #[tokio::test]
152 #[serial]
153 async fn prompts_to_choose_from_proposal_titles() -> Result<()> {
154 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
155 Relay::new(8051, None, None),
156 Relay::new(8052, None, None),
157 Relay::new(8053, None, None),
158 Relay::new(8055, None, None),
159 Relay::new(8056, None, None),
160 );
161
162 r51.events.push(generate_test_key_1_relay_list_event());
163 r51.events.push(generate_test_key_1_metadata_event("fred"));
164 r51.events.push(generate_repo_ref_event());
165
166 r55.events.push(generate_repo_ref_event());
167 r55.events.push(generate_test_key_1_metadata_event("fred"));
168 r55.events.push(generate_test_key_1_relay_list_event());
169
170 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
171 cli_tester_create_proposals()?;
172
173 let test_repo = GitTestRepo::default();
174 test_repo.populate()?;
175 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]);
176
177 p.expect("fetching updates...\r\n")?;
178 p.expect_eventually("\r\n")?; // some updates listed here
179 let mut c = p.expect_choice(
180 "all proposals",
181 vec![
182 format!("\"{PROPOSAL_TITLE_3}\""),
183 format!("\"{PROPOSAL_TITLE_2}\""),
184 format!("\"{PROPOSAL_TITLE_1}\""),
185 ],
186 )?;
187 c.succeeds_with(2, true, None)?;
188 let mut c = p.expect_choice(
189 "",
190 vec![
191 format!(
192 "create and checkout proposal branch (2 ahead 0 behind 'main')" ),
193 format!("apply to current branch with `git am`"),
194 format!("download to ./patches"),
195 format!("back"),
196 ],
197 )?;
198 c.succeeds_with(0, false, None)?;
199 p.expect(&format!(
200 "checked out proposal as 'pr/{}(",
201 FEATURE_BRANCH_NAME_1,
202 ))?;
203 p.expect_end_eventually_with(")' branch\r\n")?;
204
205 for p in [51, 52, 53, 55, 56] {
206 relay::shutdown_relay(8000 + p)?;
207 }
208 Ok(())
209 });
210
211 // launch relay
212 let _ = join!(
213 r51.listen_until_close(),
214 r52.listen_until_close(),
215 r53.listen_until_close(),
216 r55.listen_until_close(),
217 r56.listen_until_close(),
218 );
219 cli_tester_handle.join().unwrap()?;
220 println!("{:?}", r55.events);
221 Ok(())
222 }
223 }
224
225 #[tokio::test]
226 #[serial]
227 async fn proposal_branch_created_with_correct_name() -> Result<()> {
228 let (_, test_repo) =
229 prep_proposals_repo_and_repo_with_proposal_pulled_and_checkedout(1).await?;
230 assert_eq!(
231 vec![
232 "main",
233 &get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_1)?,
234 ],
235 test_repo.get_local_branch_names()?
236 );
237 Ok(())
238 }
239
240 #[tokio::test]
241 #[serial]
242 async fn proposal_branch_checked_out() -> Result<()> {
243 let (_, test_repo) =
244 prep_proposals_repo_and_repo_with_proposal_pulled_and_checkedout(1).await?;
245 assert_eq!(
246 get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_1)?,
247 test_repo.get_checked_out_branch_name()?,
248 );
249 Ok(())
250 }
251
252 #[tokio::test]
253 #[serial]
254 async fn proposal_branch_tip_is_most_recent_patch() -> Result<()> {
255 let (originating_repo, test_repo) =
256 prep_proposals_repo_and_repo_with_proposal_pulled_and_checkedout(1).await?;
257 assert_eq!(
258 originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?,
259 test_repo.get_tip_of_local_branch(&get_proposal_branch_name(
260 &test_repo,
261 FEATURE_BRANCH_NAME_1
262 )?)?,
263 );
264 Ok(())
265 }
266 }
267 mod when_third_proposal_selected {
268 use super::*;
269
270 mod cli_prompts {
271 use super::*;
272
273 #[tokio::test]
274 #[serial]
275 async fn prompts_to_choose_from_proposal_titles() -> Result<()> {
276 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
277 Relay::new(8051, None, None),
278 Relay::new(8052, None, None),
279 Relay::new(8053, None, None),
280 Relay::new(8055, None, None),
281 Relay::new(8056, None, None),
282 );
283
284 r51.events.push(generate_test_key_1_relay_list_event());
285 r51.events.push(generate_test_key_1_metadata_event("fred"));
286 r51.events.push(generate_repo_ref_event());
287
288 r55.events.push(generate_repo_ref_event());
289 r55.events.push(generate_test_key_1_metadata_event("fred"));
290 r55.events.push(generate_test_key_1_relay_list_event());
291
292 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
293 cli_tester_create_proposals()?;
294
295 let test_repo = GitTestRepo::default();
296 test_repo.populate()?;
297 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]);
298
299 p.expect("fetching updates...\r\n")?;
300 p.expect_eventually("\r\n")?; // some updates listed here
301 let mut c = p.expect_choice(
302 "all proposals",
303 vec![
304 format!("\"{PROPOSAL_TITLE_3}\""),
305 format!("\"{PROPOSAL_TITLE_2}\""),
306 format!("\"{PROPOSAL_TITLE_1}\""),
307 ],
308 )?;
309 c.succeeds_with(0, true, None)?;
310 let mut c = p.expect_choice(
311 "",
312 vec![
313 format!(
314 "create and checkout proposal branch (2 ahead 0 behind 'main')" ),
315 format!("apply to current branch with `git am`"),
316 format!("download to ./patches"),
317 format!("back"),
318 ],
319 )?;
320 c.succeeds_with(0, false, Some(0))?;
321 p.expect(&format!(
322 "checked out proposal as 'pr/{}(",
323 FEATURE_BRANCH_NAME_3,
324 ))?;
325 p.expect_end_eventually_with(")' branch\r\n")?;
326
327 for p in [51, 52, 53, 55, 56] {
328 relay::shutdown_relay(8000 + p)?;
329 }
330 Ok(())
331 });
332
333 // launch relay
334 let _ = join!(
335 r51.listen_until_close(),
336 r52.listen_until_close(),
337 r53.listen_until_close(),
338 r55.listen_until_close(),
339 r56.listen_until_close(),
340 );
341 cli_tester_handle.join().unwrap()?;
342 println!("{:?}", r55.events);
343 Ok(())
344 }
345 }
346
347 #[tokio::test]
348 #[serial]
349 async fn proposal_branch_created_with_correct_name() -> Result<()> {
350 let (_, test_repo) =
351 prep_proposals_repo_and_repo_with_proposal_pulled_and_checkedout(3).await?;
352 assert_eq!(
353 vec![
354 "main",
355 &get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_3)?,
356 ],
357 test_repo.get_local_branch_names()?
358 );
359 Ok(())
360 }
361
362 #[tokio::test]
363 #[serial]
364 async fn proposal_branch_checked_out() -> Result<()> {
365 let (_, test_repo) =
366 prep_proposals_repo_and_repo_with_proposal_pulled_and_checkedout(3).await?;
367 assert_eq!(
368 get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_3)?,
369 test_repo.get_checked_out_branch_name()?,
370 );
371 Ok(())
372 }
373
374 #[tokio::test]
375 #[serial]
376 async fn proposal_branch_tip_is_most_recent_patch() -> Result<()> {
377 let (originating_repo, test_repo) =
378 prep_proposals_repo_and_repo_with_proposal_pulled_and_checkedout(3).await?;
379 assert_eq!(
380 originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_3)?,
381 test_repo.get_tip_of_local_branch(&get_proposal_branch_name(
382 &test_repo,
383 FEATURE_BRANCH_NAME_3
384 )?)?,
385 );
386 Ok(())
387 }
388 }
389 mod when_forth_proposal_has_no_cover_letter {
390 use super::*;
391
392 async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> {
393 // fallback (51,52) user write (53, 55) repo (55, 56)
394 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
395 Relay::new(8051, None, None),
396 Relay::new(8052, None, None),
397 Relay::new(8053, None, None),
398 Relay::new(8055, None, None),
399 Relay::new(8056, None, None),
400 );
401
402 r51.events.push(generate_test_key_1_relay_list_event());
403 r51.events.push(generate_test_key_1_metadata_event("fred"));
404 r51.events.push(generate_repo_ref_event());
405
406 r55.events.push(generate_repo_ref_event());
407 r55.events.push(generate_test_key_1_metadata_event("fred"));
408 r55.events.push(generate_test_key_1_relay_list_event());
409
410 let cli_tester_handle = std::thread::spawn(
411 move || -> Result<(GitTestRepo, GitTestRepo)> {
412 let originating_repo = cli_tester_create_proposals()?;
413 cli_tester_create_proposal(
414 &originating_repo,
415 FEATURE_BRANCH_NAME_4,
416 "d",
417 None,
418 None,
419 )?;
420 let test_repo = GitTestRepo::default();
421 test_repo.populate()?;
422 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]);
423
424 p.expect("fetching updates...\r\n")?;
425 p.expect_eventually("\r\n")?; // some updates listed here
426 let mut c = p.expect_choice(
427 "all proposals",
428 vec![
429 format!("add d3.md"), // commit msg title
430 format!("\"{PROPOSAL_TITLE_3}\""),
431 format!("\"{PROPOSAL_TITLE_2}\""),
432 format!("\"{PROPOSAL_TITLE_1}\""),
433 ],
434 )?;
435 c.succeeds_with(0, true, None)?;
436 let mut c = p.expect_choice(
437 "",
438 vec![
439 format!(
440 "create and checkout proposal branch (2 ahead 0 behind 'main')" ),
441 format!("apply to current branch with `git am`"),
442 format!("download to ./patches"),
443 format!("back"),
444 ],
445 )?;
446 c.succeeds_with(0, false, Some(0))?;
447 p.expect_end_eventually_and_print()?;
448
449 for p in [51, 52, 53, 55, 56] {
450 relay::shutdown_relay(8000 + p)?;
451 }
452 Ok((originating_repo, test_repo))
453 },
454 );
455
456 // launch relay
457 let _ = join!(
458 r51.listen_until_close(),
459 r52.listen_until_close(),
460 r53.listen_until_close(),
461 r55.listen_until_close(),
462 r56.listen_until_close(),
463 );
464 let res = cli_tester_handle.join().unwrap()?;
465
466 Ok(res)
467 }
468
469 mod cli_prompts {
470 use super::*;
471
472 #[tokio::test]
473 #[serial]
474 async fn prompts_to_choose_from_proposal_titles() -> Result<()> {
475 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
476 Relay::new(8051, None, None),
477 Relay::new(8052, None, None),
478 Relay::new(8053, None, None),
479 Relay::new(8055, None, None),
480 Relay::new(8056, None, None),
481 );
482
483 r51.events.push(generate_test_key_1_relay_list_event());
484 r51.events.push(generate_test_key_1_metadata_event("fred"));
485 r51.events.push(generate_repo_ref_event());
486
487 r55.events.push(generate_repo_ref_event());
488 r55.events.push(generate_test_key_1_metadata_event("fred"));
489 r55.events.push(generate_test_key_1_relay_list_event());
490
491 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
492 let originating_repo = cli_tester_create_proposals()?;
493 std::thread::sleep(std::time::Duration::from_millis(1000));
494 cli_tester_create_proposal(
495 &originating_repo,
496 FEATURE_BRANCH_NAME_4,
497 "d",
498 None,
499 None,
500 )?;
501 let test_repo = GitTestRepo::default();
502 test_repo.populate()?;
503 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]);
504
505 p.expect("fetching updates...\r\n")?;
506 p.expect_eventually("\r\n")?; // some updates listed here
507 let mut c = p.expect_choice(
508 "all proposals",
509 vec![
510 format!("add d3.md"), // commit msg title
511 format!("\"{PROPOSAL_TITLE_3}\""),
512 format!("\"{PROPOSAL_TITLE_2}\""),
513 format!("\"{PROPOSAL_TITLE_1}\""),
514 ],
515 )?;
516 c.succeeds_with(0, true, None)?;
517 let mut c = p.expect_choice(
518 "",
519 vec![
520 format!(
521 "create and checkout proposal branch (2 ahead 0 behind 'main')" ),
522 format!("apply to current branch with `git am`"),
523 format!("download to ./patches"),
524 format!("back"),
525 ],
526 )?;
527 c.succeeds_with(0, false, Some(0))?;
528 p.expect(&format!(
529 "checked out proposal as 'pr/{}(",
530 FEATURE_BRANCH_NAME_4,
531 ))?;
532 p.expect_end_eventually_with(")' branch\r\n")?;
533
534 for p in [51, 52, 53, 55, 56] {
535 relay::shutdown_relay(8000 + p)?;
536 }
537 Ok(())
538 });
539
540 // launch relay
541 let _ = join!(
542 r51.listen_until_close(),
543 r52.listen_until_close(),
544 r53.listen_until_close(),
545 r55.listen_until_close(),
546 r56.listen_until_close(),
547 );
548 cli_tester_handle.join().unwrap()?;
549 println!("{:?}", r55.events);
550 Ok(())
551 }
552 }
553
554 #[tokio::test]
555 #[serial]
556 async fn proposal_branch_created_with_correct_name() -> Result<()> {
557 let (_, test_repo) = prep_and_run().await?;
558 assert_eq!(
559 vec![
560 "main",
561 &get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_4)?,
562 ],
563 test_repo.get_local_branch_names()?
564 );
565 Ok(())
566 }
567
568 #[tokio::test]
569 #[serial]
570 async fn proposal_branch_checked_out() -> Result<()> {
571 let (_, test_repo) = prep_and_run().await?;
572 assert_eq!(
573 get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_4)?,
574 test_repo.get_checked_out_branch_name()?,
575 );
576 Ok(())
577 }
578
579 #[tokio::test]
580 #[serial]
581 async fn proposal_branch_tip_is_most_recent_patch() -> Result<()> {
582 let (originating_repo, test_repo) = prep_and_run().await?;
583 assert_eq!(
584 originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_4)?,
585 test_repo.get_tip_of_local_branch(&get_proposal_branch_name(
586 &test_repo,
587 FEATURE_BRANCH_NAME_4
588 )?)?,
589 );
590 Ok(())
591 }
592 }
593 }
594 }
595
596 mod when_proposal_branch_exists {
597 use super::*;
598
599 mod when_main_is_checked_out {
600 use super::*;
601
602 mod when_branch_is_up_to_date {
603 use super::*;
604 async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> {
605 // fallback (51,52) user write (53, 55) repo (55, 56)
606 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
607 Relay::new(8051, None, None),
608 Relay::new(8052, None, None),
609 Relay::new(8053, None, None),
610 Relay::new(8055, None, None),
611 Relay::new(8056, None, None),
612 );
613
614 r51.events.push(generate_test_key_1_relay_list_event());
615 r51.events.push(generate_test_key_1_metadata_event("fred"));
616 r51.events.push(generate_repo_ref_event());
617
618 r55.events.push(generate_repo_ref_event());
619 r55.events.push(generate_test_key_1_metadata_event("fred"));
620 r55.events.push(generate_test_key_1_relay_list_event());
621
622 let cli_tester_handle = std::thread::spawn(
623 move || -> Result<(GitTestRepo, GitTestRepo)> {
624 let originating_repo = cli_tester_create_proposals()?;
625
626 let test_repo = GitTestRepo::default();
627 test_repo.populate()?;
628 // create proposal branch
629 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]);
630 p.expect("fetching updates...\r\n")?;
631 p.expect_eventually("\r\n")?; // some updates listed here
632 let mut c = p.expect_choice(
633 "all proposals",
634 vec![
635 format!("\"{PROPOSAL_TITLE_3}\""),
636 format!("\"{PROPOSAL_TITLE_2}\""),
637 format!("\"{PROPOSAL_TITLE_1}\""),
638 ],
639 )?;
640 c.succeeds_with(2, true, None)?;
641 let mut c = p.expect_choice(
642 "",
643 vec![
644 format!("create and checkout proposal branch (2 ahead 0 behind 'main')"),
645 format!("apply to current branch with `git am`"),
646 format!("download to ./patches"),
647 format!("back"),
648 ],
649 )?;
650 c.succeeds_with(0, false, Some(0))?;
651 p.expect_end_eventually()?;
652
653 test_repo.checkout("main")?;
654 // run test
655 p = CliTester::new_from_dir(&test_repo.dir, ["list"]);
656 p.expect("fetching updates...\r\n")?;
657 p.expect_eventually("\r\n")?; // some updates listed here
658 let mut c = p.expect_choice(
659 "all proposals",
660 vec![
661 format!("\"{PROPOSAL_TITLE_3}\""),
662 format!("\"{PROPOSAL_TITLE_2}\""),
663 format!("\"{PROPOSAL_TITLE_1}\""),
664 ],
665 )?;
666 c.succeeds_with(2, true, None)?;
667 let mut c = p.expect_choice(
668 "",
669 vec![
670 format!("checkout proposal branch (2 ahead 0 behind 'main')"),
671 format!("apply to current branch with `git am`"),
672 format!("download to ./patches"),
673 format!("back"),
674 ],
675 )?;
676 c.succeeds_with(0, false, Some(0))?;
677 p.expect_end_eventually_and_print()?;
678
679 for p in [51, 52, 53, 55, 56] {
680 relay::shutdown_relay(8000 + p)?;
681 }
682 Ok((originating_repo, test_repo))
683 },
684 );
685
686 // launch relay
687 let _ = join!(
688 r51.listen_until_close(),
689 r52.listen_until_close(),
690 r53.listen_until_close(),
691 r55.listen_until_close(),
692 r56.listen_until_close(),
693 );
694 let res = cli_tester_handle.join().unwrap()?;
695
696 Ok(res)
697 }
698
699 mod cli_prompts {
700 use super::*;
701
702 #[tokio::test]
703 #[serial]
704 async fn prompts_to_choose_from_proposal_titles() -> Result<()> {
705 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
706 Relay::new(8051, None, None),
707 Relay::new(8052, None, None),
708 Relay::new(8053, None, None),
709 Relay::new(8055, None, None),
710 Relay::new(8056, None, None),
711 );
712
713 r51.events.push(generate_test_key_1_relay_list_event());
714 r51.events.push(generate_test_key_1_metadata_event("fred"));
715 r51.events.push(generate_repo_ref_event());
716
717 r55.events.push(generate_repo_ref_event());
718 r55.events.push(generate_test_key_1_metadata_event("fred"));
719 r55.events.push(generate_test_key_1_relay_list_event());
720
721 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
722 cli_tester_create_proposals()?;
723
724 let test_repo = GitTestRepo::default();
725 test_repo.populate()?;
726 // create proposal branch
727 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]);
728 p.expect("fetching updates...\r\n")?;
729 p.expect_eventually("\r\n")?; // some updates listed here
730 let mut c = p.expect_choice(
731 "all proposals",
732 vec![
733 format!("\"{PROPOSAL_TITLE_3}\""),
734 format!("\"{PROPOSAL_TITLE_2}\""),
735 format!("\"{PROPOSAL_TITLE_1}\""),
736 ],
737 )?;
738 c.succeeds_with(2, true, None)?;
739 let mut c = p.expect_choice(
740 "",
741 vec![
742 format!("create and checkout proposal branch (2 ahead 0 behind 'main')"),
743 format!("apply to current branch with `git am`"),
744 format!("download to ./patches"),
745 format!("back"),
746 ],
747 )?;
748 c.succeeds_with(0, false, Some(0))?;
749 p.expect_end_eventually()?;
750
751 test_repo.checkout("main")?;
752 // run test
753 p = CliTester::new_from_dir(&test_repo.dir, ["list"]);
754 p.expect("fetching updates...\r\n")?;
755 p.expect_eventually("\r\n")?; // some updates listed here
756 let mut c = p.expect_choice(
757 "all proposals",
758 vec![
759 format!("\"{PROPOSAL_TITLE_3}\""),
760 format!("\"{PROPOSAL_TITLE_2}\""),
761 format!("\"{PROPOSAL_TITLE_1}\""),
762 ],
763 )?;
764 c.succeeds_with(2, true, None)?;
765 let mut c = p.expect_choice(
766 "",
767 vec![
768 format!("checkout proposal branch (2 ahead 0 behind 'main')"),
769 format!("apply to current branch with `git am`"),
770 format!("download to ./patches"),
771 format!("back"),
772 ],
773 )?;
774 c.succeeds_with(0, false, Some(0))?;
775 p.expect(&format!(
776 "checked out proposal as 'pr/{}(",
777 FEATURE_BRANCH_NAME_1,
778 ))?;
779 p.expect_end_eventually_with(")' branch\r\n")?;
780
781 for p in [51, 52, 53, 55, 56] {
782 relay::shutdown_relay(8000 + p)?;
783 }
784 Ok(())
785 });
786
787 // launch relay
788 let _ = join!(
789 r51.listen_until_close(),
790 r52.listen_until_close(),
791 r53.listen_until_close(),
792 r55.listen_until_close(),
793 r56.listen_until_close(),
794 );
795 cli_tester_handle.join().unwrap()?;
796 println!("{:?}", r55.events);
797 Ok(())
798 }
799 }
800
801 #[tokio::test]
802 #[serial]
803 async fn proposal_branch_checked_out() -> Result<()> {
804 let (_, test_repo) = prep_and_run().await?;
805 assert_eq!(
806 get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_1)?,
807 test_repo.get_checked_out_branch_name()?,
808 );
809 Ok(())
810 }
811 }
812
813 mod when_branch_is_behind {
814 use super::*;
815
816 async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> {
817 // fallback (51,52) user write (53, 55) repo (55, 56)
818 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
819 Relay::new(8051, None, None),
820 Relay::new(8052, None, None),
821 Relay::new(8053, None, None),
822 Relay::new(8055, None, None),
823 Relay::new(8056, None, None),
824 );
825
826 r51.events.push(generate_test_key_1_relay_list_event());
827 r51.events.push(generate_test_key_1_metadata_event("fred"));
828 r51.events.push(generate_repo_ref_event());
829
830 r55.events.push(generate_repo_ref_event());
831 r55.events.push(generate_test_key_1_metadata_event("fred"));
832 r55.events.push(generate_test_key_1_relay_list_event());
833
834 let cli_tester_handle = std::thread::spawn(
835 move || -> Result<(GitTestRepo, GitTestRepo)> {
836 let (originating_repo, test_repo) =
837 create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?;
838
839 remove_latest_commit_so_proposal_branch_is_behind_and_checkout_main(
840 &test_repo,
841 )?;
842
843 // run test
844 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]);
845 p.expect("fetching updates...\r\n")?;
846 p.expect_eventually("\r\n")?; // some updates listed here
847 let mut c = p.expect_choice(
848 "all proposals",
849 vec![
850 format!("\"{PROPOSAL_TITLE_3}\""),
851 format!("\"{PROPOSAL_TITLE_2}\""),
852 format!("\"{PROPOSAL_TITLE_1}\""),
853 ],
854 )?;
855 c.succeeds_with(2, true, None)?;
856 let mut c = p.expect_choice(
857 "",
858 vec![
859 format!("checkout proposal branch and apply 1 appendments"),
860 format!("apply to current branch with `git am`"),
861 format!("download to ./patches"),
862 format!("back"),
863 ],
864 )?;
865 c.succeeds_with(0, false, Some(0))?;
866 p.expect("checked out proposal branch and applied 1 appendments (2 ahead 0 behind 'main')\r\n")?;
867 p.expect_end()?;
868
869 for p in [51, 52, 53, 55, 56] {
870 relay::shutdown_relay(8000 + p)?;
871 }
872 Ok((originating_repo, test_repo))
873 },
874 );
875
876 // launch relay
877 let _ = join!(
878 r51.listen_until_close(),
879 r52.listen_until_close(),
880 r53.listen_until_close(),
881 r55.listen_until_close(),
882 r56.listen_until_close(),
883 );
884 let res = cli_tester_handle.join().unwrap()?;
885
886 Ok(res)
887 }
888
889 mod cli_prompts {
890 use super::*;
891
892 #[tokio::test]
893 #[serial]
894 async fn prompts_to_choose_from_proposal_titles() -> Result<()> {
895 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
896 Relay::new(8051, None, None),
897 Relay::new(8052, None, None),
898 Relay::new(8053, None, None),
899 Relay::new(8055, None, None),
900 Relay::new(8056, None, None),
901 );
902
903 r51.events.push(generate_test_key_1_relay_list_event());
904 r51.events.push(generate_test_key_1_metadata_event("fred"));
905 r51.events.push(generate_repo_ref_event());
906
907 r55.events.push(generate_repo_ref_event());
908 r55.events.push(generate_test_key_1_metadata_event("fred"));
909 r55.events.push(generate_test_key_1_relay_list_event());
910
911 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
912 let (_, test_repo) =
913 create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?;
914
915 remove_latest_commit_so_proposal_branch_is_behind_and_checkout_main(
916 &test_repo,
917 )?;
918
919 // run test
920 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]);
921 p.expect("fetching updates...\r\n")?;
922 p.expect_eventually("\r\n")?; // some updates listed here
923 let mut c = p.expect_choice(
924 "all proposals",
925 vec![
926 format!("\"{PROPOSAL_TITLE_3}\""),
927 format!("\"{PROPOSAL_TITLE_2}\""),
928 format!("\"{PROPOSAL_TITLE_1}\""),
929 ],
930 )?;
931 c.succeeds_with(2, true, None)?;
932 let mut c = p.expect_choice(
933 "",
934 vec![
935 format!("checkout proposal branch and apply 1 appendments"),
936 format!("apply to current branch with `git am`"),
937 format!("download to ./patches"),
938 format!("back"),
939 ],
940 )?;
941 c.succeeds_with(0, false, Some(0))?;
942 p.expect("checked out proposal branch and applied 1 appendments (2 ahead 0 behind 'main')\r\n")?;
943 p.expect_end()?;
944
945 for p in [51, 52, 53, 55, 56] {
946 relay::shutdown_relay(8000 + p)?;
947 }
948 Ok(())
949 });
950
951 // launch relay
952 let _ = join!(
953 r51.listen_until_close(),
954 r52.listen_until_close(),
955 r53.listen_until_close(),
956 r55.listen_until_close(),
957 r56.listen_until_close(),
958 );
959 cli_tester_handle.join().unwrap()?;
960 println!("{:?}", r55.events);
961 Ok(())
962 }
963 }
964
965 #[tokio::test]
966 #[serial]
967 async fn proposal_branch_checked_out() -> Result<()> {
968 let (_, test_repo) = prep_and_run().await?;
969 assert_eq!(
970 get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_1)?,
971 test_repo.get_checked_out_branch_name()?,
972 );
973 Ok(())
974 }
975
976 #[tokio::test]
977 #[serial]
978 async fn proposal_branch_tip_is_most_recent_patch() -> Result<()> {
979 let (originating_repo, test_repo) = prep_and_run().await?;
980 assert_eq!(
981 originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?,
982 test_repo.get_tip_of_local_branch(&get_proposal_branch_name(
983 &test_repo,
984 FEATURE_BRANCH_NAME_1
985 )?)?,
986 );
987 Ok(())
988 }
989 }
990
991 mod when_latest_proposal_amended_locally {
992 // other rebase scenarios should work if this test passes
993 use super::*;
994 async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> {
995 // fallback (51,52) user write (53, 55) repo (55, 56)
996 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
997 Relay::new(8051, None, None),
998 Relay::new(8052, None, None),
999 Relay::new(8053, None, None),
1000 Relay::new(8055, None, None),
1001 Relay::new(8056, None, None),
1002 );
1003
1004 r51.events.push(generate_test_key_1_relay_list_event());
1005 r51.events.push(generate_test_key_1_metadata_event("fred"));
1006 r51.events.push(generate_repo_ref_event());
1007
1008 r55.events.push(generate_repo_ref_event());
1009 r55.events.push(generate_test_key_1_metadata_event("fred"));
1010 r55.events.push(generate_test_key_1_relay_list_event());
1011
1012 let cli_tester_handle =
1013 std::thread::spawn(move || -> Result<(GitTestRepo, GitTestRepo)> {
1014 let (originating_repo, test_repo) =
1015 create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?;
1016
1017 let branch_name = test_repo.get_checked_out_branch_name()?;
1018
1019 remove_latest_commit_so_proposal_branch_is_behind_and_checkout_main(
1020 &test_repo,
1021 )?;
1022
1023 // add another commit (so we have an ammened local branch)
1024 test_repo.checkout(&branch_name)?;
1025 std::fs::write(
1026 test_repo.dir.join("ammended-commit.md"),
1027 "some content",
1028 )?;
1029 test_repo.stage_and_commit("add ammended-commit.md")?;
1030 test_repo.checkout("main")?;
1031
1032 // run test
1033 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]);
1034 p.expect("fetching updates...\r\n")?;
1035 p.expect_eventually("\r\n")?; // some updates listed here
1036 let mut c = p.expect_choice(
1037 "all proposals",
1038 vec![
1039 format!("\"{PROPOSAL_TITLE_3}\""),
1040 format!("\"{PROPOSAL_TITLE_2}\""),
1041 format!("\"{PROPOSAL_TITLE_1}\""),
1042 ],
1043 )?;
1044 c.succeeds_with(2, true, None)?;
1045 p.expect_eventually("--force`\r\n")?;
1046
1047 let mut c = p.expect_choice(
1048 "",
1049 vec![
1050 format!("checkout local branch with unpublished changes"),
1051 format!(
1052 "discard unpublished changes and checkout new revision"
1053 ),
1054 format!("apply to current branch with `git am`"),
1055 format!("download to ./patches"),
1056 "back".to_string(),
1057 ],
1058 )?;
1059 c.succeeds_with(1, false, Some(0))?;
1060
1061 p.expect_end_eventually_and_print()?;
1062
1063 for p in [51, 52, 53, 55, 56] {
1064 relay::shutdown_relay(8000 + p)?;
1065 }
1066 Ok((originating_repo, test_repo))
1067 });
1068 // launch relay
1069 let _ = join!(
1070 r51.listen_until_close(),
1071 r52.listen_until_close(),
1072 r53.listen_until_close(),
1073 r55.listen_until_close(),
1074 r56.listen_until_close(),
1075 );
1076 let res = cli_tester_handle.join().unwrap()?;
1077
1078 Ok(res)
1079 }
1080
1081 mod cli_prompts {
1082 use super::*;
1083
1084 #[tokio::test]
1085 #[serial]
1086 async fn out_reflects_second_choice_discarding_old_and_applying_new()
1087 -> Result<()> {
1088 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
1089 Relay::new(8051, None, None),
1090 Relay::new(8052, None, None),
1091 Relay::new(8053, None, None),
1092 Relay::new(8055, None, None),
1093 Relay::new(8056, None, None),
1094 );
1095
1096 r51.events.push(generate_test_key_1_relay_list_event());
1097 r51.events.push(generate_test_key_1_metadata_event("fred"));
1098 r51.events.push(generate_repo_ref_event());
1099
1100 r55.events.push(generate_repo_ref_event());
1101 r55.events.push(generate_test_key_1_metadata_event("fred"));
1102 r55.events.push(generate_test_key_1_relay_list_event());
1103
1104 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
1105 let (_, test_repo) =
1106 create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?;
1107
1108 amend_last_commit(&test_repo, "add ammended-commit.md")?;
1109 test_repo.checkout("main")?;
1110
1111 // run test
1112 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]);
1113 p.expect("fetching updates...\r\n")?;
1114 p.expect_eventually("\r\n")?; // some updates listed here
1115 let mut c = p.expect_choice(
1116 "all proposals",
1117 vec![
1118 format!("\"{PROPOSAL_TITLE_3}\""),
1119 format!("\"{PROPOSAL_TITLE_2}\""),
1120 format!("\"{PROPOSAL_TITLE_1}\""),
1121 ],
1122 )?;
1123 c.succeeds_with(2, true, None)?;
1124 p.expect("you have an amended/rebase version the proposal that is unpublished\r\n")?;
1125 p.expect("you have previously applied the latest version of the proposal (2 ahead 0 behind 'main') but your local proposal branch has amended or rebased it (2 ahead 0 behind 'main')\r\n")?;
1126 p.expect("to view the latest proposal but retain your changes:\r\n")?;
1127 p.expect(" 1) create a new branch off the tip commit of this one to store your changes\r\n")?;
1128 p.expect(" 2) run `ngit list` and checkout the latest published version of this proposal\r\n")?;
1129 p.expect("if you are confident in your changes consider running `ngit push --force`\r\n")?;
1130
1131 let mut c = p.expect_choice(
1132 "",
1133 vec![
1134 format!("checkout local branch with unpublished changes"),
1135 format!(
1136 "discard unpublished changes and checkout new revision"
1137 ),
1138 format!("apply to current branch with `git am`"),
1139 format!("download to ./patches"),
1140 "back".to_string(),
1141 ],
1142 )?;
1143 c.succeeds_with(1, false, Some(1))?;
1144 p.expect_end_with("checked out latest version of proposal (2 ahead 0 behind 'main'), replacing unpublished version (2 ahead 0 behind 'main')\r\n")?;
1145
1146 for p in [51, 52, 53, 55, 56] {
1147 relay::shutdown_relay(8000 + p)?;
1148 }
1149 Ok(())
1150 });
1151
1152 // launch relay
1153 let _ = join!(
1154 r51.listen_until_close(),
1155 r52.listen_until_close(),
1156 r53.listen_until_close(),
1157 r55.listen_until_close(),
1158 r56.listen_until_close(),
1159 );
1160 cli_tester_handle.join().unwrap()?;
1161 println!("{:?}", r55.events);
1162 Ok(())
1163 }
1164 }
1165
1166 #[tokio::test]
1167 #[serial]
1168 async fn second_choice_discarded_unpublished_commits_and_checked_out_latest_revision()
1169 -> Result<()> {
1170 let (originating_repo, test_repo) = prep_and_run().await?;
1171 println!("test_dir: {:?}", test_repo.dir);
1172 assert_eq!(
1173 test_repo.get_tip_of_local_branch(&get_proposal_branch_name(
1174 &test_repo,
1175 FEATURE_BRANCH_NAME_1
1176 )?)?,
1177 originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?,
1178 );
1179 Ok(())
1180 }
1181 }
1182
1183 mod when_local_commits_on_uptodate_proposal {
1184 use super::*;
1185 async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> {
1186 // fallback (51,52) user write (53, 55) repo (55, 56)
1187 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
1188 Relay::new(8051, None, None),
1189 Relay::new(8052, None, None),
1190 Relay::new(8053, None, None),
1191 Relay::new(8055, None, None),
1192 Relay::new(8056, None, None),
1193 );
1194
1195 r51.events.push(generate_test_key_1_relay_list_event());
1196 r51.events.push(generate_test_key_1_metadata_event("fred"));
1197 r51.events.push(generate_repo_ref_event());
1198
1199 r55.events.push(generate_repo_ref_event());
1200 r55.events.push(generate_test_key_1_metadata_event("fred"));
1201 r55.events.push(generate_test_key_1_relay_list_event());
1202
1203 let cli_tester_handle = std::thread::spawn(
1204 move || -> Result<(GitTestRepo, GitTestRepo)> {
1205 let (originating_repo, test_repo) =
1206 create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?;
1207
1208 // add another commit (so we have a local branch 1 ahead)
1209 std::fs::write(
1210 test_repo.dir.join("ammended-commit.md"),
1211 "some content",
1212 )?;
1213 test_repo.stage_and_commit("add ammended-commit.md")?;
1214 test_repo.checkout("main")?;
1215
1216 // run test
1217 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]);
1218 p.expect("fetching updates...\r\n")?;
1219 p.expect_eventually("\r\n")?; // some updates listed here
1220 let mut c = p.expect_choice(
1221 "all proposals",
1222 vec![
1223 format!("\"{PROPOSAL_TITLE_3}\""),
1224 format!("\"{PROPOSAL_TITLE_2}\""),
1225 format!("\"{PROPOSAL_TITLE_1}\""),
1226 ],
1227 )?;
1228 c.succeeds_with(2, true, None)?;
1229 p.expect(
1230 "local proposal branch exists with 1 unpublished commits on top of the most up-to-date version of the proposal (3 ahead 0 behind 'main')\r\n",
1231 )?;
1232
1233 let mut c = p.expect_choice(
1234 "",
1235 vec![
1236 format!("checkout proposal branch with 1 unpublished commits"),
1237 format!("back"),
1238 ],
1239 )?;
1240 c.succeeds_with(0, false, Some(0))?;
1241 p.expect("checked out proposal branch with 1 unpublished commits (3 ahead 0 behind 'main')\r\n")?;
1242 p.expect_end()?;
1243
1244 for p in [51, 52, 53, 55, 56] {
1245 relay::shutdown_relay(8000 + p)?;
1246 }
1247 Ok((originating_repo, test_repo))
1248 },
1249 );
1250
1251 // launch relay
1252 let _ = join!(
1253 r51.listen_until_close(),
1254 r52.listen_until_close(),
1255 r53.listen_until_close(),
1256 r55.listen_until_close(),
1257 r56.listen_until_close(),
1258 );
1259 let res = cli_tester_handle.join().unwrap()?;
1260
1261 Ok(res)
1262 }
1263
1264 mod cli_prompts {
1265 use super::*;
1266
1267 #[tokio::test]
1268 #[serial]
1269 async fn prompts_to_choose_from_proposal_titles() -> Result<()> {
1270 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
1271 Relay::new(8051, None, None),
1272 Relay::new(8052, None, None),
1273 Relay::new(8053, None, None),
1274 Relay::new(8055, None, None),
1275 Relay::new(8056, None, None),
1276 );
1277
1278 r51.events.push(generate_test_key_1_relay_list_event());
1279 r51.events.push(generate_test_key_1_metadata_event("fred"));
1280 r51.events.push(generate_repo_ref_event());
1281
1282 r55.events.push(generate_repo_ref_event());
1283 r55.events.push(generate_test_key_1_metadata_event("fred"));
1284 r55.events.push(generate_test_key_1_relay_list_event());
1285
1286 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
1287 let (_, test_repo) =
1288 create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?;
1289
1290 // add another commit (so we have a local branch 1 ahead)
1291 std::fs::write(
1292 test_repo.dir.join("ammended-commit.md"),
1293 "some content",
1294 )?;
1295 test_repo.stage_and_commit("add ammended-commit.md")?;
1296 test_repo.checkout("main")?;
1297
1298 // run test
1299 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]);
1300 p.expect("fetching updates...\r\n")?;
1301 p.expect_eventually("\r\n")?; // some updates listed here
1302 let mut c = p.expect_choice(
1303 "all proposals",
1304 vec![
1305 format!("\"{PROPOSAL_TITLE_3}\""),
1306 format!("\"{PROPOSAL_TITLE_2}\""),
1307 format!("\"{PROPOSAL_TITLE_1}\""),
1308 ],
1309 )?;
1310 c.succeeds_with(2, true, None)?;
1311 p.expect(
1312 "local proposal branch exists with 1 unpublished commits on top of the most up-to-date version of the proposal (3 ahead 0 behind 'main')\r\n",
1313 )?;
1314
1315 let mut c = p.expect_choice(
1316 "",
1317 vec![
1318 format!("checkout proposal branch with 1 unpublished commits"),
1319 format!("back"),
1320 ],
1321 )?;
1322 c.succeeds_with(0, false, Some(0))?;
1323 p.expect("checked out proposal branch with 1 unpublished commits (3 ahead 0 behind 'main')\r\n")?;
1324 p.expect_end()?;
1325
1326 for p in [51, 52, 53, 55, 56] {
1327 relay::shutdown_relay(8000 + p)?;
1328 }
1329 Ok(())
1330 });
1331
1332 // launch relay
1333 let _ = join!(
1334 r51.listen_until_close(),
1335 r52.listen_until_close(),
1336 r53.listen_until_close(),
1337 r55.listen_until_close(),
1338 r56.listen_until_close(),
1339 );
1340 cli_tester_handle.join().unwrap()?;
1341 println!("{:?}", r55.events);
1342 Ok(())
1343 }
1344 }
1345
1346 #[tokio::test]
1347 #[serial]
1348 async fn proposal_branch_checked_out() -> Result<()> {
1349 let (_, test_repo) = prep_and_run().await?;
1350 assert_eq!(
1351 get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_1)?,
1352 test_repo.get_checked_out_branch_name()?,
1353 );
1354 Ok(())
1355 }
1356
1357 #[tokio::test]
1358 #[serial]
1359 async fn didnt_overwrite_local_appendments() -> Result<()> {
1360 let (originating_repo, test_repo) = prep_and_run().await?;
1361 assert_ne!(
1362 test_repo.get_tip_of_local_branch(&get_proposal_branch_name(
1363 &test_repo,
1364 FEATURE_BRANCH_NAME_1
1365 )?)?,
1366 originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?,
1367 );
1368 Ok(())
1369 }
1370 }
1371
1372 mod when_latest_revision_rebases_branch {
1373
1374 use tokio::task::JoinHandle;
1375
1376 use super::*;
1377
1378 async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> {
1379 // fallback (51,52) user write (53, 55) repo (55, 56)
1380 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
1381 Relay::new(8051, None, None),
1382 Relay::new(8052, None, None),
1383 Relay::new(8053, None, None),
1384 Relay::new(8055, None, None),
1385 Relay::new(8056, None, None),
1386 );
1387
1388 r51.events.push(generate_test_key_1_relay_list_event());
1389 r51.events.push(generate_test_key_1_metadata_event("fred"));
1390 r51.events.push(generate_repo_ref_event());
1391
1392 r55.events.push(generate_repo_ref_event());
1393 r55.events.push(generate_test_key_1_metadata_event("fred"));
1394 r55.events.push(generate_test_key_1_relay_list_event());
1395
1396 let cli_tester_handle: JoinHandle<Result<(GitTestRepo, GitTestRepo)>> =
1397 tokio::task::spawn_blocking(move || {
1398 let (originating_repo, test_repo) = create_proposals_with_first_rebased_and_repo_with_latest_main_and_unrebased_proposal()?;
1399 test_repo.checkout("main")?;
1400
1401 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]);
1402 p.expect("fetching updates...\r\n")?;
1403 p.expect_eventually("\r\n")?; // some updates listed here
1404 let mut c = p.expect_choice(
1405 "all proposals",
1406 vec![
1407 format!("\"{PROPOSAL_TITLE_3}\""),
1408 format!("\"{PROPOSAL_TITLE_2}\""),
1409 format!("\"{PROPOSAL_TITLE_1}\""),
1410 ],
1411 )?;
1412 c.succeeds_with(2, true, None)?;
1413 p.expect("updated proposal available (2 ahead 0 behind 'main'). existing version is 2 ahead 1 behind 'main'\r\n")?;
1414 let mut c = p.expect_choice(
1415 "",
1416 vec![
1417 format!("checkout and overwrite existing proposal branch"),
1418 format!("checkout existing outdated proposal branch"),
1419 format!("apply to current branch with `git am`"),
1420 format!("download to ./patches"),
1421 format!("back"),
1422 ],
1423 )?;
1424 c.succeeds_with(0, false, Some(0))?;
1425 p.expect("checked out new version of proposal (2 ahead 0 behind 'main'), replacing old version (2 ahead 1 behind 'main')\r\n")?;
1426 p.expect_end()?;
1427
1428 for p in [51, 52, 53, 55, 56] {
1429 relay::shutdown_relay(8000 + p)?;
1430 }
1431 Ok((originating_repo, test_repo))
1432 });
1433
1434 // launch relay
1435 let _ = join!(
1436 r51.listen_until_close(),
1437 r52.listen_until_close(),
1438 r53.listen_until_close(),
1439 r55.listen_until_close(),
1440 r56.listen_until_close(),
1441 );
1442 let res = cli_tester_handle.await??;
1443
1444 Ok(res)
1445 }
1446
1447 mod cli_prompts {
1448 use super::*;
1449
1450 #[tokio::test]
1451 #[serial]
1452 async fn prompts_to_choose_from_proposal_titles() -> Result<()> {
1453 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
1454 Relay::new(8051, None, None),
1455 Relay::new(8052, None, None),
1456 Relay::new(8053, None, None),
1457 Relay::new(8055, None, None),
1458 Relay::new(8056, None, None),
1459 );
1460
1461 r51.events.push(generate_test_key_1_relay_list_event());
1462 r51.events.push(generate_test_key_1_metadata_event("fred"));
1463 r51.events.push(generate_repo_ref_event());
1464
1465 r55.events.push(generate_repo_ref_event());
1466 r55.events.push(generate_test_key_1_metadata_event("fred"));
1467 r55.events.push(generate_test_key_1_relay_list_event());
1468
1469 let cli_tester_handle: JoinHandle<Result<()>> = tokio::task::spawn_blocking(
1470 move || {
1471 let (_, test_repo) = create_proposals_with_first_rebased_and_repo_with_latest_main_and_unrebased_proposal()?;
1472 test_repo.checkout("main")?;
1473
1474 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]);
1475 p.expect("fetching updates...\r\n")?;
1476 p.expect_eventually("\r\n")?; // some updates listed here
1477 let mut c = p.expect_choice(
1478 "all proposals",
1479 vec![
1480 format!("\"{PROPOSAL_TITLE_3}\""),
1481 format!("\"{PROPOSAL_TITLE_2}\""),
1482 format!("\"{PROPOSAL_TITLE_1}\""),
1483 ],
1484 )?;
1485 c.succeeds_with(2, true, None)?;
1486 p.expect("updated proposal available (2 ahead 0 behind 'main'). existing version is 2 ahead 1 behind 'main'\r\n")?;
1487 let mut c = p.expect_choice(
1488 "",
1489 vec![
1490 format!("checkout and overwrite existing proposal branch"),
1491 format!("checkout existing outdated proposal branch"),
1492 format!("apply to current branch with `git am`"),
1493 format!("download to ./patches"),
1494 format!("back"),
1495 ],
1496 )?;
1497 c.succeeds_with(0, false, Some(0))?;
1498 p.expect("checked out new version of proposal (2 ahead 0 behind 'main'), replacing old version (2 ahead 1 behind 'main')\r\n")?;
1499 p.expect_end()?;
1500
1501 for p in [51, 52, 53, 55, 56] {
1502 relay::shutdown_relay(8000 + p)?;
1503 }
1504 Ok(())
1505 },
1506 );
1507
1508 // launch relay
1509 let _ = join!(
1510 r51.listen_until_close(),
1511 r52.listen_until_close(),
1512 r53.listen_until_close(),
1513 r55.listen_until_close(),
1514 r56.listen_until_close(),
1515 );
1516 cli_tester_handle.await??;
1517 println!("{:?}", r55.events);
1518 Ok(())
1519 }
1520 }
1521
1522 #[tokio::test]
1523 #[serial]
1524 async fn proposal_branch_checked_out() -> Result<()> {
1525 let (_, test_repo) = prep_and_run().await?;
1526 assert_eq!(
1527 get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_1)?,
1528 test_repo.get_checked_out_branch_name()?,
1529 );
1530 Ok(())
1531 }
1532
1533 #[tokio::test]
1534 #[serial]
1535 async fn proposal_branch_tip_is_most_recent_proposal_revision_tip() -> Result<()> {
1536 let (originating_repo, test_repo) = prep_and_run().await?;
1537 assert_eq!(
1538 originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?,
1539 test_repo.get_tip_of_local_branch(&get_proposal_branch_name(
1540 &test_repo,
1541 FEATURE_BRANCH_NAME_1
1542 )?)?,
1543 );
1544 Ok(())
1545 }
1546 }
1547 }
1548 }
1549}
diff --git a/tests/ngit/login.rs b/tests/ngit/login.rs
new file mode 100644
index 0000000..3bcfbf9
--- /dev/null
+++ b/tests/ngit/login.rs
@@ -0,0 +1,1168 @@
1use anyhow::Result;
2use git::GitTestRepo;
3use serial_test::serial;
4use test_utils::*;
5
6static EXPECTED_NSEC_PROMPT: &str = "login with nostr address / nsec";
7static EXPECTED_LOCAL_REPOSITORY_PROMPT: &str = "just for this repository?";
8static EXPECTED_REQUIRE_PASSWORD_PROMPT: &str = "require password?";
9static EXPECTED_SET_PASSWORD_PROMPT: &str = "encrypt with password";
10static EXPECTED_SET_PASSWORD_CONFIRM_PROMPT: &str = "confirm password";
11
12fn standard_first_time_login_encrypting_nsec() -> Result<CliTester> {
13 let test_repo = GitTestRepo::default();
14 let mut p = CliTester::new_from_dir(&test_repo.dir, ["login", "--offline"]);
15
16 p.expect_input_eventually(EXPECTED_NSEC_PROMPT)?
17 .succeeds_with(TEST_KEY_1_NSEC)?;
18
19 p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))?
20 .succeeds_with(Some(true))?;
21
22 p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))?
23 .succeeds_with(Some(true))?;
24
25 p.expect_password(EXPECTED_SET_PASSWORD_PROMPT)?
26 .with_confirmation(EXPECTED_SET_PASSWORD_CONFIRM_PROMPT)?
27 .succeeds_with(TEST_PASSWORD)?;
28
29 p.expect_end_eventually()?;
30 Ok(p)
31}
32mod with_relays {
33 use anyhow::Ok;
34 use futures::join;
35 use test_utils::relay::{shutdown_relay, ListenerReqFunc, Relay};
36
37 use super::*;
38
39 mod when_user_relay_list_aligns_with_fallback_relays {
40 // this simplifies testing
41 use super::*;
42
43 mod when_first_time_login {
44 use super::*;
45
46 // falls_back_to_fallback_relays - this is implict in the tests
47
48 mod dislays_logged_in_with_correct_name {
49
50 use super::*;
51
52 async fn run_test_displays_correct_name(
53 relay_listener1: Option<ListenerReqFunc<'_>>,
54 relay_listener2: Option<ListenerReqFunc<'_>>,
55 ) -> Result<()> {
56 let (mut r51, mut r52) = (
57 Relay::new(8051, None, relay_listener1),
58 Relay::new(8052, None, relay_listener2),
59 );
60
61 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
62 let test_repo = GitTestRepo::default();
63 let mut p = CliTester::new_from_dir(&test_repo.dir, ["login"]);
64
65 p.expect_input(EXPECTED_NSEC_PROMPT)?
66 .succeeds_with(TEST_KEY_1_NSEC)?;
67
68 p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))?
69 .succeeds_with(Some(true))?;
70
71 p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))?
72 .succeeds_with(Some(false))?;
73
74 p.expect("saved login details to local git config\r\n")?;
75
76 p.expect("searching for profile...\r\n")?;
77
78 p.expect_end_with("logged in as fred\r\n")?;
79 for p in [51, 52] {
80 shutdown_relay(8000 + p)?;
81 }
82 Ok(())
83 });
84
85 // launch relay
86 let _ = join!(r51.listen_until_close(), r52.listen_until_close(),);
87
88 cli_tester_handle.join().unwrap()?;
89 Ok(())
90 }
91
92 async fn run_test_displays_fallback_to_npub(
93 relay_listener1: Option<ListenerReqFunc<'_>>,
94 relay_listener2: Option<ListenerReqFunc<'_>>,
95 ) -> Result<()> {
96 let (mut r51, mut r52) = (
97 Relay::new(8051, None, relay_listener1),
98 Relay::new(8052, None, relay_listener2),
99 );
100
101 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
102 let test_repo = GitTestRepo::default();
103 let mut p = CliTester::new_from_dir(&test_repo.dir, ["login"]);
104
105 p.expect_input(EXPECTED_NSEC_PROMPT)?
106 .succeeds_with(TEST_KEY_1_NSEC)?;
107
108 p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))?
109 .succeeds_with(Some(true))?;
110
111 p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))?
112 .succeeds_with(Some(false))?;
113
114 p.expect("saved login details to local git config\r\n")?;
115
116 p.expect("searching for profile...\r\n")?;
117
118 p.expect("cannot extract account name from account metadata...\r\n")?;
119
120 p.expect_end_with(
121 format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str(),
122 )?;
123 for p in [51, 52] {
124 shutdown_relay(8000 + p)?;
125 }
126 Ok(())
127 });
128
129 // launch relay
130 let _ = join!(r51.listen_until_close(), r52.listen_until_close(),);
131
132 cli_tester_handle.join().unwrap()?;
133 Ok(())
134 }
135
136 #[tokio::test]
137 #[serial]
138 async fn when_latest_metadata_and_relay_list_on_all_relays() -> Result<()> {
139 run_test_displays_correct_name(
140 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
141 relay.respond_events(
142 client_id,
143 &subscription_id,
144 &vec![
145 generate_test_key_1_metadata_event("fred"),
146 generate_test_key_1_relay_list_event_same_as_fallback(),
147 ],
148 )?;
149 Ok(())
150 }),
151 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
152 relay.respond_events(
153 client_id,
154 &subscription_id,
155 &vec![
156 generate_test_key_1_metadata_event("fred"),
157 generate_test_key_1_relay_list_event_same_as_fallback(),
158 ],
159 )?;
160 Ok(())
161 }),
162 )
163 .await
164 }
165
166 mod poorly_quality_metadata_event {
167 use super::*;
168
169 #[tokio::test]
170 #[serial]
171 async fn when_metadata_contains_only_display_name() -> Result<()> {
172 run_test_displays_correct_name(
173 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
174 relay.respond_events(
175 client_id,
176 &subscription_id,
177 &vec![
178 nostr::event::EventBuilder::metadata(
179 &nostr::Metadata::new().display_name("fred"),
180 )
181 .to_event(&TEST_KEY_1_KEYS)
182 .unwrap(),
183 generate_test_key_1_relay_list_event_same_as_fallback(),
184 ],
185 )?;
186 Ok(())
187 }),
188 None,
189 )
190 .await
191 }
192
193 #[tokio::test]
194 #[serial]
195 async fn when_metadata_contains_only_displayname() -> Result<()> {
196 println!(
197 "displayName: {}",
198 nostr::Metadata::new()
199 .custom_field("displayName", "fred")
200 .custom
201 .get("displayName")
202 .unwrap()
203 );
204 println!(
205 "name: {}",
206 nostr::Metadata::new().name("fred").name.unwrap()
207 );
208
209 run_test_displays_correct_name(
210 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
211 relay.respond_events(
212 client_id,
213 &subscription_id,
214 &vec![
215 nostr::event::EventBuilder::metadata(
216 &nostr::Metadata::new()
217 .custom_field("displayName", "fred"),
218 )
219 .to_event(&TEST_KEY_1_KEYS)
220 .unwrap(),
221 generate_test_key_1_relay_list_event_same_as_fallback(),
222 ],
223 )?;
224 Ok(())
225 }),
226 None,
227 )
228 .await
229 }
230
231 #[tokio::test]
232 #[serial]
233 async fn displays_npub_when_metadata_contains_no_name_displayname_or_display_name()
234 -> Result<()> {
235 run_test_displays_fallback_to_npub(
236 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
237 relay.respond_events(
238 client_id,
239 &subscription_id,
240 &vec![
241 nostr::event::EventBuilder::metadata(
242 &nostr::Metadata::new().about("other info in metadata"),
243 )
244 .to_event(&TEST_KEY_1_KEYS)
245 .unwrap(),
246 generate_test_key_1_relay_list_event_same_as_fallback(),
247 ],
248 )?;
249 Ok(())
250 }),
251 None,
252 )
253 .await
254 }
255 }
256
257 #[tokio::test]
258 #[serial]
259 async fn when_latest_metadata_and_relay_list_on_some_relays_but_others_have_none()
260 -> Result<()> {
261 run_test_displays_correct_name(
262 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
263 relay.respond_events(
264 client_id,
265 &subscription_id,
266 &vec![
267 generate_test_key_1_metadata_event("fred"),
268 generate_test_key_1_relay_list_event_same_as_fallback(),
269 ],
270 )?;
271 Ok(())
272 }),
273 None,
274 )
275 .await
276 }
277
278 #[tokio::test]
279 #[serial]
280 async fn when_latest_metadata_only_on_relay_and_relay_list_on_another() -> Result<()>
281 {
282 run_test_displays_correct_name(
283 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
284 relay.respond_events(
285 client_id,
286 &subscription_id,
287 &vec![generate_test_key_1_metadata_event("fred")],
288 )?;
289 Ok(())
290 }),
291 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
292 relay.respond_events(
293 client_id,
294 &subscription_id,
295 &vec![generate_test_key_1_relay_list_event_same_as_fallback()],
296 )?;
297 Ok(())
298 }),
299 )
300 .await
301 }
302
303 #[tokio::test]
304 #[serial]
305 async fn when_some_relays_return_old_metadata_event() -> Result<()> {
306 run_test_displays_correct_name(
307 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
308 relay.respond_events(
309 client_id,
310 &subscription_id,
311 &vec![
312 generate_test_key_1_metadata_event("fred"),
313 generate_test_key_1_relay_list_event_same_as_fallback(),
314 ],
315 )?;
316 Ok(())
317 }),
318 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
319 relay.respond_events(
320 client_id,
321 &subscription_id,
322 &vec![generate_test_key_1_metadata_event_old("fred old")],
323 )?;
324 Ok(())
325 }),
326 )
327 .await
328 }
329
330 #[tokio::test]
331 #[serial]
332 async fn when_some_relays_return_other_users_metadata() -> Result<()> {
333 run_test_displays_correct_name(
334 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
335 relay.respond_events(
336 client_id,
337 &subscription_id,
338 &vec![generate_test_key_2_metadata_event("carole")],
339 )?;
340 Ok(())
341 }),
342 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
343 relay.respond_events(
344 client_id,
345 &subscription_id,
346 &vec![
347 generate_test_key_1_metadata_event_old("fred"),
348 generate_test_key_1_relay_list_event_same_as_fallback(),
349 ],
350 )?;
351 Ok(())
352 }),
353 )
354 .await
355 }
356
357 #[tokio::test]
358 #[serial]
359 async fn when_some_relays_return_other_event_kinds() -> Result<()> {
360 run_test_displays_correct_name(
361 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
362 let event = generate_test_key_1_kind_event(nostr::Kind::TextNote);
363 relay.respond_events(
364 client_id,
365 &subscription_id,
366 &vec![make_event_old_or_change_user(event, &TEST_KEY_1_KEYS, 0)],
367 )?;
368 Ok(())
369 }),
370 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
371 relay.respond_events(
372 client_id,
373 &subscription_id,
374 &vec![
375 generate_test_key_1_metadata_event_old("fred"),
376 generate_test_key_1_relay_list_event_same_as_fallback(),
377 ],
378 )?;
379 Ok(())
380 }),
381 )
382 .await
383 }
384
385 mod when_specifying_command_line_nsec_only {
386 use super::*;
387
388 #[tokio::test]
389 #[serial]
390 async fn displays_correct_name() -> Result<()> {
391 run_test_when_specifying_command_line_nsec_only_displays_correct_name(
392 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
393 relay.respond_events(
394 client_id,
395 &subscription_id,
396 &vec![
397 generate_test_key_1_metadata_event("fred"),
398 generate_test_key_1_relay_list_event_same_as_fallback(),
399 ],
400 )?;
401 Ok(())
402 }),
403 None,
404 )
405 .await
406 }
407 async fn run_test_when_specifying_command_line_nsec_only_displays_correct_name(
408 relay_listener1: Option<ListenerReqFunc<'_>>,
409 relay_listener2: Option<ListenerReqFunc<'_>>,
410 ) -> Result<()> {
411 let (mut r51, mut r52) = (
412 Relay::new(8051, None, relay_listener1),
413 Relay::new(8052, None, relay_listener2),
414 );
415
416 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
417 let test_repo = GitTestRepo::default();
418 let mut p = CliTester::new_from_dir(
419 &test_repo.dir,
420 ["login", "--nsec", TEST_KEY_1_NSEC],
421 );
422
423 p.expect("saved login details to local git config\r\n")?;
424
425 p.expect("searching for profile...\r\n")?;
426
427 p.expect_end_with("logged in as fred\r\n")?;
428 for p in [51, 52] {
429 shutdown_relay(8000 + p)?;
430 }
431 Ok(())
432 });
433
434 // launch relay
435 let _ = join!(r51.listen_until_close(), r52.listen_until_close(),);
436
437 cli_tester_handle.join().unwrap()?;
438 Ok(())
439 }
440 }
441 mod when_specifying_command_line_password_only {
442 use super::*;
443
444 #[tokio::test]
445 #[serial]
446 async fn displays_correct_name() -> Result<()> {
447 run_test_when_specifying_command_line_password_only_displays_correct_name(
448 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
449 relay.respond_events(
450 client_id,
451 &subscription_id,
452 &vec![
453 generate_test_key_1_metadata_event("fred"),
454 generate_test_key_1_relay_list_event_same_as_fallback(),
455 ],
456 )?;
457 Ok(())
458 }),
459 None,
460 )
461 .await
462 }
463 async fn run_test_when_specifying_command_line_password_only_displays_correct_name(
464 relay_listener1: Option<ListenerReqFunc<'_>>,
465 relay_listener2: Option<ListenerReqFunc<'_>>,
466 ) -> Result<()> {
467 let (mut r51, mut r52) = (
468 Relay::new(8051, None, relay_listener1),
469 Relay::new(8052, None, relay_listener2),
470 );
471
472 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
473 let test_repo = GitTestRepo::default();
474 CliTester::new_from_dir(
475 &test_repo.dir,
476 [
477 "login",
478 "--offline",
479 "--nsec",
480 TEST_KEY_1_NSEC,
481 "--password",
482 TEST_PASSWORD,
483 ],
484 )
485 .expect_end_eventually()?;
486
487 let mut p = CliTester::new_from_dir(
488 &test_repo.dir,
489 ["login", "--password", TEST_PASSWORD],
490 );
491
492 p.expect("searching for profile...\r\n")?;
493
494 p.expect_end_with("logged in as fred\r\n")?;
495 for p in [51, 52] {
496 shutdown_relay(8000 + p)?;
497 }
498 Ok(())
499 });
500
501 // launch relay
502 let _ = join!(r51.listen_until_close(), r52.listen_until_close(),);
503
504 cli_tester_handle.join().unwrap()?;
505 Ok(())
506 }
507 }
508
509 mod when_specifying_command_line_nsec_and_password {
510 use super::*;
511
512 #[tokio::test]
513 #[serial]
514 async fn displays_correct_name() -> Result<()> {
515 run_test_when_specifying_command_line_nsec_and_password_displays_correct_name(
516 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
517 relay.respond_events(
518 client_id,
519 &subscription_id,
520 &vec![
521 generate_test_key_1_metadata_event("fred"),
522 generate_test_key_1_relay_list_event_same_as_fallback(),
523 ],
524 )?;
525 Ok(())
526 }),
527 None,
528 ).await
529 }
530 async fn run_test_when_specifying_command_line_nsec_and_password_displays_correct_name(
531 relay_listener1: Option<ListenerReqFunc<'_>>,
532 relay_listener2: Option<ListenerReqFunc<'_>>,
533 ) -> Result<()> {
534 let (mut r51, mut r52) = (
535 Relay::new(8051, None, relay_listener1),
536 Relay::new(8052, None, relay_listener2),
537 );
538
539 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
540 let test_repo = GitTestRepo::default();
541 let mut p = CliTester::new_from_dir(
542 &test_repo.dir,
543 [
544 "login",
545 "--nsec",
546 TEST_KEY_1_NSEC,
547 "--password",
548 TEST_PASSWORD,
549 ],
550 );
551
552 p.expect("saved login details to local git config\r\n")?;
553
554 p.expect("searching for profile...\r\n")?;
555
556 p.expect_end_with("logged in as fred\r\n")?;
557 for p in [51, 52] {
558 shutdown_relay(8000 + p)?;
559 }
560 Ok(())
561 });
562
563 // launch relay
564 let _ = join!(r51.listen_until_close(), r52.listen_until_close(),);
565
566 cli_tester_handle.join().unwrap()?;
567 Ok(())
568 }
569 }
570 }
571
572 mod when_no_metadata_found {
573 use super::*;
574
575 #[tokio::test]
576 #[serial]
577 async fn warm_user_and_displays_npub() -> Result<()> {
578 run_test_when_no_metadata_found_warns_user_and_uses_npub(None, None).await
579 }
580
581 async fn run_test_when_no_metadata_found_warns_user_and_uses_npub(
582 relay_listener1: Option<ListenerReqFunc<'_>>,
583 relay_listener2: Option<ListenerReqFunc<'_>>,
584 ) -> Result<()> {
585 let (mut r51, mut r52) = (
586 Relay::new(8051, None, relay_listener1),
587 Relay::new(8052, None, relay_listener2),
588 );
589
590 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
591 let test_repo = GitTestRepo::default();
592 let mut p = CliTester::new_from_dir(&test_repo.dir, ["login"]);
593
594 p.expect_input(EXPECTED_NSEC_PROMPT)?
595 .succeeds_with(TEST_KEY_1_NSEC)?;
596
597 p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))?
598 .succeeds_with(Some(true))?;
599
600 p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))?
601 .succeeds_with(Some(false))?;
602
603 p.expect("saved login details to local git config\r\n")?;
604
605 p.expect("searching for profile...\r\n")?;
606
607 p.expect("cannot find profile...\r\n")?;
608
609 p.expect_end_with(format!("logged in as {TEST_KEY_1_NPUB}\r\n").as_str())?;
610 for p in [51, 52] {
611 shutdown_relay(8000 + p)?;
612 }
613 Ok(())
614 });
615
616 // launch relay
617 let _ = join!(r51.listen_until_close(), r52.listen_until_close(),);
618
619 cli_tester_handle.join().unwrap()?;
620 Ok(())
621 }
622 }
623
624 mod when_metadata_but_no_relay_list_found {
625 use super::*;
626
627 #[tokio::test]
628 #[serial]
629 async fn warm_user_and_displays_name() -> Result<()> {
630 run_test_when_no_relay_list_found_warns_user_and_uses_npub(
631 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
632 relay.respond_events(
633 client_id,
634 &subscription_id,
635 &vec![generate_test_key_1_metadata_event("fred")],
636 )?;
637 Ok(())
638 }),
639 None,
640 )
641 .await
642 }
643
644 async fn run_test_when_no_relay_list_found_warns_user_and_uses_npub(
645 relay_listener1: Option<ListenerReqFunc<'_>>,
646 relay_listener2: Option<ListenerReqFunc<'_>>,
647 ) -> Result<()> {
648 let (mut r51, mut r52) = (
649 Relay::new(8051, None, relay_listener1),
650 Relay::new(8052, None, relay_listener2),
651 );
652
653 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
654 let test_repo = GitTestRepo::default();
655 let mut p = CliTester::new_from_dir(&test_repo.dir, ["login"]);
656
657 p.expect_input(EXPECTED_NSEC_PROMPT)?
658 .succeeds_with(TEST_KEY_1_NSEC)?;
659
660 p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))?
661 .succeeds_with(Some(true))?;
662
663 p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))?
664 .succeeds_with(Some(false))?;
665
666 p.expect("saved login details to local git config\r\n")?;
667
668 p.expect("searching for profile...\r\n")?;
669
670 p.expect("cannot find your relay list. consider using another nostr client to create one to enhance your nostr experience.\r\n")?;
671
672 p.expect_end_with("logged in as fred\r\n")?;
673 for p in [51, 52] {
674 shutdown_relay(8000 + p)?;
675 }
676 Ok(())
677 });
678
679 // launch relay
680 let _ = join!(r51.listen_until_close(), r52.listen_until_close(),);
681
682 cli_tester_handle.join().unwrap()?;
683 Ok(())
684 }
685 }
686 }
687
688 mod when_second_time_login_and_details_already_fetched {
689 use super::*;
690
691 mod uses_cache_and_stores_and_retrieves_ncryptsec_from_local_git_config {
692 use super::*;
693
694 #[tokio::test]
695 #[serial]
696 async fn dislays_logged_in_with_correct_name() -> Result<()> {
697 run_test_dislays_logged_in_with_correct_name(Some(
698 &|relay, client_id, subscription_id, _| -> Result<()> {
699 relay.respond_events(
700 client_id,
701 &subscription_id,
702 &vec![
703 generate_test_key_1_metadata_event("fred"),
704 generate_test_key_1_relay_list_event_same_as_fallback(),
705 ],
706 )?;
707 Ok(())
708 },
709 ))
710 .await
711 }
712 async fn run_test_dislays_logged_in_with_correct_name(
713 relay_listener: Option<ListenerReqFunc<'_>>,
714 ) -> Result<()> {
715 let (mut r51, mut r52) = (
716 Relay::new(8051, None, relay_listener),
717 Relay::new(8052, None, None),
718 );
719
720 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
721 let test_repo = GitTestRepo::default();
722 let mut p = CliTester::new_from_dir(
723 &test_repo.dir,
724 [
725 "login",
726 "--nsec",
727 TEST_KEY_1_NSEC,
728 "--password",
729 TEST_PASSWORD,
730 ],
731 );
732
733 p.expect("saved login details to local git config\r\n")?;
734
735 p.expect_end_eventually_with("logged in as fred\r\n")?;
736
737 for p in [51, 52] {
738 shutdown_relay(8000 + p)?;
739 }
740
741 let mut p = CliTester::new_from_dir(
742 &test_repo.dir,
743 ["login", "--password", TEST_PASSWORD],
744 );
745
746 p.expect_end_eventually_with("logged in as fred\r\n")?;
747
748 Ok(())
749 });
750
751 // launch relay
752 let _ = join!(r51.listen_until_close(), r52.listen_until_close(),);
753
754 cli_tester_handle.join().unwrap()?;
755
756 Ok(())
757 }
758 }
759 }
760 }
761 mod when_user_relay_list_contains_write_relays_not_in_fallback_list {
762 use super::*;
763 mod when_latest_metadata_not_on_fallback_relays_only_on_relays_in_user_list {
764 use super::*;
765 async fn run_test_displays_correct_name(
766 relay_listener1: Option<ListenerReqFunc<'_>>,
767 relay_listener2: Option<ListenerReqFunc<'_>>,
768 ) -> Result<()> {
769 let (mut r51, mut r52, mut r53, mut r55) = (
770 Relay::new(8051, None, relay_listener1),
771 Relay::new(8052, None, None),
772 Relay::new(8053, None, relay_listener2),
773 Relay::new(8055, None, None),
774 );
775
776 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
777 let test_repo = GitTestRepo::default();
778 let mut p = CliTester::new_from_dir(&test_repo.dir, ["login"]);
779
780 p.expect_input(EXPECTED_NSEC_PROMPT)?
781 .succeeds_with(TEST_KEY_1_NSEC)?;
782
783 p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))?
784 .succeeds_with(Some(true))?;
785
786 p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))?
787 .succeeds_with(Some(false))?;
788
789 p.expect("saved login details to local git config\r\n")?;
790
791 p.expect("searching for profile...\r\n")?;
792
793 p.expect_end_with("logged in as fred\r\n")?;
794 for p in [51, 52, 53, 55] {
795 shutdown_relay(8000 + p)?;
796 }
797 Ok(())
798 });
799
800 // launch relay
801 let _ = join!(
802 r51.listen_until_close(),
803 r52.listen_until_close(),
804 r53.listen_until_close(),
805 r55.listen_until_close(),
806 );
807
808 cli_tester_handle.join().unwrap()?;
809 Ok(())
810 }
811
812 /// this also tests that additional relays are queried
813 #[tokio::test]
814 #[serial]
815 async fn displays_correct_name() -> Result<()> {
816 run_test_displays_correct_name(
817 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
818 relay.respond_events(
819 client_id,
820 &subscription_id,
821 &vec![
822 generate_test_key_1_metadata_event_old("Fred"),
823 generate_test_key_1_relay_list_event(),
824 ],
825 )?;
826 Ok(())
827 }),
828 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
829 relay.respond_events(
830 client_id,
831 &subscription_id,
832 &vec![
833 generate_test_key_1_metadata_event("fred"),
834 generate_test_key_1_relay_list_event(),
835 ],
836 )?;
837 Ok(())
838 }),
839 )
840 .await
841 }
842 }
843 }
844}
845
846/// using the offline flag simplifies the test. relay interaction is tested
847/// seperately
848mod with_offline_flag {
849 use super::*;
850 mod when_first_time_login {
851 use super::*;
852
853 #[test]
854 fn prompts_for_nsec_and_password() -> Result<()> {
855 standard_first_time_login_encrypting_nsec()?;
856 Ok(())
857 }
858
859 #[test]
860 fn succeeds_with_text_logged_in_as_npub() -> Result<()> {
861 let test_repo = GitTestRepo::default();
862 let mut p = CliTester::new_from_dir(&test_repo.dir, ["login", "--offline"]);
863
864 p.expect_input(EXPECTED_NSEC_PROMPT)?
865 .succeeds_with(TEST_KEY_1_NSEC)?;
866
867 p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))?
868 .succeeds_with(Some(true))?;
869
870 p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))?
871 .succeeds_with(Some(true))?;
872
873 p.expect_password(EXPECTED_SET_PASSWORD_PROMPT)?
874 .with_confirmation(EXPECTED_SET_PASSWORD_CONFIRM_PROMPT)?
875 .succeeds_with(TEST_PASSWORD)?;
876
877 p.expect("saved login details to local git config\r\n")?;
878
879 p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str())
880 }
881
882 #[test]
883 fn succeeds_with_hex_secret_key_in_place_of_nsec() -> Result<()> {
884 let test_repo = GitTestRepo::default();
885 let mut p = CliTester::new_from_dir(&test_repo.dir, ["login", "--offline"]);
886
887 p.expect_input(EXPECTED_NSEC_PROMPT)?
888 .succeeds_with(TEST_KEY_1_SK_HEX)?;
889
890 p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))?
891 .succeeds_with(Some(true))?;
892
893 p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))?
894 .succeeds_with(Some(true))?;
895
896 p.expect_password(EXPECTED_SET_PASSWORD_PROMPT)?
897 .with_confirmation(EXPECTED_SET_PASSWORD_CONFIRM_PROMPT)?
898 .succeeds_with(TEST_PASSWORD)?;
899
900 p.expect("saved login details to local git config\r\n")?;
901
902 p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str())
903 }
904
905 mod when_invalid_nsec {
906 use super::*;
907
908 #[test]
909 fn prompts_for_nsec_until_valid() -> Result<()> {
910 let invalid_nsec_response =
911 "invalid. try again with nostr address / bunker uri / nsec";
912
913 let test_repo = GitTestRepo::default();
914 let mut p = CliTester::new_from_dir(&test_repo.dir, ["login", "--offline"]);
915
916 p.expect_input(EXPECTED_NSEC_PROMPT)?
917 // this behaviour is intentional. rejecting the response with dialoguer
918 // hides the original input from the user so they
919 // cannot see the mistake they made.
920 .succeeds_with(TEST_INVALID_NSEC)?;
921
922 p.expect_input(invalid_nsec_response)?
923 .succeeds_with(TEST_INVALID_NSEC)?;
924
925 p.expect_input(invalid_nsec_response)?
926 .succeeds_with(TEST_KEY_1_NSEC)?;
927
928 p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))?
929 .succeeds_with(Some(true))?;
930
931 p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))?
932 .succeeds_with(Some(true))?;
933
934 p.expect_password(EXPECTED_SET_PASSWORD_PROMPT)?
935 .with_confirmation(EXPECTED_SET_PASSWORD_CONFIRM_PROMPT)?
936 .succeeds_with(TEST_PASSWORD)?;
937
938 p.expect("saved login details to local git config\r\n")?;
939
940 p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str())
941 }
942 }
943 }
944
945 mod when_called_with_nsec_parameter_only {
946 use super::*;
947
948 #[test]
949 fn valid_nsec_param_succeeds_without_prompts() -> Result<()> {
950 let test_repo = GitTestRepo::default();
951 let mut p = CliTester::new_from_dir(
952 &test_repo.dir,
953 ["login", "--offline", "--nsec", TEST_KEY_1_NSEC],
954 );
955
956 p.expect("saved login details to local git config\r\n")?;
957
958 p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str())
959 }
960
961 #[test]
962 fn forgets_identity() -> Result<()> {
963 let test_repo = GitTestRepo::default();
964 let mut p = CliTester::new_from_dir(
965 &test_repo.dir,
966 ["login", "--offline", "--nsec", TEST_KEY_1_NSEC],
967 );
968
969 p.expect("saved login details to local git config\r\n")?;
970
971 p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str())?;
972
973 p = CliTester::new_from_dir(&test_repo.dir, ["login", "--offline"]);
974
975 p.expect_input(EXPECTED_NSEC_PROMPT)?
976 .succeeds_with(TEST_KEY_1_NSEC)?;
977
978 p.exit()
979 }
980
981 mod when_logging_in_as_different_nsec {
982 use super::*;
983
984 #[test]
985 fn valid_nsec_param_succeeds_without_prompts_and_logs_in() -> Result<()> {
986 standard_first_time_login_encrypting_nsec()?.exit()?;
987 let test_repo = GitTestRepo::default();
988 let mut p = CliTester::new_from_dir(
989 &test_repo.dir,
990 ["login", "--offline", "--nsec", TEST_KEY_2_NSEC],
991 );
992
993 p.expect("saved login details to local git config\r\n")?;
994
995 p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_2_NPUB).as_str())
996 }
997 }
998 #[test]
999 fn invalid_nsec_param_fails_without_prompts() -> Result<()> {
1000 let test_repo = GitTestRepo::default();
1001 let mut p = CliTester::new_from_dir(
1002 &test_repo.dir,
1003 ["login", "--offline", "--nsec", TEST_INVALID_NSEC],
1004 );
1005
1006 p.expect_end_with(
1007 "Error: invalid nsec parameter\r\n\r\nCaused by:\r\n Invalid secret key\r\n",
1008 )
1009 }
1010 }
1011
1012 mod when_called_with_nsec_and_password_parameter {
1013 use super::*;
1014
1015 #[test]
1016 fn valid_nsec_param_succeeds_without_prompts() -> Result<()> {
1017 let test_repo = GitTestRepo::default();
1018 let mut p = CliTester::new_from_dir(
1019 &test_repo.dir,
1020 [
1021 "login",
1022 "--offline",
1023 "--nsec",
1024 TEST_KEY_1_NSEC,
1025 "--password",
1026 TEST_PASSWORD,
1027 ],
1028 );
1029 p.expect("saved login details to local git config\r\n")?;
1030 p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str())
1031 }
1032
1033 #[test]
1034 fn parameters_can_be_called_globally() -> Result<()> {
1035 let test_repo = GitTestRepo::default();
1036 let mut p = CliTester::new_from_dir(
1037 &test_repo.dir,
1038 [
1039 "--nsec",
1040 TEST_KEY_1_NSEC,
1041 "--password",
1042 TEST_PASSWORD,
1043 "login",
1044 "--offline",
1045 ],
1046 );
1047 p.expect("saved login details to local git config\r\n")?;
1048 p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str())
1049 }
1050
1051 mod when_logging_in_as_different_nsec {
1052 use super::*;
1053
1054 #[test]
1055 fn valid_nsec_param_succeeds_without_prompts_and_logs_in() -> Result<()> {
1056 standard_first_time_login_encrypting_nsec()?.exit()?;
1057 let test_repo = GitTestRepo::default();
1058 let mut p = CliTester::new_from_dir(
1059 &test_repo.dir,
1060 [
1061 "login",
1062 "--offline",
1063 "--nsec",
1064 TEST_KEY_2_NSEC,
1065 "--password",
1066 TEST_PASSWORD,
1067 ],
1068 );
1069 p.expect("saved login details to local git config\r\n")?;
1070 p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_2_NPUB).as_str())
1071 }
1072 }
1073
1074 mod when_provided_with_new_password {
1075 use super::*;
1076
1077 #[test]
1078 fn password_changes() -> Result<()> {
1079 standard_first_time_login_encrypting_nsec()?.exit()?;
1080 let test_repo = GitTestRepo::default();
1081 let mut p = CliTester::new_from_dir(
1082 &test_repo.dir,
1083 [
1084 "login",
1085 "--offline",
1086 "--nsec",
1087 TEST_KEY_1_NSEC,
1088 "--password",
1089 TEST_INVALID_PASSWORD,
1090 ],
1091 );
1092 p.expect("saved login details to local git config\r\n")?;
1093 p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str())?;
1094
1095 CliTester::new_from_dir(
1096 &test_repo.dir,
1097 ["--password", TEST_INVALID_PASSWORD, "login", "--offline"],
1098 )
1099 .expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str())
1100 }
1101 }
1102
1103 #[test]
1104 fn invalid_nsec_param_fails_without_prompts() -> Result<()> {
1105 let test_repo = GitTestRepo::default();
1106 let mut p = CliTester::new_from_dir(
1107 &test_repo.dir,
1108 [
1109 "login",
1110 "--offline",
1111 "--nsec",
1112 TEST_INVALID_NSEC,
1113 "--password",
1114 TEST_PASSWORD,
1115 ],
1116 );
1117 p.expect_end_with(
1118 "Error: invalid nsec parameter\r\n\r\nCaused by:\r\n Invalid secret key\r\n",
1119 )
1120 }
1121 }
1122
1123 mod when_weak_password {
1124 use super::*;
1125
1126 #[test]
1127 // combined into a single test as it is computationally expensive to run
1128 fn warns_it_might_take_a_few_seconds_then_succeeds_then_second_login_prompts_for_password_then_warns_again_then_succeeds()
1129 -> Result<()> {
1130 let test_repo = GitTestRepo::default();
1131 let mut p =
1132 CliTester::new_with_timeout_from_dir(15000, &test_repo.dir, ["login", "--offline"]);
1133 p.expect_input(EXPECTED_NSEC_PROMPT)?
1134 .succeeds_with(TEST_KEY_1_NSEC)?;
1135
1136 p.expect_confirm(EXPECTED_LOCAL_REPOSITORY_PROMPT, Some(false))?
1137 .succeeds_with(Some(true))?;
1138
1139 p.expect_confirm(EXPECTED_REQUIRE_PASSWORD_PROMPT, Some(false))?
1140 .succeeds_with(Some(true))?;
1141
1142 p.expect_password(EXPECTED_SET_PASSWORD_PROMPT)?
1143 .with_confirmation(EXPECTED_SET_PASSWORD_CONFIRM_PROMPT)?
1144 .succeeds_with(TEST_WEAK_PASSWORD)?;
1145
1146 p.expect("this may take a few seconds...\r\n")?;
1147
1148 p.expect("saved login details to local git config\r\n")?;
1149
1150 p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str())
1151
1152 // commented out as 'login' command now assumes you want to
1153 // login as a new user
1154 // p = CliTester::new_with_timeout(10000, ["login",
1155 // "--offline"]);
1156
1157 // p.expect(format!("login as {}\r\n",
1158 // TEST_KEY_1_NPUB).as_str())?
1159 // .expect_password(EXPECTED_PASSWORD_PROMPT)?
1160 // .succeeds_with(TEST_WEAK_PASSWORD)?;
1161
1162 // p.expect("this may take a few seconds...\r\n")?;
1163
1164 // p.expect_end_with(format!("logged in as {}\r\n",
1165 // TEST_KEY_1_NPUB).as_str())
1166 }
1167 }
1168}
diff --git a/tests/ngit/main.rs b/tests/ngit/main.rs
new file mode 100644
index 0000000..fe852df
--- /dev/null
+++ b/tests/ngit/main.rs
@@ -0,0 +1,6 @@
1mod init;
2mod list;
3mod login;
4mod pull;
5mod push;
6mod send;
diff --git a/tests/ngit/pull.rs b/tests/ngit/pull.rs
new file mode 100644
index 0000000..6637859
--- /dev/null
+++ b/tests/ngit/pull.rs
@@ -0,0 +1,615 @@
1use anyhow::Result;
2use futures::join;
3use serial_test::serial;
4use test_utils::{git::GitTestRepo, relay::Relay, *};
5
6mod when_main_is_checked_out {
7 use super::*;
8
9 mod cli_prompts {
10 use super::*;
11
12 #[tokio::test]
13 #[serial]
14 async fn cli_show_error() -> Result<()> {
15 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
16 Relay::new(8051, None, None),
17 Relay::new(8052, None, None),
18 Relay::new(8053, None, None),
19 Relay::new(8055, None, None),
20 Relay::new(8056, None, None),
21 );
22
23 r51.events.push(generate_test_key_1_relay_list_event());
24 r51.events.push(generate_test_key_1_metadata_event("fred"));
25 r51.events.push(generate_repo_ref_event());
26
27 r55.events.push(generate_repo_ref_event());
28 r55.events.push(generate_test_key_1_metadata_event("fred"));
29 r55.events.push(generate_test_key_1_relay_list_event());
30
31 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
32 cli_tester_create_proposals()?;
33
34 let test_repo = create_repo_with_proposal_branch_pulled_and_checkedout(1)?;
35
36 test_repo.checkout("main")?;
37
38 let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]);
39 p.expect("Error: checkout a branch associated with a proposal first\r\n")?;
40 p.expect_end()?;
41
42 for p in [51, 52, 53, 55, 56] {
43 relay::shutdown_relay(8000 + p)?;
44 }
45 Ok(())
46 });
47
48 // launch relay
49 let _ = join!(
50 r51.listen_until_close(),
51 r52.listen_until_close(),
52 r53.listen_until_close(),
53 r55.listen_until_close(),
54 r56.listen_until_close(),
55 );
56 cli_tester_handle.join().unwrap()?;
57 Ok(())
58 }
59 }
60}
61
62mod when_branch_doesnt_exist {
63 use super::*;
64
65 mod cli_prompts {
66 use super::*;
67
68 #[tokio::test]
69 #[serial]
70 async fn cli_show_error() -> Result<()> {
71 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
72 Relay::new(8051, None, None),
73 Relay::new(8052, None, None),
74 Relay::new(8053, None, None),
75 Relay::new(8055, None, None),
76 Relay::new(8056, None, None),
77 );
78
79 r51.events.push(generate_test_key_1_relay_list_event());
80 r51.events.push(generate_test_key_1_metadata_event("fred"));
81 r51.events.push(generate_repo_ref_event());
82
83 r55.events.push(generate_repo_ref_event());
84 r55.events.push(generate_test_key_1_metadata_event("fred"));
85 r55.events.push(generate_test_key_1_relay_list_event());
86
87 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
88 cli_tester_create_proposals()?;
89
90 let test_repo = GitTestRepo::default();
91 test_repo.populate()?;
92
93 test_repo.create_branch("random-name")?;
94 test_repo.checkout("random-name")?;
95
96 let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]);
97 p.expect("fetching updates...\r\n")?;
98 p.expect_eventually("\r\n")?; // some updates listed here
99 p.expect("Error: cannot find proposal that matches the current branch name\r\n")?;
100
101 p.expect_end()?;
102
103 for p in [51, 52, 53, 55, 56] {
104 relay::shutdown_relay(8000 + p)?;
105 }
106 Ok(())
107 });
108
109 // launch relay
110 let _ = join!(
111 r51.listen_until_close(),
112 r52.listen_until_close(),
113 r53.listen_until_close(),
114 r55.listen_until_close(),
115 r56.listen_until_close(),
116 );
117 cli_tester_handle.join().unwrap()?;
118 Ok(())
119 }
120 }
121}
122
123mod when_branch_is_checked_out {
124 use super::*;
125
126 mod when_branch_is_up_to_date {
127 use super::*;
128
129 mod cli_prompts {
130 use super::*;
131 #[tokio::test]
132 #[serial]
133 async fn cli_show_up_to_date() -> Result<()> {
134 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
135 Relay::new(8051, None, None),
136 Relay::new(8052, None, None),
137 Relay::new(8053, None, None),
138 Relay::new(8055, None, None),
139 Relay::new(8056, None, None),
140 );
141
142 r51.events.push(generate_test_key_1_relay_list_event());
143 r51.events.push(generate_test_key_1_metadata_event("fred"));
144 r51.events.push(generate_repo_ref_event());
145
146 r55.events.push(generate_repo_ref_event());
147 r55.events.push(generate_test_key_1_metadata_event("fred"));
148 r55.events.push(generate_test_key_1_relay_list_event());
149
150 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
151 let (_, test_repo) =
152 create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?;
153
154 let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]);
155 p.expect("fetching updates...\r\n")?;
156 p.expect_eventually("\r\n")?; // some updates listed here
157 p.expect("branch already up-to-date\r\n")?;
158 p.expect_end()?;
159
160 for p in [51, 52, 53, 55, 56] {
161 relay::shutdown_relay(8000 + p)?;
162 }
163 Ok(())
164 });
165
166 // launch relay
167 let _ = join!(
168 r51.listen_until_close(),
169 r52.listen_until_close(),
170 r53.listen_until_close(),
171 r55.listen_until_close(),
172 r56.listen_until_close(),
173 );
174 cli_tester_handle.join().unwrap()?;
175 Ok(())
176 }
177 }
178 }
179
180 mod when_branch_is_behind {
181 use super::*;
182
183 async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> {
184 // fallback (51,52) user write (53, 55) repo (55, 56)
185 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
186 Relay::new(8051, None, None),
187 Relay::new(8052, None, None),
188 Relay::new(8053, None, None),
189 Relay::new(8055, None, None),
190 Relay::new(8056, None, None),
191 );
192
193 r51.events.push(generate_test_key_1_relay_list_event());
194 r51.events.push(generate_test_key_1_metadata_event("fred"));
195 r51.events.push(generate_repo_ref_event());
196
197 r55.events.push(generate_repo_ref_event());
198 r55.events.push(generate_test_key_1_metadata_event("fred"));
199 r55.events.push(generate_test_key_1_relay_list_event());
200
201 let cli_tester_handle =
202 std::thread::spawn(move || -> Result<(GitTestRepo, GitTestRepo)> {
203 let (originating_repo, test_repo) =
204 create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?;
205
206 let branch_name =
207 remove_latest_commit_so_proposal_branch_is_behind_and_checkout_main(
208 &test_repo,
209 )?;
210 test_repo.checkout(&branch_name)?;
211
212 let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]);
213 p.expect_end_eventually()?;
214
215 for p in [51, 52, 53, 55, 56] {
216 relay::shutdown_relay(8000 + p)?;
217 }
218 Ok((originating_repo, test_repo))
219 });
220
221 // launch relay
222 let _ = join!(
223 r51.listen_until_close(),
224 r52.listen_until_close(),
225 r53.listen_until_close(),
226 r55.listen_until_close(),
227 r56.listen_until_close(),
228 );
229 let res = cli_tester_handle.join().unwrap()?;
230
231 Ok(res)
232 }
233
234 mod cli_prompts {
235 use super::*;
236
237 #[tokio::test]
238 #[serial]
239 async fn cli_applied_1_commit() -> Result<()> {
240 // fallback (51,52) user write (53, 55) repo (55, 56)
241 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
242 Relay::new(8051, None, None),
243 Relay::new(8052, None, None),
244 Relay::new(8053, None, None),
245 Relay::new(8055, None, None),
246 Relay::new(8056, None, None),
247 );
248
249 r51.events.push(generate_test_key_1_relay_list_event());
250 r51.events.push(generate_test_key_1_metadata_event("fred"));
251 r51.events.push(generate_repo_ref_event());
252
253 r55.events.push(generate_repo_ref_event());
254 r55.events.push(generate_test_key_1_metadata_event("fred"));
255 r55.events.push(generate_test_key_1_relay_list_event());
256
257 let cli_tester_handle =
258 std::thread::spawn(move || -> Result<(GitTestRepo, GitTestRepo)> {
259 let (originating_repo, test_repo) =
260 create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?;
261
262 let branch_name =
263 remove_latest_commit_so_proposal_branch_is_behind_and_checkout_main(
264 &test_repo,
265 )?;
266 test_repo.checkout(&branch_name)?;
267
268 let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]);
269 p.expect("fetching updates...\r\n")?;
270 p.expect_eventually("\r\n")?; // some updates listed here
271 p.expect_end_with("applied 1 new commits\r\n")?;
272
273 for p in [51, 52, 53, 55, 56] {
274 relay::shutdown_relay(8000 + p)?;
275 }
276 Ok((originating_repo, test_repo))
277 });
278
279 // launch relay
280 let _ = join!(
281 r51.listen_until_close(),
282 r52.listen_until_close(),
283 r53.listen_until_close(),
284 r55.listen_until_close(),
285 r56.listen_until_close(),
286 );
287 cli_tester_handle.join().unwrap()?;
288
289 Ok(())
290 }
291 }
292
293 #[tokio::test]
294 #[serial]
295 async fn proposal_branch_tip_is_most_recent_patch() -> Result<()> {
296 let (originating_repo, test_repo) = prep_and_run().await?;
297 assert_eq!(
298 originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?,
299 test_repo.get_tip_of_local_branch(&get_proposal_branch_name(
300 &test_repo,
301 FEATURE_BRANCH_NAME_1
302 )?)?,
303 );
304 Ok(())
305 }
306 }
307
308 mod when_latest_proposal_amended_locally {
309 use super::*;
310
311 mod cli_prompts {
312 use super::*;
313
314 #[tokio::test]
315 #[serial]
316 async fn cli_output_correct() -> Result<()> {
317 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
318 Relay::new(8051, None, None),
319 Relay::new(8052, None, None),
320 Relay::new(8053, None, None),
321 Relay::new(8055, None, None),
322 Relay::new(8056, None, None),
323 );
324
325 r51.events.push(generate_test_key_1_relay_list_event());
326 r51.events.push(generate_test_key_1_metadata_event("fred"));
327 r51.events.push(generate_repo_ref_event());
328
329 r55.events.push(generate_repo_ref_event());
330 r55.events.push(generate_test_key_1_metadata_event("fred"));
331 r55.events.push(generate_test_key_1_relay_list_event());
332
333 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
334 let (_, test_repo) =
335 create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?;
336
337 amend_last_commit(&test_repo, "add ammended-commit.md")?;
338
339 // run test
340 let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]);
341 p.expect("fetching updates...\r\n")?;
342 p.expect_eventually("\r\n")?; // some updates listed here
343 p.expect(
344 "you have an amended/rebase version the proposal that is unpublished\r\n",
345 )?;
346 p.expect("you have previously applied the latest version of the proposal (2 ahead 0 behind 'main') but your local proposal branch has amended or rebased it (2 ahead 0 behind 'main')\r\n")?;
347 p.expect("to view the latest proposal but retain your changes:\r\n")?;
348 p.expect(" 1) create a new branch off the tip commit of this one to store your changes\r\n")?;
349 p.expect(" 2) run `ngit list` and checkout the latest published version of this proposal\r\n")?;
350 p.expect("if you are confident in your changes consider running `ngit push --force`\r\n")?;
351 p.expect_end()?;
352
353 for p in [51, 52, 53, 55, 56] {
354 relay::shutdown_relay(8000 + p)?;
355 }
356 Ok(())
357 });
358
359 // launch relay
360 let _ = join!(
361 r51.listen_until_close(),
362 r52.listen_until_close(),
363 r53.listen_until_close(),
364 r55.listen_until_close(),
365 r56.listen_until_close(),
366 );
367 cli_tester_handle.join().unwrap()?;
368 println!("{:?}", r55.events);
369 Ok(())
370 }
371 }
372 }
373
374 mod when_local_commits_on_uptodate_proposal {
375 use super::*;
376 async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> {
377 // fallback (51,52) user write (53, 55) repo (55, 56)
378 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
379 Relay::new(8051, None, None),
380 Relay::new(8052, None, None),
381 Relay::new(8053, None, None),
382 Relay::new(8055, None, None),
383 Relay::new(8056, None, None),
384 );
385
386 r51.events.push(generate_test_key_1_relay_list_event());
387 r51.events.push(generate_test_key_1_metadata_event("fred"));
388 r51.events.push(generate_repo_ref_event());
389
390 r55.events.push(generate_repo_ref_event());
391 r55.events.push(generate_test_key_1_metadata_event("fred"));
392 r55.events.push(generate_test_key_1_relay_list_event());
393
394 let cli_tester_handle = std::thread::spawn(
395 move || -> Result<(GitTestRepo, GitTestRepo)> {
396 let (originating_repo, test_repo) =
397 create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?;
398
399 // add another commit (so we have a local branch 1 ahead)
400 std::fs::write(test_repo.dir.join("ammended-commit.md"), "some content")?;
401 test_repo.stage_and_commit("add ammended-commit.md")?;
402
403 // run test
404 let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]);
405 p.expect("fetching updates...\r\n")?;
406 p.expect_eventually("\r\n")?; // some updates listed here
407 p.expect("local proposal branch exists with 1 unpublished commits on top of the most up-to-date version of the proposal\r\n")?;
408 p.expect_end()?;
409
410 for p in [51, 52, 53, 55, 56] {
411 relay::shutdown_relay(8000 + p)?;
412 }
413 Ok((originating_repo, test_repo))
414 },
415 );
416
417 // launch relay
418 let _ = join!(
419 r51.listen_until_close(),
420 r52.listen_until_close(),
421 r53.listen_until_close(),
422 r55.listen_until_close(),
423 r56.listen_until_close(),
424 );
425 let res = cli_tester_handle.join().unwrap()?;
426
427 Ok(res)
428 }
429
430 mod cli_prompts {
431 use super::*;
432
433 #[tokio::test]
434 #[serial]
435 async fn prompts_to_choose_from_proposal_titles() -> Result<()> {
436 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
437 Relay::new(8051, None, None),
438 Relay::new(8052, None, None),
439 Relay::new(8053, None, None),
440 Relay::new(8055, None, None),
441 Relay::new(8056, None, None),
442 );
443
444 r51.events.push(generate_test_key_1_relay_list_event());
445 r51.events.push(generate_test_key_1_metadata_event("fred"));
446 r51.events.push(generate_repo_ref_event());
447
448 r55.events.push(generate_repo_ref_event());
449 r55.events.push(generate_test_key_1_metadata_event("fred"));
450 r55.events.push(generate_test_key_1_relay_list_event());
451
452 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
453 let (_, test_repo) =
454 create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?;
455
456 // add another commit (so we have a local branch 1 ahead)
457 std::fs::write(test_repo.dir.join("ammended-commit.md"), "some content")?;
458 test_repo.stage_and_commit("add ammended-commit.md")?;
459
460 // run test
461 let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]);
462 p.expect("fetching updates...\r\n")?;
463 p.expect_eventually("\r\n")?; // some updates listed here
464 p.expect("local proposal branch exists with 1 unpublished commits on top of the most up-to-date version of the proposal\r\n")?;
465 p.expect_end()?;
466
467 for p in [51, 52, 53, 55, 56] {
468 relay::shutdown_relay(8000 + p)?;
469 }
470 Ok(())
471 });
472
473 // launch relay
474 let _ = join!(
475 r51.listen_until_close(),
476 r52.listen_until_close(),
477 r53.listen_until_close(),
478 r55.listen_until_close(),
479 r56.listen_until_close(),
480 );
481 cli_tester_handle.join().unwrap()?;
482 println!("{:?}", r55.events);
483 Ok(())
484 }
485 }
486
487 #[tokio::test]
488 #[serial]
489 async fn didnt_overwrite_local_appendments() -> Result<()> {
490 let (originating_repo, test_repo) = prep_and_run().await?;
491 assert_ne!(
492 test_repo.get_tip_of_local_branch(&get_proposal_branch_name(
493 &test_repo,
494 FEATURE_BRANCH_NAME_1
495 )?)?,
496 originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?,
497 );
498 Ok(())
499 }
500 }
501 mod when_latest_event_rebases_branch {
502 use tokio::task::JoinHandle;
503
504 use super::*;
505
506 async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> {
507 // fallback (51,52) user write (53, 55) repo (55, 56)
508 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
509 Relay::new(8051, None, None),
510 Relay::new(8052, None, None),
511 Relay::new(8053, None, None),
512 Relay::new(8055, None, None),
513 Relay::new(8056, None, None),
514 );
515
516 r51.events.push(generate_test_key_1_relay_list_event());
517 r51.events.push(generate_test_key_1_metadata_event("fred"));
518 r51.events.push(generate_repo_ref_event());
519
520 r55.events.push(generate_repo_ref_event());
521 r55.events.push(generate_test_key_1_metadata_event("fred"));
522 r55.events.push(generate_test_key_1_relay_list_event());
523
524 let cli_tester_handle: JoinHandle<Result<(GitTestRepo, GitTestRepo)>> =
525 tokio::task::spawn_blocking(move || {
526 let (originating_repo, test_repo) = create_proposals_with_first_rebased_and_repo_with_latest_main_and_unrebased_proposal()?;
527
528 let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]);
529 p.expect_end_eventually_and_print()?;
530
531 for p in [51, 52, 53, 55, 56] {
532 relay::shutdown_relay(8000 + p)?;
533 }
534 Ok((originating_repo, test_repo))
535 });
536
537 // launch relay
538 let _ = join!(
539 r51.listen_until_close(),
540 r52.listen_until_close(),
541 r53.listen_until_close(),
542 r55.listen_until_close(),
543 r56.listen_until_close(),
544 );
545 let res = cli_tester_handle.await??;
546
547 Ok(res)
548 }
549
550 mod cli_prompts {
551 use super::*;
552
553 #[tokio::test]
554 #[serial]
555 async fn prompts_to_choose_from_proposal_titles() -> Result<()> {
556 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
557 Relay::new(8051, None, None),
558 Relay::new(8052, None, None),
559 Relay::new(8053, None, None),
560 Relay::new(8055, None, None),
561 Relay::new(8056, None, None),
562 );
563
564 r51.events.push(generate_test_key_1_relay_list_event());
565 r51.events.push(generate_test_key_1_metadata_event("fred"));
566 r51.events.push(generate_repo_ref_event());
567
568 r55.events.push(generate_repo_ref_event());
569 r55.events.push(generate_test_key_1_metadata_event("fred"));
570 r55.events.push(generate_test_key_1_relay_list_event());
571
572 let cli_tester_handle: JoinHandle<Result<()>> = tokio::task::spawn_blocking(
573 move || {
574 let (_, test_repo) = create_proposals_with_first_rebased_and_repo_with_latest_main_and_unrebased_proposal()?;
575
576 let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]);
577 p.expect("fetching updates...\r\n")?;
578 p.expect_eventually("\r\n")?; // some updates listed here
579 p.expect_end_with("pulled new version of proposal (2 ahead 0 behind 'main'), replacing old version (2 ahead 1 behind 'main')\r\n")?;
580 for p in [51, 52, 53, 55, 56] {
581 relay::shutdown_relay(8000 + p)?;
582 }
583 Ok(())
584 },
585 );
586
587 // launch relay
588 let _ = join!(
589 r51.listen_until_close(),
590 r52.listen_until_close(),
591 r53.listen_until_close(),
592 r55.listen_until_close(),
593 r56.listen_until_close(),
594 );
595 cli_tester_handle.await??;
596 println!("{:?}", r55.events);
597 Ok(())
598 }
599 }
600
601 #[tokio::test]
602 #[serial]
603 async fn proposal_branch_tip_is_most_recent_proposal_revision_tip() -> Result<()> {
604 let (originating_repo, test_repo) = prep_and_run().await?;
605 assert_eq!(
606 originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?,
607 test_repo.get_tip_of_local_branch(&get_proposal_branch_name(
608 &test_repo,
609 FEATURE_BRANCH_NAME_1
610 )?)?,
611 );
612 Ok(())
613 }
614 }
615}
diff --git a/tests/ngit/push.rs b/tests/ngit/push.rs
new file mode 100644
index 0000000..eb452cd
--- /dev/null
+++ b/tests/ngit/push.rs
@@ -0,0 +1,531 @@
1use anyhow::Result;
2use futures::join;
3use serial_test::serial;
4use test_utils::{git::GitTestRepo, relay::Relay, *};
5
6mod when_main_is_checked_out {
7 use super::*;
8
9 #[test]
10 fn cli_returns_error() -> Result<()> {
11 let test_repo = GitTestRepo::default();
12 test_repo.populate()?;
13 create_and_populate_branch(&test_repo, FEATURE_BRANCH_NAME_1, "a", false)?;
14 test_repo.checkout("main")?;
15 let mut p = CliTester::new_from_dir(&test_repo.dir, ["push"]);
16 p.expect("Error: checkout a branch associated with a proposal first\r\n")?;
17 p.expect_end()?;
18 Ok(())
19 }
20}
21
22mod when_proposal_isnt_associated_with_branch_name {
23 use super::*;
24
25 mod cli_prompts {
26
27 use super::*;
28
29 #[tokio::test]
30 #[serial]
31 async fn cli_show_error() -> Result<()> {
32 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
33 Relay::new(8051, None, None),
34 Relay::new(8052, None, None),
35 Relay::new(8053, None, None),
36 Relay::new(8055, None, None),
37 Relay::new(8056, None, None),
38 );
39
40 r51.events.push(generate_test_key_1_relay_list_event());
41 r51.events.push(generate_test_key_1_metadata_event("fred"));
42 r51.events.push(generate_repo_ref_event());
43
44 r55.events.push(generate_repo_ref_event());
45 r55.events.push(generate_test_key_1_metadata_event("fred"));
46 r55.events.push(generate_test_key_1_relay_list_event());
47
48 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
49 cli_tester_create_proposals()?;
50
51 let test_repo = GitTestRepo::default();
52 test_repo.populate()?;
53
54 test_repo.create_branch("random-name")?;
55 test_repo.checkout("random-name")?;
56
57 let mut p = CliTester::new_from_dir(&test_repo.dir, ["push"]);
58 p.expect("fetching updates...\r\n")?;
59 p.expect_eventually("\r\n")?; // some updates listed here
60 p.expect_end_with(
61 "Error: cannot find proposal that matches the current branch name\r\n",
62 )?;
63 for p in [51, 52, 53, 55, 56] {
64 relay::shutdown_relay(8000 + p)?;
65 }
66 Ok(())
67 });
68
69 // launch relay
70 let _ = join!(
71 r51.listen_until_close(),
72 r52.listen_until_close(),
73 r53.listen_until_close(),
74 r55.listen_until_close(),
75 r56.listen_until_close(),
76 );
77 cli_tester_handle.join().unwrap()?;
78 Ok(())
79 }
80 }
81}
82
83mod when_branch_is_checked_out {
84 use super::*;
85
86 mod when_branch_is_up_to_date {
87 use super::*;
88
89 mod cli_prompts {
90 use super::*;
91 #[tokio::test]
92 #[serial]
93 async fn cli_show_up_to_date() -> Result<()> {
94 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
95 Relay::new(8051, None, None),
96 Relay::new(8052, None, None),
97 Relay::new(8053, None, None),
98 Relay::new(8055, None, None),
99 Relay::new(8056, None, None),
100 );
101
102 r51.events.push(generate_test_key_1_relay_list_event());
103 r51.events.push(generate_test_key_1_metadata_event("fred"));
104 r51.events.push(generate_repo_ref_event());
105
106 r55.events.push(generate_repo_ref_event());
107 r55.events.push(generate_test_key_1_metadata_event("fred"));
108 r55.events.push(generate_test_key_1_relay_list_event());
109
110 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
111 let (_, test_repo) =
112 create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?;
113
114 let mut p = CliTester::new_from_dir(&test_repo.dir, ["push"]);
115 p.expect("fetching updates...\r\n")?;
116 p.expect_eventually("\r\n")?; // some updates listed here
117 p.expect_end_with("Error: proposal already up-to-date with local branch\r\n")?;
118
119 for p in [51, 52, 53, 55, 56] {
120 relay::shutdown_relay(8000 + p)?;
121 }
122 Ok(())
123 });
124
125 // launch relay
126 let _ = join!(
127 r51.listen_until_close(),
128 r52.listen_until_close(),
129 r53.listen_until_close(),
130 r55.listen_until_close(),
131 r56.listen_until_close(),
132 );
133 cli_tester_handle.join().unwrap()?;
134 Ok(())
135 }
136 }
137 }
138
139 mod when_branch_is_behind {
140 use super::*;
141
142 mod cli_prompts {
143 use super::*;
144
145 #[tokio::test]
146 #[serial]
147 async fn cli_show_proposal_ahead_error() -> Result<()> {
148 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
149 Relay::new(8051, None, None),
150 Relay::new(8052, None, None),
151 Relay::new(8053, None, None),
152 Relay::new(8055, None, None),
153 Relay::new(8056, None, None),
154 );
155
156 r51.events.push(generate_test_key_1_relay_list_event());
157 r51.events.push(generate_test_key_1_metadata_event("fred"));
158 r51.events.push(generate_repo_ref_event());
159
160 r55.events.push(generate_repo_ref_event());
161 r55.events.push(generate_test_key_1_metadata_event("fred"));
162 r55.events.push(generate_test_key_1_relay_list_event());
163
164 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
165 let (_, test_repo) =
166 create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?;
167
168 let branch_name =
169 remove_latest_commit_so_proposal_branch_is_behind_and_checkout_main(
170 &test_repo,
171 )?;
172
173 test_repo.checkout(&branch_name)?;
174 // run test
175 let mut p = CliTester::new_from_dir(&test_repo.dir, ["push"]);
176 p.expect("fetching updates...\r\n")?;
177 p.expect_eventually("\r\n")?; // some updates listed here
178 p.expect("Error: proposal is ahead of local branch\r\n")?;
179 p.expect_end()?;
180
181 for p in [51, 52, 53, 55, 56] {
182 relay::shutdown_relay(8000 + p)?;
183 }
184 Ok(())
185 });
186
187 // launch relay
188 let _ = join!(
189 r51.listen_until_close(),
190 r52.listen_until_close(),
191 r53.listen_until_close(),
192 r55.listen_until_close(),
193 r56.listen_until_close(),
194 );
195 cli_tester_handle.join().unwrap()?;
196 Ok(())
197 }
198 }
199 }
200
201 mod when_branch_is_ahead {
202 use super::*;
203
204 mod cli_prompts {
205 use test_utils::relay::expect_send_with_progress;
206
207 use super::*;
208
209 #[tokio::test]
210 #[serial]
211 async fn cli_applied_1_commit() -> Result<()> {
212 // fallback (51,52) user write (53, 55) repo (55, 56)
213 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
214 Relay::new(8051, None, None),
215 Relay::new(8052, None, None),
216 Relay::new(8053, None, None),
217 Relay::new(8055, None, None),
218 Relay::new(8056, None, None),
219 );
220
221 r51.events.push(generate_test_key_1_relay_list_event());
222 r51.events.push(generate_test_key_1_metadata_event("fred"));
223 r51.events.push(generate_repo_ref_event());
224
225 r55.events.push(generate_repo_ref_event());
226 r55.events.push(generate_test_key_1_metadata_event("fred"));
227 r55.events.push(generate_test_key_1_relay_list_event());
228
229 let cli_tester_handle =
230 std::thread::spawn(move || -> Result<(GitTestRepo, GitTestRepo)> {
231 let (originating_repo, test_repo) =
232 create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?;
233
234 // add another commit (so we have an ammened local branch)
235 std::fs::write(test_repo.dir.join("ammended-commit.md"), "some content")?;
236 test_repo.stage_and_commit("add ammended-commit.md")?;
237
238 // run test
239 let mut p = CliTester::new_from_dir(
240 &test_repo.dir,
241 [
242 "--nsec",
243 TEST_KEY_1_NSEC,
244 "--password",
245 TEST_PASSWORD,
246 "--disable-cli-spinners",
247 "push",
248 ],
249 );
250 p.expect("fetching updates...\r\n")?;
251 p.expect_eventually("\r\n")?; // some updates listed here
252 p.expect(
253 "1 commits ahead. preparing to create creating patch events.\r\n",
254 )?;
255 p.expect("logged in as fred\r\n")?;
256 p.expect("pushing 1 commits\r\n")?;
257
258 expect_send_with_progress(
259 &mut p,
260 vec![
261 (" [my-relay] [repo-relay] ws://localhost:8055", true, ""),
262 (" [my-relay] ws://localhost:8053", true, ""),
263 (" [repo-relay] ws://localhost:8056", true, ""),
264 (" [default] ws://localhost:8051", true, ""),
265 (" [default] ws://localhost:8052", true, ""),
266 ],
267 1,
268 )?;
269 p.expect_eventually("pushed 1 commits\r\n")?;
270 p.expect_end()?;
271
272 for p in [51, 52, 53, 55, 56] {
273 relay::shutdown_relay(8000 + p)?;
274 }
275 Ok((originating_repo, test_repo))
276 });
277
278 // launch relay
279 let _ = join!(
280 r51.listen_until_close(),
281 r52.listen_until_close(),
282 r53.listen_until_close(),
283 r55.listen_until_close(),
284 r56.listen_until_close(),
285 );
286 cli_tester_handle.join().unwrap()?;
287
288 Ok(())
289 }
290 }
291
292 async fn prep_and_run() -> Result<(GitTestRepo, Vec<nostr::Event>)> {
293 // fallback (51,52) user write (53, 55) repo (55, 56)
294 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
295 Relay::new(8051, None, None),
296 Relay::new(8052, None, None),
297 Relay::new(8053, None, None),
298 Relay::new(8055, None, None),
299 Relay::new(8056, None, None),
300 );
301
302 r51.events.push(generate_test_key_1_relay_list_event());
303 r51.events.push(generate_test_key_1_metadata_event("fred"));
304 r51.events.push(generate_repo_ref_event());
305
306 r55.events.push(generate_repo_ref_event());
307 r55.events.push(generate_test_key_1_metadata_event("fred"));
308 r55.events.push(generate_test_key_1_relay_list_event());
309
310 let cli_tester_handle = std::thread::spawn(move || -> Result<GitTestRepo> {
311 let (_, test_repo) =
312 create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?;
313
314 // add another commit (so we have an ammened local branch)
315 std::fs::write(test_repo.dir.join("ammended-commit.md"), "some content")?;
316 test_repo.stage_and_commit("add ammended-commit.md")?;
317
318 // run test
319
320 let mut p = CliTester::new_from_dir(
321 &test_repo.dir,
322 [
323 "--nsec",
324 TEST_KEY_1_NSEC,
325 "--password",
326 TEST_PASSWORD,
327 "--disable-cli-spinners",
328 "push",
329 ],
330 );
331 p.expect_end_eventually()?;
332
333 for p in [51, 52, 53, 55, 56] {
334 relay::shutdown_relay(8000 + p)?;
335 }
336 Ok(test_repo)
337 });
338
339 // launch relay
340 let _ = join!(
341 r51.listen_until_close(),
342 r52.listen_until_close(),
343 r53.listen_until_close(),
344 r55.listen_until_close(),
345 r56.listen_until_close(),
346 );
347 let res = cli_tester_handle.join().unwrap()?;
348
349 Ok((res, r55.events.clone()))
350 }
351 #[tokio::test]
352 #[serial]
353 async fn commits_issued_as_patch_event() -> Result<()> {
354 let (test_repo, r55_events) = prep_and_run().await?;
355
356 let commit_id = test_repo
357 .get_tip_of_local_branch(&test_repo.get_checked_out_branch_name()?)?
358 .to_string();
359 assert!(r55_events.iter().any(|e| {
360 e.tags
361 .iter()
362 .any(|t| t.as_vec()[0].eq("commit") && t.as_vec()[1].eq(&commit_id))
363 }));
364 Ok(())
365 }
366 }
367
368 mod when_branch_has_been_rebased {
369 use super::*;
370
371 mod cli_prompts {
372 use super::*;
373
374 #[tokio::test]
375 #[serial]
376 async fn cli_shows_unpublished_rebase_error() -> Result<()> {
377 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
378 Relay::new(8051, None, None),
379 Relay::new(8052, None, None),
380 Relay::new(8053, None, None),
381 Relay::new(8055, None, None),
382 Relay::new(8056, None, None),
383 );
384
385 r51.events.push(generate_test_key_1_relay_list_event());
386 r51.events.push(generate_test_key_1_metadata_event("fred"));
387 r51.events.push(generate_repo_ref_event());
388
389 r55.events.push(generate_repo_ref_event());
390 r55.events.push(generate_test_key_1_metadata_event("fred"));
391 r55.events.push(generate_test_key_1_relay_list_event());
392
393 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
394 let (_, tmp_repo) =
395 create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?;
396 let branch_name = tmp_repo.get_checked_out_branch_name()?;
397
398 let test_repo = GitTestRepo::default();
399 test_repo.populate()?;
400
401 // simulate rebase
402 std::fs::write(test_repo.dir.join("amazing.md"), "some content")?;
403 test_repo.stage_and_commit("commit for rebasing on top of")?;
404 create_and_populate_branch(&test_repo, &branch_name, "a", true)?;
405
406 let mut p = CliTester::new_from_dir(&test_repo.dir, ["push"]);
407 // p.expect_end_eventually_and_print()?;
408
409 p.expect("fetching updates...\r\n")?;
410 p.expect_eventually("\r\n")?; // some updates listed here
411 p.expect(
412 "Error: local unpublished proposal has been rebased. consider force pushing\r\n",
413 )?;
414 p.expect_end()?;
415
416 for p in [51, 52, 53, 55, 56] {
417 relay::shutdown_relay(8000 + p)?;
418 }
419 Ok(())
420 });
421
422 // launch relay
423 let _ = join!(
424 r51.listen_until_close(),
425 r52.listen_until_close(),
426 r53.listen_until_close(),
427 r55.listen_until_close(),
428 r56.listen_until_close(),
429 );
430 cli_tester_handle.join().unwrap()?;
431 Ok(())
432 }
433 }
434 mod with_force_flag {
435 use super::*;
436
437 mod cli_prompts {
438 use super::*;
439
440 #[tokio::test]
441 #[serial]
442 async fn cli_shows_revision_sent() -> Result<()> {
443 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
444 Relay::new(8051, None, None),
445 Relay::new(8052, None, None),
446 Relay::new(8053, None, None),
447 Relay::new(8055, None, None),
448 Relay::new(8056, None, None),
449 );
450
451 r51.events.push(generate_test_key_1_relay_list_event());
452 r51.events.push(generate_test_key_1_metadata_event("fred"));
453 r51.events.push(generate_repo_ref_event());
454
455 r55.events.push(generate_repo_ref_event());
456 r55.events.push(generate_test_key_1_metadata_event("fred"));
457 r55.events.push(generate_test_key_1_relay_list_event());
458
459 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
460 let (_, tmp_repo) =
461 create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?;
462 let branch_name = tmp_repo.get_checked_out_branch_name()?;
463
464 let test_repo = GitTestRepo::default();
465 test_repo.populate()?;
466
467 // simulate rebase
468 std::fs::write(test_repo.dir.join("amazing.md"), "some content")?;
469 test_repo.stage_and_commit("commit for rebasing on top of")?;
470 create_and_populate_branch(&test_repo, &branch_name, "a", false)?;
471 let mut p = CliTester::new_from_dir(
472 &test_repo.dir,
473 [
474 "--nsec",
475 TEST_KEY_1_NSEC,
476 "--password",
477 TEST_PASSWORD,
478 "--disable-cli-spinners",
479 "push",
480 "--force",
481 ],
482 );
483 p.expect("fetching updates...\r\n")?;
484 p.expect_eventually("\r\n")?; // some updates listed here
485 p.expect("preparing to force push proposal revision...\r\n")?;
486 // standard output from `ngit send`
487 p.expect("creating proposal revision for: ")?;
488 // proposal id will be printed in this gap
489 p.expect_eventually("\r\n")?;
490 p.expect("creating proposal from 2 commits:\r\n")?;
491 p.expect("355bdf1 add a4.md\r\n")?;
492 p.expect("dbd1115 add a3.md\r\n")?;
493 p.expect("logged in as fred\r\n")?;
494 p.expect("posting 2 patches without a covering letter...\r\n")?;
495
496 relay::expect_send_with_progress(
497 &mut p,
498 vec![
499 (" [my-relay] [repo-relay] ws://localhost:8055", true, ""),
500 (" [my-relay] ws://localhost:8053", true, ""),
501 (" [repo-relay] ws://localhost:8056", true, ""),
502 (" [default] ws://localhost:8051", true, ""),
503 (" [default] ws://localhost:8052", true, ""),
504 ],
505 2,
506 )?;
507 // end standard `ngit send output`
508 p.expect_after_whitespace("force pushed proposal revision\r\n")?;
509 p.expect_end()?;
510
511 for p in [51, 52, 53, 55, 56] {
512 relay::shutdown_relay(8000 + p)?;
513 }
514 Ok(())
515 });
516
517 // launch relay
518 let _ = join!(
519 r51.listen_until_close(),
520 r52.listen_until_close(),
521 r53.listen_until_close(),
522 r55.listen_until_close(),
523 r56.listen_until_close(),
524 );
525 cli_tester_handle.join().unwrap()?;
526 Ok(())
527 }
528 }
529 }
530 }
531}
diff --git a/tests/ngit/send.rs b/tests/ngit/send.rs
new file mode 100644
index 0000000..ef09425
--- /dev/null
+++ b/tests/ngit/send.rs
@@ -0,0 +1,1901 @@
1use anyhow::Result;
2use futures::join;
3use nostr_sdk::Kind;
4use serial_test::serial;
5use test_utils::{git::GitTestRepo, relay::Relay, *};
6
7#[test]
8fn when_no_main_or_master_branch_return_error() -> Result<()> {
9 let test_repo = GitTestRepo::new("notmain")?;
10 test_repo.populate()?;
11 let mut p = CliTester::new_from_dir(&test_repo.dir, ["send"]);
12 p.expect("Error: the default branches (main or master) do not exist")?;
13 Ok(())
14}
15
16// TODO when commits ahead of origin/master - test ask to proceed
17// TODO when commits in origin/master - test ask to proceed
18mod when_commits_behind_ask_to_proceed {
19 use super::*;
20
21 fn prep_test_repo() -> Result<GitTestRepo> {
22 let test_repo = GitTestRepo::default();
23 test_repo.populate()?;
24 // create feature branch with 2 commit ahead
25 test_repo.create_branch("feature")?;
26 test_repo.checkout("feature")?;
27 std::fs::write(test_repo.dir.join("t3.md"), "some content")?;
28 test_repo.stage_and_commit("add t3.md")?;
29 std::fs::write(test_repo.dir.join("t4.md"), "some content")?;
30 test_repo.stage_and_commit("add t4.md")?;
31 // checkout main and add 1 commit
32 test_repo.checkout("main")?;
33 std::fs::write(test_repo.dir.join("t5.md"), "some content")?;
34 test_repo.stage_and_commit("add t5.md")?;
35 // checkout feature branch
36 test_repo.checkout("feature")?;
37 Ok(test_repo)
38 }
39
40 fn expect_confirm_prompt(p: &mut CliTester) -> Result<CliTesterConfirmPrompt> {
41 p.expect("fetching updates...\r\n")?;
42 p.expect_eventually("\r\n")?; // may be 'no updates' or some updates
43 p.expect("creating proposal from 2 commits:\r\n")?;
44 p.expect("fe973a8 add t4.md\r\n")?;
45 p.expect("232efb3 add t3.md\r\n")?;
46 p.expect_confirm(
47 "proposal is 1 behind 'main'. consider rebasing before submission. proceed anyway?",
48 Some(false),
49 )
50 }
51
52 #[test]
53 fn asked_with_default_no() -> Result<()> {
54 let test_repo = prep_test_repo()?;
55
56 let mut p = CliTester::new_from_dir(&test_repo.dir, ["send", "HEAD~2"]);
57 expect_confirm_prompt(&mut p)?;
58 p.exit()?;
59 Ok(())
60 }
61
62 #[test]
63 fn when_response_is_false_aborts() -> Result<()> {
64 let test_repo = prep_test_repo()?;
65
66 let mut p = CliTester::new_from_dir(&test_repo.dir, ["send", "HEAD~2"]);
67
68 expect_confirm_prompt(&mut p)?.succeeds_with(Some(false))?;
69
70 p.expect_end_with("Error: aborting so commits can be rebased\r\n")?;
71
72 Ok(())
73 }
74 #[test]
75 #[serial]
76 fn when_response_is_true_proceeds() -> Result<()> {
77 let test_repo = prep_test_repo()?;
78
79 let mut p = CliTester::new_from_dir(&test_repo.dir, ["send", "HEAD~2"]);
80 expect_confirm_prompt(&mut p)?.succeeds_with(Some(true))?;
81 p.expect("? include cover letter")?;
82 p.exit()?;
83 Ok(())
84 }
85}
86
87fn is_cover_letter(event: &nostr::Event) -> bool {
88 event.kind.eq(&Kind::GitPatch)
89 && event
90 .tags()
91 .iter()
92 .any(|t| t.as_vec()[1].eq("cover-letter"))
93}
94
95fn is_patch(event: &nostr::Event) -> bool {
96 event.kind.eq(&Kind::GitPatch)
97 && !event
98 .tags()
99 .iter()
100 .any(|t| t.as_vec()[1].eq("cover-letter"))
101}
102
103fn prep_git_repo() -> Result<GitTestRepo> {
104 let test_repo = GitTestRepo::default();
105 test_repo.populate()?;
106 // create feature branch with 2 commit ahead
107 test_repo.create_branch("feature")?;
108 test_repo.checkout("feature")?;
109 std::fs::write(test_repo.dir.join("t3.md"), "some content")?;
110 test_repo.stage_and_commit("add t3.md")?;
111 std::fs::write(test_repo.dir.join("t4.md"), "some content")?;
112 test_repo.stage_and_commit("add t4.md")?;
113 Ok(test_repo)
114}
115
116fn cli_tester_create_proposal(git_repo: &GitTestRepo, include_cover_letter: bool) -> CliTester {
117 let mut args = vec![
118 "--nsec",
119 TEST_KEY_1_NSEC,
120 "--password",
121 TEST_PASSWORD,
122 "--disable-cli-spinners",
123 "send",
124 "HEAD~2",
125 ];
126 if include_cover_letter {
127 for arg in [
128 "--title",
129 "exampletitle",
130 "--description",
131 "exampledescription",
132 ] {
133 args.push(arg);
134 }
135 } else {
136 args.push("--no-cover-letter");
137 }
138 CliTester::new_from_dir(&git_repo.dir, args)
139}
140
141fn expect_msgs_first(p: &mut CliTester, include_cover_letter: bool) -> Result<()> {
142 p.expect("fetching updates...\r\n")?;
143 p.expect_eventually("\r\n")?; // may be 'no updates' or some updates
144 p.expect("creating proposal from 2 commits:\r\n")?;
145 p.expect("fe973a8 add t4.md\r\n")?;
146 p.expect("232efb3 add t3.md\r\n")?;
147 // sometimes there will be a 'searching for profile...' msg
148 p.expect_eventually("logged in as fred\r\n")?;
149 p.expect(format!(
150 "posting 2 patches {} a covering letter...\r\n",
151 if include_cover_letter {
152 "with"
153 } else {
154 "without"
155 }
156 ))?;
157 Ok(())
158}
159
160fn expect_msgs_after(p: &mut CliTester) -> Result<()> {
161 p.expect_after_whitespace("view in gitworkshop.dev: https://gitworkshop.dev/repo")?;
162 p.expect_eventually("\r\n")?;
163 p.expect("view in another client: https://njump.me/")?;
164 p.expect_eventually("\r\n")?;
165 Ok(())
166}
167
168async fn prep_run_create_proposal(
169 include_cover_letter: bool,
170) -> Result<(
171 Relay<'static>,
172 Relay<'static>,
173 Relay<'static>,
174 Relay<'static>,
175 Relay<'static>,
176)> {
177 let git_repo = prep_git_repo()?;
178 // fallback (51,52) user write (53, 55) repo (55, 56)
179 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
180 Relay::new(
181 8051,
182 None,
183 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
184 relay.respond_events(
185 client_id,
186 &subscription_id,
187 &vec![
188 generate_test_key_1_metadata_event("fred"),
189 generate_test_key_1_relay_list_event(),
190 ],
191 )?;
192 Ok(())
193 }),
194 ),
195 Relay::new(8052, None, None),
196 Relay::new(8053, None, None),
197 Relay::new(
198 8055,
199 None,
200 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
201 relay.respond_events(
202 client_id,
203 &subscription_id,
204 &vec![generate_repo_ref_event()],
205 )?;
206 Ok(())
207 }),
208 ),
209 Relay::new(8056, None, None),
210 );
211
212 // // check relay had the right number of events
213 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
214 let mut p = cli_tester_create_proposal(&git_repo, include_cover_letter);
215 p.expect_end_eventually()?;
216 for p in [51, 52, 53, 55, 56] {
217 relay::shutdown_relay(8000 + p)?;
218 }
219 Ok(())
220 });
221
222 // launch relay
223 let _ = join!(
224 r51.listen_until_close(),
225 r52.listen_until_close(),
226 r53.listen_until_close(),
227 r55.listen_until_close(),
228 r56.listen_until_close(),
229 );
230 cli_tester_handle.join().unwrap()?;
231 Ok((r51, r52, r53, r55, r56))
232}
233
234mod when_cover_letter_details_specified_with_range_of_head_2_sends_cover_letter_and_2_patches_to_3_relays {
235
236 use super::*;
237 #[tokio::test]
238 #[serial]
239 async fn only_1_cover_letter_event_sent_to_each_relay() -> Result<()> {
240 let (_, _, r53, r55, r56) = prep_run_create_proposal(true).await?;
241 for relay in [&r53, &r55, &r56] {
242 assert_eq!(
243 relay.events.iter().filter(|e| is_cover_letter(e)).count(),
244 1,
245 );
246 }
247 Ok(())
248 }
249
250 #[tokio::test]
251 #[serial]
252 async fn only_1_cover_letter_event_sent_to_user_relays() -> Result<()> {
253 let (_, _, r53, r55, _) = prep_run_create_proposal(true).await?;
254 for relay in [&r53, &r55] {
255 assert_eq!(
256 relay.events.iter().filter(|e| is_cover_letter(e)).count(),
257 1,
258 );
259 }
260 Ok(())
261 }
262
263 #[tokio::test]
264 #[serial]
265 async fn only_1_cover_letter_event_sent_to_repo_relays() -> Result<()> {
266 let (_, _, _, r55, r56) = prep_run_create_proposal(true).await?;
267 for relay in [&r55, &r56] {
268 assert_eq!(
269 relay.events.iter().filter(|e| is_cover_letter(e)).count(),
270 1
271 );
272 }
273 Ok(())
274 }
275
276 #[tokio::test]
277 #[serial]
278 async fn only_1_cover_letter_event_sent_to_fallback_relays() -> Result<()> {
279 let (r51, r52, _, _, _) = prep_run_create_proposal(true).await?;
280 for relay in [&r51, &r52] {
281 assert_eq!(
282 relay.events.iter().filter(|e| is_cover_letter(e)).count(),
283 1,
284 );
285 }
286 Ok(())
287 }
288
289 #[tokio::test]
290 #[serial]
291 async fn only_2_patch_kind_events_sent_to_each_relay() -> Result<()> {
292 let (r51, r52, r53, r55, r56) = prep_run_create_proposal(true).await?;
293 for relay in [&r51, &r52, &r53, &r55, &r56] {
294 assert_eq!(relay.events.iter().filter(|e| is_patch(e)).count(), 2,);
295 }
296 Ok(())
297 }
298
299 #[tokio::test]
300 #[serial]
301 async fn patch_content_contains_patch_in_email_format_with_patch_series_numbers() -> Result<()>
302 {
303 let (_, _, r53, r55, r56) = prep_run_create_proposal(true).await?;
304 for relay in [&r53, &r55, &r56] {
305 let patch_events: Vec<&nostr::Event> =
306 relay.events.iter().filter(|e| is_patch(e)).collect();
307
308 assert_eq!(
309 patch_events[1].content,
310 "\
311 From fe973a840fba2a8ab37dd505c154854a69a6505c Mon Sep 17 00:00:00 2001\n\
312 From: Joe Bloggs <joe.bloggs@pm.me>\n\
313 Date: Thu, 1 Jan 1970 00:00:00 +0000\n\
314 Subject: [PATCH 2/2] add t4.md\n\
315 \n\
316 ---\n \
317 t4.md | 1 +\n \
318 1 file changed, 1 insertion(+)\n \
319 create mode 100644 t4.md\n\
320 \n\
321 diff --git a/t4.md b/t4.md\n\
322 new file mode 100644\n\
323 index 0000000..f0eec86\n\
324 --- /dev/null\n\
325 +++ b/t4.md\n\
326 @@ -0,0 +1 @@\n\
327 +some content\n\\ \
328 No newline at end of file\n\
329 --\n\
330 libgit2 1.7.2\n\
331 \n\
332 ",
333 );
334 assert_eq!(
335 patch_events[0].content,
336 "\
337 From 232efb37ebc67692c9e9ff58b83c0d3d63971a0a Mon Sep 17 00:00:00 2001\n\
338 From: Joe Bloggs <joe.bloggs@pm.me>\n\
339 Date: Thu, 1 Jan 1970 00:00:00 +0000\n\
340 Subject: [PATCH 1/2] add t3.md\n\
341 \n\
342 ---\n \
343 t3.md | 1 +\n \
344 1 file changed, 1 insertion(+)\n \
345 create mode 100644 t3.md\n\
346 \n\
347 diff --git a/t3.md b/t3.md\n\
348 new file mode 100644\n\
349 index 0000000..f0eec86\n\
350 --- /dev/null\n\
351 +++ b/t3.md\n\
352 @@ -0,0 +1 @@\n\
353 +some content\n\\ \
354 No newline at end of file\n\
355 --\n\
356 libgit2 1.7.2\n\
357 \n\
358 ",
359 );
360 }
361 Ok(())
362 }
363
364 mod cover_letter_tags {
365 use super::*;
366
367 #[tokio::test]
368 #[serial]
369 async fn root_commit_as_r() -> Result<()> {
370 let (_, _, r53, r55, r56) = prep_run_create_proposal(true).await?;
371 for relay in [&r53, &r55, &r56] {
372 let cover_letter_event: &nostr::Event =
373 relay.events.iter().find(|e| is_cover_letter(e)).unwrap();
374
375 assert_eq!(
376 cover_letter_event
377 .tags()
378 .iter()
379 .find(|t| t.as_vec()[0].eq("r"))
380 .unwrap()
381 .as_vec()[1],
382 "9ee507fc4357d7ee16a5d8901bedcd103f23c17d"
383 );
384 }
385 Ok(())
386 }
387
388 #[tokio::test]
389 #[serial]
390 async fn a_tag_for_repo_event_of_each_maintainer() -> Result<()> {
391 let (_, _, r53, r55, r56) = prep_run_create_proposal(true).await?;
392 for relay in [&r53, &r55, &r56] {
393 let cover_letter_event: &nostr::Event =
394 relay.events.iter().find(|e| is_cover_letter(e)).unwrap();
395 assert!(
396 cover_letter_event
397 .tags()
398 .iter()
399 .any(|t| t.as_vec()[0].eq("a")
400 && t.as_vec()[1].eq(&format!(
401 "{}:{TEST_KEY_1_PUBKEY_HEX}:{}",
402 Kind::GitRepoAnnouncement,
403 generate_repo_ref_event().identifier().unwrap()
404 )))
405 );
406 assert!(
407 cover_letter_event
408 .tags()
409 .iter()
410 .any(|t| t.as_vec()[0].eq("a")
411 && t.as_vec()[1].eq(&format!(
412 "{}:{TEST_KEY_2_PUBKEY_HEX}:{}",
413 Kind::GitRepoAnnouncement,
414 generate_repo_ref_event().identifier().unwrap()
415 )))
416 );
417 }
418 Ok(())
419 }
420
421 #[tokio::test]
422 #[serial]
423 async fn p_tags_for_maintainers() -> Result<()> {
424 let event = generate_repo_ref_event();
425 let maintainers = &event
426 .tags()
427 .iter()
428 .find(|t| t.as_vec()[0].eq(&"maintainers"))
429 .unwrap()
430 .as_vec()[1..];
431 let (_, _, r53, r55, r56) = prep_run_create_proposal(true).await?;
432 for relay in [&r53, &r55, &r56] {
433 for m in maintainers {
434 let cover_letter_event: &nostr::Event =
435 relay.events.iter().find(|e| is_cover_letter(e)).unwrap();
436 assert!(
437 cover_letter_event
438 .tags()
439 .iter()
440 .any(|t| { t.as_vec()[0].eq("p") && t.as_vec()[1].eq(m) })
441 );
442 }
443 }
444 Ok(())
445 }
446
447 #[tokio::test]
448 #[serial]
449 async fn t_tag_cover_letter() -> Result<()> {
450 let (_, _, r53, r55, r56) = prep_run_create_proposal(true).await?;
451 for relay in [&r53, &r55, &r56] {
452 let cover_letter_event: &nostr::Event =
453 relay.events.iter().find(|e| is_cover_letter(e)).unwrap();
454 assert!(
455 cover_letter_event
456 .tags()
457 .iter()
458 .any(|t| { t.as_vec()[0].eq("t") && t.as_vec()[1].eq(&"cover-letter") })
459 );
460 }
461 Ok(())
462 }
463
464 #[tokio::test]
465 #[serial]
466 async fn t_tag_root() -> Result<()> {
467 let (_, _, r53, r55, r56) = prep_run_create_proposal(true).await?;
468 for relay in [&r53, &r55, &r56] {
469 let cover_letter_event: &nostr::Event =
470 relay.events.iter().find(|e| is_cover_letter(e)).unwrap();
471 assert!(
472 cover_letter_event
473 .tags()
474 .iter()
475 .any(|t| { t.as_vec()[0].eq("t") && t.as_vec()[1].eq(&"root") })
476 );
477 }
478 Ok(())
479 }
480
481 #[tokio::test]
482 #[serial]
483 async fn cover_letter_tags_branch_name() -> Result<()> {
484 let (_, _, r53, r55, r56) = prep_run_create_proposal(true).await?;
485 for relay in [&r53, &r55, &r56] {
486 let cover_letter_event: &nostr::Event =
487 relay.events.iter().find(|e| is_cover_letter(e)).unwrap();
488
489 // branch-name tag
490 assert_eq!(
491 cover_letter_event
492 .tags()
493 .iter()
494 .find(|t| t.as_vec()[0].eq("branch-name"))
495 .unwrap()
496 .as_vec()[1],
497 "feature"
498 );
499 }
500 Ok(())
501 }
502
503 #[tokio::test]
504 #[serial]
505 async fn cover_letter_tags_alt() -> Result<()> {
506 let (_, _, r53, r55, r56) = prep_run_create_proposal(true).await?;
507 for relay in [&r53, &r55, &r56] {
508 let cover_letter_event: &nostr::Event =
509 relay.events.iter().find(|e| is_cover_letter(e)).unwrap();
510
511 // branch-name tag
512 assert_eq!(
513 cover_letter_event
514 .tags()
515 .iter()
516 .find(|t| t.as_vec()[0].eq("alt"))
517 .unwrap()
518 .as_vec()[1],
519 "git patch cover letter: exampletitle"
520 );
521 }
522 Ok(())
523 }
524 }
525
526 mod patch_tags {
527 use super::*;
528
529 async fn prep() -> Result<nostr::Event> {
530 let (_, _, r53, _, _) = prep_run_create_proposal(true).await?;
531 Ok(r53.events.iter().find(|e| is_patch(e)).unwrap().clone())
532 }
533
534 #[tokio::test]
535 #[serial]
536 async fn commit_and_commit_r() -> Result<()> {
537 static COMMIT_ID: &str = "232efb37ebc67692c9e9ff58b83c0d3d63971a0a";
538 let most_recent_patch = prep().await?;
539 assert!(
540 most_recent_patch
541 .tags
542 .iter()
543 .any(|t| t.as_vec()[0].eq("r") && t.as_vec()[1].eq(COMMIT_ID))
544 );
545 assert!(
546 most_recent_patch
547 .tags
548 .iter()
549 .any(|t| t.as_vec()[0].eq("commit") && t.as_vec()[1].eq(COMMIT_ID))
550 );
551 Ok(())
552 }
553
554 #[tokio::test]
555 #[serial]
556 async fn parent_commit() -> Result<()> {
557 // commit parent 'r' and 'parent-commit' tag
558 static COMMIT_PARENT_ID: &str = "431b84edc0d2fa118d63faa3c2db9c73d630a5ae";
559 let most_recent_patch = prep().await?;
560 assert_eq!(
561 most_recent_patch
562 .tags
563 .iter()
564 .find(|t| t.as_vec()[0].eq("parent-commit"))
565 .unwrap()
566 .as_vec()[1],
567 COMMIT_PARENT_ID,
568 );
569 Ok(())
570 }
571
572 #[tokio::test]
573 #[serial]
574 async fn root_commit_as_r() -> Result<()> {
575 assert!(prep().await?.tags.iter().any(|t| t.as_vec()[0].eq("r")
576 && t.as_vec()[1].eq("9ee507fc4357d7ee16a5d8901bedcd103f23c17d")));
577 Ok(())
578 }
579
580 #[tokio::test]
581 #[serial]
582 async fn p_tags_for_maintainers() -> Result<()> {
583 let event = generate_repo_ref_event();
584 let maintainers = &event
585 .tags()
586 .iter()
587 .find(|t| t.as_vec()[0].eq(&"maintainers"))
588 .unwrap()
589 .as_vec()[1..];
590 for m in maintainers {
591 assert!(
592 prep()
593 .await?
594 .tags()
595 .iter()
596 .any(|t| { t.as_vec()[0].eq("p") && t.as_vec()[1].eq(m) })
597 );
598 }
599 Ok(())
600 }
601
602 #[tokio::test]
603 #[serial]
604 async fn a_tag_for_repo_event_of_each_maintainer() -> Result<()> {
605 assert!(prep().await?.tags.iter().any(|t| {
606 t.as_vec()[0].eq("a")
607 && t.as_vec()[1].eq(&format!(
608 "{}:{TEST_KEY_1_PUBKEY_HEX}:{}",
609 Kind::GitRepoAnnouncement,
610 generate_repo_ref_event().identifier().unwrap()
611 ))
612 }));
613 assert!(prep().await?.tags.iter().any(|t| {
614 t.as_vec()[0].eq("a")
615 && t.as_vec()[1].eq(&format!(
616 "{}:{TEST_KEY_2_PUBKEY_HEX}:{}",
617 Kind::GitRepoAnnouncement,
618 generate_repo_ref_event().identifier().unwrap()
619 ))
620 }));
621 Ok(())
622 }
623
624 #[tokio::test]
625 #[serial]
626 async fn description_with_commit_message() -> Result<()> {
627 assert_eq!(
628 prep()
629 .await?
630 .tags
631 .iter()
632 .find(|t| t.as_vec()[0].eq("description"))
633 .unwrap()
634 .as_vec()[1],
635 "add t3.md"
636 );
637 Ok(())
638 }
639
640 #[tokio::test]
641 #[serial]
642 async fn commit_author() -> Result<()> {
643 assert_eq!(
644 prep()
645 .await?
646 .tags
647 .iter()
648 .find(|t| t.as_vec()[0].eq("author"))
649 .unwrap()
650 .as_vec(),
651 vec!["author", "Joe Bloggs", "joe.bloggs@pm.me", "0", "0"],
652 );
653 Ok(())
654 }
655
656 #[tokio::test]
657 #[serial]
658 async fn commit_committer() -> Result<()> {
659 assert_eq!(
660 prep()
661 .await?
662 .tags
663 .iter()
664 .find(|t| t.as_vec()[0].eq("committer"))
665 .unwrap()
666 .as_vec(),
667 vec!["committer", "Joe Bloggs", "joe.bloggs@pm.me", "0", "0"],
668 );
669 Ok(())
670 }
671
672 #[tokio::test]
673 #[serial]
674 async fn alt() -> Result<()> {
675 assert_eq!(
676 prep()
677 .await?
678 .tags
679 .iter()
680 .find(|t| t.as_vec()[0].eq("alt"))
681 .unwrap()
682 .as_vec(),
683 vec!["alt", "git patch: add t3.md"],
684 );
685 Ok(())
686 }
687
688 #[tokio::test]
689 #[serial]
690 async fn patch_tags_cover_letter_event_as_root() -> Result<()> {
691 let (_, _, r53, r55, r56) = prep_run_create_proposal(true).await?;
692 for relay in [&r53, &r55, &r56] {
693 let patch_events: Vec<&nostr::Event> =
694 relay.events.iter().filter(|e| is_patch(e)).collect();
695
696 let most_recent_patch = patch_events[0];
697 let cover_letter_event = relay.events.iter().find(|e| is_cover_letter(e)).unwrap();
698
699 let root_event_tag = most_recent_patch
700 .tags
701 .iter()
702 .find(|t| {
703 t.as_vec()[0].eq("e") && t.as_vec().len().eq(&4) && t.as_vec()[3].eq("root")
704 })
705 .unwrap();
706
707 assert_eq!(
708 root_event_tag.as_vec()[1],
709 cover_letter_event.id.to_string()
710 );
711 }
712 Ok(())
713 }
714
715 #[tokio::test]
716 #[serial]
717 async fn second_patch_tags_first_with_reply() -> Result<()> {
718 let (_, _, r53, r55, r56) = prep_run_create_proposal(true).await?;
719 for relay in [&r53, &r55, &r56] {
720 let patch_events = relay
721 .events
722 .iter()
723 .filter(|e| is_patch(e))
724 .collect::<Vec<&nostr::Event>>();
725 assert_eq!(
726 patch_events[1]
727 .tags()
728 .iter()
729 .find(|t| t.as_vec()[0].eq("e")
730 && t.as_vec().len().eq(&4)
731 && t.as_vec()[3].eq("reply"))
732 .unwrap()
733 .as_vec()[1],
734 patch_events[0].id.to_string(),
735 );
736 }
737 Ok(())
738 }
739
740 #[tokio::test]
741 #[serial]
742 async fn no_t_root_tag() -> Result<()> {
743 assert!(
744 !prep()
745 .await?
746 .tags
747 .iter()
748 .any(|t| t.as_vec()[0].eq("t") && t.as_vec()[1].eq("root"))
749 );
750 Ok(())
751 }
752 }
753 mod cli_ouput {
754 use super::*;
755
756 #[tokio::test]
757 #[serial]
758 async fn check_cli_output() -> Result<()> {
759 let git_repo = prep_git_repo()?;
760
761 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
762 Relay::new(
763 8051,
764 None,
765 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
766 relay.respond_events(
767 client_id,
768 &subscription_id,
769 &vec![
770 generate_test_key_1_metadata_event("fred"),
771 generate_test_key_1_relay_list_event(),
772 ],
773 )?;
774 Ok(())
775 }),
776 ),
777 Relay::new(8052, None, None),
778 Relay::new(8053, None, None),
779 Relay::new(
780 8055,
781 None,
782 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
783 relay.respond_events(
784 client_id,
785 &subscription_id,
786 &vec![generate_repo_ref_event()],
787 )?;
788 Ok(())
789 }),
790 ),
791 Relay::new(8056, None, None),
792 );
793
794 // // check relay had the right number of events
795 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
796 let mut p = cli_tester_create_proposal(&git_repo, true);
797 expect_msgs_first(&mut p, true)?;
798 relay::expect_send_with_progress(
799 &mut p,
800 vec![
801 (" [my-relay] [repo-relay] ws://localhost:8055", true, ""),
802 (" [my-relay] ws://localhost:8053", true, ""),
803 (" [repo-relay] ws://localhost:8056", true, ""),
804 (" [default] ws://localhost:8051", true, ""),
805 (" [default] ws://localhost:8052", true, ""),
806 ],
807 3,
808 )?;
809 expect_msgs_after(&mut p)?;
810 p.expect_end_with_whitespace()?;
811 for p in [51, 52, 53, 55, 56] {
812 relay::shutdown_relay(8000 + p)?;
813 }
814 Ok(())
815 });
816
817 // launch relay
818 let _ = join!(
819 r51.listen_until_close(),
820 r52.listen_until_close(),
821 r53.listen_until_close(),
822 r55.listen_until_close(),
823 r56.listen_until_close(),
824 );
825 cli_tester_handle.join().unwrap()?;
826 Ok(())
827 }
828 }
829
830 mod first_event_rejected_by_1_relay {
831 use super::*;
832
833 mod only_first_rejected_event_sent_to_relay {
834 use super::*;
835
836 #[tokio::test]
837 #[serial]
838 async fn only_first_rejected_event_sent_to_relay() -> Result<()> {
839 let git_repo = prep_git_repo()?;
840
841 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
842 Relay::new(
843 8051,
844 None,
845 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
846 relay.respond_events(
847 client_id,
848 &subscription_id,
849 &vec![
850 generate_test_key_1_metadata_event("fred"),
851 generate_test_key_1_relay_list_event(),
852 ],
853 )?;
854 Ok(())
855 }),
856 ),
857 Relay::new(8052, None, None),
858 Relay::new(8053, None, None),
859 Relay::new(
860 8055,
861 None,
862 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
863 relay.respond_events(
864 client_id,
865 &subscription_id,
866 &vec![generate_repo_ref_event()],
867 )?;
868 Ok(())
869 }),
870 ),
871 Relay::new(
872 8056,
873 Some(&|relay, client_id, event| -> Result<()> {
874 relay.respond_ok(client_id, event, Some("Payment Required"))?;
875 Ok(())
876 }),
877 None,
878 ),
879 );
880
881 // // check relay had the right number of events
882 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
883 let mut p = cli_tester_create_proposal(&git_repo, true);
884 p.expect_end_eventually()?;
885 for p in [51, 52, 53, 55, 56] {
886 relay::shutdown_relay(8000 + p)?;
887 }
888 Ok(())
889 });
890
891 // launch relay
892 let _ = join!(
893 r51.listen_until_close(),
894 r52.listen_until_close(),
895 r53.listen_until_close(),
896 r55.listen_until_close(),
897 r56.listen_until_close(),
898 );
899 cli_tester_handle.join().unwrap()?;
900
901 assert_eq!(r56.events.len(), 1);
902
903 Ok(())
904 }
905 }
906
907 mod cli_show_rejection_with_comment {
908 use super::*;
909
910 #[tokio::test]
911 #[serial]
912 async fn cli_show_rejection_with_comment() -> Result<()> {
913 let git_repo = prep_git_repo()?;
914
915 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
916 Relay::new(
917 8051,
918 None,
919 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
920 relay.respond_events(
921 client_id,
922 &subscription_id,
923 &vec![
924 generate_test_key_1_metadata_event("fred"),
925 generate_test_key_1_relay_list_event(),
926 ],
927 )?;
928 Ok(())
929 }),
930 ),
931 Relay::new(8052, None, None),
932 Relay::new(8053, None, None),
933 Relay::new(
934 8055,
935 None,
936 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
937 relay.respond_events(
938 client_id,
939 &subscription_id,
940 &vec![generate_repo_ref_event()],
941 )?;
942 Ok(())
943 }),
944 ),
945 Relay::new(
946 8056,
947 Some(&|relay, client_id, event| -> Result<()> {
948 relay.respond_ok(client_id, event, Some("Payment Required"))?;
949 Ok(())
950 }),
951 None,
952 ),
953 );
954
955 // // check relay had the right number of events
956 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
957 let mut p = cli_tester_create_proposal(&git_repo, true);
958 expect_msgs_first(&mut p, true)?;
959 relay::expect_send_with_progress(
960 &mut p,
961 vec![
962 (" [my-relay] [repo-relay] ws://localhost:8055", true, ""),
963 (" [my-relay] ws://localhost:8053", true, ""),
964 (
965 " [repo-relay] ws://localhost:8056",
966 false,
967 "error: Payment Required",
968 ),
969 (" [default] ws://localhost:8051", true, ""),
970 (" [default] ws://localhost:8052", true, ""),
971 ],
972 3,
973 )?;
974 expect_msgs_after(&mut p)?;
975 p.expect_end_with_whitespace()?;
976 for p in [51, 52, 53, 55, 56] {
977 relay::shutdown_relay(8000 + p)?;
978 }
979
980 Ok(())
981 });
982
983 // launch relay
984 let _ = join!(
985 r51.listen_until_close(),
986 r52.listen_until_close(),
987 r53.listen_until_close(),
988 r55.listen_until_close(),
989 r56.listen_until_close(),
990 );
991 cli_tester_handle.join().unwrap()?;
992 Ok(())
993 }
994 }
995 }
996}
997
998mod when_no_cover_letter_flag_set_with_range_of_head_2_sends_2_patches_without_cover_letter {
999 use super::*;
1000
1001 mod cli_ouput {
1002 use super::*;
1003
1004 #[tokio::test]
1005 #[serial]
1006 async fn check_cli_output() -> Result<()> {
1007 let git_repo = prep_git_repo()?;
1008
1009 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
1010 Relay::new(
1011 8051,
1012 None,
1013 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
1014 relay.respond_events(
1015 client_id,
1016 &subscription_id,
1017 &vec![
1018 generate_test_key_1_metadata_event("fred"),
1019 generate_test_key_1_relay_list_event(),
1020 ],
1021 )?;
1022 Ok(())
1023 }),
1024 ),
1025 Relay::new(8052, None, None),
1026 Relay::new(8053, None, None),
1027 Relay::new(
1028 8055,
1029 None,
1030 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
1031 relay.respond_events(
1032 client_id,
1033 &subscription_id,
1034 &vec![generate_repo_ref_event()],
1035 )?;
1036 Ok(())
1037 }),
1038 ),
1039 Relay::new(8056, None, None),
1040 );
1041
1042 // // check relay had the right number of events
1043 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
1044 let mut p = cli_tester_create_proposal(&git_repo, false);
1045
1046 expect_msgs_first(&mut p, false)?;
1047 relay::expect_send_with_progress(
1048 &mut p,
1049 vec![
1050 (" [my-relay] [repo-relay] ws://localhost:8055", true, ""),
1051 (" [my-relay] ws://localhost:8053", true, ""),
1052 (" [repo-relay] ws://localhost:8056", true, ""),
1053 (" [default] ws://localhost:8051", true, ""),
1054 (" [default] ws://localhost:8052", true, ""),
1055 ],
1056 2,
1057 )?;
1058 expect_msgs_after(&mut p)?;
1059 p.expect_end_with_whitespace()?;
1060 for p in [51, 52, 53, 55, 56] {
1061 relay::shutdown_relay(8000 + p)?;
1062 }
1063 Ok(())
1064 });
1065
1066 // launch relay
1067 let _ = join!(
1068 r51.listen_until_close(),
1069 r52.listen_until_close(),
1070 r53.listen_until_close(),
1071 r55.listen_until_close(),
1072 r56.listen_until_close(),
1073 );
1074 cli_tester_handle.join().unwrap()?;
1075 Ok(())
1076 }
1077 }
1078
1079 #[tokio::test]
1080 #[serial]
1081 async fn no_cover_letter_event() -> Result<()> {
1082 let (_, _, r53, r55, r56) = prep_run_create_proposal(false).await?;
1083 for relay in [&r53, &r55, &r56] {
1084 assert_eq!(
1085 relay.events.iter().filter(|e| is_cover_letter(e)).count(),
1086 0,
1087 );
1088 }
1089 Ok(())
1090 }
1091
1092 #[tokio::test]
1093 #[serial]
1094 async fn two_patch_events() -> Result<()> {
1095 let (_, _, r53, r55, r56) = prep_run_create_proposal(false).await?;
1096 for relay in [&r53, &r55, &r56] {
1097 assert_eq!(relay.events.iter().filter(|e| is_patch(e)).count(), 2);
1098 }
1099 Ok(())
1100 }
1101
1102 #[tokio::test]
1103 #[serial]
1104 // TODO check this is the ancestor
1105 async fn first_patch_with_root_t_tag() -> Result<()> {
1106 let (_, _, r53, r55, r56) = prep_run_create_proposal(false).await?;
1107 for relay in [&r53, &r55, &r56] {
1108 let patch_events = relay
1109 .events
1110 .iter()
1111 .filter(|e| is_patch(e))
1112 .collect::<Vec<&nostr::Event>>();
1113
1114 // first patch tagged as root
1115 assert!(
1116 patch_events[0]
1117 .tags()
1118 .iter()
1119 .any(|t| t.as_vec()[0].eq("t") && t.as_vec()[1].eq("root"))
1120 );
1121 // second patch not tagged as root
1122 assert!(
1123 !patch_events[1]
1124 .tags()
1125 .iter()
1126 .any(|t| t.as_vec()[0].eq("t") && t.as_vec()[1].eq("root"))
1127 );
1128 }
1129 Ok(())
1130 }
1131
1132 #[tokio::test]
1133 #[serial]
1134 async fn root_patch_tags_branch_name() -> Result<()> {
1135 let (_, _, r53, r55, r56) = prep_run_create_proposal(false).await?;
1136 for relay in [&r53, &r55, &r56] {
1137 let patch_events = relay
1138 .events
1139 .iter()
1140 .filter(|e| is_patch(e))
1141 .collect::<Vec<&nostr::Event>>();
1142
1143 // branch-name tag
1144 assert_eq!(
1145 patch_events[0]
1146 .tags()
1147 .iter()
1148 .find(|t| t.as_vec()[0].eq("branch-name"))
1149 .unwrap()
1150 .as_vec()[1],
1151 "feature"
1152 );
1153 }
1154 Ok(())
1155 }
1156
1157 #[tokio::test]
1158 #[serial]
1159 async fn second_patch_lists_first_as_root() -> Result<()> {
1160 let (_, _, r53, r55, r56) = prep_run_create_proposal(false).await?;
1161 for relay in [&r53, &r55, &r56] {
1162 let patch_events = relay
1163 .events
1164 .iter()
1165 .filter(|e| is_patch(e))
1166 .collect::<Vec<&nostr::Event>>();
1167
1168 assert_eq!(
1169 patch_events[1]
1170 .tags()
1171 .iter()
1172 .find(|t| t.as_vec()[0].eq("e")
1173 && t.as_vec().len().eq(&4)
1174 && t.as_vec()[3].eq("root"))
1175 .unwrap()
1176 .as_vec()[1],
1177 patch_events[0].id.to_string(),
1178 );
1179 }
1180 Ok(())
1181 }
1182}
1183
1184mod when_range_ommited_prompts_for_selection_defaulting_ahead_of_main {
1185 use super::*;
1186
1187 fn cli_tester_create_proposal(git_repo: &GitTestRepo) -> CliTester {
1188 let args = vec![
1189 "--nsec",
1190 TEST_KEY_1_NSEC,
1191 "--password",
1192 TEST_PASSWORD,
1193 "--disable-cli-spinners",
1194 "send",
1195 "--no-cover-letter",
1196 ];
1197 CliTester::new_from_dir(&git_repo.dir, args)
1198 }
1199 fn expect_msgs_first(p: &mut CliTester) -> Result<()> {
1200 p.expect("fetching updates...\r\n")?;
1201 p.expect_eventually("\r\n")?; // may be 'no updates' or some updates
1202 let mut selector = p.expect_multi_select(
1203 "select commits for proposal",
1204 vec![
1205 "(Joe Bloggs) add t4.md [feature] fe973a8".to_string(),
1206 "(Joe Bloggs) add t3.md 232efb3".to_string(),
1207 "(Joe Bloggs) add t2.md [main] 431b84e".to_string(),
1208 "(Joe Bloggs) add t1.md af474d8".to_string(),
1209 "(Joe Bloggs) Initial commit 9ee507f".to_string(),
1210 ],
1211 )?;
1212 selector.succeeds_with(vec![0, 1], false, vec![0, 1])?;
1213 p.expect("creating proposal from 2 commits:\r\n")?;
1214 p.expect("fe973a8 add t4.md\r\n")?;
1215 p.expect("232efb3 add t3.md\r\n")?;
1216 p.expect("searching for profile...\r\n")?;
1217 p.expect("logged in as fred\r\n")?;
1218 p.expect("posting 2 patches without a covering letter...\r\n")?;
1219 Ok(())
1220 }
1221 async fn prep_run_create_proposal() -> Result<(
1222 Relay<'static>,
1223 Relay<'static>,
1224 Relay<'static>,
1225 Relay<'static>,
1226 Relay<'static>,
1227 )> {
1228 let git_repo = prep_git_repo()?;
1229
1230 // fallback (51,52) user write (53, 55) repo (55, 56)
1231 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
1232 Relay::new(
1233 8051,
1234 None,
1235 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
1236 relay.respond_events(
1237 client_id,
1238 &subscription_id,
1239 &vec![
1240 generate_test_key_1_metadata_event("fred"),
1241 generate_test_key_1_relay_list_event(),
1242 ],
1243 )?;
1244 Ok(())
1245 }),
1246 ),
1247 Relay::new(8052, None, None),
1248 Relay::new(8053, None, None),
1249 Relay::new(
1250 8055,
1251 None,
1252 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
1253 relay.respond_events(
1254 client_id,
1255 &subscription_id,
1256 &vec![generate_repo_ref_event()],
1257 )?;
1258 Ok(())
1259 }),
1260 ),
1261 Relay::new(8056, None, None),
1262 );
1263
1264 // // check relay had the right number of events
1265 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
1266 let mut p = cli_tester_create_proposal(&git_repo);
1267 expect_msgs_first(&mut p)?;
1268 p.expect_end_eventually()?;
1269 for p in [51, 52, 53, 55, 56] {
1270 relay::shutdown_relay(8000 + p)?;
1271 }
1272 Ok(())
1273 });
1274
1275 // launch relay
1276 let _ = join!(
1277 r51.listen_until_close(),
1278 r52.listen_until_close(),
1279 r53.listen_until_close(),
1280 r55.listen_until_close(),
1281 r56.listen_until_close(),
1282 );
1283 cli_tester_handle.join().unwrap()?;
1284 Ok((r51, r52, r53, r55, r56))
1285 }
1286 mod cli_ouput {
1287 use super::*;
1288
1289 #[tokio::test]
1290 #[serial]
1291 async fn check_cli_output() -> Result<()> {
1292 let git_repo = prep_git_repo()?;
1293
1294 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
1295 Relay::new(
1296 8051,
1297 None,
1298 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
1299 relay.respond_events(
1300 client_id,
1301 &subscription_id,
1302 &vec![
1303 generate_test_key_1_metadata_event("fred"),
1304 generate_test_key_1_relay_list_event(),
1305 ],
1306 )?;
1307 Ok(())
1308 }),
1309 ),
1310 Relay::new(8052, None, None),
1311 Relay::new(8053, None, None),
1312 Relay::new(
1313 8055,
1314 None,
1315 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
1316 relay.respond_events(
1317 client_id,
1318 &subscription_id,
1319 &vec![generate_repo_ref_event()],
1320 )?;
1321 Ok(())
1322 }),
1323 ),
1324 Relay::new(8056, None, None),
1325 );
1326
1327 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
1328 let mut p = cli_tester_create_proposal(&git_repo);
1329
1330 expect_msgs_first(&mut p)?;
1331 relay::expect_send_with_progress(
1332 &mut p,
1333 vec![
1334 (" [my-relay] [repo-relay] ws://localhost:8055", true, ""),
1335 (" [my-relay] ws://localhost:8053", true, ""),
1336 (" [repo-relay] ws://localhost:8056", true, ""),
1337 (" [default] ws://localhost:8051", true, ""),
1338 (" [default] ws://localhost:8052", true, ""),
1339 ],
1340 2,
1341 )?;
1342 expect_msgs_after(&mut p)?;
1343 p.expect_end_with_whitespace()?;
1344 for p in [51, 52, 53, 55, 56] {
1345 relay::shutdown_relay(8000 + p)?;
1346 }
1347 Ok(())
1348 });
1349
1350 // launch relay
1351 let _ = join!(
1352 r51.listen_until_close(),
1353 r52.listen_until_close(),
1354 r53.listen_until_close(),
1355 r55.listen_until_close(),
1356 r56.listen_until_close(),
1357 );
1358 cli_tester_handle.join().unwrap()?;
1359 Ok(())
1360 }
1361 }
1362
1363 #[tokio::test]
1364 #[serial]
1365 async fn two_patch_events_sent() -> Result<()> {
1366 let (_, _, r53, r55, r56) = prep_run_create_proposal().await?;
1367 for relay in [&r53, &r55, &r56] {
1368 assert_eq!(relay.events.iter().filter(|e| is_patch(e)).count(), 2);
1369 }
1370 Ok(())
1371 }
1372}
1373
1374mod root_proposal_specified_using_in_reply_to_with_range_of_head_2_and_cover_letter_details_specified {
1375
1376 use nostr::ToBech32;
1377
1378 use super::*;
1379
1380 fn cli_tester_create_proposal(git_repo: &GitTestRepo) -> CliTester {
1381 let proposal_root_bech32 = get_pretend_proposal_root_event().id.to_bech32().unwrap();
1382 let args = vec![
1383 "--nsec",
1384 TEST_KEY_1_NSEC,
1385 "--password",
1386 TEST_PASSWORD,
1387 "--disable-cli-spinners",
1388 "send",
1389 "HEAD~2",
1390 "--in-reply-to",
1391 &proposal_root_bech32,
1392 "--title",
1393 "exampletitle",
1394 "--description",
1395 "exampledescription",
1396 ];
1397 CliTester::new_from_dir(&git_repo.dir, args)
1398 }
1399 fn expect_msgs_first(p: &mut CliTester, include_cover_letter: bool) -> Result<()> {
1400 p.expect("fetching updates...\r\n")?;
1401 p.expect("updates: 1 new maintainer, 1 announcement update, 1 proposal\r\n")?;
1402 let proposal_root_bech32 = get_pretend_proposal_root_event().id.to_bech32().unwrap();
1403 p.expect(format!(
1404 "creating proposal revision for: {}\r\n",
1405 proposal_root_bech32,
1406 ))?;
1407 p.expect("creating proposal from 2 commits:\r\n")?;
1408 p.expect("fe973a8 add t4.md\r\n")?;
1409 p.expect("232efb3 add t3.md\r\n")?;
1410 p.expect("logged in as fred\r\n")?;
1411 p.expect(format!(
1412 "posting 2 patches {} a covering letter...\r\n",
1413 if include_cover_letter {
1414 "with"
1415 } else {
1416 "without"
1417 }
1418 ))?;
1419 Ok(())
1420 }
1421
1422 async fn prep_run_create_proposal() -> Result<(
1423 Relay<'static>,
1424 Relay<'static>,
1425 Relay<'static>,
1426 Relay<'static>,
1427 Relay<'static>,
1428 )> {
1429 let git_repo = prep_git_repo()?;
1430 // fallback (51,52) user write (53, 55) repo (55, 56)
1431 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
1432 Relay::new(
1433 8051,
1434 None,
1435 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
1436 relay.respond_events(
1437 client_id,
1438 &subscription_id,
1439 &vec![
1440 generate_test_key_1_metadata_event("fred"),
1441 generate_test_key_1_relay_list_event(),
1442 get_pretend_proposal_root_event(),
1443 ],
1444 )?;
1445 Ok(())
1446 }),
1447 ),
1448 Relay::new(8052, None, None),
1449 Relay::new(8053, None, None),
1450 Relay::new(
1451 8055,
1452 None,
1453 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
1454 relay.respond_events(
1455 client_id,
1456 &subscription_id,
1457 &vec![generate_repo_ref_event(), get_pretend_proposal_root_event()],
1458 )?;
1459 Ok(())
1460 }),
1461 ),
1462 Relay::new(8056, None, None),
1463 );
1464
1465 // // check relay had the right number of events
1466 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
1467 let mut p = cli_tester_create_proposal(&git_repo);
1468 p.expect_end_eventually()?;
1469 for p in [51, 52, 53, 55, 56] {
1470 relay::shutdown_relay(8000 + p)?;
1471 }
1472 Ok(())
1473 });
1474
1475 // launch relay
1476 let _ = join!(
1477 r51.listen_until_close(),
1478 r52.listen_until_close(),
1479 r53.listen_until_close(),
1480 r55.listen_until_close(),
1481 r56.listen_until_close(),
1482 );
1483 cli_tester_handle.join().unwrap()?;
1484 Ok((r51, r52, r53, r55, r56))
1485 }
1486 mod cli_ouput {
1487 use super::*;
1488
1489 #[tokio::test]
1490 #[serial]
1491 async fn check_cli_output() -> Result<()> {
1492 let git_repo = prep_git_repo()?;
1493
1494 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
1495 Relay::new(
1496 8051,
1497 None,
1498 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
1499 relay.respond_events(
1500 client_id,
1501 &subscription_id,
1502 &vec![
1503 generate_test_key_1_metadata_event("fred"),
1504 generate_test_key_1_relay_list_event(),
1505 get_pretend_proposal_root_event(),
1506 ],
1507 )?;
1508 Ok(())
1509 }),
1510 ),
1511 Relay::new(8052, None, None),
1512 Relay::new(8053, None, None),
1513 Relay::new(
1514 8055,
1515 None,
1516 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
1517 relay.respond_events(
1518 client_id,
1519 &subscription_id,
1520 &vec![generate_repo_ref_event(), get_pretend_proposal_root_event()],
1521 )?;
1522 Ok(())
1523 }),
1524 ),
1525 Relay::new(8056, None, None),
1526 );
1527
1528 // // check relay had the right number of events
1529 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
1530 let mut p = cli_tester_create_proposal(&git_repo);
1531 expect_msgs_first(&mut p, true)?;
1532 relay::expect_send_with_progress(
1533 &mut p,
1534 vec![
1535 (" [my-relay] [repo-relay] ws://localhost:8055", true, ""),
1536 (" [my-relay] ws://localhost:8053", true, ""),
1537 (" [repo-relay] ws://localhost:8056", true, ""),
1538 (" [default] ws://localhost:8051", true, ""),
1539 (" [default] ws://localhost:8052", true, ""),
1540 ],
1541 3,
1542 )?;
1543 p.expect_end_with_whitespace()?;
1544 for p in [51, 52, 53, 55, 56] {
1545 relay::shutdown_relay(8000 + p)?;
1546 }
1547 Ok(())
1548 });
1549
1550 // launch relay
1551 let _ = join!(
1552 r51.listen_until_close(),
1553 r52.listen_until_close(),
1554 r53.listen_until_close(),
1555 r55.listen_until_close(),
1556 r56.listen_until_close(),
1557 );
1558 cli_tester_handle.join().unwrap()?;
1559 Ok(())
1560 }
1561 }
1562
1563 mod cover_letter_tags {
1564 use super::*;
1565
1566 #[tokio::test]
1567 #[serial]
1568 async fn t_tag_root() -> Result<()> {
1569 let (_, _, r53, r55, r56) = prep_run_create_proposal().await?;
1570 for relay in [&r53, &r55, &r56] {
1571 let cover_letter_event: &nostr::Event =
1572 relay.events.iter().find(|e| is_cover_letter(e)).unwrap();
1573 assert!(
1574 cover_letter_event
1575 .tags()
1576 .iter()
1577 .any(|t| { t.as_vec()[0].eq("t") && t.as_vec()[1].eq(&"root") })
1578 );
1579 }
1580 Ok(())
1581 }
1582
1583 #[tokio::test]
1584 #[serial]
1585 async fn t_tag_revision_root() -> Result<()> {
1586 let (_, _, r53, r55, r56) = prep_run_create_proposal().await?;
1587 for relay in [&r53, &r55, &r56] {
1588 let cover_letter_event: &nostr::Event =
1589 relay.events.iter().find(|e| is_cover_letter(e)).unwrap();
1590 assert!(
1591 cover_letter_event
1592 .tags()
1593 .iter()
1594 .any(|t| { t.as_vec()[0].eq("t") && t.as_vec()[1].eq(&"revision-root") })
1595 );
1596 }
1597 Ok(())
1598 }
1599
1600 #[tokio::test]
1601 #[serial]
1602 async fn e_tag_in_reply_to_event_as_reply() -> Result<()> {
1603 let (_, _, r53, r55, r56) = prep_run_create_proposal().await?;
1604 for relay in [&r53, &r55, &r56] {
1605 let cover_letter_event: &nostr::Event =
1606 relay.events.iter().find(|e| is_cover_letter(e)).unwrap();
1607 assert_eq!(
1608 cover_letter_event
1609 .tags()
1610 .iter()
1611 .find(|t| {
1612 t.as_vec()[0].eq("e")
1613 && t.as_vec().len().eq(&4)
1614 && t.as_vec()[3].eq("reply")
1615 })
1616 .unwrap()
1617 .as_vec()[1],
1618 // id of state nevent
1619 "431e58eb8e1b4e20292d1d5bbe81d5cfb042e1bc165de32eddfdd52245a4cce4",
1620 );
1621 }
1622 Ok(())
1623 }
1624 }
1625
1626 #[tokio::test]
1627 #[serial]
1628 async fn patch_tags_cover_letter_event_as_root() -> Result<()> {
1629 let (_, _, r53, r55, r56) = prep_run_create_proposal().await?;
1630 for relay in [&r53, &r55, &r56] {
1631 let patch_events: Vec<&nostr::Event> =
1632 relay.events.iter().filter(|e| is_patch(e)).collect();
1633
1634 let cover_letter_event = relay.events.iter().find(|e| is_cover_letter(e)).unwrap();
1635
1636 for patch in patch_events {
1637 assert_eq!(
1638 patch
1639 .tags
1640 .iter()
1641 .find(|t| {
1642 t.as_vec()[0].eq("e")
1643 && t.as_vec().len().eq(&4)
1644 && t.as_vec()[3].eq("root")
1645 })
1646 .unwrap()
1647 .as_vec()[1],
1648 cover_letter_event.id.to_string()
1649 );
1650 }
1651 }
1652 Ok(())
1653 }
1654}
1655
1656mod in_reply_to_mentions_issue {
1657 use nostr::ToBech32;
1658
1659 use super::*;
1660 pub fn get_pretend_issue_event() -> nostr::Event {
1661 serde_json::from_str(r#"{"created_at":1709286372,"content":"please provide feedback\nthis is an example ngit issue to demonstrate gitworkshop.dev.\n\nplease provide feedback with in reply to this issue or by creating a new issue.","tags":[["r","26689f97810fc656c7134c76e2a37d33b2e40ce7"],["a","30617:a008def15796fba9a0d6fab04e8fd57089285d9fd505da5a83fe8aad57a3564d:ngit","wss://relay.damus.io","root"],["p","a008def15796fba9a0d6fab04e8fd57089285d9fd505da5a83fe8aad57a3564d"]],"kind":1621,"pubkey":"a008def15796fba9a0d6fab04e8fd57089285d9fd505da5a83fe8aad57a3564d","id":"e944765d625ae7323d080da0df069c726a0e5490a17b452f854d85e18f781588","sig":"a1af9e89a35f1f7ef93e3de33986bd86cb7c4d7d9abb233c0c6405f32b5788171e47f84551afe8515b3107d12f03472721ea784b8791ff3f25e66a3169a54c20"}"#).unwrap()
1662 }
1663
1664 fn cli_tester_create_proposal(git_repo: &GitTestRepo) -> CliTester {
1665 let issue_bech32 = get_pretend_issue_event().id.to_bech32().unwrap();
1666 let args = vec![
1667 "--nsec",
1668 TEST_KEY_1_NSEC,
1669 "--password",
1670 TEST_PASSWORD,
1671 "--disable-cli-spinners",
1672 "send",
1673 "HEAD~2",
1674 "--in-reply-to",
1675 &issue_bech32,
1676 // "note1a9z8vhtzttnny0ggpksd7p5uwf4qu4ys59a52tu9fkz7rrmczkyqc46ngg",
1677 "--title",
1678 "exampletitle",
1679 "--description",
1680 "exampledescription",
1681 ];
1682 CliTester::new_from_dir(&git_repo.dir, args)
1683 }
1684
1685 async fn prep_run_create_proposal() -> Result<(
1686 Relay<'static>,
1687 Relay<'static>,
1688 Relay<'static>,
1689 Relay<'static>,
1690 Relay<'static>,
1691 )> {
1692 let git_repo = prep_git_repo()?;
1693 // fallback (51,52) user write (53, 55) repo (55, 56)
1694 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
1695 Relay::new(
1696 8051,
1697 None,
1698 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
1699 relay.respond_events(
1700 client_id,
1701 &subscription_id,
1702 &vec![
1703 generate_test_key_1_metadata_event("fred"),
1704 generate_test_key_1_relay_list_event(),
1705 get_pretend_issue_event(),
1706 ],
1707 )?;
1708 Ok(())
1709 }),
1710 ),
1711 Relay::new(8052, None, None),
1712 Relay::new(8053, None, None),
1713 Relay::new(
1714 8055,
1715 None,
1716 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
1717 relay.respond_events(
1718 client_id,
1719 &subscription_id,
1720 &vec![generate_repo_ref_event(), get_pretend_issue_event()],
1721 )?;
1722 Ok(())
1723 }),
1724 ),
1725 Relay::new(8056, None, None),
1726 );
1727
1728 // // check relay had the right number of events
1729 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
1730 let mut p = cli_tester_create_proposal(&git_repo);
1731 p.expect_end_eventually()?;
1732 for p in [51, 52, 53, 55, 56] {
1733 relay::shutdown_relay(8000 + p)?;
1734 }
1735 Ok(())
1736 });
1737
1738 // launch relay
1739 let _ = join!(
1740 r51.listen_until_close(),
1741 r52.listen_until_close(),
1742 r53.listen_until_close(),
1743 r55.listen_until_close(),
1744 r56.listen_until_close(),
1745 );
1746 cli_tester_handle.join().unwrap()?;
1747 Ok((r51, r52, r53, r55, r56))
1748 }
1749
1750 #[tokio::test]
1751 #[serial]
1752 async fn issue_event_mentioned_in_tagged_cover_letter() -> Result<()> {
1753 let (_, _, r53, r55, r56) = prep_run_create_proposal().await?;
1754 for relay in [&r53, &r55, &r56] {
1755 let cover_letter_event: &nostr::Event =
1756 relay.events.iter().find(|e| is_cover_letter(e)).unwrap();
1757 assert!(cover_letter_event.tags().iter().any(|t| {
1758 t.as_vec()[0].eq("e")
1759 && t.as_vec()[1].eq(&get_pretend_issue_event().id.to_hex())
1760 && t.as_vec()[3].eq(&"mention")
1761 }));
1762 }
1763 Ok(())
1764 }
1765
1766 #[tokio::test]
1767 #[serial]
1768 async fn isnt_tagged_as_revision() -> Result<()> {
1769 let (_, _, r53, r55, r56) = prep_run_create_proposal().await?;
1770 for relay in [&r53, &r55, &r56] {
1771 let cover_letter_event: &nostr::Event =
1772 relay.events.iter().find(|e| is_cover_letter(e)).unwrap();
1773 assert!(
1774 !cover_letter_event
1775 .tags()
1776 .iter()
1777 .any(|t| { t.as_vec()[0].eq("t") && t.as_vec()[1].eq(&"revision-root") })
1778 );
1779 }
1780 Ok(())
1781 }
1782}
1783mod in_reply_to_mentions_npub_and_nprofile_which_get_mentioned_in_proposal_root {
1784
1785 use super::*;
1786
1787 fn cli_tester_create_proposal(git_repo: &GitTestRepo) -> CliTester {
1788 let args = vec![
1789 "--nsec",
1790 TEST_KEY_1_NSEC,
1791 "--password",
1792 TEST_PASSWORD,
1793 "--disable-cli-spinners",
1794 "send",
1795 "HEAD~2",
1796 "--in-reply-to",
1797 // nsec1q3c5xnsm5m4wgsrhwnz04p0d5mevkryyggqgdpa9jwulpq9gldhswgtxvq
1798 "npub1knxeegzqg0xqflsryvg7l7x7nmpe7kd7pl7zazug0a7t99tdsphszuyapx",
1799 // nsec1nx5ulvcndhcuu8k6q8fenw50l6y75sec7pj8vr0r68l6a44w3lqspvj02k
1800 "nprofile1qqsvru3yqrec6dxjn06f8cjh79jcu9wyaxu4y6v47yzpsx7vjm4xcuc33z2n3",
1801 "--title",
1802 "exampletitle",
1803 "--description",
1804 "exampledescription",
1805 ];
1806 CliTester::new_from_dir(&git_repo.dir, args)
1807 }
1808
1809 async fn prep_run_create_proposal() -> Result<(
1810 Relay<'static>,
1811 Relay<'static>,
1812 Relay<'static>,
1813 Relay<'static>,
1814 Relay<'static>,
1815 )> {
1816 let git_repo = prep_git_repo()?;
1817 // fallback (51,52) user write (53, 55) repo (55, 56)
1818 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
1819 Relay::new(
1820 8051,
1821 None,
1822 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
1823 relay.respond_events(
1824 client_id,
1825 &subscription_id,
1826 &vec![
1827 generate_test_key_1_metadata_event("fred"),
1828 generate_test_key_1_relay_list_event(),
1829 ],
1830 )?;
1831 Ok(())
1832 }),
1833 ),
1834 Relay::new(8052, None, None),
1835 Relay::new(8053, None, None),
1836 Relay::new(
1837 8055,
1838 None,
1839 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
1840 relay.respond_events(
1841 client_id,
1842 &subscription_id,
1843 &vec![generate_repo_ref_event()],
1844 )?;
1845 Ok(())
1846 }),
1847 ),
1848 Relay::new(8056, None, None),
1849 );
1850
1851 // // check relay had the right number of events
1852 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
1853 let mut p = cli_tester_create_proposal(&git_repo);
1854 p.expect_end_eventually()?;
1855 for p in [51, 52, 53, 55, 56] {
1856 relay::shutdown_relay(8000 + p)?;
1857 }
1858 Ok(())
1859 });
1860
1861 // launch relay
1862 let _ = join!(
1863 r51.listen_until_close(),
1864 r52.listen_until_close(),
1865 r53.listen_until_close(),
1866 r55.listen_until_close(),
1867 r56.listen_until_close(),
1868 );
1869 cli_tester_handle.join().unwrap()?;
1870 Ok((r51, r52, r53, r55, r56))
1871 }
1872
1873 #[tokio::test]
1874 #[serial]
1875 async fn npub_and_nprofile_mentioned_in_tagged_cover_letter() -> Result<()> {
1876 let (_, _, r53, r55, r56) = prep_run_create_proposal().await?;
1877 for relay in [&r53, &r55, &r56] {
1878 let cover_letter_event: &nostr::Event =
1879 relay.events.iter().find(|e| is_cover_letter(e)).unwrap();
1880 assert!(cover_letter_event.tags().iter().any(|t| {
1881 t.as_vec()[0].eq("p")
1882 && t.as_vec()[1].eq(&nostr::Keys::parse(
1883 "nsec1q3c5xnsm5m4wgsrhwnz04p0d5mevkryyggqgdpa9jwulpq9gldhswgtxvq",
1884 )
1885 .unwrap()
1886 .public_key()
1887 .to_hex())
1888 }));
1889 assert!(cover_letter_event.tags().iter().any(|t| {
1890 t.as_vec()[0].eq("p")
1891 && t.as_vec()[1].eq(&nostr::Keys::parse(
1892 "nsec1nx5ulvcndhcuu8k6q8fenw50l6y75sec7pj8vr0r68l6a44w3lqspvj02k",
1893 )
1894 .unwrap()
1895 .public_key()
1896 .to_hex())
1897 }));
1898 }
1899 Ok(())
1900 }
1901}