diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2024-09-04 08:04:48 +0100 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2024-09-04 13:30:59 +0100 |
| commit | 949c6459aa7683453a7160423b689ceadb08954b (patch) | |
| tree | 230c26ecb11b99916e5570e548673eb09ecf0a36 /src/lib/repo_ref.rs | |
| parent | a825311f2c55661aaab3a163bda9109295c96044 (diff) | |
refactor: organise into lib and bin structure
the make the code more readable
this commit just moves the files, the next commit should fix the imports
Diffstat (limited to 'src/lib/repo_ref.rs')
| -rw-r--r-- | src/lib/repo_ref.rs | 700 |
1 files changed, 700 insertions, 0 deletions
diff --git a/src/lib/repo_ref.rs b/src/lib/repo_ref.rs new file mode 100644 index 0000000..0e57d96 --- /dev/null +++ b/src/lib/repo_ref.rs | |||
| @@ -0,0 +1,700 @@ | |||
| 1 | use std::{ | ||
| 2 | collections::{HashMap, HashSet}, | ||
| 3 | fs::File, | ||
| 4 | io::BufReader, | ||
| 5 | str::FromStr, | ||
| 6 | }; | ||
| 7 | |||
| 8 | use anyhow::{bail, Context, Result}; | ||
| 9 | use console::Style; | ||
| 10 | use nostr::{nips::nip01::Coordinate, FromBech32, PublicKey, Tag, TagStandard, ToBech32}; | ||
| 11 | use nostr_sdk::{Kind, NostrSigner, Timestamp}; | ||
| 12 | use serde::{Deserialize, Serialize}; | ||
| 13 | |||
| 14 | #[cfg(not(test))] | ||
| 15 | use crate::client::Client; | ||
| 16 | use crate::{ | ||
| 17 | cli_interactor::{Interactor, InteractorPrompt, PromptInputParms}, | ||
| 18 | client::{get_event_from_global_cache, get_events_from_cache, sign_event, Connect}, | ||
| 19 | git::{NostrUrlDecoded, Repo, RepoActions}, | ||
| 20 | }; | ||
| 21 | |||
| 22 | #[derive(Default)] | ||
| 23 | pub struct RepoRef { | ||
| 24 | pub name: String, | ||
| 25 | pub description: String, | ||
| 26 | pub identifier: String, | ||
| 27 | pub root_commit: String, | ||
| 28 | pub git_server: Vec<String>, | ||
| 29 | pub web: Vec<String>, | ||
| 30 | pub relays: Vec<String>, | ||
| 31 | pub maintainers: Vec<PublicKey>, | ||
| 32 | pub events: HashMap<Coordinate, nostr::Event>, | ||
| 33 | // code languages and hashtags | ||
| 34 | } | ||
| 35 | |||
| 36 | impl TryFrom<nostr::Event> for RepoRef { | ||
| 37 | type Error = anyhow::Error; | ||
| 38 | |||
| 39 | fn try_from(event: nostr::Event) -> Result<Self> { | ||
| 40 | if !event.kind.eq(&Kind::GitRepoAnnouncement) { | ||
| 41 | bail!("incorrect kind"); | ||
| 42 | } | ||
| 43 | let mut r = Self::default(); | ||
| 44 | |||
| 45 | if let Some(t) = event.tags.iter().find(|t| t.as_vec()[0].eq("d")) { | ||
| 46 | r.identifier = t.as_vec()[1].clone(); | ||
| 47 | } | ||
| 48 | |||
| 49 | if let Some(t) = event.tags.iter().find(|t| t.as_vec()[0].eq("name")) { | ||
| 50 | r.name = t.as_vec()[1].clone(); | ||
| 51 | } | ||
| 52 | |||
| 53 | if let Some(t) = event.tags.iter().find(|t| t.as_vec()[0].eq("description")) { | ||
| 54 | r.description = t.as_vec()[1].clone(); | ||
| 55 | } | ||
| 56 | |||
| 57 | if let Some(t) = event.tags.iter().find(|t| t.as_vec()[0].eq("clone")) { | ||
| 58 | r.git_server = t.clone().to_vec(); | ||
| 59 | r.git_server.remove(0); | ||
| 60 | } | ||
| 61 | |||
| 62 | if let Some(t) = event.tags.iter().find(|t| t.as_vec()[0].eq("web")) { | ||
| 63 | r.web = t.clone().to_vec(); | ||
| 64 | r.web.remove(0); | ||
| 65 | } | ||
| 66 | |||
| 67 | if let Some(t) = event.tags.iter().find(|t| { | ||
| 68 | t.as_vec()[0].eq("r") | ||
| 69 | && t.as_vec()[1].len().eq(&40) | ||
| 70 | && git2::Oid::from_str(t.as_vec()[1].as_str()).is_ok() | ||
| 71 | }) { | ||
| 72 | r.root_commit = t.as_vec()[1].clone(); | ||
| 73 | } | ||
| 74 | |||
| 75 | if let Some(t) = event.tags.iter().find(|t| t.as_vec()[0].eq("relays")) { | ||
| 76 | r.relays = t.clone().to_vec(); | ||
| 77 | r.relays.remove(0); | ||
| 78 | } | ||
| 79 | |||
| 80 | if let Some(t) = event.tags.iter().find(|t| t.as_vec()[0].eq("maintainers")) { | ||
| 81 | let mut maintainers = t.clone().to_vec(); | ||
| 82 | maintainers.remove(0); | ||
| 83 | if !maintainers.contains(&event.pubkey.to_string()) { | ||
| 84 | r.maintainers.push(event.pubkey); | ||
| 85 | } | ||
| 86 | for pk in maintainers { | ||
| 87 | r.maintainers.push( | ||
| 88 | nostr_sdk::prelude::PublicKey::from_str(&pk) | ||
| 89 | .context(format!("cannot convert entry from maintainers tag {pk} into a valid nostr public key. it should be in hex format")) | ||
| 90 | .context("invalid repository event")?, | ||
| 91 | ); | ||
| 92 | } | ||
| 93 | } else { | ||
| 94 | r.maintainers = vec![event.pubkey]; | ||
| 95 | } | ||
| 96 | r.events = HashMap::new(); | ||
| 97 | r.events.insert( | ||
| 98 | Coordinate { | ||
| 99 | kind: event.kind, | ||
| 100 | identifier: event.identifier().unwrap().to_string(), | ||
| 101 | public_key: event.author(), | ||
| 102 | relays: vec![], | ||
| 103 | }, | ||
| 104 | event, | ||
| 105 | ); | ||
| 106 | Ok(r) | ||
| 107 | } | ||
| 108 | } | ||
| 109 | |||
| 110 | impl RepoRef { | ||
| 111 | pub async fn to_event(&self, signer: &NostrSigner) -> Result<nostr::Event> { | ||
| 112 | sign_event( | ||
| 113 | nostr_sdk::EventBuilder::new( | ||
| 114 | nostr::event::Kind::GitRepoAnnouncement, | ||
| 115 | "", | ||
| 116 | [ | ||
| 117 | vec![ | ||
| 118 | Tag::identifier(if self.identifier.to_string().is_empty() { | ||
| 119 | // fiatjaf thought a random string. its not in the draft nip. | ||
| 120 | // thread_rng() | ||
| 121 | // .sample_iter(&Alphanumeric) | ||
| 122 | // .take(15) | ||
| 123 | // .map(char::from) | ||
| 124 | // .collect() | ||
| 125 | |||
| 126 | // an identifier based on first commit is better so that users dont | ||
| 127 | // accidentally create two seperate identifiers for the same repo | ||
| 128 | // there is a hesitancy to use the commit id | ||
| 129 | // in another conversaion with fiatjaf he suggested the first 6 | ||
| 130 | // character of the commit id | ||
| 131 | // here we are using 7 which is the standard for shorthand commit id | ||
| 132 | self.root_commit.to_string()[..7].to_string() | ||
| 133 | } else { | ||
| 134 | self.identifier.to_string() | ||
| 135 | }), | ||
| 136 | Tag::custom( | ||
| 137 | nostr::TagKind::Custom(std::borrow::Cow::Borrowed("r")), | ||
| 138 | vec![self.root_commit.to_string(), "euc".to_string()], | ||
| 139 | ), | ||
| 140 | Tag::from_standardized(TagStandard::Name(self.name.clone())), | ||
| 141 | Tag::from_standardized(TagStandard::Description(self.description.clone())), | ||
| 142 | Tag::custom( | ||
| 143 | nostr::TagKind::Custom(std::borrow::Cow::Borrowed("clone")), | ||
| 144 | self.git_server.clone(), | ||
| 145 | ), | ||
| 146 | Tag::custom( | ||
| 147 | nostr::TagKind::Custom(std::borrow::Cow::Borrowed("web")), | ||
| 148 | self.web.clone(), | ||
| 149 | ), | ||
| 150 | Tag::custom( | ||
| 151 | nostr::TagKind::Custom(std::borrow::Cow::Borrowed("relays")), | ||
| 152 | self.relays.clone(), | ||
| 153 | ), | ||
| 154 | Tag::custom( | ||
| 155 | nostr::TagKind::Custom(std::borrow::Cow::Borrowed("maintainers")), | ||
| 156 | self.maintainers | ||
| 157 | .iter() | ||
| 158 | .map(std::string::ToString::to_string) | ||
| 159 | .collect::<Vec<String>>(), | ||
| 160 | ), | ||
| 161 | Tag::custom( | ||
| 162 | nostr::TagKind::Custom(std::borrow::Cow::Borrowed("alt")), | ||
| 163 | vec![format!("git repository: {}", self.name.clone())], | ||
| 164 | ), | ||
| 165 | ], | ||
| 166 | // code languages and hashtags | ||
| 167 | ] | ||
| 168 | .concat(), | ||
| 169 | ), | ||
| 170 | signer, | ||
| 171 | ) | ||
| 172 | .await | ||
| 173 | .context("failed to create repository reference event") | ||
| 174 | } | ||
| 175 | /// coordinates without relay hints | ||
| 176 | pub fn coordinates(&self) -> HashSet<Coordinate> { | ||
| 177 | let mut res = HashSet::new(); | ||
| 178 | for m in &self.maintainers { | ||
| 179 | res.insert(Coordinate { | ||
| 180 | kind: Kind::GitRepoAnnouncement, | ||
| 181 | public_key: *m, | ||
| 182 | identifier: self.identifier.clone(), | ||
| 183 | relays: vec![], | ||
| 184 | }); | ||
| 185 | } | ||
| 186 | res | ||
| 187 | } | ||
| 188 | |||
| 189 | /// coordinates without relay hints | ||
| 190 | pub fn coordinate_with_hint(&self) -> Coordinate { | ||
| 191 | Coordinate { | ||
| 192 | kind: Kind::GitRepoAnnouncement, | ||
| 193 | public_key: *self | ||
| 194 | .maintainers | ||
| 195 | .first() | ||
| 196 | .context("no maintainers in repo ref") | ||
| 197 | .unwrap(), | ||
| 198 | identifier: self.identifier.clone(), | ||
| 199 | relays: if let Some(relay) = self.relays.first() { | ||
| 200 | vec![relay.to_string()] | ||
| 201 | } else { | ||
| 202 | vec![] | ||
| 203 | }, | ||
| 204 | } | ||
| 205 | } | ||
| 206 | |||
| 207 | /// coordinates without relay hints | ||
| 208 | pub fn coordinates_with_timestamps(&self) -> Vec<(Coordinate, Option<Timestamp>)> { | ||
| 209 | self.coordinates() | ||
| 210 | .iter() | ||
| 211 | .map(|c| (c.clone(), self.events.get(c).map(|e| e.created_at))) | ||
| 212 | .collect::<Vec<(Coordinate, Option<Timestamp>)>>() | ||
| 213 | } | ||
| 214 | } | ||
| 215 | |||
| 216 | pub async fn get_repo_coordinates( | ||
| 217 | git_repo: &Repo, | ||
| 218 | #[cfg(test)] client: &crate::client::MockConnect, | ||
| 219 | #[cfg(not(test))] client: &Client, | ||
| 220 | ) -> Result<HashSet<Coordinate>> { | ||
| 221 | try_and_get_repo_coordinates(git_repo, client, true).await | ||
| 222 | } | ||
| 223 | |||
| 224 | pub async fn try_and_get_repo_coordinates( | ||
| 225 | git_repo: &Repo, | ||
| 226 | #[cfg(test)] client: &crate::client::MockConnect, | ||
| 227 | #[cfg(not(test))] client: &Client, | ||
| 228 | prompt_user: bool, | ||
| 229 | ) -> Result<HashSet<Coordinate>> { | ||
| 230 | let mut repo_coordinates = get_repo_coordinates_from_git_config(git_repo)?; | ||
| 231 | |||
| 232 | if repo_coordinates.is_empty() { | ||
| 233 | repo_coordinates = get_repo_coordinates_from_nostr_remotes(git_repo)?; | ||
| 234 | } | ||
| 235 | |||
| 236 | if repo_coordinates.is_empty() { | ||
| 237 | repo_coordinates = get_repo_coordinates_from_maintainers_yaml(git_repo, client).await?; | ||
| 238 | } | ||
| 239 | |||
| 240 | if repo_coordinates.is_empty() { | ||
| 241 | if prompt_user { | ||
| 242 | repo_coordinates = get_repo_coordinates_from_user_prompt(git_repo)?; | ||
| 243 | } else { | ||
| 244 | bail!("couldn't find repo coordinates in git config nostr.repo or in maintainers.yaml"); | ||
| 245 | } | ||
| 246 | } | ||
| 247 | Ok(repo_coordinates) | ||
| 248 | } | ||
| 249 | |||
| 250 | fn get_repo_coordinates_from_git_config(git_repo: &Repo) -> Result<HashSet<Coordinate>> { | ||
| 251 | let mut repo_coordinates = HashSet::new(); | ||
| 252 | if let Some(repo_override) = git_repo.get_git_config_item("nostr.repo", Some(false))? { | ||
| 253 | for s in repo_override.split(',') { | ||
| 254 | if let Ok(c) = Coordinate::parse(s) { | ||
| 255 | repo_coordinates.insert(c); | ||
| 256 | } | ||
| 257 | } | ||
| 258 | } | ||
| 259 | Ok(repo_coordinates) | ||
| 260 | } | ||
| 261 | |||
| 262 | fn get_repo_coordinates_from_nostr_remotes(git_repo: &Repo) -> Result<HashSet<Coordinate>> { | ||
| 263 | let mut repo_coordinates = HashSet::new(); | ||
| 264 | for remote_name in git_repo.git_repo.remotes()?.iter().flatten() { | ||
| 265 | if let Some(remote_url) = git_repo.git_repo.find_remote(remote_name)?.url() { | ||
| 266 | if let Ok(nostr_url_decoded) = NostrUrlDecoded::from_str(remote_url) { | ||
| 267 | for c in nostr_url_decoded.coordinates { | ||
| 268 | repo_coordinates.insert(c); | ||
| 269 | } | ||
| 270 | } | ||
| 271 | } | ||
| 272 | } | ||
| 273 | Ok(repo_coordinates) | ||
| 274 | } | ||
| 275 | |||
| 276 | async fn get_repo_coordinates_from_maintainers_yaml( | ||
| 277 | git_repo: &Repo, | ||
| 278 | #[cfg(test)] client: &crate::client::MockConnect, | ||
| 279 | #[cfg(not(test))] client: &Client, | ||
| 280 | ) -> Result<HashSet<Coordinate>> { | ||
| 281 | let mut repo_coordinates = HashSet::new(); | ||
| 282 | if let Ok(repo_config) = get_repo_config_from_yaml(git_repo) { | ||
| 283 | let maintainers = { | ||
| 284 | let mut maintainers = HashSet::new(); | ||
| 285 | for m in &repo_config.maintainers { | ||
| 286 | if let Ok(maintainer) = PublicKey::parse(m) { | ||
| 287 | maintainers.insert(maintainer); | ||
| 288 | } | ||
| 289 | } | ||
| 290 | maintainers | ||
| 291 | }; | ||
| 292 | if let Some(identifier) = repo_config.identifier { | ||
| 293 | for public_key in maintainers { | ||
| 294 | repo_coordinates.insert(Coordinate { | ||
| 295 | kind: Kind::GitRepoAnnouncement, | ||
| 296 | public_key, | ||
| 297 | identifier: identifier.clone(), | ||
| 298 | relays: vec![], | ||
| 299 | }); | ||
| 300 | } | ||
| 301 | } else { | ||
| 302 | // if repo_config.identifier.is_empty() { | ||
| 303 | // this will only apply for a few repositories created before ngit v1.3 | ||
| 304 | // that haven't updated their maintainers.yaml | ||
| 305 | if let Ok(Some(current_user_npub)) = git_repo.get_git_config_item("nostr.npub", None) { | ||
| 306 | if let Ok(current_user) = PublicKey::parse(current_user_npub) { | ||
| 307 | for m in &repo_config.maintainers { | ||
| 308 | if let Ok(maintainer) = PublicKey::parse(m) { | ||
| 309 | if current_user.eq(&maintainer) { | ||
| 310 | println!( | ||
| 311 | "please run `ngit init` to add the repo identifier to maintainers.yaml" | ||
| 312 | ); | ||
| 313 | } | ||
| 314 | } | ||
| 315 | } | ||
| 316 | } | ||
| 317 | } | ||
| 318 | // look find all repo refs with root_commit. for identifier | ||
| 319 | let filter = nostr::Filter::default() | ||
| 320 | .kind(nostr::Kind::GitRepoAnnouncement) | ||
| 321 | .reference(git_repo.get_root_commit()?.to_string()) | ||
| 322 | .authors(maintainers.clone()); | ||
| 323 | let mut events = | ||
| 324 | get_events_from_cache(git_repo.get_path()?, vec![filter.clone()]).await?; | ||
| 325 | if events.is_empty() { | ||
| 326 | events = | ||
| 327 | get_event_from_global_cache(git_repo.get_path()?, vec![filter.clone()]).await?; | ||
| 328 | } | ||
| 329 | if events.is_empty() { | ||
| 330 | println!( | ||
| 331 | "finding repository events for this repository for npubs in maintainers.yaml" | ||
| 332 | ); | ||
| 333 | events = client | ||
| 334 | .get_events(client.get_fallback_relays().clone(), vec![filter.clone()]) | ||
| 335 | .await?; | ||
| 336 | } | ||
| 337 | if let Some(e) = events.first() { | ||
| 338 | if let Some(identifier) = e.identifier() { | ||
| 339 | for m in &repo_config.maintainers { | ||
| 340 | if let Ok(maintainer) = PublicKey::parse(m) { | ||
| 341 | repo_coordinates.insert(Coordinate { | ||
| 342 | kind: Kind::GitRepoAnnouncement, | ||
| 343 | public_key: maintainer, | ||
| 344 | identifier: identifier.to_string(), | ||
| 345 | relays: vec![], | ||
| 346 | }); | ||
| 347 | } | ||
| 348 | } | ||
| 349 | } | ||
| 350 | } else { | ||
| 351 | let c = ask_for_naddr()?; | ||
| 352 | git_repo.save_git_config_item("nostr.repo", &c.to_bech32()?, false)?; | ||
| 353 | repo_coordinates.insert(c); | ||
| 354 | } | ||
| 355 | } | ||
| 356 | } | ||
| 357 | Ok(repo_coordinates) | ||
| 358 | } | ||
| 359 | |||
| 360 | fn get_repo_coordinates_from_user_prompt(git_repo: &Repo) -> Result<HashSet<Coordinate>> { | ||
| 361 | let mut repo_coordinates = HashSet::new(); | ||
| 362 | // TODO: present list of events filter by root_commit | ||
| 363 | // TODO: fallback to search based on identifier | ||
| 364 | let c = ask_for_naddr()?; | ||
| 365 | // PROBLEM: we are saving this before checking whether it actually exists, which | ||
| 366 | // means next time the user won't be prompted and may not know how to | ||
| 367 | // change the selected repo | ||
| 368 | git_repo.save_git_config_item("nostr.repo", &c.to_bech32()?, false)?; | ||
| 369 | repo_coordinates.insert(c); | ||
| 370 | Ok(repo_coordinates) | ||
| 371 | } | ||
| 372 | |||
| 373 | fn ask_for_naddr() -> Result<Coordinate> { | ||
| 374 | let dim = Style::new().color256(247); | ||
| 375 | println!( | ||
| 376 | "{}", | ||
| 377 | dim.apply_to("hint: https://gitworkshop.dev/repos lists repositories and their naddr"), | ||
| 378 | ); | ||
| 379 | |||
| 380 | Ok(loop { | ||
| 381 | if let Ok(c) = Coordinate::parse( | ||
| 382 | Interactor::default() | ||
| 383 | .input(PromptInputParms::default().with_prompt("repository naddr"))?, | ||
| 384 | ) { | ||
| 385 | break c; | ||
| 386 | } | ||
| 387 | println!("not a valid naddr"); | ||
| 388 | }) | ||
| 389 | } | ||
| 390 | |||
| 391 | #[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq, Eq)] | ||
| 392 | pub struct RepoConfigYaml { | ||
| 393 | pub identifier: Option<String>, | ||
| 394 | pub maintainers: Vec<String>, | ||
| 395 | pub relays: Vec<String>, | ||
| 396 | } | ||
| 397 | |||
| 398 | pub fn get_repo_config_from_yaml(git_repo: &Repo) -> Result<RepoConfigYaml> { | ||
| 399 | let path = git_repo.get_path()?.join("maintainers.yaml"); | ||
| 400 | let file = File::open(path) | ||
| 401 | .context("should open maintainers.yaml if it exists") | ||
| 402 | .context("maintainers.yaml doesnt exist")?; | ||
| 403 | let reader = BufReader::new(file); | ||
| 404 | let repo_config_yaml: RepoConfigYaml = serde_yaml::from_reader(reader) | ||
| 405 | .context("should read maintainers.yaml with serde_yaml") | ||
| 406 | .context("maintainers.yaml incorrectly formatted")?; | ||
| 407 | Ok(repo_config_yaml) | ||
| 408 | } | ||
| 409 | |||
| 410 | pub fn extract_pks(pk_strings: Vec<String>) -> Result<Vec<PublicKey>> { | ||
| 411 | let mut pks: Vec<PublicKey> = vec![]; | ||
| 412 | for s in pk_strings { | ||
| 413 | pks.push( | ||
| 414 | nostr_sdk::prelude::PublicKey::from_bech32(s.clone()) | ||
| 415 | .context(format!("cannot convert {s} into a valid nostr public key"))?, | ||
| 416 | ); | ||
| 417 | } | ||
| 418 | Ok(pks) | ||
| 419 | } | ||
| 420 | |||
| 421 | pub fn save_repo_config_to_yaml( | ||
| 422 | git_repo: &Repo, | ||
| 423 | identifier: String, | ||
| 424 | maintainers: Vec<PublicKey>, | ||
| 425 | relays: Vec<String>, | ||
| 426 | ) -> Result<()> { | ||
| 427 | let path = git_repo.get_path()?.join("maintainers.yaml"); | ||
| 428 | let file = if path.exists() { | ||
| 429 | std::fs::OpenOptions::new() | ||
| 430 | .create(true) | ||
| 431 | .write(true) | ||
| 432 | .truncate(true) | ||
| 433 | .open(path) | ||
| 434 | .context("cannot open maintainers.yaml file with write and truncate options")? | ||
| 435 | } else { | ||
| 436 | std::fs::File::create(path).context("cannot create maintainers.yaml file")? | ||
| 437 | }; | ||
| 438 | let mut maintainers_npubs = vec![]; | ||
| 439 | for m in maintainers { | ||
| 440 | maintainers_npubs.push( | ||
| 441 | m.to_bech32() | ||
| 442 | .context("cannot convert public key into npub")?, | ||
| 443 | ); | ||
| 444 | } | ||
| 445 | serde_yaml::to_writer( | ||
| 446 | file, | ||
| 447 | &RepoConfigYaml { | ||
| 448 | identifier: Some(identifier), | ||
| 449 | maintainers: maintainers_npubs, | ||
| 450 | relays, | ||
| 451 | }, | ||
| 452 | ) | ||
| 453 | .context("cannot write maintainers to maintainers.yaml file serde_yaml") | ||
| 454 | } | ||
| 455 | |||
| 456 | #[cfg(test)] | ||
| 457 | mod tests { | ||
| 458 | use test_utils::*; | ||
| 459 | |||
| 460 | use super::*; | ||
| 461 | |||
| 462 | async fn create() -> nostr::Event { | ||
| 463 | RepoRef { | ||
| 464 | identifier: "123412341".to_string(), | ||
| 465 | name: "test name".to_string(), | ||
| 466 | description: "test description".to_string(), | ||
| 467 | root_commit: "5e664e5a7845cd1373c79f580ca4fe29ab5b34d2".to_string(), | ||
| 468 | git_server: vec!["https://localhost:1000".to_string()], | ||
| 469 | web: vec![ | ||
| 470 | "https://exampleproject.xyz".to_string(), | ||
| 471 | "https://gitworkshop.dev/123".to_string(), | ||
| 472 | ], | ||
| 473 | relays: vec!["ws://relay1.io".to_string(), "ws://relay2.io".to_string()], | ||
| 474 | maintainers: vec![TEST_KEY_1_KEYS.public_key(), TEST_KEY_2_KEYS.public_key()], | ||
| 475 | events: HashMap::new(), | ||
| 476 | } | ||
| 477 | .to_event(&TEST_KEY_1_SIGNER) | ||
| 478 | .await | ||
| 479 | .unwrap() | ||
| 480 | } | ||
| 481 | mod try_from { | ||
| 482 | use super::*; | ||
| 483 | |||
| 484 | #[tokio::test] | ||
| 485 | async fn identifier() { | ||
| 486 | assert_eq!( | ||
| 487 | RepoRef::try_from(create().await).unwrap().identifier, | ||
| 488 | "123412341", | ||
| 489 | ) | ||
| 490 | } | ||
| 491 | |||
| 492 | #[tokio::test] | ||
| 493 | async fn name() { | ||
| 494 | assert_eq!(RepoRef::try_from(create().await).unwrap().name, "test name",) | ||
| 495 | } | ||
| 496 | |||
| 497 | #[tokio::test] | ||
| 498 | async fn description() { | ||
| 499 | assert_eq!( | ||
| 500 | RepoRef::try_from(create().await).unwrap().description, | ||
| 501 | "test description", | ||
| 502 | ) | ||
| 503 | } | ||
| 504 | |||
| 505 | #[tokio::test] | ||
| 506 | async fn root_commit_is_r_tag() { | ||
| 507 | assert_eq!( | ||
| 508 | RepoRef::try_from(create().await).unwrap().root_commit, | ||
| 509 | "5e664e5a7845cd1373c79f580ca4fe29ab5b34d2", | ||
| 510 | ) | ||
| 511 | } | ||
| 512 | |||
| 513 | mod root_commit_is_empty_if_no_r_tag_which_is_sha1_format { | ||
| 514 | use nostr::JsonUtil; | ||
| 515 | |||
| 516 | use super::*; | ||
| 517 | async fn create_with_incorrect_first_commit_ref(s: &str) -> nostr::Event { | ||
| 518 | nostr::Event::from_json( | ||
| 519 | create() | ||
| 520 | .await | ||
| 521 | .as_json() | ||
| 522 | .replace("5e664e5a7845cd1373c79f580ca4fe29ab5b34d2", s), | ||
| 523 | ) | ||
| 524 | .unwrap() | ||
| 525 | } | ||
| 526 | |||
| 527 | #[tokio::test] | ||
| 528 | async fn less_than_40_characters() { | ||
| 529 | let s = "5e664e5a7845cd1373"; | ||
| 530 | assert_eq!( | ||
| 531 | RepoRef::try_from(create_with_incorrect_first_commit_ref(s).await) | ||
| 532 | .unwrap() | ||
| 533 | .root_commit, | ||
| 534 | "", | ||
| 535 | ) | ||
| 536 | } | ||
| 537 | |||
| 538 | #[tokio::test] | ||
| 539 | async fn more_than_40_characters() { | ||
| 540 | let s = "5e664e5a7845cd1373c79f580ca4fe29ab5b34d2111111111"; | ||
| 541 | assert_eq!( | ||
| 542 | RepoRef::try_from(create_with_incorrect_first_commit_ref(s).await) | ||
| 543 | .unwrap() | ||
| 544 | .root_commit, | ||
| 545 | "", | ||
| 546 | ) | ||
| 547 | } | ||
| 548 | |||
| 549 | #[tokio::test] | ||
| 550 | async fn not_hex_characters() { | ||
| 551 | let s = "xxx64e5a7845cd1373c79f580ca4fe29ab5b34d2"; | ||
| 552 | assert_eq!( | ||
| 553 | RepoRef::try_from(create_with_incorrect_first_commit_ref(s).await) | ||
| 554 | .unwrap() | ||
| 555 | .root_commit, | ||
| 556 | "", | ||
| 557 | ) | ||
| 558 | } | ||
| 559 | } | ||
| 560 | |||
| 561 | #[tokio::test] | ||
| 562 | async fn git_server() { | ||
| 563 | assert_eq!( | ||
| 564 | RepoRef::try_from(create().await).unwrap().git_server, | ||
| 565 | vec!["https://localhost:1000"], | ||
| 566 | ) | ||
| 567 | } | ||
| 568 | |||
| 569 | #[tokio::test] | ||
| 570 | async fn web() { | ||
| 571 | assert_eq!( | ||
| 572 | RepoRef::try_from(create().await).unwrap().web, | ||
| 573 | vec![ | ||
| 574 | "https://exampleproject.xyz".to_string(), | ||
| 575 | "https://gitworkshop.dev/123".to_string() | ||
| 576 | ], | ||
| 577 | ) | ||
| 578 | } | ||
| 579 | |||
| 580 | #[tokio::test] | ||
| 581 | async fn relays() { | ||
| 582 | assert_eq!( | ||
| 583 | RepoRef::try_from(create().await).unwrap().relays, | ||
| 584 | vec!["ws://relay1.io".to_string(), "ws://relay2.io".to_string()], | ||
| 585 | ) | ||
| 586 | } | ||
| 587 | |||
| 588 | #[tokio::test] | ||
| 589 | async fn maintainers() { | ||
| 590 | assert_eq!( | ||
| 591 | RepoRef::try_from(create().await).unwrap().maintainers, | ||
| 592 | vec![TEST_KEY_1_KEYS.public_key(), TEST_KEY_2_KEYS.public_key()], | ||
| 593 | ) | ||
| 594 | } | ||
| 595 | } | ||
| 596 | |||
| 597 | mod to_event { | ||
| 598 | use super::*; | ||
| 599 | mod tags { | ||
| 600 | use super::*; | ||
| 601 | |||
| 602 | #[tokio::test] | ||
| 603 | async fn identifier() { | ||
| 604 | assert!( | ||
| 605 | create() | ||
| 606 | .await | ||
| 607 | .tags | ||
| 608 | .iter() | ||
| 609 | .any(|t| t.as_vec()[0].eq("d") && t.as_vec()[1].eq("123412341")) | ||
| 610 | ) | ||
| 611 | } | ||
| 612 | |||
| 613 | #[tokio::test] | ||
| 614 | async fn name() { | ||
| 615 | assert!( | ||
| 616 | create() | ||
| 617 | .await | ||
| 618 | .tags | ||
| 619 | .iter() | ||
| 620 | .any(|t| t.as_vec()[0].eq("name") && t.as_vec()[1].eq("test name")) | ||
| 621 | ) | ||
| 622 | } | ||
| 623 | |||
| 624 | #[tokio::test] | ||
| 625 | async fn alt() { | ||
| 626 | assert!( | ||
| 627 | create().await.tags.iter().any(|t| t.as_vec()[0].eq("alt") | ||
| 628 | && t.as_vec()[1].eq("git repository: test name")) | ||
| 629 | ) | ||
| 630 | } | ||
| 631 | |||
| 632 | #[tokio::test] | ||
| 633 | async fn description() { | ||
| 634 | assert!(create().await.tags.iter().any( | ||
| 635 | |t| t.as_vec()[0].eq("description") && t.as_vec()[1].eq("test description") | ||
| 636 | )) | ||
| 637 | } | ||
| 638 | |||
| 639 | #[tokio::test] | ||
| 640 | async fn root_commit_as_reference() { | ||
| 641 | assert!(create().await.tags.iter().any(|t| t.as_vec()[0].eq("r") | ||
| 642 | && t.as_vec()[1].eq("5e664e5a7845cd1373c79f580ca4fe29ab5b34d2"))) | ||
| 643 | } | ||
| 644 | |||
| 645 | #[tokio::test] | ||
| 646 | async fn git_server() { | ||
| 647 | assert!(create().await.tags.iter().any( | ||
| 648 | |t| t.as_vec()[0].eq("clone") && t.as_vec()[1].eq("https://localhost:1000") | ||
| 649 | )) | ||
| 650 | } | ||
| 651 | |||
| 652 | #[tokio::test] | ||
| 653 | async fn relays() { | ||
| 654 | let event = create().await; | ||
| 655 | let relays_tag: &nostr::Tag = event | ||
| 656 | .tags | ||
| 657 | .iter() | ||
| 658 | .find(|t| t.as_vec()[0].eq("relays")) | ||
| 659 | .unwrap(); | ||
| 660 | assert_eq!(relays_tag.as_vec().len(), 3); | ||
| 661 | assert_eq!(relays_tag.as_vec()[1], "ws://relay1.io"); | ||
| 662 | assert_eq!(relays_tag.as_vec()[2], "ws://relay2.io"); | ||
| 663 | } | ||
| 664 | |||
| 665 | #[tokio::test] | ||
| 666 | async fn web() { | ||
| 667 | let event = create().await; | ||
| 668 | let web_tag: &nostr::Tag = | ||
| 669 | event.tags.iter().find(|t| t.as_vec()[0].eq("web")).unwrap(); | ||
| 670 | assert_eq!(web_tag.as_vec().len(), 3); | ||
| 671 | assert_eq!(web_tag.as_vec()[1], "https://exampleproject.xyz"); | ||
| 672 | assert_eq!(web_tag.as_vec()[2], "https://gitworkshop.dev/123"); | ||
| 673 | } | ||
| 674 | |||
| 675 | #[tokio::test] | ||
| 676 | async fn maintainers() { | ||
| 677 | let event = create().await; | ||
| 678 | let maintainers_tag: &nostr::Tag = event | ||
| 679 | .tags | ||
| 680 | .iter() | ||
| 681 | .find(|t| t.as_vec()[0].eq("maintainers")) | ||
| 682 | .unwrap(); | ||
| 683 | assert_eq!(maintainers_tag.as_vec().len(), 3); | ||
| 684 | assert_eq!( | ||
| 685 | maintainers_tag.as_vec()[1], | ||
| 686 | TEST_KEY_1_KEYS.public_key().to_string() | ||
| 687 | ); | ||
| 688 | assert_eq!( | ||
| 689 | maintainers_tag.as_vec()[2], | ||
| 690 | TEST_KEY_2_KEYS.public_key().to_string() | ||
| 691 | ); | ||
| 692 | } | ||
| 693 | |||
| 694 | #[tokio::test] | ||
| 695 | async fn no_other_tags() { | ||
| 696 | assert_eq!(create().await.tags.len(), 9) | ||
| 697 | } | ||
| 698 | } | ||
| 699 | } | ||
| 700 | } | ||