Merge branch 'develop' into 'endorsements-api'
# Conflicts: # test/pleroma/web/pleroma_api/controllers/account_controller_test.exs
This commit is contained in:
commit
5ce3c12c28
641 changed files with 5697 additions and 1718 deletions
76
test/fixtures/friendica-dislike-undo.json
vendored
Normal file
76
test/fixtures/friendica-dislike-undo.json
vendored
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
{
|
||||
"Hashtag": "as:Hashtag",
|
||||
"PropertyValue": "schema:PropertyValue",
|
||||
"conversation": "ostatus:conversation",
|
||||
"dfrn": "http://purl.org/macgirvin/dfrn/1.0/",
|
||||
"diaspora": "https://diasporafoundation.org/ns/",
|
||||
"directMessage": "litepub:directMessage",
|
||||
"discoverable": "toot:discoverable",
|
||||
"featured": {
|
||||
"@id": "toot:featured",
|
||||
"@type": "@id"
|
||||
},
|
||||
"litepub": "http://litepub.social/ns#",
|
||||
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||
"ostatus": "http://ostatus.org#",
|
||||
"quoteUrl": "as:quoteUrl",
|
||||
"schema": "http://schema.org#",
|
||||
"sensitive": "as:sensitive",
|
||||
"toot": "http://joinmastodon.org/ns#",
|
||||
"value": "schema:value",
|
||||
"vcard": "http://www.w3.org/2006/vcard/ns#"
|
||||
}
|
||||
],
|
||||
"actor": "https://my-place.social/profile/vaartis",
|
||||
"cc": [
|
||||
"https://my-place.social/followers/vaartis"
|
||||
],
|
||||
"id": "https://my-place.social/objects/e599373b-1368-4b20-cd24-837166957182/Undo",
|
||||
"instrument": {
|
||||
"id": "https://my-place.social/friendica",
|
||||
"name": "Friendica 'Interrupted Fern' 2024.12-1576",
|
||||
"type": "Application",
|
||||
"url": "https://my-place.social"
|
||||
},
|
||||
"object": {
|
||||
"actor": "https://my-place.social/profile/vaartis",
|
||||
"cc": [
|
||||
"https://my-place.social/followers/vaartis"
|
||||
],
|
||||
"diaspora:guid": "e599373b-1968-4b20-cd24-80d340160302",
|
||||
"diaspora:like": "{\"author\":\"vaartis@my-place.social\",\"guid\":\"e599373b-1968-4b20-cd24-80d340160302\",\"parent_guid\":\"cd36feba-c31f3ed3fd5c064a-17c31593\",\"parent_type\":\"Post\",\"positive\":\"false\",\"author_signature\":\"xR2zLJNfc9Nhx1n8LLMWM1kde12my4cqamIsrH\\/UntKzuDwO4DuHBL0fkFhgC\\/dylxm4HqsHD45MQbtwQCVGq6WhC96TrbMuYEK61HIO23dTr3m+qJVtfdH4wyhUNHgiiYPhZpkLDfnR1JiRWmFTlmZC8q8JEkOB5IQsrWia2eOR6IsqDcdKO\\/Urgns9\\/BdQi8KnchBKSEFc1iUtcOEruvhunKGyW5zI\\/Rltfdz3xGH8tlw+YlMXeWXPnqgOJ9GzNA0lwG4U421L6yylYagW7oxIznnBLB4bO46vYZbgXZV1hiI9ZyveHOinLMY1QkmTj5CNvnx3\\/VJwLovd0v+0Nr2vu\\/3ftbpBXc6L1bsNjlRqtsfwJlcgl+tH1DC4W8tKf+Y3tdtzVw0CHXCuacxHLyq5wZd\\/5YfYR9SJQ\\/jInU4PHA5+hIE3PGqNUp5QfFE0umq56H7MQKsIPgM5mMV4fPAA8OpltuMVDvQYUxalrnvoTf00k90x1wCTK71\\/jQGh7r7PmGvSdfPr+65eVTjITD8\\/lTGIb8850v1fl3\\/i2R8Dv17jQIRyX5o9MXPSO6jHo4Swma5RzPA\\/0bRj6qRTyPkM1L9qEIr+2H2I7KKhT2ZE5GhAU7yI9A3VLBWzpTrUPMGbfpd1OjVTEqXAdMjpLDYI3Mh5zQ58p8xCzt+W+t0=\"}",
|
||||
"id": "https://my-place.social/objects/e599373b-1368-4b20-cd24-837166957182",
|
||||
"instrument": {
|
||||
"id": "https://my-place.social/friendica",
|
||||
"name": "Friendica 'Interrupted Fern' 2024.12-1576",
|
||||
"type": "Application",
|
||||
"url": "https://my-place.social"
|
||||
},
|
||||
"object": "https://pl.kotobank.ch/objects/301bce65-8a1b-4c49-a65c-fe2ce861a213",
|
||||
"published": "2025-06-12T18:47:41Z",
|
||||
"to": [
|
||||
"https://pl.kotobank.ch/users/vaartis",
|
||||
"https://mitra.social/users/silverpill",
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"type": "Dislike"
|
||||
},
|
||||
"published": "2025-06-12T18:41:25Z",
|
||||
"signature": {
|
||||
"created": "2025-06-12T18:44:16Z",
|
||||
"creator": "https://my-place.social/profile/vaartis#main-key",
|
||||
"nonce": "2d67847d4bd4b1b83a30d61eac6cdc7ad6b980df06a8b9b97217e1d8f7b6cf20",
|
||||
"signatureValue": "LnoRMZuQGDvTICkShGBq28ynaj2lF1bViJFGS6n4gKn3IbxPWATHxao43gxWRc+HCTrHNg7quzgaW4+PYM7UVUz3jO+bjNKsN845nijOVdyFrPOXbuaij3KQh2OoHhFJWoV/ZQQTFF0kRK1qT4BwG+P8NqOOKAMv+Cw7ruQH+f2w7uDgcNIbCD1gLcwb6cw7WVe5qu8yMkKqp2kBdqW3RCsI85RmmFgwehDgH5nrX7ER1qbeLWrqy7echwD9/fO3rqAu13xDNyiGZHDT7JB3RUt0AyMm0XCfjbwSQ0n+MkYXgE4asvFz81+iiPCLt+6gePWAFc5odF1FxdySBpSuUOs4p92NzP9OhQ0c0qrqrzYI7aYklY7oMfxjkva+X+0bm3up+2IRJdnZa/pXlmwdcqTpyMr1sgzaexMUNBp3dq7zA51eEaakLDX3i2onXJowfmze3+6XgPAFHYamR+pRNtuEoY4uyYEK3fj5GgwJ4RtFJMYVoEs/Q8h3OgYRcK1FE9UlDjSqbQ7QIRn2Ib4wjgmkeM0vrHIwh/1CtqA/M/6WuYFzCaJBc8O9ykpK9ZMbw64ToQXKf2SqhZsDoyTWRWTO1PXOk1XCAAElUh8/WCyeghvgqLXn0LHov4lmBsHA5iMUcLqBKD3GJIHd+ExrOFxMZs4mBLLGyz0p5joJ3NY=",
|
||||
"type": "RsaSignature2017"
|
||||
},
|
||||
"to": [
|
||||
"https://pl.kotobank.ch/users/vaartis",
|
||||
"https://mitra.social/users/silverpill",
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"type": "Undo"
|
||||
}
|
||||
56
test/fixtures/friendica-dislike.json
vendored
Normal file
56
test/fixtures/friendica-dislike.json
vendored
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
{
|
||||
"Hashtag": "as:Hashtag",
|
||||
"PropertyValue": "schema:PropertyValue",
|
||||
"conversation": "ostatus:conversation",
|
||||
"dfrn": "http://purl.org/macgirvin/dfrn/1.0/",
|
||||
"diaspora": "https://diasporafoundation.org/ns/",
|
||||
"directMessage": "litepub:directMessage",
|
||||
"discoverable": "toot:discoverable",
|
||||
"featured": {
|
||||
"@id": "toot:featured",
|
||||
"@type": "@id"
|
||||
},
|
||||
"litepub": "http://litepub.social/ns#",
|
||||
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||
"ostatus": "http://ostatus.org#",
|
||||
"quoteUrl": "as:quoteUrl",
|
||||
"schema": "http://schema.org#",
|
||||
"sensitive": "as:sensitive",
|
||||
"toot": "http://joinmastodon.org/ns#",
|
||||
"value": "schema:value",
|
||||
"vcard": "http://www.w3.org/2006/vcard/ns#"
|
||||
}
|
||||
],
|
||||
"actor": "https://my-place.social/profile/vaartis",
|
||||
"cc": [
|
||||
"https://my-place.social/followers/vaartis"
|
||||
],
|
||||
"diaspora:guid": "e599373b-1968-4b20-cd24-80d340160302",
|
||||
"diaspora:like": "{\"author\":\"vaartis@my-place.social\",\"guid\":\"e599373b-1968-4b20-cd24-80d340160302\",\"parent_guid\":\"cd36feba-c31f3ed3fd5c064a-17c31593\",\"parent_type\":\"Post\",\"positive\":\"false\",\"author_signature\":\"xR2zLJNfc9Nhx1n8LLMWM1kde12my4cqamIsrH\\/UntKzuDwO4DuHBL0fkFhgC\\/dylxm4HqsHD45MQbtwQCVGq6WhC96TrbMuYEK61HIO23dTr3m+qJVtfdH4wyhUNHgiiYPhZpkLDfnR1JiRWmFTlmZC8q8JEkOB5IQsrWia2eOR6IsqDcdKO\\/Urgns9\\/BdQi8KnchBKSEFc1iUtcOEruvhunKGyW5zI\\/Rltfdz3xGH8tlw+YlMXeWXPnqgOJ9GzNA0lwG4U421L6yylYagW7oxIznnBLB4bO46vYZbgXZV1hiI9ZyveHOinLMY1QkmTj5CNvnx3\\/VJwLovd0v+0Nr2vu\\/3ftbpBXc6L1bsNjlRqtsfwJlcgl+tH1DC4W8tKf+Y3tdtzVw0CHXCuacxHLyq5wZd\\/5YfYR9SJQ\\/jInU4PHA5+hIE3PGqNUp5QfFE0umq56H7MQKsIPgM5mMV4fPAA8OpltuMVDvQYUxalrnvoTf00k90x1wCTK71\\/jQGh7r7PmGvSdfPr+65eVTjITD8\\/lTGIb8850v1fl3\\/i2R8Dv17jQIRyX5o9MXPSO6jHo4Swma5RzPA\\/0bRj6qRTyPkM1L9qEIr+2H2I7KKhT2ZE5GhAU7yI9A3VLBWzpTrUPMGbfpd1OjVTEqXAdMjpLDYI3Mh5zQ58p8xCzt+W+t0=\"}",
|
||||
"id": "https://my-place.social/objects/e599373b-1368-4b20-cd24-837166957182",
|
||||
"instrument": {
|
||||
"id": "https://my-place.social/friendica",
|
||||
"name": "Friendica 'Interrupted Fern' 2024.12-1576",
|
||||
"type": "Application",
|
||||
"url": "https://my-place.social"
|
||||
},
|
||||
"object": "https://pl.kotobank.ch/objects/301bce65-8a1b-4c49-a65c-fe2ce861a213",
|
||||
"published": "2025-06-12T18:47:41Z",
|
||||
"signature": {
|
||||
"created": "2025-06-12T18:47:42Z",
|
||||
"creator": "https://my-place.social/profile/vaartis#main-key",
|
||||
"nonce": "84e496f80b09d7a299c5cc89e8cadd13abf621b3a0a321684fa74278b68a6dd8",
|
||||
"signatureValue": "qe2WxY+j7daIYLRadCctgal6A1s9XgoiMfM/8KjJm15w0sSizYYqruyQ5gS44e+cj5GHc9v5gP2ieod5v7eHAPzlcDI4bfkcyHVapAXTqU67ZebW+v6Q+21IMDgqrkYCv5TbV7LTxltW59dlqovpHE4TEe/M7xLKWJ3vVchRUcWqH9kDmak0nacoqYVAb5E9jYnQhUWPTCfPV82qQpeWQPOZ4iIvPw6rDkSSY5jL6bCogBZblHGpUjXfe/FPlacaCWiTQdoga3yOBXB1RYPw9nh5FI5Xkv/oi+52WmJrECinlD6AL8/BpiYvKz236zy7p/TR4BXlCx9RR/msjOnSabkQ4kmYFrRr80UDCGF+CdkdzLl8K9rSE3PbF1+nEqD7X0GOWn/DdtixqXJw6IR4bh32YW2SlcrSRBvI1p82Mv68BeqRaYqL6FAhKFwLhX5JpXngZ3k0g7rWWxc498voPWnFZDyCTRNxO9VIIUavDDEQ0BdFk6WDb8zx9tsAg8JoK57eVDcFly7tfVQffYiHpve06d8ag1DtzipqguRsURmuqpGNMq28XBTxwtrP2LnXXHKxoYN/YQ9cDnCKclbx7/uKmOVMLkLZlM0wAVoZpm5z2fG4voKqFiGZ1PoiFY2sN4URMArJtygV3PlTX4ASAQrak0ksvEo9msrBUD0Su9c=",
|
||||
"type": "RsaSignature2017"
|
||||
},
|
||||
"to": [
|
||||
"https://pl.kotobank.ch/users/vaartis",
|
||||
"https://mitra.social/users/silverpill",
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"type": "Dislike"
|
||||
}
|
||||
1
test/fixtures/warnings/otp_version/21.1
vendored
1
test/fixtures/warnings/otp_version/21.1
vendored
|
|
@ -1 +0,0 @@
|
|||
21.1
|
||||
1
test/fixtures/warnings/otp_version/22.1
vendored
1
test/fixtures/warnings/otp_version/22.1
vendored
|
|
@ -1 +0,0 @@
|
|||
22.1
|
||||
1
test/fixtures/warnings/otp_version/22.4
vendored
1
test/fixtures/warnings/otp_version/22.4
vendored
|
|
@ -1 +0,0 @@
|
|||
22.4
|
||||
1
test/fixtures/warnings/otp_version/23.0
vendored
1
test/fixtures/warnings/otp_version/23.0
vendored
|
|
@ -1 +0,0 @@
|
|||
23.0
|
||||
|
|
@ -42,9 +42,10 @@ defmodule Mix.Tasks.Pleroma.AppTest do
|
|||
|
||||
test "with errors" do
|
||||
Mix.Tasks.Pleroma.App.run(["create"])
|
||||
{:mix_shell, :error, ["Creating failed:"]}
|
||||
{:mix_shell, :error, ["name: can't be blank"]}
|
||||
{:mix_shell, :error, ["redirect_uris: can't be blank"]}
|
||||
|
||||
assert_receive {:mix_shell, :error, ["Creating failed:"]}
|
||||
assert_receive {:mix_shell, :error, ["name: can't be blank"]}
|
||||
assert_receive {:mix_shell, :error, ["redirect_uris: can't be blank"]}
|
||||
end
|
||||
|
||||
defp assert_app(name, redirect, scopes) do
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ defmodule Mix.Tasks.Pleroma.FrontendTest do
|
|||
@dir "test/frontend_static_test"
|
||||
|
||||
setup do
|
||||
File.mkdir_p!(@dir)
|
||||
Pleroma.Backports.mkdir_p!(@dir)
|
||||
clear_config([:instance, :static_dir], @dir)
|
||||
|
||||
on_exit(fn ->
|
||||
|
|
@ -50,7 +50,7 @@ defmodule Mix.Tasks.Pleroma.FrontendTest do
|
|||
|
||||
folder = Path.join([@dir, "frontends", "pleroma", "fantasy"])
|
||||
previously_existing = Path.join([folder, "temp"])
|
||||
File.mkdir_p!(folder)
|
||||
Pleroma.Backports.mkdir_p!(folder)
|
||||
File.write!(previously_existing, "yey")
|
||||
assert File.exists?(previously_existing)
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ defmodule Mix.Tasks.Pleroma.InstanceTest do
|
|||
use Pleroma.DataCase
|
||||
|
||||
setup do
|
||||
File.mkdir_p!(tmp_path())
|
||||
Pleroma.Backports.mkdir_p!(tmp_path())
|
||||
|
||||
on_exit(fn ->
|
||||
File.rm_rf(tmp_path())
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ defmodule Mix.Tasks.Pleroma.UploadsTest do
|
|||
upload_dir = Config.get([Pleroma.Uploaders.Local, :uploads])
|
||||
|
||||
if not File.exists?(upload_dir) || File.ls!(upload_dir) == [] do
|
||||
File.mkdir_p(upload_dir)
|
||||
Pleroma.Backports.mkdir_p(upload_dir)
|
||||
|
||||
Path.join([upload_dir, "file.txt"])
|
||||
|> File.touch()
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@ defmodule Pleroma.Emoji.PackTest do
|
|||
)
|
||||
|
||||
setup do
|
||||
# Reload emoji to ensure a clean state
|
||||
Emoji.reload()
|
||||
|
||||
pack_path = Path.join(@emoji_path, "dump_pack")
|
||||
File.mkdir(pack_path)
|
||||
|
||||
|
|
@ -58,7 +61,7 @@ defmodule Pleroma.Emoji.PackTest do
|
|||
test "skips existing emojis when adding from zip file", %{pack: pack} do
|
||||
# First, let's create a test pack with a "bear" emoji
|
||||
test_pack_path = Path.join(@emoji_path, "test_bear_pack")
|
||||
File.mkdir_p(test_pack_path)
|
||||
Pleroma.Backports.mkdir_p(test_pack_path)
|
||||
|
||||
# Create a pack.json file
|
||||
File.write!(Path.join(test_pack_path, "pack.json"), """
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ defmodule Pleroma.FrontendTest do
|
|||
@dir "test/frontend_static_test"
|
||||
|
||||
setup do
|
||||
File.mkdir_p!(@dir)
|
||||
Pleroma.Backports.mkdir_p!(@dir)
|
||||
clear_config([:instance, :static_dir], @dir)
|
||||
|
||||
on_exit(fn ->
|
||||
|
|
@ -46,7 +46,7 @@ defmodule Pleroma.FrontendTest do
|
|||
|
||||
folder = Path.join([@dir, "frontends", "pleroma", "fantasy"])
|
||||
previously_existing = Path.join([folder, "temp"])
|
||||
File.mkdir_p!(folder)
|
||||
Pleroma.Backports.mkdir_p!(folder)
|
||||
File.write!(previously_existing, "yey")
|
||||
assert File.exists?(previously_existing)
|
||||
|
||||
|
|
|
|||
|
|
@ -14,4 +14,133 @@ defmodule Pleroma.HashtagTest do
|
|||
assert {:name, {"can't be blank", [validation: :required]}} in changeset.errors
|
||||
end
|
||||
end
|
||||
|
||||
describe "search_hashtags" do
|
||||
test "searches hashtags by partial match" do
|
||||
{:ok, _} = Hashtag.get_or_create_by_name("car")
|
||||
{:ok, _} = Hashtag.get_or_create_by_name("racecar")
|
||||
{:ok, _} = Hashtag.get_or_create_by_name("nascar")
|
||||
{:ok, _} = Hashtag.get_or_create_by_name("bicycle")
|
||||
|
||||
results = Hashtag.search("car")
|
||||
assert "car" in results
|
||||
assert "racecar" in results
|
||||
assert "nascar" in results
|
||||
refute "bicycle" in results
|
||||
|
||||
results = Hashtag.search("race")
|
||||
assert "racecar" in results
|
||||
refute "car" in results
|
||||
refute "nascar" in results
|
||||
refute "bicycle" in results
|
||||
|
||||
results = Hashtag.search("nonexistent")
|
||||
assert results == []
|
||||
end
|
||||
|
||||
test "searches hashtags by multiple words in query" do
|
||||
{:ok, _} = Hashtag.get_or_create_by_name("computer")
|
||||
{:ok, _} = Hashtag.get_or_create_by_name("laptop")
|
||||
{:ok, _} = Hashtag.get_or_create_by_name("desktop")
|
||||
{:ok, _} = Hashtag.get_or_create_by_name("phone")
|
||||
|
||||
# Search for "new computer" - should return "computer"
|
||||
results = Hashtag.search("new computer")
|
||||
assert "computer" in results
|
||||
refute "laptop" in results
|
||||
refute "desktop" in results
|
||||
refute "phone" in results
|
||||
|
||||
# Search for "computer laptop" - should return both
|
||||
results = Hashtag.search("computer laptop")
|
||||
assert "computer" in results
|
||||
assert "laptop" in results
|
||||
refute "desktop" in results
|
||||
refute "phone" in results
|
||||
|
||||
# Search for "new phone" - should return "phone"
|
||||
results = Hashtag.search("new phone")
|
||||
assert "phone" in results
|
||||
refute "computer" in results
|
||||
refute "laptop" in results
|
||||
refute "desktop" in results
|
||||
end
|
||||
|
||||
test "supports pagination" do
|
||||
{:ok, _} = Hashtag.get_or_create_by_name("alpha")
|
||||
{:ok, _} = Hashtag.get_or_create_by_name("beta")
|
||||
{:ok, _} = Hashtag.get_or_create_by_name("gamma")
|
||||
{:ok, _} = Hashtag.get_or_create_by_name("delta")
|
||||
|
||||
results = Hashtag.search("a", limit: 2)
|
||||
assert length(results) == 2
|
||||
|
||||
results = Hashtag.search("a", limit: 2, offset: 1)
|
||||
assert length(results) == 2
|
||||
end
|
||||
|
||||
test "handles matching many search terms" do
|
||||
{:ok, _} = Hashtag.get_or_create_by_name("computer")
|
||||
{:ok, _} = Hashtag.get_or_create_by_name("laptop")
|
||||
{:ok, _} = Hashtag.get_or_create_by_name("phone")
|
||||
{:ok, _} = Hashtag.get_or_create_by_name("tablet")
|
||||
|
||||
results = Hashtag.search("new fast computer laptop phone tablet device")
|
||||
assert "computer" in results
|
||||
assert "laptop" in results
|
||||
assert "phone" in results
|
||||
assert "tablet" in results
|
||||
end
|
||||
|
||||
test "ranks results by match quality" do
|
||||
{:ok, _} = Hashtag.get_or_create_by_name("my_computer")
|
||||
{:ok, _} = Hashtag.get_or_create_by_name("computer_science")
|
||||
{:ok, _} = Hashtag.get_or_create_by_name("computer")
|
||||
|
||||
results = Hashtag.search("computer")
|
||||
|
||||
# Exact match first
|
||||
assert Enum.at(results, 0) == "computer"
|
||||
|
||||
# Prefix match would be next
|
||||
assert Enum.at(results, 1) == "computer_science"
|
||||
|
||||
# worst match is last
|
||||
assert Enum.at(results, 2) == "my_computer"
|
||||
end
|
||||
|
||||
test "prioritizes shorter names when ranking is equal" do
|
||||
# Create hashtags with same ranking but different lengths
|
||||
{:ok, _} = Hashtag.get_or_create_by_name("car")
|
||||
{:ok, _} = Hashtag.get_or_create_by_name("racecar")
|
||||
{:ok, _} = Hashtag.get_or_create_by_name("nascar")
|
||||
|
||||
# Search for "car" - shorter names should come first
|
||||
results = Hashtag.search("car")
|
||||
# Shortest exact match first
|
||||
assert Enum.at(results, 0) == "car"
|
||||
assert "racecar" in results
|
||||
assert "nascar" in results
|
||||
end
|
||||
|
||||
test "handles hashtag symbols in search query" do
|
||||
{:ok, _} = Hashtag.get_or_create_by_name("computer")
|
||||
{:ok, _} = Hashtag.get_or_create_by_name("laptop")
|
||||
{:ok, _} = Hashtag.get_or_create_by_name("phone")
|
||||
|
||||
results_with_hash = Hashtag.search("#computer #laptop")
|
||||
results_without_hash = Hashtag.search("computer laptop")
|
||||
|
||||
assert results_with_hash == results_without_hash
|
||||
|
||||
results_mixed = Hashtag.search("#computer laptop #phone")
|
||||
assert "computer" in results_mixed
|
||||
assert "laptop" in results_mixed
|
||||
assert "phone" in results_mixed
|
||||
|
||||
results_only_hash = Hashtag.search("#computer")
|
||||
results_no_hash = Hashtag.search("computer")
|
||||
assert results_only_hash == results_no_hash
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -25,6 +25,9 @@ defmodule Pleroma.HTTPTest do
|
|||
|
||||
%{method: :post, url: "http://example.com/world"} ->
|
||||
%Tesla.Env{status: 200, body: "world"}
|
||||
|
||||
%{method: :get, url: "https://example.com/emoji/Pack%201/koronebless.png?foo=bar+baz"} ->
|
||||
%Tesla.Env{status: 200, body: "emoji data"}
|
||||
end)
|
||||
|
||||
:ok
|
||||
|
|
@ -67,4 +70,20 @@ defmodule Pleroma.HTTPTest do
|
|||
}
|
||||
end
|
||||
end
|
||||
|
||||
test "URL encoding properly encodes URLs with spaces" do
|
||||
clear_config(:test_url_encoding, true)
|
||||
|
||||
url_with_space = "https://example.com/emoji/Pack 1/koronebless.png?foo=bar baz"
|
||||
|
||||
{:ok, result} = HTTP.get(url_with_space)
|
||||
|
||||
assert result.status == 200
|
||||
|
||||
properly_encoded_url = "https://example.com/emoji/Pack%201/koronebless.png?foo=bar+baz"
|
||||
|
||||
{:ok, result} = HTTP.get(properly_encoded_url)
|
||||
|
||||
assert result.status == 200
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,19 +3,15 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Instances.InstanceTest do
|
||||
alias Pleroma.Instances
|
||||
alias Pleroma.Instances.Instance
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.Tests.ObanHelpers
|
||||
alias Pleroma.Web.CommonAPI
|
||||
|
||||
use Oban.Testing, repo: Pleroma.Repo
|
||||
use Pleroma.DataCase
|
||||
|
||||
import ExUnit.CaptureLog
|
||||
import Pleroma.Factory
|
||||
|
||||
setup_all do: clear_config([:instance, :federation_reachability_timeout_days], 1)
|
||||
|
||||
describe "set_reachable/1" do
|
||||
test "clears `unreachable_since` of existing matching Instance record having non-nil `unreachable_since`" do
|
||||
unreachable_since = NaiveDateTime.to_iso8601(NaiveDateTime.utc_now())
|
||||
|
|
@ -31,6 +27,32 @@ defmodule Pleroma.Instances.InstanceTest do
|
|||
assert {:ok, instance} = Instance.set_reachable(instance.host)
|
||||
refute instance.unreachable_since
|
||||
end
|
||||
|
||||
test "cancels all ReachabilityWorker jobs for the domain" do
|
||||
domain = "cancelme.example.org"
|
||||
insert(:instance, host: domain, unreachable_since: NaiveDateTime.utc_now())
|
||||
|
||||
# Insert a ReachabilityWorker job for this domain, scheduled 5 minutes in the future
|
||||
scheduled_at = DateTime.add(DateTime.utc_now(), 300, :second)
|
||||
|
||||
{:ok, job} =
|
||||
Pleroma.Workers.ReachabilityWorker.new(
|
||||
%{"domain" => domain, "phase" => "phase_1min", "attempt" => 1},
|
||||
scheduled_at: scheduled_at
|
||||
)
|
||||
|> Oban.insert()
|
||||
|
||||
# Ensure the job is present
|
||||
job = Pleroma.Repo.get(Oban.Job, job.id)
|
||||
assert job
|
||||
|
||||
# Call set_reachable, which should delete the job
|
||||
assert {:ok, _} = Instance.set_reachable(domain)
|
||||
|
||||
# Reload the job and assert it is deleted
|
||||
job = Pleroma.Repo.get(Oban.Job, job.id)
|
||||
refute job
|
||||
end
|
||||
end
|
||||
|
||||
describe "set_unreachable/1" do
|
||||
|
|
@ -145,7 +167,11 @@ defmodule Pleroma.Instances.InstanceTest do
|
|||
end
|
||||
|
||||
test "Doesn't scrapes unreachable instances" do
|
||||
instance = insert(:instance, unreachable_since: Instances.reachability_datetime_threshold())
|
||||
instance =
|
||||
insert(:instance,
|
||||
unreachable_since: NaiveDateTime.utc_now() |> NaiveDateTime.add(-:timer.hours(24))
|
||||
)
|
||||
|
||||
url = "https://" <> instance.host
|
||||
|
||||
assert capture_log(fn -> assert nil == Instance.get_or_update_favicon(URI.parse(url)) end) =~
|
||||
|
|
@ -213,32 +239,44 @@ defmodule Pleroma.Instances.InstanceTest do
|
|||
end
|
||||
end
|
||||
|
||||
test "delete_users_and_activities/1 deletes remote instance users and activities" do
|
||||
[mario, luigi, _peach, wario] =
|
||||
users = [
|
||||
insert(:user, nickname: "mario@mushroom.kingdom", name: "Mario"),
|
||||
insert(:user, nickname: "luigi@mushroom.kingdom", name: "Luigi"),
|
||||
insert(:user, nickname: "peach@mushroom.kingdom", name: "Peach"),
|
||||
insert(:user, nickname: "wario@greedville.biz", name: "Wario")
|
||||
]
|
||||
test "delete/1 schedules a job to delete the instance and users" do
|
||||
insert(:user, nickname: "mario@mushroom.kingdom", name: "Mario")
|
||||
|
||||
{:ok, post1} = CommonAPI.post(mario, %{status: "letsa go!"})
|
||||
{:ok, post2} = CommonAPI.post(luigi, %{status: "itsa me... luigi"})
|
||||
{:ok, post3} = CommonAPI.post(wario, %{status: "WHA-HA-HA!"})
|
||||
{:ok, _job} = Instance.delete("mushroom.kingdom")
|
||||
|
||||
{:ok, job} = Instance.delete_users_and_activities("mushroom.kingdom")
|
||||
:ok = ObanHelpers.perform(job)
|
||||
assert_enqueued(
|
||||
worker: Pleroma.Workers.DeleteWorker,
|
||||
args: %{"op" => "delete_instance", "host" => "mushroom.kingdom"}
|
||||
)
|
||||
end
|
||||
|
||||
[mario, luigi, peach, wario] = Repo.reload(users)
|
||||
describe "check_unreachable/1" do
|
||||
test "schedules a ReachabilityWorker job for the given domain" do
|
||||
domain = "test.example.com"
|
||||
|
||||
refute mario.is_active
|
||||
refute luigi.is_active
|
||||
refute peach.is_active
|
||||
refute peach.name == "Peach"
|
||||
# Call check_unreachable
|
||||
assert {:ok, _job} = Instance.check_unreachable(domain)
|
||||
|
||||
assert wario.is_active
|
||||
assert wario.name == "Wario"
|
||||
# Verify that a ReachabilityWorker job was scheduled
|
||||
jobs = all_enqueued(worker: Pleroma.Workers.ReachabilityWorker)
|
||||
assert length(jobs) == 1
|
||||
[job] = jobs
|
||||
assert job.args["domain"] == domain
|
||||
end
|
||||
|
||||
assert [nil, nil, %{}] = Repo.reload([post1, post2, post3])
|
||||
test "handles multiple calls for the same domain (uniqueness enforced)" do
|
||||
domain = "duplicate.example.com"
|
||||
|
||||
assert {:ok, _job1} = Instance.check_unreachable(domain)
|
||||
|
||||
# Second call for the same domain
|
||||
assert {:ok, %Oban.Job{conflict?: true}} = Instance.check_unreachable(domain)
|
||||
|
||||
# Should only have one job due to uniqueness
|
||||
jobs = all_enqueued(worker: Pleroma.Workers.ReachabilityWorker)
|
||||
assert length(jobs) == 1
|
||||
[job] = jobs
|
||||
assert job.args["domain"] == domain
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,74 +6,42 @@ defmodule Pleroma.InstancesTest do
|
|||
alias Pleroma.Instances
|
||||
|
||||
use Pleroma.DataCase
|
||||
|
||||
setup_all do: clear_config([:instance, :federation_reachability_timeout_days], 1)
|
||||
use Oban.Testing, repo: Pleroma.Repo
|
||||
|
||||
describe "reachable?/1" do
|
||||
test "returns `true` for host / url with unknown reachability status" do
|
||||
assert Instances.reachable?("unknown.site")
|
||||
assert Instances.reachable?("http://unknown.site")
|
||||
end
|
||||
|
||||
test "returns `false` for host / url marked unreachable for at least `reachability_datetime_threshold()`" do
|
||||
host = "consistently-unreachable.name"
|
||||
Instances.set_consistently_unreachable(host)
|
||||
|
||||
refute Instances.reachable?(host)
|
||||
refute Instances.reachable?("http://#{host}/path")
|
||||
end
|
||||
|
||||
test "returns `true` for host / url marked unreachable for less than `reachability_datetime_threshold()`" do
|
||||
url = "http://eventually-unreachable.name/path"
|
||||
|
||||
Instances.set_unreachable(url)
|
||||
|
||||
assert Instances.reachable?(url)
|
||||
assert Instances.reachable?(URI.parse(url).host)
|
||||
end
|
||||
|
||||
test "raises FunctionClauseError exception on non-binary input" do
|
||||
assert_raise FunctionClauseError, fn -> Instances.reachable?(nil) end
|
||||
assert_raise FunctionClauseError, fn -> Instances.reachable?(1) end
|
||||
end
|
||||
end
|
||||
|
||||
describe "filter_reachable/1" do
|
||||
setup do
|
||||
host = "consistently-unreachable.name"
|
||||
url1 = "http://eventually-unreachable.com/path"
|
||||
url2 = "http://domain.com/path"
|
||||
unreachable_host = "consistently-unreachable.name"
|
||||
reachable_host = "http://domain.com/path"
|
||||
|
||||
Instances.set_consistently_unreachable(host)
|
||||
Instances.set_unreachable(url1)
|
||||
Instances.set_unreachable(unreachable_host)
|
||||
|
||||
result = Instances.filter_reachable([host, url1, url2, nil])
|
||||
%{result: result, url1: url1, url2: url2}
|
||||
result = Instances.filter_reachable([unreachable_host, reachable_host, nil])
|
||||
%{result: result, reachable_host: reachable_host, unreachable_host: unreachable_host}
|
||||
end
|
||||
|
||||
test "returns a map with keys containing 'not marked consistently unreachable' elements of supplied list",
|
||||
%{result: result, url1: url1, url2: url2} do
|
||||
assert is_map(result)
|
||||
assert Enum.sort([url1, url2]) == result |> Map.keys() |> Enum.sort()
|
||||
test "returns a list of only reachable elements",
|
||||
%{result: result, reachable_host: reachable_host} do
|
||||
assert is_list(result)
|
||||
assert [reachable_host] == result
|
||||
end
|
||||
|
||||
test "returns a map with `unreachable_since` values for keys",
|
||||
%{result: result, url1: url1, url2: url2} do
|
||||
assert is_map(result)
|
||||
assert %NaiveDateTime{} = result[url1]
|
||||
assert is_nil(result[url2])
|
||||
end
|
||||
|
||||
test "returns an empty map for empty list or list containing no hosts / url" do
|
||||
assert %{} == Instances.filter_reachable([])
|
||||
assert %{} == Instances.filter_reachable([nil])
|
||||
test "returns an empty list when provided no data" do
|
||||
assert [] == Instances.filter_reachable([])
|
||||
assert [] == Instances.filter_reachable([nil])
|
||||
end
|
||||
end
|
||||
|
||||
describe "set_reachable/1" do
|
||||
test "sets unreachable url or host reachable" do
|
||||
host = "domain.com"
|
||||
Instances.set_consistently_unreachable(host)
|
||||
Instances.set_unreachable(host)
|
||||
refute Instances.reachable?(host)
|
||||
|
||||
Instances.set_reachable(host)
|
||||
|
|
@ -103,22 +71,68 @@ defmodule Pleroma.InstancesTest do
|
|||
end
|
||||
end
|
||||
|
||||
describe "set_consistently_unreachable/1" do
|
||||
test "sets reachable url or host unreachable" do
|
||||
url = "http://domain.com?q="
|
||||
assert Instances.reachable?(url)
|
||||
describe "check_all_unreachable/0" do
|
||||
test "schedules ReachabilityWorker jobs for all unreachable instances" do
|
||||
domain1 = "unreachable1.example.com"
|
||||
domain2 = "unreachable2.example.com"
|
||||
domain3 = "unreachable3.example.com"
|
||||
|
||||
Instances.set_consistently_unreachable(url)
|
||||
refute Instances.reachable?(url)
|
||||
Instances.set_unreachable(domain1)
|
||||
Instances.set_unreachable(domain2)
|
||||
Instances.set_unreachable(domain3)
|
||||
|
||||
Instances.check_all_unreachable()
|
||||
|
||||
# Verify that ReachabilityWorker jobs were scheduled for all unreachable domains
|
||||
jobs = all_enqueued(worker: Pleroma.Workers.ReachabilityWorker)
|
||||
assert length(jobs) == 3
|
||||
|
||||
domains = Enum.map(jobs, & &1.args["domain"])
|
||||
assert domain1 in domains
|
||||
assert domain2 in domains
|
||||
assert domain3 in domains
|
||||
end
|
||||
|
||||
test "keeps unreachable url or host unreachable" do
|
||||
host = "site.name"
|
||||
Instances.set_consistently_unreachable(host)
|
||||
refute Instances.reachable?(host)
|
||||
test "does not schedule jobs for reachable instances" do
|
||||
unreachable_domain = "unreachable.example.com"
|
||||
reachable_domain = "reachable.example.com"
|
||||
|
||||
Instances.set_consistently_unreachable(host)
|
||||
refute Instances.reachable?(host)
|
||||
Instances.set_unreachable(unreachable_domain)
|
||||
Instances.set_reachable(reachable_domain)
|
||||
|
||||
Instances.check_all_unreachable()
|
||||
|
||||
# Verify that only one job was scheduled (for the unreachable domain)
|
||||
jobs = all_enqueued(worker: Pleroma.Workers.ReachabilityWorker)
|
||||
assert length(jobs) == 1
|
||||
[job] = jobs
|
||||
assert job.args["domain"] == unreachable_domain
|
||||
end
|
||||
end
|
||||
|
||||
test "delete_all_unreachable/0 schedules DeleteWorker jobs for all unreachable instances" do
|
||||
domain1 = "unreachable1.example.com"
|
||||
domain2 = "unreachable2.example.com"
|
||||
domain3 = "unreachable3.example.com"
|
||||
|
||||
Instances.set_unreachable(domain1)
|
||||
Instances.set_unreachable(domain2)
|
||||
Instances.set_unreachable(domain3)
|
||||
|
||||
Instances.delete_all_unreachable()
|
||||
|
||||
# Verify that DeleteWorker jobs were scheduled for all unreachable domains
|
||||
jobs = all_enqueued(worker: Pleroma.Workers.DeleteWorker)
|
||||
assert length(jobs) == 3
|
||||
|
||||
domains = Enum.map(jobs, & &1.args["host"])
|
||||
assert domain1 in domains
|
||||
assert domain2 in domains
|
||||
assert domain3 in domains
|
||||
|
||||
# Verify all jobs are delete_instance operations
|
||||
Enum.each(jobs, fn job ->
|
||||
assert job.args["op"] == "delete_instance"
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -308,4 +308,37 @@ defmodule Pleroma.ModerationLogTest do
|
|||
assert log.data["message"] == "@#{moderator.nickname} deleted status ##{note.id}"
|
||||
end
|
||||
end
|
||||
|
||||
describe "get_log_entry_message/1" do
|
||||
setup do
|
||||
moderator = insert(:user, is_moderator: true)
|
||||
[moderator: moderator]
|
||||
end
|
||||
|
||||
test "handles unknown action types gracefully", %{moderator: moderator} do
|
||||
log_entry = %ModerationLog{
|
||||
data: %{
|
||||
"actor" => %{"nickname" => moderator.nickname},
|
||||
"action" => "unknown_action",
|
||||
"some_data" => "test_value"
|
||||
}
|
||||
}
|
||||
|
||||
assert ModerationLog.get_log_entry_message(log_entry) =~ moderator.nickname
|
||||
assert ModerationLog.get_log_entry_message(log_entry) =~ "unknown_action"
|
||||
end
|
||||
|
||||
test "handles malformed log entries gracefully" do
|
||||
log_entry = %ModerationLog{
|
||||
data: %{
|
||||
"action" => "force_password_reset"
|
||||
# Missing "actor" and "subject" fields
|
||||
}
|
||||
}
|
||||
|
||||
message = ModerationLog.get_log_entry_message(log_entry)
|
||||
assert is_binary(message)
|
||||
assert message =~ "force_password_reset"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ defmodule Pleroma.Object.FetcherTest do
|
|||
use Pleroma.DataCase
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Instances
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Object.Fetcher
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidator
|
||||
|
|
@ -250,17 +249,6 @@ defmodule Pleroma.Object.FetcherTest do
|
|||
result = Fetcher.fetch_object_from_id("https://example.com/objects/no-content-type")
|
||||
assert {:fetch, {:error, nil}} = result
|
||||
end
|
||||
|
||||
test "it resets instance reachability on successful fetch" do
|
||||
id = "http://mastodon.example.org/@admin/99541947525187367"
|
||||
Instances.set_consistently_unreachable(id)
|
||||
refute Instances.reachable?(id)
|
||||
|
||||
{:ok, _object} =
|
||||
Fetcher.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367")
|
||||
|
||||
assert Instances.reachable?(id)
|
||||
end
|
||||
end
|
||||
|
||||
describe "implementation quirks" do
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ defmodule Pleroma.ObjectTest do
|
|||
|
||||
uploads_dir = Pleroma.Config.get!([Pleroma.Uploaders.Local, :uploads])
|
||||
|
||||
File.mkdir_p!(uploads_dir)
|
||||
Pleroma.Backports.mkdir_p!(uploads_dir)
|
||||
|
||||
file = %Plug.Upload{
|
||||
content_type: "image/jpeg",
|
||||
|
|
|
|||
|
|
@ -1,42 +0,0 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.OTPVersionTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Pleroma.OTPVersion
|
||||
|
||||
describe "check/1" do
|
||||
test "22.4" do
|
||||
assert OTPVersion.get_version_from_files(["test/fixtures/warnings/otp_version/22.4"]) ==
|
||||
"22.4"
|
||||
end
|
||||
|
||||
test "22.1" do
|
||||
assert OTPVersion.get_version_from_files(["test/fixtures/warnings/otp_version/22.1"]) ==
|
||||
"22.1"
|
||||
end
|
||||
|
||||
test "21.1" do
|
||||
assert OTPVersion.get_version_from_files(["test/fixtures/warnings/otp_version/21.1"]) ==
|
||||
"21.1"
|
||||
end
|
||||
|
||||
test "23.0" do
|
||||
assert OTPVersion.get_version_from_files(["test/fixtures/warnings/otp_version/23.0"]) ==
|
||||
"23.0"
|
||||
end
|
||||
|
||||
test "with nonexistent file" do
|
||||
assert OTPVersion.get_version_from_files([
|
||||
"test/fixtures/warnings/otp_version/non-exising",
|
||||
"test/fixtures/warnings/otp_version/22.4"
|
||||
]) == "22.4"
|
||||
end
|
||||
|
||||
test "empty paths" do
|
||||
assert OTPVersion.get_version_from_files([]) == nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -395,4 +395,40 @@ defmodule Pleroma.ReverseProxyTest do
|
|||
assert Conn.get_resp_header(conn, "content-type") == ["application/octet-stream"]
|
||||
end
|
||||
end
|
||||
|
||||
# Hackey is used for Reverse Proxy when Hackney or Finch is the Tesla Adapter
|
||||
# Gun is able to proxy through Tesla, so it does not need testing as the
|
||||
# test cases in the Pleroma.HTTPTest module are sufficient
|
||||
describe "Hackney URL encoding:" do
|
||||
setup do
|
||||
ClientMock
|
||||
|> expect(:request, fn :get,
|
||||
"https://example.com/emoji/Pack%201/koronebless.png?foo=bar+baz",
|
||||
_headers,
|
||||
_body,
|
||||
_opts ->
|
||||
{:ok, 200, [{"content-type", "image/png"}], "It works!"}
|
||||
end)
|
||||
|> stub(:stream_body, fn _ -> :done end)
|
||||
|> stub(:close, fn _ -> :ok end)
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
test "properly encodes URLs with spaces", %{conn: conn} do
|
||||
url_with_space = "https://example.com/emoji/Pack 1/koronebless.png?foo=bar baz"
|
||||
|
||||
result = ReverseProxy.call(conn, url_with_space)
|
||||
|
||||
assert result.status == 200
|
||||
end
|
||||
|
||||
test "properly encoded URL should not be altered", %{conn: conn} do
|
||||
properly_encoded_url = "https://example.com/emoji/Pack%201/koronebless.png?foo=bar+baz"
|
||||
|
||||
result = ReverseProxy.call(conn, properly_encoded_url)
|
||||
|
||||
assert result.status == 200
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@ defmodule Pleroma.SafeZipTest do
|
|||
|
||||
setup do
|
||||
# Ensure tmp directory exists
|
||||
File.mkdir_p!(@tmp_dir)
|
||||
Pleroma.Backports.mkdir_p!(@tmp_dir)
|
||||
|
||||
on_exit(fn ->
|
||||
# Clean up any files created during tests
|
||||
File.rm_rf!(@tmp_dir)
|
||||
File.mkdir_p!(@tmp_dir)
|
||||
Pleroma.Backports.mkdir_p!(@tmp_dir)
|
||||
end)
|
||||
|
||||
:ok
|
||||
|
|
@ -89,7 +89,7 @@ defmodule Pleroma.SafeZipTest do
|
|||
# For this test, we'll manually check if the file exists in the archive
|
||||
# by extracting it and verifying it exists
|
||||
extract_dir = Path.join(@tmp_dir, "extract_check")
|
||||
File.mkdir_p!(extract_dir)
|
||||
Pleroma.Backports.mkdir_p!(extract_dir)
|
||||
{:ok, files} = SafeZip.unzip_file(zip_path, extract_dir)
|
||||
|
||||
# Verify the root file was extracted
|
||||
|
|
@ -145,7 +145,7 @@ defmodule Pleroma.SafeZipTest do
|
|||
test "can create zip with directories" do
|
||||
# Create a directory structure
|
||||
dir_path = Path.join(@tmp_dir, "test_dir")
|
||||
File.mkdir_p!(dir_path)
|
||||
Pleroma.Backports.mkdir_p!(dir_path)
|
||||
|
||||
file_in_dir_path = Path.join(dir_path, "file_in_dir.txt")
|
||||
File.write!(file_in_dir_path, "file in directory")
|
||||
|
|
@ -428,7 +428,7 @@ defmodule Pleroma.SafeZipTest do
|
|||
|
||||
# Create a directory and a file in it
|
||||
dir_path = Path.join(@tmp_dir, "file_in_dir")
|
||||
File.mkdir_p!(dir_path)
|
||||
Pleroma.Backports.mkdir_p!(dir_path)
|
||||
|
||||
file_in_dir_path = Path.join(dir_path, "test_file.txt")
|
||||
File.write!(file_in_dir_path, "file in directory content")
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ defmodule Pleroma.Search.QdrantSearchTest do
|
|||
})
|
||||
|
||||
Config
|
||||
|> expect(:get, 3, fn
|
||||
|> expect(:get, 4, fn
|
||||
[Pleroma.Search, :module], nil ->
|
||||
QdrantSearch
|
||||
|
||||
|
|
@ -93,7 +93,7 @@ defmodule Pleroma.Search.QdrantSearchTest do
|
|||
})
|
||||
|
||||
Config
|
||||
|> expect(:get, 3, fn
|
||||
|> expect(:get, 4, fn
|
||||
[Pleroma.Search, :module], nil ->
|
||||
QdrantSearch
|
||||
|
||||
|
|
@ -158,7 +158,7 @@ defmodule Pleroma.Search.QdrantSearchTest do
|
|||
end)
|
||||
|
||||
Config
|
||||
|> expect(:get, 6, fn
|
||||
|> expect(:get, 7, fn
|
||||
[Pleroma.Search, :module], nil ->
|
||||
QdrantSearch
|
||||
|
||||
|
|
|
|||
|
|
@ -2669,8 +2669,12 @@ defmodule Pleroma.UserTest do
|
|||
|
||||
assert {:ok, user} = User.update_last_active_at(user)
|
||||
|
||||
assert user.last_active_at >= test_started_at
|
||||
assert user.last_active_at <= NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
|
||||
assert NaiveDateTime.compare(user.last_active_at, test_started_at) in [:gt, :eq]
|
||||
|
||||
assert NaiveDateTime.compare(
|
||||
user.last_active_at,
|
||||
NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
|
||||
) in [:lt, :eq]
|
||||
|
||||
last_active_at =
|
||||
NaiveDateTime.utc_now()
|
||||
|
|
@ -2682,10 +2686,15 @@ defmodule Pleroma.UserTest do
|
|||
|> cast(%{last_active_at: last_active_at}, [:last_active_at])
|
||||
|> User.update_and_set_cache()
|
||||
|
||||
assert user.last_active_at == last_active_at
|
||||
assert NaiveDateTime.compare(user.last_active_at, last_active_at) == :eq
|
||||
|
||||
assert {:ok, user} = User.update_last_active_at(user)
|
||||
assert user.last_active_at >= test_started_at
|
||||
assert user.last_active_at <= NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
|
||||
assert NaiveDateTime.compare(user.last_active_at, test_started_at) in [:gt, :eq]
|
||||
|
||||
assert NaiveDateTime.compare(
|
||||
user.last_active_at,
|
||||
NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
|
||||
) in [:lt, :eq]
|
||||
end
|
||||
|
||||
test "active_user_count/1" do
|
||||
|
|
@ -2783,6 +2792,15 @@ defmodule Pleroma.UserTest do
|
|||
assert user_updated.also_known_as |> length() == 1
|
||||
assert user2.ap_id in user_updated.also_known_as
|
||||
end
|
||||
|
||||
test "should tolerate non-http(s) aliases" do
|
||||
user =
|
||||
insert(:user, %{
|
||||
also_known_as: ["at://did:plc:xgvzy7ni6ig6ievcbls5jaxe"]
|
||||
})
|
||||
|
||||
assert "at://did:plc:xgvzy7ni6ig6ievcbls5jaxe" in user.also_known_as
|
||||
end
|
||||
end
|
||||
|
||||
describe "alias_users/1" do
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
|||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Delivery
|
||||
alias Pleroma.Instances
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Tests.ObanHelpers
|
||||
alias Pleroma.User
|
||||
|
|
@ -601,23 +600,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
|||
assert Activity.get_by_ap_id(data["id"])
|
||||
end
|
||||
|
||||
test "it clears `unreachable` federation status of the sender", %{conn: conn} do
|
||||
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
|
||||
|
||||
sender_url = data["actor"]
|
||||
Instances.set_consistently_unreachable(sender_url)
|
||||
refute Instances.reachable?(sender_url)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> assign(:valid_signature, true)
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> post("/inbox", data)
|
||||
|
||||
assert "ok" == json_response(conn, 200)
|
||||
assert Instances.reachable?(sender_url)
|
||||
end
|
||||
|
||||
test "accept follow activity", %{conn: conn} do
|
||||
clear_config([:instance, :federating], true)
|
||||
relay = Relay.get_actor()
|
||||
|
|
@ -941,23 +923,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
|||
assert Activity.get_by_ap_id(data["id"])
|
||||
end
|
||||
|
||||
test "it rejects an invalid incoming activity", %{conn: conn, data: data} do
|
||||
user = insert(:user, is_active: false)
|
||||
|
||||
data =
|
||||
data
|
||||
|> Map.put("bcc", [user.ap_id])
|
||||
|> Kernel.put_in(["object", "bcc"], [user.ap_id])
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> assign(:valid_signature, true)
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> post("/users/#{user.nickname}/inbox", data)
|
||||
|
||||
assert "Invalid request." == json_response(conn, 400)
|
||||
end
|
||||
|
||||
test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do
|
||||
user = insert(:user)
|
||||
|
||||
|
|
@ -1108,24 +1073,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
|||
assert response(conn, 200) =~ note_object.data["content"]
|
||||
end
|
||||
|
||||
test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
|
||||
user = insert(:user)
|
||||
data = Map.put(data, "bcc", [user.ap_id])
|
||||
|
||||
sender_host = URI.parse(data["actor"]).host
|
||||
Instances.set_consistently_unreachable(sender_host)
|
||||
refute Instances.reachable?(sender_host)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> assign(:valid_signature, true)
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> post("/users/#{user.nickname}/inbox", data)
|
||||
|
||||
assert "ok" == json_response(conn, 200)
|
||||
assert Instances.reachable?(sender_host)
|
||||
end
|
||||
|
||||
test "it removes all follower collections but actor's", %{conn: conn} do
|
||||
[actor, recipient] = insert_pair(:user)
|
||||
|
||||
|
|
@ -1205,9 +1152,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
|||
}
|
||||
],
|
||||
"actor" => actor.ap_id,
|
||||
"cc" => [
|
||||
reported_user.ap_id
|
||||
],
|
||||
# CC and TO might either not exist at all, or be empty. We should be able to handle either.
|
||||
# "cc" => [],
|
||||
"content" => "test",
|
||||
"context" => "context",
|
||||
"id" => "http://#{remote_domain}/activities/02be56cf-35e3-46b4-b2c6-47ae08dfee9e",
|
||||
|
|
@ -1341,6 +1287,50 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
|||
ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
|
||||
assert Activity.get_by_ap_id(data["id"])
|
||||
end
|
||||
|
||||
test "it returns an error when receiving an activity sent to a deactivated user", %{
|
||||
conn: conn,
|
||||
data: data
|
||||
} do
|
||||
user = insert(:user)
|
||||
{:ok, _} = User.set_activation(user, false)
|
||||
|
||||
data =
|
||||
data
|
||||
|> Map.put("bcc", [user.ap_id])
|
||||
|> Kernel.put_in(["object", "bcc"], [user.ap_id])
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> assign(:valid_signature, true)
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> post("/users/#{user.nickname}/inbox", data)
|
||||
|
||||
assert "User deactivated" == json_response(conn, 404)
|
||||
end
|
||||
|
||||
test "it returns an error when receiving an activity sent from a deactivated user", %{
|
||||
conn: conn,
|
||||
data: data
|
||||
} do
|
||||
sender = insert(:user)
|
||||
user = insert(:user)
|
||||
{:ok, _} = User.set_activation(sender, false)
|
||||
|
||||
data =
|
||||
data
|
||||
|> Map.put("bcc", [user.ap_id])
|
||||
|> Map.put("actor", sender.ap_id)
|
||||
|> Kernel.put_in(["object", "bcc"], [user.ap_id])
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> assign(:valid_signature, true)
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> post("/users/#{user.nickname}/inbox", data)
|
||||
|
||||
assert "Sender deactivated" == json_response(conn, 404)
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /users/:nickname/outbox" do
|
||||
|
|
|
|||
|
|
@ -1270,6 +1270,16 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
|
|||
assert activity == expected_activity
|
||||
end
|
||||
|
||||
test "includes only reblogs on request" do
|
||||
user = insert(:user)
|
||||
{:ok, _} = ActivityBuilder.insert(%{"type" => "Create"}, %{:user => user})
|
||||
{:ok, expected_activity} = ActivityBuilder.insert(%{"type" => "Announce"}, %{:user => user})
|
||||
|
||||
[activity] = ActivityPub.fetch_user_activities(user, nil, %{only_reblogs: true})
|
||||
|
||||
assert activity == expected_activity
|
||||
end
|
||||
|
||||
describe "irreversible filters" do
|
||||
setup do
|
||||
user = insert(:user)
|
||||
|
|
@ -1681,32 +1691,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
|
|||
} = activity
|
||||
end
|
||||
|
||||
test_with_mock "strips status data from Flag, before federating it",
|
||||
%{
|
||||
reporter: reporter,
|
||||
context: context,
|
||||
target_account: target_account,
|
||||
reported_activity: reported_activity,
|
||||
object_ap_id: object_ap_id,
|
||||
content: content
|
||||
},
|
||||
Utils,
|
||||
[:passthrough],
|
||||
[] do
|
||||
{:ok, activity} =
|
||||
ActivityPub.flag(%{
|
||||
actor: reporter,
|
||||
context: context,
|
||||
account: target_account,
|
||||
statuses: [reported_activity],
|
||||
content: content
|
||||
})
|
||||
|
||||
new_data = put_in(activity.data, ["object"], [target_account.ap_id, object_ap_id])
|
||||
|
||||
assert_called(Utils.maybe_federate(%{activity | data: new_data}))
|
||||
end
|
||||
|
||||
test_with_mock "reverts on error",
|
||||
%{
|
||||
reporter: reporter,
|
||||
|
|
|
|||
139
test/pleroma/web/activity_pub/mrf/quiet_reply_test.exs
Normal file
139
test/pleroma/web/activity_pub/mrf/quiet_reply_test.exs
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.MRF.QuietReplyTest do
|
||||
use Pleroma.DataCase
|
||||
import Pleroma.Factory
|
||||
|
||||
require Pleroma.Constants
|
||||
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Web.ActivityPub.MRF.QuietReply
|
||||
alias Pleroma.Web.CommonAPI
|
||||
|
||||
test "replying to public post is forced to be quiet" do
|
||||
batman = insert(:user, nickname: "batman")
|
||||
robin = insert(:user, nickname: "robin")
|
||||
|
||||
{:ok, post} = CommonAPI.post(batman, %{status: "To the Batmobile!"})
|
||||
|
||||
reply = %{
|
||||
"type" => "Create",
|
||||
"actor" => robin.ap_id,
|
||||
"to" => [
|
||||
batman.ap_id,
|
||||
Pleroma.Constants.as_public()
|
||||
],
|
||||
"cc" => [robin.follower_address],
|
||||
"object" => %{
|
||||
"type" => "Note",
|
||||
"actor" => robin.ap_id,
|
||||
"content" => "@batman Wait up, I forgot my spandex!",
|
||||
"to" => [
|
||||
batman.ap_id,
|
||||
Pleroma.Constants.as_public()
|
||||
],
|
||||
"cc" => [robin.follower_address],
|
||||
"inReplyTo" => Object.normalize(post).data["id"]
|
||||
}
|
||||
}
|
||||
|
||||
assert {:ok, filtered} = QuietReply.filter(reply)
|
||||
|
||||
assert batman.ap_id in filtered["to"]
|
||||
assert batman.ap_id in filtered["object"]["to"]
|
||||
assert robin.follower_address in filtered["to"]
|
||||
assert robin.follower_address in filtered["object"]["to"]
|
||||
assert Pleroma.Constants.as_public() in filtered["cc"]
|
||||
assert Pleroma.Constants.as_public() in filtered["object"]["cc"]
|
||||
end
|
||||
|
||||
test "replying to unlisted post is unmodified" do
|
||||
batman = insert(:user, nickname: "batman")
|
||||
robin = insert(:user, nickname: "robin")
|
||||
|
||||
{:ok, post} = CommonAPI.post(batman, %{status: "To the Batmobile!", visibility: "private"})
|
||||
|
||||
reply = %{
|
||||
"type" => "Create",
|
||||
"actor" => robin.ap_id,
|
||||
"to" => [batman.ap_id],
|
||||
"cc" => [],
|
||||
"object" => %{
|
||||
"type" => "Note",
|
||||
"actor" => robin.ap_id,
|
||||
"content" => "@batman Wait up, I forgot my spandex!",
|
||||
"to" => [batman.ap_id],
|
||||
"cc" => [],
|
||||
"inReplyTo" => Object.normalize(post).data["id"]
|
||||
}
|
||||
}
|
||||
|
||||
assert {:ok, filtered} = QuietReply.filter(reply)
|
||||
|
||||
assert match?(^filtered, reply)
|
||||
end
|
||||
|
||||
test "replying direct is unmodified" do
|
||||
batman = insert(:user, nickname: "batman")
|
||||
robin = insert(:user, nickname: "robin")
|
||||
|
||||
{:ok, post} = CommonAPI.post(batman, %{status: "To the Batmobile!"})
|
||||
|
||||
reply = %{
|
||||
"type" => "Create",
|
||||
"actor" => robin.ap_id,
|
||||
"to" => [batman.ap_id],
|
||||
"cc" => [],
|
||||
"object" => %{
|
||||
"type" => "Note",
|
||||
"actor" => robin.ap_id,
|
||||
"content" => "@batman Wait up, I forgot my spandex!",
|
||||
"to" => [batman.ap_id],
|
||||
"cc" => [],
|
||||
"inReplyTo" => Object.normalize(post).data["id"]
|
||||
}
|
||||
}
|
||||
|
||||
assert {:ok, filtered} = QuietReply.filter(reply)
|
||||
|
||||
assert match?(^filtered, reply)
|
||||
end
|
||||
|
||||
test "replying followers-only is unmodified" do
|
||||
batman = insert(:user, nickname: "batman")
|
||||
robin = insert(:user, nickname: "robin")
|
||||
|
||||
{:ok, post} = CommonAPI.post(batman, %{status: "To the Batmobile!"})
|
||||
|
||||
reply = %{
|
||||
"type" => "Create",
|
||||
"actor" => robin.ap_id,
|
||||
"to" => [batman.ap_id, robin.follower_address],
|
||||
"cc" => [],
|
||||
"object" => %{
|
||||
"type" => "Note",
|
||||
"actor" => robin.ap_id,
|
||||
"content" => "@batman Wait up, I forgot my spandex!",
|
||||
"to" => [batman.ap_id, robin.follower_address],
|
||||
"cc" => [],
|
||||
"inReplyTo" => Object.normalize(post).data["id"]
|
||||
}
|
||||
}
|
||||
|
||||
assert {:ok, filtered} = QuietReply.filter(reply)
|
||||
|
||||
assert match?(^filtered, reply)
|
||||
end
|
||||
|
||||
test "non-reply posts are unmodified" do
|
||||
batman = insert(:user, nickname: "batman")
|
||||
|
||||
{:ok, post} = CommonAPI.post(batman, %{status: "To the Batmobile!"})
|
||||
|
||||
assert {:ok, filtered} = QuietReply.filter(post)
|
||||
|
||||
assert match?(^filtered, post)
|
||||
end
|
||||
end
|
||||
|
|
@ -6,16 +6,15 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
|
|||
use Oban.Testing, repo: Pleroma.Repo
|
||||
use Pleroma.Web.ConnCase
|
||||
|
||||
import ExUnit.CaptureLog
|
||||
import Pleroma.Factory
|
||||
import Tesla.Mock
|
||||
import Mock
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Instances
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Tests.ObanHelpers
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Publisher
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.CommonAPI
|
||||
|
||||
@as_public "https://www.w3.org/ns/activitystreams#Public"
|
||||
|
|
@ -167,122 +166,10 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
|
|||
})
|
||||
|> Publisher.publish_one()
|
||||
end
|
||||
|
||||
test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is set",
|
||||
Instances,
|
||||
[:passthrough],
|
||||
[] do
|
||||
_actor = insert(:user)
|
||||
inbox = "http://200.site/users/nick1/inbox"
|
||||
activity = insert(:note_activity)
|
||||
|
||||
assert {:ok, _} =
|
||||
Publisher.prepare_one(%{
|
||||
inbox: inbox,
|
||||
activity_id: activity.id,
|
||||
unreachable_since: NaiveDateTime.utc_now() |> NaiveDateTime.to_string()
|
||||
})
|
||||
|> Publisher.publish_one()
|
||||
|
||||
assert called(Instances.set_reachable(inbox))
|
||||
end
|
||||
|
||||
test_with_mock "does NOT call `Instances.set_reachable` on successful federation if `unreachable_since` is nil",
|
||||
Instances,
|
||||
[:passthrough],
|
||||
[] do
|
||||
_actor = insert(:user)
|
||||
inbox = "http://200.site/users/nick1/inbox"
|
||||
activity = insert(:note_activity)
|
||||
|
||||
assert {:ok, _} =
|
||||
Publisher.prepare_one(%{
|
||||
inbox: inbox,
|
||||
activity_id: activity.id,
|
||||
unreachable_since: nil
|
||||
})
|
||||
|> Publisher.publish_one()
|
||||
|
||||
refute called(Instances.set_reachable(inbox))
|
||||
end
|
||||
|
||||
test_with_mock "calls `Instances.set_unreachable` on target inbox on non-2xx HTTP response code",
|
||||
Instances,
|
||||
[:passthrough],
|
||||
[] do
|
||||
_actor = insert(:user)
|
||||
inbox = "http://404.site/users/nick1/inbox"
|
||||
activity = insert(:note_activity)
|
||||
|
||||
assert {:cancel, _} =
|
||||
Publisher.prepare_one(%{inbox: inbox, activity_id: activity.id})
|
||||
|> Publisher.publish_one()
|
||||
|
||||
assert called(Instances.set_unreachable(inbox))
|
||||
end
|
||||
|
||||
test_with_mock "it calls `Instances.set_unreachable` on target inbox on request error of any kind",
|
||||
Instances,
|
||||
[:passthrough],
|
||||
[] do
|
||||
_actor = insert(:user)
|
||||
inbox = "http://connrefused.site/users/nick1/inbox"
|
||||
activity = insert(:note_activity)
|
||||
|
||||
assert capture_log(fn ->
|
||||
assert {:error, _} =
|
||||
Publisher.prepare_one(%{
|
||||
inbox: inbox,
|
||||
activity_id: activity.id
|
||||
})
|
||||
|> Publisher.publish_one()
|
||||
end) =~ "connrefused"
|
||||
|
||||
assert called(Instances.set_unreachable(inbox))
|
||||
end
|
||||
|
||||
test_with_mock "does NOT call `Instances.set_unreachable` if target is reachable",
|
||||
Instances,
|
||||
[:passthrough],
|
||||
[] do
|
||||
_actor = insert(:user)
|
||||
inbox = "http://200.site/users/nick1/inbox"
|
||||
activity = insert(:note_activity)
|
||||
|
||||
assert {:ok, _} =
|
||||
Publisher.prepare_one(%{inbox: inbox, activity_id: activity.id})
|
||||
|> Publisher.publish_one()
|
||||
|
||||
refute called(Instances.set_unreachable(inbox))
|
||||
end
|
||||
|
||||
test_with_mock "does NOT call `Instances.set_unreachable` if target instance has non-nil `unreachable_since`",
|
||||
Instances,
|
||||
[:passthrough],
|
||||
[] do
|
||||
_actor = insert(:user)
|
||||
inbox = "http://connrefused.site/users/nick1/inbox"
|
||||
activity = insert(:note_activity)
|
||||
|
||||
assert capture_log(fn ->
|
||||
assert {:error, _} =
|
||||
Publisher.prepare_one(%{
|
||||
inbox: inbox,
|
||||
activity_id: activity.id,
|
||||
unreachable_since: NaiveDateTime.utc_now() |> NaiveDateTime.to_string()
|
||||
})
|
||||
|> Publisher.publish_one()
|
||||
end) =~ "connrefused"
|
||||
|
||||
refute called(Instances.set_unreachable(inbox))
|
||||
end
|
||||
end
|
||||
|
||||
describe "publish/2" do
|
||||
test_with_mock "doesn't publish a non-public activity to quarantined instances.",
|
||||
Pleroma.Web.ActivityPub.Publisher,
|
||||
[:passthrough],
|
||||
[] do
|
||||
test "doesn't publish a non-public activity to quarantined instances." do
|
||||
Config.put([:instance, :quarantined_instances], [{"domain.com", "some reason"}])
|
||||
|
||||
follower =
|
||||
|
|
@ -317,10 +204,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
|
|||
)
|
||||
end
|
||||
|
||||
test_with_mock "Publishes a non-public activity to non-quarantined instances.",
|
||||
Pleroma.Web.ActivityPub.Publisher,
|
||||
[:passthrough],
|
||||
[] do
|
||||
test "Publishes a non-public activity to non-quarantined instances." do
|
||||
Config.put([:instance, :quarantined_instances], [{"somedomain.com", "some reason"}])
|
||||
|
||||
follower =
|
||||
|
|
@ -356,10 +240,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
|
|||
)
|
||||
end
|
||||
|
||||
test_with_mock "Publishes to directly addressed actors with higher priority.",
|
||||
Pleroma.Web.ActivityPub.Publisher,
|
||||
[:passthrough],
|
||||
[] do
|
||||
test "Publishes to directly addressed actors with higher priority." do
|
||||
note_activity = insert(:direct_note_activity)
|
||||
|
||||
actor = Pleroma.User.get_by_ap_id(note_activity.data["actor"])
|
||||
|
|
@ -368,21 +249,58 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
|
|||
|
||||
assert res == :ok
|
||||
|
||||
assert called(
|
||||
Publisher.enqueue_one(
|
||||
%{
|
||||
inbox: :_,
|
||||
activity_id: note_activity.id
|
||||
},
|
||||
priority: 0
|
||||
)
|
||||
)
|
||||
assert_enqueued(
|
||||
worker: "Pleroma.Workers.PublisherWorker",
|
||||
args: %{
|
||||
"op" => "publish_one",
|
||||
"params" => %{"activity_id" => note_activity.id}
|
||||
},
|
||||
priority: 0
|
||||
)
|
||||
end
|
||||
|
||||
test_with_mock "publishes an activity with BCC to all relevant peers.",
|
||||
Pleroma.Web.ActivityPub.Publisher,
|
||||
[:passthrough],
|
||||
[] do
|
||||
test "Publishes with the new actor if prepare_outgoing changes the actor." do
|
||||
mock(fn
|
||||
%{method: :post, url: "https://domain.com/users/nick1/inbox", body: body} ->
|
||||
{:ok, %Tesla.Env{status: 200, body: body}}
|
||||
end)
|
||||
|
||||
other_user =
|
||||
insert(:user, %{
|
||||
local: false,
|
||||
inbox: "https://domain.com/users/nick1/inbox"
|
||||
})
|
||||
|
||||
actor = insert(:user)
|
||||
replaced_actor = insert(:user)
|
||||
|
||||
note_activity =
|
||||
insert(:note_activity,
|
||||
user: actor,
|
||||
data_attrs: %{"to" => [other_user.ap_id]}
|
||||
)
|
||||
|
||||
Pleroma.Web.ActivityPub.TransmogrifierMock
|
||||
|> Mox.expect(:prepare_outgoing, fn data ->
|
||||
{:ok, Map.put(data, "actor", replaced_actor.ap_id)}
|
||||
end)
|
||||
|
||||
prepared =
|
||||
Publisher.prepare_one(%{
|
||||
inbox: "https://domain.com/users/nick1/inbox",
|
||||
activity_id: note_activity.id,
|
||||
cc: ["https://domain.com/users/nick2/inbox"]
|
||||
})
|
||||
|
||||
{:ok, decoded} = Jason.decode(prepared.json)
|
||||
assert decoded["actor"] == replaced_actor.ap_id
|
||||
|
||||
{:ok, published} = Publisher.publish_one(prepared)
|
||||
sent_activity = Jason.decode!(published.body)
|
||||
assert sent_activity["actor"] == replaced_actor.ap_id
|
||||
end
|
||||
|
||||
test "publishes an activity with BCC to all relevant peers." do
|
||||
follower =
|
||||
insert(:user, %{
|
||||
local: false,
|
||||
|
|
@ -414,10 +332,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
|
|||
)
|
||||
end
|
||||
|
||||
test_with_mock "publishes a delete activity to peers who signed fetch requests to the create acitvity/object.",
|
||||
Pleroma.Web.ActivityPub.Publisher,
|
||||
[:passthrough],
|
||||
[] do
|
||||
test "publishes a delete activity to peers who signed fetch requests to the create acitvity/object." do
|
||||
fetcher =
|
||||
insert(:user,
|
||||
local: false,
|
||||
|
|
@ -520,4 +435,144 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
|
|||
|
||||
assert decoded["cc"] == []
|
||||
end
|
||||
|
||||
test "unlisted activities retain public address in cc" do
|
||||
user = insert(:user)
|
||||
|
||||
# simulate unlistd activity by only having
|
||||
# public address in cc
|
||||
activity =
|
||||
insert(:note_activity,
|
||||
user: user,
|
||||
data_attrs: %{
|
||||
"cc" => [@as_public],
|
||||
"to" => [user.follower_address]
|
||||
}
|
||||
)
|
||||
|
||||
assert @as_public in activity.data["cc"]
|
||||
|
||||
prepared =
|
||||
Publisher.prepare_one(%{
|
||||
inbox: "https://remote.instance/users/someone/inbox",
|
||||
activity_id: activity.id
|
||||
})
|
||||
|
||||
{:ok, decoded} = Jason.decode(prepared.json)
|
||||
|
||||
assert @as_public in decoded["cc"]
|
||||
|
||||
# maybe we also have another inbox in cc
|
||||
# during Publishing
|
||||
activity =
|
||||
insert(:note_activity,
|
||||
user: user,
|
||||
data_attrs: %{
|
||||
"cc" => [@as_public],
|
||||
"to" => [user.follower_address]
|
||||
}
|
||||
)
|
||||
|
||||
prepared =
|
||||
Publisher.prepare_one(%{
|
||||
inbox: "https://remote.instance/users/someone/inbox",
|
||||
activity_id: activity.id,
|
||||
cc: ["https://remote.instance/users/someone_else/inbox"]
|
||||
})
|
||||
|
||||
{:ok, decoded} = Jason.decode(prepared.json)
|
||||
|
||||
assert decoded["cc"] == [@as_public, "https://remote.instance/users/someone_else/inbox"]
|
||||
end
|
||||
|
||||
test "public address in cc parameter is preserved" do
|
||||
user = insert(:user)
|
||||
|
||||
cc_with_public = [@as_public, "https://example.org/users/other"]
|
||||
|
||||
activity =
|
||||
insert(:note_activity,
|
||||
user: user,
|
||||
data_attrs: %{
|
||||
"cc" => cc_with_public,
|
||||
"to" => [user.follower_address]
|
||||
}
|
||||
)
|
||||
|
||||
assert @as_public in activity.data["cc"]
|
||||
|
||||
prepared =
|
||||
Publisher.prepare_one(%{
|
||||
inbox: "https://remote.instance/users/someone/inbox",
|
||||
activity_id: activity.id,
|
||||
cc: cc_with_public
|
||||
})
|
||||
|
||||
{:ok, decoded} = Jason.decode(prepared.json)
|
||||
|
||||
assert cc_with_public == decoded["cc"]
|
||||
end
|
||||
|
||||
test "cc parameter is preserved" do
|
||||
user = insert(:user)
|
||||
|
||||
activity =
|
||||
insert(:note_activity,
|
||||
user: user,
|
||||
data_attrs: %{
|
||||
"cc" => ["https://example.com/specific/user"],
|
||||
"to" => [user.follower_address]
|
||||
}
|
||||
)
|
||||
|
||||
prepared =
|
||||
Publisher.prepare_one(%{
|
||||
inbox: "https://remote.instance/users/someone/inbox",
|
||||
activity_id: activity.id,
|
||||
cc: ["https://example.com/specific/user"]
|
||||
})
|
||||
|
||||
{:ok, decoded} = Jason.decode(prepared.json)
|
||||
|
||||
assert decoded["cc"] == ["https://example.com/specific/user"]
|
||||
end
|
||||
|
||||
describe "prepare_one/1 with reporter anonymization" do
|
||||
test "signs with the anonymized actor keys when Transmogrifier changes actor" do
|
||||
Pleroma.SignatureMock
|
||||
|> Mox.stub(:signed_date, fn -> Pleroma.Signature.signed_date() end)
|
||||
|> Mox.expect(:sign, fn %Pleroma.User{} = user, _headers ->
|
||||
send(self(), {:signed_as, user.ap_id})
|
||||
"TESTSIG"
|
||||
end)
|
||||
|
||||
placeholder = insert(:user)
|
||||
reporter = insert(:user)
|
||||
target_account = insert(:user)
|
||||
|
||||
clear_config([:activitypub, :anonymize_reporter], true)
|
||||
clear_config([:activitypub, :anonymize_reporter_local_nickname], placeholder.nickname)
|
||||
|
||||
{:ok, reported} = CommonAPI.post(target_account, %{status: "content"})
|
||||
context = Utils.generate_context_id()
|
||||
|
||||
{:ok, activity} =
|
||||
ActivityPub.flag(%{
|
||||
actor: reporter,
|
||||
context: context,
|
||||
account: target_account,
|
||||
statuses: [reported],
|
||||
content: "reason"
|
||||
})
|
||||
|
||||
_prepared =
|
||||
Publisher.prepare_one(%{
|
||||
inbox: "http://remote.example/users/alice/inbox",
|
||||
activity_id: activity.id
|
||||
})
|
||||
|
||||
assert_received {:signed_as, ap_id}
|
||||
assert ap_id == placeholder.ap_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -143,4 +143,40 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.LikeHandlingTest do
|
|||
assert {:ok, activity} = Transmogrifier.handle_incoming(data)
|
||||
assert activity.data["type"] == "Like"
|
||||
end
|
||||
|
||||
test "it changes incoming dislikes into emoji reactions" do
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(user, %{status: "hello"})
|
||||
|
||||
data =
|
||||
File.read!("test/fixtures/friendica-dislike.json")
|
||||
|> Jason.decode!()
|
||||
|> Map.put("object", activity.data["object"])
|
||||
|
||||
_actor = insert(:user, ap_id: data["actor"], local: false)
|
||||
|
||||
{:ok, %Activity{data: data, local: false} = activity} = Transmogrifier.handle_incoming(data)
|
||||
|
||||
refute Enum.empty?(activity.recipients)
|
||||
|
||||
assert data["actor"] == "https://my-place.social/profile/vaartis"
|
||||
assert data["type"] == "EmojiReact"
|
||||
assert data["content"] == "👎"
|
||||
assert data["id"] == "https://my-place.social/objects/e599373b-1368-4b20-cd24-837166957182"
|
||||
assert data["object"] == activity.data["object"]
|
||||
|
||||
data =
|
||||
File.read!("test/fixtures/friendica-dislike-undo.json")
|
||||
|> Jason.decode!()
|
||||
|> put_in(["object", "object"], activity.data["object"])
|
||||
|
||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||
|
||||
assert data["actor"] == "https://my-place.social/profile/vaartis"
|
||||
assert data["type"] == "Undo"
|
||||
|
||||
assert data["object"] ==
|
||||
"https://my-place.social/objects/e599373b-1368-4b20-cd24-837166957182"
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -642,6 +642,69 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
|
|||
|
||||
assert [_, _, %{"@language" => "pl"}] = modified["@context"]
|
||||
end
|
||||
|
||||
test "it strips report data" do
|
||||
reporter = insert(:user)
|
||||
target_account = insert(:user)
|
||||
content = "foobar"
|
||||
{:ok, reported_activity} = CommonAPI.post(target_account, %{status: content})
|
||||
context = Utils.generate_context_id()
|
||||
|
||||
object_ap_id = reported_activity.object.data["id"]
|
||||
|
||||
assert {:ok, activity} =
|
||||
Pleroma.Web.ActivityPub.ActivityPub.flag(%{
|
||||
actor: reporter,
|
||||
context: context,
|
||||
account: target_account,
|
||||
statuses: [reported_activity],
|
||||
content: content
|
||||
})
|
||||
|
||||
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
|
||||
|
||||
expected_data =
|
||||
activity.data
|
||||
|> put_in(["object"], [target_account.ap_id, object_ap_id])
|
||||
|> Map.put("actor", reporter.ap_id)
|
||||
|> Map.merge(Utils.make_json_ld_header())
|
||||
|
||||
assert data == expected_data
|
||||
end
|
||||
|
||||
test "it strips report data and anonymize" do
|
||||
placeholder = insert(:user)
|
||||
|
||||
reporter = insert(:user)
|
||||
target_account = insert(:user)
|
||||
content = "foobar"
|
||||
{:ok, reported_activity} = CommonAPI.post(target_account, %{status: content})
|
||||
context = Utils.generate_context_id()
|
||||
|
||||
object_ap_id = reported_activity.object.data["id"]
|
||||
|
||||
assert {:ok, activity} =
|
||||
Pleroma.Web.ActivityPub.ActivityPub.flag(%{
|
||||
actor: reporter,
|
||||
context: context,
|
||||
account: target_account,
|
||||
statuses: [reported_activity],
|
||||
content: content
|
||||
})
|
||||
|
||||
clear_config([:activitypub, :anonymize_reporter], true)
|
||||
clear_config([:activitypub, :anonymize_reporter_local_nickname], placeholder.nickname)
|
||||
|
||||
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
|
||||
|
||||
expected_data =
|
||||
activity.data
|
||||
|> put_in(["object"], [target_account.ap_id, object_ap_id])
|
||||
|> Map.put("actor", placeholder.ap_id)
|
||||
|> Map.merge(Utils.make_json_ld_header())
|
||||
|
||||
assert data == expected_data
|
||||
end
|
||||
end
|
||||
|
||||
describe "actor rewriting" do
|
||||
|
|
|
|||
|
|
@ -670,4 +670,78 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do
|
|||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe "maybe_anonymize_reporter/1" do
|
||||
setup do
|
||||
reporter = insert(:user)
|
||||
report = %{"actor" => reporter.ap_id}
|
||||
|
||||
%{
|
||||
placeholder: insert(:user),
|
||||
reporter: reporter,
|
||||
report: report
|
||||
}
|
||||
end
|
||||
|
||||
test "anonymize when configured correctly", %{
|
||||
placeholder: placeholder,
|
||||
report: report
|
||||
} do
|
||||
clear_config([:activitypub, :anonymize_reporter], true)
|
||||
clear_config([:activitypub, :anonymize_reporter_local_nickname], placeholder.nickname)
|
||||
|
||||
assert %{"actor" => placeholder.ap_id} == Utils.maybe_anonymize_reporter(report)
|
||||
end
|
||||
|
||||
test "anonymize Activity", %{
|
||||
placeholder: placeholder,
|
||||
reporter: reporter,
|
||||
report: report
|
||||
} do
|
||||
clear_config([:activitypub, :anonymize_reporter], true)
|
||||
clear_config([:activitypub, :anonymize_reporter_local_nickname], placeholder.nickname)
|
||||
report_activity = %Activity{actor: reporter, data: report}
|
||||
anon_id = placeholder.ap_id
|
||||
|
||||
assert %Activity{actor: ^anon_id, data: %{"actor" => ^anon_id}} =
|
||||
Utils.maybe_anonymize_reporter(report_activity)
|
||||
end
|
||||
|
||||
test "do not anonymize when disabled", %{
|
||||
placeholder: placeholder,
|
||||
reporter: reporter,
|
||||
report: report
|
||||
} do
|
||||
clear_config([:activitypub, :anonymize_reporter], false)
|
||||
clear_config([:activitypub, :anonymize_reporter_local_nickname], placeholder.nickname)
|
||||
|
||||
assert %{"actor" => reporter.ap_id} == Utils.maybe_anonymize_reporter(report)
|
||||
end
|
||||
|
||||
test "do not anonymize when user does not exist", %{
|
||||
placeholder: placeholder,
|
||||
reporter: reporter,
|
||||
report: report
|
||||
} do
|
||||
clear_config([:activitypub, :anonymize_reporter], true)
|
||||
|
||||
clear_config(
|
||||
[:activitypub, :anonymize_reporter_local_nickname],
|
||||
placeholder.nickname <> "MewMew"
|
||||
)
|
||||
|
||||
assert %{"actor" => reporter.ap_id} == Utils.maybe_anonymize_reporter(report)
|
||||
end
|
||||
|
||||
test "do not anonymize when user is not local", %{
|
||||
reporter: reporter,
|
||||
report: report
|
||||
} do
|
||||
placeholder = insert(:user, local: false)
|
||||
clear_config([:activitypub, :anonymize_reporter], true)
|
||||
clear_config([:activitypub, :anonymize_reporter_local_nickname], placeholder.nickname)
|
||||
|
||||
assert %{"actor" => reporter.ap_id} == Utils.maybe_anonymize_reporter(report)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -321,6 +321,36 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
|
|||
assert ModerationLog.get_log_entry_message(log_entry) ==
|
||||
"@#{admin.nickname} revoked admin role from @#{user_one.nickname}, @#{user_two.nickname}"
|
||||
end
|
||||
|
||||
test "/:right DELETE, admin cannot revoke their own admin status (single)", %{
|
||||
admin: admin,
|
||||
conn: conn
|
||||
} do
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("accept", "application/json")
|
||||
|> delete("/api/pleroma/admin/users/#{admin.nickname}/permission_group/admin")
|
||||
|
||||
assert json_response(conn, 403) == %{"error" => "You can't revoke your own admin status."}
|
||||
end
|
||||
|
||||
test "/:right DELETE, admin cannot revoke their own admin status (multiple)", %{
|
||||
admin: admin,
|
||||
conn: conn
|
||||
} do
|
||||
user = insert(:user, is_admin: true)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("accept", "application/json")
|
||||
|> delete("/api/pleroma/admin/users/permission_group/admin", %{
|
||||
nicknames: [admin.nickname, user.nickname]
|
||||
})
|
||||
|
||||
assert json_response(conn, 403) == %{
|
||||
"error" => "You can't revoke your own admin/moderator status."
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
describe "/api/pleroma/admin/users/:nickname/password_reset" do
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ defmodule Pleroma.Web.AdminAPI.FrontendControllerTest do
|
|||
|
||||
setup do
|
||||
clear_config([:instance, :static_dir], @dir)
|
||||
File.mkdir_p!(Pleroma.Frontend.dir())
|
||||
Pleroma.Backports.mkdir_p!(Pleroma.Frontend.dir())
|
||||
|
||||
on_exit(fn ->
|
||||
File.rm_rf(@dir)
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@ defmodule Pleroma.Web.AdminAPI.InstanceControllerTest do
|
|||
|
||||
import Pleroma.Factory
|
||||
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.Tests.ObanHelpers
|
||||
alias Pleroma.Web.CommonAPI
|
||||
|
||||
setup_all do
|
||||
|
|
@ -69,19 +67,19 @@ defmodule Pleroma.Web.AdminAPI.InstanceControllerTest do
|
|||
|
||||
test "DELETE /instances/:instance", %{conn: conn} do
|
||||
clear_config([:instance, :admin_privileges], [:instances_delete])
|
||||
user = insert(:user, nickname: "lain@lain.com")
|
||||
post = insert(:note_activity, user: user)
|
||||
insert(:user, nickname: "lain@lain.com")
|
||||
|
||||
response =
|
||||
conn
|
||||
|> delete("/api/pleroma/admin/instances/lain.com")
|
||||
|> json_response(200)
|
||||
|
||||
[:ok] = ObanHelpers.perform_all()
|
||||
|
||||
assert response == "lain.com"
|
||||
refute Repo.reload(user).is_active
|
||||
refute Repo.reload(post)
|
||||
|
||||
assert_enqueued(
|
||||
worker: Pleroma.Workers.DeleteWorker,
|
||||
args: %{"op" => "delete_instance", "host" => "lain.com"}
|
||||
)
|
||||
|
||||
clear_config([:instance, :admin_privileges], [])
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ defmodule Pleroma.Web.AdminAPI.InstanceDocumentControllerTest do
|
|||
@default_instance_panel ~s(<p>Welcome to <a href="https://pleroma.social" target="_blank">Pleroma!</a></p>)
|
||||
|
||||
setup do
|
||||
File.mkdir_p!(@dir)
|
||||
Pleroma.Backports.mkdir_p!(@dir)
|
||||
on_exit(fn -> File.rm_rf(@dir) end)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -111,6 +111,17 @@ defmodule Pleroma.Web.CommonAPITest do
|
|||
end
|
||||
end
|
||||
|
||||
test "add expiring block", %{blocker: blocker, blocked: blocked} do
|
||||
{:ok, _} = CommonAPI.block(blocked, blocker, %{expires_in: 60})
|
||||
assert User.blocks?(blocker, blocked)
|
||||
|
||||
worker = Pleroma.Workers.MuteExpireWorker
|
||||
args = %{"op" => "unblock_user", "blocker_id" => blocker.id, "blocked_id" => blocked.id}
|
||||
|
||||
assert :ok = perform_job(worker, args)
|
||||
refute User.blocks?(blocker, blocked)
|
||||
end
|
||||
|
||||
test "it blocks and does not federate if outgoing blocks are disabled", %{
|
||||
blocker: blocker,
|
||||
blocked: blocked
|
||||
|
|
|
|||
|
|
@ -126,22 +126,17 @@ defmodule Pleroma.Web.FederatorTest do
|
|||
inbox: inbox2
|
||||
})
|
||||
|
||||
dt = NaiveDateTime.utc_now()
|
||||
Instances.set_unreachable(inbox1, dt)
|
||||
|
||||
Instances.set_consistently_unreachable(URI.parse(inbox2).host)
|
||||
Instances.set_unreachable(URI.parse(inbox2).host)
|
||||
|
||||
{:ok, _activity} =
|
||||
CommonAPI.post(user, %{status: "HI @nick1@domain.com, @nick2@domain2.com!"})
|
||||
|
||||
expected_dt = NaiveDateTime.to_iso8601(dt)
|
||||
|
||||
ObanHelpers.perform(all_enqueued(worker: PublisherWorker))
|
||||
|
||||
assert ObanHelpers.member?(
|
||||
%{
|
||||
"op" => "publish_one",
|
||||
"params" => %{"inbox" => inbox1, "unreachable_since" => expected_dt}
|
||||
"params" => %{"inbox" => inbox1}
|
||||
},
|
||||
all_enqueued(worker: PublisherWorker)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -469,6 +469,17 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
|
|||
assert [%{"id" => ^post_id}] = json_response_and_validate_schema(conn, 200)
|
||||
end
|
||||
|
||||
test "gets only a user's reblogs", %{user: user, conn: conn} do
|
||||
{:ok, %{id: post_id}} = CommonAPI.post(user, %{status: "HI!!!"})
|
||||
{:ok, %{id: reblog_id}} = CommonAPI.repeat(post_id, user)
|
||||
|
||||
conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?only_reblogs=true")
|
||||
assert [%{"id" => ^reblog_id}] = json_response_and_validate_schema(conn, 200)
|
||||
|
||||
conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?only_reblogs=1")
|
||||
assert [%{"id" => ^reblog_id}] = json_response_and_validate_schema(conn, 200)
|
||||
end
|
||||
|
||||
test "filters user's statuses by a hashtag", %{user: user, conn: conn} do
|
||||
{:ok, %{id: post_id}} = CommonAPI.post(user, %{status: "#hashtag"})
|
||||
{:ok, _post} = CommonAPI.post(user, %{status: "hashtag"})
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
|
|||
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.Endpoint
|
||||
import Pleroma.Factory
|
||||
import ExUnit.CaptureLog
|
||||
import Tesla.Mock
|
||||
|
|
@ -66,9 +65,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
|
|||
[account | _] = results["accounts"]
|
||||
assert account["id"] == to_string(user_three.id)
|
||||
|
||||
assert results["hashtags"] == [
|
||||
%{"name" => "private", "url" => "#{Endpoint.url()}/tag/private"}
|
||||
]
|
||||
assert results["hashtags"] == []
|
||||
|
||||
[status] = results["statuses"]
|
||||
assert status["id"] == to_string(activity.id)
|
||||
|
|
@ -77,9 +74,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
|
|||
get(conn, "/api/v2/search?q=天子")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert results["hashtags"] == [
|
||||
%{"name" => "天子", "url" => "#{Endpoint.url()}/tag/天子"}
|
||||
]
|
||||
assert results["hashtags"] == []
|
||||
|
||||
[status] = results["statuses"]
|
||||
assert status["id"] == to_string(activity.id)
|
||||
|
|
@ -130,84 +125,97 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
|
|||
assert [] = results["statuses"]
|
||||
end
|
||||
|
||||
test "constructs hashtags from search query", %{conn: conn} do
|
||||
test "returns empty results when no hashtags match", %{conn: conn} do
|
||||
results =
|
||||
conn
|
||||
|> get("/api/v2/search?#{URI.encode_query(%{q: "some text with #explicit #hashtags"})}")
|
||||
|> get("/api/v2/search?#{URI.encode_query(%{q: "nonexistent"})}")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert results["hashtags"] == [
|
||||
%{"name" => "explicit", "url" => "#{Endpoint.url()}/tag/explicit"},
|
||||
%{"name" => "hashtags", "url" => "#{Endpoint.url()}/tag/hashtags"}
|
||||
]
|
||||
assert results["hashtags"] == []
|
||||
end
|
||||
|
||||
test "searches hashtags by multiple words in query", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, _activity1} = CommonAPI.post(user, %{status: "This is my new #computer"})
|
||||
{:ok, _activity2} = CommonAPI.post(user, %{status: "Check out this #laptop"})
|
||||
{:ok, _activity3} = CommonAPI.post(user, %{status: "My #desktop setup"})
|
||||
{:ok, _activity4} = CommonAPI.post(user, %{status: "New #phone arrived"})
|
||||
|
||||
results =
|
||||
conn
|
||||
|> get("/api/v2/search?#{URI.encode_query(%{q: "john doe JOHN DOE"})}")
|
||||
|> get("/api/v2/search?#{URI.encode_query(%{q: "new computer"})}")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert results["hashtags"] == [
|
||||
%{"name" => "john", "url" => "#{Endpoint.url()}/tag/john"},
|
||||
%{"name" => "doe", "url" => "#{Endpoint.url()}/tag/doe"},
|
||||
%{"name" => "JohnDoe", "url" => "#{Endpoint.url()}/tag/JohnDoe"}
|
||||
]
|
||||
hashtag_names = Enum.map(results["hashtags"], & &1["name"])
|
||||
assert "computer" in hashtag_names
|
||||
refute "laptop" in hashtag_names
|
||||
refute "desktop" in hashtag_names
|
||||
refute "phone" in hashtag_names
|
||||
|
||||
results =
|
||||
conn
|
||||
|> get("/api/v2/search?#{URI.encode_query(%{q: "accident-prone"})}")
|
||||
|> get("/api/v2/search?#{URI.encode_query(%{q: "computer laptop"})}")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert results["hashtags"] == [
|
||||
%{"name" => "accident", "url" => "#{Endpoint.url()}/tag/accident"},
|
||||
%{"name" => "prone", "url" => "#{Endpoint.url()}/tag/prone"},
|
||||
%{"name" => "AccidentProne", "url" => "#{Endpoint.url()}/tag/AccidentProne"}
|
||||
]
|
||||
|
||||
results =
|
||||
conn
|
||||
|> get("/api/v2/search?#{URI.encode_query(%{q: "https://shpposter.club/users/shpuld"})}")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert results["hashtags"] == [
|
||||
%{"name" => "shpuld", "url" => "#{Endpoint.url()}/tag/shpuld"}
|
||||
]
|
||||
|
||||
results =
|
||||
conn
|
||||
|> get(
|
||||
"/api/v2/search?#{URI.encode_query(%{q: "https://www.washingtonpost.com/sports/2020/06/10/" <> "nascar-ban-display-confederate-flag-all-events-properties/"})}"
|
||||
)
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert results["hashtags"] == [
|
||||
%{"name" => "nascar", "url" => "#{Endpoint.url()}/tag/nascar"},
|
||||
%{"name" => "ban", "url" => "#{Endpoint.url()}/tag/ban"},
|
||||
%{"name" => "display", "url" => "#{Endpoint.url()}/tag/display"},
|
||||
%{"name" => "confederate", "url" => "#{Endpoint.url()}/tag/confederate"},
|
||||
%{"name" => "flag", "url" => "#{Endpoint.url()}/tag/flag"},
|
||||
%{"name" => "all", "url" => "#{Endpoint.url()}/tag/all"},
|
||||
%{"name" => "events", "url" => "#{Endpoint.url()}/tag/events"},
|
||||
%{"name" => "properties", "url" => "#{Endpoint.url()}/tag/properties"},
|
||||
%{
|
||||
"name" => "NascarBanDisplayConfederateFlagAllEventsProperties",
|
||||
"url" =>
|
||||
"#{Endpoint.url()}/tag/NascarBanDisplayConfederateFlagAllEventsProperties"
|
||||
}
|
||||
]
|
||||
hashtag_names = Enum.map(results["hashtags"], & &1["name"])
|
||||
assert "computer" in hashtag_names
|
||||
assert "laptop" in hashtag_names
|
||||
refute "desktop" in hashtag_names
|
||||
refute "phone" in hashtag_names
|
||||
end
|
||||
|
||||
test "supports pagination of hashtags search results", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, _activity1} = CommonAPI.post(user, %{status: "First #alpha hashtag"})
|
||||
{:ok, _activity2} = CommonAPI.post(user, %{status: "Second #beta hashtag"})
|
||||
{:ok, _activity3} = CommonAPI.post(user, %{status: "Third #gamma hashtag"})
|
||||
{:ok, _activity4} = CommonAPI.post(user, %{status: "Fourth #delta hashtag"})
|
||||
|
||||
results =
|
||||
conn
|
||||
|> get(
|
||||
"/api/v2/search?#{URI.encode_query(%{q: "#some #text #with #hashtags", limit: 2, offset: 1})}"
|
||||
)
|
||||
|> get("/api/v2/search?#{URI.encode_query(%{q: "a", limit: 2, offset: 1})}")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert results["hashtags"] == [
|
||||
%{"name" => "text", "url" => "#{Endpoint.url()}/tag/text"},
|
||||
%{"name" => "with", "url" => "#{Endpoint.url()}/tag/with"}
|
||||
]
|
||||
hashtag_names = Enum.map(results["hashtags"], & &1["name"])
|
||||
|
||||
# Should return 2 hashtags (alpha, beta, gamma, delta all contain 'a')
|
||||
# With offset 1, we skip the first one, so we get 2 of the remaining 3
|
||||
assert length(hashtag_names) == 2
|
||||
assert Enum.all?(hashtag_names, &String.contains?(&1, "a"))
|
||||
end
|
||||
|
||||
test "searches real hashtags from database", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, _activity1} = CommonAPI.post(user, %{status: "Check out this #car"})
|
||||
{:ok, _activity2} = CommonAPI.post(user, %{status: "Fast #racecar on the track"})
|
||||
{:ok, _activity3} = CommonAPI.post(user, %{status: "NASCAR #nascar racing"})
|
||||
|
||||
results =
|
||||
conn
|
||||
|> get("/api/v2/search?#{URI.encode_query(%{q: "car"})}")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
hashtag_names = Enum.map(results["hashtags"], & &1["name"])
|
||||
|
||||
# Should return car, racecar, and nascar since they all contain "car"
|
||||
assert "car" in hashtag_names
|
||||
assert "racecar" in hashtag_names
|
||||
assert "nascar" in hashtag_names
|
||||
|
||||
# Search for "race" - should return racecar
|
||||
results =
|
||||
conn
|
||||
|> get("/api/v2/search?#{URI.encode_query(%{q: "race"})}")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
hashtag_names = Enum.map(results["hashtags"], & &1["name"])
|
||||
|
||||
assert "racecar" in hashtag_names
|
||||
refute "car" in hashtag_names
|
||||
refute "nascar" in hashtag_names
|
||||
end
|
||||
|
||||
test "excludes a blocked users from search results", %{conn: conn} do
|
||||
|
|
@ -314,7 +322,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
|
|||
[account | _] = results["accounts"]
|
||||
assert account["id"] == to_string(user_three.id)
|
||||
|
||||
assert results["hashtags"] == ["2hu"]
|
||||
assert results["hashtags"] == []
|
||||
|
||||
[status] = results["statuses"]
|
||||
assert status["id"] == to_string(activity.id)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,371 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerDownloadZipTest do
|
||||
use Pleroma.Web.ConnCase, async: false
|
||||
|
||||
import Tesla.Mock
|
||||
import Pleroma.Factory
|
||||
|
||||
setup_all do
|
||||
# Create a base temp directory for this test module
|
||||
base_temp_dir = Path.join(System.tmp_dir!(), "emoji_test_#{Ecto.UUID.generate()}")
|
||||
|
||||
# Clean up when all tests in module are done
|
||||
on_exit(fn ->
|
||||
File.rm_rf!(base_temp_dir)
|
||||
end)
|
||||
|
||||
{:ok, %{base_temp_dir: base_temp_dir}}
|
||||
end
|
||||
|
||||
setup %{base_temp_dir: base_temp_dir} do
|
||||
# Create a unique subdirectory for each test
|
||||
test_id = Ecto.UUID.generate()
|
||||
temp_dir = Path.join(base_temp_dir, test_id)
|
||||
emoji_dir = Path.join(temp_dir, "emoji")
|
||||
|
||||
# Create the directory structure
|
||||
File.mkdir_p!(emoji_dir)
|
||||
|
||||
# Configure this test to use the temp directory
|
||||
clear_config([:instance, :static_dir], temp_dir)
|
||||
|
||||
admin = insert(:user, is_admin: true)
|
||||
token = insert(:oauth_admin_token, user: admin)
|
||||
|
||||
admin_conn =
|
||||
build_conn()
|
||||
|> assign(:user, admin)
|
||||
|> assign(:token, token)
|
||||
|
||||
Pleroma.Emoji.reload()
|
||||
|
||||
{:ok, %{admin_conn: admin_conn, emoji_path: emoji_dir}}
|
||||
end
|
||||
|
||||
describe "POST /api/pleroma/emoji/packs/download_zip" do
|
||||
setup do
|
||||
clear_config([:instance, :admin_privileges], [:emoji_manage_emoji])
|
||||
end
|
||||
|
||||
test "creates pack from uploaded ZIP file", %{admin_conn: admin_conn, emoji_path: emoji_path} do
|
||||
# Create a test ZIP file with emojis
|
||||
{:ok, zip_path} = create_test_emoji_zip()
|
||||
|
||||
upload = %Plug.Upload{
|
||||
content_type: "application/zip",
|
||||
path: zip_path,
|
||||
filename: "test_pack.zip"
|
||||
}
|
||||
|
||||
assert admin_conn
|
||||
|> put_req_header("content-type", "multipart/form-data")
|
||||
|> post("/api/pleroma/emoji/packs/download_zip", %{
|
||||
name: "test_zip_pack",
|
||||
file: upload
|
||||
})
|
||||
|> json_response_and_validate_schema(200) == "ok"
|
||||
|
||||
# Verify pack was created
|
||||
assert File.exists?("#{emoji_path}/test_zip_pack/pack.json")
|
||||
assert File.exists?("#{emoji_path}/test_zip_pack/test_emoji.png")
|
||||
|
||||
# Verify pack.json contents
|
||||
{:ok, pack_json} = File.read("#{emoji_path}/test_zip_pack/pack.json")
|
||||
pack_data = Jason.decode!(pack_json)
|
||||
|
||||
assert pack_data["files"]["test_emoji"] == "test_emoji.png"
|
||||
assert pack_data["pack"]["src_sha256"] != nil
|
||||
|
||||
# Clean up
|
||||
File.rm!(zip_path)
|
||||
end
|
||||
|
||||
test "creates pack from URL", %{admin_conn: admin_conn, emoji_path: emoji_path} do
|
||||
# Mock HTTP request to download ZIP
|
||||
{:ok, zip_path} = create_test_emoji_zip()
|
||||
{:ok, zip_data} = File.read(zip_path)
|
||||
|
||||
mock(fn
|
||||
%{method: :get, url: "https://example.com/emoji_pack.zip"} ->
|
||||
%Tesla.Env{status: 200, body: zip_data}
|
||||
end)
|
||||
|
||||
assert admin_conn
|
||||
|> put_req_header("content-type", "multipart/form-data")
|
||||
|> post("/api/pleroma/emoji/packs/download_zip", %{
|
||||
name: "test_zip_pack_url",
|
||||
url: "https://example.com/emoji_pack.zip"
|
||||
})
|
||||
|> json_response_and_validate_schema(200) == "ok"
|
||||
|
||||
# Verify pack was created
|
||||
assert File.exists?("#{emoji_path}/test_zip_pack_url/pack.json")
|
||||
assert File.exists?("#{emoji_path}/test_zip_pack_url/test_emoji.png")
|
||||
|
||||
# Verify pack.json has URL as source
|
||||
{:ok, pack_json} = File.read("#{emoji_path}/test_zip_pack_url/pack.json")
|
||||
pack_data = Jason.decode!(pack_json)
|
||||
|
||||
assert pack_data["pack"]["src"] == "https://example.com/emoji_pack.zip"
|
||||
assert pack_data["pack"]["src_sha256"] != nil
|
||||
|
||||
# Clean up
|
||||
File.rm!(zip_path)
|
||||
end
|
||||
|
||||
test "refuses to overwrite existing pack", %{admin_conn: admin_conn, emoji_path: emoji_path} do
|
||||
# Create existing pack
|
||||
pack_path = Path.join(emoji_path, "test_zip_pack")
|
||||
File.mkdir_p!(pack_path)
|
||||
File.write!(Path.join(pack_path, "pack.json"), Jason.encode!(%{files: %{}}))
|
||||
|
||||
{:ok, zip_path} = create_test_emoji_zip()
|
||||
|
||||
upload = %Plug.Upload{
|
||||
content_type: "application/zip",
|
||||
path: zip_path,
|
||||
filename: "test_pack.zip"
|
||||
}
|
||||
|
||||
assert admin_conn
|
||||
|> put_req_header("content-type", "multipart/form-data")
|
||||
|> post("/api/pleroma/emoji/packs/download_zip", %{
|
||||
name: "test_zip_pack",
|
||||
file: upload
|
||||
})
|
||||
|> json_response_and_validate_schema(400) == %{
|
||||
"error" => "Pack already exists, refusing to import test_zip_pack"
|
||||
}
|
||||
|
||||
# Clean up
|
||||
File.rm!(zip_path)
|
||||
end
|
||||
|
||||
test "handles invalid ZIP file", %{admin_conn: admin_conn} do
|
||||
# Create invalid ZIP file
|
||||
invalid_zip_path = Path.join(System.tmp_dir!(), "invalid.zip")
|
||||
File.write!(invalid_zip_path, "not a zip file")
|
||||
|
||||
upload = %Plug.Upload{
|
||||
content_type: "application/zip",
|
||||
path: invalid_zip_path,
|
||||
filename: "invalid.zip"
|
||||
}
|
||||
|
||||
assert admin_conn
|
||||
|> put_req_header("content-type", "multipart/form-data")
|
||||
|> post("/api/pleroma/emoji/packs/download_zip", %{
|
||||
name: "test_invalid_pack",
|
||||
file: upload
|
||||
})
|
||||
|> json_response_and_validate_schema(400) == %{
|
||||
"error" => "Could not unzip pack"
|
||||
}
|
||||
|
||||
# Clean up
|
||||
File.rm!(invalid_zip_path)
|
||||
end
|
||||
|
||||
test "handles URL download failure", %{admin_conn: admin_conn} do
|
||||
mock(fn
|
||||
%{method: :get, url: "https://example.com/bad_pack.zip"} ->
|
||||
%Tesla.Env{status: 404, body: "Not found"}
|
||||
end)
|
||||
|
||||
assert admin_conn
|
||||
|> put_req_header("content-type", "multipart/form-data")
|
||||
|> post("/api/pleroma/emoji/packs/download_zip", %{
|
||||
name: "test_bad_url_pack",
|
||||
url: "https://example.com/bad_pack.zip"
|
||||
})
|
||||
|> json_response_and_validate_schema(400) == %{
|
||||
"error" => "Could not download pack"
|
||||
}
|
||||
end
|
||||
|
||||
test "requires either file or URL parameter", %{admin_conn: admin_conn} do
|
||||
assert admin_conn
|
||||
|> put_req_header("content-type", "multipart/form-data")
|
||||
|> post("/api/pleroma/emoji/packs/download_zip", %{
|
||||
name: "test_no_source_pack"
|
||||
})
|
||||
|> json_response_and_validate_schema(400) == %{
|
||||
"error" => "Neither file nor URL was present in the request"
|
||||
}
|
||||
end
|
||||
|
||||
test "returns error when pack name is empty", %{admin_conn: admin_conn} do
|
||||
{:ok, zip_path} = create_test_emoji_zip()
|
||||
|
||||
upload = %Plug.Upload{
|
||||
content_type: "application/zip",
|
||||
path: zip_path,
|
||||
filename: "test_pack.zip"
|
||||
}
|
||||
|
||||
assert admin_conn
|
||||
|> put_req_header("content-type", "multipart/form-data")
|
||||
|> post("/api/pleroma/emoji/packs/download_zip", %{
|
||||
name: "",
|
||||
file: upload
|
||||
})
|
||||
|> json_response_and_validate_schema(400) == %{
|
||||
"error" => "Pack name cannot be empty"
|
||||
}
|
||||
|
||||
# Clean up
|
||||
File.rm!(zip_path)
|
||||
end
|
||||
|
||||
test "returns error when unable to create pack directory", %{
|
||||
admin_conn: admin_conn,
|
||||
emoji_path: emoji_path
|
||||
} do
|
||||
# Make the emoji directory read-only to trigger mkdir_p failure
|
||||
|
||||
# Save original permissions
|
||||
{:ok, %{mode: original_mode}} = File.stat(emoji_path)
|
||||
|
||||
# Make emoji directory read-only (no write permission)
|
||||
File.chmod!(emoji_path, 0o555)
|
||||
|
||||
{:ok, zip_path} = create_test_emoji_zip()
|
||||
|
||||
upload = %Plug.Upload{
|
||||
content_type: "application/zip",
|
||||
path: zip_path,
|
||||
filename: "test_pack.zip"
|
||||
}
|
||||
|
||||
# Try to create a pack in the read-only emoji directory
|
||||
assert admin_conn
|
||||
|> put_req_header("content-type", "multipart/form-data")
|
||||
|> post("/api/pleroma/emoji/packs/download_zip", %{
|
||||
name: "test_readonly_pack",
|
||||
file: upload
|
||||
})
|
||||
|> json_response_and_validate_schema(400) == %{
|
||||
"error" => "Could not create the pack directory"
|
||||
}
|
||||
|
||||
# Clean up - restore original permissions
|
||||
File.chmod!(emoji_path, original_mode)
|
||||
File.rm!(zip_path)
|
||||
end
|
||||
|
||||
test "preserves existing pack.json if present in ZIP", %{
|
||||
admin_conn: admin_conn,
|
||||
emoji_path: emoji_path
|
||||
} do
|
||||
# Create ZIP with pack.json
|
||||
{:ok, zip_path} = create_test_emoji_zip_with_pack_json()
|
||||
|
||||
upload = %Plug.Upload{
|
||||
content_type: "application/zip",
|
||||
path: zip_path,
|
||||
filename: "test_pack_with_json.zip"
|
||||
}
|
||||
|
||||
assert admin_conn
|
||||
|> put_req_header("content-type", "multipart/form-data")
|
||||
|> post("/api/pleroma/emoji/packs/download_zip", %{
|
||||
name: "test_zip_pack_with_json",
|
||||
file: upload
|
||||
})
|
||||
|> json_response_and_validate_schema(200) == "ok"
|
||||
|
||||
# Verify original pack.json was preserved
|
||||
{:ok, pack_json} = File.read("#{emoji_path}/test_zip_pack_with_json/pack.json")
|
||||
pack_data = Jason.decode!(pack_json)
|
||||
|
||||
assert pack_data["pack"]["description"] == "Test pack from ZIP"
|
||||
assert pack_data["pack"]["license"] == "Test License"
|
||||
|
||||
# Clean up
|
||||
File.rm!(zip_path)
|
||||
end
|
||||
|
||||
test "rejects malicious pack names", %{admin_conn: admin_conn} do
|
||||
{:ok, zip_path} = create_test_emoji_zip()
|
||||
|
||||
upload = %Plug.Upload{
|
||||
content_type: "application/zip",
|
||||
path: zip_path,
|
||||
filename: "test_pack.zip"
|
||||
}
|
||||
|
||||
# Test path traversal attempts
|
||||
malicious_names = ["../evil", "../../evil", ".", "..", "evil/../../../etc"]
|
||||
|
||||
Enum.each(malicious_names, fn name ->
|
||||
assert_raise RuntimeError, ~r/Invalid or malicious pack name/, fn ->
|
||||
admin_conn
|
||||
|> put_req_header("content-type", "multipart/form-data")
|
||||
|> post("/api/pleroma/emoji/packs/download_zip", %{
|
||||
name: name,
|
||||
file: upload
|
||||
})
|
||||
end
|
||||
end)
|
||||
|
||||
# Clean up
|
||||
File.rm!(zip_path)
|
||||
end
|
||||
end
|
||||
|
||||
defp create_test_emoji_zip do
|
||||
tmp_dir = System.tmp_dir!()
|
||||
zip_path = Path.join(tmp_dir, "test_emoji_pack_#{:rand.uniform(10000)}.zip")
|
||||
|
||||
# 1x1 pixel PNG
|
||||
png_data =
|
||||
Base.decode64!(
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="
|
||||
)
|
||||
|
||||
files = [
|
||||
{~c"test_emoji.png", png_data},
|
||||
# Will be treated as GIF based on extension
|
||||
{~c"another_emoji.gif", png_data}
|
||||
]
|
||||
|
||||
{:ok, {_name, zip_binary}} = :zip.zip(~c"test_pack.zip", files, [:memory])
|
||||
File.write!(zip_path, zip_binary)
|
||||
|
||||
{:ok, zip_path}
|
||||
end
|
||||
|
||||
defp create_test_emoji_zip_with_pack_json do
|
||||
tmp_dir = System.tmp_dir!()
|
||||
zip_path = Path.join(tmp_dir, "test_emoji_pack_json_#{:rand.uniform(10000)}.zip")
|
||||
|
||||
png_data =
|
||||
Base.decode64!(
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="
|
||||
)
|
||||
|
||||
pack_json =
|
||||
Jason.encode!(%{
|
||||
pack: %{
|
||||
description: "Test pack from ZIP",
|
||||
license: "Test License"
|
||||
},
|
||||
files: %{
|
||||
"test_emoji" => "test_emoji.png"
|
||||
}
|
||||
})
|
||||
|
||||
files = [
|
||||
{~c"test_emoji.png", png_data},
|
||||
{~c"pack.json", pack_json}
|
||||
]
|
||||
|
||||
{:ok, {_name, zip_binary}} = :zip.zip(~c"test_pack.zip", files, [:memory])
|
||||
File.write!(zip_path, zip_binary)
|
||||
|
||||
{:ok, zip_path}
|
||||
end
|
||||
end
|
||||
|
|
@ -7,16 +7,11 @@ defmodule Pleroma.Web.PleromaApi.InstancesControllerTest do
|
|||
|
||||
alias Pleroma.Instances
|
||||
|
||||
setup_all do: clear_config([:instance, :federation_reachability_timeout_days], 1)
|
||||
|
||||
setup do
|
||||
constant = "http://consistently-unreachable.name/"
|
||||
eventual = "http://eventually-unreachable.com/path"
|
||||
|
||||
{:ok, %Pleroma.Instances.Instance{unreachable_since: constant_unreachable}} =
|
||||
Instances.set_consistently_unreachable(constant)
|
||||
|
||||
_eventual_unreachable = Instances.set_unreachable(eventual)
|
||||
Instances.set_unreachable(constant)
|
||||
|
||||
%{constant_unreachable: constant_unreachable, constant: constant}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -19,10 +19,33 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleControllerTest do
|
|||
"artist" => "lain",
|
||||
"album" => "lain radio",
|
||||
"length" => "180000",
|
||||
"externalLink" => "https://www.last.fm/music/lain/lain+radio/lain+radio+episode+1"
|
||||
"external_link" => "https://www.last.fm/music/lain/lain+radio/lain+radio+episode+1"
|
||||
})
|
||||
|
||||
assert %{"title" => "lain radio episode 1"} = json_response_and_validate_schema(conn, 200)
|
||||
assert %{
|
||||
"title" => "lain radio episode 1",
|
||||
"external_link" => "https://www.last.fm/music/lain/lain+radio/lain+radio+episode+1"
|
||||
} = json_response_and_validate_schema(conn, 200)
|
||||
end
|
||||
|
||||
test "external_link fallback" do
|
||||
%{conn: conn} = oauth_access(["write"])
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/v1/pleroma/scrobble", %{
|
||||
"title" => "lain radio episode 2",
|
||||
"artist" => "lain",
|
||||
"album" => "lain radio",
|
||||
"length" => "180000",
|
||||
"externalLink" => "https://www.last.fm/music/lain/lain+radio/lain+radio+episode+2"
|
||||
})
|
||||
|
||||
assert %{
|
||||
"title" => "lain radio episode 2",
|
||||
"external_link" => "https://www.last.fm/music/lain/lain+radio/lain+radio+episode+2"
|
||||
} = json_response_and_validate_schema(conn, 200)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -35,7 +58,7 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleControllerTest do
|
|||
title: "lain radio episode 1",
|
||||
artist: "lain",
|
||||
album: "lain radio",
|
||||
externalLink: "https://www.last.fm/music/lain/lain+radio/lain+radio+episode+1"
|
||||
external_link: "https://www.last.fm/music/lain/lain+radio/lain+radio+episode+1"
|
||||
})
|
||||
|
||||
{:ok, _activity} =
|
||||
|
|
@ -43,7 +66,7 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleControllerTest do
|
|||
title: "lain radio episode 2",
|
||||
artist: "lain",
|
||||
album: "lain radio",
|
||||
externalLink: "https://www.last.fm/music/lain/lain+radio/lain+radio+episode+2"
|
||||
external_link: "https://www.last.fm/music/lain/lain+radio/lain+radio+episode+2"
|
||||
})
|
||||
|
||||
{:ok, _activity} =
|
||||
|
|
@ -51,7 +74,7 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleControllerTest do
|
|||
title: "lain radio episode 3",
|
||||
artist: "lain",
|
||||
album: "lain radio",
|
||||
externalLink: "https://www.last.fm/music/lain/lain+radio/lain+radio+episode+3"
|
||||
external_link: "https://www.last.fm/music/lain/lain+radio/lain+radio+episode+3"
|
||||
})
|
||||
|
||||
conn = get(conn, "/api/v1/pleroma/accounts/#{user.id}/scrobbles")
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@
|
|||
defmodule Pleroma.Web.Plugs.CacheTest do
|
||||
# Relies on Cachex, has to stay synchronous
|
||||
use Pleroma.DataCase
|
||||
use Plug.Test
|
||||
import Plug.Conn
|
||||
import Plug.Test
|
||||
|
||||
alias Pleroma.Web.Plugs.Cache
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@
|
|||
|
||||
defmodule Pleroma.Web.Plugs.DigestPlugTest do
|
||||
use ExUnit.Case, async: true
|
||||
use Plug.Test
|
||||
import Plug.Conn
|
||||
import Plug.Test
|
||||
|
||||
test "digest algorithm is taken from digest header" do
|
||||
body = "{\"hello\": \"world\"}"
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ defmodule Pleroma.Web.Plugs.FrontendStaticPlugTest do
|
|||
@dir "test/tmp/instance_static"
|
||||
|
||||
setup do
|
||||
File.mkdir_p!(@dir)
|
||||
Pleroma.Backports.mkdir_p!(@dir)
|
||||
on_exit(fn -> File.rm_rf(@dir) end)
|
||||
end
|
||||
|
||||
|
|
@ -38,7 +38,7 @@ defmodule Pleroma.Web.Plugs.FrontendStaticPlugTest do
|
|||
clear_config([:frontends, :primary], %{"name" => name, "ref" => ref})
|
||||
path = "#{@dir}/frontends/#{name}/#{ref}"
|
||||
|
||||
File.mkdir_p!(path)
|
||||
Pleroma.Backports.mkdir_p!(path)
|
||||
File.write!("#{path}/index.html", "from frontend plug")
|
||||
|
||||
index = get(conn, "/")
|
||||
|
|
@ -52,7 +52,7 @@ defmodule Pleroma.Web.Plugs.FrontendStaticPlugTest do
|
|||
clear_config([:frontends, :admin], %{"name" => name, "ref" => ref})
|
||||
path = "#{@dir}/frontends/#{name}/#{ref}"
|
||||
|
||||
File.mkdir_p!(path)
|
||||
Pleroma.Backports.mkdir_p!(path)
|
||||
File.write!("#{path}/index.html", "from frontend plug")
|
||||
|
||||
index = get(conn, "/pleroma/admin/")
|
||||
|
|
@ -67,7 +67,7 @@ defmodule Pleroma.Web.Plugs.FrontendStaticPlugTest do
|
|||
clear_config([:frontends, :primary], %{"name" => name, "ref" => ref})
|
||||
path = "#{@dir}/frontends/#{name}/#{ref}"
|
||||
|
||||
File.mkdir_p!("#{path}/proxy/rr/ss")
|
||||
Pleroma.Backports.mkdir_p!("#{path}/proxy/rr/ss")
|
||||
File.write!("#{path}/proxy/rr/ss/Ek7w8WPVcAApOvN.jpg:large", "FB image")
|
||||
|
||||
ConfigMock
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@
|
|||
defmodule Pleroma.Web.Plugs.IdempotencyPlugTest do
|
||||
# Relies on Cachex, has to stay synchronous
|
||||
use Pleroma.DataCase
|
||||
use Plug.Test
|
||||
import Plug.Conn
|
||||
import Plug.Test
|
||||
|
||||
alias Pleroma.Web.Plugs.IdempotencyPlug
|
||||
alias Plug.Conn
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ defmodule Pleroma.Web.Plugs.InstanceStaticTest do
|
|||
@dir "test/tmp/instance_static"
|
||||
|
||||
setup do
|
||||
File.mkdir_p!(@dir)
|
||||
Pleroma.Backports.mkdir_p!(@dir)
|
||||
on_exit(fn -> File.rm_rf(@dir) end)
|
||||
end
|
||||
|
||||
|
|
@ -34,7 +34,7 @@ defmodule Pleroma.Web.Plugs.InstanceStaticTest do
|
|||
refute html_response(bundled_index, 200) == "from frontend plug"
|
||||
|
||||
path = "#{@dir}/frontends/#{name}/#{ref}"
|
||||
File.mkdir_p!(path)
|
||||
Pleroma.Backports.mkdir_p!(path)
|
||||
File.write!("#{path}/index.html", "from frontend plug")
|
||||
|
||||
index = get(conn, "/")
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@
|
|||
|
||||
defmodule Pleroma.Web.Plugs.RemoteIpTest do
|
||||
use ExUnit.Case
|
||||
use Plug.Test
|
||||
import Plug.Conn
|
||||
import Plug.Test
|
||||
|
||||
alias Pleroma.Web.Plugs.RemoteIp
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@
|
|||
|
||||
defmodule Pleroma.Web.Plugs.SetFormatPlugTest do
|
||||
use ExUnit.Case, async: true
|
||||
use Plug.Test
|
||||
import Plug.Conn
|
||||
import Plug.Test
|
||||
|
||||
alias Pleroma.Web.Plugs.SetFormatPlug
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
defmodule Pleroma.Web.Plugs.SetLocalePlugTest do
|
||||
use ExUnit.Case, async: true
|
||||
use Plug.Test
|
||||
import Plug.Test
|
||||
|
||||
alias Pleroma.Web.Plugs.SetLocalePlug
|
||||
alias Plug.Conn
|
||||
|
|
|
|||
|
|
@ -21,8 +21,12 @@ defmodule Pleroma.Web.Plugs.UserTrackingPlugTest do
|
|||
|> assign(:user, user)
|
||||
|> UserTrackingPlug.call(%{})
|
||||
|
||||
assert user.last_active_at >= test_started_at
|
||||
assert user.last_active_at <= NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
|
||||
assert NaiveDateTime.compare(user.last_active_at, test_started_at) in [:gt, :eq]
|
||||
|
||||
assert NaiveDateTime.compare(
|
||||
user.last_active_at,
|
||||
NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
|
||||
) in [:lt, :eq]
|
||||
end
|
||||
|
||||
test "doesn't update last_active_at if it was updated recently", %{conn: conn} do
|
||||
|
|
@ -38,7 +42,7 @@ defmodule Pleroma.Web.Plugs.UserTrackingPlugTest do
|
|||
|> assign(:user, user)
|
||||
|> UserTrackingPlug.call(%{})
|
||||
|
||||
assert user.last_active_at == last_active_at
|
||||
assert NaiveDateTime.compare(user.last_active_at, last_active_at) == :eq
|
||||
end
|
||||
|
||||
test "skips updating last_active_at if user ID is nil", %{conn: conn} do
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ defmodule Pleroma.Web.RichMedia.Parsers.TwitterCardTest do
|
|||
}
|
||||
end
|
||||
|
||||
test "takes first founded title in html head if there is html markup error" do
|
||||
test "takes first found title in html head if there is html markup error" do
|
||||
html =
|
||||
File.read!("test/fixtures/nypd-facial-recognition-children-teenagers4.html")
|
||||
|> Floki.parse_document!()
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
defmodule Pleroma.Web.WebFinger.WebFingerControllerTest do
|
||||
use Pleroma.Web.ConnCase
|
||||
|
||||
import ExUnit.CaptureLog
|
||||
import Pleroma.Factory
|
||||
import Tesla.Mock
|
||||
|
||||
|
|
@ -34,27 +33,122 @@ defmodule Pleroma.Web.WebFinger.WebFingerControllerTest do
|
|||
assert match?(^response_xml, expected_xml)
|
||||
end
|
||||
|
||||
test "Webfinger JRD" do
|
||||
describe "Webfinger" do
|
||||
test "JRD" do
|
||||
clear_config([Pleroma.Web.Endpoint, :url, :host], "hyrule.world")
|
||||
clear_config([Pleroma.Web.WebFinger, :domain], "hyrule.world")
|
||||
|
||||
user =
|
||||
insert(:user,
|
||||
ap_id: "https://hyrule.world/users/zelda"
|
||||
)
|
||||
|
||||
response =
|
||||
build_conn()
|
||||
|> put_req_header("accept", "application/jrd+json")
|
||||
|> get("/.well-known/webfinger?resource=acct:#{user.nickname}@hyrule.world")
|
||||
|> json_response(200)
|
||||
|
||||
assert response["subject"] == "acct:#{user.nickname}@hyrule.world"
|
||||
|
||||
assert response["aliases"] == [
|
||||
"https://hyrule.world/users/zelda"
|
||||
]
|
||||
end
|
||||
|
||||
test "XML" do
|
||||
clear_config([Pleroma.Web.Endpoint, :url, :host], "hyrule.world")
|
||||
clear_config([Pleroma.Web.WebFinger, :domain], "hyrule.world")
|
||||
|
||||
user =
|
||||
insert(:user,
|
||||
ap_id: "https://hyrule.world/users/zelda"
|
||||
)
|
||||
|
||||
response =
|
||||
build_conn()
|
||||
|> put_req_header("accept", "application/xrd+xml")
|
||||
|> get("/.well-known/webfinger?resource=acct:#{user.nickname}@localhost")
|
||||
|> response(200)
|
||||
|
||||
assert response =~ "<Alias>https://hyrule.world/users/zelda</Alias>"
|
||||
end
|
||||
end
|
||||
|
||||
test "Webfinger defaults to JSON when no Accept header is provided" do
|
||||
clear_config([Pleroma.Web.Endpoint, :url, :host], "hyrule.world")
|
||||
clear_config([Pleroma.Web.WebFinger, :domain], "hyrule.world")
|
||||
|
||||
user =
|
||||
insert(:user,
|
||||
ap_id: "https://hyrule.world/users/zelda",
|
||||
also_known_as: ["https://mushroom.kingdom/users/toad"]
|
||||
ap_id: "https://hyrule.world/users/zelda"
|
||||
)
|
||||
|
||||
response =
|
||||
build_conn()
|
||||
|> put_req_header("accept", "application/jrd+json")
|
||||
|> get("/.well-known/webfinger?resource=acct:#{user.nickname}@localhost")
|
||||
|> get("/.well-known/webfinger?resource=acct:#{user.nickname}@hyrule.world")
|
||||
|> json_response(200)
|
||||
|
||||
assert response["subject"] == "acct:#{user.nickname}@localhost"
|
||||
assert response["subject"] == "acct:#{user.nickname}@hyrule.world"
|
||||
|
||||
assert response["aliases"] == [
|
||||
"https://hyrule.world/users/zelda",
|
||||
"https://mushroom.kingdom/users/toad"
|
||||
"https://hyrule.world/users/zelda"
|
||||
]
|
||||
end
|
||||
|
||||
describe "Webfinger returns also_known_as / aliases in the response" do
|
||||
test "JSON" do
|
||||
clear_config([Pleroma.Web.Endpoint, :url, :host], "hyrule.world")
|
||||
clear_config([Pleroma.Web.WebFinger, :domain], "hyrule.world")
|
||||
|
||||
user =
|
||||
insert(:user,
|
||||
ap_id: "https://hyrule.world/users/zelda",
|
||||
also_known_as: [
|
||||
"https://mushroom.kingdom/users/toad",
|
||||
"https://luigi.mansion/users/kingboo"
|
||||
]
|
||||
)
|
||||
|
||||
response =
|
||||
build_conn()
|
||||
|> get("/.well-known/webfinger?resource=acct:#{user.nickname}@hyrule.world")
|
||||
|> json_response(200)
|
||||
|
||||
assert response["subject"] == "acct:#{user.nickname}@hyrule.world"
|
||||
|
||||
assert response["aliases"] == [
|
||||
"https://hyrule.world/users/zelda",
|
||||
"https://mushroom.kingdom/users/toad",
|
||||
"https://luigi.mansion/users/kingboo"
|
||||
]
|
||||
end
|
||||
|
||||
test "XML" do
|
||||
clear_config([Pleroma.Web.Endpoint, :url, :host], "hyrule.world")
|
||||
clear_config([Pleroma.Web.WebFinger, :domain], "hyrule.world")
|
||||
|
||||
user =
|
||||
insert(:user,
|
||||
ap_id: "https://hyrule.world/users/zelda",
|
||||
also_known_as: [
|
||||
"https://mushroom.kingdom/users/toad",
|
||||
"https://luigi.mansion/users/kingboo"
|
||||
]
|
||||
)
|
||||
|
||||
response =
|
||||
build_conn()
|
||||
|> put_req_header("accept", "application/xrd+xml")
|
||||
|> get("/.well-known/webfinger?resource=acct:#{user.nickname}@localhost")
|
||||
|> response(200)
|
||||
|
||||
assert response =~ "<Alias>https://hyrule.world/users/zelda</Alias>"
|
||||
assert response =~ "<Alias>https://mushroom.kingdom/users/toad</Alias>"
|
||||
assert response =~ "<Alias>https://luigi.mansion/users/kingboo</Alias>"
|
||||
end
|
||||
end
|
||||
|
||||
test "reach user on tld, while pleroma is running on subdomain" do
|
||||
clear_config([Pleroma.Web.Endpoint, :url, :host], "sub.example.com")
|
||||
|
||||
|
|
@ -72,17 +166,32 @@ defmodule Pleroma.Web.WebFinger.WebFingerControllerTest do
|
|||
assert response["aliases"] == ["https://sub.example.com/users/#{user.nickname}"]
|
||||
end
|
||||
|
||||
test "it returns 404 when user isn't found (JSON)" do
|
||||
result =
|
||||
build_conn()
|
||||
|> put_req_header("accept", "application/jrd+json")
|
||||
|> get("/.well-known/webfinger?resource=acct:jimm@localhost")
|
||||
|> json_response(404)
|
||||
describe "it returns 404 when user isn't found" do
|
||||
test "JSON" do
|
||||
result =
|
||||
build_conn()
|
||||
|> put_req_header("accept", "application/jrd+json")
|
||||
|> get("/.well-known/webfinger?resource=acct:jimm@localhost")
|
||||
|> json_response(404)
|
||||
|
||||
assert result == "Couldn't find user"
|
||||
assert result == "Couldn't find user"
|
||||
end
|
||||
|
||||
test "XML" do
|
||||
result =
|
||||
build_conn()
|
||||
|> put_req_header("accept", "application/xrd+xml")
|
||||
|> get("/.well-known/webfinger?resource=acct:jimm@localhost")
|
||||
|> response(404)
|
||||
|
||||
assert result == "Couldn't find user"
|
||||
end
|
||||
end
|
||||
|
||||
test "Webfinger XML" do
|
||||
test "Returns JSON when format is not supported" do
|
||||
clear_config([Pleroma.Web.Endpoint, :url, :host], "hyrule.world")
|
||||
clear_config([Pleroma.Web.WebFinger, :domain], "hyrule.world")
|
||||
|
||||
user =
|
||||
insert(:user,
|
||||
ap_id: "https://hyrule.world/users/zelda",
|
||||
|
|
@ -91,34 +200,16 @@ defmodule Pleroma.Web.WebFinger.WebFingerControllerTest do
|
|||
|
||||
response =
|
||||
build_conn()
|
||||
|> put_req_header("accept", "application/xrd+xml")
|
||||
|> get("/.well-known/webfinger?resource=acct:#{user.nickname}@localhost")
|
||||
|> response(200)
|
||||
|> put_req_header("accept", "text/html")
|
||||
|> get("/.well-known/webfinger?resource=acct:#{user.nickname}@hyrule.world")
|
||||
|> json_response(200)
|
||||
|
||||
assert response =~ "<Alias>https://hyrule.world/users/zelda</Alias>"
|
||||
assert response =~ "<Alias>https://mushroom.kingdom/users/toad</Alias>"
|
||||
end
|
||||
assert response["subject"] == "acct:#{user.nickname}@hyrule.world"
|
||||
|
||||
test "it returns 404 when user isn't found (XML)" do
|
||||
result =
|
||||
build_conn()
|
||||
|> put_req_header("accept", "application/xrd+xml")
|
||||
|> get("/.well-known/webfinger?resource=acct:jimm@localhost")
|
||||
|> response(404)
|
||||
|
||||
assert result == "Couldn't find user"
|
||||
end
|
||||
|
||||
test "Sends a 404 when invalid format" do
|
||||
user = insert(:user)
|
||||
|
||||
assert capture_log(fn ->
|
||||
assert_raise Phoenix.NotAcceptableError, fn ->
|
||||
build_conn()
|
||||
|> put_req_header("accept", "text/html")
|
||||
|> get("/.well-known/webfinger?resource=acct:#{user.nickname}@localhost")
|
||||
end
|
||||
end) =~ "no supported media type in accept header"
|
||||
assert response["aliases"] == [
|
||||
"https://hyrule.world/users/zelda",
|
||||
"https://mushroom.kingdom/users/toad"
|
||||
]
|
||||
end
|
||||
|
||||
test "Sends a 400 when resource param is missing" do
|
||||
|
|
|
|||
|
|
@ -39,6 +39,23 @@ defmodule Pleroma.Web.WebFingerTest do
|
|||
end
|
||||
end
|
||||
|
||||
test "requires exact match for Endpoint host or WebFinger domain" do
|
||||
clear_config([Pleroma.Web.WebFinger, :domain], "pleroma.dev")
|
||||
user = insert(:user)
|
||||
|
||||
assert {:error, "Couldn't find user"} ==
|
||||
WebFinger.webfinger("#{user.nickname}@#{Pleroma.Web.Endpoint.host()}xxxx", "JSON")
|
||||
|
||||
assert {:error, "Couldn't find user"} ==
|
||||
WebFinger.webfinger("#{user.nickname}@pleroma.devxxxx", "JSON")
|
||||
|
||||
assert {:ok, _} =
|
||||
WebFinger.webfinger("#{user.nickname}@#{Pleroma.Web.Endpoint.host()}", "JSON")
|
||||
|
||||
assert {:ok, _} =
|
||||
WebFinger.webfinger("#{user.nickname}@pleroma.dev", "JSON")
|
||||
end
|
||||
|
||||
describe "fingering" do
|
||||
test "returns error for nonsensical input" do
|
||||
assert {:error, _} = WebFinger.finger("bliblablu")
|
||||
|
|
|
|||
39
test/pleroma/workers/delete_worker_test.exs
Normal file
39
test/pleroma/workers/delete_worker_test.exs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Workers.DeleteWorkerTest do
|
||||
use Pleroma.DataCase, async: true
|
||||
use Oban.Testing, repo: Pleroma.Repo
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
alias Pleroma.Instances.Instance
|
||||
alias Pleroma.Tests.ObanHelpers
|
||||
alias Pleroma.Workers.DeleteWorker
|
||||
|
||||
describe "instance deletion" do
|
||||
test "creates individual Oban jobs for each user when deleting an instance" do
|
||||
user1 = insert(:user, nickname: "alice@example.com", name: "Alice")
|
||||
user2 = insert(:user, nickname: "bob@example.com", name: "Bob")
|
||||
|
||||
{:ok, job} = Instance.delete("example.com")
|
||||
|
||||
assert_enqueued(
|
||||
worker: DeleteWorker,
|
||||
args: %{"op" => "delete_instance", "host" => "example.com"}
|
||||
)
|
||||
|
||||
{:ok, :ok} = ObanHelpers.perform(job)
|
||||
|
||||
delete_user_jobs = all_enqueued(worker: DeleteWorker, args: %{"op" => "delete_user"})
|
||||
|
||||
assert length(delete_user_jobs) == 2
|
||||
|
||||
user_ids = [user1.id, user2.id]
|
||||
job_user_ids = Enum.map(delete_user_jobs, fn job -> job.args["user_id"] end)
|
||||
|
||||
assert Enum.sort(user_ids) == Enum.sort(job_user_ids)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -7,7 +7,9 @@ defmodule Pleroma.Workers.PublisherWorkerTest do
|
|||
use Oban.Testing, repo: Pleroma.Repo
|
||||
|
||||
import Pleroma.Factory
|
||||
import Mock
|
||||
|
||||
alias Pleroma.Instances
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Builder
|
||||
|
|
@ -37,4 +39,85 @@ defmodule Pleroma.Workers.PublisherWorkerTest do
|
|||
assert {:ok, %Oban.Job{priority: 0}} = Federator.publish(post)
|
||||
end
|
||||
end
|
||||
|
||||
describe "Server reachability:" do
|
||||
setup do
|
||||
user = insert(:user)
|
||||
remote_user = insert(:user, local: false, inbox: "https://example.com/inbox")
|
||||
{:ok, _, _} = Pleroma.User.follow(remote_user, user)
|
||||
{:ok, activity} = CommonAPI.post(user, %{status: "Test post"})
|
||||
|
||||
%{
|
||||
user: user,
|
||||
remote_user: remote_user,
|
||||
activity: activity
|
||||
}
|
||||
end
|
||||
|
||||
test "marks server as unreachable only on final failure", %{activity: activity} do
|
||||
with_mock Pleroma.Web.Federator,
|
||||
perform: fn :publish_one, _params -> {:error, :connection_error} end do
|
||||
# First attempt
|
||||
job = %Oban.Job{
|
||||
args: %{
|
||||
"op" => "publish_one",
|
||||
"params" => %{
|
||||
"inbox" => "https://example.com/inbox",
|
||||
"activity_id" => activity.id
|
||||
}
|
||||
},
|
||||
attempt: 1,
|
||||
max_attempts: 5
|
||||
}
|
||||
|
||||
assert {:error, :connection_error} = Pleroma.Workers.PublisherWorker.perform(job)
|
||||
assert Instances.reachable?("https://example.com/inbox")
|
||||
|
||||
# Final attempt
|
||||
job = %{job | attempt: 5}
|
||||
assert {:error, :connection_error} = Pleroma.Workers.PublisherWorker.perform(job)
|
||||
refute Instances.reachable?("https://example.com/inbox")
|
||||
end
|
||||
end
|
||||
|
||||
test "does not mark server as unreachable on successful publish", %{activity: activity} do
|
||||
with_mock Pleroma.Web.Federator,
|
||||
perform: fn :publish_one, _params -> {:ok, %{status: 200}} end do
|
||||
job = %Oban.Job{
|
||||
args: %{
|
||||
"op" => "publish_one",
|
||||
"params" => %{
|
||||
"inbox" => "https://example.com/inbox",
|
||||
"activity_id" => activity.id
|
||||
}
|
||||
},
|
||||
attempt: 1,
|
||||
max_attempts: 5
|
||||
}
|
||||
|
||||
assert :ok = Pleroma.Workers.PublisherWorker.perform(job)
|
||||
assert Instances.reachable?("https://example.com/inbox")
|
||||
end
|
||||
end
|
||||
|
||||
test "cancels job if server is unreachable", %{activity: activity} do
|
||||
# First mark the server as unreachable
|
||||
Instances.set_unreachable("https://example.com/inbox")
|
||||
refute Instances.reachable?("https://example.com/inbox")
|
||||
|
||||
job = %Oban.Job{
|
||||
args: %{
|
||||
"op" => "publish_one",
|
||||
"params" => %{
|
||||
"inbox" => "https://example.com/inbox",
|
||||
"activity_id" => activity.id
|
||||
}
|
||||
},
|
||||
attempt: 1,
|
||||
max_attempts: 5
|
||||
}
|
||||
|
||||
assert {:cancel, :unreachable} = Pleroma.Workers.PublisherWorker.perform(job)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
226
test/pleroma/workers/reachability_worker_test.exs
Normal file
226
test/pleroma/workers/reachability_worker_test.exs
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Workers.ReachabilityWorkerTest do
|
||||
use Pleroma.DataCase, async: true
|
||||
use Oban.Testing, repo: Pleroma.Repo
|
||||
|
||||
import Mock
|
||||
|
||||
alias Pleroma.Tests.ObanHelpers
|
||||
alias Pleroma.Workers.ReachabilityWorker
|
||||
|
||||
setup do
|
||||
ObanHelpers.wipe_all()
|
||||
:ok
|
||||
end
|
||||
|
||||
describe "progressive backoff phases" do
|
||||
test "starts with phase_1min and progresses through phases on failure" do
|
||||
domain = "example.com"
|
||||
|
||||
with_mocks([
|
||||
{Pleroma.HTTP, [], [get: fn _ -> {:error, :timeout} end]},
|
||||
{Pleroma.Instances, [], [set_reachable: fn _ -> :ok end]}
|
||||
]) do
|
||||
# Start with phase_1min
|
||||
job = %Oban.Job{
|
||||
args: %{"domain" => domain, "phase" => "phase_1min", "attempt" => 1}
|
||||
}
|
||||
|
||||
# First attempt fails
|
||||
assert {:error, :timeout} = ReachabilityWorker.perform(job)
|
||||
|
||||
# Should schedule retry for phase_1min (attempt 2)
|
||||
retry_jobs = all_enqueued(worker: ReachabilityWorker)
|
||||
assert length(retry_jobs) == 1
|
||||
[retry_job] = retry_jobs
|
||||
assert retry_job.args["phase"] == "phase_1min"
|
||||
assert retry_job.args["attempt"] == 2
|
||||
|
||||
# Clear jobs and simulate second attempt failure
|
||||
ObanHelpers.wipe_all()
|
||||
|
||||
retry_job = %Oban.Job{
|
||||
args: %{"domain" => domain, "phase" => "phase_1min", "attempt" => 2}
|
||||
}
|
||||
|
||||
assert {:error, :timeout} = ReachabilityWorker.perform(retry_job)
|
||||
|
||||
# Should schedule retry for phase_1min (attempt 3)
|
||||
retry_jobs = all_enqueued(worker: ReachabilityWorker)
|
||||
assert length(retry_jobs) == 1
|
||||
[retry_job] = retry_jobs
|
||||
assert retry_job.args["phase"] == "phase_1min"
|
||||
assert retry_job.args["attempt"] == 3
|
||||
|
||||
# Clear jobs and simulate third attempt failure (final attempt for phase_1min)
|
||||
ObanHelpers.wipe_all()
|
||||
|
||||
retry_job = %Oban.Job{
|
||||
args: %{"domain" => domain, "phase" => "phase_1min", "attempt" => 3}
|
||||
}
|
||||
|
||||
assert {:error, :timeout} = ReachabilityWorker.perform(retry_job)
|
||||
|
||||
# Should schedule retry for phase_1min (attempt 4)
|
||||
retry_jobs = all_enqueued(worker: ReachabilityWorker)
|
||||
assert length(retry_jobs) == 1
|
||||
[retry_job] = retry_jobs
|
||||
assert retry_job.args["phase"] == "phase_1min"
|
||||
assert retry_job.args["attempt"] == 4
|
||||
|
||||
# Clear jobs and simulate fourth attempt failure (final attempt for phase_1min)
|
||||
ObanHelpers.wipe_all()
|
||||
|
||||
retry_job = %Oban.Job{
|
||||
args: %{"domain" => domain, "phase" => "phase_1min", "attempt" => 4}
|
||||
}
|
||||
|
||||
assert {:error, :timeout} = ReachabilityWorker.perform(retry_job)
|
||||
|
||||
# Should schedule next phase (phase_15min)
|
||||
next_phase_jobs = all_enqueued(worker: ReachabilityWorker)
|
||||
assert length(next_phase_jobs) == 1
|
||||
[next_phase_job] = next_phase_jobs
|
||||
assert next_phase_job.args["phase"] == "phase_15min"
|
||||
assert next_phase_job.args["attempt"] == 1
|
||||
end
|
||||
end
|
||||
|
||||
test "progresses through all phases correctly" do
|
||||
domain = "example.com"
|
||||
|
||||
with_mocks([
|
||||
{Pleroma.HTTP, [], [get: fn _ -> {:error, :timeout} end]},
|
||||
{Pleroma.Instances, [], [set_reachable: fn _ -> :ok end]}
|
||||
]) do
|
||||
# Simulate all phases failing
|
||||
phases = ["phase_1min", "phase_15min", "phase_1hour", "phase_8hour", "phase_24hour"]
|
||||
|
||||
Enum.each(phases, fn phase ->
|
||||
{_interval, max_attempts, next_phase} = get_phase_config(phase)
|
||||
|
||||
# Simulate all attempts failing for this phase
|
||||
Enum.each(1..max_attempts, fn attempt ->
|
||||
job = %Oban.Job{args: %{"domain" => domain, "phase" => phase, "attempt" => attempt}}
|
||||
assert {:error, :timeout} = ReachabilityWorker.perform(job)
|
||||
|
||||
if attempt < max_attempts do
|
||||
# Should schedule retry for same phase
|
||||
retry_jobs = all_enqueued(worker: ReachabilityWorker)
|
||||
assert length(retry_jobs) == 1
|
||||
[retry_job] = retry_jobs
|
||||
assert retry_job.args["phase"] == phase
|
||||
assert retry_job.args["attempt"] == attempt + 1
|
||||
ObanHelpers.wipe_all()
|
||||
else
|
||||
# Should schedule next phase (except for final phase)
|
||||
if next_phase != "final" do
|
||||
next_phase_jobs = all_enqueued(worker: ReachabilityWorker)
|
||||
assert length(next_phase_jobs) == 1
|
||||
[next_phase_job] = next_phase_jobs
|
||||
assert next_phase_job.args["phase"] == next_phase
|
||||
assert next_phase_job.args["attempt"] == 1
|
||||
ObanHelpers.wipe_all()
|
||||
else
|
||||
# Final phase - no more jobs should be scheduled
|
||||
next_phase_jobs = all_enqueued(worker: ReachabilityWorker)
|
||||
assert length(next_phase_jobs) == 0
|
||||
end
|
||||
end
|
||||
end)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
test "succeeds and stops progression when instance becomes reachable" do
|
||||
domain = "example.com"
|
||||
|
||||
with_mocks([
|
||||
{Pleroma.HTTP, [], [get: fn _ -> {:ok, %{status: 200}} end]},
|
||||
{Pleroma.Instances, [], [set_reachable: fn _ -> :ok end]}
|
||||
]) do
|
||||
job = %Oban.Job{args: %{"domain" => domain, "phase" => "phase_1hour", "attempt" => 2}}
|
||||
|
||||
# Should succeed and not schedule any more jobs
|
||||
assert :ok = ReachabilityWorker.perform(job)
|
||||
|
||||
# Verify set_reachable was called
|
||||
assert_called(Pleroma.Instances.set_reachable("https://#{domain}"))
|
||||
|
||||
# No more jobs should be scheduled
|
||||
next_jobs = all_enqueued(worker: ReachabilityWorker)
|
||||
assert length(next_jobs) == 0
|
||||
end
|
||||
end
|
||||
|
||||
test "enforces uniqueness per domain using Oban's conflict detection" do
|
||||
domain = "example.com"
|
||||
|
||||
# Insert first job for the domain
|
||||
job1 =
|
||||
%{
|
||||
"domain" => domain,
|
||||
"phase" => "phase_1min",
|
||||
"attempt" => 1
|
||||
}
|
||||
|> ReachabilityWorker.new()
|
||||
|> Oban.insert()
|
||||
|
||||
assert {:ok, _} = job1
|
||||
|
||||
# Try to insert a second job for the same domain with different phase/attempt
|
||||
job2 =
|
||||
%{
|
||||
"domain" => domain,
|
||||
"phase" => "phase_15min",
|
||||
"attempt" => 1
|
||||
}
|
||||
|> ReachabilityWorker.new()
|
||||
|> Oban.insert()
|
||||
|
||||
# Should fail due to uniqueness constraint (conflict)
|
||||
assert {:ok, %Oban.Job{conflict?: true}} = job2
|
||||
|
||||
# Verify only one job exists for this domain
|
||||
jobs = all_enqueued(worker: ReachabilityWorker)
|
||||
assert length(jobs) == 1
|
||||
[existing_job] = jobs
|
||||
assert existing_job.args["domain"] == domain
|
||||
assert existing_job.args["phase"] == "phase_1min"
|
||||
end
|
||||
|
||||
test "handles new jobs with only domain argument and transitions them to the first phase" do
|
||||
domain = "legacy.example.com"
|
||||
|
||||
with_mocks([
|
||||
{Pleroma.Instances, [], [set_reachable: fn _ -> :ok end]}
|
||||
]) do
|
||||
# Create a job with only domain (legacy format)
|
||||
job = %Oban.Job{
|
||||
args: %{"domain" => domain}
|
||||
}
|
||||
|
||||
# Should reschedule with phase_1min and attempt 1
|
||||
assert :ok = ReachabilityWorker.perform(job)
|
||||
|
||||
# Check that a new job was scheduled with the correct format
|
||||
scheduled_jobs = all_enqueued(worker: ReachabilityWorker)
|
||||
assert length(scheduled_jobs) == 1
|
||||
[scheduled_job] = scheduled_jobs
|
||||
assert scheduled_job.args["domain"] == domain
|
||||
assert scheduled_job.args["phase"] == "phase_1min"
|
||||
assert scheduled_job.args["attempt"] == 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp get_phase_config("phase_1min"), do: {1, 4, "phase_15min"}
|
||||
defp get_phase_config("phase_15min"), do: {15, 4, "phase_1hour"}
|
||||
defp get_phase_config("phase_1hour"), do: {60, 4, "phase_8hour"}
|
||||
defp get_phase_config("phase_8hour"), do: {480, 4, "phase_24hour"}
|
||||
defp get_phase_config("phase_24hour"), do: {1440, 4, "final"}
|
||||
defp get_phase_config("final"), do: {nil, 0, nil}
|
||||
end
|
||||
|
|
@ -3,13 +3,14 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Workers.ReceiverWorkerTest do
|
||||
use Pleroma.DataCase
|
||||
use Pleroma.DataCase, async: true
|
||||
use Oban.Testing, repo: Pleroma.Repo
|
||||
|
||||
import Mock
|
||||
import Pleroma.Factory
|
||||
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.Federator
|
||||
alias Pleroma.Workers.ReceiverWorker
|
||||
|
||||
|
|
@ -243,4 +244,62 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do
|
|||
|
||||
assert {:cancel, _} = ReceiverWorker.perform(oban_job)
|
||||
end
|
||||
|
||||
describe "Server reachability:" do
|
||||
setup do
|
||||
user = insert(:user)
|
||||
remote_user = insert(:user, local: false, ap_id: "https://example.com/users/remote")
|
||||
{:ok, _, _} = Pleroma.User.follow(user, remote_user)
|
||||
{:ok, activity} = CommonAPI.post(remote_user, %{status: "Test post"})
|
||||
|
||||
%{
|
||||
user: user,
|
||||
remote_user: remote_user,
|
||||
activity: activity
|
||||
}
|
||||
end
|
||||
|
||||
test "schedules ReachabilityWorker if host is unreachable", %{activity: activity} do
|
||||
with_mocks [
|
||||
{Pleroma.Web.ActivityPub.Transmogrifier, [],
|
||||
[handle_incoming: fn _ -> {:ok, activity} end]},
|
||||
{Pleroma.Instances, [], [reachable?: fn _ -> false end]},
|
||||
{Pleroma.Web.Federator, [], [perform: fn :incoming_ap_doc, _params -> {:ok, nil} end]}
|
||||
] do
|
||||
job = %Oban.Job{
|
||||
args: %{
|
||||
"op" => "incoming_ap_doc",
|
||||
"params" => activity.data
|
||||
}
|
||||
}
|
||||
|
||||
Pleroma.Workers.ReceiverWorker.perform(job)
|
||||
|
||||
assert_enqueued(
|
||||
worker: Pleroma.Workers.ReachabilityWorker,
|
||||
args: %{"domain" => "example.com"}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
test "does not schedule ReachabilityWorker if host is reachable", %{activity: activity} do
|
||||
with_mocks [
|
||||
{Pleroma.Web.ActivityPub.Transmogrifier, [],
|
||||
[handle_incoming: fn _ -> {:ok, activity} end]},
|
||||
{Pleroma.Instances, [], [reachable?: fn _ -> true end]},
|
||||
{Pleroma.Web.Federator, [], [perform: fn :incoming_ap_doc, _params -> {:ok, nil} end]}
|
||||
] do
|
||||
job = %Oban.Job{
|
||||
args: %{
|
||||
"op" => "incoming_ap_doc",
|
||||
"params" => activity.data
|
||||
}
|
||||
}
|
||||
|
||||
Pleroma.Workers.ReceiverWorker.perform(job)
|
||||
|
||||
refute_enqueued(worker: Pleroma.Workers.ReachabilityWorker)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Workers.RemoteFetcherWorkerTest do
|
||||
use Pleroma.DataCase
|
||||
use Pleroma.DataCase, async: true
|
||||
use Oban.Testing, repo: Pleroma.Repo
|
||||
|
||||
alias Pleroma.Workers.RemoteFetcherWorker
|
||||
|
|
|
|||
|
|
@ -119,6 +119,12 @@ defmodule Pleroma.DataCase do
|
|||
Mox.stub_with(Pleroma.StubbedHTTPSignaturesMock, Pleroma.Test.HTTPSignaturesProxy)
|
||||
|
||||
Mox.stub_with(Pleroma.DateTimeMock, Pleroma.DateTime.Impl)
|
||||
Mox.stub_with(Pleroma.SignatureMock, Pleroma.Signature)
|
||||
|
||||
Mox.stub_with(
|
||||
Pleroma.Web.ActivityPub.TransmogrifierMock,
|
||||
Pleroma.Web.ActivityPub.Transmogrifier
|
||||
)
|
||||
end
|
||||
|
||||
def ensure_local_uploader(context) do
|
||||
|
|
|
|||
|
|
@ -40,3 +40,9 @@ Mox.defmock(Pleroma.Language.LanguageDetectorMock,
|
|||
|
||||
Mox.defmock(Pleroma.DateTimeMock, for: Pleroma.DateTime)
|
||||
Mox.defmock(Pleroma.MogrifyMock, for: Pleroma.MogrifyBehaviour)
|
||||
|
||||
Mox.defmock(Pleroma.SignatureMock, for: Pleroma.Signature.API)
|
||||
|
||||
Mox.defmock(Pleroma.Web.ActivityPub.TransmogrifierMock,
|
||||
for: Pleroma.Web.ActivityPub.Transmogrifier.API
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@
|
|||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
Code.put_compiler_option(:warnings_as_errors, true)
|
||||
|
||||
ExUnit.configure(capture_log: true, max_cases: System.schedulers_online())
|
||||
|
||||
ExUnit.start(exclude: [:federated])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue