upleb.uk

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

summaryrefslogtreecommitdiff
path: root/tests/git_remote_nostr
diff options
context:
space:
mode:
Diffstat (limited to 'tests/git_remote_nostr')
-rw-r--r--tests/git_remote_nostr/main.rs2338
1 files changed, 2338 insertions, 0 deletions
diff --git a/tests/git_remote_nostr/main.rs b/tests/git_remote_nostr/main.rs
new file mode 100644
index 0000000..7251204
--- /dev/null
+++ b/tests/git_remote_nostr/main.rs
@@ -0,0 +1,2338 @@
1use std::{collections::HashSet, env::current_dir};
2
3use anyhow::{Context, Result};
4use futures::join;
5use git2::Oid;
6use nostr::nips::nip01::Coordinate;
7use nostr_sdk::{secp256k1::rand, Event, JsonUtil, Kind, ToBech32};
8use relay::Relay;
9use serial_test::serial;
10use test_utils::{git::GitTestRepo, *};
11
12static NOSTR_REMOTE_NAME: &str = "nostr";
13static STATE_KIND: nostr::Kind = Kind::Custom(30618);
14
15fn get_nostr_remote_url() -> Result<String> {
16 let repo_event = generate_repo_ref_event();
17 let naddr = Coordinate {
18 kind: Kind::GitRepoAnnouncement,
19 public_key: repo_event.author(),
20 identifier: repo_event.identifier().unwrap().to_string(),
21 relays: vec![
22 "ws://localhost:8055".to_string(),
23 "ws://localhost:8056".to_string(),
24 ],
25 }
26 .to_bech32()?;
27 Ok(format!("nostr://{naddr}"))
28}
29
30fn prep_git_repo() -> Result<GitTestRepo> {
31 let test_repo = GitTestRepo::without_repo_in_git_config();
32 set_git_nostr_login_config(&test_repo)?;
33 test_repo.add_remote(NOSTR_REMOTE_NAME, &get_nostr_remote_url()?)?;
34 test_repo.populate()?;
35 Ok(test_repo)
36}
37
38fn set_git_nostr_login_config(test_repo: &GitTestRepo) -> Result<()> {
39 let mut config = test_repo
40 .git_repo
41 .config()
42 .context("cannot open git config")?;
43 config.set_str("nostr.nsec", TEST_KEY_2_NSEC)?;
44 config.set_str("nostr.npub", TEST_KEY_2_NPUB)?;
45 config.set_str("user.name", "test name")?;
46 config.set_str("user.email", "test@test.com")?;
47 config.set_bool("commit.gpgSign", false)?;
48 Ok(())
49}
50
51fn clone_git_repo_with_nostr_url() -> Result<GitTestRepo> {
52 let path = current_dir()?.join(format!("tmpgit-clone{}", rand::random::<u64>()));
53 std::fs::create_dir(path.clone())?;
54 CliTester::new_git_with_remote_helper_from_dir(&path, ["clone", &get_nostr_remote_url()?, "."])
55 .expect_end_eventually_and_print()?;
56 let test_repo = GitTestRepo::open(&path)?;
57 set_git_nostr_login_config(&test_repo)?;
58 Ok(test_repo)
59}
60
61fn prep_git_repo_minus_1_commit() -> Result<GitTestRepo> {
62 let test_repo = GitTestRepo::without_repo_in_git_config();
63 set_git_nostr_login_config(&test_repo)?;
64 test_repo.add_remote(NOSTR_REMOTE_NAME, &get_nostr_remote_url()?)?;
65 test_repo.populate_minus_1()?;
66 Ok(test_repo)
67}
68
69fn cli_tester(git_repo: &GitTestRepo) -> CliTester {
70 CliTester::new_remote_helper_from_dir(&git_repo.dir, &get_nostr_remote_url().unwrap())
71}
72
73fn cli_tester_after_fetch(git_repo: &GitTestRepo) -> Result<CliTester> {
74 let mut p = cli_tester(git_repo);
75 cli_expect_nostr_fetch(&mut p)?;
76 Ok(p)
77}
78
79fn cli_expect_nostr_fetch(p: &mut CliTester) -> Result<()> {
80 p.expect("nostr: fetching...\r\n")?;
81 p.expect_eventually("updates")?; // some updates
82 p.expect_eventually("\r\n")?;
83 Ok(())
84}
85
86/// git runs `list for-push` before `push`. in `push` we use the git server
87/// remote refs downloaded by `list` to assess how to push to git servers.
88/// we are therefore running it this way in our tests
89fn cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(
90 git_repo: &GitTestRepo,
91) -> Result<CliTester> {
92 let mut p = cli_tester_after_fetch(git_repo)?;
93
94 p.send_line("list for-push")?;
95 p.expect_eventually_and_print("\r\n\r\n")?;
96 Ok(p)
97}
98
99async fn generate_repo_with_state_event() -> Result<(nostr::Event, GitTestRepo)> {
100 let git_repo = prep_git_repo()?;
101 git_repo.create_branch("example-branch")?;
102 let main_commit_id = git_repo.get_tip_of_local_branch("main")?.to_string();
103 // TODO recreate_as_bare isn't creating other branches
104 let source_git_repo = GitTestRepo::recreate_as_bare(&git_repo)?;
105 let example_commit_id = source_git_repo
106 .get_tip_of_local_branch("example-branch")?
107 .to_string();
108 let events = vec![
109 generate_test_key_1_metadata_event("fred"),
110 generate_test_key_1_relay_list_event(),
111 generate_repo_ref_event_with_git_server(vec![
112 source_git_repo.dir.to_str().unwrap().to_string(),
113 ]),
114 ];
115 // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57)
116 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
117 Relay::new(8051, None, None),
118 Relay::new(8052, None, None),
119 Relay::new(8053, None, None),
120 Relay::new(8055, None, None),
121 Relay::new(8056, None, None),
122 Relay::new(8057, None, None),
123 );
124 r51.events = events.clone();
125 r55.events = events;
126
127 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
128 let mut p = cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?;
129 p.send_line("push refs/heads/main:refs/heads/main")?;
130 p.send_line("")?;
131 // p.expect("ok refs/heads/main\r\n")?;
132 p.expect_eventually_and_print("\r\n\r\n")?;
133 p.exit()?;
134 for p in [51, 52, 53, 55, 56, 57] {
135 relay::shutdown_relay(8000 + p)?;
136 }
137 Ok(())
138 });
139 // launch relays
140 let _ = join!(
141 r51.listen_until_close(),
142 r52.listen_until_close(),
143 r53.listen_until_close(),
144 r55.listen_until_close(),
145 r56.listen_until_close(),
146 r57.listen_until_close(),
147 );
148 cli_tester_handle.join().unwrap()?;
149
150 let state_event = r56
151 .events
152 .iter()
153 .find(|e| e.kind().eq(&STATE_KIND))
154 .context("state event not created")?;
155
156 assert_eq!(
157 state_event
158 .tags
159 .iter()
160 .filter(|t| t.kind().to_string().as_str().ne("d"))
161 .map(|t| t.as_vec().to_vec())
162 .collect::<HashSet<Vec<String>>>(),
163 HashSet::from([
164 vec!["HEAD".to_string(), "ref: refs/heads/main".to_string()],
165 vec!["refs/heads/main".to_string(), main_commit_id.clone(),],
166 vec!["refs/heads/example-branch".to_string(), example_commit_id,],
167 ]),
168 );
169
170 // wait for bigger timestamp
171 std::thread::sleep(std::time::Duration::from_millis(1000));
172
173 Ok((state_event.clone(), source_git_repo))
174}
175
176async fn prep_source_repo_and_events_including_proposals()
177-> Result<(Vec<nostr::Event>, GitTestRepo)> {
178 let (state_event, source_git_repo) = generate_repo_with_state_event().await?;
179 let source_path = source_git_repo.dir.to_str().unwrap().to_string();
180
181 let events = vec![
182 generate_test_key_1_metadata_event("fred"),
183 generate_test_key_1_relay_list_event(),
184 generate_repo_ref_event_with_git_server(vec![source_path.to_string()]),
185 state_event,
186 ];
187 // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57)
188 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
189 Relay::new(8051, None, None),
190 Relay::new(8052, None, None),
191 Relay::new(8053, None, None),
192 Relay::new(8055, None, None),
193 Relay::new(8056, None, None),
194 Relay::new(8057, None, None),
195 );
196 r51.events = events.clone();
197 r55.events = events;
198
199 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
200 cli_tester_create_proposals()?;
201 for p in [51, 52, 53, 55, 56, 57] {
202 relay::shutdown_relay(8000 + p)?;
203 }
204 Ok(())
205 });
206 // launch relays
207 let _ = join!(
208 r51.listen_until_close(),
209 r52.listen_until_close(),
210 r53.listen_until_close(),
211 r55.listen_until_close(),
212 r56.listen_until_close(),
213 r57.listen_until_close(),
214 );
215 cli_tester_handle.join().unwrap()?;
216
217 Ok((r55.events, source_git_repo))
218}
219
220mod initially_runs_fetch {
221
222 use super::*;
223
224 #[tokio::test]
225 #[serial]
226 async fn runs_fetch_and_reports() -> Result<()> {
227 let source_git_repo = prep_git_repo()?;
228 let git_repo = prep_git_repo()?;
229 let events = vec![
230 generate_test_key_1_metadata_event("fred"),
231 generate_test_key_1_relay_list_event(),
232 generate_repo_ref_event_with_git_server(vec![
233 source_git_repo.dir.to_str().unwrap().to_string(),
234 ]),
235 ];
236 // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57)
237 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
238 Relay::new(8051, None, None),
239 Relay::new(8052, None, None),
240 Relay::new(8053, None, None),
241 Relay::new(8055, None, None),
242 Relay::new(8056, None, None),
243 Relay::new(8057, None, None),
244 );
245 r51.events = events.clone();
246 r55.events = events;
247
248 // // check relay had the right number of events
249 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
250 let mut p = cli_tester_after_fetch(&git_repo)?;
251 p.exit()?;
252 for p in [51, 52, 53, 55, 56, 57] {
253 relay::shutdown_relay(8000 + p)?;
254 }
255 Ok(())
256 });
257
258 // launch relays
259 let _ = join!(
260 r51.listen_until_close(),
261 r52.listen_until_close(),
262 r53.listen_until_close(),
263 r55.listen_until_close(),
264 r56.listen_until_close(),
265 r57.listen_until_close(),
266 );
267 cli_tester_handle.join().unwrap()?;
268 Ok(())
269 }
270}
271
272mod list {
273
274 use super::*;
275
276 mod without_state_announcement {
277
278 use super::*;
279
280 #[tokio::test]
281 #[serial]
282 async fn lists_head_and_2_branches_and_commit_ids_from_git_server() -> Result<()> {
283 let source_git_repo = prep_git_repo()?;
284 let source_path = source_git_repo.dir.to_str().unwrap().to_string();
285 std::fs::write(source_git_repo.dir.join("commit.md"), "some content")?;
286 let main_commit_id = source_git_repo.stage_and_commit("commit.md")?;
287
288 source_git_repo.create_branch("vnext")?;
289 source_git_repo.checkout("vnext")?;
290 std::fs::write(source_git_repo.dir.join("vnext.md"), "some content")?;
291 let vnext_commit_id = source_git_repo.stage_and_commit("vnext.md")?;
292 source_git_repo.checkout("main")?;
293
294 let git_repo = prep_git_repo()?;
295 let events = vec![
296 generate_test_key_1_metadata_event("fred"),
297 generate_test_key_1_relay_list_event(),
298 generate_repo_ref_event_with_git_server(vec![
299 source_git_repo.dir.to_str().unwrap().to_string(),
300 ]),
301 ];
302 // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57)
303 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
304 Relay::new(8051, None, None),
305 Relay::new(8052, None, None),
306 Relay::new(8053, None, None),
307 Relay::new(8055, None, None),
308 Relay::new(8056, None, None),
309 Relay::new(8057, None, None),
310 );
311 r51.events = events.clone();
312 r55.events = events;
313
314 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
315 let mut p = cli_tester_after_fetch(&git_repo)?;
316 p.send_line("list")?;
317 p.expect(
318 format!(
319 "fetching ref list over filesystem from {}...\r\n",
320 source_path
321 )
322 .as_str(),
323 )?;
324 // println!("{}", p.expect_eventually("\r\n\r\n")?);
325 let res = p.expect_eventually("\r\n\r\n")?;
326 p.exit()?;
327 for p in [51, 52, 53, 55, 56, 57] {
328 relay::shutdown_relay(8000 + p)?;
329 }
330 assert_eq!(
331 res.split("\r\n")
332 .map(|e| e.to_string())
333 .collect::<HashSet<String>>(),
334 HashSet::from([
335 "@refs/heads/main HEAD".to_string(),
336 format!("{} refs/heads/main", main_commit_id),
337 format!("{} refs/heads/vnext", vnext_commit_id),
338 ]),
339 );
340 Ok(())
341 });
342 // launch relays
343 let _ = join!(
344 r51.listen_until_close(),
345 r52.listen_until_close(),
346 r53.listen_until_close(),
347 r55.listen_until_close(),
348 r56.listen_until_close(),
349 r57.listen_until_close(),
350 );
351 cli_tester_handle.join().unwrap()?;
352 Ok(())
353 }
354 }
355 mod with_state_announcement {
356
357 use super::*;
358
359 mod when_announcement_matches_git_server {
360
361 use super::*;
362
363 #[tokio::test]
364 #[serial]
365 async fn lists_head_and_2_branches_and_commit_ids_announcement() -> Result<()> {
366 let (state_event, source_git_repo) = generate_repo_with_state_event().await?;
367 let source_path = source_git_repo.dir.to_str().unwrap().to_string();
368
369 let main_commit_id = source_git_repo.get_tip_of_local_branch("main")?;
370 let example_commit_id =
371 source_git_repo.get_tip_of_local_branch("example-branch")?;
372
373 let git_repo = prep_git_repo()?;
374 let events = vec![
375 generate_test_key_1_metadata_event("fred"),
376 generate_test_key_1_relay_list_event(),
377 generate_repo_ref_event_with_git_server(vec![
378 source_git_repo.dir.to_str().unwrap().to_string(),
379 ]),
380 state_event,
381 ];
382 // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57)
383 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
384 Relay::new(8051, None, None),
385 Relay::new(8052, None, None),
386 Relay::new(8053, None, None),
387 Relay::new(8055, None, None),
388 Relay::new(8056, None, None),
389 Relay::new(8057, None, None),
390 );
391 r51.events = events.clone();
392 r55.events = events;
393
394 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
395 let mut p = cli_tester_after_fetch(&git_repo)?;
396 p.send_line("list")?;
397 p.expect(
398 format!(
399 "fetching ref list over filesystem from {}...\r\n",
400 source_path
401 )
402 .as_str(),
403 )?;
404 // println!("{}", p.expect_eventually("\r\n\r\n")?);
405 let res = p.expect_eventually("\r\n\r\n")?;
406 p.exit()?;
407 for p in [51, 52, 53, 55, 56, 57] {
408 relay::shutdown_relay(8000 + p)?;
409 }
410 assert_eq!(
411 res.split("\r\n")
412 .map(|e| e.to_string())
413 .collect::<HashSet<String>>(),
414 HashSet::from([
415 "@refs/heads/main HEAD".to_string(),
416 format!("{} refs/heads/main", main_commit_id),
417 format!("{} refs/heads/example-branch", example_commit_id),
418 ]),
419 );
420
421 Ok(())
422 });
423 // launch relays
424 let _ = join!(
425 r51.listen_until_close(),
426 r52.listen_until_close(),
427 r53.listen_until_close(),
428 r55.listen_until_close(),
429 r56.listen_until_close(),
430 r57.listen_until_close(),
431 );
432 cli_tester_handle.join().unwrap()?;
433 Ok(())
434 }
435 }
436 mod when_announcement_doesnt_match_git_server {
437
438 use super::*;
439
440 #[tokio::test]
441 #[serial]
442 async fn anouncement_state_is_used() -> Result<()> {
443 let (state_event, source_git_repo) = generate_repo_with_state_event().await?;
444 let source_path = source_git_repo.dir.to_str().unwrap().to_string();
445 let main_original_commit_id = source_git_repo.get_tip_of_local_branch("main")?;
446
447 {
448 // add commit to main on git server
449 let tmp_repo = GitTestRepo::clone_repo(&source_git_repo)?;
450 std::fs::write(tmp_repo.dir.join("commitx.md"), "some content")?;
451 tmp_repo.stage_and_commit("commitx.md")?;
452 let mut remote = tmp_repo.git_repo.find_remote("origin")?;
453 remote.push(&["refs/heads/main:refs/heads/main"], None)?;
454 }
455
456 let main_updated_commit_id = source_git_repo.get_tip_of_local_branch("main")?;
457 assert_ne!(main_original_commit_id, main_updated_commit_id);
458 let example_commit_id =
459 source_git_repo.get_tip_of_local_branch("example-branch")?;
460
461 let git_repo = prep_git_repo()?;
462 let events = vec![
463 generate_test_key_1_metadata_event("fred"),
464 generate_test_key_1_relay_list_event(),
465 generate_repo_ref_event_with_git_server(vec![
466 source_git_repo.dir.to_str().unwrap().to_string(),
467 ]),
468 state_event,
469 ];
470 // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57)
471 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
472 Relay::new(8051, None, None),
473 Relay::new(8052, None, None),
474 Relay::new(8053, None, None),
475 Relay::new(8055, None, None),
476 Relay::new(8056, None, None),
477 Relay::new(8057, None, None),
478 );
479 r51.events = events.clone();
480 r55.events = events;
481
482 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
483 let mut p = cli_tester_after_fetch(&git_repo)?;
484 p.send_line("list")?;
485 p.expect(
486 format!(
487 "fetching ref list over filesystem from {}...\r\n",
488 source_path
489 )
490 .as_str(),
491 )?;
492 p.expect(
493 format!(
494 "WARNING: {} refs/heads/main is out of sync with nostr \r\n",
495 source_path
496 )
497 .as_str(),
498 )?;
499
500 // println!("{}", p.expect_eventually("\r\n\r\n")?);
501 let res = p.expect_eventually("\r\n\r\n")?;
502 p.exit()?;
503 for p in [51, 52, 53, 55, 56, 57] {
504 relay::shutdown_relay(8000 + p)?;
505 }
506 assert_eq!(
507 res.split("\r\n")
508 .map(|e| e.to_string())
509 .collect::<HashSet<String>>(),
510 HashSet::from([
511 "@refs/heads/main HEAD".to_string(),
512 format!("{} refs/heads/main", main_original_commit_id),
513 format!("{} refs/heads/example-branch", example_commit_id),
514 ]),
515 );
516 Ok(())
517 });
518 // launch relays
519 let _ = join!(
520 r51.listen_until_close(),
521 r52.listen_until_close(),
522 r53.listen_until_close(),
523 r55.listen_until_close(),
524 r56.listen_until_close(),
525 r57.listen_until_close(),
526 );
527 cli_tester_handle.join().unwrap()?;
528 Ok(())
529 }
530 }
531
532 mod when_there_are_open_proposals {
533
534 use super::*;
535
536 #[tokio::test]
537 #[serial]
538 async fn open_proposal_listed_in_prs_namespace() -> Result<()> {
539 let (state_event, source_git_repo) = generate_repo_with_state_event().await?;
540 let source_path = source_git_repo.dir.to_str().unwrap().to_string();
541
542 let main_commit_id = source_git_repo.get_tip_of_local_branch("main")?;
543 let example_commit_id =
544 source_git_repo.get_tip_of_local_branch("example-branch")?;
545
546 let git_repo = prep_git_repo()?;
547
548 let events = vec![
549 generate_test_key_1_metadata_event("fred"),
550 generate_test_key_1_relay_list_event(),
551 generate_repo_ref_event_with_git_server(vec![
552 source_git_repo.dir.to_str().unwrap().to_string(),
553 ]),
554 state_event,
555 ];
556 // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57)
557 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
558 Relay::new(8051, None, None),
559 Relay::new(8052, None, None),
560 Relay::new(8053, None, None),
561 Relay::new(8055, None, None),
562 Relay::new(8056, None, None),
563 Relay::new(8057, None, None),
564 );
565 r51.events = events.clone();
566 r55.events = events;
567
568 let cli_tester_handle = std::thread::spawn(move || -> Result<String> {
569 cli_tester_create_proposals()?;
570
571 let mut p = cli_tester_after_fetch(&git_repo)?;
572 p.send_line("list")?;
573 p.expect(
574 format!(
575 "fetching ref list over filesystem from {}...\r\n",
576 source_path
577 )
578 .as_str(),
579 )?;
580 // println!("{}", p.expect_eventually("\r\n\r\n")?);
581 let res = p.expect_eventually("\r\n\r\n")?;
582
583 p.exit()?;
584 for p in [51, 52, 53, 55, 56, 57] {
585 relay::shutdown_relay(8000 + p)?;
586 }
587 Ok(res)
588 });
589 // launch relays
590 let _ = join!(
591 r51.listen_until_close(),
592 r52.listen_until_close(),
593 r53.listen_until_close(),
594 r55.listen_until_close(),
595 r56.listen_until_close(),
596 r57.listen_until_close(),
597 );
598
599 let res = cli_tester_handle.join().unwrap()?;
600
601 let proposal_creation_repo = cli_tester_create_proposal_branches_ready_to_send()?;
602
603 let mut pr_refs = vec![];
604 for name in [
605 FEATURE_BRANCH_NAME_1,
606 FEATURE_BRANCH_NAME_2,
607 FEATURE_BRANCH_NAME_3,
608 ] {
609 pr_refs.push(format!(
610 "{} refs/heads/{}",
611 proposal_creation_repo.get_tip_of_local_branch(name)?,
612 get_proposal_branch_name_from_events(&r55.events, name)?,
613 ));
614 }
615
616 assert_eq!(
617 res.split("\r\n")
618 .map(|e| e.to_string())
619 .collect::<HashSet<String>>(),
620 [
621 vec![
622 "@refs/heads/main HEAD".to_string(),
623 format!("{} refs/heads/main", main_commit_id),
624 format!("{} refs/heads/example-branch", example_commit_id),
625 ],
626 pr_refs,
627 ]
628 .concat()
629 .iter()
630 .cloned()
631 .collect::<HashSet<String>>()
632 );
633
634 Ok(())
635 }
636 }
637 }
638}
639
640mod fetch {
641
642 use super::*;
643
644 #[tokio::test]
645 #[serial]
646 async fn fetch_downloads_speficied_commits_from_git_server() -> Result<()> {
647 let source_git_repo = prep_git_repo()?;
648 let source_path = source_git_repo.dir.to_str().unwrap().to_string();
649
650 std::fs::write(source_git_repo.dir.join("commit.md"), "some content")?;
651 let main_commit_id = source_git_repo.stage_and_commit("commit.md")?;
652
653 source_git_repo.create_branch("vnext")?;
654 source_git_repo.checkout("vnext")?;
655 std::fs::write(source_git_repo.dir.join("vnext.md"), "some content")?;
656 let vnext_commit_id = source_git_repo.stage_and_commit("vnext.md")?;
657
658 let git_repo = prep_git_repo()?;
659 let events = vec![
660 generate_test_key_1_metadata_event("fred"),
661 generate_test_key_1_relay_list_event(),
662 generate_repo_ref_event_with_git_server(vec![
663 source_git_repo.dir.to_str().unwrap().to_string(),
664 ]),
665 ];
666 // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57)
667 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
668 Relay::new(8051, None, None),
669 Relay::new(8052, None, None),
670 Relay::new(8053, None, None),
671 Relay::new(8055, None, None),
672 Relay::new(8056, None, None),
673 Relay::new(8057, None, None),
674 );
675 r51.events = events.clone();
676 r55.events = events;
677
678 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
679 assert!(git_repo.git_repo.find_commit(main_commit_id).is_err());
680 assert!(git_repo.git_repo.find_commit(vnext_commit_id).is_err());
681
682 let mut p = cli_tester_after_fetch(&git_repo)?;
683 p.send_line(format!("fetch {main_commit_id} main").as_str())?;
684 p.send_line(format!("fetch {vnext_commit_id} vnext").as_str())?;
685 p.send_line("")?;
686 p.expect(format!("fetching over filesystem from {source_path}...\r\n").as_str())?;
687 p.expect_eventually_and_print("\r\n")?;
688
689 assert!(git_repo.git_repo.find_commit(main_commit_id).is_ok());
690 assert!(git_repo.git_repo.find_commit(vnext_commit_id).is_ok());
691
692 p.exit()?;
693 for p in [51, 52, 53, 55, 56, 57] {
694 relay::shutdown_relay(8000 + p)?;
695 }
696 Ok(())
697 });
698 // launch relays
699 let _ = join!(
700 r51.listen_until_close(),
701 r52.listen_until_close(),
702 r53.listen_until_close(),
703 r55.listen_until_close(),
704 r56.listen_until_close(),
705 r57.listen_until_close(),
706 );
707 cli_tester_handle.join().unwrap()?;
708 Ok(())
709 }
710
711 mod when_first_git_server_fails_ {
712 use super::*;
713
714 #[tokio::test]
715 #[serial]
716 async fn fetch_downloads_speficied_commits_from_second_git_server() -> Result<()> {
717 let (state_event, source_git_repo) = generate_repo_with_state_event().await?;
718 // let source_path = source_git_repo.dir.to_str().unwrap().to_string();
719 let error_path = "./path-doesnt-exist".to_string();
720
721 let main_commit_id = source_git_repo.get_tip_of_local_branch("main")?;
722
723 let git_repo = prep_git_repo_minus_1_commit()?;
724
725 let events = vec![
726 generate_test_key_1_metadata_event("fred"),
727 generate_test_key_1_relay_list_event(),
728 generate_repo_ref_event_with_git_server(vec![
729 error_path.to_string(),
730 source_git_repo.dir.to_str().unwrap().to_string(),
731 ]),
732 state_event,
733 ];
734 // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57)
735 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
736 Relay::new(8051, None, None),
737 Relay::new(8052, None, None),
738 Relay::new(8053, None, None),
739 Relay::new(8055, None, None),
740 Relay::new(8056, None, None),
741 Relay::new(8057, None, None),
742 );
743 r51.events = events.clone();
744 r55.events = events;
745
746 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
747 assert!(git_repo.git_repo.find_commit(main_commit_id).is_err());
748
749 let mut p = cli_tester_after_fetch(&git_repo)?;
750 p.send_line(format!("fetch {main_commit_id} main").as_str())?;
751 p.send_line("")?;
752 p.expect(format!("fetching over filesystem from {error_path}...\r\n").as_str())?;
753 // not sure why the below isn't appearing
754 // p.expect(format!("fetching over filesystem from
755 // {source_path}...\r\n").as_str())?;
756 p.expect_eventually_and_print("\r\n")?;
757 // p.expect("\r\n")?;
758
759 assert!(git_repo.git_repo.find_commit(main_commit_id).is_ok());
760
761 p.exit()?;
762 for p in [51, 52, 53, 55, 56, 57] {
763 relay::shutdown_relay(8000 + p)?;
764 }
765 Ok(())
766 });
767 // launch relays
768 let _ = join!(
769 r51.listen_until_close(),
770 r52.listen_until_close(),
771 r53.listen_until_close(),
772 r55.listen_until_close(),
773 r56.listen_until_close(),
774 r57.listen_until_close(),
775 );
776 cli_tester_handle.join().unwrap()?;
777 Ok(())
778 }
779 }
780
781 #[tokio::test]
782 #[serial]
783 async fn creates_commits_from_open_proposal_with_no_warngins_printed() -> Result<()> {
784 let (events, source_git_repo) = prep_source_repo_and_events_including_proposals().await?;
785 let source_path = source_git_repo.dir.to_str().unwrap().to_string();
786
787 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
788 Relay::new(8051, None, None),
789 Relay::new(8052, None, None),
790 Relay::new(8053, None, None),
791 Relay::new(8055, None, None),
792 Relay::new(8056, None, None),
793 Relay::new(8057, None, None),
794 );
795 r51.events = events.clone();
796 r55.events = events.clone();
797
798 let git_repo = prep_git_repo()?;
799
800 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
801 let branch_name = get_proposal_branch_name_from_events(&events, FEATURE_BRANCH_NAME_1)?;
802 let proposal_tip = cli_tester_create_proposal_branches_ready_to_send()?
803 .get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?;
804
805 assert!(git_repo.git_repo.find_commit(proposal_tip).is_err());
806
807 let mut p = cli_tester_after_fetch(&git_repo)?;
808 p.send_line(format!("fetch {proposal_tip} refs/heads/{branch_name}").as_str())?;
809 p.send_line("")?;
810 p.expect(format!("fetching over filesystem from {source_path}...\r\n").as_str())?;
811 // expect no errors
812 p.expect_after_whitespace("\r\n")?;
813 p.exit()?;
814 for p in [51, 52, 53, 55, 56, 57] {
815 relay::shutdown_relay(8000 + p)?;
816 }
817
818 assert!(git_repo.git_repo.find_commit(proposal_tip).is_ok());
819
820 Ok(())
821 });
822 // launch relays
823 let _ = join!(
824 r51.listen_until_close(),
825 r52.listen_until_close(),
826 r53.listen_until_close(),
827 r55.listen_until_close(),
828 r56.listen_until_close(),
829 r57.listen_until_close(),
830 );
831
832 cli_tester_handle.join().unwrap()?;
833
834 Ok(())
835 }
836}
837
838mod push {
839
840 use super::*;
841
842 #[tokio::test]
843 #[serial]
844 async fn new_branch_when_no_state_event_exists() -> Result<()> {
845 generate_repo_with_state_event().await?;
846 Ok(())
847 }
848 mod two_branches_in_batch_one_added_one_updated {
849
850 use super::*;
851
852 #[tokio::test]
853 #[serial]
854 async fn updates_branch_on_git_server() -> Result<()> {
855 let git_repo = prep_git_repo()?;
856 let source_git_repo = GitTestRepo::recreate_as_bare(&git_repo)?;
857
858 std::fs::write(git_repo.dir.join("commit.md"), "some content")?;
859 let main_commit_id = git_repo.stage_and_commit("commit.md")?;
860
861 git_repo.create_branch("vnext")?;
862 git_repo.checkout("vnext")?;
863 std::fs::write(git_repo.dir.join("vnext.md"), "some content")?;
864 let vnext_commit_id = git_repo.stage_and_commit("vnext.md")?;
865
866 let events = vec![
867 generate_test_key_1_metadata_event("fred"),
868 generate_test_key_1_relay_list_event(),
869 generate_repo_ref_event_with_git_server(vec![
870 source_git_repo.dir.to_str().unwrap().to_string(),
871 ]),
872 ];
873 // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57)
874 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
875 Relay::new(8051, None, None),
876 Relay::new(8052, None, None),
877 Relay::new(8053, None, None),
878 Relay::new(8055, None, None),
879 Relay::new(8056, None, None),
880 Relay::new(8057, None, None),
881 );
882 r51.events = events.clone();
883 r55.events = events;
884
885 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
886 assert_ne!(
887 source_git_repo.get_tip_of_local_branch("main")?,
888 main_commit_id
889 );
890
891 let mut p =
892 cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?;
893
894 p.send_line("push refs/heads/main:refs/heads/main")?;
895 p.send_line("push refs/heads/vnext:refs/heads/vnext")?;
896 p.send_line("")?;
897 p.expect_eventually("\r\n\r\n")?;
898 p.exit()?;
899 for p in [51, 52, 53, 55, 56, 57] {
900 relay::shutdown_relay(8000 + p)?;
901 }
902
903 assert_eq!(
904 source_git_repo.get_tip_of_local_branch("main")?,
905 main_commit_id
906 );
907
908 assert_eq!(
909 source_git_repo.get_tip_of_local_branch("vnext")?,
910 vnext_commit_id
911 );
912
913 Ok(())
914 });
915 // launch relays
916 let _ = join!(
917 r51.listen_until_close(),
918 r52.listen_until_close(),
919 r53.listen_until_close(),
920 r55.listen_until_close(),
921 r56.listen_until_close(),
922 r57.listen_until_close(),
923 );
924 cli_tester_handle.join().unwrap()?;
925 Ok(())
926 }
927
928 #[tokio::test]
929 #[serial]
930 async fn remote_refs_updated_in_local_git() -> Result<()> {
931 let git_repo = prep_git_repo()?;
932 let source_git_repo = GitTestRepo::recreate_as_bare(&git_repo)?;
933
934 std::fs::write(git_repo.dir.join("commit.md"), "some content")?;
935 let main_commit_id = git_repo.stage_and_commit("commit.md")?;
936
937 git_repo.create_branch("vnext")?;
938 git_repo.checkout("vnext")?;
939 std::fs::write(git_repo.dir.join("vnext.md"), "some content")?;
940 let vnext_commit_id = git_repo.stage_and_commit("vnext.md")?;
941
942 let events = vec![
943 generate_test_key_1_metadata_event("fred"),
944 generate_test_key_1_relay_list_event(),
945 generate_repo_ref_event_with_git_server(vec![
946 source_git_repo.dir.to_str().unwrap().to_string(),
947 ]),
948 ];
949 // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57)
950 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
951 Relay::new(8051, None, None),
952 Relay::new(8052, None, None),
953 Relay::new(8053, None, None),
954 Relay::new(8055, None, None),
955 Relay::new(8056, None, None),
956 Relay::new(8057, None, None),
957 );
958 r51.events = events.clone();
959 r55.events = events;
960
961 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
962 assert_ne!(
963 source_git_repo.get_tip_of_local_branch("main")?,
964 main_commit_id
965 );
966
967 let mut p =
968 cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?;
969 p.send_line("push refs/heads/main:refs/heads/main")?;
970 p.send_line("push refs/heads/vnext:refs/heads/vnext")?;
971 p.send_line("")?;
972 p.expect_eventually("\r\n\r\n")?;
973 p.exit()?;
974 for p in [51, 52, 53, 55, 56, 57] {
975 relay::shutdown_relay(8000 + p)?;
976 }
977
978 assert_eq!(
979 git_repo
980 .git_repo
981 .find_reference("refs/remotes/nostr/main")?
982 .peel_to_commit()?
983 .id(),
984 main_commit_id,
985 );
986
987 assert_eq!(
988 git_repo
989 .git_repo
990 .find_reference("refs/remotes/nostr/vnext")?
991 .peel_to_commit()?
992 .id(),
993 vnext_commit_id
994 );
995
996 p.exit()?;
997 for p in [51, 52, 53, 55, 56, 57] {
998 relay::shutdown_relay(8000 + p)?;
999 }
1000 Ok(())
1001 });
1002 // launch relays
1003 let _ = join!(
1004 r51.listen_until_close(),
1005 r52.listen_until_close(),
1006 r53.listen_until_close(),
1007 r55.listen_until_close(),
1008 r56.listen_until_close(),
1009 r57.listen_until_close(),
1010 );
1011 cli_tester_handle.join().unwrap()?;
1012 Ok(())
1013 }
1014
1015 #[tokio::test]
1016 #[serial]
1017 async fn prints_git_helper_ok_respose() -> Result<()> {
1018 let git_repo = prep_git_repo()?;
1019 let source_git_repo = GitTestRepo::recreate_as_bare(&git_repo)?;
1020
1021 std::fs::write(git_repo.dir.join("commit.md"), "some content")?;
1022 let main_commit_id = git_repo.stage_and_commit("commit.md")?;
1023
1024 git_repo.create_branch("vnext")?;
1025 git_repo.checkout("vnext")?;
1026 std::fs::write(git_repo.dir.join("vnext.md"), "some content")?;
1027 git_repo.stage_and_commit("vnext.md")?;
1028
1029 let events = vec![
1030 generate_test_key_1_metadata_event("fred"),
1031 generate_test_key_1_relay_list_event(),
1032 generate_repo_ref_event_with_git_server(vec![
1033 source_git_repo.dir.to_str().unwrap().to_string(),
1034 ]),
1035 ];
1036 // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57)
1037 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
1038 Relay::new(8051, None, None),
1039 Relay::new(8052, None, None),
1040 Relay::new(8053, None, None),
1041 Relay::new(8055, None, None),
1042 Relay::new(8056, None, None),
1043 Relay::new(8057, None, None),
1044 );
1045 r51.events = events.clone();
1046 r55.events = events;
1047
1048 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
1049 assert_ne!(
1050 source_git_repo.get_tip_of_local_branch("main")?,
1051 main_commit_id
1052 );
1053
1054 let mut p =
1055 cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?;
1056
1057 p.send_line("push refs/heads/main:refs/heads/main")?;
1058 p.send_line("push refs/heads/vnext:refs/heads/vnext")?;
1059 p.send_line("")?;
1060 p.expect("ok refs/heads/main\r\n")?;
1061 p.expect("ok refs/heads/vnext\r\n")?;
1062 p.expect("\r\n")?;
1063 p.exit()?;
1064 for p in [51, 52, 53, 55, 56, 57] {
1065 relay::shutdown_relay(8000 + p)?;
1066 }
1067 Ok(())
1068 });
1069 // launch relays
1070 let _ = join!(
1071 r51.listen_until_close(),
1072 r52.listen_until_close(),
1073 r53.listen_until_close(),
1074 r55.listen_until_close(),
1075 r56.listen_until_close(),
1076 r57.listen_until_close(),
1077 );
1078 cli_tester_handle.join().unwrap()?;
1079 Ok(())
1080 }
1081
1082 #[tokio::test]
1083 #[serial]
1084 async fn when_no_existing_state_event_state_on_git_server_published_in_nostr_state_event()
1085 -> Result<()> {
1086 let git_repo = prep_git_repo()?;
1087 let source_git_repo = GitTestRepo::recreate_as_bare(&git_repo)?;
1088
1089 std::fs::write(git_repo.dir.join("commit.md"), "some content")?;
1090 let main_commit_id = git_repo.stage_and_commit("commit.md")?;
1091
1092 git_repo.create_branch("vnext")?;
1093 git_repo.checkout("vnext")?;
1094 std::fs::write(git_repo.dir.join("vnext.md"), "some content")?;
1095 let vnext_commit_id = git_repo.stage_and_commit("vnext.md")?;
1096
1097 let events = vec![
1098 generate_test_key_1_metadata_event("fred"),
1099 generate_test_key_1_relay_list_event(),
1100 generate_repo_ref_event_with_git_server(vec![
1101 source_git_repo.dir.to_str().unwrap().to_string(),
1102 ]),
1103 ];
1104 // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57)
1105 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
1106 Relay::new(8051, None, None),
1107 Relay::new(8052, None, None),
1108 Relay::new(8053, None, None),
1109 Relay::new(8055, None, None),
1110 Relay::new(8056, None, None),
1111 Relay::new(8057, None, None),
1112 );
1113 r51.events = events.clone();
1114 r55.events = events;
1115
1116 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
1117 let mut p =
1118 cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?;
1119 p.send_line("push refs/heads/main:refs/heads/main")?;
1120 p.send_line("push refs/heads/vnext:refs/heads/vnext")?;
1121 p.send_line("")?;
1122 p.expect_eventually_and_print("\r\n\r\n")?;
1123 p.exit()?;
1124 for p in [51, 52, 53, 55, 56, 57] {
1125 relay::shutdown_relay(8000 + p)?;
1126 }
1127 Ok(())
1128 });
1129 // launch relays
1130 let _ = join!(
1131 r51.listen_until_close(),
1132 r52.listen_until_close(),
1133 r53.listen_until_close(),
1134 r55.listen_until_close(),
1135 r56.listen_until_close(),
1136 r57.listen_until_close(),
1137 );
1138 cli_tester_handle.join().unwrap()?;
1139
1140 let state_event = r56
1141 .events
1142 .iter()
1143 .find(|e| e.kind().eq(&STATE_KIND))
1144 .context("state event not created")?;
1145
1146 assert_eq!(
1147 state_event.identifier(),
1148 generate_repo_ref_event().identifier(),
1149 );
1150 // println!("{:#?}", state_event);
1151 assert_eq!(
1152 state_event
1153 .tags
1154 .iter()
1155 .filter(|t| t.kind().to_string().as_str().ne("d"))
1156 .map(|t| t.as_vec().to_vec())
1157 .collect::<HashSet<Vec<String>>>(),
1158 HashSet::from([
1159 vec!["HEAD".to_string(), "ref: refs/heads/main".to_string()],
1160 vec!["refs/heads/main".to_string(), main_commit_id.to_string()],
1161 vec!["refs/heads/vnext".to_string(), vnext_commit_id.to_string()],
1162 ]),
1163 );
1164 Ok(())
1165 }
1166
1167 #[tokio::test]
1168 #[serial]
1169 async fn existing_state_event_published_in_nostr_state_event() -> Result<()> {
1170 let (state_event, source_git_repo) = generate_repo_with_state_event().await?;
1171
1172 let git_repo = prep_git_repo()?;
1173 let example_branch_commit_id = git_repo.get_tip_of_local_branch("main")?.to_string(); // same as example
1174
1175 std::fs::write(git_repo.dir.join("new.md"), "some content")?;
1176 let main_commit_id = git_repo.stage_and_commit("new.md")?;
1177 git_repo.create_branch("vnext")?;
1178 git_repo.checkout("vnext")?;
1179 std::fs::write(git_repo.dir.join("more.md"), "some content")?;
1180 let vnext_commit_id = git_repo.stage_and_commit("more.md")?;
1181
1182 let events = vec![
1183 generate_test_key_1_metadata_event("fred"),
1184 generate_test_key_1_relay_list_event(),
1185 generate_repo_ref_event_with_git_server(vec![
1186 source_git_repo.dir.to_str().unwrap().to_string(),
1187 ]),
1188 state_event.clone(),
1189 ];
1190
1191 // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57)
1192 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
1193 Relay::new(8051, None, None),
1194 Relay::new(8052, None, None),
1195 Relay::new(8053, None, None),
1196 Relay::new(8055, None, None),
1197 Relay::new(8056, None, None),
1198 Relay::new(8057, None, None),
1199 );
1200 r51.events = events.clone();
1201 r55.events = events;
1202
1203 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
1204 let mut p =
1205 cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?;
1206 p.send_line("push refs/heads/main:refs/heads/main")?;
1207 p.send_line("push refs/heads/vnext:refs/heads/vnext")?;
1208 p.send_line("")?;
1209 p.expect_eventually_and_print("\r\n\r\n")?;
1210 p.exit()?;
1211 for p in [51, 52, 53, 55, 56, 57] {
1212 relay::shutdown_relay(8000 + p)?;
1213 }
1214 // local refs updated
1215 assert_eq!(
1216 git_repo
1217 .git_repo
1218 .find_reference("refs/remotes/nostr/main")?
1219 .peel_to_commit()?
1220 .id(),
1221 main_commit_id,
1222 );
1223
1224 assert_eq!(
1225 git_repo
1226 .git_repo
1227 .find_reference("refs/remotes/nostr/vnext")?
1228 .peel_to_commit()?
1229 .id(),
1230 vnext_commit_id
1231 );
1232 Ok(())
1233 });
1234 // launch relays
1235 let _ = join!(
1236 r51.listen_until_close(),
1237 r52.listen_until_close(),
1238 r53.listen_until_close(),
1239 r55.listen_until_close(),
1240 r56.listen_until_close(),
1241 r57.listen_until_close(),
1242 );
1243
1244 cli_tester_handle.join().unwrap()?;
1245
1246 // git_server updated
1247 assert_eq!(
1248 source_git_repo.get_tip_of_local_branch("main")?,
1249 main_commit_id
1250 );
1251
1252 assert_eq!(
1253 source_git_repo.get_tip_of_local_branch("vnext")?,
1254 vnext_commit_id
1255 );
1256
1257 // state annoucement updated
1258 let state_event = r56
1259 .events
1260 .iter()
1261 .find(|e| e.kind().eq(&STATE_KIND))
1262 .context("state event not created")?;
1263
1264 // println!("{:#?}", state_event);
1265 assert_eq!(
1266 state_event
1267 .tags
1268 .iter()
1269 .filter(|t| t.kind().to_string().as_str().ne("d"))
1270 .map(|t| t.as_vec().to_vec())
1271 .collect::<HashSet<Vec<String>>>(),
1272 HashSet::from([
1273 vec!["HEAD".to_string(), "ref: refs/heads/main".to_string()],
1274 vec!["refs/heads/main".to_string(), main_commit_id.to_string()],
1275 vec![
1276 "refs/heads/example-branch".to_string(),
1277 example_branch_commit_id.to_string()
1278 ],
1279 vec!["refs/heads/vnext".to_string(), vnext_commit_id.to_string()],
1280 ]),
1281 );
1282 Ok(())
1283 }
1284 }
1285 mod delete_one_branch {
1286
1287 use super::*;
1288
1289 #[tokio::test]
1290 #[serial]
1291 async fn deletes_branch_on_git_server() -> Result<()> {
1292 let git_repo = prep_git_repo()?;
1293
1294 git_repo.create_branch("vnext")?;
1295 git_repo.checkout("vnext")?;
1296 std::fs::write(git_repo.dir.join("vnext.md"), "some content")?;
1297 let vnext_commit_id = git_repo.stage_and_commit("vnext.md")?;
1298
1299 let source_git_repo = GitTestRepo::recreate_as_bare(&git_repo)?;
1300
1301 let events = vec![
1302 generate_test_key_1_metadata_event("fred"),
1303 generate_test_key_1_relay_list_event(),
1304 generate_repo_ref_event_with_git_server(vec![
1305 source_git_repo.dir.to_str().unwrap().to_string(),
1306 ]),
1307 ];
1308 // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57)
1309 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
1310 Relay::new(8051, None, None),
1311 Relay::new(8052, None, None),
1312 Relay::new(8053, None, None),
1313 Relay::new(8055, None, None),
1314 Relay::new(8056, None, None),
1315 Relay::new(8057, None, None),
1316 );
1317 r51.events = events.clone();
1318 r55.events = events;
1319
1320 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
1321 assert_eq!(
1322 source_git_repo
1323 .git_repo
1324 .find_reference("refs/heads/vnext")?
1325 .peel_to_commit()?
1326 .id(),
1327 vnext_commit_id
1328 );
1329
1330 let mut p =
1331 cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?;
1332 p.send_line("push :refs/heads/vnext")?;
1333 p.send_line("")?;
1334 p.expect_eventually_and_print("\r\n\r\n")?;
1335 p.exit()?;
1336 for p in [51, 52, 53, 55, 56, 57] {
1337 relay::shutdown_relay(8000 + p)?;
1338 }
1339
1340 assert!(
1341 source_git_repo
1342 .git_repo
1343 .find_reference("refs/heads/vnext")
1344 .is_err()
1345 );
1346 Ok(())
1347 });
1348 // launch relays
1349 let _ = join!(
1350 r51.listen_until_close(),
1351 r52.listen_until_close(),
1352 r53.listen_until_close(),
1353 r55.listen_until_close(),
1354 r56.listen_until_close(),
1355 r57.listen_until_close(),
1356 );
1357 cli_tester_handle.join().unwrap()?;
1358 Ok(())
1359 }
1360
1361 #[tokio::test]
1362 #[serial]
1363 async fn remote_refs_updated_in_local_git() -> Result<()> {
1364 let git_repo = prep_git_repo()?;
1365
1366 git_repo.create_branch("vnext")?;
1367 git_repo.checkout("vnext")?;
1368 std::fs::write(git_repo.dir.join("vnext.md"), "some content")?;
1369 let vnext_commit_id = git_repo.stage_and_commit("vnext.md")?;
1370
1371 let source_git_repo = GitTestRepo::recreate_as_bare(&git_repo)?;
1372
1373 git_repo
1374 .git_repo
1375 .reference("refs/remotes/nostr/vnext", vnext_commit_id, true, "")?;
1376
1377 let events = vec![
1378 generate_test_key_1_metadata_event("fred"),
1379 generate_test_key_1_relay_list_event(),
1380 generate_repo_ref_event_with_git_server(vec![
1381 source_git_repo.dir.to_str().unwrap().to_string(),
1382 ]),
1383 ];
1384 // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57)
1385 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
1386 Relay::new(8051, None, None),
1387 Relay::new(8052, None, None),
1388 Relay::new(8053, None, None),
1389 Relay::new(8055, None, None),
1390 Relay::new(8056, None, None),
1391 Relay::new(8057, None, None),
1392 );
1393 r51.events = events.clone();
1394 r55.events = events;
1395
1396 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
1397 assert_eq!(
1398 git_repo
1399 .git_repo
1400 .find_reference("refs/remotes/nostr/vnext")?
1401 .peel_to_commit()?
1402 .id(),
1403 vnext_commit_id
1404 );
1405
1406 let mut p =
1407 cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?;
1408 p.send_line("push :refs/heads/vnext")?;
1409 p.send_line("")?;
1410 p.expect_eventually("\r\n\r\n")?;
1411 p.exit()?;
1412 for p in [51, 52, 53, 55, 56, 57] {
1413 relay::shutdown_relay(8000 + p)?;
1414 }
1415 assert!(
1416 git_repo
1417 .git_repo
1418 .find_reference("refs/remotes/nostr/vnext")
1419 .is_err()
1420 );
1421 Ok(())
1422 });
1423 // launch relays
1424 let _ = join!(
1425 r51.listen_until_close(),
1426 r52.listen_until_close(),
1427 r53.listen_until_close(),
1428 r55.listen_until_close(),
1429 r56.listen_until_close(),
1430 r57.listen_until_close(),
1431 );
1432 cli_tester_handle.join().unwrap()?;
1433 Ok(())
1434 }
1435
1436 #[tokio::test]
1437 #[serial]
1438 async fn prints_git_helper_ok_respose() -> Result<()> {
1439 let git_repo = prep_git_repo()?;
1440
1441 git_repo.create_branch("vnext")?;
1442 git_repo.checkout("vnext")?;
1443 std::fs::write(git_repo.dir.join("vnext.md"), "some content")?;
1444 let vnext_commit_id = git_repo.stage_and_commit("vnext.md")?;
1445
1446 let source_git_repo = GitTestRepo::recreate_as_bare(&git_repo)?;
1447
1448 git_repo
1449 .git_repo
1450 .reference("refs/remotes/nostr/vnext", vnext_commit_id, true, "")?;
1451
1452 let events = vec![
1453 generate_test_key_1_metadata_event("fred"),
1454 generate_test_key_1_relay_list_event(),
1455 generate_repo_ref_event_with_git_server(vec![
1456 source_git_repo.dir.to_str().unwrap().to_string(),
1457 ]),
1458 ];
1459 // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57)
1460 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
1461 Relay::new(8051, None, None),
1462 Relay::new(8052, None, None),
1463 Relay::new(8053, None, None),
1464 Relay::new(8055, None, None),
1465 Relay::new(8056, None, None),
1466 Relay::new(8057, None, None),
1467 );
1468 r51.events = events.clone();
1469 r55.events = events;
1470
1471 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
1472 let mut p =
1473 cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?;
1474 p.send_line("push :refs/heads/vnext")?;
1475 p.send_line("")?;
1476 // let res = p.expect_eventually("\r\n\r\n")?;
1477 // println!("{res}");
1478 p.expect("ok refs/heads/vnext\r\n")?;
1479 p.expect("\r\n")?;
1480 p.exit()?;
1481 for p in [51, 52, 53, 55, 56, 57] {
1482 relay::shutdown_relay(8000 + p)?;
1483 }
1484 Ok(())
1485 });
1486 // launch relays
1487 let _ = join!(
1488 r51.listen_until_close(),
1489 r52.listen_until_close(),
1490 r53.listen_until_close(),
1491 r55.listen_until_close(),
1492 r56.listen_until_close(),
1493 r57.listen_until_close(),
1494 );
1495 cli_tester_handle.join().unwrap()?;
1496 Ok(())
1497 }
1498
1499 mod when_existing_state_event {
1500 use super::*;
1501
1502 #[tokio::test]
1503 #[serial]
1504 async fn state_event_updated_and_branch_deleted_and_ok_printed() -> Result<()> {
1505 let (state_event, source_git_repo) = generate_repo_with_state_event().await?;
1506
1507 let git_repo = prep_git_repo()?;
1508 let main_commit_id = git_repo.get_tip_of_local_branch("main")?.to_string(); // same as example
1509
1510 let events = vec![
1511 generate_test_key_1_metadata_event("fred"),
1512 generate_test_key_1_relay_list_event(),
1513 generate_repo_ref_event_with_git_server(vec![
1514 source_git_repo.dir.to_str().unwrap().to_string(),
1515 ]),
1516 state_event.clone(),
1517 ];
1518
1519 // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57)
1520 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
1521 Relay::new(8051, None, None),
1522 Relay::new(8052, None, None),
1523 Relay::new(8053, None, None),
1524 Relay::new(8055, None, None),
1525 Relay::new(8056, None, None),
1526 Relay::new(8057, None, None),
1527 );
1528 r51.events = events.clone();
1529 r55.events = events;
1530
1531 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
1532 let mut p =
1533 cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?;
1534 p.send_line("push :refs/heads/example-branch")?;
1535 p.send_line("")?;
1536 p.expect("ok refs/heads/example-branch\r\n")?;
1537 p.expect("\r\n")?;
1538 p.exit()?;
1539 for p in [51, 52, 53, 55, 56, 57] {
1540 relay::shutdown_relay(8000 + p)?;
1541 }
1542 Ok(())
1543 });
1544 // launch relays
1545 let _ = join!(
1546 r51.listen_until_close(),
1547 r52.listen_until_close(),
1548 r53.listen_until_close(),
1549 r55.listen_until_close(),
1550 r56.listen_until_close(),
1551 r57.listen_until_close(),
1552 );
1553
1554 cli_tester_handle.join().unwrap()?;
1555
1556 let state_event = r56
1557 .events
1558 .iter()
1559 .find(|e| e.kind().eq(&STATE_KIND))
1560 .context("state event not created")?;
1561
1562 // println!("{:#?}", state_event);
1563 assert_eq!(
1564 state_event
1565 .tags
1566 .iter()
1567 .filter(|t| t.kind().to_string().as_str().ne("d"))
1568 .map(|t| t.as_vec().to_vec())
1569 .collect::<HashSet<Vec<String>>>(),
1570 HashSet::from([
1571 vec!["HEAD".to_string(), "ref: refs/heads/main".to_string()],
1572 vec!["refs/heads/main".to_string(), main_commit_id.to_string()],
1573 ]),
1574 );
1575 Ok(())
1576 }
1577
1578 mod already_deleted_on_git_server {
1579 use super::*;
1580
1581 #[tokio::test]
1582 #[serial]
1583 async fn existing_state_event_updated_and_ok_printed() -> Result<()> {
1584 let (state_event, source_git_repo) = generate_repo_with_state_event().await?;
1585
1586 {
1587 // delete branch on git server
1588 let tmp_repo = GitTestRepo::clone_repo(&source_git_repo)?;
1589 let mut remote = tmp_repo.git_repo.find_remote("origin")?;
1590 remote.push(&[":refs/heads/example-branch"], None)?;
1591 }
1592
1593 let git_repo = prep_git_repo()?;
1594 let main_commit_id = git_repo.get_tip_of_local_branch("main")?.to_string(); // same as example
1595
1596 let events = vec![
1597 generate_test_key_1_metadata_event("fred"),
1598 generate_test_key_1_relay_list_event(),
1599 generate_repo_ref_event_with_git_server(vec![
1600 source_git_repo.dir.to_str().unwrap().to_string(),
1601 ]),
1602 state_event.clone(),
1603 ];
1604
1605 // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57)
1606 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
1607 Relay::new(8051, None, None),
1608 Relay::new(8052, None, None),
1609 Relay::new(8053, None, None),
1610 Relay::new(8055, None, None),
1611 Relay::new(8056, None, None),
1612 Relay::new(8057, None, None),
1613 );
1614 r51.events = events.clone();
1615 r55.events = events;
1616
1617 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
1618 let mut p = cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(
1619 &git_repo,
1620 )?;
1621 p.send_line("push :refs/heads/example-branch")?;
1622 p.send_line("")?;
1623 p.expect("ok refs/heads/example-branch\r\n")?;
1624 p.expect("\r\n")?;
1625 p.exit()?;
1626 for p in [51, 52, 53, 55, 56, 57] {
1627 relay::shutdown_relay(8000 + p)?;
1628 }
1629 Ok(())
1630 });
1631 // launch relays
1632 let _ = join!(
1633 r51.listen_until_close(),
1634 r52.listen_until_close(),
1635 r53.listen_until_close(),
1636 r55.listen_until_close(),
1637 r56.listen_until_close(),
1638 r57.listen_until_close(),
1639 );
1640
1641 cli_tester_handle.join().unwrap()?;
1642
1643 let state_event = r56
1644 .events
1645 .iter()
1646 .find(|e| e.kind().eq(&STATE_KIND))
1647 .context("state event not created")?;
1648
1649 // println!("{:#?}", state_event);
1650 assert_eq!(
1651 state_event
1652 .tags
1653 .iter()
1654 .filter(|t| t.kind().to_string().as_str().ne("d"))
1655 .map(|t| t.as_vec().to_vec())
1656 .collect::<HashSet<Vec<String>>>(),
1657 HashSet::from([
1658 vec!["HEAD".to_string(), "ref: refs/heads/main".to_string()],
1659 vec!["refs/heads/main".to_string(), main_commit_id.to_string()],
1660 ]),
1661 );
1662 Ok(())
1663 }
1664 }
1665 }
1666 }
1667
1668 #[tokio::test]
1669 #[serial]
1670 async fn pushes_to_all_git_servers_listed_and_ok_printed() -> Result<()> {
1671 let (state_event, source_git_repo) = generate_repo_with_state_event().await?;
1672 let second_source_git_repo = GitTestRepo::duplicate(&source_git_repo)?;
1673
1674 // uppdate announcement with extra git server
1675
1676 let git_repo = prep_git_repo()?;
1677
1678 std::fs::write(git_repo.dir.join("new.md"), "some content")?;
1679 let main_commit_id = git_repo.stage_and_commit("new.md")?;
1680
1681 let events = vec![
1682 generate_test_key_1_metadata_event("fred"),
1683 generate_test_key_1_relay_list_event(),
1684 generate_repo_ref_event_with_git_server(vec![
1685 source_git_repo.dir.to_str().unwrap().to_string(),
1686 second_source_git_repo.dir.to_str().unwrap().to_string(),
1687 ]),
1688 state_event.clone(),
1689 ];
1690
1691 // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57)
1692 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
1693 Relay::new(8051, None, None),
1694 Relay::new(8052, None, None),
1695 Relay::new(8053, None, None),
1696 Relay::new(8055, None, None),
1697 Relay::new(8056, None, None),
1698 Relay::new(8057, None, None),
1699 );
1700 r51.events = events.clone();
1701 r55.events = events;
1702
1703 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
1704 let mut p = cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?;
1705 p.send_line("push refs/heads/main:refs/heads/main")?;
1706 p.send_line("")?;
1707 p.expect("ok refs/heads/main\r\n")?;
1708 p.expect("\r\n")?;
1709 p.exit()?;
1710 for p in [51, 52, 53, 55, 56, 57] {
1711 relay::shutdown_relay(8000 + p)?;
1712 }
1713 Ok(())
1714 });
1715 // launch relays
1716 let _ = join!(
1717 r51.listen_until_close(),
1718 r52.listen_until_close(),
1719 r53.listen_until_close(),
1720 r55.listen_until_close(),
1721 r56.listen_until_close(),
1722 r57.listen_until_close(),
1723 );
1724
1725 cli_tester_handle.join().unwrap()?;
1726
1727 // git_server updated
1728 assert_eq!(
1729 source_git_repo.get_tip_of_local_branch("main")?,
1730 main_commit_id
1731 );
1732 assert_eq!(
1733 second_source_git_repo.get_tip_of_local_branch("main")?,
1734 main_commit_id
1735 );
1736
1737 Ok(())
1738 }
1739
1740 #[tokio::test]
1741 #[serial]
1742 async fn proposal_merge_commit_pushed_to_main_leads_to_status_event_issued() -> Result<()> {
1743 //
1744 let (events, source_git_repo) = prep_source_repo_and_events_including_proposals().await?;
1745 let source_path = source_git_repo.dir.to_str().unwrap().to_string();
1746
1747 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
1748 Relay::new(8051, None, None),
1749 Relay::new(8052, None, None),
1750 Relay::new(8053, None, None),
1751 Relay::new(8055, None, None),
1752 Relay::new(8056, None, None),
1753 Relay::new(8057, None, None),
1754 );
1755 r51.events = events.clone();
1756 r55.events = events.clone();
1757
1758 #[allow(clippy::mutable_key_type)]
1759 let before = r55.events.iter().cloned().collect::<HashSet<Event>>();
1760
1761 let cli_tester_handle = std::thread::spawn(move || -> Result<(String, Oid)> {
1762 let branch_name = get_proposal_branch_name_from_events(&events, FEATURE_BRANCH_NAME_1)?;
1763
1764 let git_repo = clone_git_repo_with_nostr_url()?;
1765 git_repo.checkout_remote_branch(&branch_name)?;
1766 git_repo.checkout("refs/heads/main")?;
1767
1768 std::fs::write(git_repo.dir.join("new.md"), "some content")?;
1769 git_repo.stage_and_commit("new.md")?;
1770
1771 CliTester::new_git_with_remote_helper_from_dir(
1772 &git_repo.dir,
1773 ["merge", &branch_name, "-m", "proposal merge commit message"],
1774 )
1775 .expect_end_eventually_and_print()?;
1776
1777 let oid = git_repo.get_tip_of_local_branch("main")?;
1778
1779 let mut p = CliTester::new_git_with_remote_helper_from_dir(&git_repo.dir, ["push"]);
1780 cli_expect_nostr_fetch(&mut p)?;
1781 p.expect(
1782 format!(
1783 "fetching ref list over filesystem from {}...\r\n",
1784 source_path
1785 )
1786 .as_str(),
1787 )?;
1788
1789 p.expect("merge commit ")?;
1790 // shorthand merge commit id appears in this gap
1791 p.expect_eventually(": create nostr proposal status event\r\n")?;
1792 p.expect(format!("To {}\r\n", get_nostr_remote_url()?).as_str())?;
1793 let output = p.expect_end_eventually()?;
1794
1795 for p in [51, 52, 53, 55, 56, 57] {
1796 relay::shutdown_relay(8000 + p)?;
1797 }
1798
1799 Ok((output, oid))
1800 });
1801 // launch relays
1802 let _ = join!(
1803 r51.listen_until_close(),
1804 r52.listen_until_close(),
1805 r53.listen_until_close(),
1806 r55.listen_until_close(),
1807 r56.listen_until_close(),
1808 r57.listen_until_close(),
1809 );
1810
1811 let (output, oid) = cli_tester_handle.join().unwrap()?;
1812
1813 assert_eq!(
1814 output,
1815 format!(" 431b84e..{} main -> main\r\n", &oid.to_string()[..7])
1816 );
1817
1818 let new_events = r55
1819 .events
1820 .iter()
1821 .cloned()
1822 .collect::<HashSet<Event>>()
1823 .difference(&before)
1824 .cloned()
1825 .collect::<Vec<Event>>();
1826
1827 assert_eq!(new_events.len(), 2, "{new_events:?}");
1828
1829 let proposal = r55
1830 .events
1831 .iter()
1832 .find(|e| {
1833 e.tags()
1834 .iter()
1835 .find(|t| t.as_vec()[0].eq("branch-name"))
1836 .is_some_and(|t| t.as_vec()[1].eq(FEATURE_BRANCH_NAME_1))
1837 })
1838 .unwrap();
1839
1840 let merge_status = new_events
1841 .iter()
1842 .find(|e| e.kind().eq(&Kind::GitStatusApplied))
1843 .unwrap();
1844
1845 assert_eq!(
1846 oid.to_string(),
1847 merge_status
1848 .tags
1849 .iter()
1850 .find(|t| t.as_vec()[0].eq("merge-commit-id"))
1851 .unwrap()
1852 .as_vec()[1],
1853 "status sets correct merge-commit-id tag"
1854 );
1855
1856 let proposal_tip = r55
1857 .events
1858 .iter()
1859 .filter(|e| {
1860 e.tags()
1861 .iter()
1862 .any(|t| t.as_vec()[1].eq(&proposal.id().to_string()))
1863 && e.kind().eq(&Kind::GitPatch)
1864 })
1865 .last()
1866 .unwrap();
1867
1868 assert_eq!(
1869 proposal_tip.id().to_string(),
1870 merge_status
1871 .tags
1872 .iter()
1873 .find(|t| t.as_vec().len().eq(&4) && t.as_vec()[3].eq("mention"))
1874 .unwrap()
1875 .as_vec()[1],
1876 "status mentions proposal tip event \r\nmerge status:\r\n{}\r\nproposal tip:\r\n{}",
1877 merge_status.as_json(),
1878 proposal_tip.as_json(),
1879 );
1880
1881 assert_eq!(
1882 proposal.id().to_string(),
1883 merge_status
1884 .tags
1885 .iter()
1886 .find(|t| t.is_root())
1887 .unwrap()
1888 .as_vec()[1],
1889 "status tags proposal id as root \r\nmerge status:\r\n{}\r\nproposal:\r\n{}",
1890 merge_status.as_json(),
1891 proposal.as_json(),
1892 );
1893
1894 Ok(())
1895 }
1896
1897 #[tokio::test]
1898 #[serial]
1899 async fn push_2_commits_to_existing_proposal() -> Result<()> {
1900 let (events, source_git_repo) = prep_source_repo_and_events_including_proposals().await?;
1901 let source_path = source_git_repo.dir.to_str().unwrap().to_string();
1902
1903 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
1904 Relay::new(8051, None, None),
1905 Relay::new(8052, None, None),
1906 Relay::new(8053, None, None),
1907 Relay::new(8055, None, None),
1908 Relay::new(8056, None, None),
1909 Relay::new(8057, None, None),
1910 );
1911 r51.events = events.clone();
1912 r55.events = events.clone();
1913
1914 #[allow(clippy::mutable_key_type)]
1915 let before = r55.events.iter().cloned().collect::<HashSet<Event>>();
1916
1917 let cli_tester_handle = std::thread::spawn(move || -> Result<(String, String)> {
1918 let branch_name = get_proposal_branch_name_from_events(&events, FEATURE_BRANCH_NAME_1)?;
1919
1920 let git_repo = clone_git_repo_with_nostr_url()?;
1921 git_repo.checkout_remote_branch(&branch_name)?;
1922
1923 std::fs::write(git_repo.dir.join("new.md"), "some content")?;
1924 git_repo.stage_and_commit("new.md")?;
1925
1926 std::fs::write(git_repo.dir.join("new2.md"), "some content")?;
1927 git_repo.stage_and_commit("new2.md")?;
1928
1929 let mut p = CliTester::new_git_with_remote_helper_from_dir(&git_repo.dir, ["push"]);
1930 cli_expect_nostr_fetch(&mut p)?;
1931 p.expect(
1932 format!(
1933 "fetching ref list over filesystem from {}...\r\n",
1934 source_path
1935 )
1936 .as_str(),
1937 )?;
1938 p.expect(format!("To {}\r\n", get_nostr_remote_url()?).as_str())?;
1939 let output = p.expect_end_eventually()?;
1940
1941 for p in [51, 52, 53, 55, 56, 57] {
1942 relay::shutdown_relay(8000 + p)?;
1943 }
1944
1945 Ok((output, branch_name))
1946 });
1947 // launch relays
1948 let _ = join!(
1949 r51.listen_until_close(),
1950 r52.listen_until_close(),
1951 r53.listen_until_close(),
1952 r55.listen_until_close(),
1953 r56.listen_until_close(),
1954 r57.listen_until_close(),
1955 );
1956
1957 let (output, branch_name) = cli_tester_handle.join().unwrap()?;
1958
1959 assert_eq!(
1960 output,
1961 format!(" eb5d678..7de5e41 {branch_name} -> {branch_name}\r\n").as_str(),
1962 );
1963
1964 let new_events = r55
1965 .events
1966 .iter()
1967 .cloned()
1968 .collect::<HashSet<Event>>()
1969 .difference(&before)
1970 .cloned()
1971 .collect::<Vec<Event>>();
1972 assert_eq!(new_events.len(), 2);
1973 let first_new_patch = new_events
1974 .iter()
1975 .find(|e| e.content.contains("new.md"))
1976 .unwrap();
1977 let second_new_patch = new_events
1978 .iter()
1979 .find(|e| e.content.contains("new2.md"))
1980 .unwrap();
1981 assert!(
1982 first_new_patch.content.contains("[PATCH 3/4]"),
1983 "first patch labeled with [PATCH 3/4]"
1984 );
1985 assert!(
1986 second_new_patch.content.contains("[PATCH 4/4]"),
1987 "second patch labeled with [PATCH 4/4]"
1988 );
1989
1990 let proposal = r55
1991 .events
1992 .iter()
1993 .find(|e| {
1994 e.tags()
1995 .iter()
1996 .find(|t| t.as_vec()[0].eq("branch-name"))
1997 .is_some_and(|t| t.as_vec()[1].eq(FEATURE_BRANCH_NAME_1))
1998 })
1999 .unwrap();
2000
2001 assert_eq!(
2002 proposal.id().to_string(),
2003 first_new_patch
2004 .tags
2005 .iter()
2006 .find(|t| t.is_root())
2007 .unwrap()
2008 .as_vec()[1],
2009 "first patch sets proposal id as root"
2010 );
2011
2012 assert_eq!(
2013 first_new_patch.id().to_string(),
2014 second_new_patch
2015 .tags
2016 .iter()
2017 .find(|t| t.is_reply())
2018 .unwrap()
2019 .as_vec()[1],
2020 "second new patch replies to the first new patch"
2021 );
2022
2023 let previous_proposal_tip_event = r55
2024 .events
2025 .iter()
2026 .find(|e| {
2027 e.tags()
2028 .iter()
2029 .any(|t| t.as_vec()[1].eq(&proposal.id().to_string()))
2030 && e.content.contains("[PATCH 2/2]")
2031 })
2032 .unwrap();
2033
2034 assert_eq!(
2035 previous_proposal_tip_event.id().to_string(),
2036 first_new_patch
2037 .tags
2038 .iter()
2039 .find(|t| t.is_reply())
2040 .unwrap()
2041 .as_vec()[1],
2042 "first patch replies to the previous tip of proposal"
2043 );
2044
2045 Ok(())
2046 }
2047
2048 #[tokio::test]
2049 #[serial]
2050 async fn force_push_creates_proposal_revision() -> Result<()> {
2051 let (events, source_git_repo) = prep_source_repo_and_events_including_proposals().await?;
2052 let source_path = source_git_repo.dir.to_str().unwrap().to_string();
2053
2054 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
2055 Relay::new(8051, None, None),
2056 Relay::new(8052, None, None),
2057 Relay::new(8053, None, None),
2058 Relay::new(8055, None, None),
2059 Relay::new(8056, None, None),
2060 Relay::new(8057, None, None),
2061 );
2062 r51.events = events.clone();
2063 r55.events = events.clone();
2064
2065 #[allow(clippy::mutable_key_type)]
2066 let before = r55.events.iter().cloned().collect::<HashSet<Event>>();
2067
2068 let cli_tester_handle = std::thread::spawn(move || -> Result<(String, String)> {
2069 let branch_name = get_proposal_branch_name_from_events(&events, FEATURE_BRANCH_NAME_1)?;
2070
2071 let git_repo = clone_git_repo_with_nostr_url()?;
2072 let oid = git_repo.checkout_remote_branch(&branch_name)?;
2073 // remove last commit
2074 git_repo.checkout("main")?;
2075 git_repo.git_repo.branch(
2076 &branch_name,
2077 &git_repo.git_repo.find_commit(oid)?.parent(0)?,
2078 true,
2079 )?;
2080 git_repo.checkout(&branch_name)?;
2081
2082 std::fs::write(git_repo.dir.join("new.md"), "some content")?;
2083 git_repo.stage_and_commit("new.md")?;
2084
2085 std::fs::write(git_repo.dir.join("new2.md"), "some content")?;
2086 git_repo.stage_and_commit("new2.md")?;
2087
2088 let mut p =
2089 CliTester::new_git_with_remote_helper_from_dir(&git_repo.dir, ["push", "--force"]);
2090 cli_expect_nostr_fetch(&mut p)?;
2091 p.expect(
2092 format!(
2093 "fetching ref list over filesystem from {}...\r\n",
2094 source_path
2095 )
2096 .as_str(),
2097 )?;
2098 p.expect(format!("To {}\r\n", get_nostr_remote_url()?).as_str())?;
2099 let output = p.expect_end_eventually()?;
2100
2101 for p in [51, 52, 53, 55, 56, 57] {
2102 relay::shutdown_relay(8000 + p)?;
2103 }
2104
2105 Ok((output, branch_name))
2106 });
2107 // launch relays
2108 let _ = join!(
2109 r51.listen_until_close(),
2110 r52.listen_until_close(),
2111 r53.listen_until_close(),
2112 r55.listen_until_close(),
2113 r56.listen_until_close(),
2114 r57.listen_until_close(),
2115 );
2116
2117 let (output, branch_name) = cli_tester_handle.join().unwrap()?;
2118
2119 assert_eq!(
2120 output,
2121 format!(" + eb5d678...8a296c8 {branch_name} -> {branch_name} (forced update)\r\n")
2122 .as_str(),
2123 );
2124
2125 let new_events = r55
2126 .events
2127 .iter()
2128 .cloned()
2129 .collect::<HashSet<Event>>()
2130 .difference(&before)
2131 .cloned()
2132 .collect::<Vec<Event>>();
2133 assert_eq!(new_events.len(), 3);
2134
2135 let proposal = r55
2136 .events
2137 .iter()
2138 .find(|e| {
2139 e.tags()
2140 .iter()
2141 .find(|t| t.as_vec()[0].eq("branch-name"))
2142 .is_some_and(|t| t.as_vec()[1].eq(FEATURE_BRANCH_NAME_1))
2143 })
2144 .unwrap();
2145
2146 let revision_root_patch = new_events
2147 .iter()
2148 .find(|e| e.tags().iter().any(|t| t.as_vec()[1].eq("revision-root")))
2149 .unwrap();
2150
2151 assert_eq!(
2152 proposal.id().to_string(),
2153 revision_root_patch
2154 .tags
2155 .iter()
2156 .find(|t| t.is_reply())
2157 .unwrap()
2158 .as_vec()[1],
2159 "revision root patch replies to original proposal"
2160 );
2161
2162 assert!(
2163 revision_root_patch.content.contains("[PATCH 1/3]"),
2164 "revision root labeled with [PATCH 1/3] event: {revision_root_patch:?}",
2165 );
2166
2167 let second_patch = new_events
2168 .iter()
2169 .find(|e| e.content.contains("new.md"))
2170 .unwrap();
2171 let third_patch = new_events
2172 .iter()
2173 .find(|e| e.content.contains("new2.md"))
2174 .unwrap();
2175 assert!(
2176 second_patch.content.contains("[PATCH 2/3]"),
2177 "second patch labeled with [PATCH 2/3]"
2178 );
2179 assert!(
2180 third_patch.content.contains("[PATCH 3/3]"),
2181 "third patch labeled with [PATCH 3/3]"
2182 );
2183
2184 assert_eq!(
2185 revision_root_patch.id().to_string(),
2186 second_patch
2187 .tags
2188 .iter()
2189 .find(|t| t.is_root())
2190 .unwrap()
2191 .as_vec()[1],
2192 "second patch sets revision id as root"
2193 );
2194
2195 assert_eq!(
2196 second_patch.id().to_string(),
2197 third_patch
2198 .tags
2199 .iter()
2200 .find(|t| t.is_reply())
2201 .unwrap()
2202 .as_vec()[1],
2203 "third patch replies to the second new patch"
2204 );
2205
2206 Ok(())
2207 }
2208
2209 #[tokio::test]
2210 #[serial]
2211 async fn push_new_pr_branch_creates_proposal() -> Result<()> {
2212 let (events, source_git_repo) = prep_source_repo_and_events_including_proposals().await?;
2213 let source_path = source_git_repo.dir.to_str().unwrap().to_string();
2214
2215 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
2216 Relay::new(8051, None, None),
2217 Relay::new(8052, None, None),
2218 Relay::new(8053, None, None),
2219 Relay::new(8055, None, None),
2220 Relay::new(8056, None, None),
2221 Relay::new(8057, None, None),
2222 );
2223 r51.events = events.clone();
2224 r55.events = events.clone();
2225
2226 #[allow(clippy::mutable_key_type)]
2227 let before = r55.events.iter().cloned().collect::<HashSet<Event>>();
2228 let branch_name = "pr/my-new-proposal";
2229
2230 let cli_tester_handle = std::thread::spawn(move || -> Result<String> {
2231 let mut git_repo = clone_git_repo_with_nostr_url()?;
2232 git_repo.delete_dir_on_drop = false;
2233 git_repo.create_branch(branch_name)?;
2234 git_repo.checkout(branch_name)?;
2235
2236 std::fs::write(git_repo.dir.join("new.md"), "some content")?;
2237 git_repo.stage_and_commit("new.md")?;
2238
2239 std::fs::write(git_repo.dir.join("new2.md"), "some content")?;
2240 git_repo.stage_and_commit("new2.md")?;
2241
2242 let mut p = CliTester::new_git_with_remote_helper_from_dir(
2243 &git_repo.dir,
2244 ["push", "-u", "origin", branch_name],
2245 );
2246 cli_expect_nostr_fetch(&mut p)?;
2247 p.expect(
2248 format!(
2249 "fetching ref list over filesystem from {}...\r\n",
2250 source_path
2251 )
2252 .as_str(),
2253 )?;
2254 p.expect(format!("To {}\r\n", get_nostr_remote_url()?).as_str())?;
2255 let output = p.expect_end_eventually()?;
2256
2257 for p in [51, 52, 53, 55, 56, 57] {
2258 relay::shutdown_relay(8000 + p)?;
2259 }
2260
2261 Ok(output)
2262 });
2263 // launch relays
2264 let _ = join!(
2265 r51.listen_until_close(),
2266 r52.listen_until_close(),
2267 r53.listen_until_close(),
2268 r55.listen_until_close(),
2269 r56.listen_until_close(),
2270 r57.listen_until_close(),
2271 );
2272
2273 let output = cli_tester_handle.join().unwrap()?;
2274
2275 assert_eq!(
2276 output,
2277 format!(" * [new branch] {branch_name} -> {branch_name}\r\nbranch '{branch_name}' set up to track 'origin/{branch_name}'.\r\n").as_str(),
2278 );
2279
2280 let new_events = r55
2281 .events
2282 .iter()
2283 .cloned()
2284 .collect::<HashSet<Event>>()
2285 .difference(&before)
2286 .cloned()
2287 .collect::<Vec<Event>>();
2288 assert_eq!(new_events.len(), 2);
2289
2290 let proposal = new_events
2291 .iter()
2292 .find(|e| e.tags().iter().any(|t| t.as_vec()[1].eq("root")))
2293 .unwrap();
2294
2295 assert!(
2296 proposal.content.contains("new.md"),
2297 "first patch is proposal root"
2298 );
2299
2300 assert!(
2301 proposal.content.contains("[PATCH 1/2]"),
2302 "proposal root labeled with[PATCH 1/2] event: {proposal:?}",
2303 );
2304
2305 assert_eq!(
2306 proposal
2307 .tags()
2308 .iter()
2309 .find(|t| t.as_vec()[0].eq("branch-name"))
2310 .unwrap()
2311 .as_vec()[1],
2312 branch_name.replace("pr/", ""),
2313 );
2314
2315 let second_patch = new_events
2316 .iter()
2317 .find(|e| e.content.contains("new2.md"))
2318 .unwrap();
2319
2320 assert!(
2321 second_patch.content.contains("[PATCH 2/2]"),
2322 "second patch labeled with [PATCH 2/2]"
2323 );
2324
2325 assert_eq!(
2326 proposal.id().to_string(),
2327 second_patch
2328 .tags
2329 .iter()
2330 .find(|t| t.is_root())
2331 .unwrap()
2332 .as_vec()[1],
2333 "second patch sets proposal id as root"
2334 );
2335
2336 Ok(())
2337 }
2338}