summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/config/holder_test.exs5
-rw-r--r--test/filter_test.exs59
-rw-r--r--test/fixtures/fetch_mocks/104410921027210069.json72
-rw-r--r--test/fixtures/fetch_mocks/9wTkLEnuq47B25EehM.json59
-rw-r--r--test/fixtures/fetch_mocks/eal.json43
-rw-r--r--test/fixtures/fetch_mocks/tuxcrafting.json59
-rw-r--r--test/fixtures/preload_static/instance/panel.html1
-rw-r--r--test/fixtures/tesla_mock/baptiste.gelex.xyz-article.json228
-rw-r--r--test/fixtures/tesla_mock/framatube.org-video.json1
-rw-r--r--test/fixtures/tesla_mock/https___framatube.org_accounts_framasoft.json1
-rw-r--r--test/fixtures/tesla_mock/https___osada.macgirvin.com.html301
-rw-r--r--test/fixtures/tesla_mock/peertube.moe-vid.json188
-rw-r--r--test/html_test.exs14
-rw-r--r--test/notification_test.exs110
-rw-r--r--test/object/fetcher_test.exs40
-rw-r--r--test/support/factory.ex9
-rw-r--r--test/support/http_request_mock.ex28
-rw-r--r--test/tasks/relay_test.exs7
-rw-r--r--test/tasks/user_test.exs17
-rw-r--r--test/upload_test.exs13
-rw-r--r--test/user_search_test.exs11
-rw-r--r--test/user_test.exs25
-rw-r--r--test/web/activity_pub/activity_pub_test.exs176
-rw-r--r--test/web/activity_pub/mrf/anti_link_spam_policy_test.exs16
-rw-r--r--test/web/activity_pub/object_validator_test.exs657
-rw-r--r--test/web/activity_pub/object_validators/announce_validation_test.exs106
-rw-r--r--test/web/activity_pub/object_validators/attachment_validator_test.exs74
-rw-r--r--test/web/activity_pub/object_validators/block_validation_test.exs39
-rw-r--r--test/web/activity_pub/object_validators/chat_validation_test.exs200
-rw-r--r--test/web/activity_pub/object_validators/delete_validation_test.exs106
-rw-r--r--test/web/activity_pub/object_validators/emoji_react_validation_test.exs53
-rw-r--r--test/web/activity_pub/object_validators/follow_validation_test.exs26
-rw-r--r--test/web/activity_pub/object_validators/like_validation_test.exs113
-rw-r--r--test/web/activity_pub/object_validators/undo_validation_test.exs53
-rw-r--r--test/web/activity_pub/object_validators/update_validation_test.exs44
-rw-r--r--test/web/activity_pub/relay_test.exs6
-rw-r--r--test/web/activity_pub/side_effects_test.exs67
-rw-r--r--test/web/activity_pub/transmogrifier/block_handling_test.exs63
-rw-r--r--test/web/activity_pub/transmogrifier/follow_handling_test.exs2
-rw-r--r--test/web/activity_pub/transmogrifier_test.exs109
-rw-r--r--test/web/activity_pub/utils_test.exs25
-rw-r--r--test/web/common_api/common_api_test.exs69
-rw-r--r--test/web/masto_fe_controller_test.exs2
-rw-r--r--test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs38
-rw-r--r--test/web/mastodon_api/controllers/account_controller_test.exs38
-rw-r--r--test/web/mastodon_api/controllers/follow_request_controller_test.exs8
-rw-r--r--test/web/mastodon_api/controllers/instance_controller_test.exs6
-rw-r--r--test/web/mastodon_api/controllers/search_controller_test.exs5
-rw-r--r--test/web/mastodon_api/controllers/status_controller_test.exs11
-rw-r--r--test/web/mastodon_api/controllers/timeline_controller_test.exs74
-rw-r--r--test/web/mastodon_api/mastodon_api_test.exs2
-rw-r--r--test/web/mastodon_api/views/account_view_test.exs27
-rw-r--r--test/web/mastodon_api/views/status_view_test.exs20
-rw-r--r--test/web/pleroma_api/controllers/account_controller_test.exs99
-rw-r--r--test/web/preload/instance_test.exs17
-rw-r--r--test/web/preload/status_net_test.exs14
-rw-r--r--test/web/preload/timeline_test.exs2
-rw-r--r--test/web/preload/user_test.exs14
-rw-r--r--test/web/static_fe/static_fe_controller_test.exs14
-rw-r--r--test/web/streamer/streamer_test.exs29
-rw-r--r--test/web/twitter_api/util_controller_test.exs121
61 files changed, 2680 insertions, 1156 deletions
diff --git a/test/config/holder_test.exs b/test/config/holder_test.exs
index 15d48b5c7..abcaa27dd 100644
--- a/test/config/holder_test.exs
+++ b/test/config/holder_test.exs
@@ -10,7 +10,6 @@ defmodule Pleroma.Config.HolderTest do
test "default_config/0" do
config = Holder.default_config()
assert config[:pleroma][Pleroma.Uploaders.Local][:uploads] == "test/uploads"
- assert config[:tesla][:adapter] == Tesla.Mock
refute config[:pleroma][Pleroma.Repo]
refute config[:pleroma][Pleroma.Web.Endpoint]
@@ -18,17 +17,15 @@ defmodule Pleroma.Config.HolderTest do
refute config[:pleroma][:configurable_from_database]
refute config[:pleroma][:database]
refute config[:phoenix][:serve_endpoints]
+ refute config[:tesla][:adapter]
end
test "default_config/1" do
pleroma_config = Holder.default_config(:pleroma)
assert pleroma_config[Pleroma.Uploaders.Local][:uploads] == "test/uploads"
- tesla_config = Holder.default_config(:tesla)
- assert tesla_config[:adapter] == Tesla.Mock
end
test "default_config/2" do
assert Holder.default_config(:pleroma, Pleroma.Uploaders.Local) == [uploads: "test/uploads"]
- assert Holder.default_config(:tesla, :adapter) == Tesla.Mock
end
end
diff --git a/test/filter_test.exs b/test/filter_test.exs
index 63a30c736..0a5c4426a 100644
--- a/test/filter_test.exs
+++ b/test/filter_test.exs
@@ -3,37 +3,39 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.FilterTest do
- alias Pleroma.Repo
use Pleroma.DataCase
import Pleroma.Factory
+ alias Pleroma.Filter
+ alias Pleroma.Repo
+
describe "creating filters" do
test "creating one filter" do
user = insert(:user)
- query = %Pleroma.Filter{
+ query = %Filter{
user_id: user.id,
filter_id: 42,
phrase: "knights",
context: ["home"]
}
- {:ok, %Pleroma.Filter{} = filter} = Pleroma.Filter.create(query)
- result = Pleroma.Filter.get(filter.filter_id, user)
+ {:ok, %Filter{} = filter} = Filter.create(query)
+ result = Filter.get(filter.filter_id, user)
assert query.phrase == result.phrase
end
test "creating one filter without a pre-defined filter_id" do
user = insert(:user)
- query = %Pleroma.Filter{
+ query = %Filter{
user_id: user.id,
phrase: "knights",
context: ["home"]
}
- {:ok, %Pleroma.Filter{} = filter} = Pleroma.Filter.create(query)
+ {:ok, %Filter{} = filter} = Filter.create(query)
# Should start at 1
assert filter.filter_id == 1
end
@@ -41,23 +43,23 @@ defmodule Pleroma.FilterTest do
test "creating additional filters uses previous highest filter_id + 1" do
user = insert(:user)
- query_one = %Pleroma.Filter{
+ query_one = %Filter{
user_id: user.id,
filter_id: 42,
phrase: "knights",
context: ["home"]
}
- {:ok, %Pleroma.Filter{} = filter_one} = Pleroma.Filter.create(query_one)
+ {:ok, %Filter{} = filter_one} = Filter.create(query_one)
- query_two = %Pleroma.Filter{
+ query_two = %Filter{
user_id: user.id,
# No filter_id
phrase: "who",
context: ["home"]
}
- {:ok, %Pleroma.Filter{} = filter_two} = Pleroma.Filter.create(query_two)
+ {:ok, %Filter{} = filter_two} = Filter.create(query_two)
assert filter_two.filter_id == filter_one.filter_id + 1
end
@@ -65,29 +67,29 @@ defmodule Pleroma.FilterTest do
user_one = insert(:user)
user_two = insert(:user)
- query_one = %Pleroma.Filter{
+ query_one = %Filter{
user_id: user_one.id,
phrase: "knights",
context: ["home"]
}
- {:ok, %Pleroma.Filter{} = filter_one} = Pleroma.Filter.create(query_one)
+ {:ok, %Filter{} = filter_one} = Filter.create(query_one)
- query_two = %Pleroma.Filter{
+ query_two = %Filter{
user_id: user_two.id,
phrase: "who",
context: ["home"]
}
- {:ok, %Pleroma.Filter{} = filter_two} = Pleroma.Filter.create(query_two)
+ {:ok, %Filter{} = filter_two} = Filter.create(query_two)
assert filter_one.filter_id == 1
assert filter_two.filter_id == 1
- result_one = Pleroma.Filter.get(filter_one.filter_id, user_one)
+ result_one = Filter.get(filter_one.filter_id, user_one)
assert result_one.phrase == filter_one.phrase
- result_two = Pleroma.Filter.get(filter_two.filter_id, user_two)
+ result_two = Filter.get(filter_two.filter_id, user_two)
assert result_two.phrase == filter_two.phrase
end
end
@@ -95,38 +97,38 @@ defmodule Pleroma.FilterTest do
test "deleting a filter" do
user = insert(:user)
- query = %Pleroma.Filter{
+ query = %Filter{
user_id: user.id,
filter_id: 0,
phrase: "knights",
context: ["home"]
}
- {:ok, _filter} = Pleroma.Filter.create(query)
- {:ok, filter} = Pleroma.Filter.delete(query)
- assert is_nil(Repo.get(Pleroma.Filter, filter.filter_id))
+ {:ok, _filter} = Filter.create(query)
+ {:ok, filter} = Filter.delete(query)
+ assert is_nil(Repo.get(Filter, filter.filter_id))
end
test "getting all filters by an user" do
user = insert(:user)
- query_one = %Pleroma.Filter{
+ query_one = %Filter{
user_id: user.id,
filter_id: 1,
phrase: "knights",
context: ["home"]
}
- query_two = %Pleroma.Filter{
+ query_two = %Filter{
user_id: user.id,
filter_id: 2,
phrase: "who",
context: ["home"]
}
- {:ok, filter_one} = Pleroma.Filter.create(query_one)
- {:ok, filter_two} = Pleroma.Filter.create(query_two)
- filters = Pleroma.Filter.get_filters(user)
+ {:ok, filter_one} = Filter.create(query_one)
+ {:ok, filter_two} = Filter.create(query_two)
+ filters = Filter.get_filters(user)
assert filter_one in filters
assert filter_two in filters
end
@@ -134,7 +136,7 @@ defmodule Pleroma.FilterTest do
test "updating a filter" do
user = insert(:user)
- query_one = %Pleroma.Filter{
+ query_one = %Filter{
user_id: user.id,
filter_id: 1,
phrase: "knights",
@@ -146,8 +148,9 @@ defmodule Pleroma.FilterTest do
context: ["home", "timeline"]
}
- {:ok, filter_one} = Pleroma.Filter.create(query_one)
- {:ok, filter_two} = Pleroma.Filter.update(filter_one, changes)
+ {:ok, filter_one} = Filter.create(query_one)
+ {:ok, filter_two} = Filter.update(filter_one, changes)
+
assert filter_one != filter_two
assert filter_two.phrase == changes.phrase
assert filter_two.context == changes.context
diff --git a/test/fixtures/fetch_mocks/104410921027210069.json b/test/fixtures/fetch_mocks/104410921027210069.json
new file mode 100644
index 000000000..583f7a4dc
--- /dev/null
+++ b/test/fixtures/fetch_mocks/104410921027210069.json
@@ -0,0 +1,72 @@
+{
+ "@context" : [
+ "https://www.w3.org/ns/activitystreams",
+ {
+ "atomUri" : "ostatus:atomUri",
+ "conversation" : "ostatus:conversation",
+ "inReplyToAtomUri" : "ostatus:inReplyToAtomUri",
+ "ostatus" : "http://ostatus.org#",
+ "sensitive" : "as:sensitive",
+ "toot" : "http://joinmastodon.org/ns#",
+ "votersCount" : "toot:votersCount"
+ }
+ ],
+ "atomUri" : "https://busshi.moe/users/tuxcrafting/statuses/104410921027210069",
+ "attachment" : [],
+ "attributedTo" : "https://busshi.moe/users/tuxcrafting",
+ "cc" : [
+ "https://busshi.moe/users/tuxcrafting/followers",
+ "https://stereophonic.space/users/fixpoint",
+ "https://blob.cat/users/blobyoumu",
+ "https://cawfee.club/users/grips",
+ "https://jaeger.website/users/igel"
+ ],
+ "content" : "<p><span class=\"h-card\"><a href=\"https://stereophonic.space/users/fixpoint\" class=\"u-url mention\">@<span>fixpoint</span></a></span> <span class=\"h-card\"><a href=\"https://blob.cat/users/blobyoumu\" class=\"u-url mention\">@<span>blobyoumu</span></a></span> <span class=\"h-card\"><a href=\"https://cawfee.club/users/grips\" class=\"u-url mention\">@<span>grips</span></a></span> <span class=\"h-card\"><a href=\"https://jaeger.website/users/igel\" class=\"u-url mention\">@<span>igel</span></a></span> there&apos;s a difference between not liking nukes and not liking nuclear power<br />nukes are pretty bad as are all WMDs in general but disliking nuclear power just indicates you are unable of thought</p>",
+ "contentMap" : {
+ "en" : "<p><span class=\"h-card\"><a href=\"https://stereophonic.space/users/fixpoint\" class=\"u-url mention\">@<span>fixpoint</span></a></span> <span class=\"h-card\"><a href=\"https://blob.cat/users/blobyoumu\" class=\"u-url mention\">@<span>blobyoumu</span></a></span> <span class=\"h-card\"><a href=\"https://cawfee.club/users/grips\" class=\"u-url mention\">@<span>grips</span></a></span> <span class=\"h-card\"><a href=\"https://jaeger.website/users/igel\" class=\"u-url mention\">@<span>igel</span></a></span> there&apos;s a difference between not liking nukes and not liking nuclear power<br />nukes are pretty bad as are all WMDs in general but disliking nuclear power just indicates you are unable of thought</p>"
+ },
+ "conversation" : "https://cawfee.club/contexts/ad6c73d8-efc2-4e74-84ea-2dacf1a27a5e",
+ "id" : "https://busshi.moe/users/tuxcrafting/statuses/104410921027210069",
+ "inReplyTo" : "https://stereophonic.space/objects/02997b83-3ea7-4b63-94af-ef3aa2d4ed17",
+ "inReplyToAtomUri" : "https://stereophonic.space/objects/02997b83-3ea7-4b63-94af-ef3aa2d4ed17",
+ "published" : "2020-06-26T15:10:19Z",
+ "replies" : {
+ "first" : {
+ "items" : [],
+ "next" : "https://busshi.moe/users/tuxcrafting/statuses/104410921027210069/replies?only_other_accounts=true&page=true",
+ "partOf" : "https://busshi.moe/users/tuxcrafting/statuses/104410921027210069/replies",
+ "type" : "CollectionPage"
+ },
+ "id" : "https://busshi.moe/users/tuxcrafting/statuses/104410921027210069/replies",
+ "type" : "Collection"
+ },
+ "sensitive" : false,
+ "summary" : null,
+ "tag" : [
+ {
+ "href" : "https://stereophonic.space/users/fixpoint",
+ "name" : "@fixpoint@stereophonic.space",
+ "type" : "Mention"
+ },
+ {
+ "href" : "https://blob.cat/users/blobyoumu",
+ "name" : "@blobyoumu@blob.cat",
+ "type" : "Mention"
+ },
+ {
+ "href" : "https://cawfee.club/users/grips",
+ "name" : "@grips@cawfee.club",
+ "type" : "Mention"
+ },
+ {
+ "href" : "https://jaeger.website/users/igel",
+ "name" : "@igel@jaeger.website",
+ "type" : "Mention"
+ }
+ ],
+ "to" : [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "type" : "Note",
+ "url" : "https://busshi.moe/@tuxcrafting/104410921027210069"
+}
diff --git a/test/fixtures/fetch_mocks/9wTkLEnuq47B25EehM.json b/test/fixtures/fetch_mocks/9wTkLEnuq47B25EehM.json
new file mode 100644
index 000000000..0226b058a
--- /dev/null
+++ b/test/fixtures/fetch_mocks/9wTkLEnuq47B25EehM.json
@@ -0,0 +1,59 @@
+{
+ "@context" : [
+ "https://www.w3.org/ns/activitystreams",
+ "https://social.sakamoto.gq/schemas/litepub-0.1.jsonld",
+ {
+ "@language" : "und"
+ }
+ ],
+ "actor" : "https://social.sakamoto.gq/users/eal",
+ "attachment" : [],
+ "attributedTo" : "https://social.sakamoto.gq/users/eal",
+ "cc" : [
+ "https://social.sakamoto.gq/users/eal/followers"
+ ],
+ "content" : "<span class=\"h-card\"><a data-user=\"9uw2wH0iTYAMV7XnLU\" class=\"u-url mention\" href=\"https://busshi.moe/@tuxcrafting\" rel=\"ugc\">@<span>tuxcrafting</span></a></span> <span class=\"h-card\"><a data-user=\"9r5l8j8x23NI9KUFu4\" class=\"u-url mention\" href=\"https://stereophonic.space/users/fixpoint\" rel=\"ugc\">@<span>fixpoint</span></a></span> <span class=\"h-card\"><a data-user=\"9orDK545JwjY4Lxjge\" class=\"u-url mention\" href=\"https://blob.cat/users/blobyoumu\" rel=\"ugc\">@<span>blobyoumu</span></a></span> <span class=\"h-card\"><a data-user=\"68184\" class=\"u-url mention\" href=\"https://cawfee.club/users/grips\" rel=\"ugc\">@<span>grips</span></a></span> <span class=\"h-card\"><a data-user=\"9sAmMgHVKjTXKpgx84\" class=\"u-url mention\" href=\"https://jaeger.website/users/igel\" rel=\"ugc\">@<span>igel</span></a></span> What&#39;s bad about nukes?",
+ "context" : "https://cawfee.club/contexts/ad6c73d8-efc2-4e74-84ea-2dacf1a27a5e",
+ "conversation" : "https://cawfee.club/contexts/ad6c73d8-efc2-4e74-84ea-2dacf1a27a5e",
+ "id" : "https://social.sakamoto.gq/objects/f20f2497-66d9-4a52-a2e1-1be2a39c32c1",
+ "inReplyTo" : "https://busshi.moe/users/tuxcrafting/statuses/104410921027210069",
+ "published" : "2020-06-26T15:20:15.975737Z",
+ "sensitive" : false,
+ "summary" : "",
+ "tag" : [
+ {
+ "href" : "https://blob.cat/users/blobyoumu",
+ "name" : "@blobyoumu@blob.cat",
+ "type" : "Mention"
+ },
+ {
+ "href" : "https://busshi.moe/users/tuxcrafting",
+ "name" : "@tuxcrafting@busshi.moe",
+ "type" : "Mention"
+ },
+ {
+ "href" : "https://cawfee.club/users/grips",
+ "name" : "@grips@cawfee.club",
+ "type" : "Mention"
+ },
+ {
+ "href" : "https://jaeger.website/users/igel",
+ "name" : "@igel@jaeger.website",
+ "type" : "Mention"
+ },
+ {
+ "href" : "https://stereophonic.space/users/fixpoint",
+ "name" : "@fixpoint@stereophonic.space",
+ "type" : "Mention"
+ }
+ ],
+ "to" : [
+ "https://busshi.moe/users/tuxcrafting",
+ "https://www.w3.org/ns/activitystreams#Public",
+ "https://blob.cat/users/blobyoumu",
+ "https://stereophonic.space/users/fixpoint",
+ "https://cawfee.club/users/grips",
+ "https://jaeger.website/users/igel"
+ ],
+ "type" : "Note"
+}
diff --git a/test/fixtures/fetch_mocks/eal.json b/test/fixtures/fetch_mocks/eal.json
new file mode 100644
index 000000000..a605476e6
--- /dev/null
+++ b/test/fixtures/fetch_mocks/eal.json
@@ -0,0 +1,43 @@
+{
+ "@context" : [
+ "https://www.w3.org/ns/activitystreams",
+ "https://social.sakamoto.gq/schemas/litepub-0.1.jsonld",
+ {
+ "@language" : "und"
+ }
+ ],
+ "attachment" : [],
+ "discoverable" : true,
+ "endpoints" : {
+ "oauthAuthorizationEndpoint" : "https://social.sakamoto.gq/oauth/authorize",
+ "oauthRegistrationEndpoint" : "https://social.sakamoto.gq/api/v1/apps",
+ "oauthTokenEndpoint" : "https://social.sakamoto.gq/oauth/token",
+ "sharedInbox" : "https://social.sakamoto.gq/inbox",
+ "uploadMedia" : "https://social.sakamoto.gq/api/ap/upload_media"
+ },
+ "followers" : "https://social.sakamoto.gq/users/eal/followers",
+ "following" : "https://social.sakamoto.gq/users/eal/following",
+ "icon" : {
+ "type" : "Image",
+ "url" : "https://social.sakamoto.gq/media/f1cb6f79bf6839f3223ca240441f766056b74ddd23c69bcaf8bb1ba1ecff6eec.jpg"
+ },
+ "id" : "https://social.sakamoto.gq/users/eal",
+ "image" : {
+ "type" : "Image",
+ "url" : "https://social.sakamoto.gq/media/e5cccf26421e8366f4e34be3c9d5042b8bc8dcceccc7c8e89785fa312dd9632c.jpg"
+ },
+ "inbox" : "https://social.sakamoto.gq/users/eal/inbox",
+ "manuallyApprovesFollowers" : false,
+ "name" : "에알",
+ "outbox" : "https://social.sakamoto.gq/users/eal/outbox",
+ "preferredUsername" : "eal",
+ "publicKey" : {
+ "id" : "https://social.sakamoto.gq/users/eal#main-key",
+ "owner" : "https://social.sakamoto.gq/users/eal",
+ "publicKeyPem" : "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz3pF85YOhhv2Zaxv9YQ7\nrCe1aEhetCMVHtrK63tUVGoGdsblyKnVeJNbFcr6k3y35OpHS3HXIi6GzgihYcTu\nONLP4eQMHTnLUNAQZi03mjJA4iIq8v/tm8ZkL2mXsQSAbWj6Iq518mHNN7OvCoNt\n3Xjepl/0kgkc2gsund7m8r+Wu0Fusx6UlUyyAk3PexdDRdSSlVLeskqtP8jtdQDo\nL70pMyL+VD+Qb9RKFdtgJ+M4OqYP+7FVzCqXN0QIPhFf/kvHSLr+c4Y3Wm0nAKHU\n9CwXWXz5Xqscpv41KlgnUCOkTXb5eBSt23lNulae5srVzWBiFb6guiCpNzBGa+Sq\nrwIDAQAB\n-----END PUBLIC KEY-----\n\n"
+ },
+ "summary" : "Pizza napoletana supremacist.<br><br>Any artworks posted here that are good are not mine.",
+ "tag" : [],
+ "type" : "Person",
+ "url" : "https://social.sakamoto.gq/users/eal"
+}
diff --git a/test/fixtures/fetch_mocks/tuxcrafting.json b/test/fixtures/fetch_mocks/tuxcrafting.json
new file mode 100644
index 000000000..5dce2a16d
--- /dev/null
+++ b/test/fixtures/fetch_mocks/tuxcrafting.json
@@ -0,0 +1,59 @@
+{
+ "@context" : [
+ "https://www.w3.org/ns/activitystreams",
+ "https://w3id.org/security/v1",
+ {
+ "IdentityProof" : "toot:IdentityProof",
+ "PropertyValue" : "schema:PropertyValue",
+ "alsoKnownAs" : {
+ "@id" : "as:alsoKnownAs",
+ "@type" : "@id"
+ },
+ "discoverable" : "toot:discoverable",
+ "featured" : {
+ "@id" : "toot:featured",
+ "@type" : "@id"
+ },
+ "focalPoint" : {
+ "@container" : "@list",
+ "@id" : "toot:focalPoint"
+ },
+ "manuallyApprovesFollowers" : "as:manuallyApprovesFollowers",
+ "movedTo" : {
+ "@id" : "as:movedTo",
+ "@type" : "@id"
+ },
+ "schema" : "http://schema.org#",
+ "toot" : "http://joinmastodon.org/ns#",
+ "value" : "schema:value"
+ }
+ ],
+ "attachment" : [],
+ "discoverable" : true,
+ "endpoints" : {
+ "sharedInbox" : "https://busshi.moe/inbox"
+ },
+ "featured" : "https://busshi.moe/users/tuxcrafting/collections/featured",
+ "followers" : "https://busshi.moe/users/tuxcrafting/followers",
+ "following" : "https://busshi.moe/users/tuxcrafting/following",
+ "icon" : {
+ "mediaType" : "image/jpeg",
+ "type" : "Image",
+ "url" : "https://blobcdn.busshi.moe/busshifiles/accounts/avatars/000/046/872/original/054f0806ccb303d0.jpg"
+ },
+ "id" : "https://busshi.moe/users/tuxcrafting",
+ "inbox" : "https://busshi.moe/users/tuxcrafting/inbox",
+ "manuallyApprovesFollowers" : true,
+ "name" : "@tuxcrafting@localhost:8080",
+ "outbox" : "https://busshi.moe/users/tuxcrafting/outbox",
+ "preferredUsername" : "tuxcrafting",
+ "publicKey" : {
+ "id" : "https://busshi.moe/users/tuxcrafting#main-key",
+ "owner" : "https://busshi.moe/users/tuxcrafting",
+ "publicKeyPem" : "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwqWWTBf9OizsBiBhGS/M\nQTT6fB1VvQP6vvxouGZ5cGg1a97V67ouhjJ+nGMuWr++DNYjJYkk2TOynfykk0H/\n8rRSujSe3BNRKYGNzdnRJu/4XxgIE847Fqx5SijSP23JGYcn8TjeSUsN2u2YYVXK\n+Eb3Bu7DjGiqwNon6YB0h5qkGjkMSMVIFn0hZx6Z21bkfYWgra96Ok5OWf7Ck3je\nCuErlCMZcbQcHtFpBueJAxYchjNvm6fqwZxLX/NtaHdr7Fm2kin89mqzliapBlFH\nCXk7Jln6xV5I6ryggPAMzm3fuHzeo0RWlu8lrxLfARBVwaQQZS99bwqp6N9O2aUp\nYwIDAQAB\n-----END PUBLIC KEY-----\n"
+ },
+ "summary" : "<p>expert procrastinator</p><p>trans(humanist|gender|istorized)</p><p>web: <a href=\"https://tuxcrafting.port0.org\" rel=\"nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"\">tuxcrafting.port0.org</span><span class=\"invisible\"></span></a><br />pronouns: she/they<br />languages: french (native)/english (fluent)/hebrew (ok-ish)/esperanto (barely)</p>",
+ "tag" : [],
+ "type" : "Person",
+ "url" : "https://busshi.moe/@tuxcrafting"
+}
diff --git a/test/fixtures/preload_static/instance/panel.html b/test/fixtures/preload_static/instance/panel.html
new file mode 100644
index 000000000..fc58e4e93
--- /dev/null
+++ b/test/fixtures/preload_static/instance/panel.html
@@ -0,0 +1 @@
+HEY!
diff --git a/test/fixtures/tesla_mock/baptiste.gelex.xyz-article.json b/test/fixtures/tesla_mock/baptiste.gelex.xyz-article.json
index 3f3f0f4fb..b76ba96a5 100644
--- a/test/fixtures/tesla_mock/baptiste.gelex.xyz-article.json
+++ b/test/fixtures/tesla_mock/baptiste.gelex.xyz-article.json
@@ -1 +1,227 @@
-{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"Emoji":"toot:Emoji","Hashtag":"as:Hashtag","atomUri":"ostatus:atomUri","conversation":"ostatus:conversation","featured":"toot:featured","focalPoint":{"@container":"@list","@id":"toot:focalPoint"},"inReplyToAtomUri":"ostatus:inReplyToAtomUri","manuallyApprovesFollowers":"as:manuallyApprovesFollowers","movedTo":"as:movedTo","ostatus":"http://ostatus.org#","sensitive":"as:sensitive","toot":"http://joinmastodon.org/ns#"}],"attributedTo":["https://baptiste.gelez.xyz/@/BaptisteGelez"],"cc":[],"content":"<p>It has been one month since the last \"This Month in Plume\" article, so it is time for another edition of our monthly changelog!</p>\n<h2>Bug Fixes and Security</h2>\n<p>Let's start with the hidden, but still (very) important changes: bug fixes and security patches.</p>\n<p>First of all, <a href=\"/@/Trinity%20/\" title=\"Trinity \" rel=\"noopener noreferrer\">@Trinity </a> protected us against two major security flaws, called <em>XSS</em> and <em>CSRF</em>. The first one allows the attacker to run malicious code if you visit a Plume page where some of their personal data is present. The second one lets them post data with your Plume account by visiting one of their own website. It is two very common attack, and it is great we are now protected against them!</p>\n<p>The other big change in this area, is that we are now validating the data you are sending before doing anything with it. It means that, for instance, you will no longer be able to register with an empty username and to break everything.</p>\n<p>On the federation side, many issues were reported by <a href=\"/@/kaniini%20/\" title=\"kaniini \" rel=\"noopener noreferrer\">@kaniini </a> and <em>redmatrix</em> (respectively contributing to Pleroma and Hubzilla). By fixing some of them, we made it possible to <a href=\"https://baptiste.gelez.xyz/%7E/KaniiniTestBlog/current-status-of-plume-and-pleroma-federation/\" rel=\"noopener noreferrer\">federate Plume articles to Pleroma</a>!</p>\n<p><a href=\"/@/Trinity%20/\" title=\"Trinity \" rel=\"noopener noreferrer\">@Trinity </a> hopefully noticed that there was a bug in our password check code: we were not checking that your password was correct, but only that the verification process went without errors. Concretely, it means that you could login to any account with any password. I wrote this part of the code when I was still the only contributor to the project, so nobody could review my work. We will now be trying to check every change, especially when it deals with critical parts of Plume, to avoid similar issues in the future, and we I'm really sorry this happened (even if I think nobody exploited it).</p>\n<p><em>Zanfib</em> and <em>stephenburgess8</em> also commited some small bugfixes, improving the general experience.</p>\n<h2>New Features</h2>\n<p>Let's now talk about the features that we introduced during this month.</p>\n<p>One of the most easy to spot is the redesign of Plume, made by <a href=\"/@/Madeorsk.%20/\" title=\"Madeorsk. \" rel=\"noopener noreferrer\">@Madeorsk. </a> I personaly love what he did, it really improved the readability and gave Plume a bit more of identity than the previous design. And he is <a href=\"https://github.com/Plume-org/Plume/pull/104\" rel=\"noopener noreferrer\">still improving it</a>.</p>\n<p>We also enabled Mardown in comment, to let you write more structured and nicely formatted responses.</p>\n<p>As you may have noticed, I have used mentions in this post. Indeed, it is now possible to mention someone in your articles or in comments. It works exactly the same way as in other apps, and you should receive a notification if someone mentionned you.</p>\n<p>A dashboard to manage your blogs has also been introduced. In the future it may be used to manage your drafts, and eventually to show some statistics. The goal is to have a more specific homepage for authors.</p>\n<p>The federation with other ActivityPub softwares, like Mastodon or Pleroma is starting to work quite well, but the federation between Plume instances is far from being complete. However, we started to work on it, and it is now possible to view a distant user profile or blog from your instance, even if only basic informations are fetched yet (the articles are not loaded for instance).</p>\n<p>Another new feature that may not be visible for everyone, is the new NodeInfo endpoint. NodeInfo is a protocol allowing to get informations about a specific federated instance (whatever software it runs). It means that Plume instances can now be listed on sites like <a href=\"https://fediverse.network/plume\" rel=\"noopener noreferrer\">fediverse.network</a>.</p>\n<p>Maybe you wanted to host a Plume instance, but you don't like long install process during which you are just copy/pasting commands that you don't really understand from the documentation. That's why we introduced a setup script: the first you'll launch Plume, it will ask you a few questions and automatically setup your instance in a few minutes. We hope that this feature will help to host small instances, run by non-professional adminsys. You can see a demo of this tool on <a href=\"https://asciinema.org/a/tHktBK5iOd0zTulxmBX7LYQDc?t=32\" rel=\"noopener noreferrer\">asciinema</a>.</p>\n<p>Last but not least, Plume is now translatable! It is already available in English, French, Polish (thanks to <a href=\"/@/m4sk1n)/\" title=\"m4sk1n)\" rel=\"noopener noreferrer\">@m4sk1n)</a>) and German (thanks to <em>bitkeks</em>). If your browser is configured to display pages in these languages, you should normally see the interface in your language. And if your language is not present yet, feel free to <a href=\"https://github.com/Plume-org/Plume/blob/master/INTERNATIONALIZATION.md\" rel=\"noopener noreferrer\">add your translation</a>.</p>\n<h2>Other Changes</h2>\n<p>We also improved the code a lot. We tried to separate each part as much as possible, making it easier to re-use for other projects. For instance, our database code is now isolated from the rest of the app, which means it will be easier to make import tools from other blogging engines. Some parts of the code are even shared with another project, <a href=\"https://github.com/Aardwolf-Social/aardwolf\" rel=\"noopener noreferrer\">Aardwolf</a> a federated Facebook alternative. For instance, both of our projects use the same internationalization code, and once Aardwolf will implement federation, this part of the code will probably be shared too. Since the WebFinger module (used to find new users and blogs) and the CSRF protection code (see the \"Bug fixes and Security\" section) have been isolated in their own modules, they may be shared by both projects too.</p>\n<p>We also worked a lot on documentation. We now have articles explaining how to setup your Plume instance on various operating systems, but also documenting the translation process. I want to thank <em>BanjoFox</em> (who imported some documentation from their project, Aardwolf, as the setup is quite similar), <em>Kushal</em> and <a href=\"/@/gled@plume.mastodon.host%20/\" title=\"gled@plume.mastodon.host \" rel=\"noopener noreferrer\">@gled@plume.mastodon.host </a> for working on this.</p>\n<p>As you can see, there were many changes this month, but there still a lot to do. Your help will of course be welcome. If you want to contribute to the code, translate Plume in your language, write some documentation, or anything else (or even if you're just curious about the project), feel free to join our Matrix room: <a href=\"https://riot.im/app/#/room/#plume:disroot.org\" rel=\"noopener noreferrer\">#plume:disroot.org</a>. Otherwise, as <em>BanjoFox</em> <a href=\"https://glitch.social/@aardwolf/100329435838406278\" rel=\"noopener noreferrer\">said on the <em>Aardwolf Team</em> Mastodon account</a>, talking about the project around you is one of the easiest way to help.</p>\n","id":"https://baptiste.gelez.xyz/~/PlumeDevelopment/this-month-in-plume-june-2018/","likes":null,"name":"This Month in Plume: June 2018","published":"2018-07-10T20:16:24.087622Z","shares":null,"source":null,"tag":[{"href":"https://baptiste.gelez.xyz/@/Trinity","name":"@Trinity","type":"Mention"},{"href":"https://baptiste.gelez.xyz/@/kaniini/","name":"@kaniini","type":"Mention"},{"href":"https://baptiste.gelez.xyz/@/Trinity","name":"@Trinity","type":"Mention"}],"to":["https://unixcorn.xyz/users/Bat","https://mastodon.host/users/federationbot","https://social.tcit.fr/users/tcit","https://framapiaf.org/users/qwerty","https://mastodon.social/users/lthms","https://eldritch.cafe/users/Nausicaa","https://imaginair.es/users/Elanndelh","https://framapiaf.org/users/Drulac","https://mastodon.partipirate.org/users/NicolasConstant","https://aleph.land/users/Madeorsk","https://maly.io/users/Troll","https://hostux.social/users/superjey","https://mamot.fr/users/Phigger","https://mastodon.social/users/wakest","https://social.coop/users/wakest","https://unixcorn.xyz/users/Ce_lo","https://social.art-software.fr/users/Electron","https://framapiaf.org/users/Quenti","https://toot.plus.yt/users/Djyp","https://mastodon.social/users/brainblasted","https://social.mochi.academy/users/Ambraven","https://social.hacktivis.me/users/lanodan","https://mastodon.eliotberriot.com/users/eliotberriot","https://edolas.world/users/0x1C3B00DA","https://toot.cafe/users/zack","https://manowar.social/users/zatnosk","https://eldritch.cafe/users/fluffy","https://mastodon.social/users/david_ross","https://kosmos.social/users/xiroux","https://mastodon.art/users/EmergencyBattle","https://mastodon.social/users/trwnh","https://octodon.social/users/pybyte","https://anticapitalist.party/users/Trinity","https://mstdn.mx/users/xavavu","https://baptiste.gelez.xyz/@/m4sk1n","https://eldritch.cafe/users/milia","https://mastodon.zaclys.com/users/arx","https://toot.cafe/users/sivy","https://mastodon.social/users/ortegacmanuel","https://mastodon.observer/users/stephen","https://octodon.social/users/chloe","https://unixcorn.xyz/users/AmauryPi","https://cybre.space/users/rick_777","https://mastodon.social/users/wezm","https://baptiste.gelez.xyz/@/idlesong","https://mamot.fr/users/dr4Ke","https://imaginair.es/users/Phigger","https://mamot.fr/users/dlink","https://anticapitalist.party/users/a000d4f7a91939d0e71df1646d7a48","https://framapiaf.org/users/PhieLaidMignon","https://mastodon.social/users/y6nH","https://crazynoisybizarre.town/users/FederationBot","https://social.weho.st/users/dvn","https://mastodon.art/users/Wolthera","https://diaspodon.fr/users/dada","https://pachyder.me/users/Lanza","https://mastodon.xyz/users/ag","https://aleph.land/users/yahananxie","https://mstdn.io/users/chablis_social","https://mastodon.gougere.fr/users/fabien","https://functional.cafe/users/otini","https://social.coop/users/bhaugen","https://octodon.social/users/donblanco","https://chaos.social/users/astro","https://pachyder.me/users/sibear","https://mamot.fr/users/yohann","https://social.wxcafe.net/users/Bat","https://mastodon.social/users/dansup","https://chaos.social/users/juh","https://scifi.fyi/users/paeneultima","https://hostux.social/users/Deuchnord","https://mstdn.fr/users/taziden","https://mamot.fr/users/PifyZ","https://mastodon.social/users/plantabaja","https://mastodon.social/users/gitzgrog","https://mastodon.social/users/Syluban","https://masto.pt/users/eloisa","https://pleroma.soykaf.com/users/notclacke","https://mastodon.social/users/SiegfriedEhret","https://writing.exchange/users/write_as","https://mstdn.io/users/shellkr","https://mastodon.uy/users/jorge","https://mastodon.technology/users/bobstechsite","https://mastodon.social/users/hinterwaeldler","https://mastodon.xyz/users/mgdelacroix","https://mastodon.cloud/users/jjatria","https://baptiste.gelez.xyz/@/Jade/","https://edolas.world/users/pfm","https://mstdn.io/users/jort","https://mastodon.social/users/andreipetcu","https://mastodon.technology/users/0xf00fc7c8","https://mastodon.social/users/khanate","https://mastodon.technology/users/francois","https://mastodon.social/users/glherrmann","https://mastodon.host/users/gled","https://social.holdmybeer.solutions/users/kemonine","https://scholar.social/users/bgcarlisle","https://mastodon.social/users/oldgun","https://baptiste.gelez.xyz/@/snoe/","https://mastodon.at/users/switchingsocial","https://scifi.fyi/users/BrokenBiscuit","https://dev.glitch.social/users/hoodie","https://todon.nl/users/paulfree14","https://mastodon.social/users/aadilayub","https://social.fsck.club/users/anarchosaurus","https://mastodonten.de/users/GiantG","https://mastodon.technology/users/cj","https://cybre.space/users/sam","https://layer8.space/users/silkevicious","https://mastodon.xyz/users/Jimmyrwx","https://fosstodon.org/users/danyspin97","https://mstdn.io/users/cristhyano","https://mastodon.social/users/vanyok","https://hulvr.com/users/rook","https://niu.moe/users/Lucifer","https://mamot.fr/users/Thibaut","https://mastodont.cat/users/bgta","https://mstdn.io/users/hontoni","https://niu.moe/users/lionirdeadman","https://functional.cafe/users/phoe","https://mastodon.social/users/toontoet","https://mastodon.social/users/danipozo","https://scholar.social/users/robertson","https://mastodon.social/users/aldatsa","https://elekk.xyz/users/maloki","https://kitty.town/users/nursemchurt","https://neigh.horse/users/commagray","https://mastodon.social/users/hirojin","https://mastodon.xyz/users/mareklach","https://chaos.social/users/benthor","https://mastodon.social/users/djperreault","https://mastodon.art/users/eylul","https://mastodon.opportunis.me/users/bob","https://tootplanet.space/users/Shutsumon","https://toot.cat/users/woozle","https://mastodon.social/users/StephenLB","https://sleeping.town/users/oct2pus","https://mastodon.indie.host/users/stragu","https://social.coop/users/gilscottfitzgerald","https://icosahedron.website/users/joeld","https://mastodon.social/users/hellion","https://cybre.space/users/cooler_ranch","https://mastodon.social/users/kelsonv","https://mastodon.lat/users/scalpol","https://writing.exchange/users/hnb","https://hex.bz/users/Horst","https://mastodon.social/users/weddle","https://maly.io/users/sonya","https://social.coop/users/medusa","https://mastodon.social/users/DystopianK","https://mstdn.io/users/d_io","https://fosstodon.org/users/brandon","https://fosstodon.org/users/Cando","https://mastodon.host/users/panina","https://floss.social/users/tuxether","https://social.tchncs.de/users/suitbertmonz","https://mastodon.social/users/jrt","https://mastodon.social/users/sirikon","https://mstdn.io/users/yabirgb","https://mastodon.cloud/users/FerdiZ","https://mastodon.social/users/carlchenet","https://social.polonkai.eu/users/calendar_social","https://social.polonkai.eu/users/gergely","https://mastodon.social/users/Jelv","https://mastodon.social/users/srinicame","https://cybre.space/users/mastoabed","https://mastodon.social/users/tagomago","https://lgbt.io/users/bootblackCub","https://niu.moe/users/Nopplyy","https://mastodon.social/users/bpugh","https://www.w3.org/ns/activitystreams#Public"],"type":"Article","uploadMedia":null,"url":"https://baptiste.gelez.xyz/~/PlumeDevelopment/this-month-in-plume-june-2018/"} \ No newline at end of file
+{
+ "@context" : [
+ "https://www.w3.org/ns/activitystreams",
+ "https://w3id.org/security/v1",
+ {
+ "Emoji" : "toot:Emoji",
+ "Hashtag" : "as:Hashtag",
+ "atomUri" : "ostatus:atomUri",
+ "conversation" : "ostatus:conversation",
+ "featured" : "toot:featured",
+ "focalPoint" : {
+ "@container" : "@list",
+ "@id" : "toot:focalPoint"
+ },
+ "inReplyToAtomUri" : "ostatus:inReplyToAtomUri",
+ "manuallyApprovesFollowers" : "as:manuallyApprovesFollowers",
+ "movedTo" : "as:movedTo",
+ "ostatus" : "http://ostatus.org#",
+ "sensitive" : "as:sensitive",
+ "toot" : "http://joinmastodon.org/ns#"
+ }
+ ],
+ "attributedTo" : [
+ "https://baptiste.gelez.xyz/@/BaptisteGelez"
+ ],
+ "cc" : [],
+ "content" : "<p>It has been one month since the last \"This Month in Plume\" article, so it is time for another edition of our monthly changelog!</p>\n<h2>Bug Fixes and Security</h2>\n<p>Let's start with the hidden, but still (very) important changes: bug fixes and security patches.</p>\n<p>First of all, <a href=\"/@/Trinity%20/\" title=\"Trinity \" rel=\"noopener noreferrer\">@Trinity </a> protected us against two major security flaws, called <em>XSS</em> and <em>CSRF</em>. The first one allows the attacker to run malicious code if you visit a Plume page where some of their personal data is present. The second one lets them post data with your Plume account by visiting one of their own website. It is two very common attack, and it is great we are now protected against them!</p>\n<p>The other big change in this area, is that we are now validating the data you are sending before doing anything with it. It means that, for instance, you will no longer be able to register with an empty username and to break everything.</p>\n<p>On the federation side, many issues were reported by <a href=\"/@/kaniini%20/\" title=\"kaniini \" rel=\"noopener noreferrer\">@kaniini </a> and <em>redmatrix</em> (respectively contributing to Pleroma and Hubzilla). By fixing some of them, we made it possible to <a href=\"https://baptiste.gelez.xyz/%7E/KaniiniTestBlog/current-status-of-plume-and-pleroma-federation/\" rel=\"noopener noreferrer\">federate Plume articles to Pleroma</a>!</p>\n<p><a href=\"/@/Trinity%20/\" title=\"Trinity \" rel=\"noopener noreferrer\">@Trinity </a> hopefully noticed that there was a bug in our password check code: we were not checking that your password was correct, but only that the verification process went without errors. Concretely, it means that you could login to any account with any password. I wrote this part of the code when I was still the only contributor to the project, so nobody could review my work. We will now be trying to check every change, especially when it deals with critical parts of Plume, to avoid similar issues in the future, and we I'm really sorry this happened (even if I think nobody exploited it).</p>\n<p><em>Zanfib</em> and <em>stephenburgess8</em> also commited some small bugfixes, improving the general experience.</p>\n<h2>New Features</h2>\n<p>Let's now talk about the features that we introduced during this month.</p>\n<p>One of the most easy to spot is the redesign of Plume, made by <a href=\"/@/Madeorsk.%20/\" title=\"Madeorsk. \" rel=\"noopener noreferrer\">@Madeorsk. </a> I personaly love what he did, it really improved the readability and gave Plume a bit more of identity than the previous design. And he is <a href=\"https://github.com/Plume-org/Plume/pull/104\" rel=\"noopener noreferrer\">still improving it</a>.</p>\n<p>We also enabled Mardown in comment, to let you write more structured and nicely formatted responses.</p>\n<p>As you may have noticed, I have used mentions in this post. Indeed, it is now possible to mention someone in your articles or in comments. It works exactly the same way as in other apps, and you should receive a notification if someone mentionned you.</p>\n<p>A dashboard to manage your blogs has also been introduced. In the future it may be used to manage your drafts, and eventually to show some statistics. The goal is to have a more specific homepage for authors.</p>\n<p>The federation with other ActivityPub softwares, like Mastodon or Pleroma is starting to work quite well, but the federation between Plume instances is far from being complete. However, we started to work on it, and it is now possible to view a distant user profile or blog from your instance, even if only basic informations are fetched yet (the articles are not loaded for instance).</p>\n<p>Another new feature that may not be visible for everyone, is the new NodeInfo endpoint. NodeInfo is a protocol allowing to get informations about a specific federated instance (whatever software it runs). It means that Plume instances can now be listed on sites like <a href=\"https://fediverse.network/plume\" rel=\"noopener noreferrer\">fediverse.network</a>.</p>\n<p>Maybe you wanted to host a Plume instance, but you don't like long install process during which you are just copy/pasting commands that you don't really understand from the documentation. That's why we introduced a setup script: the first you'll launch Plume, it will ask you a few questions and automatically setup your instance in a few minutes. We hope that this feature will help to host small instances, run by non-professional adminsys. You can see a demo of this tool on <a href=\"https://asciinema.org/a/tHktBK5iOd0zTulxmBX7LYQDc?t=32\" rel=\"noopener noreferrer\">asciinema</a>.</p>\n<p>Last but not least, Plume is now translatable! It is already available in English, French, Polish (thanks to <a href=\"/@/m4sk1n)/\" title=\"m4sk1n)\" rel=\"noopener noreferrer\">@m4sk1n)</a>) and German (thanks to <em>bitkeks</em>). If your browser is configured to display pages in these languages, you should normally see the interface in your language. And if your language is not present yet, feel free to <a href=\"https://github.com/Plume-org/Plume/blob/master/INTERNATIONALIZATION.md\" rel=\"noopener noreferrer\">add your translation</a>.</p>\n<h2>Other Changes</h2>\n<p>We also improved the code a lot. We tried to separate each part as much as possible, making it easier to re-use for other projects. For instance, our database code is now isolated from the rest of the app, which means it will be easier to make import tools from other blogging engines. Some parts of the code are even shared with another project, <a href=\"https://github.com/Aardwolf-Social/aardwolf\" rel=\"noopener noreferrer\">Aardwolf</a> a federated Facebook alternative. For instance, both of our projects use the same internationalization code, and once Aardwolf will implement federation, this part of the code will probably be shared too. Since the WebFinger module (used to find new users and blogs) and the CSRF protection code (see the \"Bug fixes and Security\" section) have been isolated in their own modules, they may be shared by both projects too.</p>\n<p>We also worked a lot on documentation. We now have articles explaining how to setup your Plume instance on various operating systems, but also documenting the translation process. I want to thank <em>BanjoFox</em> (who imported some documentation from their project, Aardwolf, as the setup is quite similar), <em>Kushal</em> and <a href=\"/@/gled@plume.mastodon.host%20/\" title=\"gled@plume.mastodon.host \" rel=\"noopener noreferrer\">@gled@plume.mastodon.host </a> for working on this.</p>\n<p>As you can see, there were many changes this month, but there still a lot to do. Your help will of course be welcome. If you want to contribute to the code, translate Plume in your language, write some documentation, or anything else (or even if you're just curious about the project), feel free to join our Matrix room: <a href=\"https://riot.im/app/#/room/#plume:disroot.org\" rel=\"noopener noreferrer\">#plume:disroot.org</a>. Otherwise, as <em>BanjoFox</em> <a href=\"https://glitch.social/@aardwolf/100329435838406278\" rel=\"noopener noreferrer\">said on the <em>Aardwolf Team</em> Mastodon account</a>, talking about the project around you is one of the easiest way to help.</p>\n",
+ "id" : "https://baptiste.gelez.xyz/~/PlumeDevelopment/this-month-in-plume-june-2018/",
+ "likes" : null,
+ "name" : "This Month in Plume: June 2018",
+ "published" : "2018-07-10T20:16:24.087622Z",
+ "shares" : null,
+ "source" : null,
+ "tag" : [
+ {
+ "href" : "https://baptiste.gelez.xyz/@/Trinity",
+ "name" : "@Trinity",
+ "type" : "Mention"
+ },
+ {
+ "href" : "https://baptiste.gelez.xyz/@/kaniini/",
+ "name" : "@kaniini",
+ "type" : "Mention"
+ },
+ {
+ "href" : "https://baptiste.gelez.xyz/@/Trinity",
+ "name" : "@Trinity",
+ "type" : "Mention"
+ }
+ ],
+ "to" : [
+ "https://unixcorn.xyz/users/Bat",
+ "https://mastodon.host/users/federationbot",
+ "https://social.tcit.fr/users/tcit",
+ "https://framapiaf.org/users/qwerty",
+ "https://mastodon.social/users/lthms",
+ "https://eldritch.cafe/users/Nausicaa",
+ "https://imaginair.es/users/Elanndelh",
+ "https://framapiaf.org/users/Drulac",
+ "https://mastodon.partipirate.org/users/NicolasConstant",
+ "https://aleph.land/users/Madeorsk",
+ "https://maly.io/users/Troll",
+ "https://hostux.social/users/superjey",
+ "https://mamot.fr/users/Phigger",
+ "https://mastodon.social/users/wakest",
+ "https://social.coop/users/wakest",
+ "https://unixcorn.xyz/users/Ce_lo",
+ "https://social.art-software.fr/users/Electron",
+ "https://framapiaf.org/users/Quenti",
+ "https://toot.plus.yt/users/Djyp",
+ "https://mastodon.social/users/brainblasted",
+ "https://social.mochi.academy/users/Ambraven",
+ "https://social.hacktivis.me/users/lanodan",
+ "https://mastodon.eliotberriot.com/users/eliotberriot",
+ "https://edolas.world/users/0x1C3B00DA",
+ "https://toot.cafe/users/zack",
+ "https://manowar.social/users/zatnosk",
+ "https://eldritch.cafe/users/fluffy",
+ "https://mastodon.social/users/david_ross",
+ "https://kosmos.social/users/xiroux",
+ "https://mastodon.art/users/EmergencyBattle",
+ "https://mastodon.social/users/trwnh",
+ "https://octodon.social/users/pybyte",
+ "https://anticapitalist.party/users/Trinity",
+ "https://mstdn.mx/users/xavavu",
+ "https://baptiste.gelez.xyz/@/m4sk1n",
+ "https://eldritch.cafe/users/milia",
+ "https://mastodon.zaclys.com/users/arx",
+ "https://toot.cafe/users/sivy",
+ "https://mastodon.social/users/ortegacmanuel",
+ "https://mastodon.observer/users/stephen",
+ "https://octodon.social/users/chloe",
+ "https://unixcorn.xyz/users/AmauryPi",
+ "https://cybre.space/users/rick_777",
+ "https://mastodon.social/users/wezm",
+ "https://baptiste.gelez.xyz/@/idlesong",
+ "https://mamot.fr/users/dr4Ke",
+ "https://imaginair.es/users/Phigger",
+ "https://mamot.fr/users/dlink",
+ "https://anticapitalist.party/users/a000d4f7a91939d0e71df1646d7a48",
+ "https://framapiaf.org/users/PhieLaidMignon",
+ "https://mastodon.social/users/y6nH",
+ "https://crazynoisybizarre.town/users/FederationBot",
+ "https://social.weho.st/users/dvn",
+ "https://mastodon.art/users/Wolthera",
+ "https://diaspodon.fr/users/dada",
+ "https://pachyder.me/users/Lanza",
+ "https://mastodon.xyz/users/ag",
+ "https://aleph.land/users/yahananxie",
+ "https://mstdn.io/users/chablis_social",
+ "https://mastodon.gougere.fr/users/fabien",
+ "https://functional.cafe/users/otini",
+ "https://social.coop/users/bhaugen",
+ "https://octodon.social/users/donblanco",
+ "https://chaos.social/users/astro",
+ "https://pachyder.me/users/sibear",
+ "https://mamot.fr/users/yohann",
+ "https://social.wxcafe.net/users/Bat",
+ "https://mastodon.social/users/dansup",
+ "https://chaos.social/users/juh",
+ "https://scifi.fyi/users/paeneultima",
+ "https://hostux.social/users/Deuchnord",
+ "https://mstdn.fr/users/taziden",
+ "https://mamot.fr/users/PifyZ",
+ "https://mastodon.social/users/plantabaja",
+ "https://mastodon.social/users/gitzgrog",
+ "https://mastodon.social/users/Syluban",
+ "https://masto.pt/users/eloisa",
+ "https://pleroma.soykaf.com/users/notclacke",
+ "https://mastodon.social/users/SiegfriedEhret",
+ "https://writing.exchange/users/write_as",
+ "https://mstdn.io/users/shellkr",
+ "https://mastodon.uy/users/jorge",
+ "https://mastodon.technology/users/bobstechsite",
+ "https://mastodon.social/users/hinterwaeldler",
+ "https://mastodon.xyz/users/mgdelacroix",
+ "https://mastodon.cloud/users/jjatria",
+ "https://baptiste.gelez.xyz/@/Jade/",
+ "https://edolas.world/users/pfm",
+ "https://mstdn.io/users/jort",
+ "https://mastodon.social/users/andreipetcu",
+ "https://mastodon.technology/users/0xf00fc7c8",
+ "https://mastodon.social/users/khanate",
+ "https://mastodon.technology/users/francois",
+ "https://mastodon.social/users/glherrmann",
+ "https://mastodon.host/users/gled",
+ "https://social.holdmybeer.solutions/users/kemonine",
+ "https://scholar.social/users/bgcarlisle",
+ "https://mastodon.social/users/oldgun",
+ "https://baptiste.gelez.xyz/@/snoe/",
+ "https://mastodon.at/users/switchingsocial",
+ "https://scifi.fyi/users/BrokenBiscuit",
+ "https://dev.glitch.social/users/hoodie",
+ "https://todon.nl/users/paulfree14",
+ "https://mastodon.social/users/aadilayub",
+ "https://social.fsck.club/users/anarchosaurus",
+ "https://mastodonten.de/users/GiantG",
+ "https://mastodon.technology/users/cj",
+ "https://cybre.space/users/sam",
+ "https://layer8.space/users/silkevicious",
+ "https://mastodon.xyz/users/Jimmyrwx",
+ "https://fosstodon.org/users/danyspin97",
+ "https://mstdn.io/users/cristhyano",
+ "https://mastodon.social/users/vanyok",
+ "https://hulvr.com/users/rook",
+ "https://niu.moe/users/Lucifer",
+ "https://mamot.fr/users/Thibaut",
+ "https://mastodont.cat/users/bgta",
+ "https://mstdn.io/users/hontoni",
+ "https://niu.moe/users/lionirdeadman",
+ "https://functional.cafe/users/phoe",
+ "https://mastodon.social/users/toontoet",
+ "https://mastodon.social/users/danipozo",
+ "https://scholar.social/users/robertson",
+ "https://mastodon.social/users/aldatsa",
+ "https://elekk.xyz/users/maloki",
+ "https://kitty.town/users/nursemchurt",
+ "https://neigh.horse/users/commagray",
+ "https://mastodon.social/users/hirojin",
+ "https://mastodon.xyz/users/mareklach",
+ "https://chaos.social/users/benthor",
+ "https://mastodon.social/users/djperreault",
+ "https://mastodon.art/users/eylul",
+ "https://mastodon.opportunis.me/users/bob",
+ "https://tootplanet.space/users/Shutsumon",
+ "https://toot.cat/users/woozle",
+ "https://mastodon.social/users/StephenLB",
+ "https://sleeping.town/users/oct2pus",
+ "https://mastodon.indie.host/users/stragu",
+ "https://social.coop/users/gilscottfitzgerald",
+ "https://icosahedron.website/users/joeld",
+ "https://mastodon.social/users/hellion",
+ "https://cybre.space/users/cooler_ranch",
+ "https://mastodon.social/users/kelsonv",
+ "https://mastodon.lat/users/scalpol",
+ "https://writing.exchange/users/hnb",
+ "https://hex.bz/users/Horst",
+ "https://mastodon.social/users/weddle",
+ "https://maly.io/users/sonya",
+ "https://social.coop/users/medusa",
+ "https://mastodon.social/users/DystopianK",
+ "https://mstdn.io/users/d_io",
+ "https://fosstodon.org/users/brandon",
+ "https://fosstodon.org/users/Cando",
+ "https://mastodon.host/users/panina",
+ "https://floss.social/users/tuxether",
+ "https://social.tchncs.de/users/suitbertmonz",
+ "https://mastodon.social/users/jrt",
+ "https://mastodon.social/users/sirikon",
+ "https://mstdn.io/users/yabirgb",
+ "https://mastodon.cloud/users/FerdiZ",
+ "https://mastodon.social/users/carlchenet",
+ "https://social.polonkai.eu/users/calendar_social",
+ "https://social.polonkai.eu/users/gergely",
+ "https://mastodon.social/users/Jelv",
+ "https://mastodon.social/users/srinicame",
+ "https://cybre.space/users/mastoabed",
+ "https://mastodon.social/users/tagomago",
+ "https://lgbt.io/users/bootblackCub",
+ "https://niu.moe/users/Nopplyy",
+ "https://mastodon.social/users/bpugh",
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "type" : "Article",
+ "uploadMedia" : null,
+ "url" : "https://baptiste.gelez.xyz/~/PlumeDevelopment/this-month-in-plume-june-2018/"
+}
diff --git a/test/fixtures/tesla_mock/framatube.org-video.json b/test/fixtures/tesla_mock/framatube.org-video.json
new file mode 100644
index 000000000..3d53f0c97
--- /dev/null
+++ b/test/fixtures/tesla_mock/framatube.org-video.json
@@ -0,0 +1 @@
+{"type":"Video","id":"https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206","name":"Déframasoftisons Internet [Framasoft]","duration":"PT3622S","uuid":"6050732a-8a7a-43d4-a6cd-809525a1d206","tag":[{"type":"Hashtag","name":"déframasoftisons"},{"type":"Hashtag","name":"EPN23"},{"type":"Hashtag","name":"framaconf"},{"type":"Hashtag","name":"Framasoft"},{"type":"Hashtag","name":"pyg"}],"category":{"identifier":"15","name":"Science & Technology"},"views":122,"sensitive":false,"waitTranscoding":false,"state":1,"commentsEnabled":true,"downloadEnabled":true,"published":"2020-05-24T18:34:31.569Z","originallyPublishedAt":"2019-11-30T23:00:00.000Z","updated":"2020-07-05T09:01:01.720Z","mediaType":"text/markdown","content":"Après avoir mené avec un certain succès la campagne « Dégooglisons Internet » en 2014, l’association Framasoft annonce fin 2019 arrêter progressivement un certain nombre de ses services alternatifs aux GAFAM. Pourquoi ?\r\n\r\nTranscription par @april...","support":null,"subtitleLanguage":[],"icon":{"type":"Image","url":"https://framatube.org/static/thumbnails/6050732a-8a7a-43d4-a6cd-809525a1d206.jpg","mediaType":"image/jpeg","width":223,"height":122},"url":[{"type":"Link","mediaType":"text/html","href":"https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206"},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4","height":1080,"size":1157359410,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309939","height":1080,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-1080.torrent","height":1080},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-1080.torrent&xt=urn:btih:381c9429900552e23a4eb506318f1fa01e4d63a8&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fwebseed%2F6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4&ws=https%3A%2F%2Fpeertube.iselfhost.com%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4&ws=https%3A%2F%2Ftube.privacytools.io%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4&ws=https%3A%2F%2Fpeertube.live%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4","height":1080},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-480.mp4","height":480,"size":250095131,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309941","height":480,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-480.torrent","height":480},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-480.torrent&xt=urn:btih:a181dcbb5368ab5c31cc9ff07634becb72c344ee&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fwebseed%2F6050732a-8a7a-43d4-a6cd-809525a1d206-480.mp4&ws=https%3A%2F%2Fpeertube.iselfhost.com%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-480.mp4&ws=https%3A%2F%2Ftube.privacytools.io%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-480.mp4&ws=https%3A%2F%2Fpeertube.live%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-480.mp4","height":480},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-360.mp4","height":360,"size":171357733,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309942","height":360,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-360.torrent","height":360},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-360.torrent&xt=urn:btih:aedfa9479ea04a175eee0b0bd0bda64076308746&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fwebseed%2F6050732a-8a7a-43d4-a6cd-809525a1d206-360.mp4&ws=https%3A%2F%2Fpeertube.iselfhost.com%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-360.mp4&ws=https%3A%2F%2Ftube.privacytools.io%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-360.mp4&ws=https%3A%2F%2Fpeertube.live%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-360.mp4","height":360},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-720.mp4","height":720,"size":497100839,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309943","height":720,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-720.torrent","height":720},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-720.torrent&xt=urn:btih:71971668f82a3b24ac71bc3a982848dd8dc5a5f5&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fwebseed%2F6050732a-8a7a-43d4-a6cd-809525a1d206-720.mp4&ws=https%3A%2F%2Fpeertube.iselfhost.com%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-720.mp4&ws=https%3A%2F%2Ftube.privacytools.io%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-720.mp4&ws=https%3A%2F%2Fpeertube.live%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-720.mp4","height":720},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-240.mp4","height":240,"size":113038439,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309944","height":240,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-240.torrent","height":240},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-240.torrent&xt=urn:btih:c42aa6c95efb28d9f114ebd98537f7b00fa72246&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fwebseed%2F6050732a-8a7a-43d4-a6cd-809525a1d206-240.mp4&ws=https%3A%2F%2Fpeertube.iselfhost.com%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-240.mp4&ws=https%3A%2F%2Ftube.privacytools.io%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-240.mp4","height":240},{"type":"Link","mediaType":"application/x-mpegURL","href":"https://framatube.org/static/streaming-playlists/hls/6050732a-8a7a-43d4-a6cd-809525a1d206/master.m3u8","tag":[{"type":"Infohash","name":"f7428214539626e062f300f2ca4cf9154575144e"},{"type":"Infohash","name":"46e236dffb1ea6b9123a5396cbe88e97dd94cc6c"},{"type":"Infohash","name":"11f1045830b5d786c788f2594d19f128764e7d87"},{"type":"Infohash","name":"4327ad3e0d84de100130a27e9ab6fe40c4284f0e"},{"type":"Infohash","name":"41e2eee8e7b23a63c23a77c40a46de11492a4831"},{"type":"Link","name":"sha256","mediaType":"application/json","href":"https://framatube.org/static/streaming-playlists/hls/6050732a-8a7a-43d4-a6cd-809525a1d206/segments-sha256.json"},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/streaming-playlists/hls/6050732a-8a7a-43d4-a6cd-809525a1d206/6050732a-8a7a-43d4-a6cd-809525a1d206-1080-fragmented.mp4","height":1080,"size":1156777472,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309940","height":1080,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-1080-hls.torrent","height":1080},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-1080-hls.torrent&xt=urn:btih:0204d780ebfab0d5d9d3476a038e812ad792deeb&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F6050732a-8a7a-43d4-a6cd-809525a1d206%2F6050732a-8a7a-43d4-a6cd-809525a1d206-1080-fragmented.mp4","height":1080},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/streaming-playlists/hls/6050732a-8a7a-43d4-a6cd-809525a1d206/6050732a-8a7a-43d4-a6cd-809525a1d206-480-fragmented.mp4","height":480,"size":249562889,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309945","height":480,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-480-hls.torrent","height":480},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-480-hls.torrent&xt=urn:btih:5d14f38ded29de629668fe1cfc61a75f4cce2628&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F6050732a-8a7a-43d4-a6cd-809525a1d206%2F6050732a-8a7a-43d4-a6cd-809525a1d206-480-fragmented.mp4","height":480},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/streaming-playlists/hls/6050732a-8a7a-43d4-a6cd-809525a1d206/6050732a-8a7a-43d4-a6cd-809525a1d206-360-fragmented.mp4","height":360,"size":170836415,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309946","height":360,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-360-hls.torrent","height":360},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-360-hls.torrent&xt=urn:btih:30125488789080ad405ebcee6c214945f31b8f30&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F6050732a-8a7a-43d4-a6cd-809525a1d206%2F6050732a-8a7a-43d4-a6cd-809525a1d206-360-fragmented.mp4","height":360},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/streaming-playlists/hls/6050732a-8a7a-43d4-a6cd-809525a1d206/6050732a-8a7a-43d4-a6cd-809525a1d206-720-fragmented.mp4","height":720,"size":496533741,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309947","height":720,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-720-hls.torrent","height":720},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-720-hls.torrent&xt=urn:btih:8ed1e8bccde709901c26e315fc8f53bfd26d1ba6&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F6050732a-8a7a-43d4-a6cd-809525a1d206%2F6050732a-8a7a-43d4-a6cd-809525a1d206-720-fragmented.mp4","height":720},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/streaming-playlists/hls/6050732a-8a7a-43d4-a6cd-809525a1d206/6050732a-8a7a-43d4-a6cd-809525a1d206-240-fragmented.mp4","height":240,"size":112529249,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309948","height":240,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-240-hls.torrent","height":240},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-240-hls.torrent&xt=urn:btih:8b452bf4e70b9078d4e74ca8b5523cc9dc70d10a&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F6050732a-8a7a-43d4-a6cd-809525a1d206%2F6050732a-8a7a-43d4-a6cd-809525a1d206-240-fragmented.mp4","height":240}]}],"likes":"https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206/likes","dislikes":"https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206/dislikes","shares":"https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206/announces","comments":"https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206/comments","attributedTo":[{"type":"Person","id":"https://framatube.org/accounts/framasoft"},{"type":"Group","id":"https://framatube.org/video-channels/bf54d359-cfad-4935-9d45-9d6be93f63e8"}],"to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://framatube.org/accounts/framasoft/followers"],"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"RsaSignature2017":"https://w3id.org/security#RsaSignature2017"},{"pt":"https://joinpeertube.org/ns#","sc":"http://schema.org#","Hashtag":"as:Hashtag","uuid":"sc:identifier","category":"sc:category","licence":"sc:license","subtitleLanguage":"sc:subtitleLanguage","sensitive":"as:sensitive","language":"sc:inLanguage","Infohash":"pt:Infohash","Playlist":"pt:Playlist","PlaylistElement":"pt:PlaylistElement","originallyPublishedAt":"sc:datePublished","views":{"@type":"sc:Number","@id":"pt:views"},"state":{"@type":"sc:Number","@id":"pt:state"},"size":{"@type":"sc:Number","@id":"pt:size"},"fps":{"@type":"sc:Number","@id":"pt:fps"},"startTimestamp":{"@type":"sc:Number","@id":"pt:startTimestamp"},"stopTimestamp":{"@type":"sc:Number","@id":"pt:stopTimestamp"},"position":{"@type":"sc:Number","@id":"pt:position"},"commentsEnabled":{"@type":"sc:Boolean","@id":"pt:commentsEnabled"},"downloadEnabled":{"@type":"sc:Boolean","@id":"pt:downloadEnabled"},"waitTranscoding":{"@type":"sc:Boolean","@id":"pt:waitTranscoding"},"support":{"@type":"sc:Text","@id":"pt:support"},"likes":{"@id":"as:likes","@type":"@id"},"dislikes":{"@id":"as:dislikes","@type":"@id"},"playlists":{"@id":"pt:playlists","@type":"@id"},"shares":{"@id":"as:shares","@type":"@id"},"comments":{"@id":"as:comments","@type":"@id"}}]} \ No newline at end of file
diff --git a/test/fixtures/tesla_mock/https___framatube.org_accounts_framasoft.json b/test/fixtures/tesla_mock/https___framatube.org_accounts_framasoft.json
new file mode 100644
index 000000000..1c3f779b3
--- /dev/null
+++ b/test/fixtures/tesla_mock/https___framatube.org_accounts_framasoft.json
@@ -0,0 +1 @@
+{"type":"Person","id":"https://framatube.org/accounts/framasoft","following":"https://framatube.org/accounts/framasoft/following","followers":"https://framatube.org/accounts/framasoft/followers","playlists":"https://framatube.org/accounts/framasoft/playlists","inbox":"https://framatube.org/accounts/framasoft/inbox","outbox":"https://framatube.org/accounts/framasoft/outbox","preferredUsername":"framasoft","url":"https://framatube.org/accounts/framasoft","name":"Framasoft","endpoints":{"sharedInbox":"https://framatube.org/inbox"},"publicKey":{"id":"https://framatube.org/accounts/framasoft#main-key","owner":"https://framatube.org/accounts/framasoft","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuRh3frgIg866D0y0FThp\nSUkJImMcHGkUvpYQYv2iUgarZZtEbwT8PfQf0bJazy+cP8KqQmMDf5PBhT7dfdny\nf/GKGMw9Olc+QISeKDj3sqZ3Csrm4KV4avMGCfth6eSU7LozojeSGCXdUFz/8UgE\nfhV4mJjEX/FbwRYoKlagv5rY9mkX5XomzZU+z9j6ZVXyofwOwJvmI1hq0SYDv2bc\neB/RgIh/H0nyMtF8o+0CT42FNEET9j9m1BKOBtPzwZHmitKRkEmui5cK256s1laB\nT61KHpcD9gQKkQ+I3sFEzCBUJYfVo6fUe+GehBZuAfq4qDhd15SfE4K9veDscDFI\nTwIDAQAB\n-----END PUBLIC KEY-----"},"icon":{"type":"Image","mediaType":"image/png","url":"https://framatube.org/lazy-static/avatars/f73876f5-1d45-4f8a-942a-d3d5d5ac5dc1.png"},"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"RsaSignature2017":"https://w3id.org/security#RsaSignature2017","pt":"https://joinpeertube.org/ns#","sc":"http://schema.org#","Hashtag":"as:Hashtag","uuid":"sc:identifier","category":"sc:category","licence":"sc:license","subtitleLanguage":"sc:subtitleLanguage","sensitive":"as:sensitive","language":"sc:inLanguage","expires":"sc:expires","CacheFile":"pt:CacheFile","Infohash":"pt:Infohash","originallyPublishedAt":"sc:datePublished","views":{"@type":"sc:Number","@id":"pt:views"},"state":{"@type":"sc:Number","@id":"pt:state"},"size":{"@type":"sc:Number","@id":"pt:size"},"fps":{"@type":"sc:Number","@id":"pt:fps"},"startTimestamp":{"@type":"sc:Number","@id":"pt:startTimestamp"},"stopTimestamp":{"@type":"sc:Number","@id":"pt:stopTimestamp"},"position":{"@type":"sc:Number","@id":"pt:position"},"commentsEnabled":{"@type":"sc:Boolean","@id":"pt:commentsEnabled"},"downloadEnabled":{"@type":"sc:Boolean","@id":"pt:downloadEnabled"},"waitTranscoding":{"@type":"sc:Boolean","@id":"pt:waitTranscoding"},"support":{"@type":"sc:Text","@id":"pt:support"}},{"likes":{"@id":"as:likes","@type":"@id"},"dislikes":{"@id":"as:dislikes","@type":"@id"},"playlists":{"@id":"pt:playlists","@type":"@id"},"shares":{"@id":"as:shares","@type":"@id"},"comments":{"@id":"as:comments","@type":"@id"}}],"summary":null} \ No newline at end of file
diff --git a/test/fixtures/tesla_mock/https___osada.macgirvin.com.html b/test/fixtures/tesla_mock/https___osada.macgirvin.com.html
new file mode 100644
index 000000000..880273d74
--- /dev/null
+++ b/test/fixtures/tesla_mock/https___osada.macgirvin.com.html
@@ -0,0 +1,301 @@
+<!DOCTYPE html >
+<html prefix="og: http://ogp.me/ns#">
+<head>
+ <title>Osada</title>
+ <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
+<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1, user-scalable=0"/>
+<meta property="generator" content="osada"/>
+<link rel="stylesheet" href="http://osada.macgirvin.com/library/fork-awesome/css/fork-awesome.min.css?v=2.3" type="text/css" media="screen">
+<link rel="stylesheet" href="http://osada.macgirvin.com/vendor/twbs/bootstrap/dist/css/bootstrap.min.css?v=2.3" type="text/css" media="screen">
+<link rel="stylesheet" href="http://osada.macgirvin.com/library/bootstrap-tagsinput/bootstrap-tagsinput.css?v=2.3" type="text/css" media="screen">
+<link rel="stylesheet" href="http://osada.macgirvin.com/view/css/bootstrap-red.css?v=2.3" type="text/css" media="screen">
+<link rel="stylesheet" href="http://osada.macgirvin.com/library/datetimepicker/jquery.datetimepicker.css?v=2.3" type="text/css" media="screen">
+<link rel="stylesheet" href="http://osada.macgirvin.com/library/bootstrap-colorpicker/dist/css/bootstrap-colorpicker.min.css?v=2.3" type="text/css" media="screen">
+<link rel="stylesheet" href="http://osada.macgirvin.com/library/tiptip/tipTip.css?v=2.3" type="text/css" media="screen">
+<link rel="stylesheet" href="http://osada.macgirvin.com/library/jgrowl/jquery.jgrowl.css?v=2.3" type="text/css" media="screen">
+<link rel="stylesheet" href="http://osada.macgirvin.com/library/jRange/jquery.range.css?v=2.3" type="text/css" media="screen">
+<link rel="stylesheet" href="http://osada.macgirvin.com/view/css/conversation.css?v=2.3" type="text/css" media="screen">
+<link rel="stylesheet" href="http://osada.macgirvin.com/view/css/widgets.css?v=2.3" type="text/css" media="screen">
+<link rel="stylesheet" href="http://osada.macgirvin.com/view/css/colorbox.css?v=2.3" type="text/css" media="screen">
+<link rel="stylesheet" href="http://osada.macgirvin.com/library/justifiedGallery/justifiedGallery.min.css?v=2.3" type="text/css" media="screen">
+<link rel="stylesheet" href="http://osada.macgirvin.com/view/css/default.css?v=2.3" type="text/css" media="screen">
+<link rel="stylesheet" href="http://osada.macgirvin.com/view/css/mod_home.css?v=2.3" type="text/css" media="screen">
+<link rel="stylesheet" href="http://osada.macgirvin.com/view/theme/redbasic/php/style.pcss?v=2.3" type="text/css" media="screen">
+
+<script>
+
+ var aStr = {
+
+ 'delitem' : "Delete this item?",
+ 'comment' : "Comment",
+ 'showmore' : "<i class='fa fa-chevron-down'></i> show all",
+ 'showfewer' : "<i class='fa fa-chevron-up'></i> show less",
+ 'divgrowmore' : "<i class='fa fa-chevron-down'></i> expand",
+ 'divgrowless' : "<i class='fa fa-chevron-up'></i> collapse",
+ 'pwshort' : "Password too short",
+ 'pwnomatch' : "Passwords do not match",
+ 'everybody' : "everybody",
+ 'passphrase' : "Secret Passphrase",
+ 'passhint' : "Passphrase hint",
+ 'permschange' : "Notice: Permissions have changed but have not yet been submitted.",
+ 'closeAll' : "close all",
+ 'nothingnew' : "Nothing new here",
+ 'rating_desc' : "Rate This Channel (this is public)",
+ 'rating_val' : "Rating",
+ 'rating_text' : "Describe (optional)",
+ 'submit' : "Submit",
+ 'linkurl' : "Please enter a link URL",
+ 'leavethispage' : "Unsaved changes. Are you sure you wish to leave this page?",
+ 'location' : "Location",
+ 'lovely' : "lovely",
+ 'wonderful' : "wonderful",
+ 'fantastic' : "fantastic",
+ 'great' : "great",
+ 'nick_invld1' : "Your chosen nickname was either already taken or not valid. Please use our suggestion (",
+ 'nick_invld2' : ") or enter a new one.",
+ 'nick_valid' : "Thank you, this nickname is valid.",
+ 'name_empty' : "A channel name is required.",
+ 'name_ok1' : "This is a ",
+ 'name_ok2' : " channel name",
+
+
+
+ 't01' : "",
+ 't02' : "",
+ 't03' : "ago",
+ 't04' : "from now",
+ 't05' : "less than a minute",
+ 't06' : "about a minute",
+ 't07' : "%d minutes",
+ 't08' : "about an hour",
+ 't09' : "about %d hours",
+ 't10' : "a day",
+ 't11' : "%d days",
+ 't12' : "about a month",
+ 't13' : "%d months",
+ 't14' : "about a year",
+ 't15' : "%d years",
+ 't16' : " ",
+ 't17' : "[]",
+
+ 'monthNames' : [ "January","February","March","April","May","June","July","August","September","October","November","December" ],
+ 'monthNamesShort' : [ "Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec" ],
+ 'dayNames' : ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],
+ 'dayNamesShort' : ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],
+ 'today' : "today",
+ 'month' : "month",
+ 'week' : "week",
+ 'day' : "day",
+ 'allday' : "All day"
+ };
+
+</script>
+
+
+<script src="http://osada.macgirvin.com/view/js/jquery.js?v=2.3"></script>
+<script src="http://osada.macgirvin.com/library/justifiedGallery/jquery.justifiedGallery.min.js?v=2.3"></script>
+<script src="http://osada.macgirvin.com/library/sprintf.js/dist/sprintf.min.js?v=2.3"></script>
+<script src="http://osada.macgirvin.com/library/textcomplete/textcomplete.min.js?v=2.3"></script>
+<script src="http://osada.macgirvin.com/view/js/autocomplete.js?v=2.3"></script>
+<script src="http://osada.macgirvin.com/library/jquery.timeago.js?v=2.3"></script>
+<script src="http://osada.macgirvin.com/library/readmore.js/readmore.js?v=2.3"></script>
+<script src="http://osada.macgirvin.com/library/sticky-kit/sticky-kit.min.js?v=2.3"></script>
+<script src="http://osada.macgirvin.com/library/jgrowl/jquery.jgrowl_minimized.js?v=2.3"></script>
+<script src="http://osada.macgirvin.com/view/js/acl.js?v=2.3"></script>
+<script src="http://osada.macgirvin.com/view/js/webtoolkit.base64.js?v=2.3"></script>
+<script src="http://osada.macgirvin.com/library/jRange/jquery.range.js?v=2.3"></script>
+<script src="http://osada.macgirvin.com/library/colorbox/jquery.colorbox-min.js?v=2.3"></script>
+<script src="http://osada.macgirvin.com/library/jquery.AreYouSure/jquery.are-you-sure.js?v=2.3"></script>
+<script src="http://osada.macgirvin.com/library/tableofcontents/jquery.toc.js?v=2.3"></script>
+<script src="http://osada.macgirvin.com/library/imagesloaded/imagesloaded.pkgd.min.js?v=2.3"></script>
+<script src="http://osada.macgirvin.com/vendor/twbs/bootstrap/dist/js/bootstrap.bundle.min.js?v=2.3"></script>
+<script src="http://osada.macgirvin.com/library/bootbox/bootbox.min.js?v=2.3"></script>
+<script src="http://osada.macgirvin.com/library/bootstrap-tagsinput/bootstrap-tagsinput.js?v=2.3"></script>
+<script src="http://osada.macgirvin.com/library/datetimepicker/jquery.datetimepicker.js?v=2.3"></script>
+<script src="http://osada.macgirvin.com/library/bootstrap-colorpicker/dist/js/bootstrap-colorpicker.js?v=2.3"></script>
+<script src="http://osada.macgirvin.com/view/theme/redbasic/js/redbasic.js?v=2.3"></script>
+
+
+
+<script>
+ var updateInterval = 80000;
+ var localUser = false;
+ var zid = null;
+ var justifiedGalleryActive = false;
+ var preloadImages = 0;
+</script>
+
+
+
+<script>$(document).ready(function() { $("#nav-search-text").search_autocomplete('https://osada.macgirvin.com/acl');});</script><script src="http://osada.macgirvin.com/view/js/main.js?v=2.3"></script>
+</head>
+<body>
+ <header></header>
+ <nav class="navbar fixed-top navbar-expand-lg navbar-dark bg-dark"><div class="project-banner" title="Powered by Osada"><a href="https://osada.macgirvin.com/">&#x2638;</a></div>
+<div class="d-lg-none pt-1 pb-1">
+ <a class="btn btn-primary btn-sm text-white" href="#" title="Sign in" id="login_nav_btn_collapse" data-toggle="modal" data-target="#nav-login">
+ Login
+ </a>
+ </div>
+<div class="navbar-toggler-right">
+ <button id="expand-aside" type="button" class="d-lg-none navbar-toggler border-0" data-toggle="offcanvas" data-target="#region_1">
+ <i class="fa fa-arrow-circle-right" id="expand-aside-icon"></i>
+ </button>
+ <button id="menu-btn" class="navbar-toggler border-0" type="button" data-toggle="collapse" data-target="#navbar-collapse-2">
+ <i class="fa fa-bars"></i>
+ </button>
+</div>
+<div class="collapse navbar-collapse" id="navbar-collapse-1">
+ <ul class="navbar-nav mr-auto">
+ <li class="nav-item d-lg-flex">
+ <a class="nav-link" href="#" title="Sign in" id="login_nav_btn" data-toggle="modal" data-target="#nav-login">
+ Login
+ </a>
+ </li>
+ </ul>
+
+ <div id="banner" class="navbar-text"></div>
+
+ <ul id="nav-right" class="navbar-nav ml-auto">
+ <li class="nav-item collapse clearfix" id="nav-search">
+ <form class="form-inline" method="get" action="search" role="search">
+ <input class="form-control form-control-sm mt-1 mr-2" id="nav-search-text" type="text" value="" placeholder="@name, !forum, #tag, content" name="search" title="Search site @name, !forum, #tag, ?docs, content" onclick="this.submit();" onblur="closeMenu('nav-search'); openMenu('nav-search-btn');"/>
+ </form>
+ <div id="nav-search-spinner" class="spinner-wrapper">
+ <div class="spinner s"></div>
+ </div>
+ </li>
+ <li class="nav-item" id="nav-search-btn">
+ <a class="nav-link" href="#nav-search" title="Search site @name, !forum, #tag, ?docs, content" onclick="openMenu('nav-search'); closeMenu('nav-search-btn'); $('#nav-search-text').focus(); return false;"><i class="fa fa-fw fa-search"></i></a>
+ </li>
+ <li class="nav-item dropdown" id="app-menu">
+ <a class="nav-link" href="#" data-toggle="dropdown"><i class="fa fa-fw fa-bars"></i></a>
+ <div id="dropdown-menu" class="dropdown-menu dropdown-menu-right">
+ <a class="dropdown-item" href="https://osada.macgirvin.com/directory"><i class="generic-icons-nav fa fa-fw fa-sitemap"></i>Directory</a>
+
+
+ <a class="dropdown-item" href="https://osada.macgirvin.com/lang"><i class="generic-icons-nav fa fa-fw fa-language"></i>Language</a>
+
+
+ <a class="dropdown-item" href="https://osada.macgirvin.com/search"><i class="generic-icons-nav fa fa-fw fa-search"></i>Search</a>
+
+
+ </div>
+ </li>
+ </ul>
+</div>
+<div class="collapse d-lg-none" id="navbar-collapse-2">
+ <div class="navbar-nav mr-auto">
+ <a class="nav-link" href="https://osada.macgirvin.com/directory"><i class="generic-icons-nav fa fa-fw fa-sitemap"></i>Directory</a>
+
+
+ <a class="nav-link" href="https://osada.macgirvin.com/lang"><i class="generic-icons-nav fa fa-fw fa-language"></i>Language</a>
+
+
+ <a class="nav-link" href="https://osada.macgirvin.com/search"><i class="generic-icons-nav fa fa-fw fa-search"></i>Search</a>
+
+
+ </div>
+</div>
+</nav>
+ <main>
+ <aside id="region_1"><div class="aside_spacer"><div id="left_aside_wrapper"></div></div></aside>
+ <section id="region_2"><h1 class="home-welcome">Welcome to Osada</h1><form action="https://osada.macgirvin.com/" id="main-login" method="post">
+ <input type="hidden" name="auth-params" value="login"/>
+ <div id="login-main">
+ <div id="login-input" class="form-group">
+ <div id="id_username_wrapper" class="form-group">
+ <label for="id_username" id="label_username">Login/Email</label>
+ <input class="form-control" name="username" id="id_username" type="text" value="">
+ <small id="help_username" class="form-text text-muted"></small>
+ </div>
+ <div class="form-group">
+ <label for="id_password">Password</label>
+ <input class="form-control" type="password" name="password" id="id_password" value=""> <small id="help_password" class="form-text text-muted"></small>
+ </div>
+ <div id="remember_container" class="clearfix form-group checkbox">
+ <label for="id_remember">Remember me</label>
+ <div class="float-right"><input type="checkbox" name="remember" id="id_remember" value="1"/><label class="switchlabel" for="id_remember"> <span class="onoffswitch-inner" data-on="Yes" data-off="No"></span><span class="onoffswitch-switch"></span></label></div>
+ <small class="form-text text-muted"></small>
+ </div>
+ <button type="submit" name="submit" class="btn btn-block btn-primary">Login</button>
+ </div>
+ <div id="login-extra-links">
+ <a href="https://osada.macgirvin.com/pubsites" title="Create an account to access services and applications" id="register-link" class="pull-right">Register</a> <a href="lostpass" title="Forgot your password?" id="lost-password-link">Password Reset</a>
+ </div>
+ <hr>
+ <a href="rmagic" class="btn btn-block btn-outline-success rmagic-button">Remote Authentication</a>
+ </div>
+ <input type="hidden" name="0" value=""/>
+ </form>
+<script type="text/javascript"> $(document).ready(function() { $("#id_username").focus();} );</script>
+<div id="nav-login" class="modal" tabindex="-1" role="dialog">
+ <div class="modal-dialog" role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+ <h4 class="modal-title">Login</h4>
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+ </div>
+ <div class="modal-body">
+ <div class="form-group">
+ <form action="https://osada.macgirvin.com/" id="main-login" method="post">
+ <input type="hidden" name="auth-params" value="login"/>
+ <div id="login-main">
+ <div id="login-input" class="form-group">
+ <div id="id_username_wrapper" class="form-group">
+ <label for="id_username" id="label_username">Login/Email</label>
+ <input class="form-control" name="username" id="id_username" type="text" value="">
+ <small id="help_username" class="form-text text-muted"></small>
+ </div>
+ <div class="form-group">
+ <label for="id_password">Password</label>
+ <input class="form-control" type="password" name="password" id="id_password" value=""> <small id="help_password" class="form-text text-muted"></small>
+ </div>
+ <div id="remember_me_container" class="clearfix form-group checkbox">
+ <label for="id_remember_me">Remember me</label>
+ <div class="float-right"><input type="checkbox" name="remember_me" id="id_remember_me" value="1"/><label class="switchlabel" for="id_remember_me"> <span class="onoffswitch-inner" data-on="Yes" data-off="No"></span><span class="onoffswitch-switch"></span></label></div>
+ <small class="form-text text-muted"></small>
+ </div>
+ <button type="submit" name="submit" class="btn btn-block btn-primary">Login</button>
+ </div>
+ <div id="login-extra-links">
+ <a href="https://osada.macgirvin.com/pubsites" title="Create an account to access services and applications" id="register-link" class="pull-right">Register</a> <a href="lostpass" title="Forgot your password?" id="lost-password-link">Password Reset</a>
+ </div>
+ <hr>
+ <a href="rmagic" class="btn btn-block btn-outline-success rmagic-button">Remote Authentication</a>
+ </div>
+ <input type="hidden" name="0" value=""/>
+ </form>
+
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+ <div id="page-footer"></div>
+ <div id="pause"></div>
+ </section>
+ <aside id="region_3" class="d-none d-xl-table-cell"><div class="aside_spacer"><div id="right_aside_wrapper"></div></div></aside>
+ </main>
+ <footer></footer>
+</body>
+</html>
+<!--
+ FILE ARCHIVED ON 11:49:38 Jan 26, 2019 AND RETRIEVED FROM THE
+ INTERNET ARCHIVE ON 04:27:56 Mar 02, 2020.
+
+ CONTENT MAY BE PROTECTED BY COPYRIGHT (17 U.S.C. SECTION 108(a)(3)).
+-->
+<!--
+playback timings (ms):
+ exclusion.robots: 0.217
+ CDXLines.iter: 14.7 (3)
+ LoadShardBlock: 165.298 (3)
+ esindex: 0.01
+ PetaboxLoader3.datanode: 72.599 (4)
+ exclusion.robots.policy: 0.208
+ RedisCDXSource: 16.804
+ PetaboxLoader3.resolve: 146.316 (4)
+ captures_list: 199.59
+ load_resource: 56.473
+--> \ No newline at end of file
diff --git a/test/fixtures/tesla_mock/peertube.moe-vid.json b/test/fixtures/tesla_mock/peertube.moe-vid.json
index 76296eb7d..ceebb90b7 100644
--- a/test/fixtures/tesla_mock/peertube.moe-vid.json
+++ b/test/fixtures/tesla_mock/peertube.moe-vid.json
@@ -1 +1,187 @@
-{"type":"Video","id":"https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3","name":"Friday Night","duration":"PT29S","uuid":"df5f464b-be8d-46fb-ad81-2d4c2d1630e3","tag":[{"type":"Hashtag","name":"feels"}],"views":12,"sensitive":false,"commentsEnabled":true,"published":"2018-03-23T16:43:22.988Z","updated":"2018-03-24T16:28:46.002Z","mediaType":"text/markdown","content":"tfw\r\n\r\n\r\nsong is 'my old piano' by diana ross","support":null,"icon":{"type":"Image","url":"https://peertube.moe/static/thumbnails/df5f464b-be8d-46fb-ad81-2d4c2d1630e3.jpg","mediaType":"image/jpeg","width":200,"height":110},"url":[{"type":"Link","mimeType":"video/mp4","href":"https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4","width":480,"size":5015880},{"type":"Link","mimeType":"application/x-bittorrent","href":"https://peertube.moe/static/torrents/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.torrent","width":480},{"type":"Link","mimeType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fpeertube.moe%2Fstatic%2Ftorrents%2Fdf5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.torrent&xt=urn:btih:11d3af6b5c812a376c2b29cdbd46e5fb42ee730e&dn=Friday+Night&tr=wss%3A%2F%2Fpeertube.moe%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fpeertube.moe%2Ftracker%2Fannounce&ws=https%3A%2F%2Fpeertube.moe%2Fstatic%2Fwebseed%2Fdf5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4","width":480},{"type":"Link","mimeType":"video/mp4","href":"https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-360.mp4","width":360,"size":3620040},{"type":"Link","mimeType":"application/x-bittorrent","href":"https://peertube.moe/static/torrents/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-360.torrent","width":360},{"type":"Link","mimeType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fpeertube.moe%2Fstatic%2Ftorrents%2Fdf5f464b-be8d-46fb-ad81-2d4c2d1630e3-360.torrent&xt=urn:btih:1c3885b4d7cdb46193b62b9b76e72b1409cfb297&dn=Friday+Night&tr=wss%3A%2F%2Fpeertube.moe%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fpeertube.moe%2Ftracker%2Fannounce&ws=https%3A%2F%2Fpeertube.moe%2Fstatic%2Fwebseed%2Fdf5f464b-be8d-46fb-ad81-2d4c2d1630e3-360.mp4","width":360},{"type":"Link","mimeType":"video/mp4","href":"https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-240.mp4","width":240,"size":2305488},{"type":"Link","mimeType":"application/x-bittorrent","href":"https://peertube.moe/static/torrents/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-240.torrent","width":240},{"type":"Link","mimeType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fpeertube.moe%2Fstatic%2Ftorrents%2Fdf5f464b-be8d-46fb-ad81-2d4c2d1630e3-240.torrent&xt=urn:btih:ac5773352d9e26f982d2da63acfb244f01ccafa4&dn=Friday+Night&tr=wss%3A%2F%2Fpeertube.moe%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fpeertube.moe%2Ftracker%2Fannounce&ws=https%3A%2F%2Fpeertube.moe%2Fstatic%2Fwebseed%2Fdf5f464b-be8d-46fb-ad81-2d4c2d1630e3-240.mp4","width":240},{"type":"Link","mimeType":"video/mp4","href":"https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-720.mp4","width":720,"size":7928231},{"type":"Link","mimeType":"application/x-bittorrent","href":"https://peertube.moe/static/torrents/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-720.torrent","width":720},{"type":"Link","mimeType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fpeertube.moe%2Fstatic%2Ftorrents%2Fdf5f464b-be8d-46fb-ad81-2d4c2d1630e3-720.torrent&xt=urn:btih:b591068f4533c4e2865bb4cbb89887aecccdc523&dn=Friday+Night&tr=wss%3A%2F%2Fpeertube.moe%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fpeertube.moe%2Ftracker%2Fannounce&ws=https%3A%2F%2Fpeertube.moe%2Fstatic%2Fwebseed%2Fdf5f464b-be8d-46fb-ad81-2d4c2d1630e3-720.mp4","width":720},{"type":"Link","mimeType":"text/html","href":"https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"}],"likes":{"id":"https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3/likes","type":"OrderedCollection","totalItems":0,"orderedItems":[]},"dislikes":{"id":"https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3/dislikes","type":"OrderedCollection","totalItems":0,"orderedItems":[]},"shares":{"id":"https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3/announces","type":"OrderedCollection","totalItems":2,"orderedItems":["https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3/announces/465","https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3/announces/1"]},"comments":{"id":"https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3/comments","type":"OrderedCollection","totalItems":0,"orderedItems":[]},"attributedTo":[{"type":"Group","id":"https://peertube.moe/video-channels/5224869f-aa63-4c83-ab3a-87c3a5ac440e"},{"type":"Person","id":"https://peertube.moe/accounts/7even"}],"to":["https://www.w3.org/ns/activitystreams#Public"],"cc":[],"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"RsaSignature2017":"https://w3id.org/security#RsaSignature2017","Hashtag":"as:Hashtag","uuid":"http://schema.org/identifier","category":"http://schema.org/category","licence":"http://schema.org/license","sensitive":"as:sensitive","language":"http://schema.org/inLanguage","views":"http://schema.org/Number","size":"http://schema.org/Number","commentsEnabled":"http://schema.org/Boolean","support":"http://schema.org/Text"},{"likes":{"@id":"as:likes","@type":"@id"},"dislikes":{"@id":"as:dislikes","@type":"@id"},"shares":{"@id":"as:shares","@type":"@id"},"comments":{"@id":"as:comments","@type":"@id"}}]} \ No newline at end of file
+{
+ "@context" : [
+ "https://www.w3.org/ns/activitystreams",
+ "https://w3id.org/security/v1",
+ {
+ "Hashtag" : "as:Hashtag",
+ "RsaSignature2017" : "https://w3id.org/security#RsaSignature2017",
+ "category" : "http://schema.org/category",
+ "commentsEnabled" : "http://schema.org/Boolean",
+ "language" : "http://schema.org/inLanguage",
+ "licence" : "http://schema.org/license",
+ "sensitive" : "as:sensitive",
+ "size" : "http://schema.org/Number",
+ "support" : "http://schema.org/Text",
+ "uuid" : "http://schema.org/identifier",
+ "views" : "http://schema.org/Number"
+ },
+ {
+ "comments" : {
+ "@id" : "as:comments",
+ "@type" : "@id"
+ },
+ "dislikes" : {
+ "@id" : "as:dislikes",
+ "@type" : "@id"
+ },
+ "likes" : {
+ "@id" : "as:likes",
+ "@type" : "@id"
+ },
+ "shares" : {
+ "@id" : "as:shares",
+ "@type" : "@id"
+ }
+ }
+ ],
+ "attributedTo" : [
+ {
+ "id" : "https://peertube.moe/video-channels/5224869f-aa63-4c83-ab3a-87c3a5ac440e",
+ "type" : "Group"
+ },
+ {
+ "id" : "https://peertube.moe/accounts/7even",
+ "type" : "Person"
+ }
+ ],
+ "cc" : [],
+ "comments" : {
+ "id" : "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3/comments",
+ "orderedItems" : [],
+ "totalItems" : 0,
+ "type" : "OrderedCollection"
+ },
+ "commentsEnabled" : true,
+ "content" : "tfw\r\n\r\n\r\nsong is 'my old piano' by diana ross",
+ "dislikes" : {
+ "id" : "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3/dislikes",
+ "orderedItems" : [],
+ "totalItems" : 0,
+ "type" : "OrderedCollection"
+ },
+ "duration" : "PT29S",
+ "icon" : {
+ "height" : 110,
+ "mediaType" : "image/jpeg",
+ "type" : "Image",
+ "url" : "https://peertube.moe/static/thumbnails/df5f464b-be8d-46fb-ad81-2d4c2d1630e3.jpg",
+ "width" : 200
+ },
+ "id" : "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3",
+ "likes" : {
+ "id" : "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3/likes",
+ "orderedItems" : [],
+ "totalItems" : 0,
+ "type" : "OrderedCollection"
+ },
+ "mediaType" : "text/markdown",
+ "name" : "Friday Night",
+ "published" : "2018-03-23T16:43:22.988Z",
+ "sensitive" : false,
+ "shares" : {
+ "id" : "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3/announces",
+ "orderedItems" : [
+ "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3/announces/465",
+ "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3/announces/1"
+ ],
+ "totalItems" : 2,
+ "type" : "OrderedCollection"
+ },
+ "support" : null,
+ "tag" : [
+ {
+ "name" : "feels",
+ "type" : "Hashtag"
+ }
+ ],
+ "to" : [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "type" : "Video",
+ "updated" : "2018-03-24T16:28:46.002Z",
+ "url" : [
+ {
+ "href" : "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
+ "mimeType" : "video/mp4",
+ "size" : 5015880,
+ "type" : "Link",
+ "width" : 480
+ },
+ {
+ "href" : "https://peertube.moe/static/torrents/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.torrent",
+ "mimeType" : "application/x-bittorrent",
+ "type" : "Link",
+ "width" : 480
+ },
+ {
+ "href" : "magnet:?xs=https%3A%2F%2Fpeertube.moe%2Fstatic%2Ftorrents%2Fdf5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.torrent&xt=urn:btih:11d3af6b5c812a376c2b29cdbd46e5fb42ee730e&dn=Friday+Night&tr=wss%3A%2F%2Fpeertube.moe%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fpeertube.moe%2Ftracker%2Fannounce&ws=https%3A%2F%2Fpeertube.moe%2Fstatic%2Fwebseed%2Fdf5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
+ "mimeType" : "application/x-bittorrent;x-scheme-handler/magnet",
+ "type" : "Link",
+ "width" : 480
+ },
+ {
+ "href" : "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-360.mp4",
+ "mimeType" : "video/mp4",
+ "size" : 3620040,
+ "type" : "Link",
+ "width" : 360
+ },
+ {
+ "href" : "https://peertube.moe/static/torrents/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-360.torrent",
+ "mimeType" : "application/x-bittorrent",
+ "type" : "Link",
+ "width" : 360
+ },
+ {
+ "href" : "magnet:?xs=https%3A%2F%2Fpeertube.moe%2Fstatic%2Ftorrents%2Fdf5f464b-be8d-46fb-ad81-2d4c2d1630e3-360.torrent&xt=urn:btih:1c3885b4d7cdb46193b62b9b76e72b1409cfb297&dn=Friday+Night&tr=wss%3A%2F%2Fpeertube.moe%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fpeertube.moe%2Ftracker%2Fannounce&ws=https%3A%2F%2Fpeertube.moe%2Fstatic%2Fwebseed%2Fdf5f464b-be8d-46fb-ad81-2d4c2d1630e3-360.mp4",
+ "mimeType" : "application/x-bittorrent;x-scheme-handler/magnet",
+ "type" : "Link",
+ "width" : 360
+ },
+ {
+ "href" : "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-240.mp4",
+ "mimeType" : "video/mp4",
+ "size" : 2305488,
+ "type" : "Link",
+ "width" : 240
+ },
+ {
+ "href" : "https://peertube.moe/static/torrents/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-240.torrent",
+ "mimeType" : "application/x-bittorrent",
+ "type" : "Link",
+ "width" : 240
+ },
+ {
+ "href" : "magnet:?xs=https%3A%2F%2Fpeertube.moe%2Fstatic%2Ftorrents%2Fdf5f464b-be8d-46fb-ad81-2d4c2d1630e3-240.torrent&xt=urn:btih:ac5773352d9e26f982d2da63acfb244f01ccafa4&dn=Friday+Night&tr=wss%3A%2F%2Fpeertube.moe%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fpeertube.moe%2Ftracker%2Fannounce&ws=https%3A%2F%2Fpeertube.moe%2Fstatic%2Fwebseed%2Fdf5f464b-be8d-46fb-ad81-2d4c2d1630e3-240.mp4",
+ "mimeType" : "application/x-bittorrent;x-scheme-handler/magnet",
+ "type" : "Link",
+ "width" : 240
+ },
+ {
+ "href" : "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-720.mp4",
+ "mimeType" : "video/mp4",
+ "size" : 7928231,
+ "type" : "Link",
+ "width" : 720
+ },
+ {
+ "href" : "https://peertube.moe/static/torrents/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-720.torrent",
+ "mimeType" : "application/x-bittorrent",
+ "type" : "Link",
+ "width" : 720
+ },
+ {
+ "href" : "magnet:?xs=https%3A%2F%2Fpeertube.moe%2Fstatic%2Ftorrents%2Fdf5f464b-be8d-46fb-ad81-2d4c2d1630e3-720.torrent&xt=urn:btih:b591068f4533c4e2865bb4cbb89887aecccdc523&dn=Friday+Night&tr=wss%3A%2F%2Fpeertube.moe%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fpeertube.moe%2Ftracker%2Fannounce&ws=https%3A%2F%2Fpeertube.moe%2Fstatic%2Fwebseed%2Fdf5f464b-be8d-46fb-ad81-2d4c2d1630e3-720.mp4",
+ "mimeType" : "application/x-bittorrent;x-scheme-handler/magnet",
+ "type" : "Link",
+ "width" : 720
+ },
+ {
+ "href" : "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3",
+ "mimeType" : "text/html",
+ "type" : "Link"
+ }
+ ],
+ "uuid" : "df5f464b-be8d-46fb-ad81-2d4c2d1630e3",
+ "views" : 12
+}
diff --git a/test/html_test.exs b/test/html_test.exs
index 0a4b4ebbc..f8907c8b4 100644
--- a/test/html_test.exs
+++ b/test/html_test.exs
@@ -237,5 +237,19 @@ defmodule Pleroma.HTMLTest do
assert {:ok, nil} = HTML.extract_first_external_url(object, object.data["content"])
end
+
+ test "skips attachment links" do
+ user = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{
+ status:
+ "<a href=\"https://pleroma.gov/media/d24caa3a498e21e0298377a9ca0149a4f4f8b767178aacf837542282e2d94fb1.png?name=image.png\" class=\"attachment\">image.png</a>"
+ })
+
+ object = Object.normalize(activity)
+
+ assert {:ok, nil} = HTML.extract_first_external_url(object, object.data["content"])
+ end
end
end
diff --git a/test/notification_test.exs b/test/notification_test.exs
index 526f43fab..13e82ab2a 100644
--- a/test/notification_test.exs
+++ b/test/notification_test.exs
@@ -22,6 +22,16 @@ defmodule Pleroma.NotificationTest do
alias Pleroma.Web.Streamer
describe "create_notifications" do
+ test "never returns nil" do
+ user = insert(:user)
+ other_user = insert(:user, %{invisible: true})
+
+ {:ok, activity} = CommonAPI.post(user, %{status: "yeah"})
+ {:ok, activity} = CommonAPI.react_with_emoji(activity.id, other_user, "☕")
+
+ refute {:ok, [nil]} == Notification.create_notifications(activity)
+ end
+
test "creates a notification for an emoji reaction" do
user = insert(:user)
other_user = insert(:user)
@@ -314,6 +324,44 @@ defmodule Pleroma.NotificationTest do
{:ok, status} = CommonAPI.post(author, %{status: "hey @#{user.nickname}"})
refute Notification.create_notification(status, user)
end
+
+ test "it doesn't create notifications if content matches with an irreversible filter" do
+ user = insert(:user)
+ subscriber = insert(:user)
+
+ User.subscribe(subscriber, user)
+ insert(:filter, user: subscriber, phrase: "cofe", hide: true)
+
+ {:ok, status} = CommonAPI.post(user, %{status: "got cofe?"})
+
+ assert {:ok, []} == Notification.create_notifications(status)
+ end
+
+ test "it creates notifications if content matches with a not irreversible filter" do
+ user = insert(:user)
+ subscriber = insert(:user)
+
+ User.subscribe(subscriber, user)
+ insert(:filter, user: subscriber, phrase: "cofe", hide: false)
+
+ {:ok, status} = CommonAPI.post(user, %{status: "got cofe?"})
+ {:ok, [notification]} = Notification.create_notifications(status)
+
+ assert notification
+ end
+
+ test "it creates notifications when someone likes user's status with a filtered word" do
+ user = insert(:user)
+ other_user = insert(:user)
+ insert(:filter, user: user, phrase: "tesla", hide: true)
+
+ {:ok, activity_one} = CommonAPI.post(user, %{status: "wow tesla"})
+ {:ok, activity_two} = CommonAPI.favorite(other_user, activity_one.id)
+
+ {:ok, [notification]} = Notification.create_notifications(activity_two)
+
+ assert notification
+ end
end
describe "follow / follow_request notifications" do
@@ -980,8 +1028,13 @@ defmodule Pleroma.NotificationTest do
end
describe "for_user" do
- test "it returns notifications for muted user without notifications" do
+ setup do
user = insert(:user)
+
+ {:ok, %{user: user}}
+ end
+
+ test "it returns notifications for muted user without notifications", %{user: user} do
muted = insert(:user)
{:ok, _user_relationships} = User.mute(user, muted, false)
@@ -992,8 +1045,7 @@ defmodule Pleroma.NotificationTest do
assert notification.activity.object
end
- test "it doesn't return notifications for muted user with notifications" do
- user = insert(:user)
+ test "it doesn't return notifications for muted user with notifications", %{user: user} do
muted = insert(:user)
{:ok, _user_relationships} = User.mute(user, muted)
@@ -1002,8 +1054,7 @@ defmodule Pleroma.NotificationTest do
assert Notification.for_user(user) == []
end
- test "it doesn't return notifications for blocked user" do
- user = insert(:user)
+ test "it doesn't return notifications for blocked user", %{user: user} do
blocked = insert(:user)
{:ok, _user_relationship} = User.block(user, blocked)
@@ -1012,8 +1063,7 @@ defmodule Pleroma.NotificationTest do
assert Notification.for_user(user) == []
end
- test "it doesn't return notifications for domain-blocked non-followed user" do
- user = insert(:user)
+ test "it doesn't return notifications for domain-blocked non-followed user", %{user: user} do
blocked = insert(:user, ap_id: "http://some-domain.com")
{:ok, user} = User.block_domain(user, "some-domain.com")
@@ -1034,8 +1084,7 @@ defmodule Pleroma.NotificationTest do
assert length(Notification.for_user(user)) == 1
end
- test "it doesn't return notifications for muted thread" do
- user = insert(:user)
+ test "it doesn't return notifications for muted thread", %{user: user} do
another_user = insert(:user)
{:ok, activity} = CommonAPI.post(another_user, %{status: "hey @#{user.nickname}"})
@@ -1044,8 +1093,7 @@ defmodule Pleroma.NotificationTest do
assert Notification.for_user(user) == []
end
- test "it returns notifications from a muted user when with_muted is set" do
- user = insert(:user)
+ test "it returns notifications from a muted user when with_muted is set", %{user: user} do
muted = insert(:user)
{:ok, _user_relationships} = User.mute(user, muted)
@@ -1054,8 +1102,9 @@ defmodule Pleroma.NotificationTest do
assert length(Notification.for_user(user, %{with_muted: true})) == 1
end
- test "it doesn't return notifications from a blocked user when with_muted is set" do
- user = insert(:user)
+ test "it doesn't return notifications from a blocked user when with_muted is set", %{
+ user: user
+ } do
blocked = insert(:user)
{:ok, _user_relationship} = User.block(user, blocked)
@@ -1065,8 +1114,8 @@ defmodule Pleroma.NotificationTest do
end
test "when with_muted is set, " <>
- "it doesn't return notifications from a domain-blocked non-followed user" do
- user = insert(:user)
+ "it doesn't return notifications from a domain-blocked non-followed user",
+ %{user: user} do
blocked = insert(:user, ap_id: "http://some-domain.com")
{:ok, user} = User.block_domain(user, "some-domain.com")
@@ -1075,8 +1124,7 @@ defmodule Pleroma.NotificationTest do
assert Enum.empty?(Notification.for_user(user, %{with_muted: true}))
end
- test "it returns notifications from muted threads when with_muted is set" do
- user = insert(:user)
+ test "it returns notifications from muted threads when with_muted is set", %{user: user} do
another_user = insert(:user)
{:ok, activity} = CommonAPI.post(another_user, %{status: "hey @#{user.nickname}"})
@@ -1084,5 +1132,33 @@ defmodule Pleroma.NotificationTest do
{:ok, _} = Pleroma.ThreadMute.add_mute(user.id, activity.data["context"])
assert length(Notification.for_user(user, %{with_muted: true})) == 1
end
+
+ test "it doesn't return notifications about mentions with filtered word", %{user: user} do
+ insert(:filter, user: user, phrase: "cofe", hide: true)
+ another_user = insert(:user)
+
+ {:ok, _activity} = CommonAPI.post(another_user, %{status: "@#{user.nickname} got cofe?"})
+
+ assert Enum.empty?(Notification.for_user(user))
+ end
+
+ test "it returns notifications about mentions with not hidden filtered word", %{user: user} do
+ insert(:filter, user: user, phrase: "test", hide: false)
+ another_user = insert(:user)
+
+ {:ok, _} = CommonAPI.post(another_user, %{status: "@#{user.nickname} test"})
+
+ assert length(Notification.for_user(user)) == 1
+ end
+
+ test "it returns notifications about favorites with filtered word", %{user: user} do
+ insert(:filter, user: user, phrase: "cofe", hide: true)
+ another_user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(user, %{status: "Give me my cofe!"})
+ {:ok, _} = CommonAPI.favorite(another_user, activity.id)
+
+ assert length(Notification.for_user(user)) == 1
+ end
end
end
diff --git a/test/object/fetcher_test.exs b/test/object/fetcher_test.exs
index c06e91f12..d9098ea1b 100644
--- a/test/object/fetcher_test.exs
+++ b/test/object/fetcher_test.exs
@@ -26,6 +26,46 @@ defmodule Pleroma.Object.FetcherTest do
:ok
end
+ describe "error cases" do
+ setup do
+ mock(fn
+ %{method: :get, url: "https://social.sakamoto.gq/notice/9wTkLEnuq47B25EehM"} ->
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/fetch_mocks/9wTkLEnuq47B25EehM.json")
+ }
+
+ %{method: :get, url: "https://social.sakamoto.gq/users/eal"} ->
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/fetch_mocks/eal.json")
+ }
+
+ %{method: :get, url: "https://busshi.moe/users/tuxcrafting/statuses/104410921027210069"} ->
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/fetch_mocks/104410921027210069.json")
+ }
+
+ %{method: :get, url: "https://busshi.moe/users/tuxcrafting"} ->
+ %Tesla.Env{
+ status: 500
+ }
+ end)
+
+ :ok
+ end
+
+ @tag capture_log: true
+ test "it works when fetching the OP actor errors out" do
+ # Here we simulate a case where the author of the OP can't be read
+ assert {:ok, _} =
+ Fetcher.fetch_object_from_id(
+ "https://social.sakamoto.gq/notice/9wTkLEnuq47B25EehM"
+ )
+ end
+ end
+
describe "max thread distance restriction" do
@ap_id "http://mastodon.example.org/@admin/99541947525187367"
setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
diff --git a/test/support/factory.ex b/test/support/factory.ex
index 6e22b66a4..635d83650 100644
--- a/test/support/factory.ex
+++ b/test/support/factory.ex
@@ -67,6 +67,7 @@ defmodule Pleroma.Factory do
data = %{
"type" => "Note",
"content" => text,
+ "source" => text,
"id" => Pleroma.Web.ActivityPub.Utils.generate_object_id(),
"actor" => user.ap_id,
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
@@ -427,4 +428,12 @@ defmodule Pleroma.Factory do
user: build(:user)
}
end
+
+ def filter_factory do
+ %Pleroma.Filter{
+ user: build(:user),
+ filter_id: sequence(:filter_id, & &1),
+ phrase: "cofe"
+ }
+ end
end
diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex
index 3d5128835..19a202654 100644
--- a/test/support/http_request_mock.ex
+++ b/test/support/http_request_mock.ex
@@ -308,6 +308,22 @@ defmodule HttpRequestMock do
}}
end
+ def get("https://framatube.org/accounts/framasoft", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/https___framatube.org_accounts_framasoft.json")
+ }}
+ end
+
+ def get("https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/framatube.org-video.json")
+ }}
+ end
+
def get("https://peertube.social/accounts/craigmaloney", _, _, _) do
{:ok,
%Tesla.Env{
@@ -1326,6 +1342,18 @@ defmodule HttpRequestMock do
{:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/relay/relay.json")}}
end
+ def get("http://localhost:4001/", _, "", Accept: "text/html") do
+ {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/7369654.html")}}
+ end
+
+ def get("https://osada.macgirvin.com/", _, "", Accept: "text/html") do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/https___osada.macgirvin.com.html")
+ }}
+ end
+
def get(url, query, body, headers) do
{:error,
"Mock response not implemented for GET #{inspect(url)}, #{query}, #{inspect(body)}, #{
diff --git a/test/tasks/relay_test.exs b/test/tasks/relay_test.exs
index a8ba0658d..79ab72002 100644
--- a/test/tasks/relay_test.exs
+++ b/test/tasks/relay_test.exs
@@ -10,6 +10,8 @@ defmodule Mix.Tasks.Pleroma.RelayTest do
alias Pleroma.Web.ActivityPub.Utils
use Pleroma.DataCase
+ import Pleroma.Factory
+
setup_all do
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
@@ -46,7 +48,8 @@ defmodule Mix.Tasks.Pleroma.RelayTest do
describe "running unfollow" do
test "relay is unfollowed" do
- target_instance = "http://mastodon.example.org/users/admin"
+ user = insert(:user)
+ target_instance = user.ap_id
Mix.Tasks.Pleroma.Relay.run(["follow", target_instance])
@@ -71,7 +74,7 @@ defmodule Mix.Tasks.Pleroma.RelayTest do
assert undo_activity.data["type"] == "Undo"
assert undo_activity.data["actor"] == local_user.ap_id
- assert undo_activity.data["object"] == cancelled_activity.data
+ assert undo_activity.data["object"]["id"] == cancelled_activity.data["id"]
refute "#{target_instance}/followers" in User.following(local_user)
end
end
diff --git a/test/tasks/user_test.exs b/test/tasks/user_test.exs
index 9220d23fc..2a3e62e26 100644
--- a/test/tasks/user_test.exs
+++ b/test/tasks/user_test.exs
@@ -110,7 +110,23 @@ defmodule Mix.Tasks.Pleroma.UserTest do
test "a remote user's create activity is deleted when the object has been pruned" do
user = insert(:user)
+ user2 = insert(:user)
+
{:ok, post} = CommonAPI.post(user, %{status: "uguu"})
+ {:ok, post2} = CommonAPI.post(user2, %{status: "test"})
+ obj = Object.normalize(post2)
+
+ {:ok, like_object, meta} = Pleroma.Web.ActivityPub.Builder.like(user, obj)
+
+ {:ok, like_activity, _meta} =
+ Pleroma.Web.ActivityPub.Pipeline.common_pipeline(
+ like_object,
+ Keyword.put(meta, :local, true)
+ )
+
+ like_activity.data["object"]
+ |> Pleroma.Object.get_by_ap_id()
+ |> Repo.delete()
clear_config([:instance, :federating], true)
@@ -127,6 +143,7 @@ defmodule Mix.Tasks.Pleroma.UserTest do
assert %{deactivated: true} = User.get_by_nickname(user.nickname)
assert called(Pleroma.Web.Federator.publish(:_))
+ refute Pleroma.Repo.get(Pleroma.Activity, like_activity.id)
end
refute Activity.get_by_id(post.id)
diff --git a/test/upload_test.exs b/test/upload_test.exs
index 2abf0edec..b06b54487 100644
--- a/test/upload_test.exs
+++ b/test/upload_test.exs
@@ -107,6 +107,19 @@ defmodule Pleroma.UploadTest do
describe "Storing a file with the Local uploader" do
setup [:ensure_local_uploader]
+ test "does not allow descriptions longer than the post limit" do
+ clear_config([:instance, :description_limit], 2)
+ File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
+
+ file = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image_tmp.jpg"),
+ filename: "image.jpg"
+ }
+
+ {:error, :description_too_long} = Upload.store(file, description: "123")
+ end
+
test "returns a media url" do
File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
diff --git a/test/user_search_test.exs b/test/user_search_test.exs
index 17c63322a..f030523d3 100644
--- a/test/user_search_test.exs
+++ b/test/user_search_test.exs
@@ -17,7 +17,7 @@ defmodule Pleroma.UserSearchTest do
describe "User.search" do
setup do: clear_config([:instance, :limit_to_local_content])
- test "excluded invisible users from results" do
+ test "excludes invisible users from results" do
user = insert(:user, %{nickname: "john t1000"})
insert(:user, %{invisible: true, nickname: "john t800"})
@@ -25,6 +25,15 @@ defmodule Pleroma.UserSearchTest do
assert found_user.id == user.id
end
+ test "excludes service actors from results" do
+ insert(:user, actor_type: "Application", nickname: "user1")
+ service = insert(:user, actor_type: "Service", nickname: "user2")
+ person = insert(:user, actor_type: "Person", nickname: "user3")
+
+ assert [found_user1, found_user2] = User.search("user")
+ assert [found_user1.id, found_user2.id] -- [service.id, person.id] == []
+ end
+
test "accepts limit parameter" do
Enum.each(0..4, &insert(:user, %{nickname: "john#{&1}"}))
assert length(User.search("john", limit: 3)) == 3
diff --git a/test/user_test.exs b/test/user_test.exs
index 9b66f3f51..7126bb539 100644
--- a/test/user_test.exs
+++ b/test/user_test.exs
@@ -597,6 +597,31 @@ defmodule Pleroma.UserTest do
refute user.last_refreshed_at == orig_user.last_refreshed_at
end
+ test "if nicknames clash, the old user gets a prefix with the old id to the nickname" do
+ a_week_ago = NaiveDateTime.add(NaiveDateTime.utc_now(), -604_800)
+
+ orig_user =
+ insert(
+ :user,
+ local: false,
+ nickname: "admin@mastodon.example.org",
+ ap_id: "http://mastodon.example.org/users/harinezumigari",
+ last_refreshed_at: a_week_ago
+ )
+
+ assert orig_user.last_refreshed_at == a_week_ago
+
+ {:ok, user} = User.get_or_fetch_by_ap_id("http://mastodon.example.org/users/admin")
+
+ assert user.inbox
+
+ refute user.id == orig_user.id
+
+ orig_user = User.get_by_id(orig_user.id)
+
+ assert orig_user.nickname == "#{orig_user.id}.admin@mastodon.example.org"
+ end
+
@tag capture_log: true
test "it returns the old user if stale, but unfetchable" do
a_week_ago = NaiveDateTime.add(NaiveDateTime.utc_now(), -604_800)
diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs
index be7ab2ae4..38c98f658 100644
--- a/test/web/activity_pub/activity_pub_test.exs
+++ b/test/web/activity_pub/activity_pub_test.exs
@@ -507,6 +507,33 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
activities = ActivityPub.fetch_activities_for_context("2hu", %{blocking_user: user})
assert activities == [activity_two, activity]
end
+
+ test "doesn't return activities with filtered words" do
+ user = insert(:user)
+ user_two = insert(:user)
+ insert(:filter, user: user, phrase: "test", hide: true)
+
+ {:ok, %{id: id1, data: %{"context" => context}}} = CommonAPI.post(user, %{status: "1"})
+
+ {:ok, %{id: id2}} = CommonAPI.post(user_two, %{status: "2", in_reply_to_status_id: id1})
+
+ {:ok, %{id: id3} = user_activity} =
+ CommonAPI.post(user, %{status: "3 test?", in_reply_to_status_id: id2})
+
+ {:ok, %{id: id4} = filtered_activity} =
+ CommonAPI.post(user_two, %{status: "4 test!", in_reply_to_status_id: id3})
+
+ {:ok, _} = CommonAPI.post(user, %{status: "5", in_reply_to_status_id: id4})
+
+ activities =
+ context
+ |> ActivityPub.fetch_activities_for_context(%{user: user})
+ |> Enum.map(& &1.id)
+
+ assert length(activities) == 4
+ assert user_activity.id in activities
+ refute filtered_activity.id in activities
+ end
end
test "doesn't return blocked activities" do
@@ -642,7 +669,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
refute activity in activities
followed_user = insert(:user)
- ActivityPub.follow(user, followed_user)
+ CommonAPI.follow(user, followed_user)
{:ok, repeat_activity} = CommonAPI.repeat(activity.id, followed_user)
activities = ActivityPub.fetch_activities([], %{blocking_user: user, skip_preload: true})
@@ -785,6 +812,75 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
assert activity == expected_activity
end
+ describe "irreversible filters" do
+ setup do
+ user = insert(:user)
+ user_two = insert(:user)
+
+ insert(:filter, user: user_two, phrase: "cofe", hide: true)
+ insert(:filter, user: user_two, phrase: "ok boomer", hide: true)
+ insert(:filter, user: user_two, phrase: "test", hide: false)
+
+ params = %{
+ type: ["Create", "Announce"],
+ user: user_two
+ }
+
+ {:ok, %{user: user, user_two: user_two, params: params}}
+ end
+
+ test "it returns statuses if they don't contain exact filter words", %{
+ user: user,
+ params: params
+ } do
+ {:ok, _} = CommonAPI.post(user, %{status: "hey"})
+ {:ok, _} = CommonAPI.post(user, %{status: "got cofefe?"})
+ {:ok, _} = CommonAPI.post(user, %{status: "I am not a boomer"})
+ {:ok, _} = CommonAPI.post(user, %{status: "ok boomers"})
+ {:ok, _} = CommonAPI.post(user, %{status: "ccofee is not a word"})
+ {:ok, _} = CommonAPI.post(user, %{status: "this is a test"})
+
+ activities = ActivityPub.fetch_activities([], params)
+
+ assert Enum.count(activities) == 6
+ end
+
+ test "it does not filter user's own statuses", %{user_two: user_two, params: params} do
+ {:ok, _} = CommonAPI.post(user_two, %{status: "Give me some cofe!"})
+ {:ok, _} = CommonAPI.post(user_two, %{status: "ok boomer"})
+
+ activities = ActivityPub.fetch_activities([], params)
+
+ assert Enum.count(activities) == 2
+ end
+
+ test "it excludes statuses with filter words", %{user: user, params: params} do
+ {:ok, _} = CommonAPI.post(user, %{status: "Give me some cofe!"})
+ {:ok, _} = CommonAPI.post(user, %{status: "ok boomer"})
+ {:ok, _} = CommonAPI.post(user, %{status: "is it a cOfE?"})
+ {:ok, _} = CommonAPI.post(user, %{status: "cofe is all I need"})
+ {:ok, _} = CommonAPI.post(user, %{status: "— ok BOOMER\n"})
+
+ activities = ActivityPub.fetch_activities([], params)
+
+ assert Enum.empty?(activities)
+ end
+
+ test "it returns all statuses if user does not have any filters" do
+ another_user = insert(:user)
+ {:ok, _} = CommonAPI.post(another_user, %{status: "got cofe?"})
+ {:ok, _} = CommonAPI.post(another_user, %{status: "test!"})
+
+ activities =
+ ActivityPub.fetch_activities([], %{
+ type: ["Create", "Announce"],
+ user: another_user
+ })
+
+ assert Enum.count(activities) == 2
+ end
+ end
+
describe "public fetch activities" do
test "doesn't retrieve unlisted activities" do
user = insert(:user)
@@ -917,24 +1013,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
end
end
- describe "following / unfollowing" do
- test "it reverts follow activity" do
- follower = insert(:user)
- followed = insert(:user)
-
- with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
- assert {:error, :reverted} = ActivityPub.follow(follower, followed)
- end
-
- assert Repo.aggregate(Activity, :count, :id) == 0
- assert Repo.aggregate(Object, :count, :id) == 0
- end
-
+ describe "unfollowing" do
test "it reverts unfollow activity" do
follower = insert(:user)
followed = insert(:user)
- {:ok, follow_activity} = ActivityPub.follow(follower, followed)
+ {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
assert {:error, :reverted} = ActivityPub.unfollow(follower, followed)
@@ -947,21 +1031,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
assert activity.data["object"] == followed.ap_id
end
- test "creates a follow activity" do
- follower = insert(:user)
- followed = insert(:user)
-
- {:ok, activity} = ActivityPub.follow(follower, followed)
- assert activity.data["type"] == "Follow"
- assert activity.data["actor"] == follower.ap_id
- assert activity.data["object"] == followed.ap_id
- end
-
test "creates an undo activity for the last follow" do
follower = insert(:user)
followed = insert(:user)
- {:ok, follow_activity} = ActivityPub.follow(follower, followed)
+ {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
{:ok, activity} = ActivityPub.unfollow(follower, followed)
assert activity.data["type"] == "Undo"
@@ -978,7 +1052,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
follower = insert(:user)
followed = insert(:user, %{locked: true})
- {:ok, follow_activity} = ActivityPub.follow(follower, followed)
+ {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
{:ok, activity} = ActivityPub.unfollow(follower, followed)
assert activity.data["type"] == "Undo"
@@ -992,54 +1066,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
end
end
- describe "blocking" do
- test "reverts block activity on error" do
- [blocker, blocked] = insert_list(2, :user)
-
- with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
- assert {:error, :reverted} = ActivityPub.block(blocker, blocked)
- end
-
- assert Repo.aggregate(Activity, :count, :id) == 0
- assert Repo.aggregate(Object, :count, :id) == 0
- end
-
- test "creates a block activity" do
- clear_config([:instance, :federating], true)
- blocker = insert(:user)
- blocked = insert(:user)
-
- with_mock Pleroma.Web.Federator,
- publish: fn _ -> nil end do
- {:ok, activity} = ActivityPub.block(blocker, blocked)
-
- assert activity.data["type"] == "Block"
- assert activity.data["actor"] == blocker.ap_id
- assert activity.data["object"] == blocked.ap_id
-
- assert called(Pleroma.Web.Federator.publish(activity))
- end
- end
-
- test "works with outgoing blocks disabled, but doesn't federate" do
- clear_config([:instance, :federating], true)
- clear_config([:activitypub, :outgoing_blocks], false)
- blocker = insert(:user)
- blocked = insert(:user)
-
- with_mock Pleroma.Web.Federator,
- publish: fn _ -> nil end do
- {:ok, activity} = ActivityPub.block(blocker, blocked)
-
- assert activity.data["type"] == "Block"
- assert activity.data["actor"] == blocker.ap_id
- assert activity.data["object"] == blocked.ap_id
-
- refute called(Pleroma.Web.Federator.publish(:_))
- end
- end
- end
-
describe "timeline post-processing" do
test "it filters broken threads" do
user1 = insert(:user)
diff --git a/test/web/activity_pub/mrf/anti_link_spam_policy_test.exs b/test/web/activity_pub/mrf/anti_link_spam_policy_test.exs
index 1a13699be..6867c9853 100644
--- a/test/web/activity_pub/mrf/anti_link_spam_policy_test.exs
+++ b/test/web/activity_pub/mrf/anti_link_spam_policy_test.exs
@@ -33,7 +33,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicyTest do
describe "with new user" do
test "it allows posts without links" do
- user = insert(:user)
+ user = insert(:user, local: false)
assert user.note_count == 0
@@ -45,7 +45,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicyTest do
end
test "it disallows posts with links" do
- user = insert(:user)
+ user = insert(:user, local: false)
assert user.note_count == 0
@@ -55,6 +55,18 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicyTest do
{:reject, _} = AntiLinkSpamPolicy.filter(message)
end
+
+ test "it allows posts with links for local users" do
+ user = insert(:user)
+
+ assert user.note_count == 0
+
+ message =
+ @linkful_message
+ |> Map.put("actor", user.ap_id)
+
+ {:ok, _message} = AntiLinkSpamPolicy.filter(message)
+ end
end
describe "with old user" do
diff --git a/test/web/activity_pub/object_validator_test.exs b/test/web/activity_pub/object_validator_test.exs
deleted file mode 100644
index 770a8dcf8..000000000
--- a/test/web/activity_pub/object_validator_test.exs
+++ /dev/null
@@ -1,657 +0,0 @@
-defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do
- use Pleroma.DataCase
-
- alias Pleroma.Object
- alias Pleroma.Web.ActivityPub.ActivityPub
- alias Pleroma.Web.ActivityPub.Builder
- alias Pleroma.Web.ActivityPub.ObjectValidator
- alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
- alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
- alias Pleroma.Web.ActivityPub.Utils
- alias Pleroma.Web.CommonAPI
-
- import Pleroma.Factory
-
- describe "attachments" do
- test "works with honkerific attachments" do
- attachment = %{
- "mediaType" => "",
- "name" => "",
- "summary" => "298p3RG7j27tfsZ9RQ.jpg",
- "type" => "Document",
- "url" => "https://honk.tedunangst.com/d/298p3RG7j27tfsZ9RQ.jpg"
- }
-
- assert {:ok, attachment} =
- AttachmentValidator.cast_and_validate(attachment)
- |> Ecto.Changeset.apply_action(:insert)
-
- assert attachment.mediaType == "application/octet-stream"
- end
-
- test "it turns mastodon attachments into our attachments" do
- attachment = %{
- "url" =>
- "http://mastodon.example.org/system/media_attachments/files/000/000/002/original/334ce029e7bfb920.jpg",
- "type" => "Document",
- "name" => nil,
- "mediaType" => "image/jpeg"
- }
-
- {:ok, attachment} =
- AttachmentValidator.cast_and_validate(attachment)
- |> Ecto.Changeset.apply_action(:insert)
-
- assert [
- %{
- href:
- "http://mastodon.example.org/system/media_attachments/files/000/000/002/original/334ce029e7bfb920.jpg",
- type: "Link",
- mediaType: "image/jpeg"
- }
- ] = attachment.url
-
- assert attachment.mediaType == "image/jpeg"
- end
-
- test "it handles our own uploads" do
- user = insert(:user)
-
- file = %Plug.Upload{
- content_type: "image/jpg",
- path: Path.absname("test/fixtures/image.jpg"),
- filename: "an_image.jpg"
- }
-
- {:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id)
-
- {:ok, attachment} =
- attachment.data
- |> AttachmentValidator.cast_and_validate()
- |> Ecto.Changeset.apply_action(:insert)
-
- assert attachment.mediaType == "image/jpeg"
- end
- end
-
- describe "chat message create activities" do
- test "it is invalid if the object already exists" do
- user = insert(:user)
- recipient = insert(:user)
- {:ok, activity} = CommonAPI.post_chat_message(user, recipient, "hey")
- object = Object.normalize(activity, false)
-
- {:ok, create_data, _} = Builder.create(user, object.data, [recipient.ap_id])
-
- {:error, cng} = ObjectValidator.validate(create_data, [])
-
- assert {:object, {"The object to create already exists", []}} in cng.errors
- end
-
- test "it is invalid if the object data has a different `to` or `actor` field" do
- user = insert(:user)
- recipient = insert(:user)
- {:ok, object_data, _} = Builder.chat_message(recipient, user.ap_id, "Hey")
-
- {:ok, create_data, _} = Builder.create(user, object_data, [recipient.ap_id])
-
- {:error, cng} = ObjectValidator.validate(create_data, [])
-
- assert {:to, {"Recipients don't match with object recipients", []}} in cng.errors
- assert {:actor, {"Actor doesn't match with object actor", []}} in cng.errors
- end
- end
-
- describe "chat messages" do
- setup do
- clear_config([:instance, :remote_limit])
- user = insert(:user)
- recipient = insert(:user, local: false)
-
- {:ok, valid_chat_message, _} = Builder.chat_message(user, recipient.ap_id, "hey :firefox:")
-
- %{user: user, recipient: recipient, valid_chat_message: valid_chat_message}
- end
-
- test "let's through some basic html", %{user: user, recipient: recipient} do
- {:ok, valid_chat_message, _} =
- Builder.chat_message(
- user,
- recipient.ap_id,
- "hey <a href='https://example.org'>example</a> <script>alert('uguu')</script>"
- )
-
- assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
-
- assert object["content"] ==
- "hey <a href=\"https://example.org\">example</a> alert(&#39;uguu&#39;)"
- end
-
- test "validates for a basic object we build", %{valid_chat_message: valid_chat_message} do
- assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
-
- assert Map.put(valid_chat_message, "attachment", nil) == object
- end
-
- test "validates for a basic object with an attachment", %{
- valid_chat_message: valid_chat_message,
- user: user
- } do
- file = %Plug.Upload{
- content_type: "image/jpg",
- path: Path.absname("test/fixtures/image.jpg"),
- filename: "an_image.jpg"
- }
-
- {:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id)
-
- valid_chat_message =
- valid_chat_message
- |> Map.put("attachment", attachment.data)
-
- assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
-
- assert object["attachment"]
- end
-
- test "validates for a basic object with an attachment in an array", %{
- valid_chat_message: valid_chat_message,
- user: user
- } do
- file = %Plug.Upload{
- content_type: "image/jpg",
- path: Path.absname("test/fixtures/image.jpg"),
- filename: "an_image.jpg"
- }
-
- {:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id)
-
- valid_chat_message =
- valid_chat_message
- |> Map.put("attachment", [attachment.data])
-
- assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
-
- assert object["attachment"]
- end
-
- test "validates for a basic object with an attachment but without content", %{
- valid_chat_message: valid_chat_message,
- user: user
- } do
- file = %Plug.Upload{
- content_type: "image/jpg",
- path: Path.absname("test/fixtures/image.jpg"),
- filename: "an_image.jpg"
- }
-
- {:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id)
-
- valid_chat_message =
- valid_chat_message
- |> Map.put("attachment", attachment.data)
- |> Map.delete("content")
-
- assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
-
- assert object["attachment"]
- end
-
- test "does not validate if the message has no content", %{
- valid_chat_message: valid_chat_message
- } do
- contentless =
- valid_chat_message
- |> Map.delete("content")
-
- refute match?({:ok, _object, _meta}, ObjectValidator.validate(contentless, []))
- end
-
- test "does not validate if the message is longer than the remote_limit", %{
- valid_chat_message: valid_chat_message
- } do
- Pleroma.Config.put([:instance, :remote_limit], 2)
- refute match?({:ok, _object, _meta}, ObjectValidator.validate(valid_chat_message, []))
- end
-
- test "does not validate if the recipient is blocking the actor", %{
- valid_chat_message: valid_chat_message,
- user: user,
- recipient: recipient
- } do
- Pleroma.User.block(recipient, user)
- refute match?({:ok, _object, _meta}, ObjectValidator.validate(valid_chat_message, []))
- end
-
- test "does not validate if the actor or the recipient is not in our system", %{
- valid_chat_message: valid_chat_message
- } do
- chat_message =
- valid_chat_message
- |> Map.put("actor", "https://raymoo.com/raymoo")
-
- {:error, _} = ObjectValidator.validate(chat_message, [])
-
- chat_message =
- valid_chat_message
- |> Map.put("to", ["https://raymoo.com/raymoo"])
-
- {:error, _} = ObjectValidator.validate(chat_message, [])
- end
-
- test "does not validate for a message with multiple recipients", %{
- valid_chat_message: valid_chat_message,
- user: user,
- recipient: recipient
- } do
- chat_message =
- valid_chat_message
- |> Map.put("to", [user.ap_id, recipient.ap_id])
-
- assert {:error, _} = ObjectValidator.validate(chat_message, [])
- end
-
- test "does not validate if it doesn't concern local users" do
- user = insert(:user, local: false)
- recipient = insert(:user, local: false)
-
- {:ok, valid_chat_message, _} = Builder.chat_message(user, recipient.ap_id, "hey")
- assert {:error, _} = ObjectValidator.validate(valid_chat_message, [])
- end
- end
-
- describe "EmojiReacts" do
- setup do
- user = insert(:user)
- {:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"})
-
- object = Pleroma.Object.get_by_ap_id(post_activity.data["object"])
-
- {:ok, valid_emoji_react, []} = Builder.emoji_react(user, object, "👌")
-
- %{user: user, post_activity: post_activity, valid_emoji_react: valid_emoji_react}
- end
-
- test "it validates a valid EmojiReact", %{valid_emoji_react: valid_emoji_react} do
- assert {:ok, _, _} = ObjectValidator.validate(valid_emoji_react, [])
- end
-
- test "it is not valid without a 'content' field", %{valid_emoji_react: valid_emoji_react} do
- without_content =
- valid_emoji_react
- |> Map.delete("content")
-
- {:error, cng} = ObjectValidator.validate(without_content, [])
-
- refute cng.valid?
- assert {:content, {"can't be blank", [validation: :required]}} in cng.errors
- end
-
- test "it is not valid with a non-emoji content field", %{valid_emoji_react: valid_emoji_react} do
- without_emoji_content =
- valid_emoji_react
- |> Map.put("content", "x")
-
- {:error, cng} = ObjectValidator.validate(without_emoji_content, [])
-
- refute cng.valid?
-
- assert {:content, {"must be a single character emoji", []}} in cng.errors
- end
- end
-
- describe "Undos" do
- setup do
- user = insert(:user)
- {:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"})
- {:ok, like} = CommonAPI.favorite(user, post_activity.id)
- {:ok, valid_like_undo, []} = Builder.undo(user, like)
-
- %{user: user, like: like, valid_like_undo: valid_like_undo}
- end
-
- test "it validates a basic like undo", %{valid_like_undo: valid_like_undo} do
- assert {:ok, _, _} = ObjectValidator.validate(valid_like_undo, [])
- end
-
- test "it does not validate if the actor of the undo is not the actor of the object", %{
- valid_like_undo: valid_like_undo
- } do
- other_user = insert(:user, ap_id: "https://gensokyo.2hu/users/raymoo")
-
- bad_actor =
- valid_like_undo
- |> Map.put("actor", other_user.ap_id)
-
- {:error, cng} = ObjectValidator.validate(bad_actor, [])
-
- assert {:actor, {"not the same as object actor", []}} in cng.errors
- end
-
- test "it does not validate if the object is missing", %{valid_like_undo: valid_like_undo} do
- missing_object =
- valid_like_undo
- |> Map.put("object", "https://gensokyo.2hu/objects/1")
-
- {:error, cng} = ObjectValidator.validate(missing_object, [])
-
- assert {:object, {"can't find object", []}} in cng.errors
- assert length(cng.errors) == 1
- end
- end
-
- describe "deletes" do
- setup do
- user = insert(:user)
- {:ok, post_activity} = CommonAPI.post(user, %{status: "cancel me daddy"})
-
- {:ok, valid_post_delete, _} = Builder.delete(user, post_activity.data["object"])
- {:ok, valid_user_delete, _} = Builder.delete(user, user.ap_id)
-
- %{user: user, valid_post_delete: valid_post_delete, valid_user_delete: valid_user_delete}
- end
-
- test "it is valid for a post deletion", %{valid_post_delete: valid_post_delete} do
- {:ok, valid_post_delete, _} = ObjectValidator.validate(valid_post_delete, [])
-
- assert valid_post_delete["deleted_activity_id"]
- end
-
- test "it is invalid if the object isn't in a list of certain types", %{
- valid_post_delete: valid_post_delete
- } do
- object = Object.get_by_ap_id(valid_post_delete["object"])
-
- data =
- object.data
- |> Map.put("type", "Like")
-
- {:ok, _object} =
- object
- |> Ecto.Changeset.change(%{data: data})
- |> Object.update_and_set_cache()
-
- {:error, cng} = ObjectValidator.validate(valid_post_delete, [])
- assert {:object, {"object not in allowed types", []}} in cng.errors
- end
-
- test "it is valid for a user deletion", %{valid_user_delete: valid_user_delete} do
- assert match?({:ok, _, _}, ObjectValidator.validate(valid_user_delete, []))
- end
-
- test "it's invalid if the id is missing", %{valid_post_delete: valid_post_delete} do
- no_id =
- valid_post_delete
- |> Map.delete("id")
-
- {:error, cng} = ObjectValidator.validate(no_id, [])
-
- assert {:id, {"can't be blank", [validation: :required]}} in cng.errors
- end
-
- test "it's invalid if the object doesn't exist", %{valid_post_delete: valid_post_delete} do
- missing_object =
- valid_post_delete
- |> Map.put("object", "http://does.not/exist")
-
- {:error, cng} = ObjectValidator.validate(missing_object, [])
-
- assert {:object, {"can't find object", []}} in cng.errors
- end
-
- test "it's invalid if the actor of the object and the actor of delete are from different domains",
- %{valid_post_delete: valid_post_delete} do
- valid_user = insert(:user)
-
- valid_other_actor =
- valid_post_delete
- |> Map.put("actor", valid_user.ap_id)
-
- assert match?({:ok, _, _}, ObjectValidator.validate(valid_other_actor, []))
-
- invalid_other_actor =
- valid_post_delete
- |> Map.put("actor", "https://gensokyo.2hu/users/raymoo")
-
- {:error, cng} = ObjectValidator.validate(invalid_other_actor, [])
-
- assert {:actor, {"is not allowed to delete object", []}} in cng.errors
- end
-
- test "it's valid if the actor of the object is a local superuser",
- %{valid_post_delete: valid_post_delete} do
- user =
- insert(:user, local: true, is_moderator: true, ap_id: "https://gensokyo.2hu/users/raymoo")
-
- valid_other_actor =
- valid_post_delete
- |> Map.put("actor", user.ap_id)
-
- {:ok, _, meta} = ObjectValidator.validate(valid_other_actor, [])
- assert meta[:do_not_federate]
- end
- end
-
- describe "likes" do
- setup do
- user = insert(:user)
- {:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"})
-
- valid_like = %{
- "to" => [user.ap_id],
- "cc" => [],
- "type" => "Like",
- "id" => Utils.generate_activity_id(),
- "object" => post_activity.data["object"],
- "actor" => user.ap_id,
- "context" => "a context"
- }
-
- %{valid_like: valid_like, user: user, post_activity: post_activity}
- end
-
- test "returns ok when called in the ObjectValidator", %{valid_like: valid_like} do
- {:ok, object, _meta} = ObjectValidator.validate(valid_like, [])
-
- assert "id" in Map.keys(object)
- end
-
- test "is valid for a valid object", %{valid_like: valid_like} do
- assert LikeValidator.cast_and_validate(valid_like).valid?
- end
-
- test "sets the 'to' field to the object actor if no recipients are given", %{
- valid_like: valid_like,
- user: user
- } do
- without_recipients =
- valid_like
- |> Map.delete("to")
-
- {:ok, object, _meta} = ObjectValidator.validate(without_recipients, [])
-
- assert object["to"] == [user.ap_id]
- end
-
- test "sets the context field to the context of the object if no context is given", %{
- valid_like: valid_like,
- post_activity: post_activity
- } do
- without_context =
- valid_like
- |> Map.delete("context")
-
- {:ok, object, _meta} = ObjectValidator.validate(without_context, [])
-
- assert object["context"] == post_activity.data["context"]
- end
-
- test "it errors when the actor is missing or not known", %{valid_like: valid_like} do
- without_actor = Map.delete(valid_like, "actor")
-
- refute LikeValidator.cast_and_validate(without_actor).valid?
-
- with_invalid_actor = Map.put(valid_like, "actor", "invalidactor")
-
- refute LikeValidator.cast_and_validate(with_invalid_actor).valid?
- end
-
- test "it errors when the object is missing or not known", %{valid_like: valid_like} do
- without_object = Map.delete(valid_like, "object")
-
- refute LikeValidator.cast_and_validate(without_object).valid?
-
- with_invalid_object = Map.put(valid_like, "object", "invalidobject")
-
- refute LikeValidator.cast_and_validate(with_invalid_object).valid?
- end
-
- test "it errors when the actor has already like the object", %{
- valid_like: valid_like,
- user: user,
- post_activity: post_activity
- } do
- _like = CommonAPI.favorite(user, post_activity.id)
-
- refute LikeValidator.cast_and_validate(valid_like).valid?
- end
-
- test "it works when actor or object are wrapped in maps", %{valid_like: valid_like} do
- wrapped_like =
- valid_like
- |> Map.put("actor", %{"id" => valid_like["actor"]})
- |> Map.put("object", %{"id" => valid_like["object"]})
-
- validated = LikeValidator.cast_and_validate(wrapped_like)
-
- assert validated.valid?
-
- assert {:actor, valid_like["actor"]} in validated.changes
- assert {:object, valid_like["object"]} in validated.changes
- end
- end
-
- describe "announces" do
- setup do
- user = insert(:user)
- announcer = insert(:user)
- {:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"})
-
- object = Object.normalize(post_activity, false)
- {:ok, valid_announce, []} = Builder.announce(announcer, object)
-
- %{
- valid_announce: valid_announce,
- user: user,
- post_activity: post_activity,
- announcer: announcer
- }
- end
-
- test "returns ok for a valid announce", %{valid_announce: valid_announce} do
- assert {:ok, _object, _meta} = ObjectValidator.validate(valid_announce, [])
- end
-
- test "returns an error if the object can't be found", %{valid_announce: valid_announce} do
- without_object =
- valid_announce
- |> Map.delete("object")
-
- {:error, cng} = ObjectValidator.validate(without_object, [])
-
- assert {:object, {"can't be blank", [validation: :required]}} in cng.errors
-
- nonexisting_object =
- valid_announce
- |> Map.put("object", "https://gensokyo.2hu/objects/99999999")
-
- {:error, cng} = ObjectValidator.validate(nonexisting_object, [])
-
- assert {:object, {"can't find object", []}} in cng.errors
- end
-
- test "returns an error if we don't have the actor", %{valid_announce: valid_announce} do
- nonexisting_actor =
- valid_announce
- |> Map.put("actor", "https://gensokyo.2hu/users/raymoo")
-
- {:error, cng} = ObjectValidator.validate(nonexisting_actor, [])
-
- assert {:actor, {"can't find user", []}} in cng.errors
- end
-
- test "returns an error if the actor already announced the object", %{
- valid_announce: valid_announce,
- announcer: announcer,
- post_activity: post_activity
- } do
- _announce = CommonAPI.repeat(post_activity.id, announcer)
-
- {:error, cng} = ObjectValidator.validate(valid_announce, [])
-
- assert {:actor, {"already announced this object", []}} in cng.errors
- assert {:object, {"already announced by this actor", []}} in cng.errors
- end
-
- test "returns an error if the actor can't announce the object", %{
- announcer: announcer,
- user: user
- } do
- {:ok, post_activity} =
- CommonAPI.post(user, %{status: "a secret post", visibility: "private"})
-
- object = Object.normalize(post_activity, false)
-
- # Another user can't announce it
- {:ok, announce, []} = Builder.announce(announcer, object, public: false)
-
- {:error, cng} = ObjectValidator.validate(announce, [])
-
- assert {:actor, {"can not announce this object", []}} in cng.errors
-
- # The actor of the object can announce it
- {:ok, announce, []} = Builder.announce(user, object, public: false)
-
- assert {:ok, _, _} = ObjectValidator.validate(announce, [])
-
- # The actor of the object can not announce it publicly
- {:ok, announce, []} = Builder.announce(user, object, public: true)
-
- {:error, cng} = ObjectValidator.validate(announce, [])
-
- assert {:actor, {"can not announce this object publicly", []}} in cng.errors
- end
- end
-
- describe "updates" do
- setup do
- user = insert(:user)
-
- object = %{
- "id" => user.ap_id,
- "name" => "A new name",
- "summary" => "A new bio"
- }
-
- {:ok, valid_update, []} = Builder.update(user, object)
-
- %{user: user, valid_update: valid_update}
- end
-
- test "validates a basic object", %{valid_update: valid_update} do
- assert {:ok, _update, []} = ObjectValidator.validate(valid_update, [])
- end
-
- test "returns an error if the object can't be updated by the actor", %{
- valid_update: valid_update
- } do
- other_user = insert(:user)
-
- update =
- valid_update
- |> Map.put("actor", other_user.ap_id)
-
- assert {:error, _cng} = ObjectValidator.validate(update, [])
- end
- end
-end
diff --git a/test/web/activity_pub/object_validators/announce_validation_test.exs b/test/web/activity_pub/object_validators/announce_validation_test.exs
new file mode 100644
index 000000000..623342f76
--- /dev/null
+++ b/test/web/activity_pub/object_validators/announce_validation_test.exs
@@ -0,0 +1,106 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnouncValidationTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Object
+ alias Pleroma.Web.ActivityPub.Builder
+ alias Pleroma.Web.ActivityPub.ObjectValidator
+ alias Pleroma.Web.CommonAPI
+
+ import Pleroma.Factory
+
+ describe "announces" do
+ setup do
+ user = insert(:user)
+ announcer = insert(:user)
+ {:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"})
+
+ object = Object.normalize(post_activity, false)
+ {:ok, valid_announce, []} = Builder.announce(announcer, object)
+
+ %{
+ valid_announce: valid_announce,
+ user: user,
+ post_activity: post_activity,
+ announcer: announcer
+ }
+ end
+
+ test "returns ok for a valid announce", %{valid_announce: valid_announce} do
+ assert {:ok, _object, _meta} = ObjectValidator.validate(valid_announce, [])
+ end
+
+ test "returns an error if the object can't be found", %{valid_announce: valid_announce} do
+ without_object =
+ valid_announce
+ |> Map.delete("object")
+
+ {:error, cng} = ObjectValidator.validate(without_object, [])
+
+ assert {:object, {"can't be blank", [validation: :required]}} in cng.errors
+
+ nonexisting_object =
+ valid_announce
+ |> Map.put("object", "https://gensokyo.2hu/objects/99999999")
+
+ {:error, cng} = ObjectValidator.validate(nonexisting_object, [])
+
+ assert {:object, {"can't find object", []}} in cng.errors
+ end
+
+ test "returns an error if we don't have the actor", %{valid_announce: valid_announce} do
+ nonexisting_actor =
+ valid_announce
+ |> Map.put("actor", "https://gensokyo.2hu/users/raymoo")
+
+ {:error, cng} = ObjectValidator.validate(nonexisting_actor, [])
+
+ assert {:actor, {"can't find user", []}} in cng.errors
+ end
+
+ test "returns an error if the actor already announced the object", %{
+ valid_announce: valid_announce,
+ announcer: announcer,
+ post_activity: post_activity
+ } do
+ _announce = CommonAPI.repeat(post_activity.id, announcer)
+
+ {:error, cng} = ObjectValidator.validate(valid_announce, [])
+
+ assert {:actor, {"already announced this object", []}} in cng.errors
+ assert {:object, {"already announced by this actor", []}} in cng.errors
+ end
+
+ test "returns an error if the actor can't announce the object", %{
+ announcer: announcer,
+ user: user
+ } do
+ {:ok, post_activity} =
+ CommonAPI.post(user, %{status: "a secret post", visibility: "private"})
+
+ object = Object.normalize(post_activity, false)
+
+ # Another user can't announce it
+ {:ok, announce, []} = Builder.announce(announcer, object, public: false)
+
+ {:error, cng} = ObjectValidator.validate(announce, [])
+
+ assert {:actor, {"can not announce this object", []}} in cng.errors
+
+ # The actor of the object can announce it
+ {:ok, announce, []} = Builder.announce(user, object, public: false)
+
+ assert {:ok, _, _} = ObjectValidator.validate(announce, [])
+
+ # The actor of the object can not announce it publicly
+ {:ok, announce, []} = Builder.announce(user, object, public: true)
+
+ {:error, cng} = ObjectValidator.validate(announce, [])
+
+ assert {:actor, {"can not announce this object publicly", []}} in cng.errors
+ end
+ end
+end
diff --git a/test/web/activity_pub/object_validators/attachment_validator_test.exs b/test/web/activity_pub/object_validators/attachment_validator_test.exs
new file mode 100644
index 000000000..558bb3131
--- /dev/null
+++ b/test/web/activity_pub/object_validators/attachment_validator_test.exs
@@ -0,0 +1,74 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidatorTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
+
+ import Pleroma.Factory
+
+ describe "attachments" do
+ test "works with honkerific attachments" do
+ attachment = %{
+ "mediaType" => "",
+ "name" => "",
+ "summary" => "298p3RG7j27tfsZ9RQ.jpg",
+ "type" => "Document",
+ "url" => "https://honk.tedunangst.com/d/298p3RG7j27tfsZ9RQ.jpg"
+ }
+
+ assert {:ok, attachment} =
+ AttachmentValidator.cast_and_validate(attachment)
+ |> Ecto.Changeset.apply_action(:insert)
+
+ assert attachment.mediaType == "application/octet-stream"
+ end
+
+ test "it turns mastodon attachments into our attachments" do
+ attachment = %{
+ "url" =>
+ "http://mastodon.example.org/system/media_attachments/files/000/000/002/original/334ce029e7bfb920.jpg",
+ "type" => "Document",
+ "name" => nil,
+ "mediaType" => "image/jpeg"
+ }
+
+ {:ok, attachment} =
+ AttachmentValidator.cast_and_validate(attachment)
+ |> Ecto.Changeset.apply_action(:insert)
+
+ assert [
+ %{
+ href:
+ "http://mastodon.example.org/system/media_attachments/files/000/000/002/original/334ce029e7bfb920.jpg",
+ type: "Link",
+ mediaType: "image/jpeg"
+ }
+ ] = attachment.url
+
+ assert attachment.mediaType == "image/jpeg"
+ end
+
+ test "it handles our own uploads" do
+ user = insert(:user)
+
+ file = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ {:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id)
+
+ {:ok, attachment} =
+ attachment.data
+ |> AttachmentValidator.cast_and_validate()
+ |> Ecto.Changeset.apply_action(:insert)
+
+ assert attachment.mediaType == "image/jpeg"
+ end
+ end
+end
diff --git a/test/web/activity_pub/object_validators/block_validation_test.exs b/test/web/activity_pub/object_validators/block_validation_test.exs
new file mode 100644
index 000000000..c08d4b2e8
--- /dev/null
+++ b/test/web/activity_pub/object_validators/block_validation_test.exs
@@ -0,0 +1,39 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.BlockValidationTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Web.ActivityPub.Builder
+ alias Pleroma.Web.ActivityPub.ObjectValidator
+
+ import Pleroma.Factory
+
+ describe "blocks" do
+ setup do
+ user = insert(:user, local: false)
+ blocked = insert(:user)
+
+ {:ok, valid_block, []} = Builder.block(user, blocked)
+
+ %{user: user, valid_block: valid_block}
+ end
+
+ test "validates a basic object", %{
+ valid_block: valid_block
+ } do
+ assert {:ok, _block, []} = ObjectValidator.validate(valid_block, [])
+ end
+
+ test "returns an error if we don't know the blocked user", %{
+ valid_block: valid_block
+ } do
+ block =
+ valid_block
+ |> Map.put("object", "https://gensokyo.2hu/users/raymoo")
+
+ assert {:error, _cng} = ObjectValidator.validate(block, [])
+ end
+ end
+end
diff --git a/test/web/activity_pub/object_validators/chat_validation_test.exs b/test/web/activity_pub/object_validators/chat_validation_test.exs
new file mode 100644
index 000000000..ec1e497fa
--- /dev/null
+++ b/test/web/activity_pub/object_validators/chat_validation_test.exs
@@ -0,0 +1,200 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.ChatValidationTest do
+ use Pleroma.DataCase
+ alias Pleroma.Object
+ alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.ActivityPub.Builder
+ alias Pleroma.Web.ActivityPub.ObjectValidator
+ alias Pleroma.Web.CommonAPI
+
+ import Pleroma.Factory
+
+ describe "chat message create activities" do
+ test "it is invalid if the object already exists" do
+ user = insert(:user)
+ recipient = insert(:user)
+ {:ok, activity} = CommonAPI.post_chat_message(user, recipient, "hey")
+ object = Object.normalize(activity, false)
+
+ {:ok, create_data, _} = Builder.create(user, object.data, [recipient.ap_id])
+
+ {:error, cng} = ObjectValidator.validate(create_data, [])
+
+ assert {:object, {"The object to create already exists", []}} in cng.errors
+ end
+
+ test "it is invalid if the object data has a different `to` or `actor` field" do
+ user = insert(:user)
+ recipient = insert(:user)
+ {:ok, object_data, _} = Builder.chat_message(recipient, user.ap_id, "Hey")
+
+ {:ok, create_data, _} = Builder.create(user, object_data, [recipient.ap_id])
+
+ {:error, cng} = ObjectValidator.validate(create_data, [])
+
+ assert {:to, {"Recipients don't match with object recipients", []}} in cng.errors
+ assert {:actor, {"Actor doesn't match with object actor", []}} in cng.errors
+ end
+ end
+
+ describe "chat messages" do
+ setup do
+ clear_config([:instance, :remote_limit])
+ user = insert(:user)
+ recipient = insert(:user, local: false)
+
+ {:ok, valid_chat_message, _} = Builder.chat_message(user, recipient.ap_id, "hey :firefox:")
+
+ %{user: user, recipient: recipient, valid_chat_message: valid_chat_message}
+ end
+
+ test "let's through some basic html", %{user: user, recipient: recipient} do
+ {:ok, valid_chat_message, _} =
+ Builder.chat_message(
+ user,
+ recipient.ap_id,
+ "hey <a href='https://example.org'>example</a> <script>alert('uguu')</script>"
+ )
+
+ assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
+
+ assert object["content"] ==
+ "hey <a href=\"https://example.org\">example</a> alert(&#39;uguu&#39;)"
+ end
+
+ test "validates for a basic object we build", %{valid_chat_message: valid_chat_message} do
+ assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
+
+ assert Map.put(valid_chat_message, "attachment", nil) == object
+ end
+
+ test "validates for a basic object with an attachment", %{
+ valid_chat_message: valid_chat_message,
+ user: user
+ } do
+ file = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ {:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id)
+
+ valid_chat_message =
+ valid_chat_message
+ |> Map.put("attachment", attachment.data)
+
+ assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
+
+ assert object["attachment"]
+ end
+
+ test "validates for a basic object with an attachment in an array", %{
+ valid_chat_message: valid_chat_message,
+ user: user
+ } do
+ file = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ {:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id)
+
+ valid_chat_message =
+ valid_chat_message
+ |> Map.put("attachment", [attachment.data])
+
+ assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
+
+ assert object["attachment"]
+ end
+
+ test "validates for a basic object with an attachment but without content", %{
+ valid_chat_message: valid_chat_message,
+ user: user
+ } do
+ file = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ {:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id)
+
+ valid_chat_message =
+ valid_chat_message
+ |> Map.put("attachment", attachment.data)
+ |> Map.delete("content")
+
+ assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
+
+ assert object["attachment"]
+ end
+
+ test "does not validate if the message has no content", %{
+ valid_chat_message: valid_chat_message
+ } do
+ contentless =
+ valid_chat_message
+ |> Map.delete("content")
+
+ refute match?({:ok, _object, _meta}, ObjectValidator.validate(contentless, []))
+ end
+
+ test "does not validate if the message is longer than the remote_limit", %{
+ valid_chat_message: valid_chat_message
+ } do
+ Pleroma.Config.put([:instance, :remote_limit], 2)
+ refute match?({:ok, _object, _meta}, ObjectValidator.validate(valid_chat_message, []))
+ end
+
+ test "does not validate if the recipient is blocking the actor", %{
+ valid_chat_message: valid_chat_message,
+ user: user,
+ recipient: recipient
+ } do
+ Pleroma.User.block(recipient, user)
+ refute match?({:ok, _object, _meta}, ObjectValidator.validate(valid_chat_message, []))
+ end
+
+ test "does not validate if the actor or the recipient is not in our system", %{
+ valid_chat_message: valid_chat_message
+ } do
+ chat_message =
+ valid_chat_message
+ |> Map.put("actor", "https://raymoo.com/raymoo")
+
+ {:error, _} = ObjectValidator.validate(chat_message, [])
+
+ chat_message =
+ valid_chat_message
+ |> Map.put("to", ["https://raymoo.com/raymoo"])
+
+ {:error, _} = ObjectValidator.validate(chat_message, [])
+ end
+
+ test "does not validate for a message with multiple recipients", %{
+ valid_chat_message: valid_chat_message,
+ user: user,
+ recipient: recipient
+ } do
+ chat_message =
+ valid_chat_message
+ |> Map.put("to", [user.ap_id, recipient.ap_id])
+
+ assert {:error, _} = ObjectValidator.validate(chat_message, [])
+ end
+
+ test "does not validate if it doesn't concern local users" do
+ user = insert(:user, local: false)
+ recipient = insert(:user, local: false)
+
+ {:ok, valid_chat_message, _} = Builder.chat_message(user, recipient.ap_id, "hey")
+ assert {:error, _} = ObjectValidator.validate(valid_chat_message, [])
+ end
+ end
+end
diff --git a/test/web/activity_pub/object_validators/delete_validation_test.exs b/test/web/activity_pub/object_validators/delete_validation_test.exs
new file mode 100644
index 000000000..42cd18298
--- /dev/null
+++ b/test/web/activity_pub/object_validators/delete_validation_test.exs
@@ -0,0 +1,106 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidationTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Object
+ alias Pleroma.Web.ActivityPub.Builder
+ alias Pleroma.Web.ActivityPub.ObjectValidator
+ alias Pleroma.Web.CommonAPI
+
+ import Pleroma.Factory
+
+ describe "deletes" do
+ setup do
+ user = insert(:user)
+ {:ok, post_activity} = CommonAPI.post(user, %{status: "cancel me daddy"})
+
+ {:ok, valid_post_delete, _} = Builder.delete(user, post_activity.data["object"])
+ {:ok, valid_user_delete, _} = Builder.delete(user, user.ap_id)
+
+ %{user: user, valid_post_delete: valid_post_delete, valid_user_delete: valid_user_delete}
+ end
+
+ test "it is valid for a post deletion", %{valid_post_delete: valid_post_delete} do
+ {:ok, valid_post_delete, _} = ObjectValidator.validate(valid_post_delete, [])
+
+ assert valid_post_delete["deleted_activity_id"]
+ end
+
+ test "it is invalid if the object isn't in a list of certain types", %{
+ valid_post_delete: valid_post_delete
+ } do
+ object = Object.get_by_ap_id(valid_post_delete["object"])
+
+ data =
+ object.data
+ |> Map.put("type", "Like")
+
+ {:ok, _object} =
+ object
+ |> Ecto.Changeset.change(%{data: data})
+ |> Object.update_and_set_cache()
+
+ {:error, cng} = ObjectValidator.validate(valid_post_delete, [])
+ assert {:object, {"object not in allowed types", []}} in cng.errors
+ end
+
+ test "it is valid for a user deletion", %{valid_user_delete: valid_user_delete} do
+ assert match?({:ok, _, _}, ObjectValidator.validate(valid_user_delete, []))
+ end
+
+ test "it's invalid if the id is missing", %{valid_post_delete: valid_post_delete} do
+ no_id =
+ valid_post_delete
+ |> Map.delete("id")
+
+ {:error, cng} = ObjectValidator.validate(no_id, [])
+
+ assert {:id, {"can't be blank", [validation: :required]}} in cng.errors
+ end
+
+ test "it's invalid if the object doesn't exist", %{valid_post_delete: valid_post_delete} do
+ missing_object =
+ valid_post_delete
+ |> Map.put("object", "http://does.not/exist")
+
+ {:error, cng} = ObjectValidator.validate(missing_object, [])
+
+ assert {:object, {"can't find object", []}} in cng.errors
+ end
+
+ test "it's invalid if the actor of the object and the actor of delete are from different domains",
+ %{valid_post_delete: valid_post_delete} do
+ valid_user = insert(:user)
+
+ valid_other_actor =
+ valid_post_delete
+ |> Map.put("actor", valid_user.ap_id)
+
+ assert match?({:ok, _, _}, ObjectValidator.validate(valid_other_actor, []))
+
+ invalid_other_actor =
+ valid_post_delete
+ |> Map.put("actor", "https://gensokyo.2hu/users/raymoo")
+
+ {:error, cng} = ObjectValidator.validate(invalid_other_actor, [])
+
+ assert {:actor, {"is not allowed to delete object", []}} in cng.errors
+ end
+
+ test "it's valid if the actor of the object is a local superuser",
+ %{valid_post_delete: valid_post_delete} do
+ user =
+ insert(:user, local: true, is_moderator: true, ap_id: "https://gensokyo.2hu/users/raymoo")
+
+ valid_other_actor =
+ valid_post_delete
+ |> Map.put("actor", user.ap_id)
+
+ {:ok, _, meta} = ObjectValidator.validate(valid_other_actor, [])
+ assert meta[:do_not_federate]
+ end
+ end
+end
diff --git a/test/web/activity_pub/object_validators/emoji_react_validation_test.exs b/test/web/activity_pub/object_validators/emoji_react_validation_test.exs
new file mode 100644
index 000000000..582e6d785
--- /dev/null
+++ b/test/web/activity_pub/object_validators/emoji_react_validation_test.exs
@@ -0,0 +1,53 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactHandlingTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Web.ActivityPub.Builder
+ alias Pleroma.Web.ActivityPub.ObjectValidator
+ alias Pleroma.Web.CommonAPI
+
+ import Pleroma.Factory
+
+ describe "EmojiReacts" do
+ setup do
+ user = insert(:user)
+ {:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"})
+
+ object = Pleroma.Object.get_by_ap_id(post_activity.data["object"])
+
+ {:ok, valid_emoji_react, []} = Builder.emoji_react(user, object, "👌")
+
+ %{user: user, post_activity: post_activity, valid_emoji_react: valid_emoji_react}
+ end
+
+ test "it validates a valid EmojiReact", %{valid_emoji_react: valid_emoji_react} do
+ assert {:ok, _, _} = ObjectValidator.validate(valid_emoji_react, [])
+ end
+
+ test "it is not valid without a 'content' field", %{valid_emoji_react: valid_emoji_react} do
+ without_content =
+ valid_emoji_react
+ |> Map.delete("content")
+
+ {:error, cng} = ObjectValidator.validate(without_content, [])
+
+ refute cng.valid?
+ assert {:content, {"can't be blank", [validation: :required]}} in cng.errors
+ end
+
+ test "it is not valid with a non-emoji content field", %{valid_emoji_react: valid_emoji_react} do
+ without_emoji_content =
+ valid_emoji_react
+ |> Map.put("content", "x")
+
+ {:error, cng} = ObjectValidator.validate(without_emoji_content, [])
+
+ refute cng.valid?
+
+ assert {:content, {"must be a single character emoji", []}} in cng.errors
+ end
+ end
+end
diff --git a/test/web/activity_pub/object_validators/follow_validation_test.exs b/test/web/activity_pub/object_validators/follow_validation_test.exs
new file mode 100644
index 000000000..6e1378be2
--- /dev/null
+++ b/test/web/activity_pub/object_validators/follow_validation_test.exs
@@ -0,0 +1,26 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.FollowValidationTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Web.ActivityPub.Builder
+ alias Pleroma.Web.ActivityPub.ObjectValidator
+
+ import Pleroma.Factory
+
+ describe "Follows" do
+ setup do
+ follower = insert(:user)
+ followed = insert(:user)
+
+ {:ok, valid_follow, []} = Builder.follow(follower, followed)
+ %{follower: follower, followed: followed, valid_follow: valid_follow}
+ end
+
+ test "validates a basic follow object", %{valid_follow: valid_follow} do
+ assert {:ok, _follow, []} = ObjectValidator.validate(valid_follow, [])
+ end
+ end
+end
diff --git a/test/web/activity_pub/object_validators/like_validation_test.exs b/test/web/activity_pub/object_validators/like_validation_test.exs
new file mode 100644
index 000000000..2c033b7e2
--- /dev/null
+++ b/test/web/activity_pub/object_validators/like_validation_test.exs
@@ -0,0 +1,113 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidationTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Web.ActivityPub.ObjectValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
+ alias Pleroma.Web.ActivityPub.Utils
+ alias Pleroma.Web.CommonAPI
+
+ import Pleroma.Factory
+
+ describe "likes" do
+ setup do
+ user = insert(:user)
+ {:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"})
+
+ valid_like = %{
+ "to" => [user.ap_id],
+ "cc" => [],
+ "type" => "Like",
+ "id" => Utils.generate_activity_id(),
+ "object" => post_activity.data["object"],
+ "actor" => user.ap_id,
+ "context" => "a context"
+ }
+
+ %{valid_like: valid_like, user: user, post_activity: post_activity}
+ end
+
+ test "returns ok when called in the ObjectValidator", %{valid_like: valid_like} do
+ {:ok, object, _meta} = ObjectValidator.validate(valid_like, [])
+
+ assert "id" in Map.keys(object)
+ end
+
+ test "is valid for a valid object", %{valid_like: valid_like} do
+ assert LikeValidator.cast_and_validate(valid_like).valid?
+ end
+
+ test "sets the 'to' field to the object actor if no recipients are given", %{
+ valid_like: valid_like,
+ user: user
+ } do
+ without_recipients =
+ valid_like
+ |> Map.delete("to")
+
+ {:ok, object, _meta} = ObjectValidator.validate(without_recipients, [])
+
+ assert object["to"] == [user.ap_id]
+ end
+
+ test "sets the context field to the context of the object if no context is given", %{
+ valid_like: valid_like,
+ post_activity: post_activity
+ } do
+ without_context =
+ valid_like
+ |> Map.delete("context")
+
+ {:ok, object, _meta} = ObjectValidator.validate(without_context, [])
+
+ assert object["context"] == post_activity.data["context"]
+ end
+
+ test "it errors when the actor is missing or not known", %{valid_like: valid_like} do
+ without_actor = Map.delete(valid_like, "actor")
+
+ refute LikeValidator.cast_and_validate(without_actor).valid?
+
+ with_invalid_actor = Map.put(valid_like, "actor", "invalidactor")
+
+ refute LikeValidator.cast_and_validate(with_invalid_actor).valid?
+ end
+
+ test "it errors when the object is missing or not known", %{valid_like: valid_like} do
+ without_object = Map.delete(valid_like, "object")
+
+ refute LikeValidator.cast_and_validate(without_object).valid?
+
+ with_invalid_object = Map.put(valid_like, "object", "invalidobject")
+
+ refute LikeValidator.cast_and_validate(with_invalid_object).valid?
+ end
+
+ test "it errors when the actor has already like the object", %{
+ valid_like: valid_like,
+ user: user,
+ post_activity: post_activity
+ } do
+ _like = CommonAPI.favorite(user, post_activity.id)
+
+ refute LikeValidator.cast_and_validate(valid_like).valid?
+ end
+
+ test "it works when actor or object are wrapped in maps", %{valid_like: valid_like} do
+ wrapped_like =
+ valid_like
+ |> Map.put("actor", %{"id" => valid_like["actor"]})
+ |> Map.put("object", %{"id" => valid_like["object"]})
+
+ validated = LikeValidator.cast_and_validate(wrapped_like)
+
+ assert validated.valid?
+
+ assert {:actor, valid_like["actor"]} in validated.changes
+ assert {:object, valid_like["object"]} in validated.changes
+ end
+ end
+end
diff --git a/test/web/activity_pub/object_validators/undo_validation_test.exs b/test/web/activity_pub/object_validators/undo_validation_test.exs
new file mode 100644
index 000000000..75bbcc4b6
--- /dev/null
+++ b/test/web/activity_pub/object_validators/undo_validation_test.exs
@@ -0,0 +1,53 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.UndoHandlingTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Web.ActivityPub.Builder
+ alias Pleroma.Web.ActivityPub.ObjectValidator
+ alias Pleroma.Web.CommonAPI
+
+ import Pleroma.Factory
+
+ describe "Undos" do
+ setup do
+ user = insert(:user)
+ {:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"})
+ {:ok, like} = CommonAPI.favorite(user, post_activity.id)
+ {:ok, valid_like_undo, []} = Builder.undo(user, like)
+
+ %{user: user, like: like, valid_like_undo: valid_like_undo}
+ end
+
+ test "it validates a basic like undo", %{valid_like_undo: valid_like_undo} do
+ assert {:ok, _, _} = ObjectValidator.validate(valid_like_undo, [])
+ end
+
+ test "it does not validate if the actor of the undo is not the actor of the object", %{
+ valid_like_undo: valid_like_undo
+ } do
+ other_user = insert(:user, ap_id: "https://gensokyo.2hu/users/raymoo")
+
+ bad_actor =
+ valid_like_undo
+ |> Map.put("actor", other_user.ap_id)
+
+ {:error, cng} = ObjectValidator.validate(bad_actor, [])
+
+ assert {:actor, {"not the same as object actor", []}} in cng.errors
+ end
+
+ test "it does not validate if the object is missing", %{valid_like_undo: valid_like_undo} do
+ missing_object =
+ valid_like_undo
+ |> Map.put("object", "https://gensokyo.2hu/objects/1")
+
+ {:error, cng} = ObjectValidator.validate(missing_object, [])
+
+ assert {:object, {"can't find object", []}} in cng.errors
+ assert length(cng.errors) == 1
+ end
+ end
+end
diff --git a/test/web/activity_pub/object_validators/update_validation_test.exs b/test/web/activity_pub/object_validators/update_validation_test.exs
new file mode 100644
index 000000000..5e80cf731
--- /dev/null
+++ b/test/web/activity_pub/object_validators/update_validation_test.exs
@@ -0,0 +1,44 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.UpdateHandlingTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Web.ActivityPub.Builder
+ alias Pleroma.Web.ActivityPub.ObjectValidator
+
+ import Pleroma.Factory
+
+ describe "updates" do
+ setup do
+ user = insert(:user)
+
+ object = %{
+ "id" => user.ap_id,
+ "name" => "A new name",
+ "summary" => "A new bio"
+ }
+
+ {:ok, valid_update, []} = Builder.update(user, object)
+
+ %{user: user, valid_update: valid_update}
+ end
+
+ test "validates a basic object", %{valid_update: valid_update} do
+ assert {:ok, _update, []} = ObjectValidator.validate(valid_update, [])
+ end
+
+ test "returns an error if the object can't be updated by the actor", %{
+ valid_update: valid_update
+ } do
+ other_user = insert(:user)
+
+ update =
+ valid_update
+ |> Map.put("actor", other_user.ap_id)
+
+ assert {:error, _cng} = ObjectValidator.validate(update, [])
+ end
+ end
+end
diff --git a/test/web/activity_pub/relay_test.exs b/test/web/activity_pub/relay_test.exs
index b3b573c9b..9d657ac4f 100644
--- a/test/web/activity_pub/relay_test.exs
+++ b/test/web/activity_pub/relay_test.exs
@@ -7,8 +7,8 @@ defmodule Pleroma.Web.ActivityPub.RelayTest do
alias Pleroma.Activity
alias Pleroma.User
- alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Relay
+ alias Pleroma.Web.CommonAPI
import ExUnit.CaptureLog
import Pleroma.Factory
@@ -53,8 +53,7 @@ defmodule Pleroma.Web.ActivityPub.RelayTest do
test "returns activity" do
user = insert(:user)
service_actor = Relay.get_actor()
- ActivityPub.follow(service_actor, user)
- Pleroma.User.follow(service_actor, user)
+ CommonAPI.follow(service_actor, user)
assert "#{user.ap_id}/followers" in User.following(service_actor)
assert {:ok, %Activity{} = activity} = Relay.unfollow(user.ap_id)
assert activity.actor == "#{Pleroma.Web.Endpoint.url()}/relay"
@@ -74,6 +73,7 @@ defmodule Pleroma.Web.ActivityPub.RelayTest do
assert Relay.publish(activity) == {:error, "Not implemented"}
end
+ @tag capture_log: true
test "returns error when activity not public" do
activity = insert(:direct_note_activity)
assert Relay.publish(activity) == {:error, false}
diff --git a/test/web/activity_pub/side_effects_test.exs b/test/web/activity_pub/side_effects_test.exs
index 12c9ef1da..2649b060a 100644
--- a/test/web/activity_pub/side_effects_test.exs
+++ b/test/web/activity_pub/side_effects_test.exs
@@ -64,6 +64,47 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do
end
end
+ describe "blocking users" do
+ setup do
+ user = insert(:user)
+ blocked = insert(:user)
+ User.follow(blocked, user)
+ User.follow(user, blocked)
+
+ {:ok, block_data, []} = Builder.block(user, blocked)
+ {:ok, block, _meta} = ActivityPub.persist(block_data, local: true)
+
+ %{user: user, blocked: blocked, block: block}
+ end
+
+ test "it unfollows and blocks", %{user: user, blocked: blocked, block: block} do
+ assert User.following?(user, blocked)
+ assert User.following?(blocked, user)
+
+ {:ok, _, _} = SideEffects.handle(block)
+
+ refute User.following?(user, blocked)
+ refute User.following?(blocked, user)
+ assert User.blocks?(user, blocked)
+ end
+
+ test "it blocks but does not unfollow if the relevant setting is set", %{
+ user: user,
+ blocked: blocked,
+ block: block
+ } do
+ clear_config([:activitypub, :unfollow_blocked], false)
+ assert User.following?(user, blocked)
+ assert User.following?(blocked, user)
+
+ {:ok, _, _} = SideEffects.handle(block)
+
+ refute User.following?(user, blocked)
+ assert User.following?(blocked, user)
+ assert User.blocks?(user, blocked)
+ end
+ end
+
describe "update users" do
setup do
user = insert(:user)
@@ -242,8 +283,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do
{:ok, like} = CommonAPI.favorite(user, post.id)
{:ok, reaction} = CommonAPI.react_with_emoji(post.id, user, "👍")
{:ok, announce} = CommonAPI.repeat(post.id, user)
- {:ok, block} = ActivityPub.block(user, poster)
- User.block(user, poster)
+ {:ok, block} = CommonAPI.block(user, poster)
{:ok, undo_data, _meta} = Builder.undo(user, like)
{:ok, like_undo, _meta} = ActivityPub.persist(undo_data, local: true)
@@ -549,10 +589,29 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do
end
test "it streams out the announce", %{announce: announce} do
- with_mock Pleroma.Web.ActivityPub.ActivityPub, [:passthrough], stream_out: fn _ -> nil end do
+ with_mocks([
+ {
+ Pleroma.Web.Streamer,
+ [],
+ [
+ stream: fn _, _ -> nil end
+ ]
+ },
+ {
+ Pleroma.Web.Push,
+ [],
+ [
+ send: fn _ -> nil end
+ ]
+ }
+ ]) do
{:ok, announce, _} = SideEffects.handle(announce)
- assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out(announce))
+ assert called(
+ Pleroma.Web.Streamer.stream(["user", "list", "public", "public:local"], announce)
+ )
+
+ assert called(Pleroma.Web.Push.send(:_))
end
end
end
diff --git a/test/web/activity_pub/transmogrifier/block_handling_test.exs b/test/web/activity_pub/transmogrifier/block_handling_test.exs
new file mode 100644
index 000000000..71f1a0ed5
--- /dev/null
+++ b/test/web/activity_pub/transmogrifier/block_handling_test.exs
@@ -0,0 +1,63 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.Transmogrifier.BlockHandlingTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Activity
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.Transmogrifier
+
+ import Pleroma.Factory
+
+ test "it works for incoming blocks" do
+ user = insert(:user)
+
+ data =
+ File.read!("test/fixtures/mastodon-block-activity.json")
+ |> Poison.decode!()
+ |> Map.put("object", user.ap_id)
+
+ blocker = insert(:user, ap_id: data["actor"])
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+
+ assert data["type"] == "Block"
+ assert data["object"] == user.ap_id
+ assert data["actor"] == "http://mastodon.example.org/users/admin"
+
+ assert User.blocks?(blocker, user)
+ end
+
+ test "incoming blocks successfully tear down any follow relationship" do
+ blocker = insert(:user)
+ blocked = insert(:user)
+
+ data =
+ File.read!("test/fixtures/mastodon-block-activity.json")
+ |> Poison.decode!()
+ |> Map.put("object", blocked.ap_id)
+ |> Map.put("actor", blocker.ap_id)
+
+ {:ok, blocker} = User.follow(blocker, blocked)
+ {:ok, blocked} = User.follow(blocked, blocker)
+
+ assert User.following?(blocker, blocked)
+ assert User.following?(blocked, blocker)
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+
+ assert data["type"] == "Block"
+ assert data["object"] == blocked.ap_id
+ assert data["actor"] == blocker.ap_id
+
+ blocker = User.get_cached_by_ap_id(data["actor"])
+ blocked = User.get_cached_by_ap_id(data["object"])
+
+ assert User.blocks?(blocker, blocked)
+
+ refute User.following?(blocker, blocked)
+ refute User.following?(blocked, blocker)
+ end
+end
diff --git a/test/web/activity_pub/transmogrifier/follow_handling_test.exs b/test/web/activity_pub/transmogrifier/follow_handling_test.exs
index 06c39eed6..17e764ca1 100644
--- a/test/web/activity_pub/transmogrifier/follow_handling_test.exs
+++ b/test/web/activity_pub/transmogrifier/follow_handling_test.exs
@@ -160,7 +160,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.FollowHandlingTest do
|> Poison.decode!()
|> Map.put("object", user.ap_id)
- with_mock Pleroma.User, [:passthrough], follow: fn _, _ -> {:error, :testing} end do
+ with_mock Pleroma.User, [:passthrough], follow: fn _, _, _ -> {:error, :testing} end do
{:ok, %Activity{data: %{"id" => id}}} = Transmogrifier.handle_incoming(data)
%Activity{} = activity = Activity.get_by_ap_id(id)
diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs
index 100821056..f7b7d1a9f 100644
--- a/test/web/activity_pub/transmogrifier_test.exs
+++ b/test/web/activity_pub/transmogrifier_test.exs
@@ -11,7 +11,6 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
alias Pleroma.Object.Fetcher
alias Pleroma.Tests.ObanHelpers
alias Pleroma.User
- alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.AdminAPI.AccountView
alias Pleroma.Web.CommonAPI
@@ -445,56 +444,6 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
assert [^pending_follower] = User.get_follow_requests(user)
end
- test "it works for incoming blocks" do
- user = insert(:user)
-
- data =
- File.read!("test/fixtures/mastodon-block-activity.json")
- |> Poison.decode!()
- |> Map.put("object", user.ap_id)
-
- {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
-
- assert data["type"] == "Block"
- assert data["object"] == user.ap_id
- assert data["actor"] == "http://mastodon.example.org/users/admin"
-
- blocker = User.get_cached_by_ap_id(data["actor"])
-
- assert User.blocks?(blocker, user)
- end
-
- test "incoming blocks successfully tear down any follow relationship" do
- blocker = insert(:user)
- blocked = insert(:user)
-
- data =
- File.read!("test/fixtures/mastodon-block-activity.json")
- |> Poison.decode!()
- |> Map.put("object", blocked.ap_id)
- |> Map.put("actor", blocker.ap_id)
-
- {:ok, blocker} = User.follow(blocker, blocked)
- {:ok, blocked} = User.follow(blocked, blocker)
-
- assert User.following?(blocker, blocked)
- assert User.following?(blocked, blocker)
-
- {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
-
- assert data["type"] == "Block"
- assert data["object"] == blocked.ap_id
- assert data["actor"] == blocker.ap_id
-
- blocker = User.get_cached_by_ap_id(data["actor"])
- blocked = User.get_cached_by_ap_id(data["object"])
-
- assert User.blocks?(blocker, blocked)
-
- refute User.following?(blocker, blocked)
- refute User.following?(blocked, blocker)
- end
-
test "it works for incoming accepts which were pre-accepted" do
follower = insert(:user)
followed = insert(:user)
@@ -502,7 +451,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
{:ok, follower} = User.follow(follower, followed)
assert User.following?(follower, followed) == true
- {:ok, follow_activity} = ActivityPub.follow(follower, followed)
+ {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
accept_data =
File.read!("test/fixtures/mastodon-accept-activity.json")
@@ -532,7 +481,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
follower = insert(:user)
followed = insert(:user, locked: true)
- {:ok, follow_activity} = ActivityPub.follow(follower, followed)
+ {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
accept_data =
File.read!("test/fixtures/mastodon-accept-activity.json")
@@ -554,7 +503,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
follower = insert(:user)
followed = insert(:user, locked: true)
- {:ok, follow_activity} = ActivityPub.follow(follower, followed)
+ {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
accept_data =
File.read!("test/fixtures/mastodon-accept-activity.json")
@@ -619,7 +568,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
followed = insert(:user, locked: true)
{:ok, follower} = User.follow(follower, followed)
- {:ok, _follow_activity} = ActivityPub.follow(follower, followed)
+ {:ok, _, _, _follow_activity} = CommonAPI.follow(follower, followed)
assert User.following?(follower, followed) == true
@@ -645,7 +594,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
followed = insert(:user, locked: true)
{:ok, follower} = User.follow(follower, followed)
- {:ok, follow_activity} = ActivityPub.follow(follower, followed)
+ {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
assert User.following?(follower, followed) == true
@@ -709,22 +658,44 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
"https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
)
- attachment = %{
- "type" => "Link",
- "mediaType" => "video/mp4",
- "url" => [
- %{
- "href" =>
- "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
- "mediaType" => "video/mp4"
- }
- ]
- }
-
assert object.data["url"] ==
"https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
- assert object.data["attachment"] == [attachment]
+ assert object.data["attachment"] == [
+ %{
+ "type" => "Link",
+ "mediaType" => "video/mp4",
+ "url" => [
+ %{
+ "href" =>
+ "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
+ "mediaType" => "video/mp4"
+ }
+ ]
+ }
+ ]
+
+ {:ok, object} =
+ Fetcher.fetch_object_from_id(
+ "https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206"
+ )
+
+ assert object.data["attachment"] == [
+ %{
+ "type" => "Link",
+ "mediaType" => "video/mp4",
+ "url" => [
+ %{
+ "href" =>
+ "https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4",
+ "mediaType" => "video/mp4"
+ }
+ ]
+ }
+ ]
+
+ assert object.data["url"] ==
+ "https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206"
end
test "it accepts Flag activities" do
diff --git a/test/web/activity_pub/utils_test.exs b/test/web/activity_pub/utils_test.exs
index 15f03f193..361dc5a41 100644
--- a/test/web/activity_pub/utils_test.exs
+++ b/test/web/activity_pub/utils_test.exs
@@ -8,7 +8,6 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
- alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.AdminAPI.AccountView
alias Pleroma.Web.CommonAPI
@@ -27,16 +26,6 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do
end
end
- describe "fetch the latest Block" do
- test "fetches the latest Block activity" do
- blocker = insert(:user)
- blocked = insert(:user)
- {:ok, activity} = ActivityPub.block(blocker, blocked)
-
- assert activity == Utils.fetch_latest_block(blocker, blocked)
- end
- end
-
describe "determine_explicit_mentions()" do
test "works with an object that has mentions" do
object = %{
@@ -207,8 +196,8 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do
user = insert(:user, locked: true)
follower = insert(:user)
- {:ok, follow_activity} = ActivityPub.follow(follower, user)
- {:ok, follow_activity_two} = ActivityPub.follow(follower, user)
+ {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
+ {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
data =
follow_activity_two.data
@@ -231,8 +220,8 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do
user = insert(:user, locked: true)
follower = insert(:user)
- {:ok, follow_activity} = ActivityPub.follow(follower, user)
- {:ok, follow_activity_two} = ActivityPub.follow(follower, user)
+ {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
+ {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
data =
follow_activity_two.data
@@ -344,9 +333,9 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do
user1 = insert(:user)
user2 = insert(:user)
- assert {:ok, %Activity{} = _} = ActivityPub.block(user1, user2)
- assert {:ok, %Activity{} = _} = ActivityPub.block(user1, user2)
- assert {:ok, %Activity{} = activity} = ActivityPub.block(user1, user2)
+ assert {:ok, %Activity{} = _} = CommonAPI.block(user1, user2)
+ assert {:ok, %Activity{} = _} = CommonAPI.block(user1, user2)
+ assert {:ok, %Activity{} = activity} = CommonAPI.block(user1, user2)
assert Utils.fetch_latest_block(user1, user2) == activity
end
diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs
index 6bd26050e..7e11fede3 100644
--- a/test/web/common_api/common_api_test.exs
+++ b/test/web/common_api/common_api_test.exs
@@ -25,6 +25,52 @@ defmodule Pleroma.Web.CommonAPITest do
setup do: clear_config([:instance, :limit])
setup do: clear_config([:instance, :max_pinned_statuses])
+ describe "blocking" do
+ setup do
+ blocker = insert(:user)
+ blocked = insert(:user)
+ User.follow(blocker, blocked)
+ User.follow(blocked, blocker)
+ %{blocker: blocker, blocked: blocked}
+ end
+
+ test "it blocks and federates", %{blocker: blocker, blocked: blocked} do
+ clear_config([:instance, :federating], true)
+
+ with_mock Pleroma.Web.Federator,
+ publish: fn _ -> nil end do
+ assert {:ok, block} = CommonAPI.block(blocker, blocked)
+
+ assert block.local
+ assert User.blocks?(blocker, blocked)
+ refute User.following?(blocker, blocked)
+ refute User.following?(blocked, blocker)
+
+ assert called(Pleroma.Web.Federator.publish(block))
+ end
+ end
+
+ test "it blocks and does not federate if outgoing blocks are disabled", %{
+ blocker: blocker,
+ blocked: blocked
+ } do
+ clear_config([:instance, :federating], true)
+ clear_config([:activitypub, :outgoing_blocks], false)
+
+ with_mock Pleroma.Web.Federator,
+ publish: fn _ -> nil end do
+ assert {:ok, block} = CommonAPI.block(blocker, blocked)
+
+ assert block.local
+ assert User.blocks?(blocker, blocked)
+ refute User.following?(blocker, blocked)
+ refute User.following?(blocked, blocker)
+
+ refute called(Pleroma.Web.Federator.publish(block))
+ end
+ end
+ end
+
describe "posting chat messages" do
setup do: clear_config([:instance, :chat_limit])
@@ -445,6 +491,7 @@ defmodule Pleroma.Web.CommonAPITest do
object = Object.normalize(activity)
assert object.data["content"] == "<p><b>2hu</b></p>alert(&#39;xss&#39;)"
+ assert object.data["source"] == post
end
test "it filters out obviously bad tags when accepting a post as Markdown" do
@@ -461,6 +508,7 @@ defmodule Pleroma.Web.CommonAPITest do
object = Object.normalize(activity)
assert object.data["content"] == "<p><b>2hu</b></p>alert(&#39;xss&#39;)"
+ assert object.data["source"] == post
end
test "it does not allow replies to direct messages that are not direct messages themselves" do
@@ -886,6 +934,15 @@ defmodule Pleroma.Web.CommonAPITest do
end
end
+ describe "follow/2" do
+ test "directly follows a non-locked local user" do
+ [follower, followed] = insert_pair(:user)
+ {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
+
+ assert User.following?(follower, followed)
+ end
+ end
+
describe "unfollow/2" do
test "also unsubscribes a user" do
[follower, followed] = insert_pair(:user)
@@ -950,9 +1007,9 @@ defmodule Pleroma.Web.CommonAPITest do
follower = insert(:user)
follower_two = insert(:user)
- {:ok, follow_activity} = ActivityPub.follow(follower, user)
- {:ok, follow_activity_two} = ActivityPub.follow(follower, user)
- {:ok, follow_activity_three} = ActivityPub.follow(follower_two, user)
+ {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
+ {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
+ {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
assert follow_activity.data["state"] == "pending"
assert follow_activity_two.data["state"] == "pending"
@@ -970,9 +1027,9 @@ defmodule Pleroma.Web.CommonAPITest do
follower = insert(:user)
follower_two = insert(:user)
- {:ok, follow_activity} = ActivityPub.follow(follower, user)
- {:ok, follow_activity_two} = ActivityPub.follow(follower, user)
- {:ok, follow_activity_three} = ActivityPub.follow(follower_two, user)
+ {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
+ {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
+ {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
assert follow_activity.data["state"] == "pending"
assert follow_activity_two.data["state"] == "pending"
diff --git a/test/web/masto_fe_controller_test.exs b/test/web/masto_fe_controller_test.exs
index 1d107d56c..f3b54b5f2 100644
--- a/test/web/masto_fe_controller_test.exs
+++ b/test/web/masto_fe_controller_test.exs
@@ -24,7 +24,7 @@ defmodule Pleroma.Web.MastodonAPI.MastoFEController do
assert _result = json_response(conn, 200)
user = User.get_cached_by_ap_id(user.ap_id)
- assert user.settings == %{"programming" => "socks"}
+ assert user.mastofe_settings == %{"programming" => "socks"}
end
describe "index/2 redirections" do
diff --git a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
index f67d294ba..b55bb76a7 100644
--- a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
@@ -216,10 +216,21 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
filename: "an_image.jpg"
}
- conn = patch(conn, "/api/v1/accounts/update_credentials", %{"avatar" => new_avatar})
+ assert user.avatar == %{}
- assert user_response = json_response_and_validate_schema(conn, 200)
+ res = patch(conn, "/api/v1/accounts/update_credentials", %{"avatar" => new_avatar})
+
+ assert user_response = json_response_and_validate_schema(res, 200)
assert user_response["avatar"] != User.avatar_url(user)
+
+ user = User.get_by_id(user.id)
+ refute user.avatar == %{}
+
+ # Also resets it
+ _res = patch(conn, "/api/v1/accounts/update_credentials", %{"avatar" => ""})
+
+ user = User.get_by_id(user.id)
+ assert user.avatar == nil
end
test "updates the user's banner", %{user: user, conn: conn} do
@@ -229,26 +240,39 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
filename: "an_image.jpg"
}
- conn = patch(conn, "/api/v1/accounts/update_credentials", %{"header" => new_header})
+ res = patch(conn, "/api/v1/accounts/update_credentials", %{"header" => new_header})
- assert user_response = json_response_and_validate_schema(conn, 200)
+ assert user_response = json_response_and_validate_schema(res, 200)
assert user_response["header"] != User.banner_url(user)
+
+ # Also resets it
+ _res = patch(conn, "/api/v1/accounts/update_credentials", %{"header" => ""})
+
+ user = User.get_by_id(user.id)
+ assert user.banner == nil
end
- test "updates the user's background", %{conn: conn} do
+ test "updates the user's background", %{conn: conn, user: user} do
new_header = %Plug.Upload{
content_type: "image/jpg",
path: Path.absname("test/fixtures/image.jpg"),
filename: "an_image.jpg"
}
- conn =
+ res =
patch(conn, "/api/v1/accounts/update_credentials", %{
"pleroma_background_image" => new_header
})
- assert user_response = json_response_and_validate_schema(conn, 200)
+ assert user_response = json_response_and_validate_schema(res, 200)
assert user_response["pleroma"]["background_image"]
+ #
+ # Also resets it
+ _res =
+ patch(conn, "/api/v1/accounts/update_credentials", %{"pleroma_background_image" => ""})
+
+ user = User.get_by_id(user.id)
+ assert user.background == nil
end
test "requires 'write:accounts' permission" do
diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs
index fcc1e792b..9c7b5e9b2 100644
--- a/test/web/mastodon_api/controllers/account_controller_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller_test.exs
@@ -708,7 +708,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
followed = insert(:user)
other_user = insert(:user)
- ret_conn = post(conn, "/api/v1/accounts/#{followed.id}/follow?reblogs=false")
+ ret_conn =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/accounts/#{followed.id}/follow", %{reblogs: false})
assert %{"showing_reblogs" => false} = json_response_and_validate_schema(ret_conn, 200)
@@ -722,7 +725,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
assert %{"showing_reblogs" => true} =
conn
- |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=true")
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/accounts/#{followed.id}/follow", %{reblogs: true})
|> json_response_and_validate_schema(200)
assert [%{"id" => ^reblog_id}] =
@@ -731,6 +735,35 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
|> json_response(200)
end
+ test "following with reblogs" do
+ %{conn: conn} = oauth_access(["follow", "read:statuses"])
+ followed = insert(:user)
+ other_user = insert(:user)
+
+ ret_conn = post(conn, "/api/v1/accounts/#{followed.id}/follow")
+
+ assert %{"showing_reblogs" => true} = json_response_and_validate_schema(ret_conn, 200)
+
+ {:ok, activity} = CommonAPI.post(other_user, %{status: "hey"})
+ {:ok, %{id: reblog_id}} = CommonAPI.repeat(activity.id, followed)
+
+ assert [%{"id" => ^reblog_id}] =
+ conn
+ |> get("/api/v1/timelines/home")
+ |> json_response(200)
+
+ assert %{"showing_reblogs" => false} =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/accounts/#{followed.id}/follow", %{reblogs: false})
+ |> json_response_and_validate_schema(200)
+
+ assert [] ==
+ conn
+ |> get("/api/v1/timelines/home")
+ |> json_response(200)
+ end
+
test "following / unfollowing errors", %{user: user, conn: conn} do
# self follow
conn_res = post(conn, "/api/v1/accounts/#{user.id}/follow")
@@ -780,7 +813,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
assert %{"id" => _id, "muting" => true, "muting_notifications" => true} =
conn
- |> put_req_header("content-type", "application/json")
|> post("/api/v1/accounts/#{other_user.id}/mute")
|> json_response_and_validate_schema(200)
diff --git a/test/web/mastodon_api/controllers/follow_request_controller_test.exs b/test/web/mastodon_api/controllers/follow_request_controller_test.exs
index 44e12d15a..6749e0e83 100644
--- a/test/web/mastodon_api/controllers/follow_request_controller_test.exs
+++ b/test/web/mastodon_api/controllers/follow_request_controller_test.exs
@@ -6,7 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do
use Pleroma.Web.ConnCase
alias Pleroma.User
- alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.CommonAPI
import Pleroma.Factory
@@ -20,7 +20,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do
test "/api/v1/follow_requests works", %{user: user, conn: conn} do
other_user = insert(:user)
- {:ok, _activity} = ActivityPub.follow(other_user, user)
+ {:ok, _, _, _activity} = CommonAPI.follow(other_user, user)
{:ok, other_user} = User.follow(other_user, user, :follow_pending)
assert User.following?(other_user, user) == false
@@ -34,7 +34,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do
test "/api/v1/follow_requests/:id/authorize works", %{user: user, conn: conn} do
other_user = insert(:user)
- {:ok, _activity} = ActivityPub.follow(other_user, user)
+ {:ok, _, _, _activity} = CommonAPI.follow(other_user, user)
{:ok, other_user} = User.follow(other_user, user, :follow_pending)
user = User.get_cached_by_id(user.id)
@@ -56,7 +56,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do
test "/api/v1/follow_requests/:id/reject works", %{user: user, conn: conn} do
other_user = insert(:user)
- {:ok, _activity} = ActivityPub.follow(other_user, user)
+ {:ok, _, _, _activity} = CommonAPI.follow(other_user, user)
user = User.get_cached_by_id(user.id)
diff --git a/test/web/mastodon_api/controllers/instance_controller_test.exs b/test/web/mastodon_api/controllers/instance_controller_test.exs
index 8bdfdddd1..cc880d82c 100644
--- a/test/web/mastodon_api/controllers/instance_controller_test.exs
+++ b/test/web/mastodon_api/controllers/instance_controller_test.exs
@@ -32,11 +32,15 @@ defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do
"avatar_upload_limit" => _,
"background_upload_limit" => _,
"banner_upload_limit" => _,
- "background_image" => _
+ "background_image" => _,
+ "chat_limit" => _,
+ "description_limit" => _
} = result
+ assert result["pleroma"]["metadata"]["account_activation_required"] != nil
assert result["pleroma"]["metadata"]["features"]
assert result["pleroma"]["metadata"]["federation"]
+ assert result["pleroma"]["metadata"]["fields_limits"]
assert result["pleroma"]["vapid_public_key"]
assert email == from_config_email
diff --git a/test/web/mastodon_api/controllers/search_controller_test.exs b/test/web/mastodon_api/controllers/search_controller_test.exs
index 826f37fbc..24d1959f8 100644
--- a/test/web/mastodon_api/controllers/search_controller_test.exs
+++ b/test/web/mastodon_api/controllers/search_controller_test.exs
@@ -79,6 +79,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
assert status["id"] == to_string(activity.id)
end
+ @tag capture_log: true
test "constructs hashtags from search query", %{conn: conn} do
results =
conn
@@ -318,11 +319,13 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
test "search fetches remote accounts", %{conn: conn} do
user = insert(:user)
+ query = URI.encode_query(%{q: " mike@osada.macgirvin.com ", resolve: true})
+
results =
conn
|> assign(:user, user)
|> assign(:token, insert(:oauth_token, user: user, scopes: ["read"]))
- |> get("/api/v1/search?q=mike@osada.macgirvin.com&resolve=true")
+ |> get("/api/v1/search?#{query}")
|> json_response_and_validate_schema(200)
[account] = results["accounts"]
diff --git a/test/web/mastodon_api/controllers/status_controller_test.exs b/test/web/mastodon_api/controllers/status_controller_test.exs
index a98e939e8..fd2de8d80 100644
--- a/test/web/mastodon_api/controllers/status_controller_test.exs
+++ b/test/web/mastodon_api/controllers/status_controller_test.exs
@@ -760,13 +760,18 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
test "when you created it" do
%{user: author, conn: conn} = oauth_access(["write:statuses"])
activity = insert(:note_activity, user: author)
+ object = Object.normalize(activity)
- conn =
+ content = object.data["content"]
+ source = object.data["source"]
+
+ result =
conn
|> assign(:user, author)
|> delete("/api/v1/statuses/#{activity.id}")
+ |> json_response_and_validate_schema(200)
- assert %{} = json_response_and_validate_schema(conn, 200)
+ assert match?(%{"content" => ^content, "text" => ^source}, result)
refute Activity.get_by_id(activity.id)
end
@@ -789,7 +794,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
conn = delete(conn, "/api/v1/statuses/#{activity.id}")
- assert %{"error" => _} = json_response_and_validate_schema(conn, 403)
+ assert %{"error" => "Record not found"} == json_response_and_validate_schema(conn, 404)
assert Activity.get_by_id(activity.id) == activity
end
diff --git a/test/web/mastodon_api/controllers/timeline_controller_test.exs b/test/web/mastodon_api/controllers/timeline_controller_test.exs
index f069390c1..50e0d783d 100644
--- a/test/web/mastodon_api/controllers/timeline_controller_test.exs
+++ b/test/web/mastodon_api/controllers/timeline_controller_test.exs
@@ -418,4 +418,78 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
assert [status_none] == json_response_and_validate_schema(all_test, :ok)
end
end
+
+ describe "hashtag timeline handling of :restrict_unauthenticated setting" do
+ setup do
+ user = insert(:user)
+ {:ok, activity1} = CommonAPI.post(user, %{status: "test #tag1"})
+ {:ok, _activity2} = CommonAPI.post(user, %{status: "test #tag1"})
+
+ activity1
+ |> Ecto.Changeset.change(%{local: false})
+ |> Pleroma.Repo.update()
+
+ base_uri = "/api/v1/timelines/tag/tag1"
+ error_response = %{"error" => "authorization required for timeline view"}
+
+ %{base_uri: base_uri, error_response: error_response}
+ end
+
+ defp ensure_authenticated_access(base_uri) do
+ %{conn: auth_conn} = oauth_access(["read:statuses"])
+
+ res_conn = get(auth_conn, "#{base_uri}?local=true")
+ assert length(json_response(res_conn, 200)) == 1
+
+ res_conn = get(auth_conn, "#{base_uri}?local=false")
+ assert length(json_response(res_conn, 200)) == 2
+ end
+
+ test "with `%{local: true, federated: true}`, returns 403 for unauthenticated users", %{
+ conn: conn,
+ base_uri: base_uri,
+ error_response: error_response
+ } do
+ clear_config([:restrict_unauthenticated, :timelines, :local], true)
+ clear_config([:restrict_unauthenticated, :timelines, :federated], true)
+
+ for local <- [true, false] do
+ res_conn = get(conn, "#{base_uri}?local=#{local}")
+
+ assert json_response(res_conn, :unauthorized) == error_response
+ end
+
+ ensure_authenticated_access(base_uri)
+ end
+
+ test "with `%{local: false, federated: true}`, forbids unauthenticated access to federated timeline",
+ %{conn: conn, base_uri: base_uri, error_response: error_response} do
+ clear_config([:restrict_unauthenticated, :timelines, :local], false)
+ clear_config([:restrict_unauthenticated, :timelines, :federated], true)
+
+ res_conn = get(conn, "#{base_uri}?local=true")
+ assert length(json_response(res_conn, 200)) == 1
+
+ res_conn = get(conn, "#{base_uri}?local=false")
+ assert json_response(res_conn, :unauthorized) == error_response
+
+ ensure_authenticated_access(base_uri)
+ end
+
+ test "with `%{local: true, federated: false}`, forbids unauthenticated access to public timeline" <>
+ "(but not to local public activities which are delivered as part of federated timeline)",
+ %{conn: conn, base_uri: base_uri, error_response: error_response} do
+ clear_config([:restrict_unauthenticated, :timelines, :local], true)
+ clear_config([:restrict_unauthenticated, :timelines, :federated], false)
+
+ res_conn = get(conn, "#{base_uri}?local=true")
+ assert json_response(res_conn, :unauthorized) == error_response
+
+ # Note: local activities get delivered as part of federated timeline
+ res_conn = get(conn, "#{base_uri}?local=false")
+ assert length(json_response(res_conn, 200)) == 2
+
+ ensure_authenticated_access(base_uri)
+ end
+ end
end
diff --git a/test/web/mastodon_api/mastodon_api_test.exs b/test/web/mastodon_api/mastodon_api_test.exs
index a7f9c5205..c08be37d4 100644
--- a/test/web/mastodon_api/mastodon_api_test.exs
+++ b/test/web/mastodon_api/mastodon_api_test.exs
@@ -18,7 +18,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPITest do
follower = insert(:user)
user = insert(:user, local: true, deactivated: true)
{:error, error} = MastodonAPI.follow(follower, user)
- assert error == "Could not follow user: #{user.nickname} is deactivated."
+ assert error == :rejected
end
test "following for user" do
diff --git a/test/web/mastodon_api/views/account_view_test.exs b/test/web/mastodon_api/views/account_view_test.exs
index 80b1f734c..f5bfc9c67 100644
--- a/test/web/mastodon_api/views/account_view_test.exs
+++ b/test/web/mastodon_api/views/account_view_test.exs
@@ -5,6 +5,7 @@
defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
use Pleroma.DataCase
+ alias Pleroma.Config
alias Pleroma.User
alias Pleroma.UserRelationship
alias Pleroma.Web.CommonAPI
@@ -18,6 +19,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
:ok
end
+ setup do: clear_config([:instances_favicons, :enabled])
+
test "Represent a user account" do
background_image = %{
"url" => [%{"href" => "https://example.com/images/asuka_hospital.png"}]
@@ -75,6 +78,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
pleroma: %{
ap_id: user.ap_id,
background_image: "https://example.com/images/asuka_hospital.png",
+ favicon:
+ "https://shitposter.club/plugins/Qvitter/img/gnusocial-favicons/favicon-16x16.png",
confirmation_pending: false,
tags: [],
is_admin: false,
@@ -92,6 +97,23 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
assert expected == AccountView.render("show.json", %{user: user})
end
+ test "Favicon is nil when :instances_favicons is disabled" do
+ user = insert(:user)
+
+ Config.put([:instances_favicons, :enabled], true)
+
+ assert %{
+ pleroma: %{
+ favicon:
+ "https://shitposter.club/plugins/Qvitter/img/gnusocial-favicons/favicon-16x16.png"
+ }
+ } = AccountView.render("show.json", %{user: user})
+
+ Config.put([:instances_favicons, :enabled], false)
+
+ assert %{pleroma: %{favicon: nil}} = AccountView.render("show.json", %{user: user})
+ end
+
test "Represent the user account for the account owner" do
user = insert(:user)
@@ -152,6 +174,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
pleroma: %{
ap_id: user.ap_id,
background_image: nil,
+ favicon:
+ "https://shitposter.club/plugins/Qvitter/img/gnusocial-favicons/favicon-16x16.png",
confirmation_pending: false,
tags: [],
is_admin: false,
@@ -372,6 +396,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
user = insert(:user, hide_followers: true, hide_follows: true)
other_user = insert(:user)
{:ok, user, other_user, _activity} = CommonAPI.follow(user, other_user)
+
+ assert User.following?(user, other_user)
+ assert Pleroma.FollowingRelationship.follower_count(other_user) == 1
{:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
assert %{
diff --git a/test/web/mastodon_api/views/status_view_test.exs b/test/web/mastodon_api/views/status_view_test.exs
index 5cbadf0fc..fa26b3129 100644
--- a/test/web/mastodon_api/views/status_view_test.exs
+++ b/test/web/mastodon_api/views/status_view_test.exs
@@ -183,6 +183,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
card: nil,
reblog: nil,
content: HTML.filter_tags(object_data["content"]),
+ text: nil,
created_at: created_at,
reblogs_count: 0,
replies_count: 0,
@@ -226,7 +227,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
expires_at: nil,
direct_conversation_id: nil,
thread_muted: false,
- emoji_reactions: []
+ emoji_reactions: [],
+ parent_visible: false
}
}
@@ -620,4 +622,20 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
assert status.visibility == "list"
end
+
+ test "has a field for parent visibility" do
+ user = insert(:user)
+ poster = insert(:user)
+
+ {:ok, invisible} = CommonAPI.post(poster, %{status: "hey", visibility: "private"})
+
+ {:ok, visible} =
+ CommonAPI.post(poster, %{status: "hey", visibility: "private", in_reply_to_id: invisible.id})
+
+ status = StatusView.render("show.json", activity: visible, for: user)
+ refute status.pleroma.parent_visible
+
+ status = StatusView.render("show.json", activity: visible, for: poster)
+ assert status.pleroma.parent_visible
+ end
end
diff --git a/test/web/pleroma_api/controllers/account_controller_test.exs b/test/web/pleroma_api/controllers/account_controller_test.exs
index 103997c31..07909d48b 100644
--- a/test/web/pleroma_api/controllers/account_controller_test.exs
+++ b/test/web/pleroma_api/controllers/account_controller_test.exs
@@ -13,8 +13,6 @@ defmodule Pleroma.Web.PleromaAPI.AccountControllerTest do
import Pleroma.Factory
import Swoosh.TestAssertions
- @image "data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7"
-
describe "POST /api/v1/pleroma/accounts/confirmation_resend" do
setup do
{:ok, user} =
@@ -68,103 +66,6 @@ defmodule Pleroma.Web.PleromaAPI.AccountControllerTest do
end
end
- describe "PATCH /api/v1/pleroma/accounts/update_avatar" do
- setup do: oauth_access(["write:accounts"])
-
- test "user avatar can be set", %{user: user, conn: conn} do
- avatar_image = File.read!("test/fixtures/avatar_data_uri")
-
- conn =
- conn
- |> put_req_header("content-type", "multipart/form-data")
- |> patch("/api/v1/pleroma/accounts/update_avatar", %{img: avatar_image})
-
- user = refresh_record(user)
-
- assert %{
- "name" => _,
- "type" => _,
- "url" => [
- %{
- "href" => _,
- "mediaType" => _,
- "type" => _
- }
- ]
- } = user.avatar
-
- assert %{"url" => _} = json_response_and_validate_schema(conn, 200)
- end
-
- test "user avatar can be reset", %{user: user, conn: conn} do
- conn =
- conn
- |> put_req_header("content-type", "multipart/form-data")
- |> patch("/api/v1/pleroma/accounts/update_avatar", %{img: ""})
-
- user = User.get_cached_by_id(user.id)
-
- assert user.avatar == nil
-
- assert %{"url" => nil} = json_response_and_validate_schema(conn, 200)
- end
- end
-
- describe "PATCH /api/v1/pleroma/accounts/update_banner" do
- setup do: oauth_access(["write:accounts"])
-
- test "can set profile banner", %{user: user, conn: conn} do
- conn =
- conn
- |> put_req_header("content-type", "multipart/form-data")
- |> patch("/api/v1/pleroma/accounts/update_banner", %{"banner" => @image})
-
- user = refresh_record(user)
- assert user.banner["type"] == "Image"
-
- assert %{"url" => _} = json_response_and_validate_schema(conn, 200)
- end
-
- test "can reset profile banner", %{user: user, conn: conn} do
- conn =
- conn
- |> put_req_header("content-type", "multipart/form-data")
- |> patch("/api/v1/pleroma/accounts/update_banner", %{"banner" => ""})
-
- user = refresh_record(user)
- assert user.banner == %{}
-
- assert %{"url" => nil} = json_response_and_validate_schema(conn, 200)
- end
- end
-
- describe "PATCH /api/v1/pleroma/accounts/update_background" do
- setup do: oauth_access(["write:accounts"])
-
- test "background image can be set", %{user: user, conn: conn} do
- conn =
- conn
- |> put_req_header("content-type", "multipart/form-data")
- |> patch("/api/v1/pleroma/accounts/update_background", %{"img" => @image})
-
- user = refresh_record(user)
- assert user.background["type"] == "Image"
- # assert %{"url" => _} = json_response(conn, 200)
- assert %{"url" => _} = json_response_and_validate_schema(conn, 200)
- end
-
- test "background image can be reset", %{user: user, conn: conn} do
- conn =
- conn
- |> put_req_header("content-type", "multipart/form-data")
- |> patch("/api/v1/pleroma/accounts/update_background", %{"img" => ""})
-
- user = refresh_record(user)
- assert user.background == %{}
- assert %{"url" => nil} = json_response_and_validate_schema(conn, 200)
- end
- end
-
describe "getting favorites timeline of specified user" do
setup do
[current_user, user] = insert_pair(:user, hide_favorites: false)
diff --git a/test/web/preload/instance_test.exs b/test/web/preload/instance_test.exs
index 42a0d87bc..a46f28312 100644
--- a/test/web/preload/instance_test.exs
+++ b/test/web/preload/instance_test.exs
@@ -8,7 +8,7 @@ defmodule Pleroma.Web.Preload.Providers.InstanceTest do
setup do: {:ok, Instance.generate_terms(nil)}
- test "it renders the info", %{"/api/v1/instance": info} do
+ test "it renders the info", %{"/api/v1/instance" => info} do
assert %{
description: description,
email: "admin@example.com",
@@ -18,14 +18,25 @@ defmodule Pleroma.Web.Preload.Providers.InstanceTest do
assert String.equivalent?(description, "Pleroma: An efficient and flexible fediverse server")
end
- test "it renders the panel", %{"/instance/panel.html": panel} do
+ test "it renders the panel", %{"/instance/panel.html" => panel} do
assert String.contains?(
panel,
"<p>Welcome to <a href=\"https://pleroma.social\" target=\"_blank\">Pleroma!</a></p>"
)
end
- test "it renders the node_info", %{"/nodeinfo/2.0": nodeinfo} do
+ test "it works with overrides" do
+ clear_config([:instance, :static_dir], "test/fixtures/preload_static")
+
+ %{"/instance/panel.html" => panel} = Instance.generate_terms(nil)
+
+ assert String.contains?(
+ panel,
+ "HEY!"
+ )
+ end
+
+ test "it renders the node_info", %{"/nodeinfo/2.0.json" => nodeinfo} do
%{
metadata: metadata,
version: "2.0"
diff --git a/test/web/preload/status_net_test.exs b/test/web/preload/status_net_test.exs
deleted file mode 100644
index ab6823a7e..000000000
--- a/test/web/preload/status_net_test.exs
+++ /dev/null
@@ -1,14 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.Preload.Providers.StatusNetTest do
- use Pleroma.DataCase
- alias Pleroma.Web.Preload.Providers.StatusNet
-
- setup do: {:ok, StatusNet.generate_terms(nil)}
-
- test "it renders the info", %{"/api/statusnet/config.json": info} do
- assert info =~ "<name>Pleroma</name>"
- end
-end
diff --git a/test/web/preload/timeline_test.exs b/test/web/preload/timeline_test.exs
index da6a3aded..fea95a6a4 100644
--- a/test/web/preload/timeline_test.exs
+++ b/test/web/preload/timeline_test.exs
@@ -9,7 +9,7 @@ defmodule Pleroma.Web.Preload.Providers.TimelineTest do
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.Preload.Providers.Timelines
- @public_url :"/api/v1/timelines/public"
+ @public_url "/api/v1/timelines/public"
describe "unauthenticated timeliness when restricted" do
setup do
diff --git a/test/web/preload/user_test.exs b/test/web/preload/user_test.exs
index 99232cdfa..83f065e27 100644
--- a/test/web/preload/user_test.exs
+++ b/test/web/preload/user_test.exs
@@ -9,13 +9,11 @@ defmodule Pleroma.Web.Preload.Providers.UserTest do
describe "returns empty when user doesn't exist" do
test "nil user specified" do
- refute User.generate_terms(%{user: nil})
- |> Map.has_key?("/api/v1/accounts")
+ assert User.generate_terms(%{user: nil}) == %{}
end
test "missing user specified" do
- refute User.generate_terms(%{user: :not_a_user})
- |> Map.has_key?("/api/v1/accounts")
+ assert User.generate_terms(%{user: :not_a_user}) == %{}
end
end
@@ -23,11 +21,13 @@ defmodule Pleroma.Web.Preload.Providers.UserTest do
setup do
user = insert(:user)
- {:ok, User.generate_terms(%{user: user})}
+ terms = User.generate_terms(%{user: user})
+ %{terms: terms, user: user}
end
- test "account is rendered", %{"/api/v1/accounts": accounts} do
- assert %{acct: user, username: user} = accounts
+ test "account is rendered", %{terms: terms, user: user} do
+ account = terms["/api/v1/accounts/#{user.id}"]
+ assert %{acct: user, username: user} = account
end
end
end
diff --git a/test/web/static_fe/static_fe_controller_test.exs b/test/web/static_fe/static_fe_controller_test.exs
index a49ab002f..1598bf675 100644
--- a/test/web/static_fe/static_fe_controller_test.exs
+++ b/test/web/static_fe/static_fe_controller_test.exs
@@ -87,6 +87,20 @@ defmodule Pleroma.Web.StaticFE.StaticFEControllerTest do
assert html =~ "testing a thing!"
end
+ test "redirects to json if requested", %{conn: conn, user: user} do
+ {:ok, activity} = CommonAPI.post(user, %{status: "testing a thing!"})
+
+ conn =
+ conn
+ |> put_req_header(
+ "accept",
+ "Accept: application/activity+json, application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\", text/html"
+ )
+ |> get("/notice/#{activity.id}")
+
+ assert redirected_to(conn, 302) =~ activity.data["object"]
+ end
+
test "filters HTML tags", %{conn: conn} do
user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{status: "<script>alert('xss')</script>"})
diff --git a/test/web/streamer/streamer_test.exs b/test/web/streamer/streamer_test.exs
index 245f6e63f..d56d74464 100644
--- a/test/web/streamer/streamer_test.exs
+++ b/test/web/streamer/streamer_test.exs
@@ -116,6 +116,35 @@ defmodule Pleroma.Web.StreamerTest do
refute Streamer.filtered_by_user?(user, announce)
end
+ test "it does not stream announces of the user's own posts in the 'user' stream", %{
+ user: user
+ } do
+ Streamer.get_topic_and_add_socket("user", user)
+
+ other_user = insert(:user)
+ {:ok, activity} = CommonAPI.post(user, %{status: "hey"})
+ {:ok, announce} = CommonAPI.repeat(activity.id, other_user)
+
+ assert Streamer.filtered_by_user?(user, announce)
+ end
+
+ test "it does stream notifications announces of the user's own posts in the 'user' stream", %{
+ user: user
+ } do
+ Streamer.get_topic_and_add_socket("user", user)
+
+ other_user = insert(:user)
+ {:ok, activity} = CommonAPI.post(user, %{status: "hey"})
+ {:ok, announce} = CommonAPI.repeat(activity.id, other_user)
+
+ notification =
+ Pleroma.Notification
+ |> Repo.get_by(%{user_id: user.id, activity_id: announce.id})
+ |> Repo.preload(:activity)
+
+ refute Streamer.filtered_by_user?(user, notification)
+ end
+
test "it streams boosts of mastodon user in the 'user' stream", %{user: user} do
Streamer.get_topic_and_add_socket("user", user)
diff --git a/test/web/twitter_api/util_controller_test.exs b/test/web/twitter_api/util_controller_test.exs
index ad919d341..76e9369f7 100644
--- a/test/web/twitter_api/util_controller_test.exs
+++ b/test/web/twitter_api/util_controller_test.exs
@@ -224,105 +224,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
end
end
- describe "GET /api/statusnet/config" do
- test "it returns config in xml format", %{conn: conn} do
- instance = Config.get(:instance)
-
- response =
- conn
- |> put_req_header("accept", "application/xml")
- |> get("/api/statusnet/config")
- |> response(:ok)
-
- assert response ==
- "<config>\n<site>\n<name>#{Keyword.get(instance, :name)}</name>\n<site>#{
- Pleroma.Web.base_url()
- }</site>\n<textlimit>#{Keyword.get(instance, :limit)}</textlimit>\n<closed>#{
- !Keyword.get(instance, :registrations_open)
- }</closed>\n</site>\n</config>\n"
- end
-
- test "it returns config in json format", %{conn: conn} do
- instance = Config.get(:instance)
- Config.put([:instance, :managed_config], true)
- Config.put([:instance, :registrations_open], false)
- Config.put([:instance, :invites_enabled], true)
- Config.put([:instance, :public], false)
- Config.put([:frontend_configurations, :pleroma_fe], %{theme: "asuka-hospital"})
-
- response =
- conn
- |> put_req_header("accept", "application/json")
- |> get("/api/statusnet/config")
- |> json_response(:ok)
-
- expected_data = %{
- "site" => %{
- "accountActivationRequired" => "0",
- "closed" => "1",
- "description" => Keyword.get(instance, :description),
- "invitesEnabled" => "1",
- "name" => Keyword.get(instance, :name),
- "pleromafe" => %{"theme" => "asuka-hospital"},
- "private" => "1",
- "safeDMMentionsEnabled" => "0",
- "server" => Pleroma.Web.base_url(),
- "textlimit" => to_string(Keyword.get(instance, :limit)),
- "uploadlimit" => %{
- "avatarlimit" => to_string(Keyword.get(instance, :avatar_upload_limit)),
- "backgroundlimit" => to_string(Keyword.get(instance, :background_upload_limit)),
- "bannerlimit" => to_string(Keyword.get(instance, :banner_upload_limit)),
- "uploadlimit" => to_string(Keyword.get(instance, :upload_limit))
- },
- "vapidPublicKey" => Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
- }
- }
-
- assert response == expected_data
- end
-
- test "returns the state of safe_dm_mentions flag", %{conn: conn} do
- Config.put([:instance, :safe_dm_mentions], true)
-
- response =
- conn
- |> get("/api/statusnet/config.json")
- |> json_response(:ok)
-
- assert response["site"]["safeDMMentionsEnabled"] == "1"
-
- Config.put([:instance, :safe_dm_mentions], false)
-
- response =
- conn
- |> get("/api/statusnet/config.json")
- |> json_response(:ok)
-
- assert response["site"]["safeDMMentionsEnabled"] == "0"
- end
-
- test "it returns the managed config", %{conn: conn} do
- Config.put([:instance, :managed_config], false)
- Config.put([:frontend_configurations, :pleroma_fe], %{theme: "asuka-hospital"})
-
- response =
- conn
- |> get("/api/statusnet/config.json")
- |> json_response(:ok)
-
- refute response["site"]["pleromafe"]
-
- Config.put([:instance, :managed_config], true)
-
- response =
- conn
- |> get("/api/statusnet/config.json")
- |> json_response(:ok)
-
- assert response["site"]["pleromafe"] == %{"theme" => "asuka-hospital"}
- end
- end
-
describe "GET /api/pleroma/frontend_configurations" do
test "returns everything in :pleroma, :frontend_configurations", %{conn: conn} do
config = [
@@ -451,28 +352,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
end
end
- describe "GET /api/statusnet/version" do
- test "it returns version in xml format", %{conn: conn} do
- response =
- conn
- |> put_req_header("accept", "application/xml")
- |> get("/api/statusnet/version")
- |> response(:ok)
-
- assert response == "<version>#{Pleroma.Application.named_version()}</version>"
- end
-
- test "it returns version in json format", %{conn: conn} do
- response =
- conn
- |> put_req_header("accept", "application/json")
- |> get("/api/statusnet/version")
- |> json_response(:ok)
-
- assert response == "#{Pleroma.Application.named_version()}"
- end
- end
-
describe "POST /main/ostatus - remote_subscribe/2" do
setup do: clear_config([:instance, :federating], true)