Merge pull request 'Add ActivityPub tests and a little fixes' (#1021) from ap-tests into main

Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1021
This commit is contained in:
KitaitiMakoto 2022-02-05 09:17:27 +00:00
commit 69eccc50a3
13 changed files with 920 additions and 42 deletions

39
Cargo.lock generated
View File

@ -16,6 +16,21 @@ dependencies = [
"serde_json",
]
[[package]]
name = "activitystreams"
version = "0.7.0-alpha.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bcc3fbb392890a1942b1e5cca76cba93c8ed24b5ff50004cc3289afaab3f92c"
dependencies = [
"activitystreams-kinds",
"chrono",
"mime 0.3.16",
"serde 1.0.133",
"serde_json",
"thiserror",
"url 2.2.2",
]
[[package]]
name = "activitystreams-derive"
version = "0.1.1"
@ -27,6 +42,16 @@ dependencies = [
"syn 0.13.11",
]
[[package]]
name = "activitystreams-kinds"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0784e99afd032199d3ed70cefb8eb3a8d1aef15f7f2c4e68d033c4e12bb6079e"
dependencies = [
"serde 1.0.133",
"url 2.2.2",
]
[[package]]
name = "activitystreams-traits"
version = "0.1.0"
@ -205,6 +230,16 @@ version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a1bb320f97e6edf9f756bf015900038e43c7700e059688e5724a928c8f3b8d5"
[[package]]
name = "assert-json-diff"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50f1c3703dd33532d7f0ca049168930e9099ecac238e23cf932f3a69c42f06da"
dependencies = [
"serde 1.0.133",
"serde_json",
]
[[package]]
name = "async-trait"
version = "0.1.52"
@ -3136,6 +3171,7 @@ name = "plume-common"
version = "0.7.1"
dependencies = [
"activitypub",
"activitystreams",
"activitystreams-derive",
"activitystreams-traits",
"array_tool",
@ -3190,7 +3226,9 @@ name = "plume-models"
version = "0.7.1"
dependencies = [
"activitypub",
"activitystreams",
"ammonia",
"assert-json-diff",
"bcrypt",
"chrono",
"diesel",
@ -5232,6 +5270,7 @@ dependencies = [
"idna 0.2.3",
"matches",
"percent-encoding 2.1.0",
"serde 1.0.133",
]
[[package]]

View File

@ -24,6 +24,7 @@ tokio = "0.1.22"
regex-syntax = { version = "0.6.17", default-features = false, features = ["unicode-perl"] }
tracing = "0.1.30"
askama_escape = "0.10.2"
activitystreams = "0.7.0-alpha.14"
[dependencies.chrono]
features = ["serde"]

View File

@ -271,7 +271,7 @@ pub fn md_to_html<'a>(
media_processor: Option<MediaProcessor<'a>>,
) -> (String, HashSet<String>, HashSet<String>) {
let base_url = if let Some(base_url) = base_url {
format!("//{}/", base_url)
format!("https://{}/", base_url)
} else {
"/".to_owned()
};

View File

@ -35,6 +35,7 @@ riker = "0.4.2"
once_cell = "1.5.2"
lettre = "0.9.6"
native-tls = "0.2.8"
activitystreams = "0.7.0-alpha.14"
[dependencies.chrono]
features = ["serde"]
@ -54,6 +55,7 @@ path = "../plume-common"
path = "../plume-macro"
[dev-dependencies]
assert-json-diff = "2.0.1"
diesel_migrations = "1.3.0"
[features]

View File

@ -16,7 +16,7 @@ use activitypub::{
link,
object::{Note, Tombstone},
};
use chrono::{self, NaiveDateTime};
use chrono::{self, NaiveDateTime, TimeZone, Utc};
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl};
use plume_common::{
activity_pub::{
@ -59,7 +59,7 @@ impl Comment {
insert!(comments, NewComment, |inserted, conn| {
if inserted.ap_url.is_none() {
inserted.ap_url = Some(format!(
"{}comment/{}",
"{}/comment/{}",
inserted.get_post(conn)?.ap_url,
inserted.id
));
@ -129,7 +129,7 @@ impl Comment {
|id| Ok(Comment::get(conn, id)?.ap_url.unwrap_or_default()) as Result<String>,
)?))?;
note.object_props
.set_published_string(chrono::Utc::now().to_rfc3339())?;
.set_published_utctime(Utc.from_utc_datetime(&self.creation_date))?;
note.object_props.set_attributed_to_link(author.into_id())?;
note.object_props.set_to_link_vec(to)?;
note.object_props.set_tag_link_vec(
@ -402,23 +402,21 @@ impl CommentTree {
#[cfg(test)]
mod tests {
use super::*;
use crate::blogs::Blog;
use crate::inbox::{inbox, tests::fill_database, InboxResult};
use crate::safe_string::SafeString;
use crate::tests::db;
use crate::tests::{db, format_datetime};
use assert_json_diff::assert_json_eq;
use diesel::Connection;
use serde_json::{json, to_value};
// creates a post, get it's Create activity, delete the post,
// "send" the Create to the inbox, and check it works
#[test]
fn self_federation() {
let conn = &db();
conn.test_transaction::<_, (), _>(|| {
let (posts, users, _) = fill_database(&conn);
fn prepare_activity(conn: &DbConn) -> (Comment, Vec<Post>, Vec<User>, Vec<Blog>) {
let (posts, users, blogs) = fill_database(&conn);
let original_comm = Comment::insert(
let comment = Comment::insert(
conn,
NewComment {
content: SafeString::new("My comment"),
content: SafeString::new("My comment, mentioning to @user"),
in_response_to_id: None,
post_id: posts[0].id,
author_id: users[0].id,
@ -429,14 +427,87 @@ mod tests {
},
)
.unwrap();
(comment, posts, users, blogs)
}
// creates a post, get it's Create activity, delete the post,
// "send" the Create to the inbox, and check it works
#[test]
fn self_federation() {
let conn = &db();
conn.test_transaction::<_, (), _>(|| {
let (original_comm, posts, users, _blogs) = prepare_activity(&conn);
let act = original_comm.create_activity(&conn).unwrap();
assert_json_eq!(to_value(&act).unwrap(), json!({
"actor": "https://plu.me/@/admin/",
"cc": ["https://plu.me/@/admin/followers"],
"id": format!("https://plu.me/~/BlogName/testing/comment/{}/activity", original_comm.id),
"object": {
"attributedTo": "https://plu.me/@/admin/",
"content": r###"<p dir="auto">My comment, mentioning to <a href="https://plu.me/@/user/" title="user">@user</a></p>
"###,
"id": format!("https://plu.me/~/BlogName/testing/comment/{}", original_comm.id),
"inReplyTo": "https://plu.me/~/BlogName/testing",
"published": format_datetime(&original_comm.creation_date),
"summary": "My CW",
"tag": [
{
"href": "https://plu.me/@/user/",
"name": "@user",
"type": "Mention"
}
],
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Note"
},
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Create",
}));
let reply = Comment::insert(
conn,
NewComment {
content: SafeString::new(""),
in_response_to_id: Some(original_comm.id),
post_id: posts[0].id,
author_id: users[1].id,
ap_url: None,
sensitive: false,
spoiler_text: "".into(),
public_visibility: true,
},
)
.unwrap();
let reply_act = reply.create_activity(&conn).unwrap();
assert_json_eq!(to_value(&reply_act).unwrap(), json!({
"actor": "https://plu.me/@/user/",
"cc": ["https://plu.me/@/user/followers"],
"id": format!("https://plu.me/~/BlogName/testing/comment/{}/activity", reply.id),
"object": {
"attributedTo": "https://plu.me/@/user/",
"content": "",
"id": format!("https://plu.me/~/BlogName/testing/comment/{}", reply.id),
"inReplyTo": format!("https://plu.me/~/BlogName/testing/comment/{}", original_comm.id),
"published": format_datetime(&reply.creation_date),
"summary": "",
"tag": [],
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Note"
},
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Create"
}));
inbox(
&conn,
serde_json::to_value(original_comm.build_delete(&conn).unwrap()).unwrap(),
)
.unwrap();
match inbox(&conn, serde_json::to_value(act).unwrap()).unwrap() {
match inbox(&conn, to_value(act).unwrap()).unwrap() {
InboxResult::Commented(c) => {
// TODO: one is HTML, the other markdown: assert_eq!(c.content, original_comm.content);
assert_eq!(c.in_response_to_id, original_comm.in_response_to_id);
@ -451,4 +522,60 @@ mod tests {
Ok(())
})
}
#[test]
fn to_activity() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (comment, _posts, _users, _blogs) = prepare_activity(&conn);
let act = comment.to_activity(&conn)?;
let expected = json!({
"attributedTo": "https://plu.me/@/admin/",
"content": r###"<p dir="auto">My comment, mentioning to <a href="https://plu.me/@/user/" title="user">@user</a></p>
"###,
"id": format!("https://plu.me/~/BlogName/testing/comment/{}", comment.id),
"inReplyTo": "https://plu.me/~/BlogName/testing",
"published": format_datetime(&comment.creation_date),
"summary": "My CW",
"tag": [
{
"href": "https://plu.me/@/user/",
"name": "@user",
"type": "Mention"
}
],
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Note"
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
#[test]
fn build_delete() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (comment, _posts, _users, _blogs) = prepare_activity(&conn);
let act = comment.build_delete(&conn)?;
let expected = json!({
"actor": "https://plu.me/@/admin/",
"id": format!("https://plu.me/~/BlogName/testing/comment/{}#delete", comment.id),
"object": {
"id": format!("https://plu.me/~/BlogName/testing/comment/{}", comment.id),
"type": "Tombstone"
},
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Delete"
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
}

View File

@ -99,11 +99,27 @@ impl Follow {
)?;
res.notify(conn)?;
let accept = res.build_accept(from, target, follow)?;
broadcast(
&*target,
accept,
vec![from.clone()],
CONFIG.proxy().cloned(),
);
Ok(res)
}
pub fn build_accept<A: Signer + IntoId + Clone, B: Clone + AsActor<T> + IntoId, T>(
&self,
from: &B,
target: &A,
follow: FollowAct,
) -> Result<Accept> {
let mut accept = Accept::default();
let accept_id = ap_url(&format!(
"{}/follow/{}/accept",
"{}/follows/{}/accept",
CONFIG.base_url.as_str(),
&res.id
self.id
));
accept.object_props.set_id_string(accept_id)?;
accept
@ -116,13 +132,8 @@ impl Follow {
.accept_props
.set_actor_link::<Id>(target.clone().into_id())?;
accept.accept_props.set_object_object(follow)?;
broadcast(
&*target,
accept,
vec![from.clone()],
CONFIG.proxy().cloned(),
);
Ok(res)
Ok(accept)
}
pub fn build_undo(&self, conn: &Connection) -> Result<Undo> {
@ -219,8 +230,29 @@ impl IntoId for Follow {
#[cfg(test)]
mod tests {
use super::*;
use crate::{tests::db, users::tests as user_tests};
use crate::{tests::db, users::tests as user_tests, users::tests::fill_database};
use assert_json_diff::assert_json_eq;
use diesel::Connection;
use serde_json::{json, to_value};
fn prepare_activity(conn: &DbConn) -> (Follow, User, User, Vec<User>) {
let users = fill_database(conn);
let following = &users[1];
let follower = &users[2];
let mut follow = Follow::insert(
conn,
NewFollow {
follower_id: follower.id,
following_id: following.id,
ap_url: "".into(),
},
)
.unwrap();
// following.ap_url = format!("https://plu.me/follows/{}", follow.id);
follow.ap_url = format!("https://plu.me/follows/{}", follow.id);
(follow, following.to_owned(), follower.to_owned(), users)
}
#[test]
fn test_id() {
@ -255,4 +287,77 @@ mod tests {
Ok(())
})
}
#[test]
fn to_activity() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (follow, _following, _follower, _users) = prepare_activity(&conn);
let act = follow.to_activity(&conn)?;
let expected = json!({
"actor": "https://plu.me/@/other/",
"cc": ["https://www.w3.org/ns/activitystreams#Public"],
"id": format!("https://plu.me/follows/{}", follow.id),
"object": "https://plu.me/@/user/",
"to": ["https://plu.me/@/user/"],
"type": "Follow"
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
#[test]
fn build_accept() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (follow, following, follower, _users) = prepare_activity(&conn);
let act = follow.build_accept(&follower, &following, follow.to_activity(&conn)?)?;
let expected = json!({
"actor": "https://plu.me/@/user/",
"cc": ["https://www.w3.org/ns/activitystreams#Public"],
"id": format!("https://127.0.0.1:7878/follows/{}/accept", follow.id),
"object": {
"actor": "https://plu.me/@/other/",
"cc": ["https://www.w3.org/ns/activitystreams#Public"],
"id": format!("https://plu.me/follows/{}", follow.id),
"object": "https://plu.me/@/user/",
"to": ["https://plu.me/@/user/"],
"type": "Follow"
},
"to": ["https://plu.me/@/other/"],
"type": "Accept"
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
#[test]
fn build_undo() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (follow, _following, _follower, _users) = prepare_activity(&conn);
let act = follow.build_undo(&conn)?;
let expected = json!({
"actor": "https://plu.me/@/other/",
"cc": ["https://www.w3.org/ns/activitystreams#Public"],
"id": format!("https://plu.me/follows/{}/undo", follow.id),
"object": format!("https://plu.me/follows/{}", follow.id),
"to": ["https://plu.me/@/user/"],
"type": "Undo"
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
}

View File

@ -334,6 +334,7 @@ impl SmtpNewWithAddr for smtp::SmtpClient {
#[macro_use]
mod tests {
use crate::{db_conn, migrations::IMPORTED_MIGRATIONS, Connection as Conn, CONFIG};
use chrono::{naive::NaiveDateTime, Datelike, Timelike};
use diesel::r2d2::ConnectionManager;
use plume_common::utils::random_hex;
use std::env::temp_dir;
@ -366,6 +367,33 @@ mod tests {
pool
};
}
#[cfg(feature = "postgres")]
pub(crate) fn format_datetime(dt: &NaiveDateTime) -> String {
format!(
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:06}Z",
dt.year(),
dt.month(),
dt.day(),
dt.hour(),
dt.minute(),
dt.second(),
dt.timestamp_subsec_micros()
)
}
#[cfg(feature = "sqlite")]
pub(crate) fn format_datetime(dt: &NaiveDateTime) -> String {
format!(
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
dt.year(),
dt.month(),
dt.day(),
dt.hour(),
dt.minute(),
dt.second()
)
}
}
pub mod admin;

View File

@ -165,8 +165,7 @@ impl AsObject<User, activity::Undo, &DbConn> for Like {
impl NewLike {
pub fn new(p: &Post, u: &User) -> Self {
// TODO: this URL is not valid
let ap_url = format!("{}/like/{}", u.ap_url, p.ap_url);
let ap_url = format!("{}like/{}", u.ap_url, p.ap_url);
NewLike {
post_id: p.id,
user_id: u.id,
@ -174,3 +173,67 @@ impl NewLike {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::diesel::Connection;
use crate::{inbox::tests::fill_database, tests::db};
use assert_json_diff::assert_json_eq;
use serde_json::{json, to_value};
#[test]
fn to_activity() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (posts, _users, _blogs) = fill_database(&conn);
let post = &posts[0];
let user = &post.get_authors(&conn)?[0];
let like = Like::insert(&*conn, NewLike::new(post, user))?;
let act = like.to_activity(&conn).unwrap();
let expected = json!({
"actor": "https://plu.me/@/admin/",
"cc": ["https://plu.me/@/admin/followers"],
"id": "https://plu.me/@/admin/like/https://plu.me/~/BlogName/testing",
"object": "https://plu.me/~/BlogName/testing",
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Like",
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
#[test]
fn build_undo() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (posts, _users, _blogs) = fill_database(&conn);
let post = &posts[0];
let user = &post.get_authors(&conn)?[0];
let like = Like::insert(&*conn, NewLike::new(post, user))?;
let act = like.build_undo(&*conn)?;
let expected = json!({
"actor": "https://plu.me/@/admin/",
"cc": ["https://plu.me/@/admin/followers"],
"id": "https://plu.me/@/admin/like/https://plu.me/~/BlogName/testing#delete",
"object": {
"actor": "https://plu.me/@/admin/",
"cc": ["https://plu.me/@/admin/followers"],
"id": "https://plu.me/@/admin/like/https://plu.me/~/BlogName/testing",
"object": "https://plu.me/~/BlogName/testing",
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Like",
},
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Undo",
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
}

View File

@ -400,7 +400,15 @@ pub(crate) mod tests {
pub(crate) fn clean(conn: &Conn) {
//used to remove files generated by tests
for media in Media::list_all_medias(conn).unwrap() {
media.delete(conn).unwrap();
if let Some(err) = media.delete(conn).err() {
match &err {
Error::Io(e) => match e.kind() {
std::io::ErrorKind::NotFound => (),
_ => panic!("{:?}", err),
},
_ => panic!("{:?}", err),
}
}
}
}

View File

@ -145,3 +145,62 @@ impl Mention {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{inbox::tests::fill_database, tests::db, Error};
use assert_json_diff::assert_json_eq;
use diesel::Connection;
use serde_json::{json, to_value};
#[test]
fn build_activity() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (_posts, users, _blogs) = fill_database(&conn);
let user = &users[0];
let name = &user.username;
let act = Mention::build_activity(&conn, name)?;
let expected = json!({
"href": "https://plu.me/@/admin/",
"name": "@admin",
"type": "Mention",
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
#[test]
fn to_activity() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (posts, users, _blogs) = fill_database(&conn);
let post = &posts[0];
let user = &users[0];
let mention = Mention::insert(
&conn,
NewMention {
mentioned_id: user.id,
post_id: Some(post.id),
comment_id: None,
},
)?;
let act = mention.to_activity(&conn)?;
let expected = json!({
"href": "https://plu.me/@/admin/",
"name": "@admin",
"type": "Mention",
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
}

View File

@ -413,7 +413,7 @@ impl Post {
let article = self.to_activity(conn)?;
let mut act = Create::default();
act.object_props
.set_id_string(format!("{}activity", self.ap_url))?;
.set_id_string(format!("{}/activity", self.ap_url))?;
act.object_props
.set_to_link_vec::<Id>(article.object.object_props.to_link_vec()?)?;
act.object_props
@ -942,9 +942,28 @@ impl From<PostEvent> for Arc<Post> {
mod tests {
use super::*;
use crate::inbox::{inbox, tests::fill_database, InboxResult};
use crate::mentions::{Mention, NewMention};
use crate::safe_string::SafeString;
use crate::tests::db;
use crate::tests::{db, format_datetime};
use assert_json_diff::assert_json_eq;
use diesel::Connection;
use serde_json::{json, to_value};
fn prepare_activity(conn: &DbConn) -> (Post, Mention, Vec<Post>, Vec<User>, Vec<Blog>) {
let (posts, users, blogs) = fill_database(conn);
let post = &posts[0];
let mentioned = &users[1];
let mention = Mention::insert(
&conn,
NewMention {
mentioned_id: mentioned.id,
post_id: Some(post.id),
comment_id: None,
},
)
.unwrap();
(post.to_owned(), mention.to_owned(), posts, users, blogs)
}
// creates a post, get it's Create activity, delete the post,
// "send" the Create to the inbox, and check it works
@ -1038,4 +1057,153 @@ mod tests {
&article.object.object_props.id_string().unwrap()
);
}
#[test]
fn to_activity() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (post, _mention, _posts, _users, _blogs) = prepare_activity(&conn);
let act = post.to_activity(&conn)?;
let expected = json!({
"attributedTo": ["https://plu.me/@/admin/", "https://plu.me/~/BlogName/"],
"cc": [],
"content": "Hello",
"id": "https://plu.me/~/BlogName/testing",
"license": "WTFPL",
"name": "Testing",
"published": format_datetime(&post.creation_date),
"source": {
"content": "",
"mediaType": "text/markdown"
},
"summary": "",
"tag": [
{
"href": "https://plu.me/@/user/",
"name": "@user",
"type": "Mention"
}
],
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Article",
"url": "https://plu.me/~/BlogName/testing"
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
#[test]
fn create_activity() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (post, _mention, _posts, _users, _blogs) = prepare_activity(&conn);
let act = post.create_activity(&conn)?;
let expected = json!({
"actor": "https://plu.me/@/admin/",
"cc": [],
"id": "https://plu.me/~/BlogName/testing/activity",
"object": {
"attributedTo": ["https://plu.me/@/admin/", "https://plu.me/~/BlogName/"],
"cc": [],
"content": "Hello",
"id": "https://plu.me/~/BlogName/testing",
"license": "WTFPL",
"name": "Testing",
"published": format_datetime(&post.creation_date),
"source": {
"content": "",
"mediaType": "text/markdown"
},
"summary": "",
"tag": [
{
"href": "https://plu.me/@/user/",
"name": "@user",
"type": "Mention"
}
],
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Article",
"url": "https://plu.me/~/BlogName/testing"
},
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Create"
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
#[test]
fn update_activity() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (post, _mention, _posts, _users, _blogs) = prepare_activity(&conn);
let act = post.update_activity(&conn)?;
let expected = json!({
"actor": "https://plu.me/@/admin/",
"cc": [],
"id": "https://plu.me/~/BlogName/testing/update-",
"object": {
"attributedTo": ["https://plu.me/@/admin/", "https://plu.me/~/BlogName/"],
"cc": [],
"content": "Hello",
"id": "https://plu.me/~/BlogName/testing",
"license": "WTFPL",
"name": "Testing",
"published": format_datetime(&post.creation_date),
"source": {
"content": "",
"mediaType": "text/markdown"
},
"summary": "",
"tag": [
{
"href": "https://plu.me/@/user/",
"name": "@user",
"type": "Mention"
}
],
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Article",
"url": "https://plu.me/~/BlogName/testing"
},
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Update"
});
let actual = to_value(act)?;
let id = actual["id"].to_string();
let (id_pre, id_post) = id.rsplit_once("-").unwrap();
assert_eq!(post.ap_url, "https://plu.me/~/BlogName/testing");
assert_eq!(
id_pre,
to_value("\"https://plu.me/~/BlogName/testing/update")
.unwrap()
.as_str()
.unwrap()
);
assert_eq!(id_post.len(), 11);
assert_eq!(
id_post.matches(char::is_numeric).collect::<String>().len(),
10
);
for (key, value) in actual.as_object().unwrap().into_iter() {
if key == "id" {
continue;
}
assert_eq!(value, expected.get(key).unwrap());
}
Ok(())
});
}
}

View File

@ -191,7 +191,7 @@ impl AsObject<User, Undo, &DbConn> for Reshare {
impl NewReshare {
pub fn new(p: &Post, u: &User) -> Self {
let ap_url = format!("{}/reshare/{}", u.ap_url, p.ap_url);
let ap_url = format!("{}reshare/{}", u.ap_url, p.ap_url);
NewReshare {
post_id: p.id,
user_id: u.id,
@ -199,3 +199,67 @@ impl NewReshare {
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::diesel::Connection;
use crate::{inbox::tests::fill_database, tests::db};
use assert_json_diff::assert_json_eq;
use serde_json::{json, to_value};
#[test]
fn to_activity() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (posts, _users, _blogs) = fill_database(&conn);
let post = &posts[0];
let user = &post.get_authors(&conn)?[0];
let reshare = Reshare::insert(&*conn, NewReshare::new(post, user))?;
let act = reshare.to_activity(&conn).unwrap();
let expected = json!({
"actor": "https://plu.me/@/admin/",
"cc": ["https://plu.me/@/admin/followers"],
"id": "https://plu.me/@/admin/reshare/https://plu.me/~/BlogName/testing",
"object": "https://plu.me/~/BlogName/testing",
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Announce",
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
#[test]
fn build_undo() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (posts, _users, _blogs) = fill_database(&conn);
let post = &posts[0];
let user = &post.get_authors(&conn)?[0];
let reshare = Reshare::insert(&*conn, NewReshare::new(post, user))?;
let act = reshare.build_undo(&*conn)?;
let expected = json!({
"actor": "https://plu.me/@/admin/",
"cc": ["https://plu.me/@/admin/followers"],
"id": "https://plu.me/@/admin/reshare/https://plu.me/~/BlogName/testing#delete",
"object": {
"actor": "https://plu.me/@/admin/",
"cc": ["https://plu.me/@/admin/followers"],
"id": "https://plu.me/@/admin/reshare/https://plu.me/~/BlogName/testing",
"object": "https://plu.me/~/BlogName/testing",
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Announce"
},
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Undo",
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
}

View File

@ -429,6 +429,9 @@ impl User {
.map_err(Error::from)
}
pub fn outbox(&self, conn: &Connection) -> Result<ActivityStream<OrderedCollection>> {
Ok(ActivityStream::new(self.outbox_collection(conn)?))
}
pub fn outbox_collection(&self, conn: &Connection) -> Result<OrderedCollection> {
let mut coll = OrderedCollection::default();
let first = &format!("{}?page=1", &self.outbox_url);
let last = &format!(
@ -440,13 +443,22 @@ impl User {
coll.collection_props.set_last_link(Id::new(last))?;
coll.collection_props
.set_total_items_u64(self.get_activities_count(conn) as u64)?;
Ok(ActivityStream::new(coll))
Ok(coll)
}
pub fn outbox_page(
&self,
conn: &Connection,
(min, max): (i32, i32),
) -> Result<ActivityStream<OrderedCollectionPage>> {
Ok(ActivityStream::new(
self.outbox_collection_page(conn, (min, max))?,
))
}
pub fn outbox_collection_page(
&self,
conn: &Connection,
(min, max): (i32, i32),
) -> Result<OrderedCollectionPage> {
let acts = self.get_activities_page(conn, (min, max))?;
let n_acts = self.get_activities_count(conn);
let mut coll = OrderedCollectionPage::default();
@ -467,7 +479,7 @@ impl User {
coll.collection_props.items = serde_json::to_value(acts)?;
coll.collection_page_props
.set_part_of_link(Id::new(&self.outbox_url))?;
Ok(ActivityStream::new(coll))
Ok(coll)
}
fn fetch_outbox_page<T: Activity>(&self, url: &str) -> Result<(Vec<T>, Option<String>)> {
let mut res = get(url, Self::get_sender(), CONFIG.proxy().cloned())?;
@ -763,13 +775,13 @@ impl User {
let mut ap_signature = ApSignature::default();
ap_signature.set_public_key_publickey(public_key)?;
if let Some(avatar_id) = self.avatar_id {
let mut avatar = Image::default();
avatar.object_props.set_url_string(
self.avatar_id
.and_then(|id| Media::get(conn, id).and_then(|m| m.url()).ok())
.unwrap_or_default(),
)?;
avatar
.object_props
.set_url_string(Media::get(conn, avatar_id)?.url()?)?;
actor.object_props.set_icon_object(avatar)?;
}
Ok(CustomPerson::new(actor, ap_signature))
}
@ -1142,10 +1154,13 @@ pub(crate) mod tests {
use super::*;
use crate::{
instance::{tests as instance_tests, Instance},
medias::{Media, NewMedia},
tests::db,
Connection as Conn,
Connection as Conn, ITEMS_PER_PAGE,
};
use diesel::Connection;
use assert_json_diff::assert_json_eq;
use diesel::{Connection, SaveChangesDsl};
use serde_json::to_value;
pub(crate) fn fill_database(conn: &Conn) -> Vec<User> {
instance_tests::fill_database(conn);
@ -1169,7 +1184,7 @@ pub(crate) mod tests {
Some("invalid_user_password".to_owned()),
)
.unwrap();
let other = NewUser::new_local(
let mut other = NewUser::new_local(
conn,
"other".to_owned(),
"Another user".to_owned(),
@ -1179,9 +1194,73 @@ pub(crate) mod tests {
Some("invalid_other_password".to_owned()),
)
.unwrap();
let avatar = Media::insert(
conn,
NewMedia {
file_path: "static/media/example.png".into(),
alt_text: "Another user".into(),
is_remote: false,
remote_url: None,
sensitive: false,
content_warning: None,
owner_id: other.id,
},
)
.unwrap();
other.avatar_id = Some(avatar.id);
let other = other.save_changes::<User>(&*conn).unwrap();
vec![admin, user, other]
}
fn fill_pages(
conn: &DbConn,
) -> (
Vec<crate::posts::Post>,
Vec<crate::users::User>,
Vec<crate::blogs::Blog>,
) {
use crate::post_authors::NewPostAuthor;
use crate::posts::NewPost;
let (mut posts, users, blogs) = crate::inbox::tests::fill_database(conn);
let user = &users[0];
let blog = &blogs[0];
for i in 1..(ITEMS_PER_PAGE * 4 + 3) {
let title = format!("Post {}", i);
let content = format!("Content for post {}.", i);
let post = Post::insert(
conn,
NewPost {
blog_id: blog.id,
slug: title.clone(),
title: title.clone(),
content: SafeString::new(&content),
published: true,
license: "CC-0".into(),
creation_date: None,
ap_url: format!("{}/{}", blog.ap_url, title),
subtitle: "".into(),
source: content,
cover_id: None,
},
)
.unwrap();
PostAuthor::insert(
conn,
NewPostAuthor {
post_id: post.id,
author_id: user.id,
},
)
.unwrap();
posts.push(post);
}
(posts, users, blogs)
}
#[test]
fn find_by() {
let conn = db();
@ -1342,4 +1421,139 @@ pub(crate) mod tests {
Ok(())
});
}
#[test]
fn to_activity() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let users = fill_database(&conn);
let user = &users[0];
let act = user.to_activity(&conn)?;
let expected = json!({
"endpoints": {
"sharedInbox": "https://plu.me/inbox"
},
"followers": "https://plu.me/@/admin/followers",
"following": null,
"id": "https://plu.me/@/admin/",
"inbox": "https://plu.me/@/admin/inbox",
"liked": null,
"name": "The admin",
"outbox": "https://plu.me/@/admin/outbox",
"preferredUsername": "admin",
"publicKey": {
"id": "https://plu.me/@/admin/#main-key",
"owner": "https://plu.me/@/admin/",
"publicKeyPem": user.public_key,
},
"summary": "<p dir=\"auto\">Hello there, Im the admin</p>\n",
"type": "Person",
"url": "https://plu.me/@/admin/"
});
assert_json_eq!(to_value(act)?, expected);
let other = &users[2];
let other_act = other.to_activity(&conn)?;
let expected_other = json!({
"endpoints": {
"sharedInbox": "https://plu.me/inbox"
},
"followers": "https://plu.me/@/other/followers",
"following": null,
"icon": {
"url": "https://plu.me/static/media/example.png",
"type": "Image",
},
"id": "https://plu.me/@/other/",
"inbox": "https://plu.me/@/other/inbox",
"liked": null,
"name": "Another user",
"outbox": "https://plu.me/@/other/outbox",
"preferredUsername": "other",
"publicKey": {
"id": "https://plu.me/@/other/#main-key",
"owner": "https://plu.me/@/other/",
"publicKeyPem": other.public_key,
},
"summary": "<p dir=\"auto\">Hello there, Im someone else</p>\n",
"type": "Person",
"url": "https://plu.me/@/other/"
});
assert_json_eq!(to_value(other_act)?, expected_other);
Ok(())
});
}
#[test]
fn delete_activity() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let users = fill_database(&conn);
let user = &users[1];
let act = user.delete_activity(&conn)?;
let expected = json!({
"actor": "https://plu.me/@/user/",
"cc": [],
"id": "https://plu.me/@/user/#delete",
"object": {
"id": "https://plu.me/@/user/",
"type": "Tombstone",
},
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Delete",
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
#[test]
fn outbox_collection() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (_pages, users, _blogs) = fill_pages(&conn);
let user = &users[0];
let act = user.outbox_collection(&conn)?;
let expected = json!({
"first": "https://plu.me/@/admin/outbox?page=1",
"items": null,
"last": "https://plu.me/@/admin/outbox?page=5",
"totalItems": 51,
"type": "OrderedCollection",
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
#[test]
fn outbox_collection_page() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let users = fill_database(&conn);
let user = &users[0];
let act = user.outbox_collection_page(&conn, (33, 36))?;
let expected = json!({
"items": [],
"partOf": "https://plu.me/@/admin/outbox",
"prev": "https://plu.me/@/admin/outbox?page=2",
"type": "OrderedCollectionPage",
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
}