upleb.uk

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

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2023-11-01 00:00:00 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2023-11-01 00:00:00 +0000
commit0753e0bcdd3d606f8f0226a3980bcd817117abaa (patch)
tree8eab6115e2840cbf1c16de441e1168e3fdf18eb8
parent1b740dd135aafb52b94b710b3ae24e4aaaa99632 (diff)
feat(claim) create basic event
replacable event with root-commit, name, description and relay tags
-rw-r--r--src/main.rs7
-rw-r--r--src/sub_commands/claim.rs114
-rw-r--r--src/sub_commands/mod.rs1
-rw-r--r--src/sub_commands/prs/create.rs12
-rw-r--r--tests/claim.rs331
5 files changed, 458 insertions, 7 deletions
diff --git a/src/main.rs b/src/main.rs
index 68b0ed6..54ad748 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -33,7 +33,9 @@ pub struct Cli {
33enum Commands { 33enum Commands {
34 /// save encrypted nsec for future use 34 /// save encrypted nsec for future use
35 Login(sub_commands::login::SubCommandArgs), 35 Login(sub_commands::login::SubCommandArgs),
36 /// create and issue Prs 36 /// issue repository reference event as a maintainers
37 Claim(sub_commands::claim::SubCommandArgs),
38 /// create and issue prs
37 Prs(sub_commands::prs::SubCommandArgs), 39 Prs(sub_commands::prs::SubCommandArgs),
38} 40}
39 41
@@ -44,6 +46,9 @@ async fn main() -> Result<()> {
44 Commands::Login(args) => { 46 Commands::Login(args) => {
45 futures::executor::block_on(sub_commands::login::launch(&cli, args)) 47 futures::executor::block_on(sub_commands::login::launch(&cli, args))
46 } 48 }
49 Commands::Claim(args) => {
50 futures::executor::block_on(sub_commands::claim::launch(&cli, args))
51 }
47 Commands::Prs(args) => futures::executor::block_on(sub_commands::prs::launch(&cli, args)), 52 Commands::Prs(args) => futures::executor::block_on(sub_commands::prs::launch(&cli, args)),
48 } 53 }
49} 54}
diff --git a/src/sub_commands/claim.rs b/src/sub_commands/claim.rs
new file mode 100644
index 0000000..5eb66bb
--- /dev/null
+++ b/src/sub_commands/claim.rs
@@ -0,0 +1,114 @@
1use anyhow::{Context, Result};
2use nostr::{EventBuilder, Tag};
3
4use super::prs::create::send_events;
5#[cfg(not(test))]
6use crate::client::Client;
7#[cfg(test)]
8use crate::client::MockConnect;
9use crate::{
10 cli_interactor::{Interactor, InteractorPrompt, PromptInputParms},
11 client::Connect,
12 git::{Repo, RepoActions},
13 login, Cli,
14};
15
16#[derive(Debug, clap::Args)]
17pub struct SubCommandArgs {
18 #[clap(short, long)]
19 /// name of repository
20 title: Option<String>,
21 #[clap(short, long)]
22 /// optional description
23 description: Option<String>,
24}
25
26pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> {
27 let git_repo = Repo::discover().context("cannot find a git repository")?;
28
29 let (main_or_master_branch_name, _) = git_repo
30 .get_main_or_master_branch()
31 .context("no main or master branch")?;
32
33 let root_commit = git_repo
34 .get_root_commit(main_or_master_branch_name)
35 .context("failed to get root commit of the repository")?;
36
37 // TODO: check for empty repo
38 // TODO: check for existing maintaiers file
39 // TODO: check for other claims
40
41 let name = match &args.title {
42 Some(t) => t.clone(),
43 None => Interactor::default().input(PromptInputParms::default().with_prompt("name"))?,
44 };
45
46 let description = match &args.description {
47 Some(t) => t.clone(),
48 None => Interactor::default()
49 .input(PromptInputParms::default().with_prompt("description (Optional)"))?,
50 };
51
52 #[cfg(not(test))]
53 let mut client = Client::default();
54 #[cfg(test)]
55 let mut client = <MockConnect as std::default::Default>::default();
56
57 let (keys, user_ref) = login::launch(&cli_args.nsec, &cli_args.password, Some(&client)).await?;
58
59 client.set_keys(&keys).await;
60
61 // TODO: choice input defaulting to user relay list filtered by non paid relays
62 let repo_relays: Vec<String> = vec![
63 "ws://localhost:8055".to_string(),
64 "ws://localhost:8056".to_string(),
65 ];
66
67 println!("publishing repostory reference...");
68
69 send_events(
70 &client,
71 vec![generate_repo_event(
72 &name,
73 &description,
74 &repo_relays,
75 &root_commit.to_string(),
76 &keys,
77 )?],
78 user_ref.relays.write(),
79 repo_relays,
80 !cli_args.disable_cli_spinners,
81 )
82 .await?;
83
84 Ok(())
85}
86
87fn generate_repo_event(
88 name: &str,
89 description: &str,
90 relays: &[String],
91 // git_server: String,
92 root_commit: &String,
93 keys: &nostr::Keys,
94) -> Result<nostr::Event> {
95 EventBuilder::new(
96 nostr::event::Kind::Custom(30017),
97 "",
98 &[
99 vec![
100 Tag::Identifier(root_commit.to_string()),
101 Tag::Reference(format!("r-{root_commit}")),
102 Tag::Name(name.to_owned()),
103 Tag::Description(description.to_owned()),
104 ],
105 relays.iter().map(|r| Tag::Relay(r.into())).collect(),
106 // git_servers
107 // other maintainers
108 // code languages and hashtags
109 ]
110 .concat(),
111 )
112 .to_event(keys)
113 .context("failed to create pr event")
114}
diff --git a/src/sub_commands/mod.rs b/src/sub_commands/mod.rs
index 3c3da1d..6e99ca5 100644
--- a/src/sub_commands/mod.rs
+++ b/src/sub_commands/mod.rs
@@ -1,2 +1,3 @@
1pub mod claim;
1pub mod login; 2pub mod login;
2pub mod prs; 3pub mod prs;
diff --git a/src/sub_commands/prs/create.rs b/src/sub_commands/prs/create.rs
index aad80f4..d82f53e 100644
--- a/src/sub_commands/prs/create.rs
+++ b/src/sub_commands/prs/create.rs
@@ -105,6 +105,11 @@ pub async fn launch(
105 "ws://localhost:8056".to_string(), 105 "ws://localhost:8056".to_string(),
106 ]; 106 ];
107 107
108 println!(
109 "posting 1 pull request with {} commits...",
110 events.len() - 1
111 );
112
108 send_events( 113 send_events(
109 &client, 114 &client,
110 events, 115 events,
@@ -118,7 +123,7 @@ pub async fn launch(
118 Ok(()) 123 Ok(())
119} 124}
120 125
121async fn send_events( 126pub async fn send_events(
122 #[cfg(test)] client: &crate::client::MockConnect, 127 #[cfg(test)] client: &crate::client::MockConnect,
123 #[cfg(not(test))] client: &Client, 128 #[cfg(not(test))] client: &Client,
124 events: Vec<nostr::Event>, 129 events: Vec<nostr::Event>,
@@ -128,11 +133,6 @@ async fn send_events(
128) -> Result<()> { 133) -> Result<()> {
129 let (_, _, _, all) = unique_and_duplicate_all(&my_write_relays, &repo_read_relays); 134 let (_, _, _, all) = unique_and_duplicate_all(&my_write_relays, &repo_read_relays);
130 135
131 println!(
132 "posting 1 pull request with {} commits...",
133 events.len() - 1
134 );
135
136 let m = MultiProgress::new(); 136 let m = MultiProgress::new();
137 let pb_style = ProgressStyle::with_template(if animate { 137 let pb_style = ProgressStyle::with_template(if animate {
138 " {spinner} {prefix} {bar} {pos}/{len} {msg}" 138 " {spinner} {prefix} {bar} {pos}/{len} {msg}"
diff --git a/tests/claim.rs b/tests/claim.rs
new file mode 100644
index 0000000..ec62c0b
--- /dev/null
+++ b/tests/claim.rs
@@ -0,0 +1,331 @@
1use anyhow::Result;
2use serial_test::serial;
3use test_utils::{git::GitTestRepo, *};
4
5#[test]
6fn when_no_main_or_master_branch_return_error() -> Result<()> {
7 let test_repo = GitTestRepo::new("notmain")?;
8 test_repo.populate()?;
9 let mut p = CliTester::new_from_dir(&test_repo.dir, ["claim"]);
10 p.expect("Error: no main or master branch")?;
11 Ok(())
12}
13
14mod sends_repoistory_to_relays {
15 use futures::join;
16 use test_utils::relay::Relay;
17
18 use super::*;
19
20 static REPOSITORY_KIND: u64 = 30017;
21
22 fn prep_git_repo() -> Result<GitTestRepo> {
23 let test_repo = GitTestRepo::default();
24 test_repo.populate()?;
25 Ok(test_repo)
26 }
27
28 fn cli_tester_claim(git_repo: &GitTestRepo) -> CliTester {
29 CliTester::new_from_dir(
30 &git_repo.dir,
31 [
32 "--nsec",
33 TEST_KEY_1_NSEC,
34 "--password",
35 TEST_PASSWORD,
36 "--disable-cli-spinners",
37 "claim",
38 "--title",
39 "example-name",
40 "--description",
41 "example-description",
42 ],
43 )
44 }
45
46 fn expect_msgs_first(p: &mut CliTester) -> Result<()> {
47 p.expect("searching for your details...\r\n")?;
48 p.expect("\r")?;
49 p.expect("logged in as fred\r\n")?;
50 // // p.expect("searching for existing claims on repository...\r\n")?;
51 p.expect("publishing repostory reference...\r\n")?;
52 Ok(())
53 }
54
55 async fn prep_run_claim() -> Result<(
56 Relay<'static>,
57 Relay<'static>,
58 Relay<'static>,
59 Relay<'static>,
60 Relay<'static>,
61 )> {
62 let git_repo = prep_git_repo()?;
63 // fallback (51,52) user write (53, 55) repo (55, 56)
64 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
65 Relay::new(
66 8051,
67 None,
68 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
69 relay.respond_events(
70 client_id,
71 &subscription_id,
72 &vec![
73 generate_test_key_1_metadata_event("fred"),
74 generate_test_key_1_relay_list_event(),
75 ],
76 )?;
77 Ok(())
78 }),
79 ),
80 Relay::new(8052, None, None),
81 Relay::new(8053, None, None),
82 Relay::new(8055, None, None),
83 Relay::new(8056, None, None),
84 );
85
86 // // check relay had the right number of events
87 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
88 let mut p = cli_tester_claim(&git_repo);
89 p.expect_end_eventually()?;
90 for p in [51, 52, 53, 55, 56] {
91 relay::shutdown_relay(8000 + p)?;
92 }
93 Ok(())
94 });
95
96 // launch relay
97 let _ = join!(
98 r51.listen_until_close(),
99 r52.listen_until_close(),
100 r53.listen_until_close(),
101 r55.listen_until_close(),
102 r56.listen_until_close(),
103 );
104 cli_tester_handle.join().unwrap()?;
105 Ok((r51, r52, r53, r55, r56))
106 }
107
108 mod sent_to_correct_relays {
109 use super::*;
110
111 #[test]
112 #[serial]
113 fn only_1_repository_kind_event_sent_to_user_relays() -> Result<()> {
114 let (_, _, r53, r55, _) = futures::executor::block_on(prep_run_claim())?;
115 for relay in [&r53, &r55] {
116 assert_eq!(
117 relay
118 .events
119 .iter()
120 .filter(|e| e.kind.as_u64().eq(&REPOSITORY_KIND))
121 .count(),
122 1,
123 );
124 }
125 Ok(())
126 }
127
128 #[test]
129 #[serial]
130 fn only_1_repository_kind_event_sent_to_repo_relays() -> Result<()> {
131 let (_, _, _, r55, r56) = futures::executor::block_on(prep_run_claim())?;
132 for relay in [&r55, &r56] {
133 assert_eq!(
134 relay
135 .events
136 .iter()
137 .filter(|e| e.kind.as_u64().eq(&REPOSITORY_KIND))
138 .count(),
139 1,
140 );
141 }
142 Ok(())
143 }
144
145 #[test]
146 #[serial]
147 fn event_not_sent_to_fallback_relay() -> Result<()> {
148 let (r51, r52, _, _, _) = futures::executor::block_on(prep_run_claim())?;
149 for relay in [&r51, &r52] {
150 assert_eq!(
151 relay
152 .events
153 .iter()
154 .filter(|e| e.kind.as_u64().eq(&REPOSITORY_KIND))
155 .count(),
156 0,
157 );
158 }
159 Ok(())
160 }
161 }
162
163 mod tags {
164 use super::*;
165
166 #[test]
167 #[serial]
168 fn d_replaceable_event_identifier() -> Result<()> {
169 let (_, _, r53, r55, r56) = futures::executor::block_on(prep_run_claim())?;
170 for relay in [&r53, &r55, &r56] {
171 let event: &nostr::Event = relay
172 .events
173 .iter()
174 .find(|e| e.kind.as_u64().eq(&REPOSITORY_KIND))
175 .unwrap();
176
177 assert!(event.tags.iter().any(|t| t.as_vec()[0].eq("d")
178 && t.as_vec()[1].eq("9ee507fc4357d7ee16a5d8901bedcd103f23c17d")));
179 }
180 Ok(())
181 }
182
183 #[test]
184 #[serial]
185 fn root_commit() -> Result<()> {
186 let (_, _, r53, r55, r56) = futures::executor::block_on(prep_run_claim())?;
187 for relay in [&r53, &r55, &r56] {
188 let event: &nostr::Event = relay
189 .events
190 .iter()
191 .find(|e| e.kind.as_u64().eq(&REPOSITORY_KIND))
192 .unwrap();
193
194 // root commit 'r' tag with 'r-' prefix
195 assert!(event.tags.iter().any(|t| t.as_vec()[0].eq("r")
196 && t.as_vec()[1].eq("r-9ee507fc4357d7ee16a5d8901bedcd103f23c17d")));
197 }
198 Ok(())
199 }
200
201 #[test]
202 #[serial]
203 fn name() -> Result<()> {
204 let (_, _, r53, r55, r56) = futures::executor::block_on(prep_run_claim())?;
205 for relay in [&r53, &r55, &r56] {
206 let event: &nostr::Event = relay
207 .events
208 .iter()
209 .find(|e| e.kind.as_u64().eq(&REPOSITORY_KIND))
210 .unwrap();
211
212 assert!(
213 event
214 .tags
215 .iter()
216 .any(|t| t.as_vec()[0].eq("name") && t.as_vec()[1].eq("example-name"))
217 );
218 }
219 Ok(())
220 }
221
222 #[test]
223 #[serial]
224 fn description() -> Result<()> {
225 let (_, _, r53, r55, r56) = futures::executor::block_on(prep_run_claim())?;
226 for relay in [&r53, &r55, &r56] {
227 let event: &nostr::Event = relay
228 .events
229 .iter()
230 .find(|e| e.kind.as_u64().eq(&REPOSITORY_KIND))
231 .unwrap();
232
233 assert!(
234 event.tags.iter().any(|t| t.as_vec()[0].eq("description")
235 && t.as_vec()[1].eq("example-description"))
236 );
237 }
238 Ok(())
239 }
240
241 #[test]
242 #[serial]
243 fn relays() -> Result<()> {
244 let (_, _, r53, r55, r56) = futures::executor::block_on(prep_run_claim())?;
245 for relay in [&r53, &r55, &r56] {
246 let event: &nostr::Event = relay
247 .events
248 .iter()
249 .find(|e| e.kind.as_u64().eq(&REPOSITORY_KIND))
250 .unwrap();
251
252 let relay_tags = event
253 .tags
254 .iter()
255 .filter(|t| t.as_vec()[0].eq("relay"))
256 .collect::<Vec<&nostr::Tag>>();
257 assert_eq!(relay_tags[0].as_vec()[1], "ws://localhost:8055");
258 assert_eq!(relay_tags[1].as_vec()[1], "ws://localhost:8056");
259 }
260 Ok(())
261 }
262 }
263
264 mod cli_ouput {
265 use super::*;
266
267 async fn run_test_async() -> Result<()> {
268 let git_repo = prep_git_repo()?;
269
270 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
271 Relay::new(
272 8051,
273 None,
274 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
275 relay.respond_events(
276 client_id,
277 &subscription_id,
278 &vec![
279 generate_test_key_1_metadata_event("fred"),
280 generate_test_key_1_relay_list_event(),
281 ],
282 )?;
283 Ok(())
284 }),
285 ),
286 Relay::new(8052, None, None),
287 Relay::new(8053, None, None),
288 Relay::new(8055, None, None),
289 Relay::new(8056, None, None),
290 );
291
292 // // check relay had the right number of events
293 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
294 let mut p = cli_tester_claim(&git_repo);
295 expect_msgs_first(&mut p)?;
296 relay::expect_send_with_progress(
297 &mut p,
298 vec![
299 (" [my-relay] [repo-relay] ws://localhost:8055", true, ""),
300 (" [my-relay] ws://localhost:8053", true, ""),
301 (" [repo-relay] ws://localhost:8056", true, ""),
302 ],
303 1,
304 )?;
305 p.expect_end_with_whitespace()?;
306 for p in [51, 52, 53, 55, 56] {
307 relay::shutdown_relay(8000 + p)?;
308 }
309 Ok(())
310 });
311
312 // launch relay
313 let _ = join!(
314 r51.listen_until_close(),
315 r52.listen_until_close(),
316 r53.listen_until_close(),
317 r55.listen_until_close(),
318 r56.listen_until_close(),
319 );
320 cli_tester_handle.join().unwrap()?;
321 Ok(())
322 }
323
324 #[test]
325 #[serial]
326 fn check_cli_output() -> Result<()> {
327 futures::executor::block_on(run_test_async())?;
328 Ok(())
329 }
330 }
331}