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:
Diffstat (limited to 'tests')
-rw-r--r--tests/ngit_init.rs1419
-rw-r--r--tests/ngit_list.rs34
2 files changed, 1062 insertions, 391 deletions
diff --git a/tests/ngit_init.rs b/tests/ngit_init.rs
index f6b30ef..5483315 100644
--- a/tests/ngit_init.rs
+++ b/tests/ngit_init.rs
@@ -1,77 +1,123 @@
1use anyhow::Result; 1use anyhow::Result;
2use nostr::Event;
2use nostr_sdk::Kind; 3use nostr_sdk::Kind;
3use rstest::*; 4use rstest::*;
4use serial_test::serial; 5use serial_test::serial;
5use test_utils::{git::GitTestRepo, *}; 6use test_utils::{git::GitTestRepo, *};
6 7
7fn expect_msgs_first(p: &mut CliTester) -> Result<()> { 8// ---------------------------------------------------------------------------
8 p.expect("searching for profile...\r\n")?; 9// Helpers
9 p.expect("logged in as fred via cli arguments\r\n")?; 10// ---------------------------------------------------------------------------
10 // // p.expect("searching for existing claims on repository...\r\n")?; 11
11 p.expect("publishing repostory announcement to nostr...\r\n")?; 12/// Extract the GitRepoAnnouncement event from a relay's collected events.
12 Ok(()) 13fn get_announcement(events: &[Event]) -> &Event {
14 events
15 .iter()
16 .find(|e| e.kind.eq(&Kind::GitRepoAnnouncement))
17 .expect("GitRepoAnnouncement event not found")
18}
19
20/// Get the first value of a single-value tag (e.g. "d", "name", "description").
21fn get_tag_value<'a>(event: &'a Event, tag_name: &str) -> &'a str {
22 event
23 .tags
24 .iter()
25 .find(|t| t.as_slice()[0] == tag_name)
26 .map(|t| t.as_slice()[1].as_str())
27 .unwrap_or_else(|| panic!("tag '{tag_name}' not found"))
13} 28}
14 29
15fn get_cli_args() -> Vec<&'static str> { 30/// Get all values of a multi-value tag (e.g. "relays", "web", "maintainers",
16 vec![ 31/// "clone"). Returns slice starting from index 1 (skipping the tag name).
17 "--nsec", 32fn get_tag_values(event: &Event, tag_name: &str) -> Vec<String> {
18 TEST_KEY_1_NSEC, 33 event
19 "--password", 34 .tags
20 TEST_PASSWORD, 35 .iter()
21 "--disable-cli-spinners", 36 .find(|t| t.as_slice()[0] == tag_name)
22 "init", 37 .map(|t| t.as_slice()[1..].iter().map(|s| s.to_string()).collect())
23 "--title", 38 .unwrap_or_default()
24 "example-name",
25 "--identifier",
26 "example-identifier",
27 "--description",
28 "example-description",
29 "--web",
30 "https://exampleproject.xyz",
31 "https://gitworkshop.dev/123",
32 "--relays",
33 "ws://localhost:8055",
34 "ws://localhost:8056",
35 "--clone-url",
36 "https://git.myhosting.com/my-repo.git",
37 "--earliest-unique-commit",
38 "9ee507fc4357d7ee16a5d8901bedcd103f23c17d",
39 "--other-maintainers",
40 TEST_KEY_1_NPUB,
41 ]
42} 39}
43 40
44mod when_repo_not_previously_claimed { 41// ---------------------------------------------------------------------------
42// State A: Fresh (no coordinate)
43// ---------------------------------------------------------------------------
44
45mod state_a_fresh {
45 use super::*; 46 use super::*;
46 47
47 mod when_repo_relays_specified_as_arguments { 48 fn prep_git_repo() -> Result<GitTestRepo> {
48 use futures::join; 49 let test_repo = GitTestRepo::without_repo_in_git_config();
49 use test_utils::relay::Relay; 50 test_repo.populate()?;
51 test_repo.add_remote("origin", "https://localhost:1000")?;
52 Ok(test_repo)
53 }
50 54
55 mod errors {
51 use super::*; 56 use super::*;
52 57
53 fn prep_git_repo() -> Result<GitTestRepo> { 58 #[test]
54 let test_repo = GitTestRepo::without_repo_in_git_config(); 59 #[serial]
55 test_repo.populate()?; 60 fn bare_no_flags() -> Result<()> {
56 test_repo.add_remote("origin", "https://localhost:1000")?; 61 let git_repo = prep_git_repo()?;
57 Ok(test_repo) 62 let args = vec!["--nsec", TEST_KEY_1_NSEC, "--disable-cli-spinners", "init"];
63 let mut p = CliTester::new_from_dir(&git_repo.dir, args);
64 p.expect_eventually("logged in as")?;
65 p.expect_eventually("missing required fields")?;
66 p.expect_eventually("--name <NAME>")?;
67 p.expect_eventually("--grasp-servers")?;
68 Ok(())
58 } 69 }
59 70
60 fn cli_tester_init(git_repo: &GitTestRepo) -> CliTester { 71 #[test]
61 CliTester::new_from_dir(&git_repo.dir, get_cli_args()) 72 #[serial]
73 fn name_only_missing_server_infra() -> Result<()> {
74 let git_repo = prep_git_repo()?;
75 let args = vec![
76 "--nsec",
77 TEST_KEY_1_NSEC,
78 "--disable-cli-spinners",
79 "init",
80 "--name",
81 "My Project",
82 ];
83 let mut p = CliTester::new_from_dir(&git_repo.dir, args);
84 p.expect_eventually("logged in as")?;
85 p.expect_eventually("missing --grasp-servers")?;
86 Ok(())
62 } 87 }
63 88
64 async fn prep_run_init() -> Result<( 89 #[test]
65 Relay<'static>, 90 #[serial]
66 Relay<'static>, 91 fn relays_only_missing_name_and_servers() -> Result<()> {
67 Relay<'static>, 92 let git_repo = prep_git_repo()?;
68 Relay<'static>, 93 let args = vec![
69 Relay<'static>, 94 "--nsec",
70 Relay<'static>, 95 TEST_KEY_1_NSEC,
71 )> { 96 "--disable-cli-spinners",
97 "init",
98 "--relays",
99 "ws://localhost:8055",
100 ];
101 let mut p = CliTester::new_from_dir(&git_repo.dir, args);
102 p.expect_eventually("logged in as")?;
103 p.expect_eventually("missing required fields")?;
104 p.expect_eventually("--name <NAME>")?;
105 p.expect_eventually("--grasp-servers")?;
106 Ok(())
107 }
108 }
109
110 mod success {
111 use futures::join;
112 use test_utils::relay::Relay;
113
114 use super::*;
115
116 async fn run_init_with_grasp_server(
117 extra_args: Vec<&str>,
118 ) -> Result<(nostr::Event, GitTestRepo)> {
72 let git_repo = prep_git_repo()?; 119 let git_repo = prep_git_repo()?;
73 // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57) 120 let (mut r51, mut r52, mut r53, mut r55) = (
74 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
75 Relay::new( 121 Relay::new(
76 8051, 122 8051,
77 None, 123 None,
@@ -90,286 +136,599 @@ mod when_repo_not_previously_claimed {
90 Relay::new(8052, None, None), 136 Relay::new(8052, None, None),
91 Relay::new(8053, None, None), 137 Relay::new(8053, None, None),
92 Relay::new(8055, None, None), 138 Relay::new(8055, None, None),
93 Relay::new(8056, None, None),
94 Relay::new(8057, None, None),
95 ); 139 );
96 140
97 // // check relay had the right number of events 141 let cli_tester_handle = std::thread::spawn({
98 let cli_tester_handle = std::thread::spawn(move || -> Result<()> { 142 let dir = git_repo.dir.clone();
99 let mut p = cli_tester_init(&git_repo); 143 let extra_args_owned: Vec<String> =
100 p.expect_end_eventually()?; 144 extra_args.iter().map(|s| s.to_string()).collect();
101 for p in [51, 52, 53, 55, 56, 57] { 145 move || -> Result<()> {
102 relay::shutdown_relay(8000 + p)?; 146 let mut args =
147 vec!["--nsec", TEST_KEY_1_NSEC, "--disable-cli-spinners", "init"];
148 let extra_refs: Vec<&str> =
149 extra_args_owned.iter().map(|s| s.as_str()).collect();
150 args.extend(extra_refs);
151 let mut p = CliTester::new_from_dir(&dir, args);
152 p.expect_end_eventually()?;
153 for port in [51, 52, 53, 55] {
154 relay::shutdown_relay(8000 + port)?;
155 }
156 Ok(())
103 } 157 }
104 Ok(())
105 }); 158 });
106 159
107 // launch relay
108 let _ = join!( 160 let _ = join!(
109 r51.listen_until_close(), 161 r51.listen_until_close(),
110 r52.listen_until_close(), 162 r52.listen_until_close(),
111 r53.listen_until_close(), 163 r53.listen_until_close(),
112 r55.listen_until_close(), 164 r55.listen_until_close(),
113 r56.listen_until_close(),
114 r57.listen_until_close(),
115 ); 165 );
116 cli_tester_handle.join().unwrap()?; 166 cli_tester_handle.join().unwrap()?;
117 Ok((r51, r52, r53, r55, r56, r57))
118 }
119 167
120 mod sent_to_correct_relays { 168 let event = get_announcement(&r53.events).clone();
169 Ok((event, git_repo))
170 }
121 171
172 mod with_name_and_grasp_server {
122 use super::*; 173 use super::*;
123 174
124 #[derive(Clone)] 175 #[fixture]
125 pub struct SentToCorrectRelaysScenario { 176 async fn scenario() -> (nostr::Event, GitTestRepo) {
126 pub r51_repo_event_count: usize, 177 run_init_with_grasp_server(vec![
127 pub r52_repo_event_count: usize, 178 "--name",
128 pub r53_repo_event_count: usize, 179 "My Project",
129 pub r55_repo_event_count: usize, 180 "--grasp-servers",
130 pub r56_repo_event_count: usize, 181 "ws://localhost:8055",
131 pub r57_repo_event_count: usize, 182 ])
183 .await
184 .expect("init failed")
132 } 185 }
133 186
134 #[fixture] 187 #[rstest]
135 async fn scenario() -> SentToCorrectRelaysScenario { 188 #[tokio::test]
136 let (r51, r52, r53, r55, r56, r57) = 189 #[serial]
137 prep_run_init().await.expect("prep_run_init failed"); 190 async fn identifier_derived_from_name(
191 #[future] scenario: (nostr::Event, GitTestRepo),
192 ) -> Result<()> {
193 let (event, _) = scenario.await;
194 assert_eq!(get_tag_value(&event, "d"), "My-Project");
195 Ok(())
196 }
138 197
139 // Extract event counts for verification 198 #[rstest]
140 let r51_repo_event_count = r51 199 #[tokio::test]
141 .events 200 #[serial]
142 .iter() 201 async fn name_tag_matches(
143 .filter(|e| e.kind.eq(&Kind::GitRepoAnnouncement)) 202 #[future] scenario: (nostr::Event, GitTestRepo),
144 .count(); 203 ) -> Result<()> {
145 let r52_repo_event_count = r52 204 let (event, _) = scenario.await;
146 .events 205 assert_eq!(get_tag_value(&event, "name"), "My Project");
147 .iter() 206 Ok(())
148 .filter(|e| e.kind.eq(&Kind::GitRepoAnnouncement))
149 .count();
150 let r53_repo_event_count = r53
151 .events
152 .iter()
153 .filter(|e| e.kind.eq(&Kind::GitRepoAnnouncement))
154 .count();
155 let r55_repo_event_count = r55
156 .events
157 .iter()
158 .filter(|e| e.kind.eq(&Kind::GitRepoAnnouncement))
159 .count();
160 let r56_repo_event_count = r56
161 .events
162 .iter()
163 .filter(|e| e.kind.eq(&Kind::GitRepoAnnouncement))
164 .count();
165 let r57_repo_event_count = r57
166 .events
167 .iter()
168 .filter(|e| e.kind.eq(&Kind::GitRepoAnnouncement))
169 .count();
170
171 SentToCorrectRelaysScenario {
172 r51_repo_event_count,
173 r52_repo_event_count,
174 r53_repo_event_count,
175 r55_repo_event_count,
176 r56_repo_event_count,
177 r57_repo_event_count,
178 }
179 } 207 }
180 208
181 #[rstest] 209 #[rstest]
182 #[tokio::test] 210 #[tokio::test]
183 #[serial] 211 #[serial]
184 async fn only_1_repository_kind_event_sent_to_user_relays( 212 async fn description_empty(
185 #[future] scenario: SentToCorrectRelaysScenario, 213 #[future] scenario: (nostr::Event, GitTestRepo),
186 ) -> Result<()> { 214 ) -> Result<()> {
187 let s = scenario.await; 215 let (event, _) = scenario.await;
188 assert_eq!(s.r53_repo_event_count, 1); 216 assert_eq!(get_tag_value(&event, "description"), "");
189 assert_eq!(s.r55_repo_event_count, 1); 217 Ok(())
218 }
219
220 #[rstest]
221 #[tokio::test]
222 #[serial]
223 async fn clone_url_derived_from_grasp_server(
224 #[future] scenario: (nostr::Event, GitTestRepo),
225 ) -> Result<()> {
226 let (event, _) = scenario.await;
227 let clone_urls = get_tag_values(&event, "clone");
228 assert_eq!(clone_urls.len(), 1);
229 assert!(
230 clone_urls[0].starts_with("http://localhost:8055/"),
231 "clone url should start with grasp server: {}",
232 clone_urls[0]
233 );
234 assert!(
235 clone_urls[0].ends_with("/My-Project.git"),
236 "clone url should end with identifier.git: {}",
237 clone_urls[0]
238 );
239 assert!(
240 clone_urls[0].contains(TEST_KEY_1_NPUB),
241 "clone url should contain npub: {}",
242 clone_urls[0]
243 );
190 Ok(()) 244 Ok(())
191 } 245 }
192 246
193 #[rstest] 247 #[rstest]
194 #[tokio::test] 248 #[tokio::test]
195 #[serial] 249 #[serial]
196 async fn only_1_repository_kind_event_sent_to_specified_repo_relays( 250 async fn relays_include_grasp_derived(
197 #[future] scenario: SentToCorrectRelaysScenario, 251 #[future] scenario: (nostr::Event, GitTestRepo),
198 ) -> Result<()> { 252 ) -> Result<()> {
199 let s = scenario.await; 253 let (event, _) = scenario.await;
200 assert_eq!(s.r55_repo_event_count, 1); 254 let relays = get_tag_values(&event, "relays");
201 assert_eq!(s.r56_repo_event_count, 1); 255 assert!(
256 relays.contains(&"ws://localhost:8055".to_string()),
257 "relays should include grasp-derived relay: {:?}",
258 relays
259 );
202 Ok(()) 260 Ok(())
203 } 261 }
204 262
205 #[rstest] 263 #[rstest]
206 #[tokio::test] 264 #[tokio::test]
207 #[serial] 265 #[serial]
208 async fn only_1_repository_kind_event_sent_to_fallback_relays( 266 async fn maintainers_is_just_me(
209 #[future] scenario: SentToCorrectRelaysScenario, 267 #[future] scenario: (nostr::Event, GitTestRepo),
210 ) -> Result<()> { 268 ) -> Result<()> {
211 let s = scenario.await; 269 let (event, _) = scenario.await;
212 assert_eq!(s.r51_repo_event_count, 1); 270 let maintainers = get_tag_values(&event, "maintainers");
213 assert_eq!(s.r52_repo_event_count, 1); 271 assert_eq!(maintainers.len(), 1);
272 assert_eq!(maintainers[0], TEST_KEY_1_KEYS.public_key().to_string());
214 Ok(()) 273 Ok(())
215 } 274 }
216 275
217 #[rstest] 276 #[rstest]
218 #[tokio::test] 277 #[tokio::test]
219 #[serial] 278 #[serial]
220 async fn only_1_repository_kind_event_sent_to_blaster_relays( 279 async fn earliest_unique_commit_is_root(
221 #[future] scenario: SentToCorrectRelaysScenario, 280 #[future] scenario: (nostr::Event, GitTestRepo),
222 ) -> Result<()> { 281 ) -> Result<()> {
223 let s = scenario.await; 282 let (event, _) = scenario.await;
224 assert_eq!(s.r57_repo_event_count, 1); 283 let euc_tag = event
284 .tags
285 .iter()
286 .find(|t| {
287 t.as_slice()[0] == "r" && t.as_slice().len() > 2 && t.as_slice()[2] == "euc"
288 })
289 .expect("euc tag not found");
290 assert_eq!(
291 euc_tag.as_slice()[1],
292 "9ee507fc4357d7ee16a5d8901bedcd103f23c17d"
293 );
225 Ok(()) 294 Ok(())
226 } 295 }
227 } 296 }
297 }
298}
228 299
229 mod git_config_updated { 300// ---------------------------------------------------------------------------
301// State B: Coordinate exists, no announcement found
302// ---------------------------------------------------------------------------
230 303
231 use nostr::nips::{nip01::Coordinate, nip19::Nip19Coordinate}; 304mod state_b_coordinate_only {
232 use nostr_sdk::ToBech32; 305 use super::*;
233 306
234 use super::*; 307 fn prep_git_repo() -> Result<GitTestRepo> {
308 let test_repo = GitTestRepo::default();
309 test_repo.populate()?;
310 test_repo.add_remote("origin", "https://localhost:1000")?;
311 Ok(test_repo)
312 }
235 313
236 async fn async_run_test() -> Result<()> { 314 mod errors {
237 let git_repo = prep_git_repo()?; 315 use futures::join;
238 // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57) 316 use test_utils::relay::Relay;
239 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = ( 317
240 Relay::new( 318 use super::*;
241 8051, 319
242 None, 320 async fn run_init_expecting_error(extra_args: Vec<&str>) -> Result<String> {
243 Some(&|relay, client_id, subscription_id, _| -> Result<()> { 321 let git_repo = prep_git_repo()?;
244 relay.respond_events( 322 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
245 client_id, 323 Relay::new(
246 &subscription_id, 324 8051,
247 &vec![ 325 None,
248 generate_test_key_1_metadata_event("fred"), 326 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
249 generate_test_key_1_relay_list_event(), 327 relay.respond_events(
250 ], 328 client_id,
251 )?; 329 &subscription_id,
252 Ok(()) 330 &vec![
253 }), 331 generate_test_key_1_metadata_event("fred"),
254 ), 332 generate_test_key_1_relay_list_event(),
255 Relay::new(8052, None, None), 333 ],
256 Relay::new(8053, None, None), 334 )?;
257 Relay::new(8055, None, None), 335 Ok(())
258 Relay::new(8056, None, None), 336 }),
259 Relay::new(8057, None, None), 337 ),
260 ); 338 Relay::new(8052, None, None),
339 Relay::new(8053, None, None),
340 Relay::new(8055, None, None),
341 Relay::new(8056, None, None),
342 );
343
344 let cli_tester_handle = std::thread::spawn({
345 let dir = git_repo.dir.clone();
346 let extra_args_owned: Vec<String> =
347 extra_args.iter().map(|s| s.to_string()).collect();
348 move || -> Result<String> {
349 let mut args =
350 vec!["--nsec", TEST_KEY_1_NSEC, "--disable-cli-spinners", "init"];
351 let extra_refs: Vec<&str> =
352 extra_args_owned.iter().map(|s| s.as_str()).collect();
353 args.extend(extra_refs);
354 let mut p = CliTester::new_from_dir(&dir, args);
355 let output = p.expect_end_eventually()?;
356 for port in [51, 52, 53, 55, 56] {
357 relay::shutdown_relay(8000 + port)?;
358 }
359 Ok(output)
360 }
361 });
362
363 let _ = join!(
364 r51.listen_until_close(),
365 r52.listen_until_close(),
366 r53.listen_until_close(),
367 r55.listen_until_close(),
368 r56.listen_until_close(),
369 );
370 cli_tester_handle.join().unwrap()
371 }
261 372
262 // // check relay had the right number of events 373 #[tokio::test]
263 let cli_tester_handle = std::thread::spawn(move || -> Result<()> { 374 #[serial]
264 let mut p = cli_tester_init(&git_repo); 375 async fn bare_no_flags() -> Result<()> {
376 let output = run_init_expecting_error(vec![]).await?;
377 assert!(
378 output.contains("no announcement found for coordinate"),
379 "expected coordinate error, got: {output}"
380 );
381 Ok(())
382 }
383
384 #[tokio::test]
385 #[serial]
386 async fn defaults_still_requires_force() -> Result<()> {
387 let output = run_init_expecting_error(vec!["--defaults"]).await?;
388 assert!(
389 output.contains("no announcement found for coordinate"),
390 "expected coordinate error even with -d, got: {output}"
391 );
392 Ok(())
393 }
394 }
395
396 mod success {
397 use futures::join;
398 use test_utils::relay::Relay;
399
400 use super::*;
401
402 #[fixture]
403 async fn state_b_force() -> nostr::Event {
404 let git_repo = prep_git_repo().expect("prep failed");
405 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
406 Relay::new(
407 8051,
408 None,
409 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
410 relay.respond_events(
411 client_id,
412 &subscription_id,
413 &vec![
414 generate_test_key_1_metadata_event("fred"),
415 generate_test_key_1_relay_list_event(),
416 ],
417 )?;
418 Ok(())
419 }),
420 ),
421 Relay::new(8052, None, None),
422 Relay::new(8053, None, None),
423 Relay::new(8055, None, None),
424 Relay::new(8056, None, None),
425 );
426
427 let cli_tester_handle = std::thread::spawn({
428 let dir = git_repo.dir.clone();
429 move || -> Result<()> {
430 let args = vec![
431 "--nsec",
432 TEST_KEY_1_NSEC,
433 "--disable-cli-spinners",
434 "init",
435 "--force",
436 "--grasp-servers",
437 "ws://localhost:8055",
438 ];
439 let mut p = CliTester::new_from_dir(&dir, args);
265 p.expect_end_eventually()?; 440 p.expect_end_eventually()?;
266 for p in [51, 52, 53, 55, 56, 57] { 441 for port in [51, 52, 53, 55, 56] {
267 relay::shutdown_relay(8000 + p)?; 442 relay::shutdown_relay(8000 + port)?;
268 } 443 }
269 assert_eq!( 444 Ok(())
270 git_repo 445 }
271 .git_repo 446 });
272 .config()?
273 .get_entry("nostr.repo")?
274 .value()
275 .unwrap(),
276 Nip19Coordinate {
277 coordinate: Coordinate {
278 kind: nostr_sdk::Kind::GitRepoAnnouncement,
279 identifier: "example-identifier".to_string(),
280 public_key: TEST_KEY_1_KEYS.public_key(),
281 },
282 relays: vec![],
283 }
284 .to_bech32()?,
285 );
286 447
448 let _ = join!(
449 r51.listen_until_close(),
450 r52.listen_until_close(),
451 r53.listen_until_close(),
452 r55.listen_until_close(),
453 r56.listen_until_close(),
454 );
455 cli_tester_handle.join().unwrap().expect("cli failed");
456
457 get_announcement(&r53.events).clone()
458 }
459
460 #[rstest]
461 #[tokio::test]
462 #[serial]
463 async fn identifier_from_coordinate(#[future] state_b_force: nostr::Event) -> Result<()> {
464 let event = state_b_force.await;
465 assert_eq!(
466 get_tag_value(&event, "d"),
467 "9ee507fc4357d7ee16a5d8901bedcd103f23c17d-consider-it-random"
468 );
469 Ok(())
470 }
471
472 #[rstest]
473 #[tokio::test]
474 #[serial]
475 async fn name_defaults_to_identifier(#[future] state_b_force: nostr::Event) -> Result<()> {
476 let event = state_b_force.await;
477 assert_eq!(
478 get_tag_value(&event, "name"),
479 "9ee507fc4357d7ee16a5d8901bedcd103f23c17d-consider-it-random"
480 );
481 Ok(())
482 }
483
484 #[rstest]
485 #[tokio::test]
486 #[serial]
487 async fn clone_url_from_grasp_server(#[future] state_b_force: nostr::Event) -> Result<()> {
488 let event = state_b_force.await;
489 let clone_urls = get_tag_values(&event, "clone");
490 assert!(
491 clone_urls
492 .iter()
493 .any(|u| u.starts_with("http://localhost:8055/")),
494 "expected grasp-derived clone url, got: {:?}",
495 clone_urls
496 );
497 Ok(())
498 }
499 }
500}
501
502// ---------------------------------------------------------------------------
503// State C: Existing announcement, it's mine
504// ---------------------------------------------------------------------------
505
506mod state_c_my_announcement {
507 use futures::join;
508 use test_utils::relay::Relay;
509
510 use super::*;
511
512 fn prep_git_repo() -> Result<GitTestRepo> {
513 let test_repo = GitTestRepo::default();
514 test_repo.populate()?;
515 test_repo.add_remote("origin", "https://localhost:1000")?;
516 Ok(test_repo)
517 }
518
519 async fn run_init(extra_args: Vec<&str>) -> Result<nostr::Event> {
520 let git_repo = prep_git_repo()?;
521 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
522 Relay::new(
523 8051,
524 None,
525 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
526 relay.respond_events(
527 client_id,
528 &subscription_id,
529 &vec![
530 generate_test_key_1_metadata_event("fred"),
531 generate_test_key_1_relay_list_event(),
532 generate_repo_ref_event(),
533 ],
534 )?;
287 Ok(()) 535 Ok(())
288 }); 536 }),
289 537 ),
290 // launch relay 538 Relay::new(8052, None, None),
291 let _ = join!( 539 Relay::new(8053, None, None),
292 r51.listen_until_close(), 540 Relay::new(8055, None, None),
293 r52.listen_until_close(), 541 Relay::new(8056, None, None),
294 r53.listen_until_close(), 542 );
295 r55.listen_until_close(),
296 r56.listen_until_close(),
297 r57.listen_until_close(),
298 );
299 cli_tester_handle.join().unwrap()?;
300 Ok(())
301 }
302 543
303 #[tokio::test] 544 let cli_tester_handle = std::thread::spawn({
304 #[serial] 545 let dir = git_repo.dir.clone();
305 async fn with_nostr_repo_set_to_user_and_identifer_naddr() -> Result<()> { 546 let extra_args_owned: Vec<String> = extra_args.iter().map(|s| s.to_string()).collect();
306 async_run_test().await?; 547 move || -> Result<()> {
548 let mut args = vec!["--nsec", TEST_KEY_1_NSEC, "--disable-cli-spinners", "init"];
549 let extra_refs: Vec<&str> = extra_args_owned.iter().map(|s| s.as_str()).collect();
550 args.extend(extra_refs);
551 let mut p = CliTester::new_from_dir(&dir, args);
552 p.expect_end_eventually()?;
553 for port in [51, 52, 53, 55, 56] {
554 relay::shutdown_relay(8000 + port)?;
555 }
307 Ok(()) 556 Ok(())
308 } 557 }
558 });
559
560 let _ = join!(
561 r51.listen_until_close(),
562 r52.listen_until_close(),
563 r53.listen_until_close(),
564 r55.listen_until_close(),
565 r56.listen_until_close(),
566 );
567 cli_tester_handle.join().unwrap()?;
568
569 Ok(get_announcement(&r53.events).clone())
570 }
571
572 mod errors {
573 use super::*;
574
575 #[tokio::test]
576 #[serial]
577 async fn identifier_change_requires_force() -> Result<()> {
578 let git_repo = prep_git_repo()?;
579 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
580 Relay::new(
581 8051,
582 None,
583 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
584 relay.respond_events(
585 client_id,
586 &subscription_id,
587 &vec![
588 generate_test_key_1_metadata_event("fred"),
589 generate_test_key_1_relay_list_event(),
590 generate_repo_ref_event(),
591 ],
592 )?;
593 Ok(())
594 }),
595 ),
596 Relay::new(8052, None, None),
597 Relay::new(8053, None, None),
598 Relay::new(8055, None, None),
599 Relay::new(8056, None, None),
600 );
601
602 let cli_tester_handle = std::thread::spawn({
603 let dir = git_repo.dir.clone();
604 move || -> Result<String> {
605 let args = vec![
606 "--nsec",
607 TEST_KEY_1_NSEC,
608 "--disable-cli-spinners",
609 "init",
610 "--identifier",
611 "new-id",
612 ];
613 let mut p = CliTester::new_from_dir(&dir, args);
614 let output = p.expect_end_eventually()?;
615 for port in [51, 52, 53, 55, 56] {
616 relay::shutdown_relay(8000 + port)?;
617 }
618 Ok(output)
619 }
620 });
621
622 let _ = join!(
623 r51.listen_until_close(),
624 r52.listen_until_close(),
625 r53.listen_until_close(),
626 r55.listen_until_close(),
627 r56.listen_until_close(),
628 );
629 let output = cli_tester_handle.join().unwrap()?;
630 assert!(
631 output.contains("changing identifier creates a new repository"),
632 "expected identifier change error, got: {output}"
633 );
634 Ok(())
309 } 635 }
310 636
311 mod tags_as_specified_in_args { 637 #[tokio::test]
312 use super::*; 638 #[serial]
639 async fn bare_no_flags_requires_force() -> Result<()> {
640 let git_repo = prep_git_repo()?;
641 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
642 Relay::new(
643 8051,
644 None,
645 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
646 relay.respond_events(
647 client_id,
648 &subscription_id,
649 &vec![
650 generate_test_key_1_metadata_event("fred"),
651 generate_test_key_1_relay_list_event(),
652 generate_repo_ref_event(),
653 ],
654 )?;
655 Ok(())
656 }),
657 ),
658 Relay::new(8052, None, None),
659 Relay::new(8053, None, None),
660 Relay::new(8055, None, None),
661 Relay::new(8056, None, None),
662 );
313 663
314 #[derive(Clone)] 664 let cli_tester_handle = std::thread::spawn({
315 pub struct TagsAsSpecifiedScenario { 665 let dir = git_repo.dir.clone();
316 pub event: nostr::Event, 666 move || -> Result<String> {
317 } 667 let args = vec!["--nsec", TEST_KEY_1_NSEC, "--disable-cli-spinners", "init"];
668 let mut p = CliTester::new_from_dir(&dir, args);
669 let output = p.expect_end_eventually()?;
670 for port in [51, 52, 53, 55, 56] {
671 relay::shutdown_relay(8000 + port)?;
672 }
673 Ok(output)
674 }
675 });
318 676
319 #[fixture] 677 let _ = join!(
320 async fn scenario() -> TagsAsSpecifiedScenario { 678 r51.listen_until_close(),
321 let (_, _, r53, _r55, _r56, _r57) = 679 r52.listen_until_close(),
322 prep_run_init().await.expect("prep_run_init failed"); 680 r53.listen_until_close(),
681 r55.listen_until_close(),
682 r56.listen_until_close(),
683 );
684 let output = cli_tester_handle.join().unwrap()?;
685 assert!(
686 output.contains("no arguments specified"),
687 "expected 'no arguments specified' error, got: {output}"
688 );
689 Ok(())
690 }
691 }
323 692
324 // Extract the GitRepoAnnouncement event (should be same on all relays) 693 mod success {
325 let event = r53 694 use super::*;
326 .events 695
327 .iter() 696 mod force_refresh {
328 .find(|e| e.kind.eq(&Kind::GitRepoAnnouncement)) 697 use super::*;
329 .expect("GitRepoAnnouncement event not found")
330 .clone();
331 698
332 TagsAsSpecifiedScenario { event } 699 #[fixture]
700 async fn scenario() -> nostr::Event {
701 run_init(vec!["--force"]).await.expect("init failed")
333 } 702 }
334 703
335 #[rstest] 704 #[rstest]
336 #[tokio::test] 705 #[tokio::test]
337 #[serial] 706 #[serial]
338 async fn d_replaceable_event_identifier( 707 async fn name_preserved(#[future] scenario: nostr::Event) -> Result<()> {
339 #[future] scenario: TagsAsSpecifiedScenario, 708 let event = scenario.await;
340 ) -> Result<()> { 709 assert_eq!(get_tag_value(&event, "name"), "example name");
341 let s = scenario.await;
342 assert!(
343 s.event.tags.iter().any(
344 |t| t.as_slice()[0].eq("d") && t.as_slice()[1].eq("example-identifier")
345 )
346 );
347 Ok(()) 710 Ok(())
348 } 711 }
349 712
350 #[rstest] 713 #[rstest]
351 #[tokio::test] 714 #[tokio::test]
352 #[serial] 715 #[serial]
353 async fn earliest_unique_commit_as_reference_with_euc_marker( 716 async fn description_preserved(#[future] scenario: nostr::Event) -> Result<()> {
354 #[future] scenario: TagsAsSpecifiedScenario, 717 let event = scenario.await;
355 ) -> Result<()> { 718 assert_eq!(get_tag_value(&event, "description"), "example description");
356 let s = scenario.await;
357 assert!(s.event.tags.iter().any(|t| t.as_slice()[0].eq("r")
358 && t.as_slice()[1].eq("9ee507fc4357d7ee16a5d8901bedcd103f23c17d")
359 && t.as_slice()[2].eq("euc")));
360 Ok(()) 719 Ok(())
361 } 720 }
362 721
363 #[rstest] 722 #[rstest]
364 #[tokio::test] 723 #[tokio::test]
365 #[serial] 724 #[serial]
366 async fn name(#[future] scenario: TagsAsSpecifiedScenario) -> Result<()> { 725 async fn relays_from_my_event(#[future] scenario: nostr::Event) -> Result<()> {
367 let s = scenario.await; 726 let event = scenario.await;
727 let relays = get_tag_values(&event, "relays");
368 assert!( 728 assert!(
369 s.event 729 relays.contains(&"ws://localhost:8055".to_string()),
370 .tags 730 "relays should include my existing relay: {:?}",
371 .iter() 731 relays
372 .any(|t| t.as_slice()[0].eq("name") && t.as_slice()[1].eq("example-name"))
373 ); 732 );
374 Ok(()) 733 Ok(())
375 } 734 }
@@ -377,160 +736,472 @@ mod when_repo_not_previously_claimed {
377 #[rstest] 736 #[rstest]
378 #[tokio::test] 737 #[tokio::test]
379 #[serial] 738 #[serial]
380 async fn alt(#[future] scenario: TagsAsSpecifiedScenario) -> Result<()> { 739 async fn maintainers_preserved(#[future] scenario: nostr::Event) -> Result<()> {
381 let s = scenario.await; 740 let event = scenario.await;
382 assert!(s.event.tags.iter().any(|t| t.as_slice()[0].eq("alt") 741 let maintainers = get_tag_values(&event, "maintainers");
383 && t.as_slice()[1].eq("git repository: example-name")));
384 Ok(())
385 }
386
387 #[rstest]
388 #[tokio::test]
389 #[serial]
390 async fn description(#[future] scenario: TagsAsSpecifiedScenario) -> Result<()> {
391 let s = scenario.await;
392 assert!( 742 assert!(
393 s.event 743 maintainers.contains(&TEST_KEY_1_KEYS.public_key().to_string()),
394 .tags 744 "maintainers should include KEY_1: {:?}",
395 .iter() 745 maintainers
396 .any(|t| t.as_slice()[0].eq("description")
397 && t.as_slice()[1].eq("example-description"))
398 ); 746 );
399 Ok(())
400 }
401
402 #[rstest]
403 #[tokio::test]
404 #[serial]
405 async fn git_server(#[future] scenario: TagsAsSpecifiedScenario) -> Result<()> {
406 let s = scenario.await;
407 assert!( 747 assert!(
408 s.event.tags.iter().any(|t| t.as_slice()[0].eq("clone") 748 maintainers.contains(&TEST_KEY_2_KEYS.public_key().to_string()),
409 && t.as_slice()[1].eq("https://git.myhosting.com/my-repo.git")) /* todo check it defaults to origin */ 749 "maintainers should include KEY_2: {:?}",
750 maintainers
410 ); 751 );
411 Ok(()) 752 Ok(())
412 } 753 }
754 }
413 755
414 #[rstest] 756 mod name_override {
415 #[tokio::test] 757 use super::*;
416 #[serial] 758
417 async fn relays(#[future] scenario: TagsAsSpecifiedScenario) -> Result<()> { 759 #[fixture]
418 let s = scenario.await; 760 async fn scenario() -> nostr::Event {
419 let relays_tag = s 761 run_init(vec!["--name", "New Name"])
420 .event 762 .await
421 .tags 763 .expect("init failed")
422 .iter()
423 .find(|t| t.as_slice()[0].eq("relays"))
424 .unwrap()
425 .as_slice();
426 assert_eq!(relays_tag[1], "ws://localhost:8055",);
427 assert_eq!(relays_tag[2], "ws://localhost:8056",);
428 Ok(())
429 } 764 }
430 765
431 #[rstest] 766 #[rstest]
432 #[tokio::test] 767 #[tokio::test]
433 #[serial] 768 #[serial]
434 async fn web(#[future] scenario: TagsAsSpecifiedScenario) -> Result<()> { 769 async fn name_overridden(#[future] scenario: nostr::Event) -> Result<()> {
435 let s = scenario.await; 770 let event = scenario.await;
436 let web_tag = s 771 assert_eq!(get_tag_value(&event, "name"), "New Name");
437 .event
438 .tags
439 .iter()
440 .find(|t| t.as_slice()[0].eq("web"))
441 .unwrap()
442 .as_slice();
443 assert_eq!(web_tag[1], "https://exampleproject.xyz",);
444 assert_eq!(web_tag[2], "https://gitworkshop.dev/123",);
445 Ok(()) 772 Ok(())
446 } 773 }
447 774
448 #[rstest] 775 #[rstest]
449 #[tokio::test] 776 #[tokio::test]
450 #[serial] 777 #[serial]
451 async fn maintainers(#[future] scenario: TagsAsSpecifiedScenario) -> Result<()> { 778 async fn identifier_unchanged(#[future] scenario: nostr::Event) -> Result<()> {
452 let s = scenario.await; 779 let event = scenario.await;
453 let maintainers_tag = s 780 assert_eq!(
454 .event 781 get_tag_value(&event, "d"),
455 .tags 782 "9ee507fc4357d7ee16a5d8901bedcd103f23c17d-consider-it-random"
456 .iter() 783 );
457 .find(|t| t.as_slice()[0].eq("maintainers"))
458 .unwrap()
459 .as_slice();
460 assert_eq!(maintainers_tag[1], TEST_KEY_1_KEYS.public_key().to_string());
461 Ok(()) 784 Ok(())
462 } 785 }
463 } 786 }
787 }
788}
464 789
465 mod cli_ouput { 790// ---------------------------------------------------------------------------
466 use super::*; 791// State D: Existing announcement, not mine, I'm listed as maintainer
792// ---------------------------------------------------------------------------
467 793
468 #[tokio::test] 794mod state_d_co_maintainer {
469 #[serial] 795 use futures::join;
470 async fn check_cli_output() -> Result<()> { 796 use test_utils::relay::Relay;
471 let git_repo = prep_git_repo()?; 797
472 798 use super::*;
473 // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57) 799
474 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = ( 800 fn prep_git_repo() -> Result<GitTestRepo> {
475 Relay::new( 801 let test_repo = GitTestRepo::without_repo_in_git_config();
476 8051, 802 test_repo.populate()?;
477 None, 803 test_repo.add_remote("origin", "https://localhost:1000")?;
478 Some(&|relay, client_id, subscription_id, _| -> Result<()> { 804 test_repo.set_nostr_repo_coordinate(
479 relay.respond_events( 805 &TEST_KEY_2_KEYS.public_key(),
480 client_id, 806 "9ee507fc4357d7ee16a5d8901bedcd103f23c17d-consider-it-random",
481 &subscription_id, 807 &["ws://localhost:8055", "ws://localhost:8056"],
482 &vec![ 808 );
483 generate_test_key_1_metadata_event("fred"), 809 Ok(test_repo)
484 generate_test_key_1_relay_list_event(), 810 }
485 ], 811
486 )?; 812 mod success {
487 Ok(()) 813 use super::*;
488 }), 814
489 ), 815 #[fixture]
490 Relay::new(8052, None, None), 816 async fn scenario() -> nostr::Event {
491 Relay::new(8053, None, None), 817 let git_repo = prep_git_repo().expect("prep failed");
492 Relay::new(8055, None, None), 818 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
493 Relay::new(8056, None, None), 819 Relay::new(
494 Relay::new(8057, None, None), 820 8051,
495 ); 821 None,
822 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
823 relay.respond_events(
824 client_id,
825 &subscription_id,
826 &vec![
827 generate_test_key_1_metadata_event("fred"),
828 generate_test_key_1_relay_list_event(),
829 generate_test_key_2_metadata_event("carole"),
830 generate_test_key_2_relay_list_event(),
831 generate_repo_ref_event_as_key_2_listing_key_1(),
832 ],
833 )?;
834 Ok(())
835 }),
836 ),
837 Relay::new(8052, None, None),
838 Relay::new(8053, None, None),
839 Relay::new(8055, None, None),
840 Relay::new(8056, None, None),
841 );
496 842
497 // // check relay had the right number of events 843 let cli_tester_handle = std::thread::spawn({
498 let cli_tester_handle = std::thread::spawn(move || -> Result<()> { 844 let dir = git_repo.dir.clone();
499 let mut p = cli_tester_init(&git_repo); 845 move || -> Result<()> {
500 expect_msgs_first(&mut p)?; 846 let args = vec![
501 relay::expect_send_with_progress( 847 "--nsec",
502 &mut p, 848 TEST_KEY_1_NSEC,
503 vec![ 849 "--disable-cli-spinners",
504 (" [my-relay] [repo-relay] ws://localhost:8055", true, ""), 850 "init",
505 (" [my-relay] ws://localhost:8053", true, ""), 851 "--grasp-servers",
506 (" [repo-relay] ws://localhost:8056", true, ""), 852 "ws://localhost:8055",
507 (" [default] ws://localhost:8051", true, ""), 853 ];
508 (" [default] ws://localhost:8052", true, ""), 854 let mut p = CliTester::new_from_dir(&dir, args);
509 (" [default] ws://localhost:8057", true, ""), 855 p.expect_end_eventually()?;
856 for port in [51, 52, 53, 55, 56] {
857 relay::shutdown_relay(8000 + port)?;
858 }
859 Ok(())
860 }
861 });
862
863 let _ = join!(
864 r51.listen_until_close(),
865 r52.listen_until_close(),
866 r53.listen_until_close(),
867 r55.listen_until_close(),
868 r56.listen_until_close(),
869 );
870 cli_tester_handle.join().unwrap().expect("cli failed");
871
872 get_announcement(&r53.events).clone()
873 }
874
875 #[rstest]
876 #[tokio::test]
877 #[serial]
878 async fn name_inherited_from_other_maintainer(
879 #[future] scenario: nostr::Event,
880 ) -> Result<()> {
881 let event = scenario.await;
882 assert_eq!(get_tag_value(&event, "name"), "example name");
883 Ok(())
884 }
885
886 #[rstest]
887 #[tokio::test]
888 #[serial]
889 async fn description_inherited_from_other_maintainer(
890 #[future] scenario: nostr::Event,
891 ) -> Result<()> {
892 let event = scenario.await;
893 assert_eq!(get_tag_value(&event, "description"), "example description");
894 Ok(())
895 }
896
897 #[rstest]
898 #[tokio::test]
899 #[serial]
900 async fn web_inherited_from_other_maintainer(
901 #[future] scenario: nostr::Event,
902 ) -> Result<()> {
903 let event = scenario.await;
904 let web = get_tag_values(&event, "web");
905 assert!(
906 web.iter().any(|w| w.contains("exampleproject.xyz")),
907 "web should be inherited from KEY_2's announcement: {:?}",
908 web
909 );
910 Ok(())
911 }
912
913 #[rstest]
914 #[tokio::test]
915 #[serial]
916 async fn clone_url_from_my_grasp_server_not_theirs(
917 #[future] scenario: nostr::Event,
918 ) -> Result<()> {
919 let event = scenario.await;
920 let clone_urls = get_tag_values(&event, "clone");
921 assert!(
922 clone_urls
923 .iter()
924 .any(|u| u.starts_with("http://localhost:8055/")),
925 "clone url should be from my grasp server: {:?}",
926 clone_urls
927 );
928 assert!(
929 !clone_urls.iter().any(|u| u.contains("123.gitexample.com")),
930 "clone url should NOT contain KEY_2's git server: {:?}",
931 clone_urls
932 );
933 Ok(())
934 }
935
936 #[rstest]
937 #[tokio::test]
938 #[serial]
939 async fn relays_from_my_grasp_server(#[future] scenario: nostr::Event) -> Result<()> {
940 let event = scenario.await;
941 let relays = get_tag_values(&event, "relays");
942 assert!(
943 relays.contains(&"ws://localhost:8055".to_string()),
944 "relays should include my grasp-derived relay: {:?}",
945 relays
946 );
947 Ok(())
948 }
949
950 #[rstest]
951 #[tokio::test]
952 #[serial]
953 async fn maintainers_is_me_and_trusted(#[future] scenario: nostr::Event) -> Result<()> {
954 let event = scenario.await;
955 let maintainers = get_tag_values(&event, "maintainers");
956 assert_eq!(
957 maintainers.len(),
958 2,
959 "should have exactly 2 maintainers: {:?}",
960 maintainers
961 );
962 assert!(
963 maintainers.contains(&TEST_KEY_1_KEYS.public_key().to_string()),
964 "maintainers should include KEY_1 (me): {:?}",
965 maintainers
966 );
967 assert!(
968 maintainers.contains(&TEST_KEY_2_KEYS.public_key().to_string()),
969 "maintainers should include KEY_2 (trusted): {:?}",
970 maintainers
971 );
972 Ok(())
973 }
974 }
975}
976
977// ---------------------------------------------------------------------------
978// State E: Existing announcement, not mine, I'm NOT listed as maintainer
979// ---------------------------------------------------------------------------
980
981mod state_e_not_listed {
982 use futures::join;
983 use test_utils::relay::Relay;
984
985 use super::*;
986
987 fn prep_git_repo() -> Result<GitTestRepo> {
988 let test_repo = GitTestRepo::without_repo_in_git_config();
989 test_repo.populate()?;
990 test_repo.add_remote("origin", "https://localhost:1000")?;
991 // Point coordinate to KEY_2 (not the logged-in user)
992 test_repo.set_nostr_repo_coordinate(
993 &TEST_KEY_2_KEYS.public_key(),
994 "9ee507fc4357d7ee16a5d8901bedcd103f23c17d-consider-it-random",
995 &["ws://localhost:8055", "ws://localhost:8056"],
996 );
997 Ok(test_repo)
998 }
999
1000 /// Run init with relays that serve KEY_2's announcement NOT listing KEY_1.
1001 async fn run_init_expecting_error(extra_args: Vec<&str>) -> Result<String> {
1002 let git_repo = prep_git_repo()?;
1003 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
1004 Relay::new(
1005 8051,
1006 None,
1007 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
1008 relay.respond_events(
1009 client_id,
1010 &subscription_id,
1011 &vec![
1012 generate_test_key_1_metadata_event("fred"),
1013 generate_test_key_1_relay_list_event(),
1014 generate_test_key_2_metadata_event("carole"),
1015 generate_test_key_2_relay_list_event(),
1016 generate_repo_ref_event_as_key_2_not_listing_key_1(),
510 ], 1017 ],
511 1,
512 )?; 1018 )?;
1019 Ok(())
1020 }),
1021 ),
1022 Relay::new(8052, None, None),
1023 Relay::new(8053, None, None),
1024 Relay::new(8055, None, None),
1025 Relay::new(8056, None, None),
1026 );
1027
1028 let cli_tester_handle = std::thread::spawn({
1029 let dir = git_repo.dir.clone();
1030 let extra_args_owned: Vec<String> = extra_args.iter().map(|s| s.to_string()).collect();
1031 move || -> Result<String> {
1032 let mut args = vec!["--nsec", TEST_KEY_1_NSEC, "--disable-cli-spinners", "init"];
1033 let extra_refs: Vec<&str> = extra_args_owned.iter().map(|s| s.as_str()).collect();
1034 args.extend(extra_refs);
1035 let mut p = CliTester::new_from_dir(&dir, args);
1036 let output = p.expect_end_eventually()?;
1037 for port in [51, 52, 53, 55, 56] {
1038 relay::shutdown_relay(8000 + port)?;
1039 }
1040 Ok(output)
1041 }
1042 });
1043
1044 let _ = join!(
1045 r51.listen_until_close(),
1046 r52.listen_until_close(),
1047 r53.listen_until_close(),
1048 r55.listen_until_close(),
1049 r56.listen_until_close(),
1050 );
1051 cli_tester_handle.join().unwrap()
1052 }
1053
1054 mod errors {
1055 use super::*;
1056
1057 #[tokio::test]
1058 #[serial]
1059 async fn bare_no_flags() -> Result<()> {
1060 let output = run_init_expecting_error(vec![]).await?;
1061 assert!(
1062 output.contains("you are not listed as a maintainer"),
1063 "expected not-listed error, got: {output}"
1064 );
1065 Ok(())
1066 }
1067
1068 #[tokio::test]
1069 #[serial]
1070 async fn defaults_still_requires_force() -> Result<()> {
1071 let output = run_init_expecting_error(vec!["--defaults"]).await?;
1072 assert!(
1073 output.contains("you are not listed as a maintainer"),
1074 "expected not-listed error even with -d, got: {output}"
1075 );
1076 Ok(())
1077 }
1078 }
1079
1080 mod success {
1081 use super::*;
1082
1083 #[fixture]
1084 async fn scenario() -> nostr::Event {
1085 let git_repo = prep_git_repo().expect("prep failed");
1086 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
1087 Relay::new(
1088 8051,
1089 None,
1090 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
1091 relay.respond_events(
1092 client_id,
1093 &subscription_id,
1094 &vec![
1095 generate_test_key_1_metadata_event("fred"),
1096 generate_test_key_1_relay_list_event(),
1097 generate_test_key_2_metadata_event("carole"),
1098 generate_test_key_2_relay_list_event(),
1099 generate_repo_ref_event_as_key_2_not_listing_key_1(),
1100 ],
1101 )?;
1102 Ok(())
1103 }),
1104 ),
1105 Relay::new(8052, None, None),
1106 Relay::new(8053, None, None),
1107 Relay::new(8055, None, None),
1108 Relay::new(8056, None, None),
1109 );
1110
1111 let cli_tester_handle = std::thread::spawn({
1112 let dir = git_repo.dir.clone();
1113 move || -> Result<()> {
1114 let args = vec![
1115 "--nsec",
1116 TEST_KEY_1_NSEC,
1117 "--disable-cli-spinners",
1118 "init",
1119 "--force",
1120 "--grasp-servers",
1121 "ws://localhost:8055",
1122 ];
1123 let mut p = CliTester::new_from_dir(&dir, args);
513 p.expect_end_eventually()?; 1124 p.expect_end_eventually()?;
514 for p in [51, 52, 53, 55, 56, 57] { 1125 for port in [51, 52, 53, 55, 56] {
515 relay::shutdown_relay(8000 + p)?; 1126 relay::shutdown_relay(8000 + port)?;
516 } 1127 }
517 Ok(()) 1128 Ok(())
518 }); 1129 }
519 1130 });
520 // launch relay 1131
521 let _ = join!( 1132 let _ = join!(
522 r51.listen_until_close(), 1133 r51.listen_until_close(),
523 r52.listen_until_close(), 1134 r52.listen_until_close(),
524 r53.listen_until_close(), 1135 r53.listen_until_close(),
525 r55.listen_until_close(), 1136 r55.listen_until_close(),
526 r56.listen_until_close(), 1137 r56.listen_until_close(),
527 r57.listen_until_close(), 1138 );
528 ); 1139 cli_tester_handle.join().unwrap().expect("cli failed");
529 cli_tester_handle.join().unwrap()?; 1140
530 Ok(()) 1141 get_announcement(&r53.events).clone()
531 } 1142 }
1143
1144 #[rstest]
1145 #[tokio::test]
1146 #[serial]
1147 async fn name_inherited_from_other_maintainer(
1148 #[future] scenario: nostr::Event,
1149 ) -> Result<()> {
1150 let event = scenario.await;
1151 assert_eq!(get_tag_value(&event, "name"), "example name");
1152 Ok(())
1153 }
1154
1155 #[rstest]
1156 #[tokio::test]
1157 #[serial]
1158 async fn description_inherited_from_other_maintainer(
1159 #[future] scenario: nostr::Event,
1160 ) -> Result<()> {
1161 let event = scenario.await;
1162 assert_eq!(get_tag_value(&event, "description"), "example description");
1163 Ok(())
1164 }
1165
1166 #[rstest]
1167 #[tokio::test]
1168 #[serial]
1169 async fn web_inherited_from_other_maintainer(
1170 #[future] scenario: nostr::Event,
1171 ) -> Result<()> {
1172 let event = scenario.await;
1173 let web = get_tag_values(&event, "web");
1174 assert!(
1175 web.iter().any(|w| w.contains("exampleproject.xyz")),
1176 "web should be inherited from KEY_2's announcement: {:?}",
1177 web
1178 );
1179 Ok(())
1180 }
1181
1182 #[rstest]
1183 #[tokio::test]
1184 #[serial]
1185 async fn maintainers_is_me_and_trusted(#[future] scenario: nostr::Event) -> Result<()> {
1186 let event = scenario.await;
1187 let maintainers = get_tag_values(&event, "maintainers");
1188 assert_eq!(
1189 maintainers.len(),
1190 2,
1191 "should have exactly 2 maintainers: {:?}",
1192 maintainers
1193 );
1194 assert!(
1195 maintainers.contains(&TEST_KEY_1_KEYS.public_key().to_string()),
1196 "maintainers should include KEY_1 (me): {:?}",
1197 maintainers
1198 );
1199 assert!(
1200 maintainers.contains(&TEST_KEY_2_KEYS.public_key().to_string()),
1201 "maintainers should include KEY_2 (trusted): {:?}",
1202 maintainers
1203 );
1204 Ok(())
532 } 1205 }
533 } 1206 }
534 // TODO: cli caputuring input
535} 1207}
536// TODO: when_updating_existing_repoistory correct defaults are used
diff --git a/tests/ngit_list.rs b/tests/ngit_list.rs
index 39385d6..59e326a 100644
--- a/tests/ngit_list.rs
+++ b/tests/ngit_list.rs
@@ -77,7 +77,7 @@ mod cannot_find_repo_event {
77 let cli_tester_handle = std::thread::spawn(move || -> Result<()> { 77 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
78 let test_repo = GitTestRepo::without_repo_in_git_config(); 78 let test_repo = GitTestRepo::without_repo_in_git_config();
79 test_repo.populate()?; 79 test_repo.populate()?;
80 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); 80 let mut p = CliTester::new_from_dir(&test_repo.dir, ["-i", "list"]);
81 p.expect( 81 p.expect(
82 "hint: https://gitworkshop.dev/search lists repositories and their nostr address\r\n", 82 "hint: https://gitworkshop.dev/search lists repositories and their nostr address\r\n",
83 )?; 83 )?;
@@ -197,7 +197,7 @@ mod when_main_branch_is_uptodate {
197 197
198 let test_repo = GitTestRepo::default(); 198 let test_repo = GitTestRepo::default();
199 test_repo.populate()?; 199 test_repo.populate()?;
200 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); 200 let mut p = CliTester::new_from_dir(&test_repo.dir, ["-i", "list"]);
201 201
202 p.expect("fetching updates...\r\n")?; 202 p.expect("fetching updates...\r\n")?;
203 p.expect_eventually("\r\n")?; // some updates listed here 203 p.expect_eventually("\r\n")?; // some updates listed here
@@ -316,7 +316,7 @@ mod when_main_branch_is_uptodate {
316 316
317 let test_repo = GitTestRepo::default(); 317 let test_repo = GitTestRepo::default();
318 test_repo.populate()?; 318 test_repo.populate()?;
319 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); 319 let mut p = CliTester::new_from_dir(&test_repo.dir, ["-i", "list"]);
320 320
321 p.expect("fetching updates...\r\n")?; 321 p.expect("fetching updates...\r\n")?;
322 p.expect_eventually("\r\n")?; // some updates listed here 322 p.expect_eventually("\r\n")?; // some updates listed here
@@ -438,7 +438,7 @@ mod when_main_branch_is_uptodate {
438 )?; 438 )?;
439 let test_repo = GitTestRepo::default(); 439 let test_repo = GitTestRepo::default();
440 test_repo.populate()?; 440 test_repo.populate()?;
441 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); 441 let mut p = CliTester::new_from_dir(&test_repo.dir, ["-i", "list"]);
442 442
443 p.expect("fetching updates...\r\n")?; 443 p.expect("fetching updates...\r\n")?;
444 p.expect_eventually("\r\n")?; // some updates listed here 444 p.expect_eventually("\r\n")?; // some updates listed here
@@ -516,7 +516,7 @@ mod when_main_branch_is_uptodate {
516 )?; 516 )?;
517 let test_repo = GitTestRepo::default(); 517 let test_repo = GitTestRepo::default();
518 test_repo.populate()?; 518 test_repo.populate()?;
519 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); 519 let mut p = CliTester::new_from_dir(&test_repo.dir, ["-i", "list"]);
520 520
521 p.expect("fetching updates...\r\n")?; 521 p.expect("fetching updates...\r\n")?;
522 p.expect_eventually("\r\n")?; // some updates listed here 522 p.expect_eventually("\r\n")?; // some updates listed here
@@ -639,7 +639,7 @@ mod when_main_branch_is_uptodate {
639 let test_repo = GitTestRepo::default(); 639 let test_repo = GitTestRepo::default();
640 test_repo.populate()?; 640 test_repo.populate()?;
641 // create proposal branch 641 // create proposal branch
642 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); 642 let mut p = CliTester::new_from_dir(&test_repo.dir, ["-i", "list"]);
643 p.expect("fetching updates...\r\n")?; 643 p.expect("fetching updates...\r\n")?;
644 p.expect_eventually("\r\n")?; // some updates listed here 644 p.expect_eventually("\r\n")?; // some updates listed here
645 let mut c = p.expect_choice( 645 let mut c = p.expect_choice(
@@ -664,7 +664,7 @@ mod when_main_branch_is_uptodate {
664 664
665 test_repo.checkout("main")?; 665 test_repo.checkout("main")?;
666 // run test 666 // run test
667 p = CliTester::new_from_dir(&test_repo.dir, ["list"]); 667 p = CliTester::new_from_dir(&test_repo.dir, ["-i", "list"]);
668 p.expect("fetching updates...\r\n")?; 668 p.expect("fetching updates...\r\n")?;
669 p.expect_eventually("\r\n")?; // some updates listed here 669 p.expect_eventually("\r\n")?; // some updates listed here
670 let mut c = p.expect_choice( 670 let mut c = p.expect_choice(
@@ -735,7 +735,7 @@ mod when_main_branch_is_uptodate {
735 let test_repo = GitTestRepo::default(); 735 let test_repo = GitTestRepo::default();
736 test_repo.populate()?; 736 test_repo.populate()?;
737 // create proposal branch 737 // create proposal branch
738 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); 738 let mut p = CliTester::new_from_dir(&test_repo.dir, ["-i", "list"]);
739 p.expect("fetching updates...\r\n")?; 739 p.expect("fetching updates...\r\n")?;
740 p.expect_eventually("\r\n")?; // some updates listed here 740 p.expect_eventually("\r\n")?; // some updates listed here
741 let mut c = p.expect_choice( 741 let mut c = p.expect_choice(
@@ -760,7 +760,7 @@ mod when_main_branch_is_uptodate {
760 760
761 test_repo.checkout("main")?; 761 test_repo.checkout("main")?;
762 // run test 762 // run test
763 p = CliTester::new_from_dir(&test_repo.dir, ["list"]); 763 p = CliTester::new_from_dir(&test_repo.dir, ["-i", "list"]);
764 p.expect("fetching updates...\r\n")?; 764 p.expect("fetching updates...\r\n")?;
765 p.expect_eventually("\r\n")?; // some updates listed here 765 p.expect_eventually("\r\n")?; // some updates listed here
766 let mut c = p.expect_choice( 766 let mut c = p.expect_choice(
@@ -850,7 +850,7 @@ mod when_main_branch_is_uptodate {
850 )?; 850 )?;
851 851
852 // run test 852 // run test
853 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); 853 let mut p = CliTester::new_from_dir(&test_repo.dir, ["-i", "list"]);
854 p.expect("fetching updates...\r\n")?; 854 p.expect("fetching updates...\r\n")?;
855 p.expect_eventually("\r\n")?; // some updates listed here 855 p.expect_eventually("\r\n")?; // some updates listed here
856 let mut c = p.expect_choice( 856 let mut c = p.expect_choice(
@@ -926,7 +926,7 @@ mod when_main_branch_is_uptodate {
926 )?; 926 )?;
927 927
928 // run test 928 // run test
929 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); 929 let mut p = CliTester::new_from_dir(&test_repo.dir, ["-i", "list"]);
930 p.expect("fetching updates...\r\n")?; 930 p.expect("fetching updates...\r\n")?;
931 p.expect_eventually("\r\n")?; // some updates listed here 931 p.expect_eventually("\r\n")?; // some updates listed here
932 let mut c = p.expect_choice( 932 let mut c = p.expect_choice(
@@ -1039,7 +1039,7 @@ mod when_main_branch_is_uptodate {
1039 test_repo.checkout("main")?; 1039 test_repo.checkout("main")?;
1040 1040
1041 // run test 1041 // run test
1042 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); 1042 let mut p = CliTester::new_from_dir(&test_repo.dir, ["-i", "list"]);
1043 p.expect("fetching updates...\r\n")?; 1043 p.expect("fetching updates...\r\n")?;
1044 p.expect_eventually("\r\n")?; // some updates listed here 1044 p.expect_eventually("\r\n")?; // some updates listed here
1045 let mut c = p.expect_choice( 1045 let mut c = p.expect_choice(
@@ -1118,7 +1118,7 @@ mod when_main_branch_is_uptodate {
1118 test_repo.checkout("main")?; 1118 test_repo.checkout("main")?;
1119 1119
1120 // run test 1120 // run test
1121 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); 1121 let mut p = CliTester::new_from_dir(&test_repo.dir, ["-i", "list"]);
1122 p.expect("fetching updates...\r\n")?; 1122 p.expect("fetching updates...\r\n")?;
1123 p.expect_eventually("\r\n")?; // some updates listed here 1123 p.expect_eventually("\r\n")?; // some updates listed here
1124 let mut c = p.expect_choice( 1124 let mut c = p.expect_choice(
@@ -1223,7 +1223,7 @@ mod when_main_branch_is_uptodate {
1223 test_repo.checkout("main")?; 1223 test_repo.checkout("main")?;
1224 1224
1225 // run test 1225 // run test
1226 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); 1226 let mut p = CliTester::new_from_dir(&test_repo.dir, ["-i", "list"]);
1227 p.expect("fetching updates...\r\n")?; 1227 p.expect("fetching updates...\r\n")?;
1228 p.expect_eventually("\r\n")?; // some updates listed here 1228 p.expect_eventually("\r\n")?; // some updates listed here
1229 let mut c = p.expect_choice( 1229 let mut c = p.expect_choice(
@@ -1305,7 +1305,7 @@ mod when_main_branch_is_uptodate {
1305 test_repo.checkout("main")?; 1305 test_repo.checkout("main")?;
1306 1306
1307 // run test 1307 // run test
1308 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); 1308 let mut p = CliTester::new_from_dir(&test_repo.dir, ["-i", "list"]);
1309 p.expect("fetching updates...\r\n")?; 1309 p.expect("fetching updates...\r\n")?;
1310 p.expect_eventually("\r\n")?; // some updates listed here 1310 p.expect_eventually("\r\n")?; // some updates listed here
1311 let mut c = p.expect_choice( 1311 let mut c = p.expect_choice(
@@ -1407,7 +1407,7 @@ mod when_main_branch_is_uptodate {
1407 let (originating_repo, test_repo) = create_proposals_with_first_rebased_and_repo_with_latest_main_and_unrebased_proposal()?; 1407 let (originating_repo, test_repo) = create_proposals_with_first_rebased_and_repo_with_latest_main_and_unrebased_proposal()?;
1408 test_repo.checkout("main")?; 1408 test_repo.checkout("main")?;
1409 1409
1410 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); 1410 let mut p = CliTester::new_from_dir(&test_repo.dir, ["-i", "list"]);
1411 p.expect("fetching updates...\r\n")?; 1411 p.expect("fetching updates...\r\n")?;
1412 p.expect_eventually("\r\n")?; // some updates listed here 1412 p.expect_eventually("\r\n")?; // some updates listed here
1413 let mut c = p.expect_choice( 1413 let mut c = p.expect_choice(
@@ -1480,7 +1480,7 @@ mod when_main_branch_is_uptodate {
1480 let (_, test_repo) = create_proposals_with_first_rebased_and_repo_with_latest_main_and_unrebased_proposal()?; 1480 let (_, test_repo) = create_proposals_with_first_rebased_and_repo_with_latest_main_and_unrebased_proposal()?;
1481 test_repo.checkout("main")?; 1481 test_repo.checkout("main")?;
1482 1482
1483 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); 1483 let mut p = CliTester::new_from_dir(&test_repo.dir, ["-i", "list"]);
1484 p.expect("fetching updates...\r\n")?; 1484 p.expect("fetching updates...\r\n")?;
1485 p.expect_eventually("\r\n")?; // some updates listed here 1485 p.expect_eventually("\r\n")?; // some updates listed here
1486 let mut c = p.expect_choice( 1486 let mut c = p.expect_choice(