diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2024-07-19 21:59:50 +0100 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2024-07-19 21:59:50 +0100 |
| commit | dde029b4f5988cfa830ebc36bee0f35c93bd2544 (patch) | |
| tree | cd2362d202afcc5c4f388ac17390b42715a5eddc | |
| parent | 55fd2693e314a87d32872cd74d3d563cd94201a2 (diff) | |
feat: integrate `fetch` into `list`
as part of a project to use fetch and the stored cache everywhere
| -rw-r--r-- | src/sub_commands/list.rs | 262 | ||||
| -rw-r--r-- | tests/list.rs | 115 |
2 files changed, 159 insertions, 218 deletions
diff --git a/src/sub_commands/list.rs b/src/sub_commands/list.rs index d3f583f..2ae4cfb 100644 --- a/src/sub_commands/list.rs +++ b/src/sub_commands/list.rs | |||
| @@ -1,17 +1,19 @@ | |||
| 1 | use std::{io::Write, ops::Add}; | 1 | use std::{collections::HashSet, io::Write, ops::Add, path::Path}; |
| 2 | 2 | ||
| 3 | use anyhow::{bail, Context, Result}; | 3 | use anyhow::{bail, Context, Result}; |
| 4 | use nostr::nips::nip01::Coordinate; | ||
| 5 | use nostr_sdk::PublicKey; | ||
| 4 | 6 | ||
| 5 | use super::send::event_is_patch_set_root; | 7 | use super::send::event_is_patch_set_root; |
| 6 | #[cfg(not(test))] | ||
| 7 | use crate::client::Client; | ||
| 8 | #[cfg(test)] | 8 | #[cfg(test)] |
| 9 | use crate::client::MockConnect; | 9 | use crate::client::MockConnect; |
| 10 | #[cfg(not(test))] | ||
| 11 | use crate::client::{Client, Connect}; | ||
| 10 | use crate::{ | 12 | use crate::{ |
| 11 | cli_interactor::{Interactor, InteractorPrompt, PromptChoiceParms, PromptConfirmParms}, | 13 | cli_interactor::{Interactor, InteractorPrompt, PromptChoiceParms, PromptConfirmParms}, |
| 12 | client::Connect, | 14 | client::{fetching_with_report, get_events_from_cache, get_repo_ref_from_cache}, |
| 13 | git::{str_to_sha1, Repo, RepoActions}, | 15 | git::{str_to_sha1, Repo, RepoActions}, |
| 14 | repo_ref::{self, RepoRef, REPO_REF_KIND}, | 16 | repo_ref::{get_repo_coordinates, RepoRef}, |
| 15 | sub_commands::send::{ | 17 | sub_commands::send::{ |
| 16 | commit_msg_from_patch_oneliner, event_is_cover_letter, event_is_revision_root, | 18 | commit_msg_from_patch_oneliner, event_is_cover_letter, event_is_revision_root, |
| 17 | event_to_cover_letter, patch_supports_commit_ids, PATCH_KIND, | 19 | event_to_cover_letter, patch_supports_commit_ids, PATCH_KIND, |
| @@ -21,10 +23,7 @@ use crate::{ | |||
| 21 | #[allow(clippy::too_many_lines)] | 23 | #[allow(clippy::too_many_lines)] |
| 22 | pub async fn launch() -> Result<()> { | 24 | pub async fn launch() -> Result<()> { |
| 23 | let git_repo = Repo::discover().context("cannot find a git repository")?; | 25 | let git_repo = Repo::discover().context("cannot find a git repository")?; |
| 24 | 26 | let git_repo_path = git_repo.get_path()?; | |
| 25 | let root_commit = git_repo | ||
| 26 | .get_root_commit() | ||
| 27 | .context("failed to get root commit of the repository")?; | ||
| 28 | 27 | ||
| 29 | // TODO: check for empty repo | 28 | // TODO: check for empty repo |
| 30 | // TODO: check for existing maintaiers file | 29 | // TODO: check for existing maintaiers file |
| @@ -35,36 +34,47 @@ pub async fn launch() -> Result<()> { | |||
| 35 | #[cfg(test)] | 34 | #[cfg(test)] |
| 36 | let client = <MockConnect as std::default::Default>::default(); | 35 | let client = <MockConnect as std::default::Default>::default(); |
| 37 | 36 | ||
| 38 | let repo_ref = repo_ref::fetch( | 37 | let repo_coordinates = get_repo_coordinates(&git_repo, &client).await?; |
| 39 | &git_repo, | ||
| 40 | root_commit.to_string(), | ||
| 41 | &client, | ||
| 42 | client.get_fallback_relays().clone(), | ||
| 43 | true, | ||
| 44 | ) | ||
| 45 | .await?; | ||
| 46 | 38 | ||
| 47 | println!("finding proposals..."); | 39 | fetching_with_report(git_repo_path, &client, &repo_coordinates).await?; |
| 48 | 40 | ||
| 49 | let proposal_events_and_revisions_and_statuses: Vec<nostr::Event> = | 41 | let repo_ref = get_repo_ref_from_cache(git_repo_path, &repo_coordinates).await?; |
| 50 | find_proposal_and_status_events(&client, &repo_ref, &root_commit.to_string()).await?; | ||
| 51 | 42 | ||
| 52 | if proposal_events_and_revisions_and_statuses.is_empty() { | 43 | let proposals_and_revisions: Vec<nostr::Event> = |
| 44 | get_proposals_and_revisions_from_cache(git_repo_path, repo_ref.coordinates()).await?; | ||
| 45 | if proposals_and_revisions.is_empty() { | ||
| 53 | println!("no proposals found... create one? try `ngit send`"); | 46 | println!("no proposals found... create one? try `ngit send`"); |
| 54 | return Ok(()); | 47 | return Ok(()); |
| 55 | } | 48 | } |
| 56 | let proposal_events: Vec<&nostr::Event> = proposal_events_and_revisions_and_statuses | 49 | |
| 57 | .iter() | 50 | let statuses: Vec<nostr::Event> = { |
| 58 | .filter(|e| !event_is_revision_root(e) && e.kind().as_u16().eq(&PATCH_KIND)) | 51 | let mut statuses = get_events_from_cache( |
| 59 | .collect::<Vec<&nostr::Event>>(); | 52 | git_repo_path, |
| 53 | vec![ | ||
| 54 | nostr::Filter::default() | ||
| 55 | .kinds(status_kinds().clone()) | ||
| 56 | .events(proposals_and_revisions.iter().map(nostr::Event::id)), | ||
| 57 | ], | ||
| 58 | ) | ||
| 59 | .await?; | ||
| 60 | statuses.sort_by_key(|e| e.created_at); | ||
| 61 | statuses.reverse(); | ||
| 62 | statuses | ||
| 63 | }; | ||
| 60 | 64 | ||
| 61 | let mut open_proposals: Vec<&nostr::Event> = vec![]; | 65 | let mut open_proposals: Vec<&nostr::Event> = vec![]; |
| 62 | let mut draft_proposals: Vec<&nostr::Event> = vec![]; | 66 | let mut draft_proposals: Vec<&nostr::Event> = vec![]; |
| 63 | let mut closed_proposals: Vec<&nostr::Event> = vec![]; | 67 | let mut closed_proposals: Vec<&nostr::Event> = vec![]; |
| 64 | let mut applied_proposals: Vec<&nostr::Event> = vec![]; | 68 | let mut applied_proposals: Vec<&nostr::Event> = vec![]; |
| 65 | 69 | ||
| 66 | for proposal in &proposal_events { | 70 | let proposals: Vec<nostr::Event> = proposals_and_revisions |
| 67 | let status = if let Some(e) = proposal_events_and_revisions_and_statuses | 71 | .iter() |
| 72 | .filter(|e| !event_is_revision_root(e)) | ||
| 73 | .cloned() | ||
| 74 | .collect(); | ||
| 75 | |||
| 76 | for proposal in &proposals { | ||
| 77 | let status = if let Some(e) = statuses | ||
| 68 | .iter() | 78 | .iter() |
| 69 | .filter(|e| { | 79 | .filter(|e| { |
| 70 | status_kinds().contains(&e.kind()) | 80 | status_kinds().contains(&e.kind()) |
| @@ -89,11 +99,6 @@ pub async fn launch() -> Result<()> { | |||
| 89 | } | 99 | } |
| 90 | } | 100 | } |
| 91 | 101 | ||
| 92 | if proposal_events_and_revisions_and_statuses.is_empty() { | ||
| 93 | println!("no open proposals found... create one? try `ngit send`"); | ||
| 94 | return Ok(()); | ||
| 95 | } | ||
| 96 | |||
| 97 | let mut selected_status = STATUS_KIND_OPEN; | 102 | let mut selected_status = STATUS_KIND_OPEN; |
| 98 | 103 | ||
| 99 | loop { | 104 | loop { |
| @@ -106,10 +111,10 @@ pub async fn launch() -> Result<()> { | |||
| 106 | } else if selected_status == STATUS_KIND_APPLIED { | 111 | } else if selected_status == STATUS_KIND_APPLIED { |
| 107 | &applied_proposals | 112 | &applied_proposals |
| 108 | } else { | 113 | } else { |
| 109 | &proposal_events | 114 | &open_proposals |
| 110 | }; | 115 | }; |
| 111 | 116 | ||
| 112 | let prompt = if proposal_events.len().eq(&open_proposals.len()) { | 117 | let prompt = if proposals.len().eq(&open_proposals.len()) { |
| 113 | "all proposals" | 118 | "all proposals" |
| 114 | } else if selected_status == STATUS_KIND_OPEN { | 119 | } else if selected_status == STATUS_KIND_OPEN { |
| 115 | if open_proposals.is_empty() { | 120 | if open_proposals.is_empty() { |
| @@ -176,31 +181,12 @@ pub async fn launch() -> Result<()> { | |||
| 176 | let cover_letter = event_to_cover_letter(proposals_for_status[selected_index]) | 181 | let cover_letter = event_to_cover_letter(proposals_for_status[selected_index]) |
| 177 | .context("cannot extract proposal details from proposal root event")?; | 182 | .context("cannot extract proposal details from proposal root event")?; |
| 178 | 183 | ||
| 179 | println!("finding commits..."); | 184 | let commits_events: Vec<nostr::Event> = get_all_proposal_patch_events_from_cache( |
| 180 | 185 | git_repo_path, | |
| 181 | let commits_events: Vec<nostr::Event> = find_commits_for_proposal_root_events( | ||
| 182 | &client, | ||
| 183 | &[ | ||
| 184 | vec![proposals_for_status[selected_index]], | ||
| 185 | proposal_events_and_revisions_and_statuses | ||
| 186 | .iter() | ||
| 187 | .filter(|e| { | ||
| 188 | e.kind.as_u16().eq(&PATCH_KIND) | ||
| 189 | && e.tags.iter().any(|t| { | ||
| 190 | t.as_vec().len().gt(&1) | ||
| 191 | && t.as_vec()[1] | ||
| 192 | .eq(&proposals_for_status[selected_index].id.to_string()) | ||
| 193 | }) | ||
| 194 | }) | ||
| 195 | .collect::<Vec<&nostr::Event>>(), | ||
| 196 | ] | ||
| 197 | .concat(), | ||
| 198 | &repo_ref, | 186 | &repo_ref, |
| 187 | &proposals_for_status[selected_index].id(), | ||
| 199 | ) | 188 | ) |
| 200 | .await?; | 189 | .await?; |
| 201 | // for commit in &commits_events { | ||
| 202 | // println!("cevent: {:?}", commit.as_json()); | ||
| 203 | // } | ||
| 204 | 190 | ||
| 205 | let Ok(most_recent_proposal_patch_chain) = | 191 | let Ok(most_recent_proposal_patch_chain) = |
| 206 | get_most_recent_patch_with_ancestors(commits_events.clone()) | 192 | get_most_recent_patch_with_ancestors(commits_events.clone()) |
| @@ -829,109 +815,93 @@ pub fn status_kinds() -> Vec<nostr::Kind> { | |||
| 829 | ] | 815 | ] |
| 830 | } | 816 | } |
| 831 | 817 | ||
| 832 | pub async fn find_proposal_and_status_events( | 818 | pub async fn get_proposals_and_revisions_from_cache( |
| 833 | #[cfg(test)] client: &crate::client::MockConnect, | 819 | git_repo_path: &Path, |
| 834 | #[cfg(not(test))] client: &Client, | 820 | repo_coordinates: HashSet<Coordinate>, |
| 835 | repo_ref: &RepoRef, | ||
| 836 | root_commit: &str, | ||
| 837 | ) -> Result<Vec<nostr::Event>> { | 821 | ) -> Result<Vec<nostr::Event>> { |
| 838 | let repo_tags_filter = nostr::Filter::default().custom_tag( | 822 | let mut proposals = get_events_from_cache( |
| 839 | nostr::SingleLetterTag::lowercase(nostr::Alphabet::A), | 823 | git_repo_path, |
| 840 | repo_ref | 824 | vec![ |
| 841 | .maintainers | 825 | nostr::Filter::default() |
| 842 | .iter() | 826 | .kind(nostr::Kind::Custom(PATCH_KIND)) |
| 843 | .map(|m| format!("{REPO_REF_KIND}:{m}:{}", repo_ref.identifier)), | 827 | .custom_tag( |
| 844 | ); | 828 | nostr::SingleLetterTag::lowercase(nostr_sdk::Alphabet::A), |
| 845 | let mut proposals = client | 829 | repo_coordinates |
| 846 | .get_events( | 830 | .iter() |
| 847 | repo_ref.relays.clone(), | 831 | .map(std::string::ToString::to_string) |
| 848 | vec![ | 832 | .collect::<Vec<String>>(), |
| 849 | repo_tags_filter | 833 | ), |
| 850 | .clone() | 834 | ], |
| 851 | .kind(nostr::Kind::Custom(PATCH_KIND)) | 835 | ) |
| 852 | .custom_tag( | 836 | .await? |
| 853 | nostr::SingleLetterTag::lowercase(nostr::Alphabet::T), | 837 | .iter() |
| 854 | vec!["root"], | 838 | .filter(|e| event_is_patch_set_root(e)) |
| 855 | ), | 839 | .cloned() |
| 856 | // also pick up proposals from the same repo but no target at our maintainers repo | 840 | .collect::<Vec<nostr::Event>>(); |
| 857 | // events | ||
| 858 | nostr::Filter::default() | ||
| 859 | .reference(root_commit) | ||
| 860 | .kind(nostr::Kind::Custom(PATCH_KIND)) | ||
| 861 | .custom_tag( | ||
| 862 | nostr::SingleLetterTag::lowercase(nostr::Alphabet::T), | ||
| 863 | vec!["root"], | ||
| 864 | ), | ||
| 865 | repo_tags_filter.clone().kinds(status_kinds().clone()), | ||
| 866 | nostr::Filter::default() | ||
| 867 | .reference(root_commit) | ||
| 868 | .kinds(status_kinds().clone()), | ||
| 869 | ], | ||
| 870 | ) | ||
| 871 | .await | ||
| 872 | .context("cannot get proposal events")? | ||
| 873 | .iter() | ||
| 874 | .filter(|e| { | ||
| 875 | (event_is_patch_set_root(e) || status_kinds().contains(&e.kind())) | ||
| 876 | && (e | ||
| 877 | .tags | ||
| 878 | .iter() | ||
| 879 | .any(|t| t.as_vec().len() > 1 && t.as_vec()[1].eq(root_commit)) | ||
| 880 | || e.tags.iter().any(|t| { | ||
| 881 | t.as_vec().len() > 1 | ||
| 882 | && repo_ref | ||
| 883 | .maintainers | ||
| 884 | .iter() | ||
| 885 | .map(|m| format!("{REPO_REF_KIND}:{m}:{}", repo_ref.identifier)) | ||
| 886 | .any(|d| t.as_vec()[1].eq(&d)) | ||
| 887 | })) | ||
| 888 | }) | ||
| 889 | .map(std::borrow::ToOwned::to_owned) | ||
| 890 | .collect::<Vec<nostr::Event>>(); | ||
| 891 | proposals.sort_by_key(|e| e.created_at); | 841 | proposals.sort_by_key(|e| e.created_at); |
| 892 | proposals.reverse(); | 842 | proposals.reverse(); |
| 893 | Ok(proposals) | 843 | Ok(proposals) |
| 894 | } | 844 | } |
| 895 | 845 | ||
| 896 | pub async fn find_commits_for_proposal_root_events( | 846 | pub async fn get_all_proposal_patch_events_from_cache( |
| 897 | #[cfg(test)] client: &crate::client::MockConnect, | 847 | git_repo_path: &Path, |
| 898 | #[cfg(not(test))] client: &Client, | ||
| 899 | proposal_root_events: &Vec<&nostr::Event>, | ||
| 900 | repo_ref: &RepoRef, | 848 | repo_ref: &RepoRef, |
| 849 | proposal_id: &nostr::EventId, | ||
| 901 | ) -> Result<Vec<nostr::Event>> { | 850 | ) -> Result<Vec<nostr::Event>> { |
| 902 | let mut patch_events: Vec<nostr::Event> = client | 851 | let mut commit_events = get_events_from_cache( |
| 903 | .get_events( | 852 | git_repo_path, |
| 904 | repo_ref.relays.clone(), | 853 | vec![ |
| 854 | nostr::Filter::default() | ||
| 855 | .kind(nostr::Kind::Custom(PATCH_KIND)) | ||
| 856 | .event(*proposal_id), | ||
| 857 | nostr::Filter::default() | ||
| 858 | .kind(nostr::Kind::Custom(PATCH_KIND)) | ||
| 859 | .id(*proposal_id), | ||
| 860 | ], | ||
| 861 | ) | ||
| 862 | .await?; | ||
| 863 | |||
| 864 | let permissioned_users: HashSet<PublicKey> = [ | ||
| 865 | repo_ref.maintainers.clone(), | ||
| 866 | vec![ | ||
| 867 | commit_events | ||
| 868 | .iter() | ||
| 869 | .find(|e| e.id().eq(proposal_id)) | ||
| 870 | .context("proposal not in cache")? | ||
| 871 | .author(), | ||
| 872 | ], | ||
| 873 | ] | ||
| 874 | .concat() | ||
| 875 | .iter() | ||
| 876 | .copied() | ||
| 877 | .collect(); | ||
| 878 | commit_events.retain(|e| permissioned_users.contains(e.author_ref())); | ||
| 879 | |||
| 880 | let revision_roots: HashSet<nostr::EventId> = commit_events | ||
| 881 | .iter() | ||
| 882 | .filter(|e| event_is_revision_root(e)) | ||
| 883 | .map(nostr::Event::id) | ||
| 884 | .collect(); | ||
| 885 | |||
| 886 | if !revision_roots.is_empty() { | ||
| 887 | for event in get_events_from_cache( | ||
| 888 | git_repo_path, | ||
| 905 | vec![ | 889 | vec![ |
| 906 | nostr::Filter::default() | 890 | nostr::Filter::default() |
| 907 | .kind(nostr::Kind::Custom(PATCH_KIND)) | 891 | .kind(nostr::Kind::Custom(PATCH_KIND)) |
| 908 | .events( | 892 | .events(revision_roots) |
| 909 | proposal_root_events | 893 | .authors(permissioned_users.clone()), |
| 910 | .iter() | ||
| 911 | .map(|e| e.id) | ||
| 912 | .collect::<Vec<nostr::EventId>>(), | ||
| 913 | ), | ||
| 914 | ], | 894 | ], |
| 915 | ) | 895 | ) |
| 916 | .await | 896 | .await? |
| 917 | .context("cannot fetch patch events")? | 897 | { |
| 918 | .iter() | 898 | commit_events.push(event); |
| 919 | .filter(|e| { | ||
| 920 | e.kind.as_u16() == PATCH_KIND | ||
| 921 | && e.tags.iter().any(|t| { | ||
| 922 | t.as_vec().len() > 2 | ||
| 923 | && proposal_root_events | ||
| 924 | .iter() | ||
| 925 | .any(|e| t.as_vec()[1].eq(&e.id.to_string())) | ||
| 926 | }) | ||
| 927 | }) | ||
| 928 | .map(std::borrow::ToOwned::to_owned) | ||
| 929 | .collect(); | ||
| 930 | |||
| 931 | for e in proposal_root_events { | ||
| 932 | if !event_is_cover_letter(e) && !patch_events.iter().any(|e2| e2.id.eq(&e.id)) { | ||
| 933 | patch_events.push(e.to_owned().clone()); | ||
| 934 | } | 899 | } |
| 935 | } | 900 | } |
| 936 | Ok(patch_events) | 901 | |
| 902 | Ok(commit_events | ||
| 903 | .iter() | ||
| 904 | .filter(|e| !event_is_cover_letter(e) && permissioned_users.contains(e.author_ref())) | ||
| 905 | .cloned() | ||
| 906 | .collect()) | ||
| 937 | } | 907 | } |
diff --git a/tests/list.rs b/tests/list.rs index cd071a9..0005053 100644 --- a/tests/list.rs +++ b/tests/list.rs | |||
| @@ -132,17 +132,10 @@ fn cli_tester_create_proposal( | |||
| 132 | mod cannot_find_repo_event { | 132 | mod cannot_find_repo_event { |
| 133 | use super::*; | 133 | use super::*; |
| 134 | mod cli_prompts { | 134 | mod cli_prompts { |
| 135 | use nostr::{ | 135 | use nostr::{nips::nip01::Coordinate, ToBech32}; |
| 136 | nips::{nip01::Coordinate, nip19::Nip19Event}, | ||
| 137 | ToBech32, | ||
| 138 | }; | ||
| 139 | 136 | ||
| 140 | use super::*; | 137 | use super::*; |
| 141 | async fn run_async_repo_event_ref_needed( | 138 | async fn run_async_repo_event_ref_needed(invalid_input: bool, naddr: bool) -> Result<()> { |
| 142 | invalid_input: bool, | ||
| 143 | nevent: bool, | ||
| 144 | naddr: bool, | ||
| 145 | ) -> Result<()> { | ||
| 146 | let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( | 139 | let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( |
| 147 | Relay::new(8051, None, None), | 140 | Relay::new(8051, None, None), |
| 148 | Relay::new(8052, None, None), | 141 | Relay::new(8052, None, None), |
| @@ -161,35 +154,19 @@ mod cannot_find_repo_event { | |||
| 161 | r56.events.push(repo_event.clone()); | 154 | r56.events.push(repo_event.clone()); |
| 162 | 155 | ||
| 163 | let cli_tester_handle = std::thread::spawn(move || -> Result<()> { | 156 | let cli_tester_handle = std::thread::spawn(move || -> Result<()> { |
| 164 | let test_repo = GitTestRepo::default(); | 157 | let test_repo = GitTestRepo::without_repo_in_git_config(); |
| 165 | test_repo.populate()?; | 158 | test_repo.populate()?; |
| 166 | let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); | 159 | let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); |
| 167 | 160 | ||
| 168 | p.expect("cannot find repo event\r\n")?; | ||
| 169 | |||
| 170 | if invalid_input { | 161 | if invalid_input { |
| 171 | let mut input = p.expect_input("repository naddr or nevent")?; | 162 | let mut input = p.expect_input("repository naddr")?; |
| 172 | input.succeeds_with("dfgvfvfzadvd")?; | 163 | input.succeeds_with("dfgvfvfzadvd")?; |
| 173 | p.expect("not a valid nevent or naddr\r\n")?; | 164 | p.expect("not a valid naddr\r\n")?; |
| 174 | let _ = p.expect_input("repository naddr or nevent")?; | 165 | let _ = p.expect_input("repository naddr")?; |
| 175 | p.exit()?; | 166 | p.exit()?; |
| 176 | } | 167 | } |
| 177 | if nevent { | ||
| 178 | let mut input = p.expect_input("repository naddr or nevent")?; | ||
| 179 | input.succeeds_with( | ||
| 180 | &Nip19Event { | ||
| 181 | event_id: repo_event.id, | ||
| 182 | author: Some(TEST_KEY_1_KEYS.public_key()), | ||
| 183 | relays: vec!["ws://localhost:8056".to_string()], | ||
| 184 | kind: None, | ||
| 185 | } | ||
| 186 | .to_bech32()?, | ||
| 187 | )?; | ||
| 188 | p.expect("finding proposals...\r\n")?; | ||
| 189 | p.expect_end_with("no proposals found... create one? try `ngit send`\r\n")?; | ||
| 190 | } | ||
| 191 | if naddr { | 168 | if naddr { |
| 192 | let mut input = p.expect_input("repository naddr or nevent")?; | 169 | let mut input = p.expect_input("repository naddr")?; |
| 193 | input.succeeds_with( | 170 | input.succeeds_with( |
| 194 | &Coordinate { | 171 | &Coordinate { |
| 195 | kind: nostr::Kind::Custom(REPOSITORY_KIND), | 172 | kind: nostr::Kind::Custom(REPOSITORY_KIND), |
| @@ -199,9 +176,9 @@ mod cannot_find_repo_event { | |||
| 199 | } | 176 | } |
| 200 | .to_bech32()?, | 177 | .to_bech32()?, |
| 201 | )?; | 178 | )?; |
| 202 | p.expect("finding proposals...\r\n")?; | 179 | p.expect("fetching updates...\r\n")?; |
| 180 | p.expect_eventually("\r\n")?; // some updates listed here | ||
| 203 | p.expect_end_with("no proposals found... create one? try `ngit send`\r\n")?; | 181 | p.expect_end_with("no proposals found... create one? try `ngit send`\r\n")?; |
| 204 | p.expect_end_eventually()?; | ||
| 205 | } | 182 | } |
| 206 | 183 | ||
| 207 | for p in [51, 52, 53, 55, 56] { | 184 | for p in [51, 52, 53, 55, 56] { |
| @@ -225,19 +202,13 @@ mod cannot_find_repo_event { | |||
| 225 | #[tokio::test] | 202 | #[tokio::test] |
| 226 | #[serial] | 203 | #[serial] |
| 227 | async fn warns_not_valid_input_and_asks_again() -> Result<()> { | 204 | async fn warns_not_valid_input_and_asks_again() -> Result<()> { |
| 228 | run_async_repo_event_ref_needed(true, false, false).await | 205 | run_async_repo_event_ref_needed(true, false).await |
| 229 | } | ||
| 230 | |||
| 231 | #[tokio::test] | ||
| 232 | #[serial] | ||
| 233 | async fn finds_based_on_nevent_on_embeded_relay() -> Result<()> { | ||
| 234 | run_async_repo_event_ref_needed(false, true, false).await | ||
| 235 | } | 206 | } |
| 236 | 207 | ||
| 237 | #[tokio::test] | 208 | #[tokio::test] |
| 238 | #[serial] | 209 | #[serial] |
| 239 | async fn finds_based_on_naddr_on_embeded_relay() -> Result<()> { | 210 | async fn finds_based_on_naddr_on_embeded_relay() -> Result<()> { |
| 240 | run_async_repo_event_ref_needed(false, false, true).await | 211 | run_async_repo_event_ref_needed(false, true).await |
| 241 | } | 212 | } |
| 242 | } | 213 | } |
| 243 | } | 214 | } |
| @@ -281,7 +252,8 @@ mod when_main_branch_is_uptodate { | |||
| 281 | test_repo.populate()?; | 252 | test_repo.populate()?; |
| 282 | let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); | 253 | let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); |
| 283 | 254 | ||
| 284 | p.expect("finding proposals...\r\n")?; | 255 | p.expect("fetching updates...\r\n")?; |
| 256 | p.expect_eventually("\r\n")?; // some updates listed here | ||
| 285 | let mut c = p.expect_choice( | 257 | let mut c = p.expect_choice( |
| 286 | "all proposals", | 258 | "all proposals", |
| 287 | vec![ | 259 | vec![ |
| @@ -292,7 +264,6 @@ mod when_main_branch_is_uptodate { | |||
| 292 | )?; | 264 | )?; |
| 293 | c.succeeds_with(2, true, None)?; | 265 | c.succeeds_with(2, true, None)?; |
| 294 | 266 | ||
| 295 | p.expect("finding commits...\r\n")?; | ||
| 296 | let mut c = p.expect_choice( | 267 | let mut c = p.expect_choice( |
| 297 | "", | 268 | "", |
| 298 | vec![ | 269 | vec![ |
| @@ -356,7 +327,8 @@ mod when_main_branch_is_uptodate { | |||
| 356 | test_repo.populate()?; | 327 | test_repo.populate()?; |
| 357 | let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); | 328 | let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); |
| 358 | 329 | ||
| 359 | p.expect("finding proposals...\r\n")?; | 330 | p.expect("fetching updates...\r\n")?; |
| 331 | p.expect_eventually("\r\n")?; // some updates listed here | ||
| 360 | let mut c = p.expect_choice( | 332 | let mut c = p.expect_choice( |
| 361 | "all proposals", | 333 | "all proposals", |
| 362 | vec![ | 334 | vec![ |
| @@ -366,7 +338,6 @@ mod when_main_branch_is_uptodate { | |||
| 366 | ], | 338 | ], |
| 367 | )?; | 339 | )?; |
| 368 | c.succeeds_with(2, true, None)?; | 340 | c.succeeds_with(2, true, None)?; |
| 369 | p.expect("finding commits...\r\n")?; | ||
| 370 | let mut c = p.expect_choice( | 341 | let mut c = p.expect_choice( |
| 371 | "", | 342 | "", |
| 372 | vec![ | 343 | vec![ |
| @@ -465,7 +436,8 @@ mod when_main_branch_is_uptodate { | |||
| 465 | test_repo.populate()?; | 436 | test_repo.populate()?; |
| 466 | let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); | 437 | let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); |
| 467 | 438 | ||
| 468 | p.expect("finding proposals...\r\n")?; | 439 | p.expect("fetching updates...\r\n")?; |
| 440 | p.expect_eventually("\r\n")?; // some updates listed here | ||
| 469 | let mut c = p.expect_choice( | 441 | let mut c = p.expect_choice( |
| 470 | "all proposals", | 442 | "all proposals", |
| 471 | vec![ | 443 | vec![ |
| @@ -476,7 +448,6 @@ mod when_main_branch_is_uptodate { | |||
| 476 | )?; | 448 | )?; |
| 477 | c.succeeds_with(0, true, None)?; | 449 | c.succeeds_with(0, true, None)?; |
| 478 | 450 | ||
| 479 | p.expect("finding commits...\r\n")?; | ||
| 480 | let mut c = p.expect_choice( | 451 | let mut c = p.expect_choice( |
| 481 | "", | 452 | "", |
| 482 | vec![ | 453 | vec![ |
| @@ -542,7 +513,8 @@ mod when_main_branch_is_uptodate { | |||
| 542 | test_repo.populate()?; | 513 | test_repo.populate()?; |
| 543 | let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); | 514 | let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); |
| 544 | 515 | ||
| 545 | p.expect("finding proposals...\r\n")?; | 516 | p.expect("fetching updates...\r\n")?; |
| 517 | p.expect_eventually("\r\n")?; // some updates listed here | ||
| 546 | let mut c = p.expect_choice( | 518 | let mut c = p.expect_choice( |
| 547 | "all proposals", | 519 | "all proposals", |
| 548 | vec![ | 520 | vec![ |
| @@ -552,7 +524,6 @@ mod when_main_branch_is_uptodate { | |||
| 552 | ], | 524 | ], |
| 553 | )?; | 525 | )?; |
| 554 | c.succeeds_with(0, true, None)?; | 526 | c.succeeds_with(0, true, None)?; |
| 555 | p.expect("finding commits...\r\n")?; | ||
| 556 | let mut c = p.expect_choice( | 527 | let mut c = p.expect_choice( |
| 557 | "", | 528 | "", |
| 558 | vec![ | 529 | vec![ |
| @@ -657,7 +628,8 @@ mod when_main_branch_is_uptodate { | |||
| 657 | test_repo.populate()?; | 628 | test_repo.populate()?; |
| 658 | let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); | 629 | let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); |
| 659 | 630 | ||
| 660 | p.expect("finding proposals...\r\n")?; | 631 | p.expect("fetching updates...\r\n")?; |
| 632 | p.expect_eventually("\r\n")?; // some updates listed here | ||
| 661 | let mut c = p.expect_choice( | 633 | let mut c = p.expect_choice( |
| 662 | "all proposals", | 634 | "all proposals", |
| 663 | vec![ | 635 | vec![ |
| @@ -668,7 +640,6 @@ mod when_main_branch_is_uptodate { | |||
| 668 | ], | 640 | ], |
| 669 | )?; | 641 | )?; |
| 670 | c.succeeds_with(0, true, None)?; | 642 | c.succeeds_with(0, true, None)?; |
| 671 | p.expect("finding commits...\r\n")?; | ||
| 672 | let mut c = p.expect_choice( | 643 | let mut c = p.expect_choice( |
| 673 | "", | 644 | "", |
| 674 | vec![ | 645 | vec![ |
| @@ -741,7 +712,8 @@ mod when_main_branch_is_uptodate { | |||
| 741 | test_repo.populate()?; | 712 | test_repo.populate()?; |
| 742 | let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); | 713 | let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); |
| 743 | 714 | ||
| 744 | p.expect("finding proposals...\r\n")?; | 715 | p.expect("fetching updates...\r\n")?; |
| 716 | p.expect_eventually("\r\n")?; // some updates listed here | ||
| 745 | let mut c = p.expect_choice( | 717 | let mut c = p.expect_choice( |
| 746 | "all proposals", | 718 | "all proposals", |
| 747 | vec![ | 719 | vec![ |
| @@ -752,7 +724,6 @@ mod when_main_branch_is_uptodate { | |||
| 752 | ], | 724 | ], |
| 753 | )?; | 725 | )?; |
| 754 | c.succeeds_with(0, true, None)?; | 726 | c.succeeds_with(0, true, None)?; |
| 755 | p.expect("finding commits...\r\n")?; | ||
| 756 | let mut c = p.expect_choice( | 727 | let mut c = p.expect_choice( |
| 757 | "", | 728 | "", |
| 758 | vec![ | 729 | vec![ |
| @@ -866,7 +837,8 @@ mod when_main_branch_is_uptodate { | |||
| 866 | false, | 837 | false, |
| 867 | )?; | 838 | )?; |
| 868 | test_repo.checkout("main")?; | 839 | test_repo.checkout("main")?; |
| 869 | p.expect("finding proposals...\r\n")?; | 840 | p.expect("fetching updates...\r\n")?; |
| 841 | p.expect_eventually("\r\n")?; // some updates listed here | ||
| 870 | let mut c = p.expect_choice( | 842 | let mut c = p.expect_choice( |
| 871 | "all proposals", | 843 | "all proposals", |
| 872 | vec![ | 844 | vec![ |
| @@ -876,7 +848,6 @@ mod when_main_branch_is_uptodate { | |||
| 876 | ], | 848 | ], |
| 877 | )?; | 849 | )?; |
| 878 | c.succeeds_with(2, true, None)?; | 850 | c.succeeds_with(2, true, None)?; |
| 879 | p.expect("finding commits...\r\n")?; | ||
| 880 | let mut c = p.expect_choice( | 851 | let mut c = p.expect_choice( |
| 881 | "", | 852 | "", |
| 882 | vec![ | 853 | vec![ |
| @@ -948,7 +919,8 @@ mod when_main_branch_is_uptodate { | |||
| 948 | )?; | 919 | )?; |
| 949 | test_repo.checkout("main")?; | 920 | test_repo.checkout("main")?; |
| 950 | 921 | ||
| 951 | p.expect("finding proposals...\r\n")?; | 922 | p.expect("fetching updates...\r\n")?; |
| 923 | p.expect_eventually("\r\n")?; // some updates listed here | ||
| 952 | let mut c = p.expect_choice( | 924 | let mut c = p.expect_choice( |
| 953 | "all proposals", | 925 | "all proposals", |
| 954 | vec![ | 926 | vec![ |
| @@ -958,7 +930,6 @@ mod when_main_branch_is_uptodate { | |||
| 958 | ], | 930 | ], |
| 959 | )?; | 931 | )?; |
| 960 | c.succeeds_with(2, true, None)?; | 932 | c.succeeds_with(2, true, None)?; |
| 961 | p.expect("finding commits...\r\n")?; | ||
| 962 | let mut c = p.expect_choice( | 933 | let mut c = p.expect_choice( |
| 963 | "", | 934 | "", |
| 964 | vec![ | 935 | vec![ |
| @@ -1043,7 +1014,8 @@ mod when_main_branch_is_uptodate { | |||
| 1043 | )?; | 1014 | )?; |
| 1044 | test_repo.checkout("main")?; | 1015 | test_repo.checkout("main")?; |
| 1045 | 1016 | ||
| 1046 | p.expect("finding proposals...\r\n")?; | 1017 | p.expect("fetching updates...\r\n")?; |
| 1018 | p.expect_eventually("\r\n")?; // some updates listed here | ||
| 1047 | let mut c = p.expect_choice( | 1019 | let mut c = p.expect_choice( |
| 1048 | "all proposals", | 1020 | "all proposals", |
| 1049 | vec![ | 1021 | vec![ |
| @@ -1053,7 +1025,6 @@ mod when_main_branch_is_uptodate { | |||
| 1053 | ], | 1025 | ], |
| 1054 | )?; | 1026 | )?; |
| 1055 | c.succeeds_with(2, true, None)?; | 1027 | c.succeeds_with(2, true, None)?; |
| 1056 | p.expect("finding commits...\r\n")?; | ||
| 1057 | let mut c = p.expect_choice( | 1028 | let mut c = p.expect_choice( |
| 1058 | "", | 1029 | "", |
| 1059 | vec![ | 1030 | vec![ |
| @@ -1124,7 +1095,8 @@ mod when_main_branch_is_uptodate { | |||
| 1124 | )?; | 1095 | )?; |
| 1125 | test_repo.checkout("main")?; | 1096 | test_repo.checkout("main")?; |
| 1126 | 1097 | ||
| 1127 | p.expect("finding proposals...\r\n")?; | 1098 | p.expect("fetching updates...\r\n")?; |
| 1099 | p.expect_eventually("\r\n")?; // some updates listed here | ||
| 1128 | let mut c = p.expect_choice( | 1100 | let mut c = p.expect_choice( |
| 1129 | "all proposals", | 1101 | "all proposals", |
| 1130 | vec![ | 1102 | vec![ |
| @@ -1134,7 +1106,6 @@ mod when_main_branch_is_uptodate { | |||
| 1134 | ], | 1106 | ], |
| 1135 | )?; | 1107 | )?; |
| 1136 | c.succeeds_with(2, true, None)?; | 1108 | c.succeeds_with(2, true, None)?; |
| 1137 | p.expect("finding commits...\r\n")?; | ||
| 1138 | let mut c = p.expect_choice( | 1109 | let mut c = p.expect_choice( |
| 1139 | "", | 1110 | "", |
| 1140 | vec![ | 1111 | vec![ |
| @@ -1236,7 +1207,8 @@ mod when_main_branch_is_uptodate { | |||
| 1236 | )?; | 1207 | )?; |
| 1237 | 1208 | ||
| 1238 | test_repo.checkout("main")?; | 1209 | test_repo.checkout("main")?; |
| 1239 | p.expect("finding proposals...\r\n")?; | 1210 | p.expect("fetching updates...\r\n")?; |
| 1211 | p.expect_eventually("\r\n")?; // some updates listed here | ||
| 1240 | let mut c = p.expect_choice( | 1212 | let mut c = p.expect_choice( |
| 1241 | "all proposals", | 1213 | "all proposals", |
| 1242 | vec![ | 1214 | vec![ |
| @@ -1246,7 +1218,6 @@ mod when_main_branch_is_uptodate { | |||
| 1246 | ], | 1218 | ], |
| 1247 | )?; | 1219 | )?; |
| 1248 | c.succeeds_with(2, true, None)?; | 1220 | c.succeeds_with(2, true, None)?; |
| 1249 | p.expect("finding commits...\r\n")?; | ||
| 1250 | p.expect_eventually("--force`\r\n")?; | 1221 | p.expect_eventually("--force`\r\n")?; |
| 1251 | 1222 | ||
| 1252 | let mut c = p.expect_choice( | 1223 | let mut c = p.expect_choice( |
| @@ -1329,7 +1300,8 @@ mod when_main_branch_is_uptodate { | |||
| 1329 | )?; | 1300 | )?; |
| 1330 | 1301 | ||
| 1331 | test_repo.checkout("main")?; | 1302 | test_repo.checkout("main")?; |
| 1332 | p.expect("finding proposals...\r\n")?; | 1303 | p.expect("fetching updates...\r\n")?; |
| 1304 | p.expect_eventually("\r\n")?; // some updates listed here | ||
| 1333 | let mut c = p.expect_choice( | 1305 | let mut c = p.expect_choice( |
| 1334 | "all proposals", | 1306 | "all proposals", |
| 1335 | vec![ | 1307 | vec![ |
| @@ -1339,7 +1311,6 @@ mod when_main_branch_is_uptodate { | |||
| 1339 | ], | 1311 | ], |
| 1340 | )?; | 1312 | )?; |
| 1341 | c.succeeds_with(2, true, None)?; | 1313 | c.succeeds_with(2, true, None)?; |
| 1342 | p.expect("finding commits...\r\n")?; | ||
| 1343 | p.expect("you have an amended/rebase version the proposal that is unpublished\r\n")?; | 1314 | p.expect("you have an amended/rebase version the proposal that is unpublished\r\n")?; |
| 1344 | p.expect("you have previously applied the latest version of the proposal (2 ahead 0 behind 'main') but your local proposal branch has amended or rebased it (2 ahead 0 behind 'main')\r\n")?; | 1315 | p.expect("you have previously applied the latest version of the proposal (2 ahead 0 behind 'main') but your local proposal branch has amended or rebased it (2 ahead 0 behind 'main')\r\n")?; |
| 1345 | p.expect("to view the latest proposal but retain your changes:\r\n")?; | 1316 | p.expect("to view the latest proposal but retain your changes:\r\n")?; |
| @@ -1435,7 +1406,8 @@ mod when_main_branch_is_uptodate { | |||
| 1435 | test_repo.stage_and_commit("appended commit")?; | 1406 | test_repo.stage_and_commit("appended commit")?; |
| 1436 | 1407 | ||
| 1437 | test_repo.checkout("main")?; | 1408 | test_repo.checkout("main")?; |
| 1438 | p.expect("finding proposals...\r\n")?; | 1409 | p.expect("fetching updates...\r\n")?; |
| 1410 | p.expect_eventually("\r\n")?; // some updates listed here | ||
| 1439 | let mut c = p.expect_choice( | 1411 | let mut c = p.expect_choice( |
| 1440 | "all proposals", | 1412 | "all proposals", |
| 1441 | vec![ | 1413 | vec![ |
| @@ -1445,7 +1417,6 @@ mod when_main_branch_is_uptodate { | |||
| 1445 | ], | 1417 | ], |
| 1446 | )?; | 1418 | )?; |
| 1447 | c.succeeds_with(2, true, None)?; | 1419 | c.succeeds_with(2, true, None)?; |
| 1448 | p.expect("finding commits...\r\n")?; | ||
| 1449 | p.expect( | 1420 | p.expect( |
| 1450 | "local proposal branch exists with 1 unpublished commits on top of the most up-to-date version of the proposal (3 ahead 0 behind 'main')\r\n", | 1421 | "local proposal branch exists with 1 unpublished commits on top of the most up-to-date version of the proposal (3 ahead 0 behind 'main')\r\n", |
| 1451 | )?; | 1422 | )?; |
| @@ -1521,7 +1492,8 @@ mod when_main_branch_is_uptodate { | |||
| 1521 | test_repo.stage_and_commit("appended commit")?; | 1492 | test_repo.stage_and_commit("appended commit")?; |
| 1522 | 1493 | ||
| 1523 | test_repo.checkout("main")?; | 1494 | test_repo.checkout("main")?; |
| 1524 | p.expect("finding proposals...\r\n")?; | 1495 | p.expect("fetching updates...\r\n")?; |
| 1496 | p.expect_eventually("\r\n")?; // some updates listed here | ||
| 1525 | let mut c = p.expect_choice( | 1497 | let mut c = p.expect_choice( |
| 1526 | "all proposals", | 1498 | "all proposals", |
| 1527 | vec![ | 1499 | vec![ |
| @@ -1531,7 +1503,6 @@ mod when_main_branch_is_uptodate { | |||
| 1531 | ], | 1503 | ], |
| 1532 | )?; | 1504 | )?; |
| 1533 | c.succeeds_with(2, true, None)?; | 1505 | c.succeeds_with(2, true, None)?; |
| 1534 | p.expect("finding commits...\r\n")?; | ||
| 1535 | p.expect( | 1506 | p.expect( |
| 1536 | "local proposal branch exists with 1 unpublished commits on top of the most up-to-date version of the proposal (3 ahead 0 behind 'main')\r\n", | 1507 | "local proposal branch exists with 1 unpublished commits on top of the most up-to-date version of the proposal (3 ahead 0 behind 'main')\r\n", |
| 1537 | )?; | 1508 | )?; |
| @@ -1680,7 +1651,8 @@ mod when_main_branch_is_uptodate { | |||
| 1680 | test_repo.stage_and_commit("commit for rebasing on top of")?; | 1651 | test_repo.stage_and_commit("commit for rebasing on top of")?; |
| 1681 | 1652 | ||
| 1682 | let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); | 1653 | let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); |
| 1683 | p.expect("finding proposals...\r\n")?; | 1654 | p.expect("fetching updates...\r\n")?; |
| 1655 | p.expect_eventually("\r\n")?; // some updates listed here | ||
| 1684 | let mut c = p.expect_choice( | 1656 | let mut c = p.expect_choice( |
| 1685 | "all proposals", | 1657 | "all proposals", |
| 1686 | vec![ | 1658 | vec![ |
| @@ -1690,7 +1662,6 @@ mod when_main_branch_is_uptodate { | |||
| 1690 | ], | 1662 | ], |
| 1691 | )?; | 1663 | )?; |
| 1692 | c.succeeds_with(2, true, None)?; | 1664 | c.succeeds_with(2, true, None)?; |
| 1693 | p.expect("finding commits...\r\n")?; | ||
| 1694 | p.expect("updated proposal available (2 ahead 0 behind 'main'). existing version is 2 ahead 1 behind 'main'\r\n")?; | 1665 | p.expect("updated proposal available (2 ahead 0 behind 'main'). existing version is 2 ahead 1 behind 'main'\r\n")?; |
| 1695 | let mut c = p.expect_choice( | 1666 | let mut c = p.expect_choice( |
| 1696 | "", | 1667 | "", |
| @@ -1814,7 +1785,8 @@ mod when_main_branch_is_uptodate { | |||
| 1814 | test_repo.stage_and_commit("commit for rebasing on top of")?; | 1785 | test_repo.stage_and_commit("commit for rebasing on top of")?; |
| 1815 | 1786 | ||
| 1816 | let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); | 1787 | let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); |
| 1817 | p.expect("finding proposals...\r\n")?; | 1788 | p.expect("fetching updates...\r\n")?; |
| 1789 | p.expect_eventually("\r\n")?; // some updates listed here | ||
| 1818 | let mut c = p.expect_choice( | 1790 | let mut c = p.expect_choice( |
| 1819 | "all proposals", | 1791 | "all proposals", |
| 1820 | vec![ | 1792 | vec![ |
| @@ -1824,7 +1796,6 @@ mod when_main_branch_is_uptodate { | |||
| 1824 | ], | 1796 | ], |
| 1825 | )?; | 1797 | )?; |
| 1826 | c.succeeds_with(2, true, None)?; | 1798 | c.succeeds_with(2, true, None)?; |
| 1827 | p.expect("finding commits...\r\n")?; | ||
| 1828 | p.expect("updated proposal available (2 ahead 0 behind 'main'). existing version is 2 ahead 1 behind 'main'\r\n")?; | 1799 | p.expect("updated proposal available (2 ahead 0 behind 'main'). existing version is 2 ahead 1 behind 'main'\r\n")?; |
| 1829 | let mut c = p.expect_choice( | 1800 | let mut c = p.expect_choice( |
| 1830 | "", | 1801 | "", |