mirror of
https://github.com/Nutomic/ibis.git
synced 2024-11-22 05:51:10 +00:00
Add test case for federating edit of remote article
This commit is contained in:
parent
4e458650b8
commit
61682100f2
7 changed files with 157 additions and 38 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -5,7 +5,7 @@ version = 3
|
|||
[[package]]
|
||||
name = "activitypub_federation"
|
||||
version = "0.5.0-beta.5"
|
||||
source = "git+https://github.com/LemmyNet/activitypub-federation-rust.git?branch=parse-impl#b80408d80619ac014a5cedf5079967c20058532d"
|
||||
source = "git+https://github.com/LemmyNet/activitypub-federation-rust.git?branch=parse-impl#2aa64ad1de7943840677f4b96a20a11d38e2be56"
|
||||
dependencies = [
|
||||
"activitystreams-kinds",
|
||||
"async-trait",
|
||||
|
|
|
@ -29,10 +29,8 @@ pub fn api_routes() -> Router {
|
|||
#[derive(Deserialize, Serialize)]
|
||||
pub struct CreateArticleData {
|
||||
pub title: String,
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
// TODO: new article should be created with empty content
|
||||
#[debug_handler]
|
||||
async fn create_article(
|
||||
data: Data<DatabaseHandle>,
|
||||
|
@ -48,7 +46,7 @@ async fn create_article(
|
|||
.into();
|
||||
let article = DbArticle {
|
||||
title: create_article.title,
|
||||
text: create_article.text,
|
||||
text: String::new(),
|
||||
ap_id,
|
||||
edits: vec![],
|
||||
instance: local_instance_id,
|
||||
|
|
|
@ -33,16 +33,14 @@ impl CreateArticle {
|
|||
let local_instance = data.local_instance();
|
||||
let object = article.clone().into_json(data).await?;
|
||||
let id = generate_activity_id(local_instance.ap_id.inner())?;
|
||||
let create_or_update = CreateArticle {
|
||||
let create = CreateArticle {
|
||||
actor: local_instance.ap_id.clone(),
|
||||
to: local_instance.follower_ids(),
|
||||
object,
|
||||
kind: Default::default(),
|
||||
id,
|
||||
};
|
||||
local_instance
|
||||
.send_to_followers(create_or_update, data)
|
||||
.await?;
|
||||
local_instance.send_to_followers(create, data).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -64,7 +62,10 @@ impl ActivityHandler for CreateArticle {
|
|||
}
|
||||
|
||||
async fn receive(self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
||||
DbArticle::from_json(self.object, data).await?;
|
||||
let article = DbArticle::from_json(self.object.clone(), data).await?;
|
||||
if article.local {
|
||||
data.local_instance().send_to_followers(self, data).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,17 +36,30 @@ impl UpdateArticle {
|
|||
) -> MyResult<()> {
|
||||
let local_instance = data.local_instance();
|
||||
let id = generate_activity_id(local_instance.ap_id.inner())?;
|
||||
let create_or_update = UpdateArticle {
|
||||
actor: local_instance.ap_id.clone(),
|
||||
to: local_instance.follower_ids(),
|
||||
object: article.ap_id,
|
||||
result: edit.into_json(data).await?,
|
||||
kind: Default::default(),
|
||||
id,
|
||||
};
|
||||
local_instance
|
||||
.send_to_followers(create_or_update, data)
|
||||
.await?;
|
||||
if article.local {
|
||||
let update = UpdateArticle {
|
||||
actor: local_instance.ap_id.clone(),
|
||||
to: local_instance.follower_ids(),
|
||||
object: article.ap_id,
|
||||
result: edit.into_json(data).await?,
|
||||
kind: Default::default(),
|
||||
id,
|
||||
};
|
||||
local_instance.send_to_followers(update, data).await?;
|
||||
} else {
|
||||
let article_instance = article.instance.dereference(data).await?;
|
||||
let update = UpdateArticle {
|
||||
actor: local_instance.ap_id.clone(),
|
||||
to: vec![article_instance.ap_id.into_inner()],
|
||||
object: article.ap_id,
|
||||
result: edit.into_json(data).await?,
|
||||
kind: Default::default(),
|
||||
id,
|
||||
};
|
||||
local_instance
|
||||
.send(update, vec![article_instance.inbox], data)
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -68,13 +81,35 @@ impl ActivityHandler for UpdateArticle {
|
|||
}
|
||||
|
||||
async fn receive(self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
||||
let edit = DbEdit::from_json(self.result.clone(), data).await?;
|
||||
let mut lock = data.articles.lock().unwrap();
|
||||
let article = lock.get_mut(self.object.inner()).unwrap();
|
||||
article.edits.push(edit);
|
||||
// TODO: probably better to apply patch inside DbEdit::from_json()
|
||||
let patch = Patch::from_str(&self.result.diff)?;
|
||||
article.text = apply(&article.text, &patch)?;
|
||||
let article_local = {
|
||||
let edit = DbEdit::from_json(self.result.clone(), data).await?;
|
||||
let mut lock = data.articles.lock().unwrap();
|
||||
let article = lock.get_mut(self.object.inner()).unwrap();
|
||||
article.edits.push(edit);
|
||||
// TODO: probably better to apply patch inside DbEdit::from_json()
|
||||
let patch = Patch::from_str(&self.result.diff)?;
|
||||
article.text = apply(&article.text, &patch)?;
|
||||
article.local
|
||||
};
|
||||
|
||||
if article_local {
|
||||
// No need to wrap in announce, we can construct a new activity as all important info
|
||||
// is in the object and result fields.
|
||||
let local_instance = data.local_instance();
|
||||
let id = generate_activity_id(local_instance.ap_id.inner())?;
|
||||
let update = UpdateArticle {
|
||||
actor: local_instance.ap_id.clone(),
|
||||
to: local_instance.follower_ids(),
|
||||
object: self.object,
|
||||
result: self.result,
|
||||
kind: Default::default(),
|
||||
id,
|
||||
};
|
||||
data.local_instance()
|
||||
.send_to_followers(update, data)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,10 +81,7 @@ impl Collection for DbArticleCollection {
|
|||
.map(|i| DbArticle::from_json(i, data)),
|
||||
)
|
||||
.await?;
|
||||
let mut lock = data.articles.lock().unwrap();
|
||||
for a in &articles {
|
||||
lock.insert(a.ap_id.inner().clone(), a.clone());
|
||||
}
|
||||
|
||||
// TODO: return value propably not needed
|
||||
Ok(DbArticleCollection(articles))
|
||||
}
|
||||
|
|
|
@ -16,8 +16,10 @@ pub static CLIENT: Lazy<Client> = Lazy::new(Client::new);
|
|||
pub struct TestData {
|
||||
pub hostname_alpha: &'static str,
|
||||
pub hostname_beta: &'static str,
|
||||
pub hostname_gamma: &'static str,
|
||||
handle_alpha: JoinHandle<()>,
|
||||
handle_beta: JoinHandle<()>,
|
||||
handle_gamma: JoinHandle<()>,
|
||||
}
|
||||
|
||||
impl TestData {
|
||||
|
@ -33,23 +35,30 @@ impl TestData {
|
|||
|
||||
let hostname_alpha = "localhost:8131";
|
||||
let hostname_beta = "localhost:8132";
|
||||
let hostname_gamma = "localhost:8133";
|
||||
let handle_alpha = tokio::task::spawn(async {
|
||||
start(hostname_alpha).await.unwrap();
|
||||
});
|
||||
let handle_beta = tokio::task::spawn(async {
|
||||
start(hostname_beta).await.unwrap();
|
||||
});
|
||||
let handle_gamma = tokio::task::spawn(async {
|
||||
start(hostname_gamma).await.unwrap();
|
||||
});
|
||||
Self {
|
||||
hostname_alpha,
|
||||
hostname_beta,
|
||||
hostname_gamma,
|
||||
handle_alpha,
|
||||
handle_beta,
|
||||
handle_gamma,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stop(self) -> MyResult<()> {
|
||||
self.handle_alpha.abort();
|
||||
self.handle_beta.abort();
|
||||
self.handle_gamma.abort();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ use url::Url;
|
|||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_create_and_read_article() -> MyResult<()> {
|
||||
async fn test_create_read_and_edit_article() -> MyResult<()> {
|
||||
let data = TestData::start();
|
||||
|
||||
// error on nonexistent article
|
||||
|
@ -31,7 +31,6 @@ async fn test_create_and_read_article() -> MyResult<()> {
|
|||
// create article
|
||||
let create_article = CreateArticleData {
|
||||
title: get_article.title.to_string(),
|
||||
text: "Lorem ipsum".to_string(),
|
||||
};
|
||||
let create_res: DbArticle = post(data.hostname_alpha, "article", &create_article).await?;
|
||||
assert_eq!(create_article.title, create_res.title);
|
||||
|
@ -45,9 +44,18 @@ async fn test_create_and_read_article() -> MyResult<()> {
|
|||
)
|
||||
.await?;
|
||||
assert_eq!(create_article.title, get_res.title);
|
||||
assert_eq!(create_article.text, get_res.text);
|
||||
assert!(get_res.text.is_empty());
|
||||
assert!(get_res.local);
|
||||
|
||||
// edit article
|
||||
let edit_form = EditArticleData {
|
||||
ap_id: create_res.ap_id.clone(),
|
||||
new_text: "Lorem Ipsum 2".to_string(),
|
||||
};
|
||||
let edit_res: DbArticle = patch(data.hostname_alpha, "article", &edit_form).await?;
|
||||
assert_eq!(edit_form.new_text, edit_res.text);
|
||||
assert_eq!(1, edit_res.edits.len());
|
||||
|
||||
data.stop()
|
||||
}
|
||||
|
||||
|
@ -82,10 +90,10 @@ async fn test_synchronize_articles() -> MyResult<()> {
|
|||
// create article on alpha
|
||||
let create_article = CreateArticleData {
|
||||
title: "Manu_Chao".to_string(),
|
||||
text: "Lorem ipsum".to_string(),
|
||||
};
|
||||
let create_res: DbArticle = post(data.hostname_alpha, "article", &create_article).await?;
|
||||
assert_eq!(create_article.title, create_res.title);
|
||||
assert_eq!(0, create_res.edits.len());
|
||||
assert!(create_res.local);
|
||||
|
||||
// article is not yet on beta
|
||||
|
@ -115,7 +123,8 @@ async fn test_synchronize_articles() -> MyResult<()> {
|
|||
.await?;
|
||||
assert_eq!(create_res.ap_id, get_res.ap_id);
|
||||
assert_eq!(create_article.title, get_res.title);
|
||||
assert_eq!(create_article.text, get_res.text);
|
||||
assert_eq!(0, get_res.edits.len());
|
||||
assert!(get_res.text.is_empty());
|
||||
assert!(!get_res.local);
|
||||
|
||||
data.stop()
|
||||
|
@ -123,7 +132,7 @@ async fn test_synchronize_articles() -> MyResult<()> {
|
|||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_federate_article_changes() -> MyResult<()> {
|
||||
async fn test_edit_local_article() -> MyResult<()> {
|
||||
let data = TestData::start();
|
||||
|
||||
follow_instance(data.hostname_alpha, data.hostname_beta).await?;
|
||||
|
@ -131,10 +140,10 @@ async fn test_federate_article_changes() -> MyResult<()> {
|
|||
// create new article
|
||||
let create_form = CreateArticleData {
|
||||
title: "Manu_Chao".to_string(),
|
||||
text: "Lorem ipsum".to_string(),
|
||||
};
|
||||
let create_res: DbArticle = post(data.hostname_beta, "article", &create_form).await?;
|
||||
assert_eq!(create_res.title, create_form.title);
|
||||
assert!(create_res.local);
|
||||
|
||||
// article should be federated to alpha
|
||||
let get_article = GetArticleData {
|
||||
|
@ -144,6 +153,8 @@ async fn test_federate_article_changes() -> MyResult<()> {
|
|||
get_query::<DbArticle, _>(data.hostname_alpha, "article", Some(get_article.clone()))
|
||||
.await?;
|
||||
assert_eq!(create_res.title, get_res.title);
|
||||
assert_eq!(0, get_res.edits.len());
|
||||
assert!(!get_res.local);
|
||||
assert_eq!(create_res.text, get_res.text);
|
||||
|
||||
// edit the article
|
||||
|
@ -167,6 +178,74 @@ async fn test_federate_article_changes() -> MyResult<()> {
|
|||
get_query::<DbArticle, _>(data.hostname_alpha, "article", Some(get_article.clone()))
|
||||
.await?;
|
||||
assert_eq!(edit_res.title, get_res.title);
|
||||
assert_eq!(edit_res.edits.len(), 1);
|
||||
assert_eq!(edit_res.text, get_res.text);
|
||||
|
||||
data.stop()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_edit_remote_article() -> MyResult<()> {
|
||||
let data = TestData::start();
|
||||
|
||||
follow_instance(data.hostname_alpha, data.hostname_beta).await?;
|
||||
follow_instance(data.hostname_gamma, data.hostname_beta).await?;
|
||||
|
||||
// create new article
|
||||
let create_form = CreateArticleData {
|
||||
title: "Manu_Chao".to_string(),
|
||||
};
|
||||
let create_res: DbArticle = post(data.hostname_beta, "article", &create_form).await?;
|
||||
assert_eq!(create_res.title, create_form.title);
|
||||
assert!(create_res.local);
|
||||
|
||||
// article should be federated to alpha and gamma
|
||||
let get_article = GetArticleData {
|
||||
title: create_res.title.clone(),
|
||||
};
|
||||
let get_res =
|
||||
get_query::<DbArticle, _>(data.hostname_alpha, "article", Some(get_article.clone()))
|
||||
.await?;
|
||||
assert_eq!(create_res.title, get_res.title);
|
||||
assert_eq!(0, get_res.edits.len());
|
||||
assert!(!get_res.local);
|
||||
assert_eq!(create_res.text, get_res.text);
|
||||
|
||||
let get_res =
|
||||
get_query::<DbArticle, _>(data.hostname_gamma, "article", Some(get_article.clone()))
|
||||
.await?;
|
||||
assert_eq!(create_res.title, get_res.title);
|
||||
assert_eq!(create_res.text, get_res.text);
|
||||
|
||||
let edit_form = EditArticleData {
|
||||
ap_id: create_res.ap_id,
|
||||
new_text: "Lorem Ipsum 2".to_string(),
|
||||
};
|
||||
let edit_res: DbArticle = patch(data.hostname_alpha, "article", &edit_form).await?;
|
||||
assert_eq!(edit_res.text, edit_form.new_text);
|
||||
assert_eq!(edit_res.edits.len(), 1);
|
||||
assert!(!edit_res.local);
|
||||
assert!(edit_res.edits[0]
|
||||
.id
|
||||
.to_string()
|
||||
.starts_with(&edit_res.ap_id.to_string()));
|
||||
|
||||
// edit should be federated to beta and gamma
|
||||
let get_article = GetArticleData {
|
||||
title: edit_res.title.clone(),
|
||||
};
|
||||
let get_res =
|
||||
get_query::<DbArticle, _>(data.hostname_beta, "article", Some(get_article.clone())).await?;
|
||||
assert_eq!(edit_res.title, get_res.title);
|
||||
assert_eq!(edit_res.edits.len(), 1);
|
||||
assert_eq!(edit_res.text, get_res.text);
|
||||
|
||||
let get_res =
|
||||
get_query::<DbArticle, _>(data.hostname_gamma, "article", Some(get_article.clone()))
|
||||
.await?;
|
||||
assert_eq!(edit_res.title, get_res.title);
|
||||
assert_eq!(edit_res.edits.len(), 1);
|
||||
assert_eq!(edit_res.text, get_res.text);
|
||||
|
||||
data.stop()
|
||||
|
|
Loading…
Reference in a new issue