From 173249ea8e3e5c63d1a65c20ae4c97b780d8fec7 Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Wed, 17 Jan 2024 17:14:16 +0100 Subject: [PATCH] move some api methods to struct --- src/frontend/api.rs | 90 +++++++++++++++++++++++++++++++++- tests/common.rs | 104 +-------------------------------------- tests/test.rs | 115 ++++++++++++++++++++++++++------------------ 3 files changed, 158 insertions(+), 151 deletions(-) diff --git a/src/frontend/api.rs b/src/frontend/api.rs index a0f5f9d..06c08ff 100644 --- a/src/frontend/api.rs +++ b/src/frontend/api.rs @@ -1,11 +1,17 @@ -use crate::common::GetArticleData; +use crate::backend::api::article::{CreateArticleData, EditArticleData}; +use crate::backend::api::instance::FollowInstance; +use crate::backend::api::{ResolveObject, SearchArticleData}; +use crate::backend::database::conflict::ApiConflict; +use crate::backend::database::instance::{DbInstance, InstanceView}; use crate::common::LocalUserView; use crate::common::{ArticleView, LoginUserData, RegisterUserData}; +use crate::common::{DbArticle, GetArticleData}; use crate::frontend::error::MyResult; use anyhow::anyhow; use once_cell::sync::Lazy; -use reqwest::{Client, RequestBuilder}; +use reqwest::{Client, RequestBuilder, StatusCode}; use serde::{Deserialize, Serialize}; +use url::Url; pub static CLIENT: Lazy = Lazy::new(Client::new); @@ -55,6 +61,84 @@ impl ApiClient { .form(&login_form); handle_json_res::(req).await } + + pub async fn create_article(&self, title: String, new_text: String) -> MyResult { + let create_form = CreateArticleData { + title: title.clone(), + }; + let req = self + .client + .post(format!("http://{}/api/v1/article", &self.hostname)) + .form(&create_form); + let article: ArticleView = handle_json_res(req).await?; + + // create initial edit to ensure that conflicts are generated (there are no conflicts on empty file) + // TODO: maybe take initial text directly in create article, no reason to have empty article + let edit_form = EditArticleData { + article_id: article.article.id, + new_text, + previous_version_id: article.latest_version, + resolve_conflict_id: None, + }; + Ok(self.edit_article(&edit_form).await.unwrap()) + } + + pub async fn edit_article_with_conflict( + &self, + edit_form: &EditArticleData, + ) -> MyResult> { + let req = self + .client + .patch(format!("http://{}/api/v1/article", self.hostname)) + .form(edit_form); + handle_json_res(req).await + } + + pub async fn edit_article(&self, edit_form: &EditArticleData) -> MyResult { + let edit_res = self.edit_article_with_conflict(edit_form).await?; + assert!(edit_res.is_none()); + + self.get_article(GetArticleData { + title: None, + instance_id: None, + id: Some(edit_form.article_id), + }) + .await + } + + pub async fn search(&self, search_form: &SearchArticleData) -> MyResult> { + self.get_query("search", Some(search_form)).await + } + + pub async fn get_local_instance(&self) -> MyResult { + self.get_query("instance", None::).await + } + + pub async fn follow_instance(&self, follow_instance: &str) -> MyResult { + // fetch beta instance on alpha + let resolve_form = ResolveObject { + id: Url::parse(&format!("http://{}", follow_instance))?, + }; + let instance_resolved: DbInstance = + get_query(&self.hostname, "instance/resolve", Some(resolve_form)).await?; + + // send follow + let follow_form = FollowInstance { + id: instance_resolved.id, + }; + // cant use post helper because follow doesnt return json + let res = self + .client + .post(format!("http://{}/api/v1/instance/follow", self.hostname)) + .form(&follow_form) + .send() + .await?; + if res.status() == StatusCode::OK { + Ok(instance_resolved) + } else { + Err(anyhow!("API error: {}", res.text().await?).into()) + } + } } pub async fn get_query(hostname: &str, endpoint: &str, query: Option) -> MyResult @@ -83,11 +167,13 @@ where } } +// TODO: cover in integration test pub async fn my_profile(hostname: &str) -> MyResult { let req = CLIENT.get(format!("http://{}/api/v1/account/my_profile", hostname)); handle_json_res::(req).await } +// TODO: cover in integration test pub async fn logout(hostname: &str) -> MyResult<()> { CLIENT .get(format!("http://{}/api/v1/account/logout", hostname)) diff --git a/tests/common.rs b/tests/common.rs index 183ae61..1e3a8bc 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -1,12 +1,12 @@ use anyhow::anyhow; -use ibis_lib::backend::api::article::{CreateArticleData, EditArticleData, ForkArticleData}; +use ibis_lib::backend::api::article::ForkArticleData; use ibis_lib::backend::api::instance::FollowInstance; use ibis_lib::backend::api::ResolveObject; use ibis_lib::backend::database::conflict::ApiConflict; use ibis_lib::backend::database::instance::DbInstance; use ibis_lib::backend::start; +use ibis_lib::common::ArticleView; use ibis_lib::common::RegisterUserData; -use ibis_lib::common::{ArticleView, GetArticleData}; use ibis_lib::frontend::api::ApiClient; use ibis_lib::frontend::api::{get_query, handle_json_res}; use ibis_lib::frontend::error::MyResult; @@ -166,45 +166,6 @@ impl Deref for IbisInstance { } pub const TEST_ARTICLE_DEFAULT_TEXT: &str = "some\nexample\ntext\n"; -pub async fn create_article(instance: &IbisInstance, title: String) -> MyResult { - let create_form = CreateArticleData { - title: title.clone(), - }; - let req = instance - .api_client - .client - .post(format!( - "http://{}/api/v1/article", - &instance.api_client.hostname - )) - .form(&create_form); - let article: ArticleView = handle_json_res(req).await?; - - // create initial edit to ensure that conflicts are generated (there are no conflicts on empty file) - let edit_form = EditArticleData { - article_id: article.article.id, - new_text: TEST_ARTICLE_DEFAULT_TEXT.to_string(), - previous_version_id: article.latest_version, - resolve_conflict_id: None, - }; - Ok(edit_article(instance, &edit_form).await.unwrap()) -} - -pub async fn edit_article_with_conflict( - instance: &IbisInstance, - edit_form: &EditArticleData, -) -> MyResult> { - let req = instance - .api_client - .client - .patch(format!( - "http://{}/api/v1/article", - instance.api_client.hostname - )) - .form(edit_form); - handle_json_res(req).await -} - pub async fn get_conflicts(instance: &IbisInstance) -> MyResult> { let req = instance.api_client.client.get(format!( "http://{}/api/v1/edit_conflicts", @@ -213,30 +174,6 @@ pub async fn get_conflicts(instance: &IbisInstance) -> MyResult Ok(handle_json_res(req).await.unwrap()) } -pub async fn edit_article( - instance: &IbisInstance, - edit_form: &EditArticleData, -) -> MyResult { - let edit_res = edit_article_with_conflict(instance, edit_form).await?; - assert!(edit_res.is_none()); - - instance - .api_client - .get_article(GetArticleData { - title: None, - instance_id: None, - id: Some(edit_form.article_id), - }) - .await -} - -pub async fn get(hostname: &str, endpoint: &str) -> MyResult -where - T: for<'de> Deserialize<'de>, -{ - Ok(get_query(hostname, endpoint, None::).await.unwrap()) -} - pub async fn fork_article( instance: &IbisInstance, form: &ForkArticleData, @@ -251,40 +188,3 @@ pub async fn fork_article( .form(form); Ok(handle_json_res(req).await.unwrap()) } - -pub async fn follow_instance( - instance: &IbisInstance, - follow_instance: &str, -) -> MyResult { - // fetch beta instance on alpha - let resolve_form = ResolveObject { - id: Url::parse(&format!("http://{}", follow_instance))?, - }; - let instance_resolved: DbInstance = get_query( - &instance.api_client.hostname, - "instance/resolve", - Some(resolve_form), - ) - .await?; - - // send follow - let follow_form = FollowInstance { - id: instance_resolved.id, - }; - // cant use post helper because follow doesnt return json - let res = instance - .api_client - .client - .post(format!( - "http://{}/api/v1/instance/follow", - instance.api_client.hostname - )) - .form(&follow_form) - .send() - .await?; - if res.status() == StatusCode::OK { - Ok(instance_resolved) - } else { - Err(anyhow!("API error: {}", res.text().await?).into()) - } -} diff --git a/tests/test.rs b/tests/test.rs index 7280045..8c6a8c0 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -4,10 +4,7 @@ mod common; use crate::common::fork_article; use crate::common::get_conflicts; -use crate::common::{ - create_article, edit_article, edit_article_with_conflict, follow_instance, get, TestData, - TEST_ARTICLE_DEFAULT_TEXT, -}; +use crate::common::{TestData, TEST_ARTICLE_DEFAULT_TEXT}; use ibis_lib::backend::api::article::{CreateArticleData, EditArticleData, ForkArticleData}; use ibis_lib::backend::api::{ResolveObject, SearchArticleData}; use ibis_lib::backend::database::instance::{DbInstance, InstanceView}; @@ -25,7 +22,10 @@ async fn test_create_read_and_edit_local_article() -> MyResult<()> { // create article let title = "Manu_Chao".to_string(); - let create_res = create_article(&data.alpha, title.clone()).await?; + let create_res = data + .alpha + .create_article(title.clone(), TEST_ARTICLE_DEFAULT_TEXT.to_string()) + .await?; assert_eq!(title, create_res.article.title); assert!(create_res.article.local); @@ -35,11 +35,7 @@ async fn test_create_read_and_edit_local_article() -> MyResult<()> { instance_id: None, id: None, }; - let get_res = data - .alpha - .get_article(get_article_data.clone()) - .await - .unwrap(); + let get_res = data.alpha.get_article(get_article_data.clone()).await?; assert_eq!(title, get_res.article.title); assert_eq!(TEST_ARTICLE_DEFAULT_TEXT, get_res.article.text); assert!(get_res.article.local); @@ -55,17 +51,14 @@ async fn test_create_read_and_edit_local_article() -> MyResult<()> { previous_version_id: get_res.latest_version, resolve_conflict_id: None, }; - let edit_res = edit_article(&data.alpha, &edit_form).await?; + let edit_res = data.alpha.edit_article(&edit_form).await?; assert_eq!(edit_form.new_text, edit_res.article.text); assert_eq!(2, edit_res.edits.len()); let search_form = SearchArticleData { query: title.clone(), }; - let search_res: Vec = - get_query(&data.alpha.api_client.hostname, "search", Some(search_form)) - .await - .unwrap(); + let search_res = data.alpha.search(&search_form).await?; assert_eq!(1, search_res.len()); assert_eq!(edit_res.article, search_res[0]); @@ -78,11 +71,17 @@ async fn test_create_duplicate_article() -> MyResult<()> { // create article let title = "Manu_Chao".to_string(); - let create_res = create_article(&data.alpha, title.clone()).await?; + let create_res = data + .alpha + .create_article(title.clone(), TEST_ARTICLE_DEFAULT_TEXT.to_string()) + .await?; assert_eq!(title, create_res.article.title); assert!(create_res.article.local); - let create_res = create_article(&data.alpha, title.clone()).await; + let create_res = data + .alpha + .create_article(title.clone(), TEST_ARTICLE_DEFAULT_TEXT.to_string()) + .await; assert!(create_res.is_err()); data.stop() @@ -93,17 +92,17 @@ async fn test_follow_instance() -> MyResult<()> { let data = TestData::start().await; // check initial state - let alpha_instance: InstanceView = get(&data.alpha.hostname, "instance").await?; + let alpha_instance = data.alpha.get_local_instance().await?; assert_eq!(0, alpha_instance.followers.len()); assert_eq!(0, alpha_instance.following.len()); - let beta_instance: InstanceView = get(&data.beta.hostname, "instance").await?; + let beta_instance = data.beta.get_local_instance().await?; assert_eq!(0, beta_instance.followers.len()); assert_eq!(0, beta_instance.following.len()); - follow_instance(&data.alpha, &data.beta.hostname).await?; + data.alpha.follow_instance(&data.beta.hostname).await?; // check that follow was federated - let alpha_instance: InstanceView = get(&data.alpha.hostname, "instance").await?; + let alpha_instance = data.alpha.get_local_instance().await?; assert_eq!(1, alpha_instance.following.len()); assert_eq!(0, alpha_instance.followers.len()); assert_eq!( @@ -111,7 +110,7 @@ async fn test_follow_instance() -> MyResult<()> { alpha_instance.following[0].ap_id ); - let beta_instance: InstanceView = get(&data.beta.hostname, "instance").await?; + let beta_instance = data.beta.get_local_instance().await?; assert_eq!(0, beta_instance.following.len()); assert_eq!(1, beta_instance.followers.len()); // TODO: compare full ap_id of alpha user, but its not available through api yet @@ -129,7 +128,10 @@ async fn test_synchronize_articles() -> MyResult<()> { // create article on alpha let title = "Manu_Chao".to_string(); - let create_res = create_article(&data.alpha, title.clone()).await?; + let create_res = data + .alpha + .create_article(title.clone(), TEST_ARTICLE_DEFAULT_TEXT.to_string()) + .await?; assert_eq!(title, create_res.article.title); assert_eq!(1, create_res.edits.len()); assert!(create_res.article.local); @@ -141,7 +143,7 @@ async fn test_synchronize_articles() -> MyResult<()> { previous_version_id: create_res.latest_version, resolve_conflict_id: None, }; - edit_article(&data.alpha, &edit_form).await?; + data.alpha.edit_article(&edit_form).await?; // fetch alpha instance on beta, articles are also fetched automatically let resolve_object = ResolveObject { @@ -180,11 +182,14 @@ async fn test_synchronize_articles() -> MyResult<()> { async fn test_edit_local_article() -> MyResult<()> { let data = TestData::start().await; - let beta_instance = follow_instance(&data.alpha, &data.beta.hostname).await?; + let beta_instance = data.alpha.follow_instance(&data.beta.hostname).await?; // create new article let title = "Manu_Chao".to_string(); - let create_res = create_article(&data.beta, title.clone()).await?; + let create_res = data + .beta + .create_article(title.clone(), TEST_ARTICLE_DEFAULT_TEXT.to_string()) + .await?; assert_eq!(title, create_res.article.title); assert!(create_res.article.local); @@ -207,7 +212,7 @@ async fn test_edit_local_article() -> MyResult<()> { previous_version_id: get_res.latest_version, resolve_conflict_id: None, }; - let edit_res = edit_article(&data.beta, &edit_form).await?; + let edit_res = data.beta.edit_article(&edit_form).await?; assert_eq!(edit_res.article.text, edit_form.new_text); assert_eq!(edit_res.edits.len(), 2); assert!(edit_res.edits[0] @@ -228,12 +233,15 @@ async fn test_edit_local_article() -> MyResult<()> { async fn test_edit_remote_article() -> MyResult<()> { let data = TestData::start().await; - let beta_id_on_alpha = follow_instance(&data.alpha, &data.beta.hostname).await?; - let beta_id_on_gamma = follow_instance(&data.gamma, &data.beta.hostname).await?; + let beta_id_on_alpha = data.alpha.follow_instance(&data.beta.hostname).await?; + let beta_id_on_gamma = data.gamma.follow_instance(&data.beta.hostname).await?; // create new article let title = "Manu_Chao".to_string(); - let create_res = create_article(&data.beta, title.clone()).await?; + let create_res = data + .beta + .create_article(title.clone(), TEST_ARTICLE_DEFAULT_TEXT.to_string()) + .await?; assert_eq!(&title, &create_res.article.title); assert!(create_res.article.local); @@ -269,7 +277,7 @@ async fn test_edit_remote_article() -> MyResult<()> { previous_version_id: get_res.latest_version, resolve_conflict_id: None, }; - let edit_res = edit_article(&data.alpha, &edit_form).await?; + let edit_res = data.alpha.edit_article(&edit_form).await?; assert_eq!(edit_form.new_text, edit_res.article.text); assert_eq!(2, edit_res.edits.len()); assert!(!edit_res.article.local); @@ -298,7 +306,10 @@ async fn test_local_edit_conflict() -> MyResult<()> { // create new article let title = "Manu_Chao".to_string(); - let create_res = create_article(&data.alpha, title.clone()).await?; + let create_res = data + .alpha + .create_article(title.clone(), TEST_ARTICLE_DEFAULT_TEXT.to_string()) + .await?; assert_eq!(title, create_res.article.title); assert!(create_res.article.local); @@ -309,7 +320,7 @@ async fn test_local_edit_conflict() -> MyResult<()> { previous_version_id: create_res.latest_version.clone(), resolve_conflict_id: None, }; - let edit_res = edit_article(&data.alpha, &edit_form).await?; + let edit_res = data.alpha.edit_article(&edit_form).await?; assert_eq!(edit_res.article.text, edit_form.new_text); assert_eq!(2, edit_res.edits.len()); @@ -320,7 +331,9 @@ async fn test_local_edit_conflict() -> MyResult<()> { previous_version_id: create_res.latest_version, resolve_conflict_id: None, }; - let edit_res = edit_article_with_conflict(&data.alpha, &edit_form) + let edit_res = data + .alpha + .edit_article_with_conflict(&edit_form) .await? .unwrap(); assert_eq!("<<<<<<< ours\nIpsum Lorem\n||||||| original\nsome\nexample\ntext\n=======\nLorem Ipsum\n>>>>>>> theirs\n", edit_res.three_way_merge); @@ -335,7 +348,7 @@ async fn test_local_edit_conflict() -> MyResult<()> { previous_version_id: edit_res.previous_version_id, resolve_conflict_id: Some(edit_res.id), }; - let edit_res = edit_article(&data.alpha, &edit_form).await?; + let edit_res = data.alpha.edit_article(&edit_form).await?; assert_eq!(edit_form.new_text, edit_res.article.text); let conflicts = get_conflicts(&data.alpha).await?; @@ -348,11 +361,14 @@ async fn test_local_edit_conflict() -> MyResult<()> { async fn test_federated_edit_conflict() -> MyResult<()> { let data = TestData::start().await; - let beta_id_on_alpha = follow_instance(&data.alpha, &data.beta.hostname).await?; + let beta_id_on_alpha = data.alpha.follow_instance(&data.beta.hostname).await?; // create new article let title = "Manu_Chao".to_string(); - let create_res = create_article(&data.beta, title.clone()).await?; + let create_res = data + .beta + .create_article(title.clone(), TEST_ARTICLE_DEFAULT_TEXT.to_string()) + .await?; assert_eq!(title, create_res.article.title); assert!(create_res.article.local); @@ -383,7 +399,7 @@ async fn test_federated_edit_conflict() -> MyResult<()> { previous_version_id: create_res.latest_version.clone(), resolve_conflict_id: None, }; - let edit_res = edit_article(&data.alpha, &edit_form).await?; + let edit_res = data.alpha.edit_article(&edit_form).await?; assert_eq!(edit_res.article.text, edit_form.new_text); assert_eq!(2, edit_res.edits.len()); assert!(!edit_res.article.local); @@ -400,7 +416,7 @@ async fn test_federated_edit_conflict() -> MyResult<()> { previous_version_id: create_res.latest_version, resolve_conflict_id: None, }; - let edit_res = edit_article(&data.gamma, &edit_form).await?; + let edit_res = data.gamma.edit_article(&edit_form).await?; assert_ne!(edit_form.new_text, edit_res.article.text); assert_eq!(1, edit_res.edits.len()); assert!(!edit_res.article.local); @@ -415,7 +431,7 @@ async fn test_federated_edit_conflict() -> MyResult<()> { previous_version_id: conflicts[0].previous_version_id.clone(), resolve_conflict_id: Some(conflicts[0].id.clone()), }; - let edit_res = edit_article(&data.gamma, &edit_form).await?; + let edit_res = data.gamma.edit_article(&edit_form).await?; assert_eq!(edit_form.new_text, edit_res.article.text); assert_eq!(3, edit_res.edits.len()); @@ -431,7 +447,10 @@ async fn test_overlapping_edits_no_conflict() -> MyResult<()> { // create new article let title = "Manu_Chao".to_string(); - let create_res = create_article(&data.alpha, title.clone()).await?; + let create_res = data + .alpha + .create_article(title.clone(), TEST_ARTICLE_DEFAULT_TEXT.to_string()) + .await?; assert_eq!(title, create_res.article.title); assert!(create_res.article.local); @@ -442,7 +461,7 @@ async fn test_overlapping_edits_no_conflict() -> MyResult<()> { previous_version_id: create_res.latest_version.clone(), resolve_conflict_id: None, }; - let edit_res = edit_article(&data.alpha, &edit_form).await?; + let edit_res = data.alpha.edit_article(&edit_form).await?; assert_eq!(edit_res.article.text, edit_form.new_text); assert_eq!(2, edit_res.edits.len()); @@ -453,7 +472,7 @@ async fn test_overlapping_edits_no_conflict() -> MyResult<()> { previous_version_id: create_res.latest_version, resolve_conflict_id: None, }; - let edit_res = edit_article(&data.alpha, &edit_form).await?; + let edit_res = data.alpha.edit_article(&edit_form).await?; let conflicts = get_conflicts(&data.alpha).await?; assert_eq!(0, conflicts.len()); assert_eq!(3, edit_res.edits.len()); @@ -468,7 +487,10 @@ async fn test_fork_article() -> MyResult<()> { // create article let title = "Manu_Chao".to_string(); - let create_res = create_article(&data.alpha, title.clone()).await?; + let create_res = data + .alpha + .create_article(title.clone(), TEST_ARTICLE_DEFAULT_TEXT.to_string()) + .await?; assert_eq!(title, create_res.article.title); assert!(create_res.article.local); @@ -497,15 +519,14 @@ async fn test_fork_article() -> MyResult<()> { assert_ne!(resolved_article.ap_id, forked_article.ap_id); assert!(forked_article.local); - let beta_instance: InstanceView = get(&data.beta.hostname, "instance").await?; + let beta_instance = data.beta.get_local_instance().await?; assert_eq!(forked_article.instance_id, beta_instance.instance.id); // now search returns two articles for this title (original and forked) let search_form = SearchArticleData { query: title.clone(), }; - let search_res: Vec = - get_query(&data.beta.hostname, "search", Some(search_form)).await?; + let search_res = data.beta.search(&search_form).await?; assert_eq!(2, search_res.len()); data.stop()