get follows working

This commit is contained in:
Felix Ableitner 2023-12-04 15:10:07 +01:00
parent 90c4fbf8e4
commit aec05ac6b5
20 changed files with 67 additions and 83 deletions

1
Cargo.lock generated
View File

@ -5,6 +5,7 @@ version = 3
[[package]] [[package]]
name = "activitypub_federation" name = "activitypub_federation"
version = "0.5.0-beta.5" version = "0.5.0-beta.5"
source = "git+https://github.com/LemmyNet/activitypub-federation-rust.git?branch=diesel-feature#ffb020bcdd5004fdcba501950e6a87bc82c806ed"
dependencies = [ dependencies = [
"activitystreams-kinds", "activitystreams-kinds",
"async-trait", "async-trait",

View File

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
activitypub_federation = { path = "../lemmy/activitypub-federation-rust", features = ["axum", "diesel"], default-features = false } activitypub_federation = { git = "https://github.com/LemmyNet/activitypub-federation-rust.git", branch = "diesel-feature", features = ["axum", "diesel"], default-features = false }
anyhow = "1.0.75" anyhow = "1.0.75"
async-trait = "0.1.74" async-trait = "0.1.74"
axum = "0.6.20" axum = "0.6.20"

View File

@ -11,11 +11,12 @@ create table instance (
create table instance_follow ( create table instance_follow (
id serial primary key, id serial primary key,
instance_id int REFERENCES instance ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
follower_id int REFERENCES instance ON UPDATE CASCADE ON DELETE CASCADE NOT NULL, follower_id int REFERENCES instance ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
followed_id int REFERENCES instance ON UPDATE CASCADE ON DELETE CASCADE NOT NULL, pending boolean not null,
pending boolean not null unique(instance_id, follower_id)
); );
create table article ( create table article (
id serial primary key, id serial primary key,
title text not null, title text not null,

View File

@ -129,7 +129,6 @@ async fn edit_article(
let ancestor = let ancestor =
generate_article_version(&original_article.edits, &edit_form.previous_version)?; generate_article_version(&original_article.edits, &edit_form.previous_version)?;
let patch = create_patch(&ancestor, &edit_form.new_text); let patch = create_patch(&ancestor, &edit_form.new_text);
dbg!(&edit_form.previous_version);
let db_conflict = DbConflict { let db_conflict = DbConflict {
id: random(), id: random(),
@ -141,7 +140,7 @@ async fn edit_article(
let mut lock = data.conflicts.lock().unwrap(); let mut lock = data.conflicts.lock().unwrap();
lock.push(db_conflict.clone()); lock.push(db_conflict.clone());
} }
Ok(Json(dbg!(db_conflict.to_api_conflict(&data).await)?)) Ok(Json(db_conflict.to_api_conflict(&data).await?))
} }
} }
@ -226,21 +225,16 @@ async fn follow_instance(
/// Get a list of all unresolved edit conflicts. /// Get a list of all unresolved edit conflicts.
#[debug_handler] #[debug_handler]
async fn edit_conflicts(data: Data<MyDataHandle>) -> MyResult<Json<Vec<ApiConflict>>> { async fn edit_conflicts(data: Data<MyDataHandle>) -> MyResult<Json<Vec<ApiConflict>>> {
dbg!("a");
let conflicts = { data.conflicts.lock().unwrap().to_vec() }; let conflicts = { data.conflicts.lock().unwrap().to_vec() };
dbg!(&conflicts);
dbg!("b");
let conflicts: Vec<ApiConflict> = try_join_all(conflicts.into_iter().map(|c| { let conflicts: Vec<ApiConflict> = try_join_all(conflicts.into_iter().map(|c| {
let data = data.reset_request_count(); let data = data.reset_request_count();
dbg!(&c.previous_version); async move { c.to_api_conflict(&data).await }
async move { dbg!(c.to_api_conflict(&data).await) }
})) }))
.await? .await?
.into_iter() .into_iter()
.flatten() .flatten()
.collect(); .collect();
dbg!("c"); Ok(Json(conflicts))
Ok(Json(dbg!(conflicts)))
} }
#[derive(Deserialize, Serialize, Clone)] #[derive(Deserialize, Serialize, Clone)]

View File

@ -1,5 +1,5 @@
use crate::database::edit::{DbEdit, EditVersion}; use crate::database::edit::{DbEdit, EditVersion};
use crate::database::instance::DbInstance;
use crate::database::schema::article; use crate::database::schema::article;
use crate::error::MyResult; use crate::error::MyResult;
use crate::federation::objects::edits_collection::DbEditCollection; use crate::federation::objects::edits_collection::DbEditCollection;

View File

@ -1,19 +1,18 @@
use crate::database::article::DbArticle;
use crate::database::schema::{instance, instance_follow}; use crate::database::schema::{instance, instance_follow};
use crate::database::MyDataHandle; use crate::database::MyDataHandle;
use crate::error::{Error, MyResult}; use crate::error::{Error, MyResult};
use crate::federation::activities::follow::Follow;
use crate::federation::objects::articles_collection::DbArticleCollection; use crate::federation::objects::articles_collection::DbArticleCollection;
use activitypub_federation::activity_sending::SendActivityTask; use activitypub_federation::activity_sending::SendActivityTask;
use activitypub_federation::config::Data; use activitypub_federation::config::Data;
use activitypub_federation::fetch::collection_id::CollectionId; use activitypub_federation::fetch::collection_id::CollectionId;
use activitypub_federation::fetch::object_id::ObjectId; use activitypub_federation::fetch::object_id::ObjectId;
use activitypub_federation::protocol::context::WithContext; use activitypub_federation::protocol::context::WithContext;
use activitypub_federation::traits::{ActivityHandler, Actor}; use activitypub_federation::traits::ActivityHandler;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use diesel::ExpressionMethods; use diesel::ExpressionMethods;
use diesel::{ use diesel::{
insert_into, update, AsChangeset, Identifiable, Insertable, JoinOnDsl, PgConnection, QueryDsl, insert_into, AsChangeset, Identifiable, Insertable, JoinOnDsl, PgConnection, QueryDsl,
Queryable, RunQueryDsl, Selectable, Queryable, RunQueryDsl, Selectable,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -56,7 +55,7 @@ pub struct DbInstanceForm {
pub struct InstanceView { pub struct InstanceView {
pub instance: DbInstance, pub instance: DbInstance,
pub followers: Vec<DbInstance>, pub followers: Vec<DbInstance>,
pub followed: Vec<DbInstance>, pub following: Vec<DbInstance>,
} }
impl DbInstance { impl DbInstance {
@ -151,49 +150,54 @@ impl DbInstance {
pub fn read_local_view(conn: &Mutex<PgConnection>) -> MyResult<InstanceView> { pub fn read_local_view(conn: &Mutex<PgConnection>) -> MyResult<InstanceView> {
let instance = DbInstance::read_local_instance(conn)?; let instance = DbInstance::read_local_instance(conn)?;
let followers = DbInstance::read_followers(instance.id, conn)?; let followers = DbInstance::read_followers(instance.id, conn)?;
let followed = DbInstance::read_followed(instance.id, conn)?; let following = DbInstance::read_following(instance.id, conn)?;
Ok(InstanceView { Ok(InstanceView {
instance, instance,
followers, followers,
followed, following,
}) })
} }
pub fn follow( pub fn follow(
follower_id_: i32, follower_id_: i32,
followed_id_: i32, instance_id_: i32,
pending_: bool, pending_: bool,
data: &Data<MyDataHandle>, data: &Data<MyDataHandle>,
) -> MyResult<()> { ) -> MyResult<()> {
use instance_follow::dsl::{followed_id, follower_id, pending}; debug_assert_ne!(follower_id_, instance_id_);
use instance_follow::dsl::{follower_id, instance_id, pending};
let mut conn = data.db_connection.lock().unwrap(); let mut conn = data.db_connection.lock().unwrap();
insert_into(instance_follow::table) let form = (
.values(( instance_id.eq(instance_id_),
follower_id.eq(follower_id_), follower_id.eq(follower_id_),
followed_id.eq(followed_id_),
pending.eq(pending_), pending.eq(pending_),
)) );
dbg!(follower_id_, instance_id_, pending_);
insert_into(instance_follow::table)
.values(form)
.on_conflict((instance_id, follower_id))
.do_update()
.set(form)
.execute(conn.deref_mut())?; .execute(conn.deref_mut())?;
Ok(()) Ok(())
} }
pub fn read_followers(id_: i32, conn: &Mutex<PgConnection>) -> MyResult<Vec<Self>> { pub fn read_followers(id_: i32, conn: &Mutex<PgConnection>) -> MyResult<Vec<Self>> {
use instance_follow::dsl::{followed_id, id}; use instance_follow::dsl::{follower_id, instance_id};
let mut conn = conn.lock().unwrap(); let mut conn = conn.lock().unwrap();
Ok(instance_follow::table Ok(instance_follow::table
.inner_join(instance::table.on(id.eq(instance::dsl::id))) .inner_join(instance::table.on(follower_id.eq(instance::dsl::id)))
.filter(followed_id.eq(id_)) .filter(instance_id.eq(id_))
.select(instance::all_columns) .select(instance::all_columns)
.get_results(conn.deref_mut())?) .get_results(conn.deref_mut())?)
} }
pub fn read_followed(id_: i32, conn: &Mutex<PgConnection>) -> MyResult<Vec<Self>> { pub fn read_following(id_: i32, conn: &Mutex<PgConnection>) -> MyResult<Vec<Self>> {
// TODO: is this correct? use instance_follow::dsl::{follower_id, instance_id};
use instance_follow::dsl::{follower_id, id};
let mut conn = conn.lock().unwrap(); let mut conn = conn.lock().unwrap();
Ok(instance_follow::table Ok(instance_follow::table
.inner_join(instance::table.on(id.eq(instance::dsl::id))) .inner_join(instance::table.on(instance_id.eq(instance::dsl::id)))
.filter(follower_id.eq(id_)) .filter(follower_id.eq(id_))
.select(instance::all_columns) .select(instance::all_columns)
.get_results(conn.deref_mut())?) .get_results(conn.deref_mut())?)

View File

@ -9,11 +9,9 @@ use activitypub_federation::fetch::object_id::ObjectId;
use diesel::PgConnection; use diesel::PgConnection;
use diffy::{apply, merge, Patch}; use diffy::{apply, merge, Patch};
use edit::EditVersion; use edit::EditVersion;
use instance::DbInstance;
use std::collections::HashMap;
use std::ops::Deref; use std::ops::Deref;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use url::Url;
pub mod article; pub mod article;
pub mod edit; pub mod edit;
@ -58,12 +56,10 @@ impl DbConflict {
// create common ancestor version // create common ancestor version
let edits = DbEdit::for_article(&original_article, &data.db_connection)?; let edits = DbEdit::for_article(&original_article, &data.db_connection)?;
let ancestor = generate_article_version(&edits, &self.previous_version)?; let ancestor = generate_article_version(&edits, &self.previous_version)?;
dbg!(&ancestor, &self.previous_version);
dbg!(&self.diff);
let patch = Patch::from_str(&self.diff)?; let patch = Patch::from_str(&self.diff)?;
// apply self.diff to ancestor to get `ours` // apply self.diff to ancestor to get `ours`
let ours = dbg!(apply(&ancestor, &patch))?; let ours = apply(&ancestor, &patch)?;
match merge(&ancestor, &ours, &original_article.text) { match merge(&ancestor, &ours, &original_article.text) {
Ok(new_text) => { Ok(new_text) => {
// patch applies cleanly so we are done // patch applies cleanly so we are done

View File

@ -42,8 +42,8 @@ diesel::table! {
diesel::table! { diesel::table! {
instance_follow (id) { instance_follow (id) {
id -> Int4, id -> Int4,
instance_id -> Int4,
follower_id -> Int4, follower_id -> Int4,
followed_id -> Int4,
pending -> Bool, pending -> Bool,
} }
} }

View File

@ -51,7 +51,7 @@ impl ActivityHandler for Accept {
// add to follows // add to follows
let local_instance = DbInstance::read_local_instance(&data.db_connection)?; let local_instance = DbInstance::read_local_instance(&data.db_connection)?;
let actor = self.actor.dereference(data).await?; let actor = self.actor.dereference(data).await?;
DbInstance::follow(local_instance.id, actor.id, false, &data)?; DbInstance::follow(local_instance.id, actor.id, false, data)?;
Ok(()) Ok(())
} }
} }

View File

@ -30,7 +30,7 @@ impl CreateArticle {
let local_instance = DbInstance::read_local_instance(&data.db_connection)?; let local_instance = DbInstance::read_local_instance(&data.db_connection)?;
let object = article.clone().into_json(data).await?; let object = article.clone().into_json(data).await?;
let id = generate_activity_id(local_instance.ap_id.inner())?; let id = generate_activity_id(local_instance.ap_id.inner())?;
let to = local_instance.follower_ids(&data)?; let to = local_instance.follower_ids(data)?;
let create = CreateArticle { let create = CreateArticle {
actor: local_instance.ap_id.clone(), actor: local_instance.ap_id.clone(),
to, to,

View File

@ -60,7 +60,7 @@ impl ActivityHandler for Follow {
async fn receive(self, data: &Data<Self::DataType>) -> Result<(), Self::Error> { async fn receive(self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
let actor = self.actor.dereference(data).await?; let actor = self.actor.dereference(data).await?;
let local_instance = DbInstance::read_local_instance(&data.db_connection)?; let local_instance = DbInstance::read_local_instance(&data.db_connection)?;
DbInstance::follow(actor.id, local_instance.id, false, &data)?; DbInstance::follow(actor.id, local_instance.id, false, data)?;
// send back an accept // send back an accept
let follower = self.actor.dereference(data).await?; let follower = self.actor.dereference(data).await?;

View File

@ -66,7 +66,6 @@ impl ActivityHandler for RejectEdit {
} }
async fn receive(self, data: &Data<Self::DataType>) -> Result<(), Self::Error> { async fn receive(self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
dbg!(&self);
// cant convert this to DbEdit as it tries to apply patch and fails // cant convert this to DbEdit as it tries to apply patch and fails
let mut lock = data.conflicts.lock().unwrap(); let mut lock = data.conflicts.lock().unwrap();
let conflict = DbConflict { let conflict = DbConflict {

View File

@ -37,7 +37,7 @@ impl UpdateLocalArticle {
debug_assert!(article.local); debug_assert!(article.local);
let local_instance = DbInstance::read_local_instance(&data.db_connection)?; let local_instance = DbInstance::read_local_instance(&data.db_connection)?;
let id = generate_activity_id(local_instance.ap_id.inner())?; let id = generate_activity_id(local_instance.ap_id.inner())?;
let mut to = local_instance.follower_ids(&data)?; let mut to = local_instance.follower_ids(data)?;
to.extend(extra_recipients.iter().map(|i| i.ap_id.inner().clone())); to.extend(extra_recipients.iter().map(|i| i.ap_id.inner().clone()));
let update = UpdateLocalArticle { let update = UpdateLocalArticle {
actor: local_instance.ap_id.clone(), actor: local_instance.ap_id.clone(),

View File

@ -47,9 +47,6 @@ impl UpdateRemoteArticle {
kind: Default::default(), kind: Default::default(),
id, id,
}; };
// TODO: this is wrong and causes test failure. need to take previous_version from api param,
// or put previous_version in DbEdit
dbg!(&update.object.previous_version);
local_instance local_instance
.send(update, vec![Url::parse(&article_instance.inbox_url)?], data) .send(update, vec![Url::parse(&article_instance.inbox_url)?], data)
.await?; .await?;

View File

@ -1,4 +1,4 @@
use crate::database::article::{ArticleView, DbArticle}; use crate::database::article::DbArticle;
use crate::database::MyDataHandle; use crate::database::MyDataHandle;
use crate::error::Error; use crate::error::Error;
use crate::federation::objects::edit::ApubEdit; use crate::federation::objects::edit::ApubEdit;

View File

@ -8,12 +8,12 @@ use activitypub_federation::{
config::Data, config::Data,
fetch::object_id::ObjectId, fetch::object_id::ObjectId,
protocol::{public_key::PublicKey, verification::verify_domains_match}, protocol::{public_key::PublicKey, verification::verify_domains_match},
traits::{ActivityHandler, Actor, Object}, traits::{Actor, Object},
}; };
use chrono::{DateTime, Local, Utc}; use chrono::{DateTime, Local, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::Debug; use std::fmt::Debug;
use url::{ParseError, Url}; use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -40,7 +40,7 @@ impl Object for DbInstance {
object_id: Url, object_id: Url,
data: &Data<Self::DataType>, data: &Data<Self::DataType>,
) -> Result<Option<Self>, Self::Error> { ) -> Result<Option<Self>, Self::Error> {
Ok(DbInstance::read_from_ap_id(&object_id.into(), &data).ok()) Ok(DbInstance::read_from_ap_id(&object_id.into(), data).ok())
} }
async fn into_json(self, _data: &Data<Self::DataType>) -> Result<Self::Kind, Self::Error> { async fn into_json(self, _data: &Data<Self::DataType>) -> Result<Self::Kind, Self::Error> {

View File

@ -18,7 +18,6 @@ use diesel_migrations::MigrationHarness;
use std::net::ToSocketAddrs; use std::net::ToSocketAddrs;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use tracing::info; use tracing::info;
use url::Url;
pub mod api; pub mod api;
pub mod database; pub mod database;

View File

@ -29,7 +29,6 @@ pub fn generate_article_version(edits: &Vec<DbEdit>, version: &EditVersion) -> M
return Ok(generated); return Ok(generated);
} }
for e in edits { for e in edits {
dbg!(&e);
let patch = Patch::from_str(&e.diff)?; let patch = Patch::from_str(&e.diff)?;
generated = apply(&generated, &patch)?; generated = apply(&generated, &patch)?;
if &e.version == version { if &e.version == version {

View File

@ -167,13 +167,7 @@ pub async fn edit_article_with_conflict(
} }
pub async fn edit_article(hostname: &str, edit_form: &EditArticleData) -> MyResult<ArticleView> { pub async fn edit_article(hostname: &str, edit_form: &EditArticleData) -> MyResult<ArticleView> {
let edit_res: Option<ApiConflict> = CLIENT let edit_res = edit_article_with_conflict(hostname, edit_form).await?;
.patch(format!("http://{}/api/v1/article", hostname))
.form(&edit_form)
.send()
.await?
.json()
.await?;
assert!(edit_res.is_none()); assert!(edit_res.is_none());
get_article(hostname, edit_form.article_id).await get_article(hostname, edit_form.article_id).await
} }
@ -211,13 +205,13 @@ where
.await?) .await?)
} }
pub async fn follow_instance(follow_instance: &str, followed_instance: &str) -> MyResult<()> { pub async fn follow_instance(api_instance: &str, follow_instance: &str) -> MyResult<()> {
// fetch beta instance on alpha // fetch beta instance on alpha
let resolve_form = ResolveObject { let resolve_form = ResolveObject {
id: Url::parse(&format!("http://{}", followed_instance))?, id: Url::parse(&format!("http://{}", follow_instance))?,
}; };
let instance_resolved: DbInstance = let instance_resolved: DbInstance =
get_query(followed_instance, "resolve_instance", Some(resolve_form)).await?; get_query(api_instance, "resolve_instance", Some(resolve_form)).await?;
// send follow // send follow
let follow_form = FollowInstance { let follow_form = FollowInstance {
@ -225,7 +219,7 @@ pub async fn follow_instance(follow_instance: &str, followed_instance: &str) ->
}; };
// cant use post helper because follow doesnt return json // cant use post helper because follow doesnt return json
CLIENT CLIENT
.post(format!("http://{}/api/v1/instance/follow", follow_instance)) .post(format!("http://{}/api/v1/instance/follow", api_instance))
.form(&follow_form) .form(&follow_form)
.send() .send()
.await?; .await?;

View File

@ -83,19 +83,29 @@ async fn test_follow_instance() -> MyResult<()> {
// check initial state // check initial state
let alpha_instance: InstanceView = get(&data.alpha.hostname, "instance").await?; let alpha_instance: InstanceView = get(&data.alpha.hostname, "instance").await?;
assert_eq!(0, alpha_instance.followers.len()); assert_eq!(0, alpha_instance.followers.len());
assert_eq!(0, alpha_instance.followed.len()); assert_eq!(0, alpha_instance.following.len());
let beta_instance: InstanceView = get(&data.beta.hostname, "instance").await?; let beta_instance: InstanceView = get(&data.beta.hostname, "instance").await?;
assert_eq!(0, beta_instance.followers.len()); assert_eq!(0, beta_instance.followers.len());
assert_eq!(0, beta_instance.followed.len()); assert_eq!(0, beta_instance.following.len());
follow_instance(&data.alpha.hostname, &data.beta.hostname).await?; follow_instance(&data.alpha.hostname, &data.beta.hostname).await?;
// check that follow was federated // check that follow was federated
let beta_instance: InstanceView = get(&data.beta.hostname, "instance").await?;
assert_eq!(1, beta_instance.followers.len());
let alpha_instance: InstanceView = get(&data.alpha.hostname, "instance").await?; let alpha_instance: InstanceView = get(&data.alpha.hostname, "instance").await?;
assert_eq!(1, alpha_instance.followed.len()); assert_eq!(1, alpha_instance.following.len());
assert_eq!(0, alpha_instance.followers.len());
assert_eq!(
beta_instance.instance.ap_id,
alpha_instance.following[0].ap_id
);
let beta_instance: InstanceView = get(&data.beta.hostname, "instance").await?;
assert_eq!(0, beta_instance.following.len());
assert_eq!(1, beta_instance.followers.len());
assert_eq!(
alpha_instance.instance.ap_id,
beta_instance.followers[0].ap_id
);
data.stop() data.stop()
} }
@ -157,7 +167,6 @@ async fn test_edit_local_article() -> MyResult<()> {
let create_res = create_article(&data.beta.hostname, title.clone()).await?; let create_res = create_article(&data.beta.hostname, title.clone()).await?;
assert_eq!(title, create_res.article.title); assert_eq!(title, create_res.article.title);
assert!(create_res.article.local); assert!(create_res.article.local);
dbg!(1);
// article should be federated to alpha // article should be federated to alpha
let get_res = get_article(&data.alpha.hostname, create_res.article.id).await?; let get_res = get_article(&data.alpha.hostname, create_res.article.id).await?;
@ -165,7 +174,6 @@ async fn test_edit_local_article() -> MyResult<()> {
assert_eq!(1, get_res.edits.len()); assert_eq!(1, get_res.edits.len());
assert!(!get_res.article.local); assert!(!get_res.article.local);
assert_eq!(create_res.article.text, get_res.article.text); assert_eq!(create_res.article.text, get_res.article.text);
dbg!(2);
// edit the article // edit the article
let edit_form = EditArticleData { let edit_form = EditArticleData {
@ -175,7 +183,6 @@ async fn test_edit_local_article() -> MyResult<()> {
resolve_conflict_id: None, resolve_conflict_id: None,
}; };
let edit_res = edit_article(&data.beta.hostname, &edit_form).await?; let edit_res = edit_article(&data.beta.hostname, &edit_form).await?;
dbg!(3);
assert_eq!(edit_res.article.text, edit_form.new_text); assert_eq!(edit_res.article.text, edit_form.new_text);
assert_eq!(edit_res.edits.len(), 2); assert_eq!(edit_res.edits.len(), 2);
assert!(edit_res.edits[0] assert!(edit_res.edits[0]
@ -185,7 +192,6 @@ async fn test_edit_local_article() -> MyResult<()> {
// edit should be federated to alpha // edit should be federated to alpha
let get_res = get_article(&data.alpha.hostname, edit_res.article.id).await?; let get_res = get_article(&data.alpha.hostname, edit_res.article.id).await?;
dbg!(4);
assert_eq!(edit_res.article.title, get_res.article.title); assert_eq!(edit_res.article.title, get_res.article.title);
assert_eq!(edit_res.edits.len(), 2); assert_eq!(edit_res.edits.len(), 2);
assert_eq!(edit_res.article.text, get_res.article.text); assert_eq!(edit_res.article.text, get_res.article.text);
@ -341,20 +347,17 @@ async fn test_federated_edit_conflict() -> MyResult<()> {
// gamma also edits, as its not the latest version there is a conflict. local version should // gamma also edits, as its not the latest version there is a conflict. local version should
// not be updated with this conflicting version, instead user needs to handle the conflict // not be updated with this conflicting version, instead user needs to handle the conflict
dbg!(&create_res.article.text, &create_res.latest_version);
let edit_form = EditArticleData { let edit_form = EditArticleData {
article_id: create_res.article.id, article_id: create_res.article.id,
new_text: "aaaa\n".to_string(), new_text: "aaaa\n".to_string(),
previous_version: create_res.latest_version, previous_version: create_res.latest_version,
resolve_conflict_id: None, resolve_conflict_id: None,
}; };
dbg!(1);
let edit_res = edit_article(&data.gamma.hostname, &edit_form).await?; let edit_res = edit_article(&data.gamma.hostname, &edit_form).await?;
assert_ne!(edit_form.new_text, edit_res.article.text); assert_ne!(edit_form.new_text, edit_res.article.text);
assert_eq!(2, edit_res.edits.len()); assert_eq!(2, edit_res.edits.len());
assert!(!edit_res.article.local); assert!(!edit_res.article.local);
dbg!(2);
let conflicts: Vec<ApiConflict> = let conflicts: Vec<ApiConflict> =
get_query(&data.gamma.hostname, "edit_conflicts", None::<()>).await?; get_query(&data.gamma.hostname, "edit_conflicts", None::<()>).await?;
assert_eq!(1, conflicts.len()); assert_eq!(1, conflicts.len());
@ -366,16 +369,13 @@ async fn test_federated_edit_conflict() -> MyResult<()> {
previous_version: conflicts[0].previous_version.clone(), previous_version: conflicts[0].previous_version.clone(),
resolve_conflict_id: Some(conflicts[0].id), resolve_conflict_id: Some(conflicts[0].id),
}; };
dbg!(3);
let edit_res = edit_article(&data.gamma.hostname, &edit_form).await?; let edit_res = edit_article(&data.gamma.hostname, &edit_form).await?;
assert_eq!(edit_form.new_text, edit_res.article.text); assert_eq!(edit_form.new_text, edit_res.article.text);
assert_eq!(3, edit_res.edits.len()); assert_eq!(3, edit_res.edits.len());
dbg!(4);
let conflicts: Vec<ApiConflict> = let conflicts: Vec<ApiConflict> =
get_query(&data.gamma.hostname, "edit_conflicts", None::<()>).await?; get_query(&data.gamma.hostname, "edit_conflicts", None::<()>).await?;
assert_eq!(0, conflicts.len()); assert_eq!(0, conflicts.len());
dbg!(5);
data.stop() data.stop()
} }