Refactor the ActivityPub module

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2021-04-22 12:17:56 +02:00
parent 17a6a6eada
commit 280f461ba7
32 changed files with 385 additions and 332 deletions

View File

@@ -11,13 +11,12 @@ defmodule Mobilizon.Federation.ActivityPubTest do
import Mox
import Mobilizon.Factory
alias Mobilizon.{Actors, Discussions, Events}
alias Mobilizon.Actors.Actor
alias Mobilizon.{Discussions, Events}
alias Mobilizon.Resources.Resource
alias Mobilizon.Todos.{Todo, TodoList}
alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.{Relay, Utils}
alias Mobilizon.Federation.ActivityPub.Utils
alias Mobilizon.Federation.HTTPSignatures.Signature
alias Mobilizon.Service.HTTP.ActivityPub.Mock
@@ -40,116 +39,6 @@ defmodule Mobilizon.Federation.ActivityPubTest do
end
end
describe "fetching actor from its url" do
test "returns an actor from nickname" do
use_cassette "activity_pub/fetch_tcit@framapiaf.org" do
assert {:ok,
%Actor{preferred_username: "tcit", domain: "framapiaf.org", visibility: :public} =
_actor} = ActivityPub.make_actor_from_nickname("tcit@framapiaf.org")
end
use_cassette "activity_pub/fetch_tcit@framapiaf.org_not_discoverable" do
assert {:ok,
%Actor{preferred_username: "tcit", domain: "framapiaf.org", visibility: :unlisted} =
_actor} = ActivityPub.make_actor_from_nickname("tcit@framapiaf.org")
end
end
@actor_url "https://framapiaf.org/users/tcit"
test "returns an actor from url" do
# Initial fetch
use_cassette "activity_pub/fetch_framapiaf.org_users_tcit" do
# Unlisted because discoverable is not present in the JSON payload
assert {:ok,
%Actor{preferred_username: "tcit", domain: "framapiaf.org", visibility: :unlisted}} =
ActivityPub.get_or_fetch_actor_by_url(@actor_url)
end
# Fetch uses cache if Actors.needs_update? returns false
with_mocks([
{Actors, [:passthrough],
[
get_actor_by_url: fn @actor_url, false ->
{:ok,
%Actor{
preferred_username: "tcit",
domain: "framapiaf.org"
}}
end,
needs_update?: fn _ -> false end
]},
{ActivityPub, [:passthrough],
make_actor_from_url: fn @actor_url, false ->
{:ok,
%Actor{
preferred_username: "tcit",
domain: "framapiaf.org"
}}
end}
]) do
assert {:ok, %Actor{preferred_username: "tcit", domain: "framapiaf.org"}} =
ActivityPub.get_or_fetch_actor_by_url(@actor_url)
assert_called(Actors.needs_update?(:_))
refute called(ActivityPub.make_actor_from_url(@actor_url, false))
end
# Fetch doesn't use cache if Actors.needs_update? returns true
with_mocks([
{Actors, [:passthrough],
[
get_actor_by_url: fn @actor_url, false ->
{:ok,
%Actor{
preferred_username: "tcit",
domain: "framapiaf.org"
}}
end,
needs_update?: fn _ -> true end
]},
{ActivityPub, [:passthrough],
make_actor_from_url: fn @actor_url, false ->
{:ok,
%Actor{
preferred_username: "tcit",
domain: "framapiaf.org"
}}
end}
]) do
assert {:ok, %Actor{preferred_username: "tcit", domain: "framapiaf.org"}} =
ActivityPub.get_or_fetch_actor_by_url(@actor_url)
assert_called(ActivityPub.get_or_fetch_actor_by_url(@actor_url))
assert_called(Actors.get_actor_by_url(@actor_url, false))
assert_called(Actors.needs_update?(:_))
assert_called(ActivityPub.make_actor_from_url(@actor_url, false))
end
end
@public_url "https://www.w3.org/ns/activitystreams#Public"
test "activitystreams#Public uri returns Relay actor" do
assert ActivityPub.get_or_fetch_actor_by_url(@public_url) == {:ok, Relay.get_actor()}
end
end
describe "create activities" do
# test "removes doubled 'to' recipients" do
# actor = insert(:actor)
#
# {:ok, activity, _} =
# ActivityPub.create(%{
# to: ["user1", "user1", "user2"],
# actor: actor,
# context: "",
# object: %{}
# })
#
# assert activity.data["to"] == ["user1", "user2"]
# assert activity.actor == actor.url
# assert activity.recipients == ["user1", "user2"]
# end
end
describe "fetching an" do
test "object by url" do
url = "https://framapiaf.org/users/Framasoft/statuses/102093631881522097"

View File

@@ -0,0 +1,120 @@
defmodule Mobilizon.Federation.ActivityPub.ActorTest do
use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney
use Mobilizon.DataCase
import Mock
alias Mobilizon.Actors
alias Mobilizon.Actors.Actor
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
alias Mobilizon.Federation.ActivityPub.{Fetcher, Relay}
describe "fetching actor from its url" do
test "returns an actor from nickname" do
use_cassette "activity_pub/fetch_tcit@framapiaf.org" do
assert {:ok,
%Actor{preferred_username: "tcit", domain: "framapiaf.org", visibility: :public} =
_actor} = ActivityPubActor.make_actor_from_nickname("tcit@framapiaf.org")
end
use_cassette "activity_pub/fetch_tcit@framapiaf.org_not_discoverable" do
assert {:ok,
%Actor{preferred_username: "tcit", domain: "framapiaf.org", visibility: :unlisted} =
_actor} = ActivityPubActor.make_actor_from_nickname("tcit@framapiaf.org")
end
end
@actor_url "https://framapiaf.org/users/tcit"
test "returns an actor from url" do
# Initial fetch
use_cassette "activity_pub/fetch_framapiaf.org_users_tcit" do
# Unlisted because discoverable is not present in the JSON payload
assert {:ok,
%Actor{preferred_username: "tcit", domain: "framapiaf.org", visibility: :unlisted}} =
ActivityPubActor.get_or_fetch_actor_by_url(@actor_url)
end
# Fetch uses cache if Actors.needs_update? returns false
with_mocks([
{Actors, [:passthrough],
[
get_actor_by_url: fn @actor_url, false ->
{:ok,
%Actor{
preferred_username: "tcit",
domain: "framapiaf.org"
}}
end,
needs_update?: fn _ -> false end
]},
{ActivityPubActor, [:passthrough],
make_actor_from_url: fn @actor_url, false ->
{:ok,
%Actor{
preferred_username: "tcit",
domain: "framapiaf.org"
}}
end}
]) do
assert {:ok, %Actor{preferred_username: "tcit", domain: "framapiaf.org"}} =
ActivityPubActor.get_or_fetch_actor_by_url(@actor_url)
assert_called(Actors.needs_update?(:_))
refute called(ActivityPubActor.make_actor_from_url(@actor_url, false))
end
# Fetch doesn't use cache if Actors.needs_update? returns true
with_mocks([
{Actors, [:passthrough],
[
get_actor_by_url: fn @actor_url, false ->
{:ok,
%Actor{
preferred_username: "tcit",
domain: "framapiaf.org"
}}
end,
needs_update?: fn _ -> true end
]},
{ActivityPubActor, [:passthrough],
make_actor_from_url: fn @actor_url, false ->
{:ok,
%Actor{
preferred_username: "tcit",
domain: "framapiaf.org"
}}
end}
]) do
assert {:ok, %Actor{preferred_username: "tcit", domain: "framapiaf.org"}} =
ActivityPubActor.get_or_fetch_actor_by_url(@actor_url)
assert_called(ActivityPubActor.get_or_fetch_actor_by_url(@actor_url))
assert_called(Actors.get_actor_by_url(@actor_url, false))
assert_called(Actors.needs_update?(:_))
assert_called(ActivityPubActor.make_actor_from_url(@actor_url, false))
end
end
test "handles remote actor being deleted" do
with_mocks([
{Fetcher, [:passthrough],
fetch_and_prepare_actor_from_url: fn @actor_url ->
{:error, :actor_deleted}
end}
]) do
assert match?(
{:error, :actor_deleted},
ActivityPubActor.make_actor_from_url(@actor_url, false)
)
assert_called(Fetcher.fetch_and_prepare_actor_from_url(@actor_url))
end
end
@public_url "https://www.w3.org/ns/activitystreams#Public"
test "activitystreams#Public uri returns Relay actor" do
assert ActivityPubActor.get_or_fetch_actor_by_url(@public_url) == {:ok, Relay.get_actor()}
end
end
end

View File

@@ -3,7 +3,6 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.DeleteTest do
use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney
use Oban.Testing, repo: Mobilizon.Storage.Repo
import Mobilizon.Factory
import ExUnit.CaptureLog
import Mox
alias Mobilizon.{Actors, Discussions, Events, Posts, Resources}
@@ -78,7 +77,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.DeleteTest do
data
|> Map.put("object", object)
:error = Transmogrifier.handle_incoming(data)
{:error, :unknown_actor} = Transmogrifier.handle_incoming(data)
assert Discussions.get_comment_from_url(comment.url)
end
@@ -119,13 +118,14 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.DeleteTest do
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")
|> Jason.decode!()
|> Map.put("actor", url)
deleted_actor_url = "https://framapiaf.org/users/admin"
deleted_actor_data =
File.read!("test/fixtures/mastodon-actor.json")
|> Jason.decode!()
@@ -137,9 +137,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.DeleteTest do
{: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"
assert :error == Transmogrifier.handle_incoming(data)
assert Actors.get_actor_by_url(url)
end

View File

@@ -7,7 +7,6 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.PostsTest do
alias Mobilizon.Federation.ActivityPub.{Activity, Transmogrifier}
alias Mobilizon.Federation.ActivityStream.Convertible
alias Mobilizon.Posts.Post
alias Mobilizon.Service.HTTP.ActivityPub.Mock
describe "handle incoming posts" do
setup :verify_on_exit!

View File

@@ -21,6 +21,7 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
alias Mobilizon.Todos.{Todo, TodoList}
alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
alias Mobilizon.Federation.ActivityPub.Utils
alias Mobilizon.Federation.ActivityPub.{Activity, Relay, Transmogrifier}
alias Mobilizon.Federation.ActivityStream.Convertible
@@ -89,7 +90,7 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
preferred_username: "member"
)
with_mock ActivityPub, [:passthrough],
with_mock ActivityPubActor, [:passthrough],
get_or_fetch_actor_by_url: fn url ->
case url do
^group_url -> {:ok, group}
@@ -168,7 +169,7 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
group = insert(:group, domain: "morebilizon.com", url: @mobilizon_group_url)
%Actor{url: actor_url} = actor = insert(:actor)
with_mock ActivityPub, [:passthrough],
with_mock ActivityPubActor, [:passthrough],
get_or_fetch_actor_by_url: fn url ->
case url do
@mobilizon_group_url -> {:ok, group}
@@ -198,7 +199,7 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
test "it accepts incoming todo lists and handles group being not found" do
%Actor{url: actor_url} = actor = insert(:actor)
with_mock ActivityPub, [:passthrough],
with_mock ActivityPubActor, [:passthrough],
get_or_fetch_actor_by_url: fn url ->
case url do
@mobilizon_group_url -> {:error, "Not found"}
@@ -274,7 +275,7 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
group = insert(:group, domain: "morebilizon.com", url: @mobilizon_group_url)
%Actor{url: actor_url} = actor = insert(:actor)
with_mock ActivityPub, [:passthrough],
with_mock ActivityPubActor, [:passthrough],
get_or_fetch_actor_by_url: fn url ->
case url do
@mobilizon_group_url -> {:ok, group}
@@ -304,7 +305,7 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
test "it accepts incoming todo lists and handles group being not found" do
%Actor{url: actor_url} = actor = insert(:actor)
with_mock ActivityPub, [:passthrough],
with_mock ActivityPubActor, [:passthrough],
get_or_fetch_actor_by_url: fn url ->
case url do
@mobilizon_group_url -> {:error, "Not found"}

View File

@@ -12,14 +12,15 @@ defmodule Mobilizon.GraphQL.API.SearchTest do
alias Mobilizon.GraphQL.API.Search
alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
test "search an user by username" do
with_mock ActivityPub,
with_mock ActivityPubActor,
find_or_make_actor_from_nickname: fn "toto@domain.tld" -> {:ok, %Actor{id: 42}} end do
assert {:ok, %Page{total: 1, elements: [%Actor{id: 42}]}} ==
Search.search_actors(%{term: "toto@domain.tld"}, 1, 10, :Person)
assert_called(ActivityPub.find_or_make_actor_from_nickname("toto@domain.tld"))
assert_called(ActivityPubActor.find_or_make_actor_from_nickname("toto@domain.tld"))
end
end

View File

@@ -13,7 +13,7 @@ defmodule Mobilizon.ActorsTest do
alias Mobilizon.Service.Workers
alias Mobilizon.Storage.Page
alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
alias Mobilizon.Web.Upload.Uploader
@@ -106,7 +106,7 @@ defmodule Mobilizon.ActorsTest do
preferred_username: preferred_username,
domain: domain,
avatar: %FileModel{name: picture_name} = _picture
} = _actor} = ActivityPub.get_or_fetch_actor_by_url(@remote_account_url)
} = _actor} = ActivityPubActor.get_or_fetch_actor_by_url(@remote_account_url)
assert picture_name == "a28c50ce5f2b13fd.jpg"
@@ -156,7 +156,8 @@ defmodule Mobilizon.ActorsTest do
test "get_actor_by_name_with_preload!/1 returns the remote actor with its organized events" do
use_cassette "actors/remote_actor_mastodon_tcit" do
with {:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_actor_by_url(@remote_account_url) do
with {:ok, %Actor{} = actor} <-
ActivityPubActor.get_or_fetch_actor_by_url(@remote_account_url) do
assert Actors.get_actor_by_name_with_preload(
"#{actor.preferred_username}@#{actor.domain}"
).organized_events == []
@@ -186,7 +187,7 @@ defmodule Mobilizon.ActorsTest do
%{actor: %Actor{id: actor_id}} do
use_cassette "actors/remote_actor_mastodon_tcit" do
with {:ok, %Actor{id: actor2_id}} <-
ActivityPub.get_or_fetch_actor_by_url(@remote_account_url) do
ActivityPubActor.get_or_fetch_actor_by_url(@remote_account_url) do
%Page{total: 2, elements: actors} =
Actors.build_actors_by_username_or_name_page("tcit",
actor_type: [:Person],