upleb.uk

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

summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2026-03-05 19:57:28 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2026-03-05 21:31:17 +0000
commit83b0886b97e2e90e328f91fcfaeb59726c93308f (patch)
tree7e5f17529608738c98dd45c8c01f61e3d12b1b32 /tests
parent84a197700cac6b2ef72cf0723474ac3185c5d1de (diff)
test(pr-checkout): replace broken ngit_list tests with ngit_pr_checkout
tests/ngit_list.rs had 27 tests all failing because the interactive mode they tested has been replaced by a non-interactive implementation. Replace the file with a stub documenting the coverage gaps and add tests/ngit_pr_checkout.rs covering the same proposal branch checkout logic via `ngit pr checkout <id>`, starting with the fresh-checkout case.
Diffstat (limited to 'tests')
-rw-r--r--tests/ngit_list.rs1558
-rw-r--r--tests/ngit_pr_checkout.rs536
2 files changed, 536 insertions, 1558 deletions
diff --git a/tests/ngit_list.rs b/tests/ngit_list.rs
deleted file mode 100644
index 30e6fe6..0000000
--- a/tests/ngit_list.rs
+++ /dev/null
@@ -1,1558 +0,0 @@
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::{
53 ToBech32,
54 nips::{nip01::Coordinate, nip19::Nip19Coordinate},
55 };
56 use nostr_sdk::RelayUrl;
57
58 use super::*;
59 async fn run_async_repo_event_ref_needed(invalid_input: bool, naddr: bool) -> Result<()> {
60 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
61 Relay::new(8051, None, None),
62 Relay::new(8052, None, None),
63 Relay::new(8053, None, None),
64 Relay::new(8055, None, None),
65 Relay::new(8056, None, None),
66 );
67
68 r51.events.push(generate_test_key_1_relay_list_event());
69 r51.events.push(generate_test_key_1_metadata_event("fred"));
70
71 r55.events.push(generate_test_key_1_relay_list_event());
72 r55.events.push(generate_test_key_1_metadata_event("fred"));
73
74 let repo_event = generate_repo_ref_event();
75 r56.events.push(repo_event.clone());
76
77 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
78 let test_repo = GitTestRepo::without_repo_in_git_config();
79 test_repo.populate()?;
80 let mut p = CliTester::new_from_dir(&test_repo.dir, ["-i", "list"]);
81 p.expect(
82 "hint: https://gitworkshop.dev/search lists repositories and their nostr address\r\n",
83 )?;
84 if invalid_input {
85 let mut input = p.expect_input("nostr repository")?;
86 input.succeeds_with("dfgvfvfzadvd")?;
87 p.expect("not a valid naddr or git nostr remote URL starting nostr://\r\n")?;
88 let _ = p.expect_input("nostr repository")?;
89 p.exit()?;
90 }
91 if naddr {
92 let mut input = p.expect_input("nostr repository")?;
93 let coordinate = Nip19Coordinate {
94 coordinate: Coordinate {
95 kind: nostr::Kind::GitRepoAnnouncement,
96 public_key: TEST_KEY_1_KEYS.public_key(),
97 identifier: repo_event.tags.identifier().unwrap().to_string(),
98 },
99 relays: vec![RelayUrl::parse("ws://localhost:8056").unwrap()],
100 };
101 input.succeeds_with(&coordinate.to_bech32()?)?;
102 p.expect("searching for repository...\r\n")?;
103 p.expect_eventually("repository found\r\n")?;
104 p.expect_confirm(
105 "set git remote \"origin\" to nostr repository url?",
106 Some(true),
107 )?
108 .succeeds_with(None)?;
109 let nostr_url = format!(
110 "nostr://{}/{}/{}",
111 TEST_KEY_1_NPUB,
112 // first relay in repo ann event
113 urlencoding::encode("ws://localhost:8055"),
114 coordinate.identifier,
115 );
116 p.expect(format!("set git remote \"origin\" to {}\r\n", &nostr_url))?;
117 p.expect("Checking nostr relays...\r\n")?;
118 // no updates as they were fetched when searching for repo
119 p.expect("no updates\r\n")?;
120 p.expect_end_with("no proposals found... create one? try `ngit send`\r\n")?;
121
122 assert_eq!(
123 test_repo.git_repo.find_remote("origin")?.url().unwrap(),
124 nostr_url
125 );
126 }
127
128 for p in [51, 52, 53, 55, 56] {
129 relay::shutdown_relay(8000 + p)?;
130 }
131 Ok(())
132 });
133
134 // launch relay
135 let _ = join!(
136 r51.listen_until_close(),
137 r52.listen_until_close(),
138 r53.listen_until_close(),
139 r55.listen_until_close(),
140 r56.listen_until_close(),
141 );
142 cli_tester_handle.join().unwrap()?;
143 Ok(())
144 }
145
146 #[tokio::test]
147 #[serial]
148 async fn warns_not_valid_input_and_asks_again() -> Result<()> {
149 run_async_repo_event_ref_needed(true, false).await
150 }
151
152 #[tokio::test]
153 #[serial]
154 async fn finds_based_on_naddr_on_embeded_relay_and_added_as_origin_remote() -> Result<()> {
155 run_async_repo_event_ref_needed(false, true).await
156 }
157 }
158}
159mod when_main_branch_is_uptodate {
160 use super::*;
161
162 mod when_proposal_branch_doesnt_exist {
163 use super::*;
164
165 mod when_main_is_checked_out {
166 use super::*;
167
168 mod when_first_proposal_selected {
169 use super::*;
170
171 // TODO: test when other proposals with the same name but from other
172 // repositories are present on relays
173
174 mod cli_prompts {
175 use super::*;
176 #[tokio::test]
177 #[serial]
178 async fn prompts_to_choose_from_proposal_titles() -> Result<()> {
179 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
180 Relay::new(8051, None, None),
181 Relay::new(8052, None, None),
182 Relay::new(8053, None, None),
183 Relay::new(8055, None, None),
184 Relay::new(8056, None, None),
185 );
186
187 r51.events.push(generate_test_key_1_relay_list_event());
188 r51.events.push(generate_test_key_1_metadata_event("fred"));
189 r51.events.push(generate_repo_ref_event());
190
191 r55.events.push(generate_repo_ref_event());
192 r55.events.push(generate_test_key_1_metadata_event("fred"));
193 r55.events.push(generate_test_key_1_relay_list_event());
194
195 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
196 cli_tester_create_proposals()?;
197
198 let test_repo = GitTestRepo::default();
199 test_repo.populate()?;
200 let mut p = CliTester::new_from_dir(&test_repo.dir, ["-i", "list"]);
201
202 p.expect("Checking nostr relays...\r\n")?;
203 p.expect_eventually("\r\n")?; // some updates listed here
204 let mut c = p.expect_choice(
205 "all proposals",
206 vec![
207 format!("\"{PROPOSAL_TITLE_3}\""),
208 format!("\"{PROPOSAL_TITLE_2}\""),
209 format!("\"{PROPOSAL_TITLE_1}\""),
210 ],
211 )?;
212 c.succeeds_with(2, true, None)?;
213 let mut c = p.expect_choice("", vec![
214 format!(
215 "create and checkout proposal branch (2 ahead 0 behind 'main')"
216 ),
217 format!("apply to current branch with `git am`"),
218 format!("download to ./patches"),
219 format!("back"),
220 ])?;
221 c.succeeds_with(0, true, None)?;
222 p.expect(format!(
223 "checked out proposal as 'pr/{FEATURE_BRANCH_NAME_1}(",
224 ))?;
225 p.expect_end_eventually_with(")' branch\r\n")?;
226
227 for p in [51, 52, 53, 55, 56] {
228 relay::shutdown_relay(8000 + p)?;
229 }
230 Ok(())
231 });
232
233 // launch relay
234 let _ = join!(
235 r51.listen_until_close(),
236 r52.listen_until_close(),
237 r53.listen_until_close(),
238 r55.listen_until_close(),
239 r56.listen_until_close(),
240 );
241 cli_tester_handle.join().unwrap()?;
242 println!("{:?}", r55.events);
243 Ok(())
244 }
245 }
246
247 #[tokio::test]
248 #[serial]
249 async fn proposal_branch_created_with_correct_name() -> Result<()> {
250 let (_, test_repo) =
251 prep_proposals_repo_and_repo_with_proposal_pulled_and_checkedout(1).await?;
252 assert_eq!(
253 vec![
254 "main",
255 &get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_1)?,
256 ],
257 test_repo.get_local_branch_names()?
258 );
259 Ok(())
260 }
261
262 #[tokio::test]
263 #[serial]
264 async fn proposal_branch_checked_out() -> Result<()> {
265 let (_, test_repo) =
266 prep_proposals_repo_and_repo_with_proposal_pulled_and_checkedout(1).await?;
267 assert_eq!(
268 get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_1)?,
269 test_repo.get_checked_out_branch_name()?,
270 );
271 Ok(())
272 }
273
274 #[tokio::test]
275 #[serial]
276 async fn proposal_branch_tip_is_most_recent_patch() -> Result<()> {
277 let (originating_repo, test_repo) =
278 prep_proposals_repo_and_repo_with_proposal_pulled_and_checkedout(1).await?;
279 assert_eq!(
280 originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?,
281 test_repo.get_tip_of_local_branch(&get_proposal_branch_name(
282 &test_repo,
283 FEATURE_BRANCH_NAME_1
284 )?)?,
285 );
286 Ok(())
287 }
288 }
289 mod when_third_proposal_selected {
290 use super::*;
291
292 mod cli_prompts {
293 use super::*;
294
295 #[tokio::test]
296 #[serial]
297 async fn prompts_to_choose_from_proposal_titles() -> Result<()> {
298 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
299 Relay::new(8051, None, None),
300 Relay::new(8052, None, None),
301 Relay::new(8053, None, None),
302 Relay::new(8055, None, None),
303 Relay::new(8056, None, None),
304 );
305
306 r51.events.push(generate_test_key_1_relay_list_event());
307 r51.events.push(generate_test_key_1_metadata_event("fred"));
308 r51.events.push(generate_repo_ref_event());
309
310 r55.events.push(generate_repo_ref_event());
311 r55.events.push(generate_test_key_1_metadata_event("fred"));
312 r55.events.push(generate_test_key_1_relay_list_event());
313
314 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
315 cli_tester_create_proposals()?;
316
317 let test_repo = GitTestRepo::default();
318 test_repo.populate()?;
319 let mut p = CliTester::new_from_dir(&test_repo.dir, ["-i", "list"]);
320
321 p.expect("Checking nostr relays...\r\n")?;
322 p.expect_eventually("\r\n")?; // some updates listed here
323 let mut c = p.expect_choice(
324 "all proposals",
325 vec![
326 format!("\"{PROPOSAL_TITLE_3}\""),
327 format!("\"{PROPOSAL_TITLE_2}\""),
328 format!("\"{PROPOSAL_TITLE_1}\""),
329 ],
330 )?;
331 c.succeeds_with(0, true, None)?;
332 let mut c = p.expect_choice("", vec![
333 format!(
334 "create and checkout proposal branch (2 ahead 0 behind 'main')"
335 ),
336 format!("apply to current branch with `git am`"),
337 format!("download to ./patches"),
338 format!("back"),
339 ])?;
340 c.succeeds_with(0, true, Some(0))?;
341 p.expect(format!(
342 "checked out proposal as 'pr/{FEATURE_BRANCH_NAME_3}(",
343 ))?;
344 p.expect_end_eventually_with(")' branch\r\n")?;
345
346 for p in [51, 52, 53, 55, 56] {
347 relay::shutdown_relay(8000 + p)?;
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 );
360 cli_tester_handle.join().unwrap()?;
361 println!("{:?}", r55.events);
362 Ok(())
363 }
364 }
365
366 #[tokio::test]
367 #[serial]
368 async fn proposal_branch_created_with_correct_name() -> Result<()> {
369 let (_, test_repo) =
370 prep_proposals_repo_and_repo_with_proposal_pulled_and_checkedout(3).await?;
371 assert_eq!(
372 vec![
373 "main",
374 &get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_3)?,
375 ],
376 test_repo.get_local_branch_names()?
377 );
378 Ok(())
379 }
380
381 #[tokio::test]
382 #[serial]
383 async fn proposal_branch_checked_out() -> Result<()> {
384 let (_, test_repo) =
385 prep_proposals_repo_and_repo_with_proposal_pulled_and_checkedout(3).await?;
386 assert_eq!(
387 get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_3)?,
388 test_repo.get_checked_out_branch_name()?,
389 );
390 Ok(())
391 }
392
393 #[tokio::test]
394 #[serial]
395 async fn proposal_branch_tip_is_most_recent_patch() -> Result<()> {
396 let (originating_repo, test_repo) =
397 prep_proposals_repo_and_repo_with_proposal_pulled_and_checkedout(3).await?;
398 assert_eq!(
399 originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_3)?,
400 test_repo.get_tip_of_local_branch(&get_proposal_branch_name(
401 &test_repo,
402 FEATURE_BRANCH_NAME_3
403 )?)?,
404 );
405 Ok(())
406 }
407 }
408 mod when_forth_proposal_has_no_cover_letter {
409 use super::*;
410
411 async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> {
412 // fallback (51,52) user write (53, 55) repo (55, 56)
413 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
414 Relay::new(8051, None, None),
415 Relay::new(8052, None, None),
416 Relay::new(8053, None, None),
417 Relay::new(8055, None, None),
418 Relay::new(8056, None, None),
419 );
420
421 r51.events.push(generate_test_key_1_relay_list_event());
422 r51.events.push(generate_test_key_1_metadata_event("fred"));
423 r51.events.push(generate_repo_ref_event());
424
425 r55.events.push(generate_repo_ref_event());
426 r55.events.push(generate_test_key_1_metadata_event("fred"));
427 r55.events.push(generate_test_key_1_relay_list_event());
428
429 let cli_tester_handle =
430 std::thread::spawn(move || -> Result<(GitTestRepo, GitTestRepo)> {
431 let originating_repo = cli_tester_create_proposals()?;
432 cli_tester_create_proposal(
433 &originating_repo,
434 FEATURE_BRANCH_NAME_4,
435 "d",
436 None,
437 None,
438 )?;
439 let test_repo = GitTestRepo::default();
440 test_repo.populate()?;
441 let mut p = CliTester::new_from_dir(&test_repo.dir, ["-i", "list"]);
442
443 p.expect("Checking nostr relays...\r\n")?;
444 p.expect_eventually("\r\n")?; // some updates listed here
445 let mut c = p.expect_choice(
446 "all proposals",
447 vec![
448 format!("add d3.md"), // commit msg title
449 format!("\"{PROPOSAL_TITLE_3}\""),
450 format!("\"{PROPOSAL_TITLE_2}\""),
451 format!("\"{PROPOSAL_TITLE_1}\""),
452 ],
453 )?;
454 c.succeeds_with(0, true, None)?;
455 let mut c = p.expect_choice("", vec![
456 format!(
457 "create and checkout proposal branch (2 ahead 0 behind 'main')"
458 ),
459 format!("apply to current branch with `git am`"),
460 format!("download to ./patches"),
461 format!("back"),
462 ])?;
463 c.succeeds_with(0, true, Some(0))?;
464 p.expect_end_eventually_and_print()?;
465
466 for p in [51, 52, 53, 55, 56] {
467 relay::shutdown_relay(8000 + p)?;
468 }
469 Ok((originating_repo, test_repo))
470 });
471
472 // launch relay
473 let _ = join!(
474 r51.listen_until_close(),
475 r52.listen_until_close(),
476 r53.listen_until_close(),
477 r55.listen_until_close(),
478 r56.listen_until_close(),
479 );
480 let res = cli_tester_handle.join().unwrap()?;
481
482 Ok(res)
483 }
484
485 mod cli_prompts {
486 use super::*;
487
488 #[tokio::test]
489 #[serial]
490 async fn prompts_to_choose_from_proposal_titles() -> Result<()> {
491 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
492 Relay::new(8051, None, None),
493 Relay::new(8052, None, None),
494 Relay::new(8053, None, None),
495 Relay::new(8055, None, None),
496 Relay::new(8056, None, None),
497 );
498
499 r51.events.push(generate_test_key_1_relay_list_event());
500 r51.events.push(generate_test_key_1_metadata_event("fred"));
501 r51.events.push(generate_repo_ref_event());
502
503 r55.events.push(generate_repo_ref_event());
504 r55.events.push(generate_test_key_1_metadata_event("fred"));
505 r55.events.push(generate_test_key_1_relay_list_event());
506
507 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
508 let originating_repo = cli_tester_create_proposals()?;
509 std::thread::sleep(std::time::Duration::from_millis(1000));
510 cli_tester_create_proposal(
511 &originating_repo,
512 FEATURE_BRANCH_NAME_4,
513 "d",
514 None,
515 None,
516 )?;
517 let test_repo = GitTestRepo::default();
518 test_repo.populate()?;
519 let mut p = CliTester::new_from_dir(&test_repo.dir, ["-i", "list"]);
520
521 p.expect("Checking nostr relays...\r\n")?;
522 p.expect_eventually("\r\n")?; // some updates listed here
523 let mut c = p.expect_choice(
524 "all proposals",
525 vec![
526 format!("add d3.md"), // commit msg title
527 format!("\"{PROPOSAL_TITLE_3}\""),
528 format!("\"{PROPOSAL_TITLE_2}\""),
529 format!("\"{PROPOSAL_TITLE_1}\""),
530 ],
531 )?;
532 c.succeeds_with(0, true, None)?;
533 let mut c = p.expect_choice("", vec![
534 format!(
535 "create and checkout proposal branch (2 ahead 0 behind 'main')"
536 ),
537 format!("apply to current branch with `git am`"),
538 format!("download to ./patches"),
539 format!("back"),
540 ])?;
541 c.succeeds_with(0, true, Some(0))?;
542 p.expect(format!(
543 "checked out proposal as 'pr/{FEATURE_BRANCH_NAME_4}(",
544 ))?;
545 p.expect_end_eventually_with(")' branch\r\n")?;
546
547 for p in [51, 52, 53, 55, 56] {
548 relay::shutdown_relay(8000 + p)?;
549 }
550 Ok(())
551 });
552
553 // launch relay
554 let _ = join!(
555 r51.listen_until_close(),
556 r52.listen_until_close(),
557 r53.listen_until_close(),
558 r55.listen_until_close(),
559 r56.listen_until_close(),
560 );
561 cli_tester_handle.join().unwrap()?;
562 println!("{:?}", r55.events);
563 Ok(())
564 }
565 }
566
567 #[tokio::test]
568 #[serial]
569 async fn proposal_branch_created_with_correct_name() -> Result<()> {
570 let (_, test_repo) = prep_and_run().await?;
571 assert_eq!(
572 vec![
573 "main",
574 &get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_4)?,
575 ],
576 test_repo.get_local_branch_names()?
577 );
578 Ok(())
579 }
580
581 #[tokio::test]
582 #[serial]
583 async fn proposal_branch_checked_out() -> Result<()> {
584 let (_, test_repo) = prep_and_run().await?;
585 assert_eq!(
586 get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_4)?,
587 test_repo.get_checked_out_branch_name()?,
588 );
589 Ok(())
590 }
591
592 #[tokio::test]
593 #[serial]
594 async fn proposal_branch_tip_is_most_recent_patch() -> Result<()> {
595 let (originating_repo, test_repo) = prep_and_run().await?;
596 assert_eq!(
597 originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_4)?,
598 test_repo.get_tip_of_local_branch(&get_proposal_branch_name(
599 &test_repo,
600 FEATURE_BRANCH_NAME_4
601 )?)?,
602 );
603 Ok(())
604 }
605 }
606 }
607 }
608
609 mod when_proposal_branch_exists {
610 use super::*;
611
612 mod when_main_is_checked_out {
613 use super::*;
614
615 mod when_branch_is_up_to_date {
616 use super::*;
617 async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> {
618 // fallback (51,52) user write (53, 55) repo (55, 56)
619 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
620 Relay::new(8051, None, None),
621 Relay::new(8052, None, None),
622 Relay::new(8053, None, None),
623 Relay::new(8055, None, None),
624 Relay::new(8056, None, None),
625 );
626
627 r51.events.push(generate_test_key_1_relay_list_event());
628 r51.events.push(generate_test_key_1_metadata_event("fred"));
629 r51.events.push(generate_repo_ref_event());
630
631 r55.events.push(generate_repo_ref_event());
632 r55.events.push(generate_test_key_1_metadata_event("fred"));
633 r55.events.push(generate_test_key_1_relay_list_event());
634
635 let cli_tester_handle =
636 std::thread::spawn(move || -> Result<(GitTestRepo, GitTestRepo)> {
637 let originating_repo = cli_tester_create_proposals()?;
638
639 let test_repo = GitTestRepo::default();
640 test_repo.populate()?;
641 // create proposal branch
642 let mut p = CliTester::new_from_dir(&test_repo.dir, ["-i", "list"]);
643 p.expect("Checking nostr relays...\r\n")?;
644 p.expect_eventually("\r\n")?; // some updates listed here
645 let mut c = p.expect_choice(
646 "all proposals",
647 vec![
648 format!("\"{PROPOSAL_TITLE_3}\""),
649 format!("\"{PROPOSAL_TITLE_2}\""),
650 format!("\"{PROPOSAL_TITLE_1}\""),
651 ],
652 )?;
653 c.succeeds_with(2, true, None)?;
654 let mut c = p.expect_choice("", vec![
655 format!(
656 "create and checkout proposal branch (2 ahead 0 behind 'main')"
657 ),
658 format!("apply to current branch with `git am`"),
659 format!("download to ./patches"),
660 format!("back"),
661 ])?;
662 c.succeeds_with(0, true, Some(0))?;
663 p.expect_end_eventually()?;
664
665 test_repo.checkout("main")?;
666 // run test
667 p = CliTester::new_from_dir(&test_repo.dir, ["-i", "list"]);
668 p.expect("Checking nostr relays...\r\n")?;
669 p.expect_eventually("\r\n")?; // some updates listed here
670 let mut c = p.expect_choice(
671 "all proposals",
672 vec![
673 format!("\"{PROPOSAL_TITLE_3}\""),
674 format!("\"{PROPOSAL_TITLE_2}\""),
675 format!("\"{PROPOSAL_TITLE_1}\""),
676 ],
677 )?;
678 c.succeeds_with(2, true, None)?;
679 let mut c = p.expect_choice(
680 "",
681 vec![
682 format!("checkout proposal branch (2 ahead 0 behind 'main')"),
683 format!("apply to current branch with `git am`"),
684 format!("download to ./patches"),
685 format!("back"),
686 ],
687 )?;
688 c.succeeds_with(0, true, Some(0))?;
689 p.expect_end_eventually_and_print()?;
690
691 for p in [51, 52, 53, 55, 56] {
692 relay::shutdown_relay(8000 + p)?;
693 }
694 Ok((originating_repo, test_repo))
695 });
696
697 // launch relay
698 let _ = join!(
699 r51.listen_until_close(),
700 r52.listen_until_close(),
701 r53.listen_until_close(),
702 r55.listen_until_close(),
703 r56.listen_until_close(),
704 );
705 let res = cli_tester_handle.join().unwrap()?;
706
707 Ok(res)
708 }
709
710 mod cli_prompts {
711 use super::*;
712
713 #[tokio::test]
714 #[serial]
715 async fn prompts_to_choose_from_proposal_titles() -> Result<()> {
716 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
717 Relay::new(8051, None, None),
718 Relay::new(8052, None, None),
719 Relay::new(8053, None, None),
720 Relay::new(8055, None, None),
721 Relay::new(8056, None, None),
722 );
723
724 r51.events.push(generate_test_key_1_relay_list_event());
725 r51.events.push(generate_test_key_1_metadata_event("fred"));
726 r51.events.push(generate_repo_ref_event());
727
728 r55.events.push(generate_repo_ref_event());
729 r55.events.push(generate_test_key_1_metadata_event("fred"));
730 r55.events.push(generate_test_key_1_relay_list_event());
731
732 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
733 cli_tester_create_proposals()?;
734
735 let test_repo = GitTestRepo::default();
736 test_repo.populate()?;
737 // create proposal branch
738 let mut p = CliTester::new_from_dir(&test_repo.dir, ["-i", "list"]);
739 p.expect("Checking nostr relays...\r\n")?;
740 p.expect_eventually("\r\n")?; // some updates listed here
741 let mut c = p.expect_choice(
742 "all proposals",
743 vec![
744 format!("\"{PROPOSAL_TITLE_3}\""),
745 format!("\"{PROPOSAL_TITLE_2}\""),
746 format!("\"{PROPOSAL_TITLE_1}\""),
747 ],
748 )?;
749 c.succeeds_with(2, true, None)?;
750 let mut c = p.expect_choice("", vec![
751 format!(
752 "create and checkout proposal branch (2 ahead 0 behind 'main')"
753 ),
754 format!("apply to current branch with `git am`"),
755 format!("download to ./patches"),
756 format!("back"),
757 ])?;
758 c.succeeds_with(0, true, Some(0))?;
759 p.expect_end_eventually()?;
760
761 test_repo.checkout("main")?;
762 // run test
763 p = CliTester::new_from_dir(&test_repo.dir, ["-i", "list"]);
764 p.expect("Checking nostr relays...\r\n")?;
765 p.expect_eventually("\r\n")?; // some updates listed here
766 let mut c = p.expect_choice(
767 "all proposals",
768 vec![
769 format!("\"{PROPOSAL_TITLE_3}\""),
770 format!("\"{PROPOSAL_TITLE_2}\""),
771 format!("\"{PROPOSAL_TITLE_1}\""),
772 ],
773 )?;
774 c.succeeds_with(2, true, None)?;
775 let mut c = p.expect_choice(
776 "",
777 vec![
778 format!("checkout proposal branch (2 ahead 0 behind 'main')"),
779 format!("apply to current branch with `git am`"),
780 format!("download to ./patches"),
781 format!("back"),
782 ],
783 )?;
784 c.succeeds_with(0, true, Some(0))?;
785 p.expect(format!(
786 "checked out proposal as 'pr/{FEATURE_BRANCH_NAME_1}(",
787 ))?;
788 p.expect_end_eventually_with(")' branch\r\n")?;
789
790 for p in [51, 52, 53, 55, 56] {
791 relay::shutdown_relay(8000 + p)?;
792 }
793 Ok(())
794 });
795
796 // launch relay
797 let _ = join!(
798 r51.listen_until_close(),
799 r52.listen_until_close(),
800 r53.listen_until_close(),
801 r55.listen_until_close(),
802 r56.listen_until_close(),
803 );
804 cli_tester_handle.join().unwrap()?;
805 println!("{:?}", r55.events);
806 Ok(())
807 }
808 }
809
810 #[tokio::test]
811 #[serial]
812 async fn proposal_branch_checked_out() -> Result<()> {
813 let (_, test_repo) = prep_and_run().await?;
814 assert_eq!(
815 get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_1)?,
816 test_repo.get_checked_out_branch_name()?,
817 );
818 Ok(())
819 }
820 }
821
822 mod when_branch_is_behind {
823 use super::*;
824
825 async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> {
826 // fallback (51,52) user write (53, 55) repo (55, 56)
827 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
828 Relay::new(8051, None, None),
829 Relay::new(8052, None, None),
830 Relay::new(8053, None, None),
831 Relay::new(8055, None, None),
832 Relay::new(8056, None, None),
833 );
834
835 r51.events.push(generate_test_key_1_relay_list_event());
836 r51.events.push(generate_test_key_1_metadata_event("fred"));
837 r51.events.push(generate_repo_ref_event());
838
839 r55.events.push(generate_repo_ref_event());
840 r55.events.push(generate_test_key_1_metadata_event("fred"));
841 r55.events.push(generate_test_key_1_relay_list_event());
842
843 let cli_tester_handle = std::thread::spawn(
844 move || -> Result<(GitTestRepo, GitTestRepo)> {
845 let (originating_repo, test_repo) =
846 create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?;
847
848 remove_latest_commit_so_proposal_branch_is_behind_and_checkout_main(
849 &test_repo,
850 )?;
851
852 // run test
853 let mut p = CliTester::new_from_dir(&test_repo.dir, ["-i", "list"]);
854 p.expect("Checking nostr relays...\r\n")?;
855 p.expect_eventually("\r\n")?; // some updates listed here
856 let mut c = p.expect_choice(
857 "all proposals",
858 vec![
859 format!("\"{PROPOSAL_TITLE_3}\""),
860 format!("\"{PROPOSAL_TITLE_2}\""),
861 format!("\"{PROPOSAL_TITLE_1}\""),
862 ],
863 )?;
864 c.succeeds_with(2, true, None)?;
865 let mut c = p.expect_choice(
866 "",
867 vec![
868 format!("checkout proposal branch and apply 1 appendments"),
869 format!("apply to current branch with `git am`"),
870 format!("download to ./patches"),
871 format!("back"),
872 ],
873 )?;
874 c.succeeds_with(0, true, Some(0))?;
875 p.expect("checked out proposal branch and applied 1 appendments (2 ahead 0 behind 'main')\r\n")?;
876 p.expect_end()?;
877
878 for p in [51, 52, 53, 55, 56] {
879 relay::shutdown_relay(8000 + p)?;
880 }
881 Ok((originating_repo, test_repo))
882 },
883 );
884
885 // launch relay
886 let _ = join!(
887 r51.listen_until_close(),
888 r52.listen_until_close(),
889 r53.listen_until_close(),
890 r55.listen_until_close(),
891 r56.listen_until_close(),
892 );
893 let res = cli_tester_handle.join().unwrap()?;
894
895 Ok(res)
896 }
897
898 mod cli_prompts {
899 use super::*;
900
901 #[tokio::test]
902 #[serial]
903 async fn prompts_to_choose_from_proposal_titles() -> Result<()> {
904 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
905 Relay::new(8051, None, None),
906 Relay::new(8052, None, None),
907 Relay::new(8053, None, None),
908 Relay::new(8055, None, None),
909 Relay::new(8056, None, None),
910 );
911
912 r51.events.push(generate_test_key_1_relay_list_event());
913 r51.events.push(generate_test_key_1_metadata_event("fred"));
914 r51.events.push(generate_repo_ref_event());
915
916 r55.events.push(generate_repo_ref_event());
917 r55.events.push(generate_test_key_1_metadata_event("fred"));
918 r55.events.push(generate_test_key_1_relay_list_event());
919
920 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
921 let (_, test_repo) =
922 create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?;
923
924 remove_latest_commit_so_proposal_branch_is_behind_and_checkout_main(
925 &test_repo,
926 )?;
927
928 // run test
929 let mut p = CliTester::new_from_dir(&test_repo.dir, ["-i", "list"]);
930 p.expect("Checking nostr relays...\r\n")?;
931 p.expect_eventually("\r\n")?; // some updates listed here
932 let mut c = p.expect_choice(
933 "all proposals",
934 vec![
935 format!("\"{PROPOSAL_TITLE_3}\""),
936 format!("\"{PROPOSAL_TITLE_2}\""),
937 format!("\"{PROPOSAL_TITLE_1}\""),
938 ],
939 )?;
940 c.succeeds_with(2, true, None)?;
941 let mut c = p.expect_choice(
942 "",
943 vec![
944 format!("checkout proposal branch and apply 1 appendments"),
945 format!("apply to current branch with `git am`"),
946 format!("download to ./patches"),
947 format!("back"),
948 ],
949 )?;
950 c.succeeds_with(0, true, Some(0))?;
951 p.expect("checked out proposal branch and applied 1 appendments (2 ahead 0 behind 'main')\r\n")?;
952 p.expect_end()?;
953
954 for p in [51, 52, 53, 55, 56] {
955 relay::shutdown_relay(8000 + p)?;
956 }
957 Ok(())
958 });
959
960 // launch relay
961 let _ = join!(
962 r51.listen_until_close(),
963 r52.listen_until_close(),
964 r53.listen_until_close(),
965 r55.listen_until_close(),
966 r56.listen_until_close(),
967 );
968 cli_tester_handle.join().unwrap()?;
969 println!("{:?}", r55.events);
970 Ok(())
971 }
972 }
973
974 #[tokio::test]
975 #[serial]
976 async fn proposal_branch_checked_out() -> Result<()> {
977 let (_, test_repo) = prep_and_run().await?;
978 assert_eq!(
979 get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_1)?,
980 test_repo.get_checked_out_branch_name()?,
981 );
982 Ok(())
983 }
984
985 #[tokio::test]
986 #[serial]
987 async fn proposal_branch_tip_is_most_recent_patch() -> Result<()> {
988 let (originating_repo, test_repo) = prep_and_run().await?;
989 assert_eq!(
990 originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?,
991 test_repo.get_tip_of_local_branch(&get_proposal_branch_name(
992 &test_repo,
993 FEATURE_BRANCH_NAME_1
994 )?)?,
995 );
996 Ok(())
997 }
998 }
999
1000 mod when_latest_proposal_amended_locally {
1001 // other rebase scenarios should work if this test passes
1002 use super::*;
1003 async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> {
1004 // fallback (51,52) user write (53, 55) repo (55, 56)
1005 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
1006 Relay::new(8051, None, None),
1007 Relay::new(8052, None, None),
1008 Relay::new(8053, None, None),
1009 Relay::new(8055, None, None),
1010 Relay::new(8056, None, None),
1011 );
1012
1013 r51.events.push(generate_test_key_1_relay_list_event());
1014 r51.events.push(generate_test_key_1_metadata_event("fred"));
1015 r51.events.push(generate_repo_ref_event());
1016
1017 r55.events.push(generate_repo_ref_event());
1018 r55.events.push(generate_test_key_1_metadata_event("fred"));
1019 r55.events.push(generate_test_key_1_relay_list_event());
1020
1021 let cli_tester_handle =
1022 std::thread::spawn(move || -> Result<(GitTestRepo, GitTestRepo)> {
1023 let (originating_repo, test_repo) =
1024 create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?;
1025
1026 let branch_name = test_repo.get_checked_out_branch_name()?;
1027
1028 remove_latest_commit_so_proposal_branch_is_behind_and_checkout_main(
1029 &test_repo,
1030 )?;
1031
1032 // add another commit (so we have an ammened local branch)
1033 test_repo.checkout(&branch_name)?;
1034 std::fs::write(
1035 test_repo.dir.join("ammended-commit.md"),
1036 "some content",
1037 )?;
1038 test_repo.stage_and_commit("add ammended-commit.md")?;
1039 test_repo.checkout("main")?;
1040
1041 // run test
1042 let mut p = CliTester::new_from_dir(&test_repo.dir, ["-i", "list"]);
1043 p.expect("Checking nostr relays...\r\n")?;
1044 p.expect_eventually("\r\n")?; // some updates listed here
1045 let mut c = p.expect_choice(
1046 "all proposals",
1047 vec![
1048 format!("\"{PROPOSAL_TITLE_3}\""),
1049 format!("\"{PROPOSAL_TITLE_2}\""),
1050 format!("\"{PROPOSAL_TITLE_1}\""),
1051 ],
1052 )?;
1053 c.succeeds_with(2, true, None)?;
1054 p.expect_eventually("--force`\r\n")?;
1055
1056 let mut c = p.expect_choice(
1057 "",
1058 vec![
1059 format!("checkout local branch with unpublished changes"),
1060 format!(
1061 "discard unpublished changes and checkout new revision"
1062 ),
1063 format!("apply to current branch with `git am`"),
1064 format!("download to ./patches"),
1065 "back".to_string(),
1066 ],
1067 )?;
1068 c.succeeds_with(1, true, Some(0))?;
1069
1070 p.expect_end_eventually_and_print()?;
1071
1072 for p in [51, 52, 53, 55, 56] {
1073 relay::shutdown_relay(8000 + p)?;
1074 }
1075 Ok((originating_repo, test_repo))
1076 });
1077 // launch relay
1078 let _ = join!(
1079 r51.listen_until_close(),
1080 r52.listen_until_close(),
1081 r53.listen_until_close(),
1082 r55.listen_until_close(),
1083 r56.listen_until_close(),
1084 );
1085 let res = cli_tester_handle.join().unwrap()?;
1086
1087 Ok(res)
1088 }
1089
1090 mod cli_prompts {
1091 use super::*;
1092
1093 #[tokio::test]
1094 #[serial]
1095 async fn out_reflects_second_choice_discarding_old_and_applying_new()
1096 -> Result<()> {
1097 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
1098 Relay::new(8051, None, None),
1099 Relay::new(8052, None, None),
1100 Relay::new(8053, None, None),
1101 Relay::new(8055, None, None),
1102 Relay::new(8056, None, None),
1103 );
1104
1105 r51.events.push(generate_test_key_1_relay_list_event());
1106 r51.events.push(generate_test_key_1_metadata_event("fred"));
1107 r51.events.push(generate_repo_ref_event());
1108
1109 r55.events.push(generate_repo_ref_event());
1110 r55.events.push(generate_test_key_1_metadata_event("fred"));
1111 r55.events.push(generate_test_key_1_relay_list_event());
1112
1113 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
1114 let (_, test_repo) =
1115 create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?;
1116
1117 amend_last_commit(&test_repo, "add ammended-commit.md")?;
1118 test_repo.checkout("main")?;
1119
1120 // run test
1121 let mut p = CliTester::new_from_dir(&test_repo.dir, ["-i", "list"]);
1122 p.expect("Checking nostr relays...\r\n")?;
1123 p.expect_eventually("\r\n")?; // some updates listed here
1124 let mut c = p.expect_choice(
1125 "all proposals",
1126 vec![
1127 format!("\"{PROPOSAL_TITLE_3}\""),
1128 format!("\"{PROPOSAL_TITLE_2}\""),
1129 format!("\"{PROPOSAL_TITLE_1}\""),
1130 ],
1131 )?;
1132 c.succeeds_with(2, true, None)?;
1133 p.expect("you have an amended/rebase version the proposal that is unpublished\r\n")?;
1134 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")?;
1135 p.expect("to view the latest proposal but retain your changes:\r\n")?;
1136 p.expect(" 1) create a new branch off the tip commit of this one to store your changes\r\n")?;
1137 p.expect(" 2) run `ngit list` and checkout the latest published version of this proposal\r\n")?;
1138 p.expect("if you are confident in your changes consider running `ngit push --force`\r\n")?;
1139
1140 let mut c = p.expect_choice(
1141 "",
1142 vec![
1143 format!("checkout local branch with unpublished changes"),
1144 format!(
1145 "discard unpublished changes and checkout new revision"
1146 ),
1147 format!("apply to current branch with `git am`"),
1148 format!("download to ./patches"),
1149 "back".to_string(),
1150 ],
1151 )?;
1152 c.succeeds_with(1, true, Some(1))?;
1153 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")?;
1154
1155 for p in [51, 52, 53, 55, 56] {
1156 relay::shutdown_relay(8000 + p)?;
1157 }
1158 Ok(())
1159 });
1160
1161 // launch relay
1162 let _ = join!(
1163 r51.listen_until_close(),
1164 r52.listen_until_close(),
1165 r53.listen_until_close(),
1166 r55.listen_until_close(),
1167 r56.listen_until_close(),
1168 );
1169 cli_tester_handle.join().unwrap()?;
1170 println!("{:?}", r55.events);
1171 Ok(())
1172 }
1173 }
1174
1175 #[tokio::test]
1176 #[serial]
1177 async fn second_choice_discarded_unpublished_commits_and_checked_out_latest_revision()
1178 -> Result<()> {
1179 let (originating_repo, test_repo) = prep_and_run().await?;
1180 println!("test_dir: {:?}", test_repo.dir);
1181 assert_eq!(
1182 test_repo.get_tip_of_local_branch(&get_proposal_branch_name(
1183 &test_repo,
1184 FEATURE_BRANCH_NAME_1
1185 )?)?,
1186 originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?,
1187 );
1188 Ok(())
1189 }
1190 }
1191
1192 mod when_local_commits_on_uptodate_proposal {
1193 use super::*;
1194 async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> {
1195 // fallback (51,52) user write (53, 55) repo (55, 56)
1196 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
1197 Relay::new(8051, None, None),
1198 Relay::new(8052, None, None),
1199 Relay::new(8053, None, None),
1200 Relay::new(8055, None, None),
1201 Relay::new(8056, None, None),
1202 );
1203
1204 r51.events.push(generate_test_key_1_relay_list_event());
1205 r51.events.push(generate_test_key_1_metadata_event("fred"));
1206 r51.events.push(generate_repo_ref_event());
1207
1208 r55.events.push(generate_repo_ref_event());
1209 r55.events.push(generate_test_key_1_metadata_event("fred"));
1210 r55.events.push(generate_test_key_1_relay_list_event());
1211
1212 let cli_tester_handle = std::thread::spawn(
1213 move || -> Result<(GitTestRepo, GitTestRepo)> {
1214 let (originating_repo, test_repo) =
1215 create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?;
1216
1217 // add another commit (so we have a local branch 1 ahead)
1218 std::fs::write(
1219 test_repo.dir.join("ammended-commit.md"),
1220 "some content",
1221 )?;
1222 test_repo.stage_and_commit("add ammended-commit.md")?;
1223 test_repo.checkout("main")?;
1224
1225 // run test
1226 let mut p = CliTester::new_from_dir(&test_repo.dir, ["-i", "list"]);
1227 p.expect("Checking nostr relays...\r\n")?;
1228 p.expect_eventually("\r\n")?; // some updates listed here
1229 let mut c = p.expect_choice(
1230 "all proposals",
1231 vec![
1232 format!("\"{PROPOSAL_TITLE_3}\""),
1233 format!("\"{PROPOSAL_TITLE_2}\""),
1234 format!("\"{PROPOSAL_TITLE_1}\""),
1235 ],
1236 )?;
1237 c.succeeds_with(2, true, None)?;
1238 p.expect(
1239 "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",
1240 )?;
1241
1242 let mut c = p.expect_choice(
1243 "",
1244 vec![
1245 format!("checkout proposal branch with 1 unpublished commits"),
1246 format!("back"),
1247 ],
1248 )?;
1249 c.succeeds_with(0, true, Some(0))?;
1250 p.expect("checked out proposal branch with 1 unpublished commits (3 ahead 0 behind 'main')\r\n")?;
1251 p.expect_end()?;
1252
1253 for p in [51, 52, 53, 55, 56] {
1254 relay::shutdown_relay(8000 + p)?;
1255 }
1256 Ok((originating_repo, test_repo))
1257 },
1258 );
1259
1260 // launch relay
1261 let _ = join!(
1262 r51.listen_until_close(),
1263 r52.listen_until_close(),
1264 r53.listen_until_close(),
1265 r55.listen_until_close(),
1266 r56.listen_until_close(),
1267 );
1268 let res = cli_tester_handle.join().unwrap()?;
1269
1270 Ok(res)
1271 }
1272
1273 mod cli_prompts {
1274 use super::*;
1275
1276 #[tokio::test]
1277 #[serial]
1278 async fn prompts_to_choose_from_proposal_titles() -> Result<()> {
1279 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
1280 Relay::new(8051, None, None),
1281 Relay::new(8052, None, None),
1282 Relay::new(8053, None, None),
1283 Relay::new(8055, None, None),
1284 Relay::new(8056, None, None),
1285 );
1286
1287 r51.events.push(generate_test_key_1_relay_list_event());
1288 r51.events.push(generate_test_key_1_metadata_event("fred"));
1289 r51.events.push(generate_repo_ref_event());
1290
1291 r55.events.push(generate_repo_ref_event());
1292 r55.events.push(generate_test_key_1_metadata_event("fred"));
1293 r55.events.push(generate_test_key_1_relay_list_event());
1294
1295 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
1296 let (_, test_repo) =
1297 create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?;
1298
1299 // add another commit (so we have a local branch 1 ahead)
1300 std::fs::write(
1301 test_repo.dir.join("ammended-commit.md"),
1302 "some content",
1303 )?;
1304 test_repo.stage_and_commit("add ammended-commit.md")?;
1305 test_repo.checkout("main")?;
1306
1307 // run test
1308 let mut p = CliTester::new_from_dir(&test_repo.dir, ["-i", "list"]);
1309 p.expect("Checking nostr relays...\r\n")?;
1310 p.expect_eventually("\r\n")?; // some updates listed here
1311 let mut c = p.expect_choice(
1312 "all proposals",
1313 vec![
1314 format!("\"{PROPOSAL_TITLE_3}\""),
1315 format!("\"{PROPOSAL_TITLE_2}\""),
1316 format!("\"{PROPOSAL_TITLE_1}\""),
1317 ],
1318 )?;
1319 c.succeeds_with(2, true, None)?;
1320 p.expect(
1321 "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",
1322 )?;
1323
1324 let mut c = p.expect_choice(
1325 "",
1326 vec![
1327 format!("checkout proposal branch with 1 unpublished commits"),
1328 format!("back"),
1329 ],
1330 )?;
1331 c.succeeds_with(0, true, Some(0))?;
1332 p.expect("checked out proposal branch with 1 unpublished commits (3 ahead 0 behind 'main')\r\n")?;
1333 p.expect_end()?;
1334
1335 for p in [51, 52, 53, 55, 56] {
1336 relay::shutdown_relay(8000 + p)?;
1337 }
1338 Ok(())
1339 });
1340
1341 // launch relay
1342 let _ = join!(
1343 r51.listen_until_close(),
1344 r52.listen_until_close(),
1345 r53.listen_until_close(),
1346 r55.listen_until_close(),
1347 r56.listen_until_close(),
1348 );
1349 cli_tester_handle.join().unwrap()?;
1350 println!("{:?}", r55.events);
1351 Ok(())
1352 }
1353 }
1354
1355 #[tokio::test]
1356 #[serial]
1357 async fn proposal_branch_checked_out() -> Result<()> {
1358 let (_, test_repo) = prep_and_run().await?;
1359 assert_eq!(
1360 get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_1)?,
1361 test_repo.get_checked_out_branch_name()?,
1362 );
1363 Ok(())
1364 }
1365
1366 #[tokio::test]
1367 #[serial]
1368 async fn didnt_overwrite_local_appendments() -> Result<()> {
1369 let (originating_repo, test_repo) = prep_and_run().await?;
1370 assert_ne!(
1371 test_repo.get_tip_of_local_branch(&get_proposal_branch_name(
1372 &test_repo,
1373 FEATURE_BRANCH_NAME_1
1374 )?)?,
1375 originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?,
1376 );
1377 Ok(())
1378 }
1379 }
1380
1381 mod when_latest_revision_rebases_branch {
1382
1383 use tokio::task::JoinHandle;
1384
1385 use super::*;
1386
1387 async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> {
1388 // fallback (51,52) user write (53, 55) repo (55, 56)
1389 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
1390 Relay::new(8051, None, None),
1391 Relay::new(8052, None, None),
1392 Relay::new(8053, None, None),
1393 Relay::new(8055, None, None),
1394 Relay::new(8056, None, None),
1395 );
1396
1397 r51.events.push(generate_test_key_1_relay_list_event());
1398 r51.events.push(generate_test_key_1_metadata_event("fred"));
1399 r51.events.push(generate_repo_ref_event());
1400
1401 r55.events.push(generate_repo_ref_event());
1402 r55.events.push(generate_test_key_1_metadata_event("fred"));
1403 r55.events.push(generate_test_key_1_relay_list_event());
1404
1405 let cli_tester_handle: JoinHandle<Result<(GitTestRepo, GitTestRepo)>> =
1406 tokio::task::spawn_blocking(move || {
1407 let (originating_repo, test_repo) = create_proposals_with_first_rebased_and_repo_with_latest_main_and_unrebased_proposal()?;
1408 test_repo.checkout("main")?;
1409
1410 let mut p = CliTester::new_from_dir(&test_repo.dir, ["-i", "list"]);
1411 p.expect("Checking nostr relays...\r\n")?;
1412 p.expect_eventually("\r\n")?; // some updates listed here
1413 let mut c = p.expect_choice(
1414 "all proposals",
1415 vec![
1416 format!("\"{PROPOSAL_TITLE_3}\""),
1417 format!("\"{PROPOSAL_TITLE_2}\""),
1418 format!("\"{PROPOSAL_TITLE_1}\""),
1419 ],
1420 )?;
1421 c.succeeds_with(2, true, None)?;
1422 p.expect("updated proposal available (2 ahead 0 behind 'main'). existing version is 2 ahead 1 behind 'main'\r\n")?;
1423 let mut c = p.expect_choice(
1424 "",
1425 vec![
1426 format!("checkout and overwrite existing proposal branch"),
1427 format!("checkout existing outdated proposal branch"),
1428 format!("apply to current branch with `git am`"),
1429 format!("download to ./patches"),
1430 format!("back"),
1431 ],
1432 )?;
1433 c.succeeds_with(0, true, Some(0))?;
1434 p.expect("checked out new version of proposal (2 ahead 0 behind 'main'), replacing old version (2 ahead 1 behind 'main')\r\n")?;
1435 p.expect_end()?;
1436
1437 for p in [51, 52, 53, 55, 56] {
1438 relay::shutdown_relay(8000 + p)?;
1439 }
1440 Ok((originating_repo, test_repo))
1441 });
1442
1443 // launch relay
1444 let _ = join!(
1445 r51.listen_until_close(),
1446 r52.listen_until_close(),
1447 r53.listen_until_close(),
1448 r55.listen_until_close(),
1449 r56.listen_until_close(),
1450 );
1451 let res = cli_tester_handle.await??;
1452
1453 Ok(res)
1454 }
1455
1456 mod cli_prompts {
1457 use super::*;
1458
1459 #[tokio::test]
1460 #[serial]
1461 async fn prompts_to_choose_from_proposal_titles() -> Result<()> {
1462 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
1463 Relay::new(8051, None, None),
1464 Relay::new(8052, None, None),
1465 Relay::new(8053, None, None),
1466 Relay::new(8055, None, None),
1467 Relay::new(8056, None, None),
1468 );
1469
1470 r51.events.push(generate_test_key_1_relay_list_event());
1471 r51.events.push(generate_test_key_1_metadata_event("fred"));
1472 r51.events.push(generate_repo_ref_event());
1473
1474 r55.events.push(generate_repo_ref_event());
1475 r55.events.push(generate_test_key_1_metadata_event("fred"));
1476 r55.events.push(generate_test_key_1_relay_list_event());
1477
1478 let cli_tester_handle: JoinHandle<Result<()>> = tokio::task::spawn_blocking(
1479 move || {
1480 let (_, test_repo) = create_proposals_with_first_rebased_and_repo_with_latest_main_and_unrebased_proposal()?;
1481 test_repo.checkout("main")?;
1482
1483 let mut p = CliTester::new_from_dir(&test_repo.dir, ["-i", "list"]);
1484 p.expect("Checking nostr relays...\r\n")?;
1485 p.expect_eventually("\r\n")?; // some updates listed here
1486 let mut c = p.expect_choice(
1487 "all proposals",
1488 vec![
1489 format!("\"{PROPOSAL_TITLE_3}\""),
1490 format!("\"{PROPOSAL_TITLE_2}\""),
1491 format!("\"{PROPOSAL_TITLE_1}\""),
1492 ],
1493 )?;
1494 c.succeeds_with(2, true, None)?;
1495 p.expect("updated proposal available (2 ahead 0 behind 'main'). existing version is 2 ahead 1 behind 'main'\r\n")?;
1496 let mut c = p.expect_choice(
1497 "",
1498 vec![
1499 format!("checkout and overwrite existing proposal branch"),
1500 format!("checkout existing outdated proposal branch"),
1501 format!("apply to current branch with `git am`"),
1502 format!("download to ./patches"),
1503 format!("back"),
1504 ],
1505 )?;
1506 c.succeeds_with(0, true, Some(0))?;
1507 p.expect("checked out new version of proposal (2 ahead 0 behind 'main'), replacing old version (2 ahead 1 behind 'main')\r\n")?;
1508 p.expect_end()?;
1509
1510 for p in [51, 52, 53, 55, 56] {
1511 relay::shutdown_relay(8000 + p)?;
1512 }
1513 Ok(())
1514 },
1515 );
1516
1517 // launch relay
1518 let _ = join!(
1519 r51.listen_until_close(),
1520 r52.listen_until_close(),
1521 r53.listen_until_close(),
1522 r55.listen_until_close(),
1523 r56.listen_until_close(),
1524 );
1525 cli_tester_handle.await??;
1526 println!("{:?}", r55.events);
1527 Ok(())
1528 }
1529 }
1530
1531 #[tokio::test]
1532 #[serial]
1533 async fn proposal_branch_checked_out() -> Result<()> {
1534 let (_, test_repo) = prep_and_run().await?;
1535 assert_eq!(
1536 get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_1)?,
1537 test_repo.get_checked_out_branch_name()?,
1538 );
1539 Ok(())
1540 }
1541
1542 #[tokio::test]
1543 #[serial]
1544 async fn proposal_branch_tip_is_most_recent_proposal_revision_tip() -> Result<()> {
1545 let (originating_repo, test_repo) = prep_and_run().await?;
1546 assert_eq!(
1547 originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?,
1548 test_repo.get_tip_of_local_branch(&get_proposal_branch_name(
1549 &test_repo,
1550 FEATURE_BRANCH_NAME_1
1551 )?)?,
1552 );
1553 Ok(())
1554 }
1555 }
1556 }
1557 }
1558}
diff --git a/tests/ngit_pr_checkout.rs b/tests/ngit_pr_checkout.rs
new file mode 100644
index 0000000..f7d7855
--- /dev/null
+++ b/tests/ngit_pr_checkout.rs
@@ -0,0 +1,536 @@
1use anyhow::Result;
2use futures::join;
3use serial_test::serial;
4use test_utils::{git::GitTestRepo, relay::Relay, *};
5
6/// Run `ngit pr list --json --offline` in `dir` and return the nevent id for
7/// the proposal whose branch-name matches `branch_name_in_event`.
8fn get_proposal_id_for_branch(dir: &std::path::Path, branch_name_in_event: &str) -> Result<String> {
9 let output = std::process::Command::new(assert_cmd::cargo::cargo_bin("ngit"))
10 .env("NGITTEST", "TRUE")
11 .current_dir(dir)
12 .args([
13 "--nsec",
14 TEST_KEY_1_NSEC,
15 "--password",
16 TEST_PASSWORD,
17 "--disable-cli-spinners",
18 "pr",
19 "list",
20 "--json",
21 "--offline",
22 ])
23 .output()?;
24 let stdout = String::from_utf8(output.stdout)?;
25 let proposals: Vec<serde_json::Value> = serde_json::from_str(&stdout)
26 .map_err(|e| anyhow::anyhow!("failed to parse pr list json: {e}\nstdout: {stdout}"))?;
27 let entry = proposals
28 .iter()
29 .find(|p| {
30 p["branch"]
31 .as_str()
32 .map(|b| b.starts_with(&format!("pr/{branch_name_in_event}(")))
33 .unwrap_or(false)
34 })
35 .ok_or_else(|| {
36 anyhow::anyhow!(
37 "no proposal found for branch {branch_name_in_event} in: {stdout}"
38 )
39 })?;
40 Ok(entry["id"].as_str().unwrap_or_default().to_string())
41}
42
43/// Run `ngit pr checkout --offline <id>` (cache must already be populated).
44fn run_pr_checkout(test_repo: &GitTestRepo, branch_name_in_event: &str) -> Result<()> {
45 run_pr_checkout_with_args(test_repo, branch_name_in_event, &["--offline"])
46}
47
48/// Run `ngit pr checkout --force --offline <id>` (cache must already be populated).
49fn run_pr_checkout_force(test_repo: &GitTestRepo, branch_name_in_event: &str) -> Result<()> {
50 run_pr_checkout_with_args(test_repo, branch_name_in_event, &["--force", "--offline"])
51}
52
53fn run_pr_checkout_with_args(
54 test_repo: &GitTestRepo,
55 branch_name_in_event: &str,
56 extra_args: &[&str],
57) -> Result<()> {
58 let proposal_id = get_proposal_id_for_branch(&test_repo.dir, branch_name_in_event)?;
59 let mut args = vec![
60 "--nsec",
61 TEST_KEY_1_NSEC,
62 "--password",
63 TEST_PASSWORD,
64 "--disable-cli-spinners",
65 "pr",
66 "checkout",
67 ];
68 args.extend_from_slice(extra_args);
69 args.push(&proposal_id);
70 // Use std::process::Command directly (not CliTester/rexpect) so that a
71 // non-zero exit code is reliably detected without PTY timeout issues.
72 let status = std::process::Command::new(assert_cmd::cargo::cargo_bin("ngit"))
73 .env("NGITTEST", "TRUE")
74 .current_dir(&test_repo.dir)
75 .args(&args)
76 .status()?;
77 if status.success() {
78 Ok(())
79 } else {
80 anyhow::bail!("ngit pr checkout exited with {status}")
81 }
82}
83
84/// Spin up the standard 5-relay set used by all tests in this file.
85/// Returns the five relay handles in port order (51,52,53,55,56).
86#[allow(clippy::type_complexity)]
87fn make_relays() -> (
88 Relay<'static>,
89 Relay<'static>,
90 Relay<'static>,
91 Relay<'static>,
92 Relay<'static>,
93) {
94 let mut r51 = Relay::new(8051, None, None);
95 let r52 = Relay::new(8052, None, None);
96 let r53 = Relay::new(8053, None, None);
97 let mut r55 = Relay::new(8055, None, None);
98 let r56 = Relay::new(8056, None, None);
99
100 r51.events.push(generate_test_key_1_relay_list_event());
101 r51.events.push(generate_test_key_1_metadata_event("fred"));
102 r51.events.push(generate_repo_ref_event());
103
104 r55.events.push(generate_repo_ref_event());
105 r55.events.push(generate_test_key_1_metadata_event("fred"));
106 r55.events.push(generate_test_key_1_relay_list_event());
107
108 (r51, r52, r53, r55, r56)
109}
110
111fn shutdown_relays() -> Result<()> {
112 for port in [51u64, 52, 53, 55, 56] {
113 relay::shutdown_relay(8000 + port)?;
114 }
115 Ok(())
116}
117
118mod when_proposal_branch_doesnt_exist {
119 use super::*;
120
121 async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> {
122 let (mut r51, mut r52, mut r53, mut r55, mut r56) = make_relays();
123
124 let cli_tester_handle =
125 std::thread::spawn(move || -> Result<(GitTestRepo, GitTestRepo)> {
126 let originating_repo = cli_tester_create_proposals()?;
127
128 let test_repo = GitTestRepo::default();
129 test_repo.populate()?;
130
131 use_ngit_pr_checkout(&test_repo, FEATURE_BRANCH_NAME_1)?;
132
133 shutdown_relays()?;
134 Ok((originating_repo, test_repo))
135 });
136
137 let _ = join!(
138 r51.listen_until_close(),
139 r52.listen_until_close(),
140 r53.listen_until_close(),
141 r55.listen_until_close(),
142 r56.listen_until_close(),
143 );
144 cli_tester_handle.join().unwrap()
145 }
146
147 #[tokio::test]
148 #[serial]
149 async fn proposal_branch_created_with_correct_name() -> Result<()> {
150 let (_, test_repo) = prep_and_run().await?;
151 let expected_branch = get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_1)?;
152 assert!(
153 test_repo.get_local_branch_names()?.contains(&expected_branch),
154 "expected branch {expected_branch} to exist"
155 );
156 Ok(())
157 }
158
159 #[tokio::test]
160 #[serial]
161 async fn proposal_branch_checked_out() -> Result<()> {
162 let (_, test_repo) = prep_and_run().await?;
163 assert_eq!(
164 get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_1)?,
165 test_repo.get_checked_out_branch_name()?,
166 );
167 Ok(())
168 }
169
170 #[tokio::test]
171 #[serial]
172 async fn proposal_branch_tip_is_most_recent_patch() -> Result<()> {
173 let (originating_repo, test_repo) = prep_and_run().await?;
174 assert_eq!(
175 originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?,
176 test_repo.get_tip_of_local_branch(
177 &get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_1)?
178 )?,
179 );
180 Ok(())
181 }
182}
183
184mod when_proposal_branch_exists_and_is_up_to_date {
185 use super::*;
186
187 async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> {
188 let (mut r51, mut r52, mut r53, mut r55, mut r56) = make_relays();
189
190 let cli_tester_handle =
191 std::thread::spawn(move || -> Result<(GitTestRepo, GitTestRepo)> {
192 let originating_repo = cli_tester_create_proposals()?;
193
194 let test_repo = GitTestRepo::default();
195 test_repo.populate()?;
196
197 // first checkout creates the branch
198 use_ngit_pr_checkout(&test_repo, FEATURE_BRANCH_NAME_1)?;
199 test_repo.checkout("main")?;
200
201 // second checkout: branch already exists and is up to date
202 run_pr_checkout(&test_repo, FEATURE_BRANCH_NAME_1)?;
203
204 shutdown_relays()?;
205 Ok((originating_repo, test_repo))
206 });
207
208 let _ = join!(
209 r51.listen_until_close(),
210 r52.listen_until_close(),
211 r53.listen_until_close(),
212 r55.listen_until_close(),
213 r56.listen_until_close(),
214 );
215 cli_tester_handle.join().unwrap()
216 }
217
218 #[tokio::test]
219 #[serial]
220 async fn proposal_branch_checked_out() -> Result<()> {
221 let (_, test_repo) = prep_and_run().await?;
222 assert_eq!(
223 get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_1)?,
224 test_repo.get_checked_out_branch_name()?,
225 );
226 Ok(())
227 }
228
229 #[tokio::test]
230 #[serial]
231 async fn proposal_branch_tip_unchanged() -> Result<()> {
232 let (originating_repo, test_repo) = prep_and_run().await?;
233 assert_eq!(
234 originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?,
235 test_repo.get_tip_of_local_branch(
236 &get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_1)?
237 )?,
238 );
239 Ok(())
240 }
241}
242
243mod when_proposal_branch_exists_and_is_behind {
244 use super::*;
245
246 async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> {
247 let (mut r51, mut r52, mut r53, mut r55, mut r56) = make_relays();
248
249 let cli_tester_handle =
250 std::thread::spawn(move || -> Result<(GitTestRepo, GitTestRepo)> {
251 let originating_repo = cli_tester_create_proposals()?;
252
253 let test_repo = GitTestRepo::default();
254 test_repo.populate()?;
255
256 use_ngit_pr_checkout(&test_repo, FEATURE_BRANCH_NAME_1)?;
257
258 // wind the local branch back one commit so it's behind
259 remove_latest_commit_so_proposal_branch_is_behind_and_checkout_main(&test_repo)?;
260
261 // checkout again — should fast-forward to the latest patch
262 run_pr_checkout(&test_repo, FEATURE_BRANCH_NAME_1)?;
263
264 shutdown_relays()?;
265 Ok((originating_repo, test_repo))
266 });
267
268 let _ = join!(
269 r51.listen_until_close(),
270 r52.listen_until_close(),
271 r53.listen_until_close(),
272 r55.listen_until_close(),
273 r56.listen_until_close(),
274 );
275 cli_tester_handle.join().unwrap()
276 }
277
278 #[tokio::test]
279 #[serial]
280 async fn proposal_branch_checked_out() -> Result<()> {
281 let (_, test_repo) = prep_and_run().await?;
282 assert_eq!(
283 get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_1)?,
284 test_repo.get_checked_out_branch_name()?,
285 );
286 Ok(())
287 }
288
289 #[tokio::test]
290 #[serial]
291 async fn proposal_branch_tip_is_most_recent_patch() -> Result<()> {
292 let (originating_repo, test_repo) = prep_and_run().await?;
293 assert_eq!(
294 originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?,
295 test_repo.get_tip_of_local_branch(
296 &get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_1)?
297 )?,
298 );
299 Ok(())
300 }
301}
302
303mod when_proposal_branch_has_local_amendments {
304 use super::*;
305
306 async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> {
307 let (mut r51, mut r52, mut r53, mut r55, mut r56) = make_relays();
308
309 let cli_tester_handle =
310 std::thread::spawn(move || -> Result<(GitTestRepo, GitTestRepo)> {
311 let originating_repo = cli_tester_create_proposals()?;
312
313 let test_repo = GitTestRepo::default();
314 test_repo.populate()?;
315
316 use_ngit_pr_checkout(&test_repo, FEATURE_BRANCH_NAME_1)?;
317
318 // amend: remove the tip and add a different commit in its place
319 amend_last_commit(&test_repo, "add ammended-commit.md")?;
320 test_repo.checkout("main")?;
321
322 // checkout without --force should bail on diverged branch
323 assert!(
324 run_pr_checkout(&test_repo, FEATURE_BRANCH_NAME_1).is_err(),
325 "expected checkout to fail without --force on amended branch"
326 );
327
328 shutdown_relays()?;
329 Ok((originating_repo, test_repo))
330 });
331
332 let _ = join!(
333 r51.listen_until_close(),
334 r52.listen_until_close(),
335 r53.listen_until_close(),
336 r55.listen_until_close(),
337 r56.listen_until_close(),
338 );
339 cli_tester_handle.join().unwrap()
340 }
341
342 #[tokio::test]
343 #[serial]
344 async fn local_unpublished_commits_are_not_overwritten() -> Result<()> {
345 let (originating_repo, test_repo) = prep_and_run().await?;
346 // the local branch tip must differ from the published tip — local work preserved
347 assert_ne!(
348 originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?,
349 test_repo.get_tip_of_local_branch(
350 &get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_1)?
351 )?,
352 );
353 Ok(())
354 }
355}
356
357mod when_proposal_branch_has_local_commits_on_top {
358 use super::*;
359
360 async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> {
361 let (mut r51, mut r52, mut r53, mut r55, mut r56) = make_relays();
362
363 let cli_tester_handle =
364 std::thread::spawn(move || -> Result<(GitTestRepo, GitTestRepo)> {
365 let originating_repo = cli_tester_create_proposals()?;
366
367 let test_repo = GitTestRepo::default();
368 test_repo.populate()?;
369
370 use_ngit_pr_checkout(&test_repo, FEATURE_BRANCH_NAME_1)?;
371
372 // add an extra local commit on top of the proposal branch
373 std::fs::write(test_repo.dir.join("local-extra.md"), "local work")?;
374 test_repo.stage_and_commit("add local-extra.md")?;
375 test_repo.checkout("main")?;
376
377 // checkout again — should not discard the extra local commit
378 run_pr_checkout(&test_repo, FEATURE_BRANCH_NAME_1)?;
379
380 shutdown_relays()?;
381 Ok((originating_repo, test_repo))
382 });
383
384 let _ = join!(
385 r51.listen_until_close(),
386 r52.listen_until_close(),
387 r53.listen_until_close(),
388 r55.listen_until_close(),
389 r56.listen_until_close(),
390 );
391 cli_tester_handle.join().unwrap()
392 }
393
394 #[tokio::test]
395 #[serial]
396 async fn proposal_branch_checked_out() -> Result<()> {
397 let (_, test_repo) = prep_and_run().await?;
398 assert_eq!(
399 get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_1)?,
400 test_repo.get_checked_out_branch_name()?,
401 );
402 Ok(())
403 }
404
405 #[tokio::test]
406 #[serial]
407 async fn local_commits_are_not_discarded() -> Result<()> {
408 let (originating_repo, test_repo) = prep_and_run().await?;
409 let branch = get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_1)?;
410 let local_tip = test_repo.get_tip_of_local_branch(&branch)?;
411 let published_tip = originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?;
412 // local tip must be ahead of (not equal to) the published tip
413 assert_ne!(local_tip, published_tip, "local commits were discarded");
414 // and the published tip must be an ancestor of the local tip
415 assert!(
416 test_repo
417 .git_repo
418 .graph_descendant_of(local_tip, published_tip)?,
419 "local branch is not descended from published tip"
420 );
421 Ok(())
422 }
423}
424
425mod when_newer_revision_rebases_proposal {
426 use super::*;
427
428 async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> {
429 let (mut r51, mut r52, mut r53, mut r55, mut r56) = make_relays();
430
431 let cli_tester_handle =
432 std::thread::spawn(move || -> Result<(GitTestRepo, GitTestRepo)> {
433 let (new_originating_repo, test_repo) =
434 create_proposals_with_rebased_first_proposal()?;
435
436 // refresh test_repo cache so it sees the new rebased revision
437 let mut p = CliTester::new_from_dir(
438 &test_repo.dir,
439 [
440 "--nsec",
441 TEST_KEY_1_NSEC,
442 "--password",
443 TEST_PASSWORD,
444 "--disable-cli-spinners",
445 "pr",
446 "list",
447 ],
448 );
449 p.expect_end_eventually()?;
450
451 // checkout without --force should bail on diverged branch
452 assert!(
453 run_pr_checkout(&test_repo, FEATURE_BRANCH_NAME_1).is_err(),
454 "expected checkout to fail without --force on diverged branch"
455 );
456 // checkout with --force should update to the new rebased revision
457 // (relays still needed for the fetch inside checkout)
458 run_pr_checkout_force(&test_repo, FEATURE_BRANCH_NAME_1)?;
459
460 shutdown_relays()?;
461 Ok((new_originating_repo, test_repo))
462 });
463
464 let _ = join!(
465 r51.listen_until_close(),
466 r52.listen_until_close(),
467 r53.listen_until_close(),
468 r55.listen_until_close(),
469 r56.listen_until_close(),
470 );
471 cli_tester_handle.join().unwrap()
472 }
473
474 #[tokio::test]
475 #[serial]
476 async fn proposal_branch_checked_out() -> Result<()> {
477 let (_, test_repo) = prep_and_run().await?;
478 assert_eq!(
479 get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_1)?,
480 test_repo.get_checked_out_branch_name()?,
481 );
482 Ok(())
483 }
484
485 #[tokio::test]
486 #[serial]
487 async fn proposal_branch_tip_is_most_recent_revision_tip() -> Result<()> {
488 let (new_originating_repo, test_repo) = prep_and_run().await?;
489 assert_eq!(
490 new_originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?,
491 test_repo.get_tip_of_local_branch(
492 &get_proposal_branch_name(&test_repo, FEATURE_BRANCH_NAME_1)?
493 )?,
494 );
495 Ok(())
496 }
497}
498
499/// Creates 3 proposals, checks out proposal 1 in a test repo, then publishes
500/// a rebased revision of proposal 1 from a second originating repo. Returns
501/// (new_originating_repo, test_repo) with the test repo still on the old branch.
502fn create_proposals_with_rebased_first_proposal(
503) -> Result<(GitTestRepo, GitTestRepo)> {
504 // create the initial 3 proposals and check out proposal 1 in a test repo
505 let (_, test_repo) =
506 create_proposals_and_repo_with_proposal_branch_checked_out(FEATURE_BRANCH_NAME_1)?;
507
508 // get the original proposal id to use as in_reply_to for the rebased revision
509 let original_proposal_id =
510 get_proposal_id_for_branch(&test_repo.dir, FEATURE_BRANCH_NAME_1)?;
511
512 // publish a rebased revision of proposal 1 from a second originating repo
513 let second_originating_repo = GitTestRepo::default();
514 second_originating_repo.populate()?;
515 std::fs::write(
516 second_originating_repo.dir.join("amazing.md"),
517 "some content",
518 )?;
519 second_originating_repo.stage_and_commit("commit for rebasing on top of")?;
520 cli_tester_create_proposal(
521 &second_originating_repo,
522 FEATURE_BRANCH_NAME_1,
523 "a",
524 Some((PROPOSAL_TITLE_1, "proposal a description")),
525 Some(original_proposal_id),
526 )?;
527
528 // simulate the test repo having pulled the updated main branch
529 let branch_name = test_repo.get_checked_out_branch_name()?;
530 test_repo.checkout("main")?;
531 std::fs::write(test_repo.dir.join("amazing.md"), "some content")?;
532 test_repo.stage_and_commit("commit for rebasing on top of")?;
533 test_repo.checkout(&branch_name)?;
534
535 Ok((second_originating_repo, test_repo))
536}