mirror of
https://github.com/Nutomic/ibis.git
synced 2024-11-25 20:01:09 +00:00
require auth for create/edit article endpoints
This commit is contained in:
parent
f8da5ae965
commit
b72cc51814
9 changed files with 144 additions and 59 deletions
|
@ -2,6 +2,7 @@ use crate::database::article::{ArticleView, DbArticle, DbArticleForm};
|
|||
use crate::database::conflict::{ApiConflict, DbConflict, DbConflictForm};
|
||||
use crate::database::edit::{DbEdit, DbEditForm};
|
||||
use crate::database::instance::DbInstance;
|
||||
use crate::database::user::LocalUserView;
|
||||
use crate::database::version::EditVersion;
|
||||
use crate::database::MyDataHandle;
|
||||
use crate::error::MyResult;
|
||||
|
@ -11,6 +12,7 @@ use crate::utils::generate_article_version;
|
|||
use activitypub_federation::config::Data;
|
||||
use activitypub_federation::fetch::object_id::ObjectId;
|
||||
use axum::extract::Query;
|
||||
use axum::Extension;
|
||||
use axum::Form;
|
||||
use axum::Json;
|
||||
use axum_macros::debug_handler;
|
||||
|
@ -25,6 +27,7 @@ pub struct CreateArticleData {
|
|||
/// Create a new article with empty text, and federate it to followers.
|
||||
#[debug_handler]
|
||||
pub(in crate::api) async fn create_article(
|
||||
Extension(_user): Extension<LocalUserView>,
|
||||
data: Data<MyDataHandle>,
|
||||
Form(create_article): Form<CreateArticleData>,
|
||||
) -> MyResult<Json<ArticleView>> {
|
||||
|
@ -74,6 +77,7 @@ pub struct EditArticleData {
|
|||
/// Conflicts are stored in the database so they can be retrieved later from `/api/v3/edit_conflicts`.
|
||||
#[debug_handler]
|
||||
pub(in crate::api) async fn edit_article(
|
||||
Extension(_user): Extension<LocalUserView>,
|
||||
data: Data<MyDataHandle>,
|
||||
Form(edit_form): Form<EditArticleData>,
|
||||
) -> MyResult<Json<Option<ApiConflict>>> {
|
||||
|
|
|
@ -4,6 +4,7 @@ use crate::api::instance::follow_instance;
|
|||
use crate::api::instance::get_local_instance;
|
||||
use crate::api::user::login_user;
|
||||
use crate::api::user::register_user;
|
||||
use crate::api::user::validate;
|
||||
use crate::database::article::{ArticleView, DbArticle};
|
||||
use crate::database::conflict::{ApiConflict, DbConflict};
|
||||
use crate::database::edit::DbEdit;
|
||||
|
@ -14,10 +15,19 @@ use activitypub_federation::config::Data;
|
|||
use activitypub_federation::fetch::object_id::ObjectId;
|
||||
use axum::extract::Query;
|
||||
use axum::routing::{get, post};
|
||||
use axum::{
|
||||
extract::TypedHeader,
|
||||
headers::authorization::{Authorization, Bearer},
|
||||
http::Request,
|
||||
http::StatusCode,
|
||||
middleware::{self, Next},
|
||||
response::Response,
|
||||
};
|
||||
use axum::{Json, Router};
|
||||
use axum_macros::debug_handler;
|
||||
use futures::future::try_join_all;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::warn;
|
||||
use url::Url;
|
||||
|
||||
pub mod article;
|
||||
|
@ -39,6 +49,24 @@ pub fn api_routes() -> Router {
|
|||
.route("/search", get(search_article))
|
||||
.route("/user/register", post(register_user))
|
||||
.route("/user/login", post(login_user))
|
||||
.route_layer(middleware::from_fn(auth))
|
||||
}
|
||||
|
||||
async fn auth<B>(
|
||||
data: Data<MyDataHandle>,
|
||||
auth: Option<TypedHeader<Authorization<Bearer>>>,
|
||||
mut request: Request<B>,
|
||||
next: Next<B>,
|
||||
) -> Result<Response, StatusCode> {
|
||||
if let Some(auth) = auth {
|
||||
let user = validate(auth.token(), &data).await.map_err(|e| {
|
||||
warn!("Failed to validate auth token: {e}");
|
||||
StatusCode::UNAUTHORIZED
|
||||
})?;
|
||||
request.extensions_mut().insert(user);
|
||||
}
|
||||
let response = next.run(request).await;
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::database::user::{DbLocalUser, DbPerson};
|
||||
use crate::database::user::{DbLocalUser, DbPerson, LocalUserView};
|
||||
use crate::database::MyDataHandle;
|
||||
use crate::error::MyResult;
|
||||
use activitypub_federation::config::Data;
|
||||
|
@ -7,6 +7,9 @@ use axum::{Form, Json};
|
|||
use axum_macros::debug_handler;
|
||||
use bcrypt::verify;
|
||||
use chrono::Utc;
|
||||
use jsonwebtoken::DecodingKey;
|
||||
use jsonwebtoken::Validation;
|
||||
use jsonwebtoken::{decode, get_current_timestamp};
|
||||
use jsonwebtoken::{encode, EncodingKey, Header};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
@ -18,8 +21,13 @@ pub struct Claims {
|
|||
pub iss: String,
|
||||
/// Creation time as unix timestamp
|
||||
pub iat: i64,
|
||||
/// Expiration time
|
||||
pub exp: u64,
|
||||
}
|
||||
|
||||
// TODO: move to config
|
||||
const SECRET: &[u8] = "secret".as_bytes();
|
||||
|
||||
pub(in crate::api) fn generate_login_token(
|
||||
local_user: DbLocalUser,
|
||||
data: &Data<MyDataHandle>,
|
||||
|
@ -29,14 +37,21 @@ pub(in crate::api) fn generate_login_token(
|
|||
sub: local_user.id.to_string(),
|
||||
iss: hostname,
|
||||
iat: Utc::now().timestamp(),
|
||||
exp: get_current_timestamp(),
|
||||
};
|
||||
|
||||
// TODO: move to config
|
||||
let key = EncodingKey::from_secret("secret".as_bytes());
|
||||
let key = EncodingKey::from_secret(SECRET);
|
||||
let jwt = encode(&Header::default(), &claims, &key)?;
|
||||
Ok(LoginResponse { jwt })
|
||||
}
|
||||
|
||||
pub async fn validate(jwt: &str, data: &Data<MyDataHandle>) -> MyResult<LocalUserView> {
|
||||
let validation = Validation::default();
|
||||
let key = DecodingKey::from_secret(SECRET);
|
||||
let claims = decode::<Claims>(jwt, &key, &validation)?;
|
||||
DbPerson::read_local_from_id(claims.claims.sub.parse()?, data)
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct RegisterUserData {
|
||||
pub name: String,
|
||||
|
|
|
@ -111,7 +111,6 @@ impl DbInstance {
|
|||
follower_id.eq(follower_id_),
|
||||
pending.eq(pending_),
|
||||
);
|
||||
dbg!(follower_id_, instance_id_, pending_);
|
||||
insert_into(instance_follow::table)
|
||||
.values(form)
|
||||
.on_conflict((instance_id, follower_id))
|
||||
|
|
|
@ -137,4 +137,13 @@ impl DbPerson {
|
|||
.filter(person::dsl::username.eq(username))
|
||||
.get_result(conn.deref_mut())?)
|
||||
}
|
||||
|
||||
pub fn read_local_from_id(id: i32, data: &Data<MyDataHandle>) -> MyResult<LocalUserView> {
|
||||
let mut conn = data.db_connection.lock().unwrap();
|
||||
Ok(person::table
|
||||
.inner_join(local_user::table)
|
||||
.filter(person::dsl::local)
|
||||
.filter(person::dsl::id.eq(id))
|
||||
.get_result(conn.deref_mut())?)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,7 +52,6 @@ impl ActivityHandler for Accept {
|
|||
let local_instance = DbInstance::read_local_instance(&data.db_connection)?;
|
||||
let actor = self.actor.dereference(data).await?;
|
||||
DbInstance::follow(local_instance.id, actor.id, false, data)?;
|
||||
dbg!(&self);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,6 @@ impl Follow {
|
|||
to: DbInstance,
|
||||
data: &Data<MyDataHandle>,
|
||||
) -> MyResult<()> {
|
||||
dbg!(1);
|
||||
let id = generate_activity_id(local_instance.ap_id.inner())?;
|
||||
let follow = Follow {
|
||||
actor: local_instance.ap_id.clone(),
|
||||
|
@ -34,7 +33,6 @@ impl Follow {
|
|||
kind: Default::default(),
|
||||
id,
|
||||
};
|
||||
dbg!(&follow);
|
||||
local_instance
|
||||
.send(follow, vec![to.shared_inbox_or_inbox()], data)
|
||||
.await?;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use anyhow::anyhow;
|
||||
use fediwiki::api::article::{CreateArticleData, EditArticleData, GetArticleData};
|
||||
use fediwiki::api::instance::FollowInstance;
|
||||
use fediwiki::api::user::LoginResponse;
|
||||
use fediwiki::api::user::RegisterUserData;
|
||||
use fediwiki::api::ResolveObject;
|
||||
use fediwiki::database::article::ArticleView;
|
||||
use fediwiki::database::conflict::ApiConflict;
|
||||
|
@ -31,7 +33,7 @@ pub struct TestData {
|
|||
}
|
||||
|
||||
impl TestData {
|
||||
pub fn start() -> Self {
|
||||
pub async fn start() -> Self {
|
||||
static INIT: Once = Once::new();
|
||||
INIT.call_once(|| {
|
||||
env_logger::builder()
|
||||
|
@ -67,9 +69,9 @@ impl TestData {
|
|||
}
|
||||
|
||||
Self {
|
||||
alpha: FediwikiInstance::start(alpha_db_path, port_alpha),
|
||||
beta: FediwikiInstance::start(beta_db_path, port_beta),
|
||||
gamma: FediwikiInstance::start(gamma_db_path, port_gamma),
|
||||
alpha: FediwikiInstance::start(alpha_db_path, port_alpha, "alpha").await,
|
||||
beta: FediwikiInstance::start(beta_db_path, port_beta, "beta").await,
|
||||
gamma: FediwikiInstance::start(gamma_db_path, port_gamma, "gamma").await,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -93,6 +95,7 @@ fn generate_db_path(name: &'static str, port: i32) -> String {
|
|||
|
||||
pub struct FediwikiInstance {
|
||||
pub hostname: String,
|
||||
pub jwt: String,
|
||||
db_path: String,
|
||||
db_handle: JoinHandle<()>,
|
||||
}
|
||||
|
@ -109,16 +112,25 @@ impl FediwikiInstance {
|
|||
})
|
||||
}
|
||||
|
||||
fn start(db_path: String, port: i32) -> Self {
|
||||
async fn start(db_path: String, port: i32, username: &str) -> Self {
|
||||
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();
|
||||
});
|
||||
let register_form = RegisterUserData {
|
||||
name: username.to_string(),
|
||||
password: "hunter2".to_string(),
|
||||
};
|
||||
let register: LoginResponse = post(&hostname, "user/register", ®ister_form)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(!register.jwt.is_empty());
|
||||
Self {
|
||||
db_path,
|
||||
jwt: register.jwt,
|
||||
hostname,
|
||||
db_path,
|
||||
db_handle: handle,
|
||||
}
|
||||
}
|
||||
|
@ -138,11 +150,16 @@ impl FediwikiInstance {
|
|||
|
||||
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(instance: &FediwikiInstance, title: String) -> MyResult<ArticleView> {
|
||||
let create_form = CreateArticleData {
|
||||
title: title.clone(),
|
||||
};
|
||||
let article: ArticleView = post(hostname, "article", &create_form).await?;
|
||||
let req = CLIENT
|
||||
.post(format!("http://{}/api/v1/article", &instance.hostname))
|
||||
.form(&create_form)
|
||||
.bearer_auth(&instance.jwt);
|
||||
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,
|
||||
|
@ -150,7 +167,7 @@ pub async fn create_article(hostname: &str, title: String) -> MyResult<ArticleVi
|
|||
previous_version_id: article.latest_version,
|
||||
resolve_conflict_id: None,
|
||||
};
|
||||
edit_article(hostname, &edit_form).await
|
||||
edit_article(&instance, &edit_form).await
|
||||
}
|
||||
|
||||
pub async fn get_article(hostname: &str, article_id: i32) -> MyResult<ArticleView> {
|
||||
|
@ -159,19 +176,23 @@ pub async fn get_article(hostname: &str, article_id: i32) -> MyResult<ArticleVie
|
|||
}
|
||||
|
||||
pub async fn edit_article_with_conflict(
|
||||
hostname: &str,
|
||||
instance: &FediwikiInstance,
|
||||
edit_form: &EditArticleData,
|
||||
) -> MyResult<Option<ApiConflict>> {
|
||||
let req = CLIENT
|
||||
.patch(format!("http://{}/api/v1/article", hostname))
|
||||
.form(edit_form);
|
||||
.patch(format!("http://{}/api/v1/article", instance.hostname))
|
||||
.form(edit_form)
|
||||
.bearer_auth(&instance.jwt);
|
||||
handle_json_res(req).await
|
||||
}
|
||||
|
||||
pub async fn edit_article(hostname: &str, edit_form: &EditArticleData) -> MyResult<ArticleView> {
|
||||
let edit_res = edit_article_with_conflict(hostname, edit_form).await?;
|
||||
pub async fn edit_article(
|
||||
instance: &FediwikiInstance,
|
||||
edit_form: &EditArticleData,
|
||||
) -> MyResult<ArticleView> {
|
||||
let edit_res = edit_article_with_conflict(instance, edit_form).await?;
|
||||
assert!(edit_res.is_none());
|
||||
get_article(hostname, edit_form.article_id).await
|
||||
get_article(&instance.hostname, edit_form.article_id).await
|
||||
}
|
||||
|
||||
pub async fn get<T>(hostname: &str, endpoint: &str) -> MyResult<T>
|
||||
|
@ -203,7 +224,7 @@ where
|
|||
handle_json_res(req).await
|
||||
}
|
||||
|
||||
async fn handle_json_res<T>(req: RequestBuilder) -> MyResult<T>
|
||||
pub async fn handle_json_res<T>(req: RequestBuilder) -> MyResult<T>
|
||||
where
|
||||
T: for<'de> Deserialize<'de>,
|
||||
{
|
||||
|
|
|
@ -2,12 +2,13 @@ extern crate fediwiki;
|
|||
|
||||
mod common;
|
||||
|
||||
use crate::common::handle_json_res;
|
||||
use crate::common::{
|
||||
create_article, edit_article, edit_article_with_conflict, follow_instance, get_article,
|
||||
get_query, post, TestData, TEST_ARTICLE_DEFAULT_TEXT,
|
||||
get_query, post, TestData, CLIENT, TEST_ARTICLE_DEFAULT_TEXT,
|
||||
};
|
||||
use common::get;
|
||||
use fediwiki::api::article::{EditArticleData, ForkArticleData};
|
||||
use fediwiki::api::article::{CreateArticleData, EditArticleData, ForkArticleData};
|
||||
use fediwiki::api::user::{LoginResponse, RegisterUserData};
|
||||
use fediwiki::api::{ResolveObject, SearchArticleData};
|
||||
use fediwiki::database::article::{ArticleView, DbArticle};
|
||||
|
@ -19,11 +20,11 @@ use url::Url;
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_create_read_and_edit_article() -> MyResult<()> {
|
||||
let data = TestData::start();
|
||||
let data = TestData::start().await;
|
||||
|
||||
// create article
|
||||
let title = "Manu_Chao".to_string();
|
||||
let create_res = create_article(&data.alpha.hostname, title.clone()).await?;
|
||||
let create_res = create_article(&data.alpha, title.clone()).await?;
|
||||
assert_eq!(title, create_res.article.title);
|
||||
assert!(create_res.article.local);
|
||||
|
||||
|
@ -44,7 +45,7 @@ async fn test_create_read_and_edit_article() -> MyResult<()> {
|
|||
previous_version_id: get_res.latest_version,
|
||||
resolve_conflict_id: None,
|
||||
};
|
||||
let edit_res = edit_article(&data.alpha.hostname, &edit_form).await?;
|
||||
let edit_res = edit_article(&data.alpha, &edit_form).await?;
|
||||
assert_eq!(edit_form.new_text, edit_res.article.text);
|
||||
assert_eq!(2, edit_res.edits.len());
|
||||
|
||||
|
@ -61,15 +62,15 @@ async fn test_create_read_and_edit_article() -> MyResult<()> {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_create_duplicate_article() -> MyResult<()> {
|
||||
let data = TestData::start();
|
||||
let data = TestData::start().await;
|
||||
|
||||
// create article
|
||||
let title = "Manu_Chao".to_string();
|
||||
let create_res = create_article(&data.alpha.hostname, title.clone()).await?;
|
||||
let create_res = create_article(&data.alpha, title.clone()).await?;
|
||||
assert_eq!(title, create_res.article.title);
|
||||
assert!(create_res.article.local);
|
||||
|
||||
let create_res = create_article(&data.alpha.hostname, title.clone()).await;
|
||||
let create_res = create_article(&data.alpha, title.clone()).await;
|
||||
assert!(create_res.is_err());
|
||||
|
||||
data.stop()
|
||||
|
@ -77,7 +78,7 @@ async fn test_create_duplicate_article() -> MyResult<()> {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_follow_instance() -> MyResult<()> {
|
||||
let data = TestData::start();
|
||||
let data = TestData::start().await;
|
||||
|
||||
// check initial state
|
||||
let alpha_instance: InstanceView = get(&data.alpha.hostname, "instance").await?;
|
||||
|
@ -91,7 +92,6 @@ async fn test_follow_instance() -> MyResult<()> {
|
|||
|
||||
// check that follow was federated
|
||||
let alpha_instance: InstanceView = get(&data.alpha.hostname, "instance").await?;
|
||||
dbg!(&alpha_instance);
|
||||
assert_eq!(1, alpha_instance.following.len());
|
||||
assert_eq!(0, alpha_instance.followers.len());
|
||||
assert_eq!(
|
||||
|
@ -112,11 +112,11 @@ async fn test_follow_instance() -> MyResult<()> {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_synchronize_articles() -> MyResult<()> {
|
||||
let data = TestData::start();
|
||||
let data = TestData::start().await;
|
||||
|
||||
// create article on alpha
|
||||
let title = "Manu_Chao".to_string();
|
||||
let create_res = create_article(&data.alpha.hostname, title.clone()).await?;
|
||||
let create_res = create_article(&data.alpha, title.clone()).await?;
|
||||
assert_eq!(title, create_res.article.title);
|
||||
assert_eq!(1, create_res.edits.len());
|
||||
assert!(create_res.article.local);
|
||||
|
@ -128,7 +128,7 @@ async fn test_synchronize_articles() -> MyResult<()> {
|
|||
previous_version_id: create_res.latest_version,
|
||||
resolve_conflict_id: None,
|
||||
};
|
||||
edit_article(&data.alpha.hostname, &edit_form).await?;
|
||||
edit_article(&data.alpha, &edit_form).await?;
|
||||
|
||||
// article is not yet on beta
|
||||
let get_res = get_article(&data.beta.hostname, create_res.article.id).await;
|
||||
|
@ -158,13 +158,13 @@ async fn test_synchronize_articles() -> MyResult<()> {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_edit_local_article() -> MyResult<()> {
|
||||
let data = TestData::start();
|
||||
let data = TestData::start().await;
|
||||
|
||||
follow_instance(&data.alpha.hostname, &data.beta.hostname).await?;
|
||||
|
||||
// create new article
|
||||
let title = "Manu_Chao".to_string();
|
||||
let create_res = create_article(&data.beta.hostname, title.clone()).await?;
|
||||
let create_res = create_article(&data.beta, title.clone()).await?;
|
||||
assert_eq!(title, create_res.article.title);
|
||||
assert!(create_res.article.local);
|
||||
|
||||
|
@ -182,7 +182,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.hostname, &edit_form).await?;
|
||||
let edit_res = edit_article(&data.beta, &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]
|
||||
|
@ -201,14 +201,14 @@ async fn test_edit_local_article() -> MyResult<()> {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_edit_remote_article() -> MyResult<()> {
|
||||
let data = TestData::start();
|
||||
let data = TestData::start().await;
|
||||
|
||||
follow_instance(&data.alpha.hostname, &data.beta.hostname).await?;
|
||||
follow_instance(&data.gamma.hostname, &data.beta.hostname).await?;
|
||||
|
||||
// create new article
|
||||
let title = "Manu_Chao".to_string();
|
||||
let create_res = create_article(&data.beta.hostname, title.clone()).await?;
|
||||
let create_res = create_article(&data.beta, title.clone()).await?;
|
||||
assert_eq!(title, create_res.article.title);
|
||||
assert!(create_res.article.local);
|
||||
|
||||
|
@ -228,7 +228,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.hostname, &edit_form).await?;
|
||||
let edit_res = edit_article(&data.alpha, &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);
|
||||
|
@ -253,11 +253,11 @@ async fn test_edit_remote_article() -> MyResult<()> {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_local_edit_conflict() -> MyResult<()> {
|
||||
let data = TestData::start();
|
||||
let data = TestData::start().await;
|
||||
|
||||
// create new article
|
||||
let title = "Manu_Chao".to_string();
|
||||
let create_res = create_article(&data.alpha.hostname, title.clone()).await?;
|
||||
let create_res = create_article(&data.alpha, title.clone()).await?;
|
||||
assert_eq!(title, create_res.article.title);
|
||||
assert!(create_res.article.local);
|
||||
|
||||
|
@ -268,7 +268,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.hostname, &edit_form).await?;
|
||||
let edit_res = edit_article(&data.alpha, &edit_form).await?;
|
||||
assert_eq!(edit_res.article.text, edit_form.new_text);
|
||||
assert_eq!(2, edit_res.edits.len());
|
||||
|
||||
|
@ -279,7 +279,7 @@ 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.hostname, &edit_form)
|
||||
let edit_res = edit_article_with_conflict(&data.alpha, &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);
|
||||
|
@ -295,7 +295,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.hostname, &edit_form).await?;
|
||||
let edit_res = edit_article(&data.alpha, &edit_form).await?;
|
||||
assert_eq!(edit_form.new_text, edit_res.article.text);
|
||||
|
||||
let conflicts: Vec<ApiConflict> =
|
||||
|
@ -307,13 +307,13 @@ async fn test_local_edit_conflict() -> MyResult<()> {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_federated_edit_conflict() -> MyResult<()> {
|
||||
let data = TestData::start();
|
||||
let data = TestData::start().await;
|
||||
|
||||
follow_instance(&data.alpha.hostname, &data.beta.hostname).await?;
|
||||
|
||||
// create new article
|
||||
let title = "Manu_Chao".to_string();
|
||||
let create_res = create_article(&data.beta.hostname, title.clone()).await?;
|
||||
let create_res = create_article(&data.beta, title.clone()).await?;
|
||||
assert_eq!(title, create_res.article.title);
|
||||
assert!(create_res.article.local);
|
||||
|
||||
|
@ -336,7 +336,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.hostname, &edit_form).await?;
|
||||
let edit_res = edit_article(&data.alpha, &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);
|
||||
|
@ -353,7 +353,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.hostname, &edit_form).await?;
|
||||
let edit_res = edit_article(&data.gamma, &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);
|
||||
|
@ -369,7 +369,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.hostname, &edit_form).await?;
|
||||
let edit_res = edit_article(&data.gamma, &edit_form).await?;
|
||||
assert_eq!(edit_form.new_text, edit_res.article.text);
|
||||
assert_eq!(3, edit_res.edits.len());
|
||||
|
||||
|
@ -382,11 +382,11 @@ async fn test_federated_edit_conflict() -> MyResult<()> {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_overlapping_edits_no_conflict() -> MyResult<()> {
|
||||
let data = TestData::start();
|
||||
let data = TestData::start().await;
|
||||
|
||||
// create new article
|
||||
let title = "Manu_Chao".to_string();
|
||||
let create_res = create_article(&data.alpha.hostname, title.clone()).await?;
|
||||
let create_res = create_article(&data.alpha, title.clone()).await?;
|
||||
assert_eq!(title, create_res.article.title);
|
||||
assert!(create_res.article.local);
|
||||
|
||||
|
@ -397,7 +397,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.hostname, &edit_form).await?;
|
||||
let edit_res = edit_article(&data.alpha, &edit_form).await?;
|
||||
assert_eq!(edit_res.article.text, edit_form.new_text);
|
||||
assert_eq!(2, edit_res.edits.len());
|
||||
|
||||
|
@ -408,7 +408,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.hostname, &edit_form).await?;
|
||||
let edit_res = edit_article(&data.alpha, &edit_form).await?;
|
||||
let conflicts: Vec<ApiConflict> =
|
||||
get_query(&data.alpha.hostname, "edit_conflicts", None::<()>).await?;
|
||||
assert_eq!(0, conflicts.len());
|
||||
|
@ -420,11 +420,11 @@ async fn test_overlapping_edits_no_conflict() -> MyResult<()> {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_fork_article() -> MyResult<()> {
|
||||
let data = TestData::start();
|
||||
let data = TestData::start().await;
|
||||
|
||||
// create article
|
||||
let title = "Manu_Chao".to_string();
|
||||
let create_res = create_article(&data.alpha.hostname, title.clone()).await?;
|
||||
let create_res = create_article(&data.alpha, title.clone()).await?;
|
||||
assert_eq!(title, create_res.article.title);
|
||||
assert!(create_res.article.local);
|
||||
|
||||
|
@ -469,7 +469,7 @@ async fn test_fork_article() -> MyResult<()> {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_user_registration_login() -> MyResult<()> {
|
||||
let data = TestData::start();
|
||||
let data = TestData::start().await;
|
||||
let register_form = RegisterUserData {
|
||||
name: "my_user".to_string(),
|
||||
password: "hunter2".to_string(),
|
||||
|
@ -490,5 +490,17 @@ async fn test_user_registration_login() -> MyResult<()> {
|
|||
let valid_login: LoginResponse = post(&data.alpha.hostname, "user/login", &login_form).await?;
|
||||
assert!(!valid_login.jwt.is_empty());
|
||||
|
||||
let title = "Manu_Chao".to_string();
|
||||
let create_form = CreateArticleData {
|
||||
title: title.clone(),
|
||||
};
|
||||
|
||||
let req = CLIENT
|
||||
.post(format!("http://{}/api/v1/article", &data.alpha.hostname))
|
||||
.form(&create_form)
|
||||
.bearer_auth(valid_login.jwt);
|
||||
let create_res: ArticleView = handle_json_res(req).await?;
|
||||
assert_eq!(title, create_res.article.title);
|
||||
|
||||
data.stop()
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue