tests are running with temp db

This commit is contained in:
Felix Ableitner 2023-12-01 12:11:19 +01:00
parent 9ca2558b06
commit 7c789a1c06
11 changed files with 280 additions and 113 deletions

85
Cargo.lock generated
View File

@ -529,6 +529,17 @@ dependencies = [
"syn 2.0.39", "syn 2.0.39",
] ]
[[package]]
name = "diesel_migrations"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6036b3f0120c5961381b570ee20a02432d7e2d27ea60de9578799cf9156914ac"
dependencies = [
"diesel",
"migrations_internals",
"migrations_macros",
]
[[package]] [[package]]
name = "diesel_table_macro_syntax" name = "diesel_table_macro_syntax"
version = "0.1.0" version = "0.1.0"
@ -669,6 +680,7 @@ dependencies = [
"chrono", "chrono",
"diesel", "diesel",
"diesel-derive-newtype", "diesel-derive-newtype",
"diesel_migrations",
"diffy", "diffy",
"enum_delegate", "enum_delegate",
"env_logger", "env_logger",
@ -1194,6 +1206,27 @@ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "migrations_internals"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f23f71580015254b020e856feac3df5878c2c7a8812297edd6c0a485ac9dada"
dependencies = [
"serde",
"toml",
]
[[package]]
name = "migrations_macros"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cce3325ac70e67bbab5bd837a31cae01f1a6db64e0e744a33cb03a543469ef08"
dependencies = [
"migrations_internals",
"proc-macro2",
"quote",
]
[[package]] [[package]]
name = "mime" name = "mime"
version = "0.3.17" version = "0.3.17"
@ -1810,6 +1843,15 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_spanned"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "serde_urlencoded" name = "serde_urlencoded"
version = "0.7.1" version = "0.7.1"
@ -2100,6 +2142,40 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "toml"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.19.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
dependencies = [
"indexmap 2.1.0",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
]
[[package]] [[package]]
name = "tower" name = "tower"
version = "0.4.13" version = "0.4.13"
@ -2467,6 +2543,15 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "winnow"
version = "0.5.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "winreg" name = "winreg"
version = "0.50.0" version = "0.50.0"

View File

@ -12,6 +12,7 @@ axum-macros = "0.3.8"
chrono = { version = "0.4.31", features = ["serde"] } chrono = { version = "0.4.31", features = ["serde"] }
diesel = {version = "2.1.4", features = ["postgres"] } diesel = {version = "2.1.4", features = ["postgres"] }
diesel-derive-newtype = "2.1.0" diesel-derive-newtype = "2.1.0"
diesel_migrations = "2.1.0"
diffy = "0.3.0" diffy = "0.3.0"
enum_delegate = "0.2.0" enum_delegate = "0.2.0"
env_logger = { version = "0.10.1", default-features = false } env_logger = { version = "0.10.1", default-features = false }

View File

@ -2,7 +2,7 @@ create table article (
id serial primary key, id serial primary key,
title text not null, title text not null,
text text not null, text text not null,
ap_id varchar(255) not null, ap_id varchar(255) not null unique,
instance_id varchar(255) not null, instance_id varchar(255) not null,
latest_version text not null, latest_version text not null,
local bool not null local bool not null
@ -10,7 +10,7 @@ create table article (
create table edit ( create table edit (
id serial primary key, id serial primary key,
ap_id varchar(255) not null, ap_id varchar(255) not null unique,
diff text not null, diff text not null,
article_id int REFERENCES article ON UPDATE CASCADE ON DELETE CASCADE NOT NULL, article_id int REFERENCES article ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
version text not null, version text not null,

View File

@ -45,10 +45,12 @@ async fn create_article(
data: Data<MyDataHandle>, data: Data<MyDataHandle>,
Form(create_article): Form<CreateArticleData>, Form(create_article): Form<CreateArticleData>,
) -> MyResult<Json<DbArticle>> { ) -> MyResult<Json<DbArticle>> {
dbg!(1);
let existing_article = DbArticle::read_local_title(&create_article.title, &data.db_connection); let existing_article = DbArticle::read_local_title(&create_article.title, &data.db_connection);
if existing_article.is_ok() { if existing_article.is_ok() {
return Err(anyhow!("A local article with this title already exists").into()); return Err(anyhow!("A local article with this title already exists").into());
} }
dbg!(2);
let instance_id = data.local_instance().ap_id; let instance_id = data.local_instance().ap_id;
let ap_id = ObjectId::parse(&format!( let ap_id = ObjectId::parse(&format!(
@ -66,9 +68,12 @@ async fn create_article(
instance_id, instance_id,
local: true, local: true,
}; };
let article = DbArticle::create(&form, &data.db_connection)?; dbg!(3);
let article = dbg!(DbArticle::create(&form, &data.db_connection))?;
dbg!(4);
CreateArticle::send_to_followers(article.clone(), &data).await?; CreateArticle::send_to_followers(article.clone(), &data).await?;
dbg!(5);
Ok(Json(article)) Ok(Json(article))
} }

View File

@ -1,8 +1,6 @@
use crate::database::{FakeDatabase, MyData, MyDataHandle}; use crate::database::FakeDatabase;
use crate::error::Error; use crate::error::Error;
use crate::establish_db_connection;
use crate::federation::objects::instance::DbInstance; use crate::federation::objects::instance::DbInstance;
use activitypub_federation::config::FederationConfig;
use activitypub_federation::fetch::collection_id::CollectionId; use activitypub_federation::fetch::collection_id::CollectionId;
use activitypub_federation::http_signatures::generate_actor_keypair; use activitypub_federation::http_signatures::generate_actor_keypair;
use chrono::Local; use chrono::Local;
@ -14,7 +12,7 @@ pub mod activities;
pub mod objects; pub mod objects;
pub mod routes; pub mod routes;
pub async fn federation_config(hostname: &str) -> Result<FederationConfig<MyDataHandle>, Error> { pub async fn create_fake_db(hostname: &str) -> Result<Arc<FakeDatabase>, Error> {
let ap_id = Url::parse(&format!("http://{}", hostname))?; let ap_id = Url::parse(&format!("http://{}", hostname))?;
let articles_id = CollectionId::parse(&format!("http://{}/all_articles", hostname))?; let articles_id = CollectionId::parse(&format!("http://{}/all_articles", hostname))?;
let inbox = Url::parse(&format!("http://{}/inbox", hostname))?; let inbox = Url::parse(&format!("http://{}/inbox", hostname))?;
@ -37,16 +35,5 @@ pub async fn federation_config(hostname: &str) -> Result<FederationConfig<MyData
)])), )])),
conflicts: Mutex::new(vec![]), conflicts: Mutex::new(vec![]),
}); });
let db_connection = Arc::new(Mutex::new(establish_db_connection()?)); Ok(fake_db)
let data = MyData {
db_connection,
fake_db,
};
let config = FederationConfig::builder()
.domain(hostname)
.app_data(data)
.debug(true)
.build()
.await?;
Ok(config)
} }

View File

@ -1,13 +1,18 @@
use crate::api::api_routes; use crate::api::api_routes;
use crate::database::MyData;
use crate::error::MyResult; use crate::error::MyResult;
use crate::federation::routes::federation_routes; use crate::federation::routes::federation_routes;
use crate::utils::generate_activity_id; use crate::utils::generate_activity_id;
use activitypub_federation::config::FederationMiddleware; use activitypub_federation::config::{FederationConfig, FederationMiddleware};
use axum::{Router, Server}; use axum::{Router, Server};
use diesel::Connection; use diesel::Connection;
use diesel::PgConnection; use diesel::PgConnection;
use federation::federation_config; use diesel_migrations::embed_migrations;
use diesel_migrations::EmbeddedMigrations;
use diesel_migrations::MigrationHarness;
use federation::create_fake_db;
use std::net::ToSocketAddrs; use std::net::ToSocketAddrs;
use std::sync::{Arc, Mutex};
use tracing::info; use tracing::info;
pub mod api; pub mod api;
@ -16,8 +21,29 @@ pub mod error;
pub mod federation; pub mod federation;
mod utils; mod utils;
pub async fn start(hostname: &str) -> MyResult<()> { const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations");
let config = federation_config(hostname).await?;
pub async fn start(hostname: &str, database_url: &str) -> MyResult<()> {
let fake_db = create_fake_db(hostname).await?;
dbg!(database_url);
let db_connection = Arc::new(Mutex::new(PgConnection::establish(database_url)?));
db_connection
.lock()
.unwrap()
.run_pending_migrations(MIGRATIONS)
.unwrap();
let data = MyData {
db_connection,
fake_db,
};
let config = FederationConfig::builder()
.domain(hostname)
.app_data(data)
.debug(true)
.build()
.await?;
info!("Listening with axum on {hostname}"); info!("Listening with axum on {hostname}");
let config = config.clone(); let config = config.clone();
@ -36,9 +62,3 @@ pub async fn start(hostname: &str) -> MyResult<()> {
Ok(()) Ok(())
} }
pub fn establish_db_connection() -> MyResult<PgConnection> {
// TODO: read from config file
let database_url = "postgres://fediwiki:password@localhost:5432/fediwiki";
Ok(PgConnection::establish(&database_url)?)
}

View File

@ -9,6 +9,7 @@ pub async fn main() -> MyResult<()> {
.filter_module("activitypub_federation", LevelFilter::Info) .filter_module("activitypub_federation", LevelFilter::Info)
.filter_module("fediwiki", LevelFilter::Info) .filter_module("fediwiki", LevelFilter::Info)
.init(); .init();
start("localhost:8131").await?; let database_url = "postgres://fediwiki:password@localhost:5432/fediwiki";
start("localhost:8131", &database_url).await?;
Ok(()) Ok(())
} }

View File

@ -9,6 +9,8 @@ use once_cell::sync::Lazy;
use reqwest::Client; use reqwest::Client;
use serde::de::Deserialize; use serde::de::Deserialize;
use serde::ser::Serialize; use serde::ser::Serialize;
use std::env::current_dir;
use std::process::{Command, Stdio};
use std::sync::Once; use std::sync::Once;
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
use tracing::log::LevelFilter; use tracing::log::LevelFilter;
@ -17,12 +19,9 @@ use url::Url;
pub static CLIENT: Lazy<Client> = Lazy::new(Client::new); pub static CLIENT: Lazy<Client> = Lazy::new(Client::new);
pub struct TestData { pub struct TestData {
pub hostname_alpha: &'static str, pub alpha: Instance,
pub hostname_beta: &'static str, pub beta: Instance,
pub hostname_gamma: &'static str, pub gamma: Instance,
handle_alpha: JoinHandle<()>,
handle_beta: JoinHandle<()>,
handle_gamma: JoinHandle<()>,
} }
impl TestData { impl TestData {
@ -36,36 +35,61 @@ impl TestData {
.init(); .init();
}); });
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 { Self {
hostname_alpha, alpha: Instance::start("alpha", 8131),
hostname_beta, beta: Instance::start("beta", 8132),
hostname_gamma, gamma: Instance::start("gamma", 8133),
handle_alpha,
handle_beta,
handle_gamma,
} }
} }
pub fn stop(self) -> MyResult<()> { pub fn stop(self) -> MyResult<()> {
self.handle_alpha.abort(); self.alpha.stop();
self.handle_beta.abort(); self.beta.stop();
self.handle_gamma.abort(); self.gamma.stop();
Ok(()) Ok(())
} }
} }
pub struct Instance {
db_path: String,
pub hostname: String,
handle: JoinHandle<()>,
}
impl Instance {
fn start(name: &'static str, port: i32) -> Self {
let db_path = format!("{}/target/test_db/{name}", current_dir().unwrap().display());
// TODO: would be faster to use async Command from tokio and run in parallel
Command::new("./tests/scripts/start_dev_db.sh")
.arg(&db_path)
.stdout(Stdio::null())
.stderr(Stdio::null())
.output()
.unwrap();
let db_url = format!("postgresql://lemmy:password@/lemmy?host={db_path}");
let hostname = format!("localhost:{port}");
let hostname_ = hostname.clone();
let handle = tokio::task::spawn(async move {
start(&hostname_, &db_url).await.unwrap();
});
Self {
db_path,
hostname,
handle,
}
}
fn stop(self) {
self.handle.abort();
Command::new("./tests/scripts/stop_dev_db.sh")
.arg(&self.db_path)
.stdout(Stdio::null())
.stderr(Stdio::null())
.output()
.unwrap();
}
}
pub const TEST_ARTICLE_DEFAULT_TEXT: &str = "some\nexample\ntext\n"; pub const TEST_ARTICLE_DEFAULT_TEXT: &str = "some\nexample\ntext\n";
pub async fn create_article(hostname: &str, title: String) -> MyResult<ArticleView> { pub async fn create_article(hostname: &str, title: String) -> MyResult<ArticleView> {

26
tests/scripts/start_dev_db.sh Executable file
View File

@ -0,0 +1,26 @@
#!/bin/bash
set -e
export PGHOST=$1
export PGDATA="$1/dev_pgdata"
# If cluster exists, stop the server and delete the cluster
if [ -d $PGDATA ]
then
# Prevent `stop` from failing if server already stopped
pg_ctl restart > /dev/null
pg_ctl stop
rm -rf $PGDATA
fi
# Create cluster
initdb --username=postgres --auth=trust --no-instructions
#touch "$PGHOST/.s.PGSQL.5432"
# Start server that only listens to socket in current directory
pg_ctl start --options="-c listen_addresses= -c unix_socket_directories=$PGHOST"
# Setup database
psql -c "CREATE USER lemmy WITH PASSWORD 'password' SUPERUSER;" -U postgres
psql -c "CREATE DATABASE lemmy WITH OWNER lemmy;" -U postgres

9
tests/scripts/stop_dev_db.sh Executable file
View File

@ -0,0 +1,9 @@
#!/bin/bash
set -e
export PGHOST=$1
export PGDATA="$1/dev_pgdata"
echo $PGHOST
pg_ctl stop
rm -rf $PGDATA

View File

@ -24,18 +24,18 @@ async fn test_create_read_and_edit_article() -> MyResult<()> {
// create article // create article
let title = "Manu_Chao".to_string(); let title = "Manu_Chao".to_string();
let create_res = create_article(data.hostname_alpha, title.clone()).await?; let create_res = create_article(&data.alpha.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);
// now article can be read // now article can be read
let get_res = get_article(data.hostname_alpha, create_res.article.id).await?; let get_res = get_article(&data.alpha.hostname, create_res.article.id).await?;
assert_eq!(title, get_res.article.title); assert_eq!(title, get_res.article.title);
assert_eq!(TEST_ARTICLE_DEFAULT_TEXT, get_res.article.text); assert_eq!(TEST_ARTICLE_DEFAULT_TEXT, get_res.article.text);
assert!(get_res.article.local); assert!(get_res.article.local);
// error on article which wasnt federated // error on article which wasnt federated
let not_found = get_article(data.hostname_beta, create_res.article.id).await; let not_found = get_article(&data.beta.hostname, create_res.article.id).await;
assert!(not_found.is_err()); assert!(not_found.is_err());
// edit article // edit article
@ -45,7 +45,7 @@ async fn test_create_read_and_edit_article() -> MyResult<()> {
previous_version: get_res.article.latest_version, previous_version: get_res.article.latest_version,
resolve_conflict_id: None, resolve_conflict_id: None,
}; };
let edit_res = edit_article(data.hostname_alpha, &edit_form).await?; let edit_res = edit_article(&data.alpha.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!(2, edit_res.edits.len()); assert_eq!(2, edit_res.edits.len());
@ -53,7 +53,7 @@ async fn test_create_read_and_edit_article() -> MyResult<()> {
query: title.clone(), query: title.clone(),
}; };
let search_res: Vec<DbArticle> = let search_res: Vec<DbArticle> =
get_query(data.hostname_alpha, "search", Some(search_form)).await?; get_query(&data.alpha.hostname, "search", Some(search_form)).await?;
assert_eq!(1, search_res.len()); assert_eq!(1, search_res.len());
assert_eq!(edit_res.article, search_res[0]); assert_eq!(edit_res.article, search_res[0]);
@ -67,11 +67,11 @@ async fn test_create_duplicate_article() -> MyResult<()> {
// create article // create article
let title = "Manu_Chao".to_string(); let title = "Manu_Chao".to_string();
let create_res = create_article(data.hostname_alpha, title.clone()).await?; let create_res = create_article(&data.alpha.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);
let create_res = create_article(data.hostname_alpha, title.clone()).await; let create_res = create_article(&data.alpha.hostname, title.clone()).await;
assert!(create_res.is_err()); assert!(create_res.is_err());
data.stop() data.stop()
@ -83,22 +83,23 @@ async fn test_follow_instance() -> MyResult<()> {
let data = TestData::start(); let data = TestData::start();
// check initial state // check initial state
let alpha_instance: DbInstance = get(data.hostname_alpha, "instance").await?; let alpha_instance: DbInstance = get(&data.alpha.hostname, "instance").await?;
assert_eq!(0, alpha_instance.follows.len()); assert_eq!(0, alpha_instance.follows.len());
let beta_instance: DbInstance = get(data.hostname_beta, "instance").await?; let beta_instance: DbInstance = get(&data.beta.hostname, "instance").await?;
assert_eq!(0, beta_instance.followers.len()); assert_eq!(0, beta_instance.followers.len());
follow_instance(data.hostname_alpha, data.hostname_beta).await?; follow_instance(&data.alpha.hostname, &data.beta.hostname).await?;
// check that follow was federated // check that follow was federated
let beta_instance: DbInstance = get(data.hostname_beta, "instance").await?; let beta_instance: DbInstance = get(&data.beta.hostname, "instance").await?;
assert_eq!(1, beta_instance.followers.len()); assert_eq!(1, beta_instance.followers.len());
let alpha_instance: DbInstance = get(data.hostname_alpha, "instance").await?; let alpha_instance: DbInstance = get(&data.alpha.hostname, "instance").await?;
assert_eq!(1, alpha_instance.follows.len()); assert_eq!(1, alpha_instance.follows.len());
data.stop() data.stop()
} }
#[tokio::test] #[tokio::test]
#[serial] #[serial]
async fn test_synchronize_articles() -> MyResult<()> { async fn test_synchronize_articles() -> MyResult<()> {
@ -106,7 +107,7 @@ async fn test_synchronize_articles() -> MyResult<()> {
// create article on alpha // create article on alpha
let title = "Manu_Chao".to_string(); let title = "Manu_Chao".to_string();
let create_res = create_article(data.hostname_alpha, title.clone()).await?; let create_res = create_article(&data.alpha.hostname, title.clone()).await?;
assert_eq!(title, create_res.article.title); assert_eq!(title, create_res.article.title);
assert_eq!(1, create_res.edits.len()); assert_eq!(1, create_res.edits.len());
assert!(create_res.article.local); assert!(create_res.article.local);
@ -118,21 +119,25 @@ async fn test_synchronize_articles() -> MyResult<()> {
previous_version: create_res.article.latest_version, previous_version: create_res.article.latest_version,
resolve_conflict_id: None, resolve_conflict_id: None,
}; };
edit_article(data.hostname_alpha, &edit_form).await?; edit_article(&data.alpha.hostname, &edit_form).await?;
// article is not yet on beta // article is not yet on beta
let get_res = get_article(data.hostname_beta, create_res.article.id).await; let get_res = get_article(&data.beta.hostname, create_res.article.id).await;
assert!(get_res.is_err()); assert!(get_res.is_err());
// fetch alpha instance on beta, articles are also fetched automatically // fetch alpha instance on beta, articles are also fetched automatically
let resolve_object = ResolveObject { let resolve_object = ResolveObject {
id: Url::parse(&format!("http://{}", data.hostname_alpha))?, id: Url::parse(&format!("http://{}", &data.alpha.hostname))?,
}; };
get_query::<DbInstance, _>(data.hostname_beta, "resolve_instance", Some(resolve_object)) get_query::<DbInstance, _>(
.await?; &data.beta.hostname,
"resolve_instance",
Some(resolve_object),
)
.await?;
// get the article and compare // get the article and compare
let get_res = get_article(data.hostname_beta, create_res.article.id).await?; let get_res = get_article(&data.beta.hostname, create_res.article.id).await?;
assert_eq!(create_res.article.ap_id, get_res.article.ap_id); assert_eq!(create_res.article.ap_id, get_res.article.ap_id);
assert_eq!(title, get_res.article.title); assert_eq!(title, get_res.article.title);
assert_eq!(2, get_res.edits.len()); assert_eq!(2, get_res.edits.len());
@ -147,16 +152,16 @@ async fn test_synchronize_articles() -> MyResult<()> {
async fn test_edit_local_article() -> MyResult<()> { async fn test_edit_local_article() -> MyResult<()> {
let data = TestData::start(); let data = TestData::start();
follow_instance(data.hostname_alpha, data.hostname_beta).await?; follow_instance(&data.alpha.hostname, &data.beta.hostname).await?;
// create new article // create new article
let title = "Manu_Chao".to_string(); let title = "Manu_Chao".to_string();
let create_res = create_article(data.hostname_beta, 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);
// article should be federated to alpha // article should be federated to alpha
let get_res = get_article(data.hostname_alpha, create_res.article.id).await?; let get_res = get_article(&data.alpha.hostname, create_res.article.id).await?;
assert_eq!(create_res.article.title, get_res.article.title); assert_eq!(create_res.article.title, get_res.article.title);
assert_eq!(1, get_res.edits.len()); assert_eq!(1, get_res.edits.len());
assert!(!get_res.article.local); assert!(!get_res.article.local);
@ -169,7 +174,7 @@ async fn test_edit_local_article() -> MyResult<()> {
previous_version: get_res.article.latest_version, previous_version: get_res.article.latest_version,
resolve_conflict_id: None, resolve_conflict_id: None,
}; };
let edit_res = edit_article(data.hostname_beta, &edit_form).await?; let edit_res = edit_article(&data.beta.hostname, &edit_form).await?;
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]
@ -178,7 +183,7 @@ async fn test_edit_local_article() -> MyResult<()> {
.starts_with(&edit_res.article.ap_id.to_string())); .starts_with(&edit_res.article.ap_id.to_string()));
// edit should be federated to alpha // edit should be federated to alpha
let get_res = get_article(data.hostname_alpha, edit_res.article.id).await?; let get_res = get_article(&data.alpha.hostname, edit_res.article.id).await?;
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);
@ -191,22 +196,22 @@ async fn test_edit_local_article() -> MyResult<()> {
async fn test_edit_remote_article() -> MyResult<()> { async fn test_edit_remote_article() -> MyResult<()> {
let data = TestData::start(); let data = TestData::start();
follow_instance(data.hostname_alpha, data.hostname_beta).await?; follow_instance(&data.alpha.hostname, &data.beta.hostname).await?;
follow_instance(data.hostname_gamma, data.hostname_beta).await?; follow_instance(&data.gamma.hostname, &data.beta.hostname).await?;
// create new article // create new article
let title = "Manu_Chao".to_string(); let title = "Manu_Chao".to_string();
let create_res = create_article(data.hostname_beta, 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);
// article should be federated to alpha and gamma // article should be federated to alpha and gamma
let get_res = get_article(data.hostname_alpha, create_res.article.id).await?; let get_res = get_article(&data.alpha.hostname, create_res.article.id).await?;
assert_eq!(create_res.article.title, get_res.article.title); assert_eq!(create_res.article.title, get_res.article.title);
assert_eq!(1, get_res.edits.len()); assert_eq!(1, get_res.edits.len());
assert!(!get_res.article.local); assert!(!get_res.article.local);
let get_res = get_article(data.hostname_gamma, create_res.article.id).await?; let get_res = get_article(&data.gamma.hostname, create_res.article.id).await?;
assert_eq!(create_res.article.title, get_res.article.title); assert_eq!(create_res.article.title, get_res.article.title);
assert_eq!(create_res.article.text, get_res.article.text); assert_eq!(create_res.article.text, get_res.article.text);
@ -216,7 +221,7 @@ async fn test_edit_remote_article() -> MyResult<()> {
previous_version: get_res.article.latest_version, previous_version: get_res.article.latest_version,
resolve_conflict_id: None, resolve_conflict_id: None,
}; };
let edit_res = edit_article(data.hostname_alpha, &edit_form).await?; let edit_res = edit_article(&data.alpha.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!(2, edit_res.edits.len()); assert_eq!(2, edit_res.edits.len());
assert!(!edit_res.article.local); assert!(!edit_res.article.local);
@ -226,12 +231,12 @@ async fn test_edit_remote_article() -> MyResult<()> {
.starts_with(&edit_res.article.ap_id.to_string())); .starts_with(&edit_res.article.ap_id.to_string()));
// edit should be federated to beta and gamma // edit should be federated to beta and gamma
let get_res = get_article(data.hostname_alpha, create_res.article.id).await?; let get_res = get_article(&data.alpha.hostname, create_res.article.id).await?;
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);
let get_res = get_article(data.hostname_gamma, create_res.article.id).await?; let get_res = get_article(&data.gamma.hostname, create_res.article.id).await?;
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);
@ -246,7 +251,7 @@ async fn test_local_edit_conflict() -> MyResult<()> {
// create new article // create new article
let title = "Manu_Chao".to_string(); let title = "Manu_Chao".to_string();
let create_res = create_article(data.hostname_alpha, title.clone()).await?; let create_res = create_article(&data.alpha.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);
@ -257,7 +262,7 @@ async fn test_local_edit_conflict() -> MyResult<()> {
previous_version: create_res.article.latest_version.clone(), previous_version: create_res.article.latest_version.clone(),
resolve_conflict_id: None, resolve_conflict_id: None,
}; };
let edit_res = edit_article(data.hostname_alpha, &edit_form).await?; let edit_res = edit_article(&data.alpha.hostname, &edit_form).await?;
assert_eq!(edit_res.article.text, edit_form.new_text); assert_eq!(edit_res.article.text, edit_form.new_text);
assert_eq!(2, edit_res.edits.len()); assert_eq!(2, edit_res.edits.len());
@ -268,13 +273,13 @@ async fn test_local_edit_conflict() -> MyResult<()> {
previous_version: create_res.article.latest_version, previous_version: create_res.article.latest_version,
resolve_conflict_id: None, resolve_conflict_id: None,
}; };
let edit_res = edit_article_with_conflict(data.hostname_alpha, &edit_form) let edit_res = edit_article_with_conflict(&data.alpha.hostname, &edit_form)
.await? .await?
.unwrap(); .unwrap();
assert_eq!("<<<<<<< ours\nIpsum Lorem\n||||||| original\nsome\nexample\ntext\n=======\nLorem Ipsum\n>>>>>>> theirs\n", edit_res.three_way_merge); assert_eq!("<<<<<<< ours\nIpsum Lorem\n||||||| original\nsome\nexample\ntext\n=======\nLorem Ipsum\n>>>>>>> theirs\n", edit_res.three_way_merge);
let conflicts: Vec<ApiConflict> = let conflicts: Vec<ApiConflict> =
get_query(data.hostname_alpha, "edit_conflicts", None::<()>).await?; get_query(&data.alpha.hostname, "edit_conflicts", None::<()>).await?;
assert_eq!(1, conflicts.len()); assert_eq!(1, conflicts.len());
assert_eq!(conflicts[0], edit_res); assert_eq!(conflicts[0], edit_res);
@ -284,11 +289,11 @@ async fn test_local_edit_conflict() -> MyResult<()> {
previous_version: edit_res.previous_version, previous_version: edit_res.previous_version,
resolve_conflict_id: Some(edit_res.id), resolve_conflict_id: Some(edit_res.id),
}; };
let edit_res = edit_article(data.hostname_alpha, &edit_form).await?; let edit_res = edit_article(&data.alpha.hostname, &edit_form).await?;
assert_eq!(edit_form.new_text, edit_res.article.text); assert_eq!(edit_form.new_text, edit_res.article.text);
let conflicts: Vec<ApiConflict> = let conflicts: Vec<ApiConflict> =
get_query(data.hostname_alpha, "edit_conflicts", None::<()>).await?; get_query(&data.alpha.hostname, "edit_conflicts", None::<()>).await?;
assert_eq!(0, conflicts.len()); assert_eq!(0, conflicts.len());
data.stop() data.stop()
@ -299,11 +304,11 @@ async fn test_local_edit_conflict() -> MyResult<()> {
async fn test_federated_edit_conflict() -> MyResult<()> { async fn test_federated_edit_conflict() -> MyResult<()> {
let data = TestData::start(); let data = TestData::start();
follow_instance(data.hostname_alpha, data.hostname_beta).await?; follow_instance(&data.alpha.hostname, &data.beta.hostname).await?;
// create new article // create new article
let title = "Manu_Chao".to_string(); let title = "Manu_Chao".to_string();
let create_res = create_article(data.hostname_beta, 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);
@ -311,8 +316,12 @@ async fn test_federated_edit_conflict() -> MyResult<()> {
let resolve_object = ResolveObject { let resolve_object = ResolveObject {
id: create_res.article.ap_id.inner().clone(), id: create_res.article.ap_id.inner().clone(),
}; };
let resolve_res: DbArticle = let resolve_res: DbArticle = get_query(
get_query(data.hostname_gamma, "resolve_article", Some(resolve_object)).await?; &data.gamma.hostname,
"resolve_article",
Some(resolve_object),
)
.await?;
assert_eq!(create_res.article.text, resolve_res.text); assert_eq!(create_res.article.text, resolve_res.text);
// alpha edits article // alpha edits article
@ -322,7 +331,7 @@ async fn test_federated_edit_conflict() -> MyResult<()> {
previous_version: create_res.article.latest_version.clone(), previous_version: create_res.article.latest_version.clone(),
resolve_conflict_id: None, resolve_conflict_id: None,
}; };
let edit_res = edit_article(data.hostname_alpha, &edit_form).await?; let edit_res = edit_article(&data.alpha.hostname, &edit_form).await?;
assert_eq!(edit_res.article.text, edit_form.new_text); assert_eq!(edit_res.article.text, edit_form.new_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);
@ -339,13 +348,13 @@ async fn test_federated_edit_conflict() -> MyResult<()> {
previous_version: create_res.article.latest_version, previous_version: create_res.article.latest_version,
resolve_conflict_id: None, resolve_conflict_id: None,
}; };
let edit_res = edit_article(data.hostname_gamma, &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);
let conflicts: Vec<ApiConflict> = let conflicts: Vec<ApiConflict> =
get_query(data.hostname_gamma, "edit_conflicts", None::<()>).await?; get_query(&data.gamma.hostname, "edit_conflicts", None::<()>).await?;
assert_eq!(1, conflicts.len()); assert_eq!(1, conflicts.len());
// resolve the conflict // resolve the conflict
@ -355,12 +364,12 @@ 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),
}; };
let edit_res = edit_article(data.hostname_gamma, &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());
let conflicts: Vec<ApubEdit> = let conflicts: Vec<ApubEdit> =
get_query(data.hostname_gamma, "edit_conflicts", None::<()>).await?; get_query(&data.gamma.hostname, "edit_conflicts", None::<()>).await?;
assert_eq!(0, conflicts.len()); assert_eq!(0, conflicts.len());
data.stop() data.stop()
@ -373,7 +382,7 @@ async fn test_overlapping_edits_no_conflict() -> MyResult<()> {
// create new article // create new article
let title = "Manu_Chao".to_string(); let title = "Manu_Chao".to_string();
let create_res = create_article(data.hostname_alpha, title.clone()).await?; let create_res = create_article(&data.alpha.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);
@ -384,7 +393,7 @@ async fn test_overlapping_edits_no_conflict() -> MyResult<()> {
previous_version: create_res.article.latest_version.clone(), previous_version: create_res.article.latest_version.clone(),
resolve_conflict_id: None, resolve_conflict_id: None,
}; };
let edit_res = edit_article(data.hostname_alpha, &edit_form).await?; let edit_res = edit_article(&data.alpha.hostname, &edit_form).await?;
assert_eq!(edit_res.article.text, edit_form.new_text); assert_eq!(edit_res.article.text, edit_form.new_text);
assert_eq!(2, edit_res.edits.len()); assert_eq!(2, edit_res.edits.len());
@ -395,9 +404,9 @@ async fn test_overlapping_edits_no_conflict() -> MyResult<()> {
previous_version: create_res.article.latest_version, previous_version: create_res.article.latest_version,
resolve_conflict_id: None, resolve_conflict_id: None,
}; };
let edit_res = edit_article(data.hostname_alpha, &edit_form).await?; let edit_res = edit_article(&data.alpha.hostname, &edit_form).await?;
let conflicts: Vec<ApiConflict> = let conflicts: Vec<ApiConflict> =
get_query(data.hostname_alpha, "edit_conflicts", None::<()>).await?; get_query(&data.alpha.hostname, "edit_conflicts", None::<()>).await?;
assert_eq!(0, conflicts.len()); assert_eq!(0, conflicts.len());
assert_eq!(3, edit_res.edits.len()); assert_eq!(3, edit_res.edits.len());
assert_eq!("my\nexample\narticle\n", edit_res.article.text); assert_eq!("my\nexample\narticle\n", edit_res.article.text);
@ -412,7 +421,7 @@ async fn test_fork_article() -> MyResult<()> {
// create article // create article
let title = "Manu_Chao".to_string(); let title = "Manu_Chao".to_string();
let create_res = create_article(data.hostname_alpha, title.clone()).await?; let create_res = create_article(&data.alpha.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);
@ -421,7 +430,7 @@ async fn test_fork_article() -> MyResult<()> {
id: create_res.article.ap_id.into_inner(), id: create_res.article.ap_id.into_inner(),
}; };
let resolve_res: ArticleView = let resolve_res: ArticleView =
get_query(data.hostname_beta, "resolve_article", Some(resolve_object)).await?; get_query(&data.beta.hostname, "resolve_article", Some(resolve_object)).await?;
let resolved_article = resolve_res.article; let resolved_article = resolve_res.article;
assert_eq!(create_res.edits.len(), resolve_res.edits.len()); assert_eq!(create_res.edits.len(), resolve_res.edits.len());
@ -429,7 +438,7 @@ async fn test_fork_article() -> MyResult<()> {
let fork_form = ForkArticleData { let fork_form = ForkArticleData {
article_id: resolved_article.id, article_id: resolved_article.id,
}; };
let fork_res: ArticleView = post(data.hostname_beta, "article/fork", &fork_form).await?; let fork_res: ArticleView = post(&data.beta.hostname, "article/fork", &fork_form).await?;
let forked_article = fork_res.article; let forked_article = fork_res.article;
assert_eq!(resolved_article.title, forked_article.title); assert_eq!(resolved_article.title, forked_article.title);
assert_eq!(resolved_article.text, forked_article.text); assert_eq!(resolved_article.text, forked_article.text);
@ -441,7 +450,7 @@ async fn test_fork_article() -> MyResult<()> {
assert_ne!(resolved_article.ap_id, forked_article.ap_id); assert_ne!(resolved_article.ap_id, forked_article.ap_id);
assert!(forked_article.local); assert!(forked_article.local);
let beta_instance: DbInstance = get(data.hostname_beta, "instance").await?; let beta_instance: DbInstance = get(&data.beta.hostname, "instance").await?;
assert_eq!(forked_article.instance_id, beta_instance.ap_id); assert_eq!(forked_article.instance_id, beta_instance.ap_id);
// now search returns two articles for this title (original and forked) // now search returns two articles for this title (original and forked)
@ -449,7 +458,7 @@ async fn test_fork_article() -> MyResult<()> {
query: title.clone(), query: title.clone(),
}; };
let search_res: Vec<DbArticle> = let search_res: Vec<DbArticle> =
get_query(data.hostname_beta, "search", Some(search_form)).await?; get_query(&data.beta.hostname, "search", Some(search_form)).await?;
assert_eq!(2, search_res.len()); assert_eq!(2, search_res.len());
data.stop() data.stop()