Introduce group posts

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2020-07-09 17:24:28 +02:00
parent bec1c69d4b
commit 9c9f1385fb
249 changed files with 11886 additions and 5023 deletions

View File

@@ -8,9 +8,10 @@ defmodule Mobilizon.Federation.ActivityPubTest do
use Mobilizon.DataCase
import Mock
import Mox
import Mobilizon.Factory
alias Mobilizon.{Actors, Conversations, Events}
alias Mobilizon.{Actors, Discussions, Events}
alias Mobilizon.Actors.Actor
alias Mobilizon.Resources.Resource
alias Mobilizon.Todos.{Todo, TodoList}
@@ -18,13 +19,10 @@ defmodule Mobilizon.Federation.ActivityPubTest do
alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.Utils
alias Mobilizon.Federation.HTTPSignatures.Signature
alias Mobilizon.Service.HTTP.ActivityPub.Mock
@activity_pub_public_audience "https://www.w3.org/ns/activitystreams#Public"
setup_all do
HTTPoison.start()
end
describe "setting HTTP signature" do
test "set http signature header" do
actor = insert(:actor)
@@ -140,40 +138,75 @@ defmodule Mobilizon.Federation.ActivityPubTest do
describe "fetching an" do
test "object by url" do
use_cassette "activity_pub/fetch_framapiaf_framasoft_status" do
{:ok, object} =
ActivityPub.fetch_object_from_url(
"https://framapiaf.org/users/Framasoft/statuses/102093631881522097"
)
url = "https://framapiaf.org/users/Framasoft/statuses/102093631881522097"
{:ok, object_again} =
ActivityPub.fetch_object_from_url(
"https://framapiaf.org/users/Framasoft/statuses/102093631881522097"
)
data =
File.read!("test/fixtures/mastodon-status-2.json")
|> Jason.decode!()
assert object.id == object_again.id
end
Mock
|> expect(:call, fn
%{method: :get, url: ^url}, _opts ->
{:ok, %Tesla.Env{status: 200, body: data}}
end)
{:ok, object} = ActivityPub.fetch_object_from_url(url)
{:ok, object_again} = ActivityPub.fetch_object_from_url(url)
assert object.id == object_again.id
end
test "object reply by url" do
use_cassette "activity_pub/fetch_framasoft_framapiaf_reply" do
{:ok, object} =
ActivityPub.fetch_object_from_url("https://mamot.fr/@imacrea/102094441327423790")
url = "https://zoltasila.pl/objects/1c295713-8e3c-411e-9e62-57a7b9c9e514"
reply_to_url = "https://framapiaf.org/users/peertube/statuses/104584600044284729"
assert object.in_reply_to_comment.url ==
"https://framapiaf.org/users/Framasoft/statuses/102093632302210150"
end
data =
File.read!("test/fixtures/mastodon-status-3.json")
|> Jason.decode!()
reply_to_data =
File.read!("test/fixtures/mastodon-status-4.json")
|> Jason.decode!()
Mock
|> expect(:call, 2, fn
%{method: :get, url: ^url}, _opts ->
{:ok, %Tesla.Env{status: 200, body: data}}
%{method: :get, url: ^reply_to_url}, _opts ->
{:ok, %Tesla.Env{status: 200, body: reply_to_data}}
end)
{:ok, object} = ActivityPub.fetch_object_from_url(url)
assert object.in_reply_to_comment.url == reply_to_url
end
test "object reply to a video by url" do
use_cassette "activity_pub/fetch_reply_to_framatube" do
{:ok, object} =
ActivityPub.fetch_object_from_url(
"https://diaspodon.fr/users/dada/statuses/100820008426311925"
)
url = "https://diaspodon.fr/users/dada/statuses/100820008426311925"
origin_url = "https://framatube.org/videos/watch/9c9de5e8-0a1e-484a-b099-e80766180a6d"
assert object.in_reply_to_comment == nil
end
data =
File.read!("test/fixtures/mastodon-status-5.json")
|> Jason.decode!()
origin_data =
File.read!("test/fixtures/peertube-video.json")
|> Jason.decode!()
Mock
|> expect(:call, 2, fn
%{method: :get, url: ^url}, _opts ->
{:ok, %Tesla.Env{status: 200, body: data}}
%{method: :get, url: ^origin_url}, _opts ->
{:ok, %Tesla.Env{status: 200, body: origin_data}}
end)
{:ok, object} = ActivityPub.fetch_object_from_url(url)
assert object.in_reply_to_comment == nil
end
end
@@ -181,26 +214,28 @@ defmodule Mobilizon.Federation.ActivityPubTest do
test "it creates a delete activity and deletes the original event" do
event = insert(:event)
event = Events.get_public_event_by_url_with_preload!(event.url)
{:ok, delete, _} = ActivityPub.delete(event)
{:ok, delete, _} = ActivityPub.delete(event, event.organizer_actor)
assert delete.data["type"] == "Delete"
assert delete.data["actor"] == event.organizer_actor.url
assert delete.data["object"] == event.url
assert delete.data["object"]["type"] == "Event"
assert delete.data["object"]["id"] == event.url
assert Events.get_event_by_url(event.url) == nil
end
test "it deletes the original event but only locally if needed" do
with_mock Utils,
with_mock Utils, [:passthrough],
maybe_federate: fn _ -> :ok end,
lazy_put_activity_defaults: fn args -> args end do
event = insert(:event)
event = Events.get_public_event_by_url_with_preload!(event.url)
{:ok, delete, _} = ActivityPub.delete(event, false)
{:ok, delete, _} = ActivityPub.delete(event, event.organizer_actor, false)
assert delete.data["type"] == "Delete"
assert delete.data["actor"] == event.organizer_actor.url
assert delete.data["object"] == event.url
assert delete.data["object"]["type"] == "Event"
assert delete.data["object"]["id"] == event.url
assert delete.local == false
assert Events.get_event_by_url(event.url) == nil
@@ -211,15 +246,16 @@ defmodule Mobilizon.Federation.ActivityPubTest do
test "it creates a delete activity and deletes the original comment" do
comment = insert(:comment)
comment = Conversations.get_comment_from_url_with_preload!(comment.url)
assert is_nil(Conversations.get_comment_from_url(comment.url).deleted_at)
{:ok, delete, _} = ActivityPub.delete(comment)
comment = Discussions.get_comment_from_url_with_preload!(comment.url)
assert is_nil(Discussions.get_comment_from_url(comment.url).deleted_at)
{:ok, delete, _} = ActivityPub.delete(comment, comment.actor)
assert delete.data["type"] == "Delete"
assert delete.data["actor"] == comment.actor.url
assert delete.data["object"] == comment.url
assert delete.data["object"]["type"] == "Note"
assert delete.data["object"]["id"] == comment.url
refute is_nil(Conversations.get_comment_from_url(comment.url).deleted_at)
refute is_nil(Discussions.get_comment_from_url(comment.url).deleted_at)
end
end
@@ -230,7 +266,7 @@ defmodule Mobilizon.Federation.ActivityPubTest do
actor = insert(:actor)
actor_data = %{summary: @updated_actor_summary}
{:ok, update, _} = ActivityPub.update(:actor, actor, actor_data, false)
{:ok, update, _} = ActivityPub.update(actor, actor_data, false)
assert update.data["actor"] == actor.url
assert update.data["to"] == [@activity_pub_public_audience]
@@ -246,7 +282,7 @@ defmodule Mobilizon.Federation.ActivityPubTest do
event = insert(:event, organizer_actor: actor)
event_data = %{begins_on: @updated_start_time}
{:ok, update, _} = ActivityPub.update(:event, event, event_data)
{:ok, update, _} = ActivityPub.update(event, event_data)
assert update.data["actor"] == actor.url
assert update.data["to"] == [@activity_pub_public_audience]
@@ -272,8 +308,8 @@ defmodule Mobilizon.Federation.ActivityPubTest do
assert create_data.local
assert create_data.data["object"]["id"] == todo_list_url
assert create_data.data["object"]["type"] == "TodoList"
assert create_data.data["object"]["title"] == @todo_list_title
assert create_data.data["to"] == [group.url]
assert create_data.data["object"]["name"] == @todo_list_title
assert create_data.data["to"] == [group.members_url]
assert create_data.data["actor"] == actor.url
assert_called(Utils.maybe_federate(create_data))
@@ -301,7 +337,7 @@ defmodule Mobilizon.Federation.ActivityPubTest do
assert create_data.data["object"]["id"] == todo_url
assert create_data.data["object"]["type"] == "Todo"
assert create_data.data["object"]["name"] == @todo_title
assert create_data.data["to"] == [todo_list.actor.url]
assert create_data.data["to"] == [todo_list.actor.members_url]
assert create_data.data["actor"] == actor.url
assert_called(Utils.maybe_federate(create_data))
@@ -341,7 +377,7 @@ defmodule Mobilizon.Federation.ActivityPubTest do
assert create_data.data["object"]["url"] == @resource_url
assert create_data.data["to"] == [group.url]
assert create_data.data["to"] == [group.members_url]
assert create_data.data["actor"] == actor.url
assert create_data.data["attributedTo"] == [actor.url]
@@ -372,7 +408,7 @@ defmodule Mobilizon.Federation.ActivityPubTest do
assert create_data.data["object"]["id"] == url
assert create_data.data["object"]["type"] == "ResourceCollection"
assert create_data.data["object"]["name"] == @folder_title
assert create_data.data["to"] == [group.url]
assert create_data.data["to"] == [group.members_url]
assert create_data.data["actor"] == actor.url
assert create_data.data["attributedTo"] == [actor.url]
@@ -411,7 +447,7 @@ defmodule Mobilizon.Federation.ActivityPubTest do
assert create_data.data["object"]["url"] == @resource_url
assert create_data.data["to"] == [group.url]
assert create_data.data["to"] == [group.members_url]
assert create_data.data["actor"] == actor.url
assert create_data.data["attributedTo"] == [actor.url]
@@ -437,7 +473,6 @@ defmodule Mobilizon.Federation.ActivityPubTest do
{:ok, update_data, %Resource{url: url}} =
ActivityPub.update(
:resource,
resource,
%{
title: @updated_resource_title
@@ -453,7 +488,7 @@ defmodule Mobilizon.Federation.ActivityPubTest do
assert update_data.data["object"]["url"] == @resource_url
assert update_data.data["to"] == [group.url]
assert update_data.data["to"] == [group.members_url]
assert update_data.data["actor"] == actor.url
assert update_data.data["attributedTo"] == [actor.url]
@@ -496,7 +531,7 @@ defmodule Mobilizon.Federation.ActivityPubTest do
assert update_data.data["object"]["url"] == @resource_url
assert update_data.data["to"] == [group.url]
assert update_data.data["to"] == [group.members_url]
assert update_data.data["actor"] == actor.url
assert update_data.data["origin"] == nil
assert update_data.data["target"] == parent_url
@@ -524,19 +559,23 @@ defmodule Mobilizon.Federation.ActivityPubTest do
{:ok, update_data, %Resource{url: url}} =
ActivityPub.delete(
resource,
actor,
true
)
assert update_data.local
assert update_data.data["type"] == "Delete"
assert update_data.data["object"] == url
assert update_data.data["to"] == [group.url]
# TODO : Add actor parameter to ActivityPub.delete/2
# assert update_data.data["actor"] == actor.url
# assert update_data.data["attributedTo"] == [actor.url]
assert update_data.data["object"]["type"] == "Document"
assert update_data.data["object"]["id"] == url
assert update_data.data["to"] == [group.members_url]
assert update_data.data["actor"] == actor.url
assert update_data.data["attributedTo"] == [group.url]
assert_called(Utils.maybe_federate(update_data))
end
end
end
describe "announce" do
end
end

View File

@@ -2,37 +2,35 @@ defmodule Mobilizon.Federation.ActivityPub.RefresherTest do
use Mobilizon.DataCase
alias Mobilizon.Actors.{Actor, Member}
alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.Refresher
alias Mobilizon.Service.HTTP.ActivityPub.Mock
alias Mobilizon.Web.ActivityPub.ActorView
import Mobilizon.Factory
import Mock
import Mox
test "refreshes a members collection" do
%Actor{members_url: members_url, url: group_url} = group = insert(:group)
%Actor{url: actor_url} = actor = insert(:actor)
%Member{} = insert(:member, parent: group, actor: actor, role: :member)
describe "refreshes a" do
setup :verify_on_exit!
data =
ActorView.render("members.json", %{group: group, actor_applicant: actor}) |> Jason.encode!()
test "members collection" do
%Actor{members_url: members_url} =
group =
insert(:group,
url: "https://remoteinstance.tld/@group",
members_url: "https://remoteinstance.tld/@group/members",
domain: "remoteinstance.tld"
)
%Actor{} = actor = insert(:actor)
%Member{} = insert(:member, parent: group, actor: actor, role: :member)
data = ActorView.render("members.json", %{actor: group, actor_applicant: actor})
Mock
|> expect(:call, fn
%{method: :get, url: ^members_url}, _opts ->
{:ok, %Tesla.Env{status: 200, body: data}}
end)
with_mocks([
{HTTPoison, [],
[
get!: fn ^members_url, _headers, _options ->
%HTTPoison.Response{status_code: 200, body: data}
end
]},
{ActivityPub, [],
[
get_or_fetch_actor_by_url: fn url ->
case url do
^actor_url -> {:ok, actor}
^group_url -> {:ok, group}
end
end
]}
]) do
assert :ok == Refresher.fetch_collection(group.members_url, actor)
end
end

View File

@@ -0,0 +1,173 @@
defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.AnnouncesTest do
use Mobilizon.DataCase
import Mobilizon.Factory
import Mox
alias Mobilizon.Actors.Actor
alias Mobilizon.Discussions
alias Mobilizon.Discussions.{Comment, Discussion}
alias Mobilizon.Federation.ActivityPub.Transmogrifier
alias Mobilizon.Federation.ActivityStream.Convertible
alias Mobilizon.Service.HTTP.ActivityPub.Mock
alias Mobilizon.Tombstone
@comment_text "my comment"
describe "incoming announces for discussion creation" do
setup :verify_on_exit!
test "by group member works" do
actor = insert(:actor)
group = insert(:group)
insert(:member, parent: group, actor: actor, role: :member)
%Comment{url: comment_url} =
comment = build(:comment, actor: actor, attributed_to: group, event: nil)
comment_data = Convertible.model_to_as(comment)
Mock
|> expect(:call, fn
%{method: :get, url: ^comment_url}, _opts ->
{:ok, %Tesla.Env{status: 200, body: comment_data}}
end)
data =
File.read!("test/fixtures/mastodon-announce.json")
|> Jason.decode!()
|> Map.put("actor", group.url)
|> Map.put("object", comment.url)
{:ok, _, %Comment{actor: %Actor{url: actor_url}, url: comment_url}} =
Transmogrifier.handle_incoming(data)
assert actor_url == comment.actor.url
assert comment_url == comment.url
end
end
describe "handle incoming announces for discussion updates" do
setup :verify_on_exit!
@updated_title "Updated title"
test "by group member works" do
actor =
insert(:actor,
domain: "otherremoteinstance.tld",
url: "http://otherremoteinstance.tld/@somemember"
)
group =
insert(:group,
url: "http://remoteinstance.tld/@mygroup",
domain: "remoteinstance.tld",
members_url: "http://remoteinstance.tld/@mygroup/members"
)
insert(:member, parent: group, actor: actor, role: :member)
%Comment{url: _comment_url} =
comment =
insert(:comment,
actor: actor,
attributed_to: group,
text: @comment_text,
url: "http://otherremoteinstance.tld/@somemember/uuid"
)
%Discussion{url: discussion_url} =
discussion =
insert(:discussion,
last_comment: comment,
comments: [comment],
creator: actor,
actor: group,
url: "http://otherremoteinstance.tld/@mygroup/c/talk-of-something-sh0rt-uu1d"
)
discussion_updated = Map.put(discussion, :title, @updated_title)
discussion_updated_data = Convertible.model_to_as(discussion_updated)
Mock
|> expect(:call, fn
%{url: ^discussion_url}, _opts ->
{:ok, %Tesla.Env{status: 200, body: discussion_updated_data}}
end)
data =
File.read!("test/fixtures/mastodon-announce.json")
|> Jason.decode!()
|> Map.put("actor", group.url)
|> Map.put("object", discussion_url)
assert {:ok, _, %Discussion{title: title}} = Transmogrifier.handle_incoming(data)
assert title == @updated_title
end
end
describe "handle incoming announces for discussion deletion" do
setup :verify_on_exit!
test "by group member works" do
actor =
insert(:actor,
url: "http://otherremoteinstance.tld/@somemember",
domain: "otherremoteinstance.tld"
)
group =
insert(:group,
url: "http://remoteinstance.tld/@mygroup",
domain: "remoteinstance.tld",
members_url: "http://remoteinstance.tld/@mygroup/members"
)
insert(:member, parent: group, actor: actor, role: :member)
%Comment{url: comment_url} =
comment =
insert(:comment,
actor: actor,
attributed_to: group,
text: @comment_text,
url: "http://otherremoteinstance.tld/comment/uuid"
)
tombstone = build(:tombstone, uri: comment.url, actor: actor)
tombstone_data = Convertible.model_to_as(tombstone)
Mock
|> expect(:call, fn
%{url: ^comment_url}, _opts ->
{:ok, %Tesla.Env{status: 200, body: tombstone_data}}
end)
data =
File.read!("test/fixtures/mastodon-announce.json")
|> Jason.decode!()
|> Map.put("actor", group.url)
|> Map.put("object", comment.url)
%Comment{deleted_at: deleted_at, text: comment_text} =
Discussions.get_comment_from_url(comment.url)
assert is_nil(deleted_at)
assert comment_text == @comment_text
{:ok, _, %Comment{deleted_at: deleted_at, text: comment_text}} =
Transmogrifier.handle_incoming(data)
refute is_nil(deleted_at)
refute comment_text == @comment_text
%Tombstone{actor_id: _actor_id, uri: tombstone_uri} = Tombstone.find_tombstone(comment_url)
# assert actor_id == comment.actor.id
assert tombstone_uri == comment.url
end
end
end

View File

@@ -0,0 +1,155 @@
defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.CommentsTest do
use Mobilizon.DataCase
import Mobilizon.Factory
import Mox
import ExUnit.CaptureLog
alias Mobilizon.Actors
alias Mobilizon.Actors.Actor
alias Mobilizon.Discussions
alias Mobilizon.Discussions.Comment
alias Mobilizon.Federation.ActivityPub.{Activity, Transmogrifier}
alias Mobilizon.Federation.ActivityStream.Convertible
alias Mobilizon.Service.HTTP.ActivityPub.Mock
describe "handle incoming comments" do
setup :verify_on_exit!
test "it ignores an incoming comment if we already have it" do
comment = insert(:comment)
comment = Repo.preload(comment, [:attributed_to])
activity = %{
"type" => "Create",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"actor" => comment.actor.url,
"object" => Convertible.model_to_as(comment)
}
data =
File.read!("test/fixtures/mastodon-post-activity.json")
|> Jason.decode!()
|> Map.put("object", activity["object"])
assert {:ok, nil, _} = Transmogrifier.handle_incoming(data)
end
test "it fetches replied-to activities if we don't have them" do
data =
File.read!("test/fixtures/mastodon-post-activity.json")
|> Jason.decode!()
reply_to_url = "https://blob.cat/objects/02fdea3d-932c-4348-9ecb-3f9eb3fbdd94"
object =
data["object"]
|> Map.put("inReplyTo", reply_to_url)
data =
data
|> Map.put("object", object)
reply_to_data =
File.read!("test/fixtures/pleroma-comment-object.json")
|> Jason.decode!()
Mock
|> expect(:call, fn
%{method: :get, url: ^reply_to_url}, _opts ->
{:ok, %Tesla.Env{status: 200, body: reply_to_data}}
end)
{:ok, returned_activity, _} = Transmogrifier.handle_incoming(data)
%Comment{} =
origin_comment =
Discussions.get_comment_from_url(
"https://blob.cat/objects/02fdea3d-932c-4348-9ecb-3f9eb3fbdd94"
)
assert returned_activity.data["object"]["inReplyTo"] ==
"https://blob.cat/objects/02fdea3d-932c-4348-9ecb-3f9eb3fbdd94"
assert returned_activity.data["object"]["inReplyTo"] == origin_comment.url
end
@url_404 "https://404.site/whatever"
test "it does not crash if the object in inReplyTo can't be fetched" do
data =
File.read!("test/fixtures/mastodon-post-activity.json")
|> Jason.decode!()
object =
data["object"]
|> Map.put("inReplyTo", @url_404)
data =
data
|> Map.put("object", object)
Mock
|> expect(:call, fn
%{method: :get, url: "https://404.site/whatever"}, _opts ->
{:ok, %Tesla.Env{status: 404, body: "Not found"}}
end)
assert capture_log([level: :warn], fn ->
{:ok, _returned_activity, _entity} = Transmogrifier.handle_incoming(data)
end) =~ "[warn] Parent object is something we don't handle"
end
test "it works for incoming notices" do
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
{:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data)
assert data["id"] ==
"https://framapiaf.org/users/admin/statuses/99512778738411822/activity"
assert data["to"] == [
"https://www.w3.org/ns/activitystreams#Public",
"https://framapiaf.org/users/tcit"
]
# assert data["cc"] == [
# "https://framapiaf.org/users/admin/followers",
# "http://mobilizon.com/@tcit"
# ]
assert data["actor"] == "https://framapiaf.org/users/admin"
object = data["object"]
assert object["id"] == "https://framapiaf.org/users/admin/statuses/99512778738411822"
assert object["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
# assert object["cc"] == [
# "https://framapiaf.org/users/admin/followers",
# "http://localtesting.pleroma.lol/users/lain"
# ]
assert object["actor"] == "https://framapiaf.org/users/admin"
assert object["attributedTo"] == "https://framapiaf.org/users/admin"
{:ok, %Actor{}} = Actors.get_actor_by_url(object["actor"])
end
test "it works for incoming notices with hashtags" do
data = File.read!("test/fixtures/mastodon-post-activity-hashtag.json") |> Jason.decode!()
{:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data)
assert Enum.at(data["object"]["tag"], 0)["name"] == "@tcit@framapiaf.org"
assert Enum.at(data["object"]["tag"], 1)["name"] == "#moo"
end
test "it works for incoming notices with url not being a string (prismo)" do
data = File.read!("test/fixtures/prismo-url-map.json") |> Jason.decode!()
assert {:error, :not_supported} == Transmogrifier.handle_incoming(data)
# Pages without groups are not supported
# {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
# assert data["object"]["url"] == "https://prismo.news/posts/83"
end
end
end

View File

@@ -0,0 +1,122 @@
defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.FollowTest do
use Mobilizon.DataCase
import Mobilizon.Factory
alias Mobilizon.Actors
alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.{Activity, Transmogrifier}
describe "handle incoming follow accept activities" do
test "it works for incoming accepts which were pre-accepted" do
follower = insert(:actor)
followed = insert(:actor)
refute Actors.is_following(follower, followed)
{:ok, follow_activity, _} = ActivityPub.follow(follower, followed)
assert Actors.is_following(follower, followed)
accept_data =
File.read!("test/fixtures/mastodon-accept-activity.json")
|> Jason.decode!()
|> Map.put("actor", followed.url)
object =
accept_data["object"]
|> Map.put("actor", follower.url)
|> Map.put("id", follow_activity.data["id"])
accept_data = Map.put(accept_data, "object", object)
{:ok, activity, _} = Transmogrifier.handle_incoming(accept_data)
refute activity.local
assert activity.data["object"]["id"] == follow_activity.data["id"]
{:ok, follower} = Actors.get_actor_by_url(follower.url)
assert Actors.is_following(follower, followed)
end
test "it works for incoming accepts which are referenced by IRI only" do
follower = insert(:actor)
followed = insert(:actor)
{:ok, follow_activity, _} = ActivityPub.follow(follower, followed)
accept_data =
File.read!("test/fixtures/mastodon-accept-activity.json")
|> Jason.decode!()
|> Map.put("actor", followed.url)
|> Map.put("object", follow_activity.data["id"])
{:ok, activity, _} = Transmogrifier.handle_incoming(accept_data)
assert activity.data["object"]["id"] == follow_activity.data["id"]
assert activity.data["object"]["id"] =~ "/follow/"
assert activity.data["id"] =~ "/accept/follow/"
{:ok, follower} = Actors.get_actor_by_url(follower.url)
assert Actors.is_following(follower, followed)
end
test "it fails for incoming accepts which cannot be correlated" do
follower = insert(:actor)
followed = insert(:actor)
accept_data =
File.read!("test/fixtures/mastodon-accept-activity.json")
|> Jason.decode!()
|> Map.put("actor", followed.url)
accept_data =
Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.url))
:error = Transmogrifier.handle_incoming(accept_data)
{:ok, follower} = Actors.get_actor_by_url(follower.url)
refute Actors.is_following(follower, followed)
end
end
describe "handle incoming follow reject activities" do
test "it fails for incoming rejects which cannot be correlated" do
follower = insert(:actor)
followed = insert(:actor)
accept_data =
File.read!("test/fixtures/mastodon-reject-activity.json")
|> Jason.decode!()
|> Map.put("actor", followed.url)
accept_data =
Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.url))
:error = Transmogrifier.handle_incoming(accept_data)
{:ok, follower} = Actors.get_actor_by_url(follower.url)
refute Actors.is_following(follower, followed)
end
test "it works for incoming rejects which are referenced by IRI only" do
follower = insert(:actor)
followed = insert(:actor)
{:ok, follow_activity, _} = ActivityPub.follow(follower, followed)
assert Actors.is_following(follower, followed)
reject_data =
File.read!("test/fixtures/mastodon-reject-activity.json")
|> Jason.decode!()
|> Map.put("actor", followed.url)
|> Map.put("object", follow_activity.data["id"])
{:ok, %Activity{data: _}, _} = Transmogrifier.handle_incoming(reject_data)
refute Actors.is_following(follower, followed)
end
end
end

View File

@@ -0,0 +1,60 @@
defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.InviteTest do
use Mobilizon.DataCase
import Mobilizon.Factory
alias Mobilizon.Actors
alias Mobilizon.Actors.{Actor, Member}
alias Mobilizon.Federation.ActivityPub.Transmogrifier
describe "handle Invite activities on group" do
test "it accepts Invite activities" do
%Actor{url: group_url, id: group_id} = group = insert(:group)
%Actor{url: group_admin_url, id: group_admin_id} = group_admin = insert(:actor)
%Member{} =
_group_admin_member =
insert(:member, parent: group, actor: group_admin, role: :administrator)
%Actor{url: invitee_url, id: invitee_id} = _invitee = insert(:actor)
invite_data =
File.read!("test/fixtures/mobilizon-invite-activity.json")
|> Jason.decode!()
|> Map.put("actor", group_admin_url)
|> Map.put("object", group_url)
|> Map.put("target", invitee_url)
assert {:ok, activity, %Member{}} = Transmogrifier.handle_incoming(invite_data)
assert %Member{} = member = Actors.get_member_by_url(invite_data["id"])
assert member.actor.id == invitee_id
assert member.parent.id == group_id
assert member.role == :invited
assert member.invited_by_id == group_admin_id
end
test "it refuses Invite activities for " do
%Actor{url: group_url, id: group_id} = group = insert(:group)
%Actor{url: group_admin_url, id: group_admin_id} = group_admin = insert(:actor)
%Member{} =
_group_admin_member =
insert(:member, parent: group, actor: group_admin, role: :administrator)
%Actor{url: invitee_url, id: invitee_id} = _invitee = insert(:actor)
invite_data =
File.read!("test/fixtures/mobilizon-invite-activity.json")
|> Jason.decode!()
|> Map.put("actor", group_admin_url)
|> Map.put("object", group_url)
|> Map.put("target", invitee_url)
assert {:ok, activity, %Member{}} = Transmogrifier.handle_incoming(invite_data)
assert %Member{} = member = Actors.get_member_by_url(invite_data["id"])
assert member.actor.id == invitee_id
assert member.parent.id == group_id
assert member.role == :invited
assert member.invited_by_id == group_admin_id
end
end
end

View File

@@ -0,0 +1,104 @@
defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.JoinTest do
use Mobilizon.DataCase
import Mobilizon.Factory
import ExUnit.CaptureLog
alias Mobilizon.Actors.Actor
alias Mobilizon.Events
alias Mobilizon.Events.{Event, Participant}
alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.Transmogrifier
describe "handle incoming join activities" do
@join_message "I want to get in!"
test "it accepts Join activities" do
%Actor{url: organizer_url} = organizer = insert(:actor)
%Actor{url: participant_url} = _participant = insert(:actor)
%Event{url: event_url} = _event = insert(:event, organizer_actor: organizer)
join_data =
File.read!("test/fixtures/mobilizon-join-activity.json")
|> Jason.decode!()
|> Map.put("actor", participant_url)
|> Map.put("object", event_url)
|> Map.put("participationMessage", @join_message)
assert {:ok, activity, %Participant{} = participant} =
Transmogrifier.handle_incoming(join_data)
assert participant.metadata.message == @join_message
assert participant.role == :participant
assert activity.data["type"] == "Accept"
assert activity.data["object"]["object"] == event_url
assert activity.data["object"]["id"] =~ "/join/event/"
assert activity.data["object"]["type"] =~ "Join"
assert activity.data["object"]["participationMessage"] == @join_message
assert activity.data["actor"] == organizer_url
assert activity.data["id"] =~ "/accept/join/"
end
end
describe "handle incoming accept join activities" do
test "it accepts Accept activities for Join activities" do
%Actor{url: organizer_url} = organizer = insert(:actor)
%Actor{} = participant_actor = insert(:actor)
%Event{} = event = insert(:event, organizer_actor: organizer, join_options: :restricted)
{:ok, join_activity, participation} =
ActivityPub.join(event, participant_actor, false, %{metadata: %{role: :not_approved}})
accept_data =
File.read!("test/fixtures/mastodon-accept-activity.json")
|> Jason.decode!()
|> Map.put("actor", organizer_url)
|> Map.put("object", participation.url)
{:ok, accept_activity, _} = Transmogrifier.handle_incoming(accept_data)
assert accept_activity.data["object"]["id"] == join_activity.data["id"]
assert accept_activity.data["object"]["id"] =~ "/join/"
assert accept_activity.data["id"] =~ "/accept/join/"
# We don't accept already accepted Accept activities
:error = Transmogrifier.handle_incoming(accept_data)
end
end
describe "handle incoming reject join activities" do
test "it accepts Reject activities for Join activities" do
%Actor{url: organizer_url} = organizer = insert(:actor)
%Actor{} = participant_actor = insert(:actor)
%Event{} = event = insert(:event, organizer_actor: organizer, join_options: :restricted)
{:ok, join_activity, participation} = ActivityPub.join(event, participant_actor)
reject_data =
File.read!("test/fixtures/mastodon-reject-activity.json")
|> Jason.decode!()
|> Map.put("actor", organizer_url)
|> Map.put("object", participation.url)
{:ok, reject_activity, _} = Transmogrifier.handle_incoming(reject_data)
assert reject_activity.data["object"]["id"] == join_activity.data["id"]
assert reject_activity.data["object"]["id"] =~ "/join/"
assert reject_activity.data["id"] =~ "/reject/join/"
# We don't accept already rejected Reject activities
assert capture_log([level: :warn], fn ->
assert :error == Transmogrifier.handle_incoming(reject_data)
end) =~
"Unable to process Reject activity \"http://mastodon.example.org/users/admin#rejects/follows/4\". Object \"#{
join_activity.data["id"]
}\" wasn't found."
# Organiser is not present since we use factories directly
assert event.id
|> Events.list_participants_for_event()
|> Map.get(:elements)
|> Enum.map(& &1.role) == [:rejected]
end
end
end

View File

@@ -0,0 +1,60 @@
defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.LeaveTest do
use Mobilizon.DataCase
import Mobilizon.Factory
alias Mobilizon.Actors.Actor
alias Mobilizon.Events
alias Mobilizon.Events.{Event, Participant}
alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.Transmogrifier
describe "handle incoming leave activities on events" do
test "it accepts Leave activities" do
%Actor{url: _organizer_url} = organizer = insert(:actor)
%Actor{url: participant_url} = participant_actor = insert(:actor)
%Event{url: event_url} =
event = insert(:event, organizer_actor: organizer, join_options: :restricted)
organizer_participation =
%Participant{} = insert(:participant, event: event, actor: organizer, role: :creator)
{:ok, _join_activity, _participation} = ActivityPub.join(event, participant_actor)
join_data =
File.read!("test/fixtures/mobilizon-leave-activity.json")
|> Jason.decode!()
|> Map.put("actor", participant_url)
|> Map.put("object", event_url)
assert {:ok, activity, _} = Transmogrifier.handle_incoming(join_data)
assert activity.data["object"] == event_url
assert activity.data["actor"] == participant_url
# The only participant left is the organizer
assert event.id
|> Events.list_participants_for_event()
|> Map.get(:elements)
|> Enum.map(& &1.id) ==
[organizer_participation.id]
end
test "it refuses Leave activities when actor is the only organizer" do
%Actor{url: organizer_url} = organizer = insert(:actor)
%Event{url: event_url} =
event = insert(:event, organizer_actor: organizer, join_options: :restricted)
%Participant{} = insert(:participant, event: event, actor: organizer, role: :creator)
join_data =
File.read!("test/fixtures/mobilizon-leave-activity.json")
|> Jason.decode!()
|> Map.put("actor", organizer_url)
|> Map.put("object", event_url)
assert :error = Transmogrifier.handle_incoming(join_data)
end
end
end

View File

@@ -0,0 +1,85 @@
defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.UndoTest do
use Mobilizon.DataCase
import Mobilizon.Factory
import Mox
alias Mobilizon.Actors
alias Mobilizon.Discussions.Comment
alias Mobilizon.Federation.ActivityPub.{Activity, Transmogrifier}
alias Mobilizon.Service.HTTP.ActivityPub.Mock
describe "handle incoming undo activities" do
test "it works for incoming unannounces with an existing notice" do
comment = insert(:comment)
announce_data =
File.read!("test/fixtures/mastodon-announce.json")
|> Jason.decode!()
|> Map.put("object", comment.url)
actor_data =
File.read!("test/fixtures/mastodon-actor.json")
|> Jason.decode!()
Mock
|> expect(:call, fn
%{method: :get, url: "https://framapiaf.org/users/Framasoft"}, _opts ->
{:ok, %Tesla.Env{status: 200, body: actor_data}}
end)
{:ok, _, %Comment{}} = Transmogrifier.handle_incoming(announce_data)
data =
File.read!("test/fixtures/mastodon-undo-announce.json")
|> Jason.decode!()
|> Map.put("object", announce_data)
|> Map.put("actor", announce_data["actor"])
{:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data)
assert data["type"] == "Undo"
assert data["object"]["type"] == "Announce"
assert data["object"]["object"] == comment.url
assert data["object"]["id"] ==
"https://framapiaf.org/users/peertube/statuses/104584600044284729/activity"
end
test "it works for incomming unfollows with an existing follow" do
actor = insert(:actor)
follow_data =
File.read!("test/fixtures/mastodon-follow-activity.json")
|> Jason.decode!()
|> Map.put("object", actor.url)
actor_data =
File.read!("test/fixtures/mastodon-actor.json")
|> Jason.decode!()
|> Map.put("id", "https://social.tcit.fr/users/tcit")
Mock
|> expect(:call, fn
%{method: :get, url: "https://social.tcit.fr/users/tcit"}, _opts ->
{:ok, %Tesla.Env{status: 200, body: actor_data}}
end)
{:ok, %Activity{data: _, local: false}, _} = Transmogrifier.handle_incoming(follow_data)
data =
File.read!("test/fixtures/mastodon-unfollow-activity.json")
|> Jason.decode!()
|> Map.put("object", follow_data)
{:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data)
assert data["type"] == "Undo"
assert data["object"]["type"] == "Follow"
assert data["object"]["object"] == actor.url
assert data["actor"] == "https://social.tcit.fr/users/tcit"
{:ok, followed} = Actors.get_actor_by_url(data["actor"])
refute Actors.is_following(followed, actor)
end
end
end

View File

@@ -11,11 +11,12 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
import Mobilizon.Factory
import ExUnit.CaptureLog
import Mock
import Mox
alias Mobilizon.{Actors, Conversations, Events}
alias Mobilizon.Actors.{Actor, Member}
alias Mobilizon.Conversations.Comment
alias Mobilizon.Events.{Event, Participant}
alias Mobilizon.{Actors, Discussions, Events}
alias Mobilizon.Actors.Actor
alias Mobilizon.Discussions.Comment
alias Mobilizon.Events.Event
alias Mobilizon.Resources.Resource
alias Mobilizon.Todos.{Todo, TodoList}
@@ -25,13 +26,10 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
alias Mobilizon.Federation.ActivityStream.Convertible
alias Mobilizon.GraphQL.API
alias Mobilizon.Service.HTTP.ActivityPub.Mock
alias Mobilizon.Tombstone
alias Mobilizon.Web.Endpoint
setup_all do
HTTPoison.start()
end
describe "handle incoming events" do
test "it works for incoming events" do
use_cassette "activity_pub/fetch_mobilizon_post_activity" do
@@ -67,7 +65,7 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
assert object["actor"] == "https://test.mobilizon.org/@Alicia"
assert object["location"]["name"] == "Locaux de Framasoft"
assert object["attributedTo"] == "https://test.mobilizon.org/@Alicia"
# assert object["attributedTo"] == "https://test.mobilizon.org/@Alicia"
assert event.physical_address.street == "10 Rue Jangot"
@@ -79,172 +77,47 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
{:ok, %Actor{}} = Actors.get_actor_by_url(object["actor"])
end
end
end
describe "handle incoming comments" do
test "it ignores an incoming comment if we already have it" do
comment = insert(:comment)
test "it works for incoming events for local groups" do
%Actor{url: group_url, id: group_id} = group = insert(:group)
activity = %{
"type" => "Create",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"actor" => comment.actor.url,
"object" => Convertible.model_to_as(comment)
}
data =
File.read!("test/fixtures/mastodon-post-activity.json")
|> Jason.decode!()
|> Map.put("object", activity["object"])
assert {:ok, nil, _} = Transmogrifier.handle_incoming(data)
end
test "it fetches replied-to activities if we don't have them" do
data =
File.read!("test/fixtures/mastodon-post-activity.json")
|> Jason.decode!()
object =
data["object"]
|> Map.put("inReplyTo", "https://blob.cat/objects/02fdea3d-932c-4348-9ecb-3f9eb3fbdd94")
data =
data
|> Map.put("object", object)
{:ok, returned_activity, _} = Transmogrifier.handle_incoming(data)
%Comment{} =
origin_comment =
Conversations.get_comment_from_url(
"https://blob.cat/objects/02fdea3d-932c-4348-9ecb-3f9eb3fbdd94"
%Actor{url: actor_url, id: actor_id} =
actor =
insert(:actor,
domain: "test.mobilizon.org",
url: "https://test.mobilizon.org/@member",
preferred_username: "member"
)
assert returned_activity.data["object"]["inReplyTo"] ==
"https://blob.cat/objects/02fdea3d-932c-4348-9ecb-3f9eb3fbdd94"
with_mock ActivityPub, [:passthrough],
get_or_fetch_actor_by_url: fn url ->
case url do
^group_url -> {:ok, group}
^actor_url -> {:ok, actor}
end
end do
data = File.read!("test/fixtures/mobilizon-post-activity-group.json") |> Jason.decode!()
assert returned_activity.data["object"]["inReplyTo"] == origin_comment.url
end
object =
data["object"] |> Map.put("actor", actor_url) |> Map.put("attributedTo", group_url)
test "it does not crash if the object in inReplyTo can't be fetched" do
data =
File.read!("test/fixtures/mastodon-post-activity.json")
|> Poison.decode!()
data =
data
|> Map.put("actor", actor_url)
|> Map.put("attributedTo", group_url)
|> Map.put("object", object)
object =
data["object"]
|> Map.put("inReplyTo", "https://404.site/whatever")
assert {:ok, %Activity{data: activity_data, local: false}, %Event{} = event} =
Transmogrifier.handle_incoming(data)
data =
data
|> Map.put("object", object)
assert capture_log([level: :warn], fn ->
{:ok, _returned_activity, _entity} = Transmogrifier.handle_incoming(data)
end) =~ "[warn] Parent object is something we don't handle"
end
test "it works for incoming notices" do
use_cassette "activity_pub/mastodon_post_activity" do
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
{:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data)
assert data["id"] ==
"https://framapiaf.org/users/admin/statuses/99512778738411822/activity"
assert data["to"] == [
"https://www.w3.org/ns/activitystreams#Public",
"https://framapiaf.org/users/tcit"
]
# assert data["cc"] == [
# "https://framapiaf.org/users/admin/followers",
# "http://mobilizon.com/@tcit"
# ]
assert data["actor"] == "https://framapiaf.org/users/admin"
object = data["object"]
assert object["id"] == "https://framapiaf.org/users/admin/statuses/99512778738411822"
assert object["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
# assert object["cc"] == [
# "https://framapiaf.org/users/admin/followers",
# "http://localtesting.pleroma.lol/users/lain"
# ]
assert object["actor"] == "https://framapiaf.org/users/admin"
assert object["attributedTo"] == "https://framapiaf.org/users/admin"
{:ok, %Actor{}} = Actors.get_actor_by_url(object["actor"])
assert event.organizer_actor_id == actor_id
assert event.attributed_to_id == group_id
assert activity_data["actor"] == actor_url
assert activity_data["attributedTo"] == group_url
assert activity_data["object"]["actor"] == actor_url
assert activity_data["object"]["attributedTo"] == group_url
end
end
test "it works for incoming notices with hashtags" do
use_cassette "activity_pub/mastodon_activity_hashtag" do
data = File.read!("test/fixtures/mastodon-post-activity-hashtag.json") |> Jason.decode!()
{:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data)
assert Enum.at(data["object"]["tag"], 0)["name"] == "@tcit@framapiaf.org"
assert Enum.at(data["object"]["tag"], 1)["name"] == "#moo"
end
end
# test "it works for incoming notices with contentMap" do
# data =
# File.read!("test/fixtures/mastodon-post-activity-contentmap.json") |> Jason.decode!()
# {:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data)
# assert data["object"]["content"] ==
# "<p><span class=\"h-card\"><a href=\"http://localtesting.pleroma.lol/users/lain\" class=\"u-url mention\">@<span>lain</span></a></span></p>"
# end
# test "it works for incoming notices with to/cc not being an array (kroeg)" do
# data = File.read!("test/fixtures/kroeg-post-activity.json") |> Jason.decode!()
# {:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data)
# assert data["object"]["content"] ==
# "<p>henlo from my Psion netBook</p><p>message sent from my Psion netBook</p>"
# end
# test "it works for incoming announces with actor being inlined (kroeg)" do
# data = File.read!("test/fixtures/kroeg-announce-with-inline-actor.json") |> Jason.decode!()
# {:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data)
# assert data["actor"] == "https://puckipedia.com/"
# end
# test "it works for incoming notices with tag not being an array (kroeg)" do
# data = File.read!("test/fixtures/kroeg-array-less-emoji.json") |> Jason.decode!()
# {:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data)
# assert data["object"]["emoji"] == %{
# "icon_e_smile" => "https://puckipedia.com/forum/images/smilies/icon_e_smile.png"
# }
# data = File.read!("test/fixtures/kroeg-array-less-hashtag.json") |> Jason.decode!()
# {:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data)
# assert "test" in data["object"]["tag"]
# end
test "it works for incoming notices with url not being a string (prismo)" do
data = File.read!("test/fixtures/prismo-url-map.json") |> Jason.decode!()
assert {:error, :not_supported} == Transmogrifier.handle_incoming(data)
# Pages are not supported
# {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
# assert data["object"]["url"] == "https://prismo.news/posts/83"
end
end
describe "handle incoming todo lists" do
@@ -463,7 +336,7 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
activity = %{
"type" => "Add",
"to" => [group.url],
"to" => [group.members_url],
"actor" => actor.url,
"target" => group.resources_url,
"object" => Convertible.model_to_as(resource)
@@ -491,7 +364,7 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
activity = %{
"type" => "Add",
"to" => [group.url],
"to" => [group.members_url],
"actor" => creator.url,
"target" => group.resources_url,
"object" => %{
@@ -534,7 +407,7 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
activity = %{
"type" => "Add",
"to" => [group.url],
"to" => [group.members_url],
"actor" => creator.url,
"target" => group.resources_url,
"object" => %{
@@ -586,7 +459,7 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
activity = %{
"type" => "Add",
"to" => [group.url],
"to" => [group.members_url],
"actor" => creator.url,
"target" => parent_resource.url,
"object" => %{
@@ -631,7 +504,7 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
activity = %{
"type" => "Add",
"to" => [group.url],
"to" => [group.members_url],
"actor" => creator.url,
"target" => group.resources_url,
"object" => %{
@@ -665,7 +538,7 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
activity = %{
"type" => "Add",
"to" => [group.url],
"to" => [group.members_url],
"actor" => creator.url,
"target" => group.resources_url,
"object" => %{
@@ -787,43 +660,49 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
describe "handle incoming follow announces" do
test "it works for incoming announces" do
use_cassette "activity_pub/mastodon_announce_activity" do
data = File.read!("test/fixtures/mastodon-announce.json") |> Jason.decode!()
data = File.read!("test/fixtures/mastodon-announce.json") |> Jason.decode!()
status_data = File.read!("test/fixtures/mastodon-status.json") |> Jason.decode!()
{:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data)
Mock
|> expect(:call, fn
%{method: :get, url: "https://framapiaf.org/users/peertube/statuses/104584600044284729"},
_opts ->
{:ok, %Tesla.Env{status: 200, body: status_data}}
end)
assert data["actor"] == "https://framapiaf.org/users/Framasoft"
assert data["type"] == "Announce"
{:ok, _, %Comment{actor: %Actor{url: actor_url}, url: comment_url}} =
Transmogrifier.handle_incoming(data)
assert data["id"] ==
"https://framapiaf.org/users/Framasoft/statuses/102501959686438400/activity"
assert actor_url == "https://framapiaf.org/users/peertube"
assert data["object"] ==
"https://framapiaf.org/users/Framasoft/statuses/102501959686438400"
assert %Comment{} = Conversations.get_comment_from_url(data["object"])
end
assert comment_url ==
"https://framapiaf.org/users/peertube/statuses/104584600044284729"
end
test "it works for incoming announces with an existing activity" do
use_cassette "activity_pub/mastodon_announce_existing_activity" do
comment = insert(:comment)
%Comment{url: comment_url, actor: %Actor{url: actor_url} = actor} = insert(:comment)
data =
File.read!("test/fixtures/mastodon-announce.json")
|> Jason.decode!()
|> Map.put("object", comment.url)
actor_data =
File.read!("test/fixtures/mastodon-actor.json")
|> Jason.decode!()
{:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data)
data =
File.read!("test/fixtures/mastodon-announce.json")
|> Jason.decode!()
|> Map.put("object", comment_url)
assert data["actor"] == "https://framapiaf.org/users/Framasoft"
assert data["type"] == "Announce"
Mock
|> expect(:call, fn
%{method: :get, url: actor_url}, _opts ->
{:ok, %Tesla.Env{status: 200, body: actor_data}}
end)
assert data["id"] ==
"https://framapiaf.org/users/Framasoft/statuses/102501959686438400/activity"
{:ok, _, %Comment{actor: %Actor{url: actor_url}, url: comment_url_2}} =
Transmogrifier.handle_incoming(data)
assert data["object"] == comment.url
end
assert actor_url == actor.url
assert comment_url == comment_url_2
end
end
@@ -926,12 +805,12 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
|> Map.put("object", object)
|> Map.put("actor", actor_url)
assert Conversations.get_comment_from_url(comment_url)
assert is_nil(Conversations.get_comment_from_url(comment_url).deleted_at)
assert Discussions.get_comment_from_url(comment_url)
assert is_nil(Discussions.get_comment_from_url(comment_url).deleted_at)
{:ok, %Activity{local: false}, _} = Transmogrifier.handle_incoming(data)
refute is_nil(Conversations.get_comment_from_url(comment_url).deleted_at)
refute is_nil(Discussions.get_comment_from_url(comment_url).deleted_at)
end
test "it fails for incoming deletes with spoofed origin" do
@@ -942,7 +821,7 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
|> Jason.decode!()
|> Map.put("object", comment.url)
{:ok, %Activity{local: false}, _} = Transmogrifier.handle_incoming(announce_data)
{:ok, _, _} = Transmogrifier.handle_incoming(announce_data)
data =
File.read!("test/fixtures/mastodon-delete.json")
@@ -958,9 +837,11 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
:error = Transmogrifier.handle_incoming(data)
assert Conversations.get_comment_from_url(comment.url)
assert Discussions.get_comment_from_url(comment.url)
end
setup :set_mox_from_context
test "it works for incoming actor deletes" do
%Actor{url: url} = actor = insert(:actor, url: "https://framapiaf.org/users/admin")
%Event{url: event1_url} = event1 = insert(:event, organizer_actor: actor)
@@ -971,7 +852,13 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
data =
File.read!("test/fixtures/mastodon-delete-user.json")
|> Poison.decode!()
|> Jason.decode!()
Mock
|> expect(:call, fn
%{method: :get, url: "https://framapiaf.org/users/admin"}, _opts ->
{:ok, %Tesla.Env{status: 410, body: "Gone"}}
end)
{:ok, _activity, _actor} = Transmogrifier.handle_incoming(data)
assert %{success: 1, failure: 0} == Oban.drain_queue(:background)
@@ -980,19 +867,31 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
assert {:error, :event_not_found} = Events.get_event(event1.id)
# Tombstone are cascade deleted, seems correct for now
# assert %Tombstone{} = Tombstone.find_tombstone(event1_url)
assert %Comment{deleted_at: deleted_at} = Conversations.get_comment(comment1.id)
assert %Comment{deleted_at: deleted_at} = Discussions.get_comment(comment1.id)
refute is_nil(deleted_at)
# assert %Tombstone{} = Tombstone.find_tombstone(comment1_url)
end
test "it fails for incoming actor deletes with spoofed origin" do
%{url: url} = insert(:actor)
deleted_actor_url = "https://framapiaf.org/users/admin"
data =
File.read!("test/fixtures/mastodon-delete-user.json")
|> Poison.decode!()
|> Jason.decode!()
|> Map.put("actor", url)
deleted_actor_data =
File.read!("test/fixtures/mastodon-actor.json")
|> Jason.decode!()
|> Map.put("id", deleted_actor_url)
Mock
|> expect(:call, fn
%{url: ^deleted_actor_url}, _opts ->
{:ok, %Tesla.Env{status: 200, body: deleted_actor_data}}
end)
assert capture_log(fn ->
assert :error == Transmogrifier.handle_incoming(data)
end) =~ "Object origin check failed"
@@ -1001,62 +900,29 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
end
end
describe "handle incoming undo activities" do
test "it works for incoming unannounces with an existing notice" do
use_cassette "activity_pub/mastodon_unannounce_activity" do
comment = insert(:comment)
describe "handle tombstones" do
setup :verify_on_exit!
announce_data =
File.read!("test/fixtures/mastodon-announce.json")
|> Jason.decode!()
|> Map.put("object", comment.url)
# This is a hack to handle fetching tombstones
test "works for incoming tombstone creations" do
%Comment{url: comment_url} = comment = insert(:comment, local: false)
tombstone = build(:tombstone, uri: comment_url)
data = Convertible.model_to_as(tombstone)
{:ok, %Activity{data: announce_data, local: false}, _} =
Transmogrifier.handle_incoming(announce_data)
activity = %{
"type" => "Create",
"to" => data["to"],
"cc" => data["cc"],
"actor" => data["actor"],
"attributedTo" => data["attributedTo"],
"object" => data
}
data =
File.read!("test/fixtures/mastodon-undo-announce.json")
|> Jason.decode!()
|> Map.put("object", announce_data)
|> Map.put("actor", announce_data["actor"])
{:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data)
assert data["type"] == "Undo"
assert data["object"]["type"] == "Announce"
assert data["object"]["object"] == comment.url
assert data["object"]["id"] ==
"https://framapiaf.org/users/Framasoft/statuses/102501959686438400/activity"
end
end
test "it works for incomming unfollows with an existing follow" do
use_cassette "activity_pub/unfollow_existing_follow_activity" do
actor = insert(:actor)
follow_data =
File.read!("test/fixtures/mastodon-follow-activity.json")
|> Jason.decode!()
|> Map.put("object", actor.url)
{:ok, %Activity{data: _, local: false}, _} = Transmogrifier.handle_incoming(follow_data)
data =
File.read!("test/fixtures/mastodon-unfollow-activity.json")
|> Jason.decode!()
|> Map.put("object", follow_data)
{:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data)
assert data["type"] == "Undo"
assert data["object"]["type"] == "Follow"
assert data["object"]["object"] == actor.url
assert data["actor"] == "https://social.tcit.fr/users/tcit"
{:ok, followed} = Actors.get_actor_by_url(data["actor"])
refute Actors.is_following(followed, actor)
end
{:ok, _activity, %Comment{url: comment_url}} = Transmogrifier.handle_incoming(activity)
assert comment_url == comment.url
assert %Comment{} = comment = Discussions.get_comment_from_url(comment_url)
assert %Tombstone{} = Tombstone.find_tombstone(comment_url)
refute is_nil(comment.deleted_at)
end
end
@@ -1136,120 +1002,6 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
# refute User.blocks?(blocker, user)
# end
describe "handle incoming follow accept activities" do
test "it works for incoming accepts which were pre-accepted" do
follower = insert(:actor)
followed = insert(:actor)
refute Actors.is_following(follower, followed)
{:ok, follow_activity, _} = ActivityPub.follow(follower, followed)
assert Actors.is_following(follower, followed)
accept_data =
File.read!("test/fixtures/mastodon-accept-activity.json")
|> Jason.decode!()
|> Map.put("actor", followed.url)
object =
accept_data["object"]
|> Map.put("actor", follower.url)
|> Map.put("id", follow_activity.data["id"])
accept_data = Map.put(accept_data, "object", object)
{:ok, activity, _} = Transmogrifier.handle_incoming(accept_data)
refute activity.local
assert activity.data["object"]["id"] == follow_activity.data["id"]
{:ok, follower} = Actors.get_actor_by_url(follower.url)
assert Actors.is_following(follower, followed)
end
test "it works for incoming accepts which are referenced by IRI only" do
follower = insert(:actor)
followed = insert(:actor)
{:ok, follow_activity, _} = ActivityPub.follow(follower, followed)
accept_data =
File.read!("test/fixtures/mastodon-accept-activity.json")
|> Jason.decode!()
|> Map.put("actor", followed.url)
|> Map.put("object", follow_activity.data["id"])
{:ok, activity, _} = Transmogrifier.handle_incoming(accept_data)
assert activity.data["object"]["id"] == follow_activity.data["id"]
assert activity.data["object"]["id"] =~ "/follow/"
assert activity.data["id"] =~ "/accept/follow/"
{:ok, follower} = Actors.get_actor_by_url(follower.url)
assert Actors.is_following(follower, followed)
end
test "it fails for incoming accepts which cannot be correlated" do
follower = insert(:actor)
followed = insert(:actor)
accept_data =
File.read!("test/fixtures/mastodon-accept-activity.json")
|> Jason.decode!()
|> Map.put("actor", followed.url)
accept_data =
Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.url))
:error = Transmogrifier.handle_incoming(accept_data)
{:ok, follower} = Actors.get_actor_by_url(follower.url)
refute Actors.is_following(follower, followed)
end
end
describe "handle incoming follow reject activities" do
test "it fails for incoming rejects which cannot be correlated" do
follower = insert(:actor)
followed = insert(:actor)
accept_data =
File.read!("test/fixtures/mastodon-reject-activity.json")
|> Jason.decode!()
|> Map.put("actor", followed.url)
accept_data =
Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.url))
:error = Transmogrifier.handle_incoming(accept_data)
{:ok, follower} = Actors.get_actor_by_url(follower.url)
refute Actors.is_following(follower, followed)
end
test "it works for incoming rejects which are referenced by IRI only" do
follower = insert(:actor)
followed = insert(:actor)
{:ok, follow_activity, _} = ActivityPub.follow(follower, followed)
assert Actors.is_following(follower, followed)
reject_data =
File.read!("test/fixtures/mastodon-reject-activity.json")
|> Jason.decode!()
|> Map.put("actor", followed.url)
|> Map.put("object", follow_activity.data["id"])
{:ok, %Activity{data: _}, _} = Transmogrifier.handle_incoming(reject_data)
refute Actors.is_following(follower, followed)
end
end
describe "handle incoming flag activities" do
test "it accepts Flag activities" do
%Actor{url: reporter_url} = Relay.get_actor()
@@ -1276,201 +1028,6 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
end
end
describe "handle incoming join activities" do
@join_message "I want to get in!"
test "it accepts Join activities" do
%Actor{url: organizer_url} = organizer = insert(:actor)
%Actor{url: participant_url} = _participant = insert(:actor)
%Event{url: event_url} = _event = insert(:event, organizer_actor: organizer)
join_data =
File.read!("test/fixtures/mobilizon-join-activity.json")
|> Jason.decode!()
|> Map.put("actor", participant_url)
|> Map.put("object", event_url)
|> Map.put("participationMessage", @join_message)
assert {:ok, activity, %Participant{} = participant} =
Transmogrifier.handle_incoming(join_data)
assert participant.metadata.message == @join_message
assert participant.role == :participant
assert activity.data["type"] == "Accept"
assert activity.data["object"]["object"] == event_url
assert activity.data["object"]["id"] =~ "/join/event/"
assert activity.data["object"]["type"] =~ "Join"
assert activity.data["object"]["participationMessage"] == @join_message
assert activity.data["actor"] == organizer_url
assert activity.data["id"] =~ "/accept/join/"
end
end
describe "handle incoming accept join activities" do
test "it accepts Accept activities for Join activities" do
%Actor{url: organizer_url} = organizer = insert(:actor)
%Actor{} = participant_actor = insert(:actor)
%Event{} = event = insert(:event, organizer_actor: organizer, join_options: :restricted)
{:ok, join_activity, participation} =
ActivityPub.join(event, participant_actor, false, %{metadata: %{role: :not_approved}})
accept_data =
File.read!("test/fixtures/mastodon-accept-activity.json")
|> Jason.decode!()
|> Map.put("actor", organizer_url)
|> Map.put("object", participation.url)
{:ok, accept_activity, _} = Transmogrifier.handle_incoming(accept_data)
assert accept_activity.data["object"]["id"] == join_activity.data["id"]
assert accept_activity.data["object"]["id"] =~ "/join/"
assert accept_activity.data["id"] =~ "/accept/join/"
# We don't accept already accepted Accept activities
:error = Transmogrifier.handle_incoming(accept_data)
end
end
describe "handle incoming reject join activities" do
test "it accepts Reject activities for Join activities" do
%Actor{url: organizer_url} = organizer = insert(:actor)
%Actor{} = participant_actor = insert(:actor)
%Event{} = event = insert(:event, organizer_actor: organizer, join_options: :restricted)
{:ok, join_activity, participation} = ActivityPub.join(event, participant_actor)
reject_data =
File.read!("test/fixtures/mastodon-reject-activity.json")
|> Jason.decode!()
|> Map.put("actor", organizer_url)
|> Map.put("object", participation.url)
{:ok, reject_activity, _} = Transmogrifier.handle_incoming(reject_data)
assert reject_activity.data["object"]["id"] == join_activity.data["id"]
assert reject_activity.data["object"]["id"] =~ "/join/"
assert reject_activity.data["id"] =~ "/reject/join/"
# We don't accept already rejected Reject activities
assert capture_log([level: :warn], fn ->
assert :error == Transmogrifier.handle_incoming(reject_data)
end) =~
"Unable to process Reject activity \"http://mastodon.example.org/users/admin#rejects/follows/4\". Object \"#{
join_activity.data["id"]
}\" wasn't found."
# Organiser is not present since we use factories directly
assert event.id
|> Events.list_participants_for_event()
|> Map.get(:elements)
|> Enum.map(& &1.role) == [:rejected]
end
end
describe "handle incoming leave activities on events" do
test "it accepts Leave activities" do
%Actor{url: _organizer_url} = organizer = insert(:actor)
%Actor{url: participant_url} = participant_actor = insert(:actor)
%Event{url: event_url} =
event = insert(:event, organizer_actor: organizer, join_options: :restricted)
organizer_participation =
%Participant{} = insert(:participant, event: event, actor: organizer, role: :creator)
{:ok, _join_activity, _participation} = ActivityPub.join(event, participant_actor)
join_data =
File.read!("test/fixtures/mobilizon-leave-activity.json")
|> Jason.decode!()
|> Map.put("actor", participant_url)
|> Map.put("object", event_url)
assert {:ok, activity, _} = Transmogrifier.handle_incoming(join_data)
assert activity.data["object"] == event_url
assert activity.data["actor"] == participant_url
# The only participant left is the organizer
assert event.id
|> Events.list_participants_for_event()
|> Map.get(:elements)
|> Enum.map(& &1.id) ==
[organizer_participation.id]
end
test "it refuses Leave activities when actor is the only organizer" do
%Actor{url: organizer_url} = organizer = insert(:actor)
%Event{url: event_url} =
event = insert(:event, organizer_actor: organizer, join_options: :restricted)
%Participant{} = insert(:participant, event: event, actor: organizer, role: :creator)
join_data =
File.read!("test/fixtures/mobilizon-leave-activity.json")
|> Jason.decode!()
|> Map.put("actor", organizer_url)
|> Map.put("object", event_url)
assert :error = Transmogrifier.handle_incoming(join_data)
end
end
describe "handle Invite activities on group" do
test "it accepts Invite activities" do
%Actor{url: group_url, id: group_id} = group = insert(:group)
%Actor{url: group_admin_url, id: group_admin_id} = group_admin = insert(:actor)
%Member{} =
_group_admin_member =
insert(:member, parent: group, actor: group_admin, role: :administrator)
%Actor{url: invitee_url, id: invitee_id} = _invitee = insert(:actor)
invite_data =
File.read!("test/fixtures/mobilizon-invite-activity.json")
|> Jason.decode!()
|> Map.put("actor", group_admin_url)
|> Map.put("object", group_url)
|> Map.put("target", invitee_url)
assert {:ok, activity, %Member{}} = Transmogrifier.handle_incoming(invite_data)
assert %Member{} = member = Actors.get_member_by_url(invite_data["id"])
assert member.actor.id == invitee_id
assert member.parent.id == group_id
assert member.role == :invited
assert member.invited_by_id == group_admin_id
end
test "it refuses Invite activities for " do
%Actor{url: group_url, id: group_id} = group = insert(:group)
%Actor{url: group_admin_url, id: group_admin_id} = group_admin = insert(:actor)
%Member{} =
_group_admin_member =
insert(:member, parent: group, actor: group_admin, role: :administrator)
%Actor{url: invitee_url, id: invitee_id} = _invitee = insert(:actor)
invite_data =
File.read!("test/fixtures/mobilizon-invite-activity.json")
|> Jason.decode!()
|> Map.put("actor", group_admin_url)
|> Map.put("object", group_url)
|> Map.put("target", invitee_url)
assert {:ok, activity, %Member{}} = Transmogrifier.handle_incoming(invite_data)
assert %Member{} = member = Actors.get_member_by_url(invite_data["id"])
assert member.actor.id == invitee_id
assert member.parent.id == group_id
assert member.role == :invited
assert member.invited_by_id == group_admin_id
end
end
describe "prepare outgoing" do
test "it turns mentions into tags" do
actor = insert(:actor)
@@ -1501,7 +1058,7 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
assert Enum.member?(object["tag"], expected_mention)
end
test "it adds the json-ld context and the conversation property" do
test "it adds the json-ld context and the discussion property" do
actor = insert(:actor)
{:ok, activity, _} = API.Comments.create_comment(%{actor_id: actor.id, text: "hey"})
@@ -1558,24 +1115,40 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
describe "actor origin check" do
test "it rejects objects with a bogus origin" do
use_cassette "activity_pub/object_bogus_origin" do
{:error, _} = ActivityPub.fetch_object_from_url("https://info.pleroma.site/activity.json")
end
data =
File.read!("test/fixtures/https__info.pleroma.site_activity.json")
|> Jason.decode!()
Mock
|> expect(:call, fn
%{method: :get, url: "https://info.pleroma.site/activity.json"}, _opts ->
{:ok, %Tesla.Env{status: 200, body: data}}
end)
{:error, _} = ActivityPub.fetch_object_from_url("https://info.pleroma.site/activity.json")
end
test "it rejects activities which reference objects with bogus origins" do
use_cassette "activity_pub/activity_object_bogus" do
data = %{
"@context" => "https://www.w3.org/ns/activitystreams",
"id" => "https://framapiaf.org/users/admin/activities/1234",
"actor" => "https://framapiaf.org/users/admin",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"object" => "https://info.pleroma.site/activity.json",
"type" => "Announce"
}
data =
File.read!("test/fixtures/https__info.pleroma.site_activity.json")
|> Jason.decode!()
:error = Transmogrifier.handle_incoming(data)
end
Mock
|> expect(:call, fn
%{method: :get, url: "https://info.pleroma.site/activity.json"}, _opts ->
{:ok, %Tesla.Env{status: 200, body: data}}
end)
data = %{
"@context" => "https://www.w3.org/ns/activitystreams",
"id" => "https://framapiaf.org/users/admin/activities/1234",
"actor" => "https://framapiaf.org/users/admin",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"object" => "https://info.pleroma.site/activity.json",
"type" => "Announce"
}
:error = Transmogrifier.handle_incoming(data)
end
end
end

View File

@@ -10,15 +10,11 @@ defmodule Mobilizon.Federation.ActivityPub.UtilsTest do
alias Mobilizon.Web.Endpoint
alias Mobilizon.Web.Router.Helpers, as: Routes
setup_all do
HTTPoison.start()
end
describe "make" do
test "comment data from struct" do
comment = insert(:comment)
tag = insert(:tag, title: "MyTag")
reply = insert(:comment, in_reply_to_comment: comment, tags: [tag])
reply = insert(:comment, in_reply_to_comment: comment, tags: [tag], attributed_to: nil)
assert %{
"type" => "Note",
@@ -42,8 +38,8 @@ defmodule Mobilizon.Federation.ActivityPub.UtilsTest do
end
test "comment data from map" do
comment = insert(:comment)
reply = insert(:comment, in_reply_to_comment: comment)
comment = insert(:comment, attributed_to: nil)
reply = insert(:comment, in_reply_to_comment: comment, attributed_to: nil)
to = ["https://www.w3.org/ns/activitystreams#Public"]
comment_data = Converter.Comment.model_to_as(reply)
assert comment_data["type"] == "Note"