diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2023-12-12 00:00:00 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2023-12-12 00:00:00 +0000 |
| commit | 3e52ecb609f8424cffb2e6398c599aa78224825a (patch) | |
| tree | 5c199510c10e206d6c6801dbabdae9691c03341c | |
| parent | 6d3c9218d2d3320f5d7fb9b9ede8750e947b70e8 (diff) | |
feat(claim) create yaml add maintainers and relays
- create yaml file with maintainers and relays
- add maintainers to repo event
- add current user as maintainer
- custom repo relays from cli argument
- save git-server in repo event
| -rw-r--r-- | Cargo.lock | 20 | ||||
| -rw-r--r-- | Cargo.toml | 1 | ||||
| -rw-r--r-- | src/git.rs | 33 | ||||
| -rw-r--r-- | src/repo_ref.rs | 184 | ||||
| -rw-r--r-- | src/sub_commands/claim.rs | 52 | ||||
| -rw-r--r-- | src/sub_commands/prs/create.rs | 2 | ||||
| -rw-r--r-- | src/sub_commands/prs/list.rs | 1 | ||||
| -rw-r--r-- | src/sub_commands/pull.rs | 1 | ||||
| -rw-r--r-- | src/sub_commands/push.rs | 1 | ||||
| -rw-r--r-- | test_utils/src/git.rs | 5 | ||||
| -rw-r--r-- | tests/claim.rs | 785 |
11 files changed, 829 insertions, 256 deletions
| @@ -1601,6 +1601,7 @@ dependencies = [ | |||
| 1601 | "scrypt", | 1601 | "scrypt", |
| 1602 | "serde", | 1602 | "serde", |
| 1603 | "serde_json", | 1603 | "serde_json", |
| 1604 | "serde_yaml", | ||
| 1604 | "serial_test", | 1605 | "serial_test", |
| 1605 | "test_utils", | 1606 | "test_utils", |
| 1606 | "tokio", | 1607 | "tokio", |
| @@ -2555,6 +2556,19 @@ dependencies = [ | |||
| 2555 | ] | 2556 | ] |
| 2556 | 2557 | ||
| 2557 | [[package]] | 2558 | [[package]] |
| 2559 | name = "serde_yaml" | ||
| 2560 | version = "0.9.27" | ||
| 2561 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2562 | checksum = "3cc7a1570e38322cfe4154732e5110f887ea57e22b76f4bfd32b5bdd3368666c" | ||
| 2563 | dependencies = [ | ||
| 2564 | "indexmap 2.1.0", | ||
| 2565 | "itoa", | ||
| 2566 | "ryu", | ||
| 2567 | "serde", | ||
| 2568 | "unsafe-libyaml", | ||
| 2569 | ] | ||
| 2570 | |||
| 2571 | [[package]] | ||
| 2558 | name = "serial_test" | 2572 | name = "serial_test" |
| 2559 | version = "2.0.0" | 2573 | version = "2.0.0" |
| 2560 | source = "registry+https://github.com/rust-lang/crates.io-index" | 2574 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -3028,6 +3042,12 @@ dependencies = [ | |||
| 3028 | ] | 3042 | ] |
| 3029 | 3043 | ||
| 3030 | [[package]] | 3044 | [[package]] |
| 3045 | name = "unsafe-libyaml" | ||
| 3046 | version = "0.2.9" | ||
| 3047 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3048 | checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" | ||
| 3049 | |||
| 3050 | [[package]] | ||
| 3031 | name = "untrusted" | 3051 | name = "untrusted" |
| 3032 | version = "0.9.0" | 3052 | version = "0.9.0" |
| 3033 | source = "registry+https://github.com/rust-lang/crates.io-index" | 3053 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -29,6 +29,7 @@ passwords = "3.1.13" | |||
| 29 | scrypt = "0.11.0" | 29 | scrypt = "0.11.0" |
| 30 | serde = { version = "1.0.181", features = ["derive"] } | 30 | serde = { version = "1.0.181", features = ["derive"] } |
| 31 | serde_json = "1.0.105" | 31 | serde_json = "1.0.105" |
| 32 | serde_yaml = "0.9.27" | ||
| 32 | tokio = "1.33.0" | 33 | tokio = "1.33.0" |
| 33 | zeroize = "1.6.0" | 34 | zeroize = "1.6.0" |
| 34 | 35 | ||
| @@ -1,6 +1,6 @@ | |||
| 1 | use std::env::current_dir; | ||
| 2 | #[cfg(test)] | 1 | #[cfg(test)] |
| 3 | use std::path::PathBuf; | 2 | use std::path::PathBuf; |
| 3 | use std::{env::current_dir, path::Path}; | ||
| 4 | 4 | ||
| 5 | use anyhow::{bail, Context, Result}; | 5 | use anyhow::{bail, Context, Result}; |
| 6 | use git2::{Oid, Revwalk}; | 6 | use git2::{Oid, Revwalk}; |
| @@ -30,6 +30,8 @@ impl Repo { | |||
| 30 | // pub type Sha1 = [u8; 20]; | 30 | // pub type Sha1 = [u8; 20]; |
| 31 | 31 | ||
| 32 | pub trait RepoActions { | 32 | pub trait RepoActions { |
| 33 | fn get_path(&self) -> Result<&Path>; | ||
| 34 | fn get_origin_url(&self) -> Result<String>; | ||
| 33 | fn get_local_branch_names(&self) -> Result<Vec<String>>; | 35 | fn get_local_branch_names(&self) -> Result<Vec<String>>; |
| 34 | fn get_main_or_master_branch(&self) -> Result<(&str, Sha1Hash)>; | 36 | fn get_main_or_master_branch(&self) -> Result<(&str, Sha1Hash)>; |
| 35 | fn get_checked_out_branch_name(&self) -> Result<String>; | 37 | fn get_checked_out_branch_name(&self) -> Result<String>; |
| @@ -61,6 +63,23 @@ pub trait RepoActions { | |||
| 61 | } | 63 | } |
| 62 | 64 | ||
| 63 | impl RepoActions for Repo { | 65 | impl RepoActions for Repo { |
| 66 | fn get_path(&self) -> Result<&Path> { | ||
| 67 | self.git_repo | ||
| 68 | .path() | ||
| 69 | .parent() | ||
| 70 | .context("cannot find repositiory path as .git has no parent") | ||
| 71 | } | ||
| 72 | |||
| 73 | fn get_origin_url(&self) -> Result<String> { | ||
| 74 | Ok(self | ||
| 75 | .git_repo | ||
| 76 | .find_remote("origin") | ||
| 77 | .context("cannot find origin")? | ||
| 78 | .url() | ||
| 79 | .context("cannot find origin url")? | ||
| 80 | .to_string()) | ||
| 81 | } | ||
| 82 | |||
| 64 | fn get_main_or_master_branch(&self) -> Result<(&str, Sha1Hash)> { | 83 | fn get_main_or_master_branch(&self) -> Result<(&str, Sha1Hash)> { |
| 65 | let main_branch_name = { | 84 | let main_branch_name = { |
| 66 | let local_branches = self | 85 | let local_branches = self |
| @@ -860,6 +879,18 @@ mod tests { | |||
| 860 | } | 879 | } |
| 861 | } | 880 | } |
| 862 | 881 | ||
| 882 | mod get_origin_url { | ||
| 883 | use super::*; | ||
| 884 | |||
| 885 | #[test] | ||
| 886 | fn returns_origin_url() -> Result<()> { | ||
| 887 | let test_repo = GitTestRepo::default(); | ||
| 888 | test_repo.add_remote("origin", "https://localhost:1000")?; | ||
| 889 | let git_repo = Repo::from_path(&test_repo.dir)?; | ||
| 890 | assert_eq!(git_repo.get_origin_url()?, "https://localhost:1000"); | ||
| 891 | Ok(()) | ||
| 892 | } | ||
| 893 | } | ||
| 863 | mod get_checked_out_branch_name { | 894 | mod get_checked_out_branch_name { |
| 864 | use super::*; | 895 | use super::*; |
| 865 | 896 | ||
diff --git a/src/repo_ref.rs b/src/repo_ref.rs index a92b5b3..a5b35c9 100644 --- a/src/repo_ref.rs +++ b/src/repo_ref.rs | |||
| @@ -1,20 +1,26 @@ | |||
| 1 | use std::{fs::File, io::BufReader, str::FromStr}; | ||
| 2 | |||
| 1 | use anyhow::{bail, Context, Result}; | 3 | use anyhow::{bail, Context, Result}; |
| 2 | use nostr::Tag; | 4 | use nostr::{secp256k1::XOnlyPublicKey, FromBech32, Tag, ToBech32}; |
| 5 | use serde::{Deserialize, Serialize}; | ||
| 3 | 6 | ||
| 4 | #[cfg(not(test))] | 7 | #[cfg(not(test))] |
| 5 | use crate::client::Client; | 8 | use crate::client::Client; |
| 6 | use crate::client::Connect; | ||
| 7 | #[cfg(test)] | 9 | #[cfg(test)] |
| 8 | use crate::client::MockConnect; | 10 | use crate::client::MockConnect; |
| 11 | use crate::{ | ||
| 12 | client::Connect, | ||
| 13 | git::{Repo, RepoActions}, | ||
| 14 | }; | ||
| 9 | 15 | ||
| 10 | #[derive(Default)] | 16 | #[derive(Default)] |
| 11 | pub struct RepoRef { | 17 | pub struct RepoRef { |
| 12 | pub name: String, | 18 | pub name: String, |
| 13 | pub description: String, | 19 | pub description: String, |
| 14 | pub root_commit: String, | 20 | pub root_commit: String, |
| 21 | pub git_server: String, | ||
| 15 | pub relays: Vec<String>, | 22 | pub relays: Vec<String>, |
| 16 | // git_server: String, | 23 | pub maintainers: Vec<XOnlyPublicKey>, |
| 17 | // other maintainers | ||
| 18 | // code languages and hashtags | 24 | // code languages and hashtags |
| 19 | } | 25 | } |
| 20 | 26 | ||
| @@ -35,6 +41,10 @@ impl TryFrom<nostr::Event> for RepoRef { | |||
| 35 | r.description = t.as_vec()[1].clone(); | 41 | r.description = t.as_vec()[1].clone(); |
| 36 | } | 42 | } |
| 37 | 43 | ||
| 44 | if let Some(t) = event.tags.iter().find(|t| t.as_vec()[0].eq("git-server")) { | ||
| 45 | r.git_server = t.as_vec()[1].clone(); | ||
| 46 | } | ||
| 47 | |||
| 38 | if let Some(t) = event.tags.iter().find(|t| t.as_vec()[0].eq("d")) { | 48 | if let Some(t) = event.tags.iter().find(|t| t.as_vec()[0].eq("d")) { |
| 39 | r.root_commit = t.as_vec()[1].clone(); | 49 | r.root_commit = t.as_vec()[1].clone(); |
| 40 | } | 50 | } |
| @@ -46,6 +56,15 @@ impl TryFrom<nostr::Event> for RepoRef { | |||
| 46 | .map(|t| t.as_vec()[1].clone()) | 56 | .map(|t| t.as_vec()[1].clone()) |
| 47 | .collect(); | 57 | .collect(); |
| 48 | 58 | ||
| 59 | for tag in event.tags.iter().filter(|t| t.as_vec()[0].eq("p")) { | ||
| 60 | let pk = tag.as_vec()[1].clone(); | ||
| 61 | r.maintainers.push( | ||
| 62 | nostr_sdk::prelude::XOnlyPublicKey::from_str(&pk) | ||
| 63 | .context(format!("cannot convert {pk} into a valid nostr public key")) | ||
| 64 | .context("invalid repository event")?, | ||
| 65 | ); | ||
| 66 | } | ||
| 67 | |||
| 49 | Ok(r) | 68 | Ok(r) |
| 50 | } | 69 | } |
| 51 | } | 70 | } |
| @@ -62,10 +81,17 @@ impl RepoRef { | |||
| 62 | Tag::Reference(format!("r-{}", self.root_commit)), | 81 | Tag::Reference(format!("r-{}", self.root_commit)), |
| 63 | Tag::Name(self.name.clone()), | 82 | Tag::Name(self.name.clone()), |
| 64 | Tag::Description(self.description.clone()), | 83 | Tag::Description(self.description.clone()), |
| 84 | Tag::Generic( | ||
| 85 | nostr::TagKind::Custom("git-server".to_string()), | ||
| 86 | vec![self.git_server.clone()], | ||
| 87 | ), | ||
| 88 | Tag::Reference(self.git_server.clone()), | ||
| 65 | ], | 89 | ], |
| 66 | self.relays.iter().map(|r| Tag::Relay(r.into())).collect(), | 90 | self.relays.iter().map(|r| Tag::Relay(r.into())).collect(), |
| 67 | // git_servers | 91 | self.maintainers |
| 68 | // other maintainers | 92 | .iter() |
| 93 | .map(|pk| Tag::PubKey(*pk, None)) | ||
| 94 | .collect(), | ||
| 69 | // code languages and hashtags | 95 | // code languages and hashtags |
| 70 | ] | 96 | ] |
| 71 | .concat(), | 97 | .concat(), |
| @@ -76,24 +102,30 @@ impl RepoRef { | |||
| 76 | } | 102 | } |
| 77 | 103 | ||
| 78 | pub async fn fetch( | 104 | pub async fn fetch( |
| 105 | git_repo: &Repo, | ||
| 79 | root_commit: String, | 106 | root_commit: String, |
| 80 | #[cfg(test)] client: &MockConnect, | 107 | #[cfg(test)] client: &MockConnect, |
| 81 | #[cfg(not(test))] client: &Client, | 108 | #[cfg(not(test))] client: &Client, |
| 82 | // TODO: more rubust way of finding repo events | 109 | // TODO: more rubust way of finding repo events |
| 83 | relays: Vec<String>, | 110 | fallback_relays: Vec<String>, |
| 84 | ) -> Result<RepoRef> { | 111 | ) -> Result<RepoRef> { |
| 85 | // TODO: fetch relay information from file | 112 | let repo_config = get_repo_config_from_yaml(git_repo); |
| 86 | 113 | ||
| 87 | let events: Vec<nostr::Event> = client | 114 | // TODO: check events only from maintainers. get relay list of maintainters. |
| 88 | .get_events( | 115 | // check those relays. |
| 89 | relays, | 116 | |
| 90 | vec![ | 117 | let mut repo_event_filter = nostr::Filter::default() |
| 91 | nostr::Filter::default() | 118 | .kind(nostr::Kind::Custom(REPO_REF_KIND)) |
| 92 | .kind(nostr::Kind::Custom(REPO_REF_KIND)) | 119 | .identifier(root_commit); |
| 93 | .identifier(root_commit), | 120 | |
| 94 | ], | 121 | let mut relays = fallback_relays; |
| 95 | ) | 122 | if let Ok(repo_config) = repo_config { |
| 96 | .await?; | 123 | repo_event_filter = |
| 124 | repo_event_filter.pubkeys(extract_pks(repo_config.maintainers.clone())?); | ||
| 125 | relays = repo_config.relays.clone(); | ||
| 126 | } | ||
| 127 | |||
| 128 | let events: Vec<nostr::Event> = client.get_events(relays, vec![repo_event_filter]).await?; | ||
| 97 | 129 | ||
| 98 | RepoRef::try_from( | 130 | RepoRef::try_from( |
| 99 | events | 131 | events |
| @@ -105,6 +137,68 @@ pub async fn fetch( | |||
| 105 | ) | 137 | ) |
| 106 | } | 138 | } |
| 107 | 139 | ||
| 140 | #[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq, Eq)] | ||
| 141 | pub struct RepoConfigYaml { | ||
| 142 | pub maintainers: Vec<String>, | ||
| 143 | pub relays: Vec<String>, | ||
| 144 | } | ||
| 145 | |||
| 146 | pub fn get_repo_config_from_yaml(git_repo: &Repo) -> Result<RepoConfigYaml> { | ||
| 147 | let path = git_repo.get_path()?.join("maintainers.yaml"); | ||
| 148 | let file = File::open(path) | ||
| 149 | .context("should open maintainers.yaml if it exists") | ||
| 150 | .context("maintainers.yaml doesnt exist")?; | ||
| 151 | let reader = BufReader::new(file); | ||
| 152 | let repo_config_yaml: RepoConfigYaml = serde_yaml::from_reader(reader) | ||
| 153 | .context("should read maintainers.yaml with serde_yaml") | ||
| 154 | .context("maintainers.yaml incorrectly formatted")?; | ||
| 155 | Ok(repo_config_yaml) | ||
| 156 | } | ||
| 157 | |||
| 158 | pub fn extract_pks(pk_strings: Vec<String>) -> Result<Vec<XOnlyPublicKey>> { | ||
| 159 | let mut pks: Vec<XOnlyPublicKey> = vec![]; | ||
| 160 | for s in pk_strings { | ||
| 161 | pks.push( | ||
| 162 | nostr_sdk::prelude::XOnlyPublicKey::from_bech32(s.clone()) | ||
| 163 | .context(format!("cannot convert {s} into a valid nostr public key"))?, | ||
| 164 | ); | ||
| 165 | } | ||
| 166 | Ok(pks) | ||
| 167 | } | ||
| 168 | |||
| 169 | pub fn save_repo_config_to_yaml( | ||
| 170 | git_repo: &Repo, | ||
| 171 | maintainers: Vec<XOnlyPublicKey>, | ||
| 172 | relays: Vec<String>, | ||
| 173 | ) -> Result<()> { | ||
| 174 | let path = git_repo.get_path()?.join("maintainers.yaml"); | ||
| 175 | let file = if path.exists() { | ||
| 176 | std::fs::OpenOptions::new() | ||
| 177 | .create(true) | ||
| 178 | .write(true) | ||
| 179 | .truncate(true) | ||
| 180 | .open(path) | ||
| 181 | .context("cannot open maintainers.yaml file with write and truncate options")? | ||
| 182 | } else { | ||
| 183 | std::fs::File::create(path).context("cannot create maintainers.yaml file")? | ||
| 184 | }; | ||
| 185 | let mut maintainers_npubs = vec![]; | ||
| 186 | for m in maintainers { | ||
| 187 | maintainers_npubs.push( | ||
| 188 | m.to_bech32() | ||
| 189 | .context("cannot convert public key into npub")?, | ||
| 190 | ); | ||
| 191 | } | ||
| 192 | serde_yaml::to_writer( | ||
| 193 | file, | ||
| 194 | &RepoConfigYaml { | ||
| 195 | maintainers: maintainers_npubs, | ||
| 196 | relays, | ||
| 197 | }, | ||
| 198 | ) | ||
| 199 | .context("cannot write maintainers to maintainers.yaml file serde_yaml") | ||
| 200 | } | ||
| 201 | |||
| 108 | #[cfg(test)] | 202 | #[cfg(test)] |
| 109 | mod tests { | 203 | mod tests { |
| 110 | use test_utils::*; | 204 | use test_utils::*; |
| @@ -116,7 +210,9 @@ mod tests { | |||
| 116 | name: "test name".to_string(), | 210 | name: "test name".to_string(), |
| 117 | description: "test description".to_string(), | 211 | description: "test description".to_string(), |
| 118 | root_commit: "23471389461".to_string(), | 212 | root_commit: "23471389461".to_string(), |
| 213 | git_server: "https://localhost:1000".to_string(), | ||
| 119 | relays: vec!["ws://relay1.io".to_string(), "ws://relay2.io".to_string()], | 214 | relays: vec!["ws://relay1.io".to_string(), "ws://relay2.io".to_string()], |
| 215 | maintainers: vec![TEST_KEY_1_KEYS.public_key(), TEST_KEY_2_KEYS.public_key()], | ||
| 120 | } | 216 | } |
| 121 | .to_event(&TEST_KEY_1_KEYS) | 217 | .to_event(&TEST_KEY_1_KEYS) |
| 122 | .unwrap() | 218 | .unwrap() |
| @@ -146,12 +242,28 @@ mod tests { | |||
| 146 | } | 242 | } |
| 147 | 243 | ||
| 148 | #[test] | 244 | #[test] |
| 245 | fn git_server() { | ||
| 246 | assert_eq!( | ||
| 247 | RepoRef::try_from(create()).unwrap().git_server, | ||
| 248 | "https://localhost:1000", | ||
| 249 | ) | ||
| 250 | } | ||
| 251 | |||
| 252 | #[test] | ||
| 149 | fn relays() { | 253 | fn relays() { |
| 150 | assert_eq!( | 254 | assert_eq!( |
| 151 | RepoRef::try_from(create()).unwrap().relays, | 255 | RepoRef::try_from(create()).unwrap().relays, |
| 152 | vec!["ws://relay1.io".to_string(), "ws://relay2.io".to_string()], | 256 | vec!["ws://relay1.io".to_string(), "ws://relay2.io".to_string()], |
| 153 | ) | 257 | ) |
| 154 | } | 258 | } |
| 259 | |||
| 260 | #[test] | ||
| 261 | fn maintainers() { | ||
| 262 | assert_eq!( | ||
| 263 | RepoRef::try_from(create()).unwrap().maintainers, | ||
| 264 | vec![TEST_KEY_1_KEYS.public_key(), TEST_KEY_2_KEYS.public_key()], | ||
| 265 | ) | ||
| 266 | } | ||
| 155 | } | 267 | } |
| 156 | 268 | ||
| 157 | mod to_event { | 269 | mod to_event { |
| @@ -186,6 +298,21 @@ mod tests { | |||
| 186 | } | 298 | } |
| 187 | 299 | ||
| 188 | #[test] | 300 | #[test] |
| 301 | fn git_server() { | ||
| 302 | assert!(create().tags.iter().any(|t| t.as_vec()[0].eq("git-server") | ||
| 303 | && t.as_vec()[1].eq("https://localhost:1000"))) | ||
| 304 | } | ||
| 305 | |||
| 306 | #[test] | ||
| 307 | fn git_server_as_reference() { | ||
| 308 | assert!( | ||
| 309 | create().tags.iter().any( | ||
| 310 | |t| t.as_vec()[0].eq("r") && t.as_vec()[1].eq("https://localhost:1000") | ||
| 311 | ) | ||
| 312 | ) | ||
| 313 | } | ||
| 314 | |||
| 315 | #[test] | ||
| 189 | fn root_commit_as_reference() { | 316 | fn root_commit_as_reference() { |
| 190 | assert!( | 317 | assert!( |
| 191 | create() | 318 | create() |
| @@ -209,8 +336,27 @@ mod tests { | |||
| 209 | } | 336 | } |
| 210 | 337 | ||
| 211 | #[test] | 338 | #[test] |
| 339 | fn maintainers() { | ||
| 340 | let event = create(); | ||
| 341 | let p_tags = event | ||
| 342 | .tags | ||
| 343 | .iter() | ||
| 344 | .filter(|t| t.as_vec()[0].eq("p")) | ||
| 345 | .collect::<Vec<&nostr::Tag>>(); | ||
| 346 | assert_eq!(p_tags[0].as_vec().len(), 2); | ||
| 347 | assert_eq!( | ||
| 348 | p_tags[0].as_vec()[1], | ||
| 349 | TEST_KEY_1_KEYS.public_key().to_string() | ||
| 350 | ); | ||
| 351 | assert_eq!( | ||
| 352 | p_tags[1].as_vec()[1], | ||
| 353 | TEST_KEY_2_KEYS.public_key().to_string() | ||
| 354 | ); | ||
| 355 | } | ||
| 356 | |||
| 357 | #[test] | ||
| 212 | fn no_other_tags() { | 358 | fn no_other_tags() { |
| 213 | assert_eq!(create().tags.len(), 6) | 359 | assert_eq!(create().tags.len(), 10) |
| 214 | } | 360 | } |
| 215 | } | 361 | } |
| 216 | } | 362 | } |
diff --git a/src/sub_commands/claim.rs b/src/sub_commands/claim.rs index c0d26dd..2573267 100644 --- a/src/sub_commands/claim.rs +++ b/src/sub_commands/claim.rs | |||
| @@ -10,7 +10,7 @@ use crate::{ | |||
| 10 | client::Connect, | 10 | client::Connect, |
| 11 | git::{Repo, RepoActions}, | 11 | git::{Repo, RepoActions}, |
| 12 | login, | 12 | login, |
| 13 | repo_ref::RepoRef, | 13 | repo_ref::{extract_pks, get_repo_config_from_yaml, save_repo_config_to_yaml, RepoRef}, |
| 14 | Cli, | 14 | Cli, |
| 15 | }; | 15 | }; |
| 16 | 16 | ||
| @@ -22,6 +22,9 @@ pub struct SubCommandArgs { | |||
| 22 | #[clap(short, long)] | 22 | #[clap(short, long)] |
| 23 | /// optional description | 23 | /// optional description |
| 24 | description: Option<String>, | 24 | description: Option<String>, |
| 25 | #[clap(short, long, value_parser, num_args = 1..)] | ||
| 26 | /// relays contributors push patches and comments to | ||
| 27 | relays: Vec<String>, | ||
| 25 | } | 28 | } |
| 26 | 29 | ||
| 27 | pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> { | 30 | pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> { |
| @@ -37,6 +40,8 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> { | |||
| 37 | 40 | ||
| 38 | // TODO: check for empty repo | 41 | // TODO: check for empty repo |
| 39 | // TODO: check for existing maintaiers file | 42 | // TODO: check for existing maintaiers file |
| 43 | |||
| 44 | let repo_config_result = get_repo_config_from_yaml(&git_repo); | ||
| 40 | // TODO: check for other claims | 45 | // TODO: check for other claims |
| 41 | 46 | ||
| 42 | let name = match &args.title { | 47 | let name = match &args.title { |
| @@ -50,6 +55,8 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> { | |||
| 50 | .input(PromptInputParms::default().with_prompt("description (Optional)"))?, | 55 | .input(PromptInputParms::default().with_prompt("description (Optional)"))?, |
| 51 | }; | 56 | }; |
| 52 | 57 | ||
| 58 | let git_server = git_repo.get_origin_url()?; | ||
| 59 | |||
| 53 | #[cfg(not(test))] | 60 | #[cfg(not(test))] |
| 54 | let mut client = Client::default(); | 61 | let mut client = Client::default(); |
| 55 | #[cfg(test)] | 62 | #[cfg(test)] |
| @@ -59,11 +66,41 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> { | |||
| 59 | 66 | ||
| 60 | client.set_keys(&keys).await; | 67 | client.set_keys(&keys).await; |
| 61 | 68 | ||
| 62 | // TODO: choice input defaulting to user relay list filtered by non paid relays | 69 | let mut maintainers = vec![keys.public_key()]; |
| 63 | let repo_relays: Vec<String> = vec![ | 70 | |
| 64 | "ws://localhost:8055".to_string(), | 71 | let repo_relays: Vec<String> = if !args.relays.is_empty() { |
| 65 | "ws://localhost:8056".to_string(), | 72 | args.relays.clone() |
| 66 | ]; | 73 | } else if let Ok(config) = &repo_config_result { |
| 74 | config.relays.clone() | ||
| 75 | } else { | ||
| 76 | // TODO: choice input defaulting to user relay list filtered by non paid relays | ||
| 77 | // TODO: allow manual input for more relays | ||
| 78 | // TODO: reccommend some free relays | ||
| 79 | user_ref.relays.write() | ||
| 80 | }; | ||
| 81 | |||
| 82 | if let Ok(config) = &repo_config_result { | ||
| 83 | maintainers = extract_pks(config.maintainers.clone())?; | ||
| 84 | } | ||
| 85 | |||
| 86 | // if yaml file doesnt exist or needs updating | ||
| 87 | if match &repo_config_result { | ||
| 88 | Ok(config) => { | ||
| 89 | !(extract_pks(config.maintainers.clone())?.eq(&maintainers) | ||
| 90 | && config.relays.eq(&repo_relays)) | ||
| 91 | } | ||
| 92 | Err(_) => true, | ||
| 93 | } { | ||
| 94 | save_repo_config_to_yaml(&git_repo, maintainers.clone(), repo_relays.clone())?; | ||
| 95 | println!( | ||
| 96 | "maintainers.yaml {}. commit and push.", | ||
| 97 | if repo_config_result.is_err() { | ||
| 98 | "created" | ||
| 99 | } else { | ||
| 100 | "updated" | ||
| 101 | } | ||
| 102 | ); | ||
| 103 | } | ||
| 67 | 104 | ||
| 68 | println!("publishing repostory reference..."); | 105 | println!("publishing repostory reference..."); |
| 69 | 106 | ||
| @@ -71,10 +108,13 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> { | |||
| 71 | name, | 108 | name, |
| 72 | description, | 109 | description, |
| 73 | root_commit: root_commit.to_string(), | 110 | root_commit: root_commit.to_string(), |
| 111 | git_server, | ||
| 74 | relays: repo_relays.clone(), | 112 | relays: repo_relays.clone(), |
| 113 | maintainers, | ||
| 75 | } | 114 | } |
| 76 | .to_event(&keys)?; | 115 | .to_event(&keys)?; |
| 77 | 116 | ||
| 117 | // TODO: send repo event to blaster | ||
| 78 | send_events( | 118 | send_events( |
| 79 | &client, | 119 | &client, |
| 80 | vec![repo_event], | 120 | vec![repo_event], |
diff --git a/src/sub_commands/prs/create.rs b/src/sub_commands/prs/create.rs index 5c4e578..5e41a56 100644 --- a/src/sub_commands/prs/create.rs +++ b/src/sub_commands/prs/create.rs | |||
| @@ -100,12 +100,12 @@ pub async fn launch( | |||
| 100 | generate_pr_and_patch_events(&title, &description, &to_branch, &git_repo, &ahead, &keys)?; | 100 | generate_pr_and_patch_events(&title, &description, &to_branch, &git_repo, &ahead, &keys)?; |
| 101 | 101 | ||
| 102 | let repo_ref = repo_ref::fetch( | 102 | let repo_ref = repo_ref::fetch( |
| 103 | &git_repo, | ||
| 103 | git_repo | 104 | git_repo |
| 104 | .get_root_commit(&to_branch) | 105 | .get_root_commit(&to_branch) |
| 105 | .context("failed to get root commit of the repository")? | 106 | .context("failed to get root commit of the repository")? |
| 106 | .to_string(), | 107 | .to_string(), |
| 107 | &client, | 108 | &client, |
| 108 | // TODO: get relay list from local yaml file | ||
| 109 | user_ref.relays.write(), | 109 | user_ref.relays.write(), |
| 110 | ) | 110 | ) |
| 111 | .await?; | 111 | .await?; |
diff --git a/src/sub_commands/prs/list.rs b/src/sub_commands/prs/list.rs index 13f29fd..a6aa8b7 100644 --- a/src/sub_commands/prs/list.rs +++ b/src/sub_commands/prs/list.rs | |||
| @@ -45,6 +45,7 @@ pub async fn launch( | |||
| 45 | let client = <MockConnect as std::default::Default>::default(); | 45 | let client = <MockConnect as std::default::Default>::default(); |
| 46 | 46 | ||
| 47 | let repo_ref = repo_ref::fetch( | 47 | let repo_ref = repo_ref::fetch( |
| 48 | &git_repo, | ||
| 48 | root_commit.to_string(), | 49 | root_commit.to_string(), |
| 49 | &client, | 50 | &client, |
| 50 | client.get_more_fallback_relays().clone(), | 51 | client.get_more_fallback_relays().clone(), |
diff --git a/src/sub_commands/pull.rs b/src/sub_commands/pull.rs index a6513e8..a656e09 100644 --- a/src/sub_commands/pull.rs +++ b/src/sub_commands/pull.rs | |||
| @@ -38,6 +38,7 @@ pub async fn launch() -> Result<()> { | |||
| 38 | let client = <MockConnect as std::default::Default>::default(); | 38 | let client = <MockConnect as std::default::Default>::default(); |
| 39 | 39 | ||
| 40 | let repo_ref = repo_ref::fetch( | 40 | let repo_ref = repo_ref::fetch( |
| 41 | &git_repo, | ||
| 41 | root_commit.to_string(), | 42 | root_commit.to_string(), |
| 42 | &client, | 43 | &client, |
| 43 | client.get_more_fallback_relays().clone(), | 44 | client.get_more_fallback_relays().clone(), |
diff --git a/src/sub_commands/push.rs b/src/sub_commands/push.rs index 968aa0a..7c64bb2 100644 --- a/src/sub_commands/push.rs +++ b/src/sub_commands/push.rs | |||
| @@ -41,6 +41,7 @@ pub async fn launch(cli_args: &Cli) -> Result<()> { | |||
| 41 | let mut client = <MockConnect as std::default::Default>::default(); | 41 | let mut client = <MockConnect as std::default::Default>::default(); |
| 42 | 42 | ||
| 43 | let repo_ref = repo_ref::fetch( | 43 | let repo_ref = repo_ref::fetch( |
| 44 | &git_repo, | ||
| 44 | root_commit.to_string(), | 45 | root_commit.to_string(), |
| 45 | &client, | 46 | &client, |
| 46 | client.get_more_fallback_relays().clone(), | 47 | client.get_more_fallback_relays().clone(), |
diff --git a/test_utils/src/git.rs b/test_utils/src/git.rs index af87a3a..7f0b4c7 100644 --- a/test_utils/src/git.rs +++ b/test_utils/src/git.rs | |||
| @@ -146,6 +146,11 @@ impl GitTestRepo { | |||
| 146 | .context(format!("cannot find branch {branch_name}"))?; | 146 | .context(format!("cannot find branch {branch_name}"))?; |
| 147 | Ok(branch.into_reference().peel_to_commit()?.id()) | 147 | Ok(branch.into_reference().peel_to_commit()?.id()) |
| 148 | } | 148 | } |
| 149 | |||
| 150 | pub fn add_remote(&self, name: &str, url: &str) -> Result<()> { | ||
| 151 | self.git_repo.remote(name, url)?; | ||
| 152 | Ok(()) | ||
| 153 | } | ||
| 149 | } | 154 | } |
| 150 | 155 | ||
| 151 | impl Drop for GitTestRepo { | 156 | impl Drop for GitTestRepo { |
diff --git a/tests/claim.rs b/tests/claim.rs index 61be68b..cd845f7 100644 --- a/tests/claim.rs +++ b/tests/claim.rs | |||
| @@ -11,260 +11,509 @@ fn when_no_main_or_master_branch_return_error() -> Result<()> { | |||
| 11 | Ok(()) | 11 | Ok(()) |
| 12 | } | 12 | } |
| 13 | 13 | ||
| 14 | mod sends_repoistory_to_relays { | 14 | fn expect_msgs_first(p: &mut CliTester) -> Result<()> { |
| 15 | use futures::join; | 15 | p.expect("searching for your details...\r\n")?; |
| 16 | use test_utils::relay::Relay; | 16 | p.expect("\r")?; |
| 17 | p.expect("logged in as fred\r\n")?; | ||
| 18 | // // p.expect("searching for existing claims on repository...\r\n")?; | ||
| 19 | p.expect("maintainers.yaml created. commit and push.\r\n")?; | ||
| 20 | p.expect("publishing repostory reference...\r\n")?; | ||
| 21 | Ok(()) | ||
| 22 | } | ||
| 17 | 23 | ||
| 24 | mod when_repo_not_previously_claimed { | ||
| 18 | use super::*; | 25 | use super::*; |
| 19 | 26 | ||
| 20 | fn prep_git_repo() -> Result<GitTestRepo> { | 27 | mod when_repo_relays_specified_as_arguments { |
| 21 | let test_repo = GitTestRepo::default(); | 28 | use futures::join; |
| 22 | test_repo.populate()?; | 29 | use test_utils::relay::Relay; |
| 23 | Ok(test_repo) | ||
| 24 | } | ||
| 25 | 30 | ||
| 26 | fn cli_tester_claim(git_repo: &GitTestRepo) -> CliTester { | 31 | use super::*; |
| 27 | CliTester::new_from_dir( | ||
| 28 | &git_repo.dir, | ||
| 29 | [ | ||
| 30 | "--nsec", | ||
| 31 | TEST_KEY_1_NSEC, | ||
| 32 | "--password", | ||
| 33 | TEST_PASSWORD, | ||
| 34 | "--disable-cli-spinners", | ||
| 35 | "claim", | ||
| 36 | "--title", | ||
| 37 | "example-name", | ||
| 38 | "--description", | ||
| 39 | "example-description", | ||
| 40 | ], | ||
| 41 | ) | ||
| 42 | } | ||
| 43 | 32 | ||
| 44 | fn expect_msgs_first(p: &mut CliTester) -> Result<()> { | 33 | fn prep_git_repo() -> Result<GitTestRepo> { |
| 45 | p.expect("searching for your details...\r\n")?; | 34 | let test_repo = GitTestRepo::default(); |
| 46 | p.expect("\r")?; | 35 | test_repo.populate()?; |
| 47 | p.expect("logged in as fred\r\n")?; | 36 | test_repo.add_remote("origin", "https://localhost:1000")?; |
| 48 | // // p.expect("searching for existing claims on repository...\r\n")?; | 37 | Ok(test_repo) |
| 49 | p.expect("publishing repostory reference...\r\n")?; | 38 | } |
| 50 | Ok(()) | ||
| 51 | } | ||
| 52 | 39 | ||
| 53 | async fn prep_run_claim() -> Result<( | 40 | fn cli_tester_claim(git_repo: &GitTestRepo) -> CliTester { |
| 54 | Relay<'static>, | 41 | CliTester::new_from_dir( |
| 55 | Relay<'static>, | 42 | &git_repo.dir, |
| 56 | Relay<'static>, | 43 | [ |
| 57 | Relay<'static>, | 44 | "--nsec", |
| 58 | Relay<'static>, | 45 | TEST_KEY_1_NSEC, |
| 59 | )> { | 46 | "--password", |
| 60 | let git_repo = prep_git_repo()?; | 47 | TEST_PASSWORD, |
| 61 | // fallback (51,52) user write (53, 55) repo (55, 56) | 48 | "--disable-cli-spinners", |
| 62 | let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( | 49 | "claim", |
| 63 | Relay::new( | 50 | "--title", |
| 64 | 8051, | 51 | "example-name", |
| 65 | None, | 52 | "--description", |
| 66 | Some(&|relay, client_id, subscription_id, _| -> Result<()> { | 53 | "example-description", |
| 67 | relay.respond_events( | 54 | "--relays", |
| 68 | client_id, | 55 | "ws://localhost:8055", |
| 69 | &subscription_id, | 56 | "ws://localhost:8056", |
| 70 | &vec![ | 57 | ], |
| 71 | generate_test_key_1_metadata_event("fred"), | 58 | ) |
| 72 | generate_test_key_1_relay_list_event(), | 59 | } |
| 73 | ], | 60 | |
| 74 | )?; | 61 | async fn prep_run_claim() -> Result<( |
| 62 | Relay<'static>, | ||
| 63 | Relay<'static>, | ||
| 64 | Relay<'static>, | ||
| 65 | Relay<'static>, | ||
| 66 | Relay<'static>, | ||
| 67 | )> { | ||
| 68 | let git_repo = prep_git_repo()?; | ||
| 69 | // fallback (51,52) user write (53, 55) repo (55, 56) | ||
| 70 | let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( | ||
| 71 | Relay::new( | ||
| 72 | 8051, | ||
| 73 | None, | ||
| 74 | Some(&|relay, client_id, subscription_id, _| -> Result<()> { | ||
| 75 | relay.respond_events( | ||
| 76 | client_id, | ||
| 77 | &subscription_id, | ||
| 78 | &vec![ | ||
| 79 | generate_test_key_1_metadata_event("fred"), | ||
| 80 | generate_test_key_1_relay_list_event(), | ||
| 81 | ], | ||
| 82 | )?; | ||
| 83 | Ok(()) | ||
| 84 | }), | ||
| 85 | ), | ||
| 86 | Relay::new(8052, None, None), | ||
| 87 | Relay::new(8053, None, None), | ||
| 88 | Relay::new(8055, None, None), | ||
| 89 | Relay::new(8056, None, None), | ||
| 90 | ); | ||
| 91 | |||
| 92 | // // check relay had the right number of events | ||
| 93 | let cli_tester_handle = std::thread::spawn(move || -> Result<()> { | ||
| 94 | let mut p = cli_tester_claim(&git_repo); | ||
| 95 | p.expect_end_eventually()?; | ||
| 96 | for p in [51, 52, 53, 55, 56] { | ||
| 97 | relay::shutdown_relay(8000 + p)?; | ||
| 98 | } | ||
| 99 | Ok(()) | ||
| 100 | }); | ||
| 101 | |||
| 102 | // launch relay | ||
| 103 | let _ = join!( | ||
| 104 | r51.listen_until_close(), | ||
| 105 | r52.listen_until_close(), | ||
| 106 | r53.listen_until_close(), | ||
| 107 | r55.listen_until_close(), | ||
| 108 | r56.listen_until_close(), | ||
| 109 | ); | ||
| 110 | cli_tester_handle.join().unwrap()?; | ||
| 111 | Ok((r51, r52, r53, r55, r56)) | ||
| 112 | } | ||
| 113 | |||
| 114 | mod sent_to_correct_relays { | ||
| 115 | use super::*; | ||
| 116 | |||
| 117 | #[test] | ||
| 118 | #[serial] | ||
| 119 | fn only_1_repository_kind_event_sent_to_user_relays() -> Result<()> { | ||
| 120 | let (_, _, r53, r55, _) = futures::executor::block_on(prep_run_claim())?; | ||
| 121 | for relay in [&r53, &r55] { | ||
| 122 | assert_eq!( | ||
| 123 | relay | ||
| 124 | .events | ||
| 125 | .iter() | ||
| 126 | .filter(|e| e.kind.as_u64().eq(&REPOSITORY_KIND)) | ||
| 127 | .count(), | ||
| 128 | 1, | ||
| 129 | ); | ||
| 130 | } | ||
| 131 | Ok(()) | ||
| 132 | } | ||
| 133 | |||
| 134 | #[test] | ||
| 135 | #[serial] | ||
| 136 | fn only_1_repository_kind_event_sent_to_specified_repo_relays() -> Result<()> { | ||
| 137 | let (_, _, _, r55, r56) = futures::executor::block_on(prep_run_claim())?; | ||
| 138 | for relay in [&r55, &r56] { | ||
| 139 | assert_eq!( | ||
| 140 | relay | ||
| 141 | .events | ||
| 142 | .iter() | ||
| 143 | .filter(|e| e.kind.as_u64().eq(&REPOSITORY_KIND)) | ||
| 144 | .count(), | ||
| 145 | 1, | ||
| 146 | ); | ||
| 147 | } | ||
| 148 | Ok(()) | ||
| 149 | } | ||
| 150 | |||
| 151 | #[test] | ||
| 152 | #[serial] | ||
| 153 | fn event_not_sent_to_fallback_relay() -> Result<()> { | ||
| 154 | let (r51, r52, _, _, _) = futures::executor::block_on(prep_run_claim())?; | ||
| 155 | for relay in [&r51, &r52] { | ||
| 156 | assert_eq!( | ||
| 157 | relay | ||
| 158 | .events | ||
| 159 | .iter() | ||
| 160 | .filter(|e| e.kind.as_u64().eq(&REPOSITORY_KIND)) | ||
| 161 | .count(), | ||
| 162 | 0, | ||
| 163 | ); | ||
| 164 | } | ||
| 165 | Ok(()) | ||
| 166 | } | ||
| 167 | } | ||
| 168 | |||
| 169 | mod yaml_file { | ||
| 170 | use std::{fs, io::Read}; | ||
| 171 | |||
| 172 | use super::*; | ||
| 173 | |||
| 174 | async fn async_run_test() -> Result<()> { | ||
| 175 | let git_repo = prep_git_repo()?; | ||
| 176 | // fallback (51,52) user write (53, 55) repo (55, 56) | ||
| 177 | let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( | ||
| 178 | Relay::new( | ||
| 179 | 8051, | ||
| 180 | None, | ||
| 181 | Some(&|relay, client_id, subscription_id, _| -> Result<()> { | ||
| 182 | relay.respond_events( | ||
| 183 | client_id, | ||
| 184 | &subscription_id, | ||
| 185 | &vec![ | ||
| 186 | generate_test_key_1_metadata_event("fred"), | ||
| 187 | generate_test_key_1_relay_list_event(), | ||
| 188 | ], | ||
| 189 | )?; | ||
| 190 | Ok(()) | ||
| 191 | }), | ||
| 192 | ), | ||
| 193 | Relay::new(8052, None, None), | ||
| 194 | Relay::new(8053, None, None), | ||
| 195 | Relay::new(8055, None, None), | ||
| 196 | Relay::new(8056, None, None), | ||
| 197 | ); | ||
| 198 | |||
| 199 | // // check relay had the right number of events | ||
| 200 | let cli_tester_handle = std::thread::spawn(move || -> Result<()> { | ||
| 201 | let mut p = cli_tester_claim(&git_repo); | ||
| 202 | p.expect_end_eventually()?; | ||
| 203 | |||
| 204 | let yaml_path = git_repo.dir.join("maintainers.yaml"); | ||
| 205 | |||
| 206 | assert!(yaml_path.exists()); | ||
| 207 | |||
| 208 | let mut file = fs::File::open(yaml_path).expect("no such file"); | ||
| 209 | let mut file_contents = "".to_string(); | ||
| 210 | let _ = file.read_to_string(&mut file_contents); | ||
| 211 | assert_eq!( | ||
| 212 | file_contents, | ||
| 213 | format!( | ||
| 214 | "\ | ||
| 215 | maintainers:\n\ | ||
| 216 | - {TEST_KEY_1_NPUB}\n\ | ||
| 217 | relays:\n\ | ||
| 218 | - ws://localhost:8055\n\ | ||
| 219 | - ws://localhost:8056\n\ | ||
| 220 | " | ||
| 221 | ), | ||
| 222 | ); | ||
| 223 | for p in [51, 52, 53, 55, 56] { | ||
| 224 | relay::shutdown_relay(8000 + p)?; | ||
| 225 | } | ||
| 75 | Ok(()) | 226 | Ok(()) |
| 76 | }), | 227 | }); |
| 77 | ), | 228 | |
| 78 | Relay::new(8052, None, None), | 229 | // launch relay |
| 79 | Relay::new(8053, None, None), | 230 | let _ = join!( |
| 80 | Relay::new(8055, None, None), | 231 | r51.listen_until_close(), |
| 81 | Relay::new(8056, None, None), | 232 | r52.listen_until_close(), |
| 82 | ); | 233 | r53.listen_until_close(), |
| 83 | 234 | r55.listen_until_close(), | |
| 84 | // // check relay had the right number of events | 235 | r56.listen_until_close(), |
| 85 | let cli_tester_handle = std::thread::spawn(move || -> Result<()> { | 236 | ); |
| 86 | let mut p = cli_tester_claim(&git_repo); | 237 | cli_tester_handle.join().unwrap()?; |
| 87 | p.expect_end_eventually()?; | 238 | Ok(()) |
| 88 | for p in [51, 52, 53, 55, 56] { | ||
| 89 | relay::shutdown_relay(8000 + p)?; | ||
| 90 | } | 239 | } |
| 91 | Ok(()) | ||
| 92 | }); | ||
| 93 | |||
| 94 | // launch relay | ||
| 95 | let _ = join!( | ||
| 96 | r51.listen_until_close(), | ||
| 97 | r52.listen_until_close(), | ||
| 98 | r53.listen_until_close(), | ||
| 99 | r55.listen_until_close(), | ||
| 100 | r56.listen_until_close(), | ||
| 101 | ); | ||
| 102 | cli_tester_handle.join().unwrap()?; | ||
| 103 | Ok((r51, r52, r53, r55, r56)) | ||
| 104 | } | ||
| 105 | 240 | ||
| 106 | mod sent_to_correct_relays { | 241 | #[test] |
| 107 | use super::*; | 242 | #[serial] |
| 243 | fn contains_maintainers_and_relays() -> Result<()> { | ||
| 244 | futures::executor::block_on(async_run_test())?; | ||
| 245 | Ok(()) | ||
| 246 | } | ||
| 247 | } | ||
| 248 | |||
| 249 | mod tags { | ||
| 250 | use super::*; | ||
| 108 | 251 | ||
| 109 | #[test] | 252 | #[test] |
| 110 | #[serial] | 253 | #[serial] |
| 111 | fn only_1_repository_kind_event_sent_to_user_relays() -> Result<()> { | 254 | fn root_commit_as_d_replaceable_event_identifier() -> Result<()> { |
| 112 | let (_, _, r53, r55, _) = futures::executor::block_on(prep_run_claim())?; | 255 | let (_, _, r53, r55, r56) = futures::executor::block_on(prep_run_claim())?; |
| 113 | for relay in [&r53, &r55] { | 256 | for relay in [&r53, &r55, &r56] { |
| 114 | assert_eq!( | 257 | let event: &nostr::Event = relay |
| 115 | relay | ||
| 116 | .events | 258 | .events |
| 117 | .iter() | 259 | .iter() |
| 118 | .filter(|e| e.kind.as_u64().eq(&REPOSITORY_KIND)) | 260 | .find(|e| e.kind.as_u64().eq(&REPOSITORY_KIND)) |
| 119 | .count(), | 261 | .unwrap(); |
| 120 | 1, | 262 | |
| 121 | ); | 263 | assert!(event.tags.iter().any(|t| t.as_vec()[0].eq("d") |
| 264 | && t.as_vec()[1].eq("9ee507fc4357d7ee16a5d8901bedcd103f23c17d"))); | ||
| 265 | } | ||
| 266 | Ok(()) | ||
| 122 | } | 267 | } |
| 123 | Ok(()) | ||
| 124 | } | ||
| 125 | 268 | ||
| 126 | #[test] | 269 | #[test] |
| 127 | #[serial] | 270 | #[serial] |
| 128 | fn only_1_repository_kind_event_sent_to_repo_relays() -> Result<()> { | 271 | fn root_commit_as_reference() -> Result<()> { |
| 129 | let (_, _, _, r55, r56) = futures::executor::block_on(prep_run_claim())?; | 272 | let (_, _, r53, r55, r56) = futures::executor::block_on(prep_run_claim())?; |
| 130 | for relay in [&r55, &r56] { | 273 | for relay in [&r53, &r55, &r56] { |
| 131 | assert_eq!( | 274 | let event: &nostr::Event = relay |
| 132 | relay | ||
| 133 | .events | 275 | .events |
| 134 | .iter() | 276 | .iter() |
| 135 | .filter(|e| e.kind.as_u64().eq(&REPOSITORY_KIND)) | 277 | .find(|e| e.kind.as_u64().eq(&REPOSITORY_KIND)) |
| 136 | .count(), | 278 | .unwrap(); |
| 137 | 1, | 279 | |
| 138 | ); | 280 | // root commit 'r' tag with 'r-' prefix |
| 281 | assert!(event.tags.iter().any(|t| t.as_vec()[0].eq("r") | ||
| 282 | && t.as_vec()[1].eq("r-9ee507fc4357d7ee16a5d8901bedcd103f23c17d"))); | ||
| 283 | } | ||
| 284 | Ok(()) | ||
| 139 | } | 285 | } |
| 140 | Ok(()) | ||
| 141 | } | ||
| 142 | 286 | ||
| 143 | #[test] | 287 | #[test] |
| 144 | #[serial] | 288 | #[serial] |
| 145 | fn event_not_sent_to_fallback_relay() -> Result<()> { | 289 | fn name() -> Result<()> { |
| 146 | let (r51, r52, _, _, _) = futures::executor::block_on(prep_run_claim())?; | 290 | let (_, _, r53, r55, r56) = futures::executor::block_on(prep_run_claim())?; |
| 147 | for relay in [&r51, &r52] { | 291 | for relay in [&r53, &r55, &r56] { |
| 148 | assert_eq!( | 292 | let event: &nostr::Event = relay |
| 149 | relay | ||
| 150 | .events | 293 | .events |
| 151 | .iter() | 294 | .iter() |
| 152 | .filter(|e| e.kind.as_u64().eq(&REPOSITORY_KIND)) | 295 | .find(|e| e.kind.as_u64().eq(&REPOSITORY_KIND)) |
| 153 | .count(), | 296 | .unwrap(); |
| 154 | 0, | 297 | |
| 155 | ); | 298 | assert!( |
| 299 | event | ||
| 300 | .tags | ||
| 301 | .iter() | ||
| 302 | .any(|t| t.as_vec()[0].eq("name") && t.as_vec()[1].eq("example-name")) | ||
| 303 | ); | ||
| 304 | } | ||
| 305 | Ok(()) | ||
| 156 | } | 306 | } |
| 157 | Ok(()) | ||
| 158 | } | ||
| 159 | } | ||
| 160 | 307 | ||
| 161 | mod tags { | 308 | #[test] |
| 162 | use super::*; | 309 | #[serial] |
| 310 | fn description() -> Result<()> { | ||
| 311 | let (_, _, r53, r55, r56) = futures::executor::block_on(prep_run_claim())?; | ||
| 312 | for relay in [&r53, &r55, &r56] { | ||
| 313 | let event: &nostr::Event = relay | ||
| 314 | .events | ||
| 315 | .iter() | ||
| 316 | .find(|e| e.kind.as_u64().eq(&REPOSITORY_KIND)) | ||
| 317 | .unwrap(); | ||
| 163 | 318 | ||
| 164 | #[test] | 319 | assert!(event.tags.iter().any(|t| t.as_vec()[0].eq("description") |
| 165 | #[serial] | 320 | && t.as_vec()[1].eq("example-description"))); |
| 166 | fn root_commit_as_d_replaceable_event_identifier() -> Result<()> { | 321 | } |
| 167 | let (_, _, r53, r55, r56) = futures::executor::block_on(prep_run_claim())?; | 322 | Ok(()) |
| 168 | for relay in [&r53, &r55, &r56] { | ||
| 169 | let event: &nostr::Event = relay | ||
| 170 | .events | ||
| 171 | .iter() | ||
| 172 | .find(|e| e.kind.as_u64().eq(&REPOSITORY_KIND)) | ||
| 173 | .unwrap(); | ||
| 174 | |||
| 175 | assert!(event.tags.iter().any(|t| t.as_vec()[0].eq("d") | ||
| 176 | && t.as_vec()[1].eq("9ee507fc4357d7ee16a5d8901bedcd103f23c17d"))); | ||
| 177 | } | 323 | } |
| 178 | Ok(()) | ||
| 179 | } | ||
| 180 | 324 | ||
| 181 | #[test] | 325 | #[test] |
| 182 | #[serial] | 326 | #[serial] |
| 183 | fn root_commit_as_reference() -> Result<()> { | 327 | fn git_server() -> Result<()> { |
| 184 | let (_, _, r53, r55, r56) = futures::executor::block_on(prep_run_claim())?; | 328 | let (_, _, r53, r55, r56) = futures::executor::block_on(prep_run_claim())?; |
| 185 | for relay in [&r53, &r55, &r56] { | 329 | for relay in [&r53, &r55, &r56] { |
| 186 | let event: &nostr::Event = relay | 330 | let event: &nostr::Event = relay |
| 187 | .events | 331 | .events |
| 188 | .iter() | 332 | .iter() |
| 189 | .find(|e| e.kind.as_u64().eq(&REPOSITORY_KIND)) | 333 | .find(|e| e.kind.as_u64().eq(&REPOSITORY_KIND)) |
| 190 | .unwrap(); | 334 | .unwrap(); |
| 191 | 335 | ||
| 192 | // root commit 'r' tag with 'r-' prefix | 336 | assert!(event.tags.iter().any(|t| t.as_vec()[0].eq("git-server") |
| 193 | assert!(event.tags.iter().any(|t| t.as_vec()[0].eq("r") | 337 | && t.as_vec()[1].eq("https://localhost:1000"))); |
| 194 | && t.as_vec()[1].eq("r-9ee507fc4357d7ee16a5d8901bedcd103f23c17d"))); | 338 | } |
| 339 | Ok(()) | ||
| 340 | } | ||
| 341 | |||
| 342 | #[test] | ||
| 343 | #[serial] | ||
| 344 | fn git_server_as_reference() -> Result<()> { | ||
| 345 | let (_, _, r53, r55, r56) = futures::executor::block_on(prep_run_claim())?; | ||
| 346 | for relay in [&r53, &r55, &r56] { | ||
| 347 | let event: &nostr::Event = relay | ||
| 348 | .events | ||
| 349 | .iter() | ||
| 350 | .find(|e| e.kind.as_u64().eq(&REPOSITORY_KIND)) | ||
| 351 | .unwrap(); | ||
| 352 | |||
| 353 | assert!(event.tags.iter().any( | ||
| 354 | |t| t.as_vec()[0].eq("r") && t.as_vec()[1].eq("https://localhost:1000") | ||
| 355 | )); | ||
| 356 | } | ||
| 357 | Ok(()) | ||
| 195 | } | 358 | } |
| 196 | Ok(()) | ||
| 197 | } | ||
| 198 | 359 | ||
| 199 | #[test] | 360 | #[test] |
| 200 | #[serial] | 361 | #[serial] |
| 201 | fn name() -> Result<()> { | 362 | fn relays() -> Result<()> { |
| 202 | let (_, _, r53, r55, r56) = futures::executor::block_on(prep_run_claim())?; | 363 | let (_, _, r53, r55, r56) = futures::executor::block_on(prep_run_claim())?; |
| 203 | for relay in [&r53, &r55, &r56] { | 364 | for relay in [&r53, &r55, &r56] { |
| 204 | let event: &nostr::Event = relay | 365 | let event: &nostr::Event = relay |
| 205 | .events | 366 | .events |
| 206 | .iter() | 367 | .iter() |
| 207 | .find(|e| e.kind.as_u64().eq(&REPOSITORY_KIND)) | 368 | .find(|e| e.kind.as_u64().eq(&REPOSITORY_KIND)) |
| 208 | .unwrap(); | 369 | .unwrap(); |
| 209 | 370 | ||
| 210 | assert!( | 371 | let relay_tags = event |
| 211 | event | ||
| 212 | .tags | 372 | .tags |
| 213 | .iter() | 373 | .iter() |
| 214 | .any(|t| t.as_vec()[0].eq("name") && t.as_vec()[1].eq("example-name")) | 374 | .filter(|t| t.as_vec()[0].eq("relay")) |
| 215 | ); | 375 | .collect::<Vec<&nostr::Tag>>(); |
| 376 | assert_eq!(relay_tags[0].as_vec()[1], "ws://localhost:8055"); | ||
| 377 | assert_eq!(relay_tags[1].as_vec()[1], "ws://localhost:8056"); | ||
| 378 | } | ||
| 379 | Ok(()) | ||
| 380 | } | ||
| 381 | |||
| 382 | #[test] | ||
| 383 | #[serial] | ||
| 384 | fn current_user_tagged_indicating_maintainer() -> Result<()> { | ||
| 385 | let (_, _, r53, r55, r56) = futures::executor::block_on(prep_run_claim())?; | ||
| 386 | for relay in [&r53, &r55, &r56] { | ||
| 387 | let event: &nostr::Event = relay | ||
| 388 | .events | ||
| 389 | .iter() | ||
| 390 | .find(|e| e.kind.as_u64().eq(&REPOSITORY_KIND)) | ||
| 391 | .unwrap(); | ||
| 392 | |||
| 393 | let relay_tags = event | ||
| 394 | .tags | ||
| 395 | .iter() | ||
| 396 | .filter(|t| t.as_vec()[0].eq("p")) | ||
| 397 | .collect::<Vec<&nostr::Tag>>(); | ||
| 398 | assert_eq!(relay_tags.len(), 1); | ||
| 399 | assert_eq!( | ||
| 400 | relay_tags[0].as_vec()[1], | ||
| 401 | TEST_KEY_1_KEYS.public_key().to_string() | ||
| 402 | ); | ||
| 403 | } | ||
| 404 | Ok(()) | ||
| 216 | } | 405 | } |
| 217 | Ok(()) | ||
| 218 | } | 406 | } |
| 219 | 407 | ||
| 220 | #[test] | 408 | mod cli_ouput { |
| 221 | #[serial] | 409 | use super::*; |
| 222 | fn description() -> Result<()> { | 410 | |
| 223 | let (_, _, r53, r55, r56) = futures::executor::block_on(prep_run_claim())?; | 411 | async fn run_test_async() -> Result<()> { |
| 224 | for relay in [&r53, &r55, &r56] { | 412 | let git_repo = prep_git_repo()?; |
| 225 | let event: &nostr::Event = relay | 413 | |
| 226 | .events | 414 | let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( |
| 227 | .iter() | 415 | Relay::new( |
| 228 | .find(|e| e.kind.as_u64().eq(&REPOSITORY_KIND)) | 416 | 8051, |
| 229 | .unwrap(); | 417 | None, |
| 230 | 418 | Some(&|relay, client_id, subscription_id, _| -> Result<()> { | |
| 231 | assert!( | 419 | relay.respond_events( |
| 232 | event.tags.iter().any(|t| t.as_vec()[0].eq("description") | 420 | client_id, |
| 233 | && t.as_vec()[1].eq("example-description")) | 421 | &subscription_id, |
| 422 | &vec![ | ||
| 423 | generate_test_key_1_metadata_event("fred"), | ||
| 424 | generate_test_key_1_relay_list_event(), | ||
| 425 | ], | ||
| 426 | )?; | ||
| 427 | Ok(()) | ||
| 428 | }), | ||
| 429 | ), | ||
| 430 | Relay::new(8052, None, None), | ||
| 431 | Relay::new(8053, None, None), | ||
| 432 | Relay::new(8055, None, None), | ||
| 433 | Relay::new(8056, None, None), | ||
| 234 | ); | 434 | ); |
| 435 | |||
| 436 | // // check relay had the right number of events | ||
| 437 | let cli_tester_handle = std::thread::spawn(move || -> Result<()> { | ||
| 438 | let mut p = cli_tester_claim(&git_repo); | ||
| 439 | expect_msgs_first(&mut p)?; | ||
| 440 | relay::expect_send_with_progress( | ||
| 441 | &mut p, | ||
| 442 | vec![ | ||
| 443 | (" [my-relay] [repo-relay] ws://localhost:8055", true, ""), | ||
| 444 | (" [my-relay] ws://localhost:8053", true, ""), | ||
| 445 | (" [repo-relay] ws://localhost:8056", true, ""), | ||
| 446 | ], | ||
| 447 | 1, | ||
| 448 | )?; | ||
| 449 | p.expect_end_with_whitespace()?; | ||
| 450 | for p in [51, 52, 53, 55, 56] { | ||
| 451 | relay::shutdown_relay(8000 + p)?; | ||
| 452 | } | ||
| 453 | Ok(()) | ||
| 454 | }); | ||
| 455 | |||
| 456 | // launch relay | ||
| 457 | let _ = join!( | ||
| 458 | r51.listen_until_close(), | ||
| 459 | r52.listen_until_close(), | ||
| 460 | r53.listen_until_close(), | ||
| 461 | r55.listen_until_close(), | ||
| 462 | r56.listen_until_close(), | ||
| 463 | ); | ||
| 464 | cli_tester_handle.join().unwrap()?; | ||
| 465 | Ok(()) | ||
| 235 | } | 466 | } |
| 236 | Ok(()) | ||
| 237 | } | ||
| 238 | 467 | ||
| 239 | #[test] | 468 | #[test] |
| 240 | #[serial] | 469 | #[serial] |
| 241 | fn relays() -> Result<()> { | 470 | fn check_cli_output() -> Result<()> { |
| 242 | let (_, _, r53, r55, r56) = futures::executor::block_on(prep_run_claim())?; | 471 | futures::executor::block_on(run_test_async())?; |
| 243 | for relay in [&r53, &r55, &r56] { | 472 | Ok(()) |
| 244 | let event: &nostr::Event = relay | ||
| 245 | .events | ||
| 246 | .iter() | ||
| 247 | .find(|e| e.kind.as_u64().eq(&REPOSITORY_KIND)) | ||
| 248 | .unwrap(); | ||
| 249 | |||
| 250 | let relay_tags = event | ||
| 251 | .tags | ||
| 252 | .iter() | ||
| 253 | .filter(|t| t.as_vec()[0].eq("relay")) | ||
| 254 | .collect::<Vec<&nostr::Tag>>(); | ||
| 255 | assert_eq!(relay_tags[0].as_vec()[1], "ws://localhost:8055"); | ||
| 256 | assert_eq!(relay_tags[1].as_vec()[1], "ws://localhost:8056"); | ||
| 257 | } | 473 | } |
| 258 | Ok(()) | ||
| 259 | } | 474 | } |
| 260 | } | 475 | } |
| 261 | 476 | ||
| 262 | mod cli_ouput { | 477 | mod when_repo_relays_not_specified { |
| 478 | use futures::join; | ||
| 479 | use test_utils::relay::Relay; | ||
| 480 | |||
| 263 | use super::*; | 481 | use super::*; |
| 264 | 482 | ||
| 265 | async fn run_test_async() -> Result<()> { | 483 | fn prep_git_repo() -> Result<GitTestRepo> { |
| 266 | let git_repo = prep_git_repo()?; | 484 | let test_repo = GitTestRepo::default(); |
| 485 | test_repo.populate()?; | ||
| 486 | test_repo.add_remote("origin", "https://localhost:1000")?; | ||
| 487 | Ok(test_repo) | ||
| 488 | } | ||
| 489 | |||
| 490 | fn cli_tester_claim(git_repo: &GitTestRepo) -> CliTester { | ||
| 491 | CliTester::new_from_dir( | ||
| 492 | &git_repo.dir, | ||
| 493 | [ | ||
| 494 | "--nsec", | ||
| 495 | TEST_KEY_1_NSEC, | ||
| 496 | "--password", | ||
| 497 | TEST_PASSWORD, | ||
| 498 | "--disable-cli-spinners", | ||
| 499 | "claim", | ||
| 500 | "--title", | ||
| 501 | "example-name", | ||
| 502 | "--description", | ||
| 503 | "example-description", | ||
| 504 | ], | ||
| 505 | ) | ||
| 506 | } | ||
| 267 | 507 | ||
| 508 | async fn prep_run_claim() -> Result<( | ||
| 509 | Relay<'static>, | ||
| 510 | Relay<'static>, | ||
| 511 | Relay<'static>, | ||
| 512 | Relay<'static>, | ||
| 513 | Relay<'static>, | ||
| 514 | )> { | ||
| 515 | let git_repo = prep_git_repo()?; | ||
| 516 | // fallback (51,52) user write (53, 55) repo (55, 56) | ||
| 268 | let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( | 517 | let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( |
| 269 | Relay::new( | 518 | Relay::new( |
| 270 | 8051, | 519 | 8051, |
| @@ -290,17 +539,7 @@ mod sends_repoistory_to_relays { | |||
| 290 | // // check relay had the right number of events | 539 | // // check relay had the right number of events |
| 291 | let cli_tester_handle = std::thread::spawn(move || -> Result<()> { | 540 | let cli_tester_handle = std::thread::spawn(move || -> Result<()> { |
| 292 | let mut p = cli_tester_claim(&git_repo); | 541 | let mut p = cli_tester_claim(&git_repo); |
| 293 | expect_msgs_first(&mut p)?; | 542 | p.expect_end_eventually()?; |
| 294 | relay::expect_send_with_progress( | ||
| 295 | &mut p, | ||
| 296 | vec![ | ||
| 297 | (" [my-relay] [repo-relay] ws://localhost:8055", true, ""), | ||
| 298 | (" [my-relay] ws://localhost:8053", true, ""), | ||
| 299 | (" [repo-relay] ws://localhost:8056", true, ""), | ||
| 300 | ], | ||
| 301 | 1, | ||
| 302 | )?; | ||
| 303 | p.expect_end_with_whitespace()?; | ||
| 304 | for p in [51, 52, 53, 55, 56] { | 543 | for p in [51, 52, 53, 55, 56] { |
| 305 | relay::shutdown_relay(8000 + p)?; | 544 | relay::shutdown_relay(8000 + p)?; |
| 306 | } | 545 | } |
| @@ -316,14 +555,102 @@ mod sends_repoistory_to_relays { | |||
| 316 | r56.listen_until_close(), | 555 | r56.listen_until_close(), |
| 317 | ); | 556 | ); |
| 318 | cli_tester_handle.join().unwrap()?; | 557 | cli_tester_handle.join().unwrap()?; |
| 319 | Ok(()) | 558 | Ok((r51, r52, r53, r55, r56)) |
| 559 | } | ||
| 560 | |||
| 561 | mod tags { | ||
| 562 | use super::*; | ||
| 563 | |||
| 564 | #[test] | ||
| 565 | #[serial] | ||
| 566 | fn relays_match_user_write_relays() -> Result<()> { | ||
| 567 | let (_, _, r53, r55, _) = futures::executor::block_on(prep_run_claim())?; | ||
| 568 | for relay in [&r53, &r55] { | ||
| 569 | let event: &nostr::Event = relay | ||
| 570 | .events | ||
| 571 | .iter() | ||
| 572 | .find(|e| e.kind.as_u64().eq(&REPOSITORY_KIND)) | ||
| 573 | .unwrap(); | ||
| 574 | |||
| 575 | let relay_tags = event | ||
| 576 | .tags | ||
| 577 | .iter() | ||
| 578 | .filter(|t| t.as_vec()[0].eq("relay")) | ||
| 579 | .collect::<Vec<&nostr::Tag>>(); | ||
| 580 | assert_eq!(relay_tags[0].as_vec()[1], "ws://localhost:8053"); | ||
| 581 | assert_eq!(relay_tags[1].as_vec()[1], "ws://localhost:8055"); | ||
| 582 | } | ||
| 583 | Ok(()) | ||
| 584 | } | ||
| 320 | } | 585 | } |
| 321 | 586 | ||
| 322 | #[test] | 587 | mod cli_ouput { |
| 323 | #[serial] | 588 | use super::*; |
| 324 | fn check_cli_output() -> Result<()> { | 589 | |
| 325 | futures::executor::block_on(run_test_async())?; | 590 | async fn run_test_async() -> Result<()> { |
| 326 | Ok(()) | 591 | let git_repo = prep_git_repo()?; |
| 592 | |||
| 593 | let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( | ||
| 594 | Relay::new( | ||
| 595 | 8051, | ||
| 596 | None, | ||
| 597 | Some(&|relay, client_id, subscription_id, _| -> Result<()> { | ||
| 598 | relay.respond_events( | ||
| 599 | client_id, | ||
| 600 | &subscription_id, | ||
| 601 | &vec![ | ||
| 602 | generate_test_key_1_metadata_event("fred"), | ||
| 603 | generate_test_key_1_relay_list_event(), | ||
| 604 | ], | ||
| 605 | )?; | ||
| 606 | Ok(()) | ||
| 607 | }), | ||
| 608 | ), | ||
| 609 | Relay::new(8052, None, None), | ||
| 610 | Relay::new(8053, None, None), | ||
| 611 | Relay::new(8055, None, None), | ||
| 612 | Relay::new(8056, None, None), | ||
| 613 | ); | ||
| 614 | |||
| 615 | // // check relay had the right number of events | ||
| 616 | let cli_tester_handle = std::thread::spawn(move || -> Result<()> { | ||
| 617 | let mut p = cli_tester_claim(&git_repo); | ||
| 618 | expect_msgs_first(&mut p)?; | ||
| 619 | relay::expect_send_with_progress( | ||
| 620 | &mut p, | ||
| 621 | vec![ | ||
| 622 | (" [my-relay] [repo-relay] ws://localhost:8053", true, ""), | ||
| 623 | (" [my-relay] [repo-relay] ws://localhost:8055", true, ""), | ||
| 624 | ], | ||
| 625 | 1, | ||
| 626 | )?; | ||
| 627 | p.expect_end_with_whitespace()?; | ||
| 628 | for p in [51, 52, 53, 55, 56] { | ||
| 629 | relay::shutdown_relay(8000 + p)?; | ||
| 630 | } | ||
| 631 | Ok(()) | ||
| 632 | }); | ||
| 633 | |||
| 634 | // launch relay | ||
| 635 | let _ = join!( | ||
| 636 | r51.listen_until_close(), | ||
| 637 | r52.listen_until_close(), | ||
| 638 | r53.listen_until_close(), | ||
| 639 | r55.listen_until_close(), | ||
| 640 | r56.listen_until_close(), | ||
| 641 | ); | ||
| 642 | cli_tester_handle.join().unwrap()?; | ||
| 643 | Ok(()) | ||
| 644 | } | ||
| 645 | |||
| 646 | #[test] | ||
| 647 | #[serial] | ||
| 648 | fn check_cli_output() -> Result<()> { | ||
| 649 | futures::executor::block_on(run_test_async())?; | ||
| 650 | Ok(()) | ||
| 651 | } | ||
| 327 | } | 652 | } |
| 328 | } | 653 | } |
| 329 | } | 654 | } |
| 655 | |||
| 656 | // TODO: when_updating_existing_repoistory | ||