mirror of
https://github.com/Nutomic/ibis.git
synced 2024-11-22 19:01:10 +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::conflict::{ApiConflict, DbConflict, DbConflictForm};
|
||||||
use crate::database::edit::{DbEdit, DbEditForm};
|
use crate::database::edit::{DbEdit, DbEditForm};
|
||||||
use crate::database::instance::DbInstance;
|
use crate::database::instance::DbInstance;
|
||||||
|
use crate::database::user::LocalUserView;
|
||||||
use crate::database::version::EditVersion;
|
use crate::database::version::EditVersion;
|
||||||
use crate::database::MyDataHandle;
|
use crate::database::MyDataHandle;
|
||||||
use crate::error::MyResult;
|
use crate::error::MyResult;
|
||||||
|
@ -11,6 +12,7 @@ use crate::utils::generate_article_version;
|
||||||
use activitypub_federation::config::Data;
|
use activitypub_federation::config::Data;
|
||||||
use activitypub_federation::fetch::object_id::ObjectId;
|
use activitypub_federation::fetch::object_id::ObjectId;
|
||||||
use axum::extract::Query;
|
use axum::extract::Query;
|
||||||
|
use axum::Extension;
|
||||||
use axum::Form;
|
use axum::Form;
|
||||||
use axum::Json;
|
use axum::Json;
|
||||||
use axum_macros::debug_handler;
|
use axum_macros::debug_handler;
|
||||||
|
@ -25,6 +27,7 @@ pub struct CreateArticleData {
|
||||||
/// Create a new article with empty text, and federate it to followers.
|
/// Create a new article with empty text, and federate it to followers.
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
pub(in crate::api) async fn create_article(
|
pub(in crate::api) async fn create_article(
|
||||||
|
Extension(_user): Extension<LocalUserView>,
|
||||||
data: Data<MyDataHandle>,
|
data: Data<MyDataHandle>,
|
||||||
Form(create_article): Form<CreateArticleData>,
|
Form(create_article): Form<CreateArticleData>,
|
||||||
) -> MyResult<Json<ArticleView>> {
|
) -> 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`.
|
/// Conflicts are stored in the database so they can be retrieved later from `/api/v3/edit_conflicts`.
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
pub(in crate::api) async fn edit_article(
|
pub(in crate::api) async fn edit_article(
|
||||||
|
Extension(_user): Extension<LocalUserView>,
|
||||||
data: Data<MyDataHandle>,
|
data: Data<MyDataHandle>,
|
||||||
Form(edit_form): Form<EditArticleData>,
|
Form(edit_form): Form<EditArticleData>,
|
||||||
) -> MyResult<Json<Option<ApiConflict>>> {
|
) -> MyResult<Json<Option<ApiConflict>>> {
|
||||||
|
|
|
@ -4,6 +4,7 @@ use crate::api::instance::follow_instance;
|
||||||
use crate::api::instance::get_local_instance;
|
use crate::api::instance::get_local_instance;
|
||||||
use crate::api::user::login_user;
|
use crate::api::user::login_user;
|
||||||
use crate::api::user::register_user;
|
use crate::api::user::register_user;
|
||||||
|
use crate::api::user::validate;
|
||||||
use crate::database::article::{ArticleView, DbArticle};
|
use crate::database::article::{ArticleView, DbArticle};
|
||||||
use crate::database::conflict::{ApiConflict, DbConflict};
|
use crate::database::conflict::{ApiConflict, DbConflict};
|
||||||
use crate::database::edit::DbEdit;
|
use crate::database::edit::DbEdit;
|
||||||
|
@ -14,10 +15,19 @@ use activitypub_federation::config::Data;
|
||||||
use activitypub_federation::fetch::object_id::ObjectId;
|
use activitypub_federation::fetch::object_id::ObjectId;
|
||||||
use axum::extract::Query;
|
use axum::extract::Query;
|
||||||
use axum::routing::{get, post};
|
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::{Json, Router};
|
||||||
use axum_macros::debug_handler;
|
use axum_macros::debug_handler;
|
||||||
use futures::future::try_join_all;
|
use futures::future::try_join_all;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tracing::warn;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
pub mod article;
|
pub mod article;
|
||||||
|
@ -39,6 +49,24 @@ pub fn api_routes() -> Router {
|
||||||
.route("/search", get(search_article))
|
.route("/search", get(search_article))
|
||||||
.route("/user/register", post(register_user))
|
.route("/user/register", post(register_user))
|
||||||
.route("/user/login", post(login_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)]
|
#[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::database::MyDataHandle;
|
||||||
use crate::error::MyResult;
|
use crate::error::MyResult;
|
||||||
use activitypub_federation::config::Data;
|
use activitypub_federation::config::Data;
|
||||||
|
@ -7,6 +7,9 @@ use axum::{Form, Json};
|
||||||
use axum_macros::debug_handler;
|
use axum_macros::debug_handler;
|
||||||
use bcrypt::verify;
|
use bcrypt::verify;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
|
use jsonwebtoken::DecodingKey;
|
||||||
|
use jsonwebtoken::Validation;
|
||||||
|
use jsonwebtoken::{decode, get_current_timestamp};
|
||||||
use jsonwebtoken::{encode, EncodingKey, Header};
|
use jsonwebtoken::{encode, EncodingKey, Header};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -18,8 +21,13 @@ pub struct Claims {
|
||||||
pub iss: String,
|
pub iss: String,
|
||||||
/// Creation time as unix timestamp
|
/// Creation time as unix timestamp
|
||||||
pub iat: i64,
|
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(
|
pub(in crate::api) fn generate_login_token(
|
||||||
local_user: DbLocalUser,
|
local_user: DbLocalUser,
|
||||||
data: &Data<MyDataHandle>,
|
data: &Data<MyDataHandle>,
|
||||||
|
@ -29,14 +37,21 @@ pub(in crate::api) fn generate_login_token(
|
||||||
sub: local_user.id.to_string(),
|
sub: local_user.id.to_string(),
|
||||||
iss: hostname,
|
iss: hostname,
|
||||||
iat: Utc::now().timestamp(),
|
iat: Utc::now().timestamp(),
|
||||||
|
exp: get_current_timestamp(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: move to config
|
let key = EncodingKey::from_secret(SECRET);
|
||||||
let key = EncodingKey::from_secret("secret".as_bytes());
|
|
||||||
let jwt = encode(&Header::default(), &claims, &key)?;
|
let jwt = encode(&Header::default(), &claims, &key)?;
|
||||||
Ok(LoginResponse { jwt })
|
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)]
|
#[derive(Deserialize, Serialize)]
|
||||||
pub struct RegisterUserData {
|
pub struct RegisterUserData {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
|
@ -111,7 +111,6 @@ impl DbInstance {
|
||||||
follower_id.eq(follower_id_),
|
follower_id.eq(follower_id_),
|
||||||
pending.eq(pending_),
|
pending.eq(pending_),
|
||||||
);
|
);
|
||||||
dbg!(follower_id_, instance_id_, pending_);
|
|
||||||
insert_into(instance_follow::table)
|
insert_into(instance_follow::table)
|
||||||
.values(form)
|
.values(form)
|
||||||
.on_conflict((instance_id, follower_id))
|
.on_conflict((instance_id, follower_id))
|
||||||
|
|
|
@ -137,4 +137,13 @@ impl DbPerson {
|
||||||
.filter(person::dsl::username.eq(username))
|
.filter(person::dsl::username.eq(username))
|
||||||
.get_result(conn.deref_mut())?)
|
.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 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)?;
|
||||||
dbg!(&self);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,6 @@ impl Follow {
|
||||||
to: DbInstance,
|
to: DbInstance,
|
||||||
data: &Data<MyDataHandle>,
|
data: &Data<MyDataHandle>,
|
||||||
) -> MyResult<()> {
|
) -> MyResult<()> {
|
||||||
dbg!(1);
|
|
||||||
let id = generate_activity_id(local_instance.ap_id.inner())?;
|
let id = generate_activity_id(local_instance.ap_id.inner())?;
|
||||||
let follow = Follow {
|
let follow = Follow {
|
||||||
actor: local_instance.ap_id.clone(),
|
actor: local_instance.ap_id.clone(),
|
||||||
|
@ -34,7 +33,6 @@ impl Follow {
|
||||||
kind: Default::default(),
|
kind: Default::default(),
|
||||||
id,
|
id,
|
||||||
};
|
};
|
||||||
dbg!(&follow);
|
|
||||||
local_instance
|
local_instance
|
||||||
.send(follow, vec![to.shared_inbox_or_inbox()], data)
|
.send(follow, vec![to.shared_inbox_or_inbox()], data)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use fediwiki::api::article::{CreateArticleData, EditArticleData, GetArticleData};
|
use fediwiki::api::article::{CreateArticleData, EditArticleData, GetArticleData};
|
||||||
use fediwiki::api::instance::FollowInstance;
|
use fediwiki::api::instance::FollowInstance;
|
||||||
|
use fediwiki::api::user::LoginResponse;
|
||||||
|
use fediwiki::api::user::RegisterUserData;
|
||||||
use fediwiki::api::ResolveObject;
|
use fediwiki::api::ResolveObject;
|
||||||
use fediwiki::database::article::ArticleView;
|
use fediwiki::database::article::ArticleView;
|
||||||
use fediwiki::database::conflict::ApiConflict;
|
use fediwiki::database::conflict::ApiConflict;
|
||||||
|
@ -31,7 +33,7 @@ pub struct TestData {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestData {
|
impl TestData {
|
||||||
pub fn start() -> Self {
|
pub async fn start() -> Self {
|
||||||
static INIT: Once = Once::new();
|
static INIT: Once = Once::new();
|
||||||
INIT.call_once(|| {
|
INIT.call_once(|| {
|
||||||
env_logger::builder()
|
env_logger::builder()
|
||||||
|
@ -67,9 +69,9 @@ impl TestData {
|
||||||
}
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
alpha: FediwikiInstance::start(alpha_db_path, port_alpha),
|
alpha: FediwikiInstance::start(alpha_db_path, port_alpha, "alpha").await,
|
||||||
beta: FediwikiInstance::start(beta_db_path, port_beta),
|
beta: FediwikiInstance::start(beta_db_path, port_beta, "beta").await,
|
||||||
gamma: FediwikiInstance::start(gamma_db_path, port_gamma),
|
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 struct FediwikiInstance {
|
||||||
pub hostname: String,
|
pub hostname: String,
|
||||||
|
pub jwt: String,
|
||||||
db_path: String,
|
db_path: String,
|
||||||
db_handle: JoinHandle<()>,
|
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 db_url = format!("postgresql://lemmy:password@/lemmy?host={db_path}");
|
||||||
let hostname = format!("localhost:{port}");
|
let hostname = format!("localhost:{port}");
|
||||||
let hostname_ = hostname.clone();
|
let hostname_ = hostname.clone();
|
||||||
let handle = tokio::task::spawn(async move {
|
let handle = tokio::task::spawn(async move {
|
||||||
start(&hostname_, &db_url).await.unwrap();
|
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 {
|
Self {
|
||||||
db_path,
|
jwt: register.jwt,
|
||||||
hostname,
|
hostname,
|
||||||
|
db_path,
|
||||||
db_handle: handle,
|
db_handle: handle,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -138,11 +150,16 @@ impl FediwikiInstance {
|
||||||
|
|
||||||
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(instance: &FediwikiInstance, title: String) -> MyResult<ArticleView> {
|
||||||
let create_form = CreateArticleData {
|
let create_form = CreateArticleData {
|
||||||
title: title.clone(),
|
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)
|
// create initial edit to ensure that conflicts are generated (there are no conflicts on empty file)
|
||||||
let edit_form = EditArticleData {
|
let edit_form = EditArticleData {
|
||||||
article_id: article.article.id,
|
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,
|
previous_version_id: article.latest_version,
|
||||||
resolve_conflict_id: None,
|
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> {
|
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(
|
pub async fn edit_article_with_conflict(
|
||||||
hostname: &str,
|
instance: &FediwikiInstance,
|
||||||
edit_form: &EditArticleData,
|
edit_form: &EditArticleData,
|
||||||
) -> MyResult<Option<ApiConflict>> {
|
) -> MyResult<Option<ApiConflict>> {
|
||||||
let req = CLIENT
|
let req = CLIENT
|
||||||
.patch(format!("http://{}/api/v1/article", hostname))
|
.patch(format!("http://{}/api/v1/article", instance.hostname))
|
||||||
.form(edit_form);
|
.form(edit_form)
|
||||||
|
.bearer_auth(&instance.jwt);
|
||||||
handle_json_res(req).await
|
handle_json_res(req).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn edit_article(hostname: &str, edit_form: &EditArticleData) -> MyResult<ArticleView> {
|
pub async fn edit_article(
|
||||||
let edit_res = edit_article_with_conflict(hostname, edit_form).await?;
|
instance: &FediwikiInstance,
|
||||||
|
edit_form: &EditArticleData,
|
||||||
|
) -> MyResult<ArticleView> {
|
||||||
|
let edit_res = edit_article_with_conflict(instance, edit_form).await?;
|
||||||
assert!(edit_res.is_none());
|
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>
|
pub async fn get<T>(hostname: &str, endpoint: &str) -> MyResult<T>
|
||||||
|
@ -203,7 +224,7 @@ where
|
||||||
handle_json_res(req).await
|
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
|
where
|
||||||
T: for<'de> Deserialize<'de>,
|
T: for<'de> Deserialize<'de>,
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,12 +2,13 @@ extern crate fediwiki;
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
|
|
||||||
|
use crate::common::handle_json_res;
|
||||||
use crate::common::{
|
use crate::common::{
|
||||||
create_article, edit_article, edit_article_with_conflict, follow_instance, get_article,
|
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 common::get;
|
||||||
use fediwiki::api::article::{EditArticleData, ForkArticleData};
|
use fediwiki::api::article::{CreateArticleData, EditArticleData, ForkArticleData};
|
||||||
use fediwiki::api::user::{LoginResponse, RegisterUserData};
|
use fediwiki::api::user::{LoginResponse, RegisterUserData};
|
||||||
use fediwiki::api::{ResolveObject, SearchArticleData};
|
use fediwiki::api::{ResolveObject, SearchArticleData};
|
||||||
use fediwiki::database::article::{ArticleView, DbArticle};
|
use fediwiki::database::article::{ArticleView, DbArticle};
|
||||||
|
@ -19,11 +20,11 @@ use url::Url;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_create_read_and_edit_article() -> MyResult<()> {
|
async fn test_create_read_and_edit_article() -> MyResult<()> {
|
||||||
let data = TestData::start();
|
let data = TestData::start().await;
|
||||||
|
|
||||||
// create article
|
// create article
|
||||||
let title = "Manu_Chao".to_string();
|
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!(title, create_res.article.title);
|
||||||
assert!(create_res.article.local);
|
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,
|
previous_version_id: get_res.latest_version,
|
||||||
resolve_conflict_id: None,
|
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!(edit_form.new_text, edit_res.article.text);
|
||||||
assert_eq!(2, edit_res.edits.len());
|
assert_eq!(2, edit_res.edits.len());
|
||||||
|
|
||||||
|
@ -61,15 +62,15 @@ async fn test_create_read_and_edit_article() -> MyResult<()> {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_create_duplicate_article() -> MyResult<()> {
|
async fn test_create_duplicate_article() -> MyResult<()> {
|
||||||
let data = TestData::start();
|
let data = TestData::start().await;
|
||||||
|
|
||||||
// create article
|
// create article
|
||||||
let title = "Manu_Chao".to_string();
|
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!(title, create_res.article.title);
|
||||||
assert!(create_res.article.local);
|
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());
|
assert!(create_res.is_err());
|
||||||
|
|
||||||
data.stop()
|
data.stop()
|
||||||
|
@ -77,7 +78,7 @@ async fn test_create_duplicate_article() -> MyResult<()> {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_follow_instance() -> MyResult<()> {
|
async fn test_follow_instance() -> MyResult<()> {
|
||||||
let data = TestData::start();
|
let data = TestData::start().await;
|
||||||
|
|
||||||
// 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?;
|
||||||
|
@ -91,7 +92,6 @@ async fn test_follow_instance() -> MyResult<()> {
|
||||||
|
|
||||||
// check that follow was federated
|
// check that follow was federated
|
||||||
let alpha_instance: InstanceView = get(&data.alpha.hostname, "instance").await?;
|
let alpha_instance: InstanceView = get(&data.alpha.hostname, "instance").await?;
|
||||||
dbg!(&alpha_instance);
|
|
||||||
assert_eq!(1, alpha_instance.following.len());
|
assert_eq!(1, alpha_instance.following.len());
|
||||||
assert_eq!(0, alpha_instance.followers.len());
|
assert_eq!(0, alpha_instance.followers.len());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -112,11 +112,11 @@ async fn test_follow_instance() -> MyResult<()> {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_synchronize_articles() -> MyResult<()> {
|
async fn test_synchronize_articles() -> MyResult<()> {
|
||||||
let data = TestData::start();
|
let data = TestData::start().await;
|
||||||
|
|
||||||
// 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.alpha.hostname, title.clone()).await?;
|
let create_res = create_article(&data.alpha, 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);
|
||||||
|
@ -128,7 +128,7 @@ async fn test_synchronize_articles() -> MyResult<()> {
|
||||||
previous_version_id: create_res.latest_version,
|
previous_version_id: create_res.latest_version,
|
||||||
resolve_conflict_id: None,
|
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
|
// article is not yet on beta
|
||||||
let get_res = get_article(&data.beta.hostname, create_res.article.id).await;
|
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]
|
#[tokio::test]
|
||||||
async fn test_edit_local_article() -> MyResult<()> {
|
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?;
|
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.beta.hostname, title.clone()).await?;
|
let create_res = create_article(&data.beta, 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);
|
||||||
|
|
||||||
|
@ -182,7 +182,7 @@ async fn test_edit_local_article() -> MyResult<()> {
|
||||||
previous_version_id: get_res.latest_version,
|
previous_version_id: get_res.latest_version,
|
||||||
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, &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]
|
||||||
|
@ -201,14 +201,14 @@ async fn test_edit_local_article() -> MyResult<()> {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_edit_remote_article() -> MyResult<()> {
|
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.alpha.hostname, &data.beta.hostname).await?;
|
||||||
follow_instance(&data.gamma.hostname, &data.beta.hostname).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.beta.hostname, title.clone()).await?;
|
let create_res = create_article(&data.beta, 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);
|
||||||
|
|
||||||
|
@ -228,7 +228,7 @@ async fn test_edit_remote_article() -> MyResult<()> {
|
||||||
previous_version_id: get_res.latest_version,
|
previous_version_id: get_res.latest_version,
|
||||||
resolve_conflict_id: None,
|
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!(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);
|
||||||
|
@ -253,11 +253,11 @@ async fn test_edit_remote_article() -> MyResult<()> {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_local_edit_conflict() -> MyResult<()> {
|
async fn test_local_edit_conflict() -> MyResult<()> {
|
||||||
let data = TestData::start();
|
let data = TestData::start().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.alpha.hostname, title.clone()).await?;
|
let create_res = create_article(&data.alpha, 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);
|
||||||
|
|
||||||
|
@ -268,7 +268,7 @@ async fn test_local_edit_conflict() -> MyResult<()> {
|
||||||
previous_version_id: create_res.latest_version.clone(),
|
previous_version_id: create_res.latest_version.clone(),
|
||||||
resolve_conflict_id: None,
|
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!(edit_res.article.text, edit_form.new_text);
|
||||||
assert_eq!(2, edit_res.edits.len());
|
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,
|
previous_version_id: create_res.latest_version,
|
||||||
resolve_conflict_id: None,
|
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?
|
.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);
|
||||||
|
@ -295,7 +295,7 @@ async fn test_local_edit_conflict() -> MyResult<()> {
|
||||||
previous_version_id: edit_res.previous_version_id,
|
previous_version_id: edit_res.previous_version_id,
|
||||||
resolve_conflict_id: Some(edit_res.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);
|
assert_eq!(edit_form.new_text, edit_res.article.text);
|
||||||
|
|
||||||
let conflicts: Vec<ApiConflict> =
|
let conflicts: Vec<ApiConflict> =
|
||||||
|
@ -307,13 +307,13 @@ async fn test_local_edit_conflict() -> MyResult<()> {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_federated_edit_conflict() -> MyResult<()> {
|
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?;
|
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.beta.hostname, title.clone()).await?;
|
let create_res = create_article(&data.beta, 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);
|
||||||
|
|
||||||
|
@ -336,7 +336,7 @@ async fn test_federated_edit_conflict() -> MyResult<()> {
|
||||||
previous_version_id: create_res.latest_version.clone(),
|
previous_version_id: create_res.latest_version.clone(),
|
||||||
resolve_conflict_id: None,
|
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!(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);
|
||||||
|
@ -353,7 +353,7 @@ async fn test_federated_edit_conflict() -> MyResult<()> {
|
||||||
previous_version_id: create_res.latest_version,
|
previous_version_id: create_res.latest_version,
|
||||||
resolve_conflict_id: None,
|
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_ne!(edit_form.new_text, edit_res.article.text);
|
||||||
assert_eq!(1, edit_res.edits.len());
|
assert_eq!(1, edit_res.edits.len());
|
||||||
assert!(!edit_res.article.local);
|
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(),
|
previous_version_id: conflicts[0].previous_version_id.clone(),
|
||||||
resolve_conflict_id: Some(conflicts[0].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!(edit_form.new_text, edit_res.article.text);
|
||||||
assert_eq!(3, edit_res.edits.len());
|
assert_eq!(3, edit_res.edits.len());
|
||||||
|
|
||||||
|
@ -382,11 +382,11 @@ async fn test_federated_edit_conflict() -> MyResult<()> {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_overlapping_edits_no_conflict() -> MyResult<()> {
|
async fn test_overlapping_edits_no_conflict() -> MyResult<()> {
|
||||||
let data = TestData::start();
|
let data = TestData::start().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.alpha.hostname, title.clone()).await?;
|
let create_res = create_article(&data.alpha, 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);
|
||||||
|
|
||||||
|
@ -397,7 +397,7 @@ async fn test_overlapping_edits_no_conflict() -> MyResult<()> {
|
||||||
previous_version_id: create_res.latest_version.clone(),
|
previous_version_id: create_res.latest_version.clone(),
|
||||||
resolve_conflict_id: None,
|
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!(edit_res.article.text, edit_form.new_text);
|
||||||
assert_eq!(2, edit_res.edits.len());
|
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,
|
previous_version_id: create_res.latest_version,
|
||||||
resolve_conflict_id: None,
|
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> =
|
let conflicts: Vec<ApiConflict> =
|
||||||
get_query(&data.alpha.hostname, "edit_conflicts", None::<()>).await?;
|
get_query(&data.alpha.hostname, "edit_conflicts", None::<()>).await?;
|
||||||
assert_eq!(0, conflicts.len());
|
assert_eq!(0, conflicts.len());
|
||||||
|
@ -420,11 +420,11 @@ async fn test_overlapping_edits_no_conflict() -> MyResult<()> {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_fork_article() -> MyResult<()> {
|
async fn test_fork_article() -> MyResult<()> {
|
||||||
let data = TestData::start();
|
let data = TestData::start().await;
|
||||||
|
|
||||||
// create article
|
// create article
|
||||||
let title = "Manu_Chao".to_string();
|
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!(title, create_res.article.title);
|
||||||
assert!(create_res.article.local);
|
assert!(create_res.article.local);
|
||||||
|
|
||||||
|
@ -469,7 +469,7 @@ async fn test_fork_article() -> MyResult<()> {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_user_registration_login() -> MyResult<()> {
|
async fn test_user_registration_login() -> MyResult<()> {
|
||||||
let data = TestData::start();
|
let data = TestData::start().await;
|
||||||
let register_form = RegisterUserData {
|
let register_form = RegisterUserData {
|
||||||
name: "my_user".to_string(),
|
name: "my_user".to_string(),
|
||||||
password: "hunter2".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?;
|
let valid_login: LoginResponse = post(&data.alpha.hostname, "user/login", &login_form).await?;
|
||||||
assert!(!valid_login.jwt.is_empty());
|
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()
|
data.stop()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue