upleb.uk

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

summaryrefslogtreecommitdiff
path: root/tests/ngit/send.rs
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/send.rs
parentd2d0eeb72912809a00f09fafdae4e827a34d0e26 (diff)
test: refactor into binary subdirs
in prep for splitting git_remote_nostr tests
Diffstat (limited to 'tests/ngit/send.rs')
-rw-r--r--tests/ngit/send.rs1901
1 files changed, 1901 insertions, 0 deletions
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}