Merge remote-tracking branch 'origin/develop' into translate-posts

This commit is contained in:
mkljczk 2025-02-22 14:07:23 +01:00
commit 013c60e13a
52 changed files with 955 additions and 31 deletions

BIN
test/fixtures/break_analyze.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 KiB

View file

@ -0,0 +1,90 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
{
"atomUri": "ostatus:atomUri",
"conversation": "ostatus:conversation",
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
"ostatus": "http://ostatus.org#",
"sensitive": "as:sensitive",
"toot": "http://joinmastodon.org/ns#",
"votersCount": "toot:votersCount"
},
"https://w3id.org/security/v1"
],
"actor": "https://pol.social/users/mkljczk",
"cc": ["https://www.w3.org/ns/activitystreams#Public",
"https://pol.social/users/aemstuz", "https://gts.mkljczk.pl/users/mkljczk",
"https://pl.fediverse.pl/users/mkljczk",
"https://fedi.kutno.pl/users/mkljczk"],
"id": "https://pol.social/users/mkljczk/statuses/113907871635572263#updates/1738096776",
"object": {
"atomUri": "https://pol.social/users/mkljczk/statuses/113907871635572263",
"attachment": [],
"attributedTo": "https://pol.social/users/mkljczk",
"cc": ["https://www.w3.org/ns/activitystreams#Public",
"https://pol.social/users/aemstuz", "https://gts.mkljczk.pl/users/mkljczk",
"https://pl.fediverse.pl/users/mkljczk",
"https://fedi.kutno.pl/users/mkljczk"],
"content": "<p>test</p>",
"contentMap": {
"pl": "<p>test</p>"
},
"conversation": "https://fedi.kutno.pl/contexts/43c14c70-d3fb-42b4-a36d-4eacfab9695a",
"id": "https://pol.social/users/mkljczk/statuses/113907871635572263",
"inReplyTo": "https://pol.social/users/aemstuz/statuses/113907854282654767",
"inReplyToAtomUri": "https://pol.social/users/aemstuz/statuses/113907854282654767",
"likes": {
"id": "https://pol.social/users/mkljczk/statuses/113907871635572263/likes",
"totalItems": 1,
"type": "Collection"
},
"published": "2025-01-28T20:29:45Z",
"replies": {
"first": {
"items": [],
"next": "https://pol.social/users/mkljczk/statuses/113907871635572263/replies?only_other_accounts=true&page=true",
"partOf": "https://pol.social/users/mkljczk/statuses/113907871635572263/replies",
"type": "CollectionPage"
},
"id": "https://pol.social/users/mkljczk/statuses/113907871635572263/replies",
"type": "Collection"
},
"sensitive": false,
"shares": {
"id": "https://pol.social/users/mkljczk/statuses/113907871635572263/shares",
"totalItems": 0,
"type": "Collection"
},
"summary": null,
"tag": [
{
"href": "https://pol.social/users/aemstuz",
"name": "@aemstuz",
"type": "Mention"
},
{
"href": "https://gts.mkljczk.pl/users/mkljczk",
"name": "@mkljczk@gts.mkljczk.pl",
"type": "Mention"
},
{
"href": "https://pl.fediverse.pl/users/mkljczk",
"name": "@mkljczk@fediverse.pl",
"type": "Mention"
},
{
"href": "https://fedi.kutno.pl/users/mkljczk",
"name": "@mkljczk@fedi.kutno.pl",
"type": "Mention"
}
],
"to": ["https://pol.social/users/mkljczk/followers"],
"type": "Note",
"updated": "2025-01-28T20:39:36Z",
"url": "https://pol.social/@mkljczk/113907871635572263"
},
"published": "2025-01-28T20:39:36Z",
"to": ["https://pol.social/users/mkljczk/followers"],
"type": "Update"
}

View file

@ -411,7 +411,7 @@ defmodule Mix.Tasks.Pleroma.DatabaseTest do
["scheduled_activities"],
["schema_migrations"],
["thread_mutes"],
# ["user_follows_hashtag"], # not in pleroma
["user_follows_hashtag"],
# ["user_frontend_setting_profiles"], # not in pleroma
["user_invite_tokens"],
["user_notes"],

View file

@ -34,6 +34,20 @@ defmodule Pleroma.Upload.Filter.AnalyzeMetadataTest do
assert meta.blurhash == "eXJi-E:SwCEm5rCmn$+YWYn+15K#5A$xxCi{SiV]s*W:Efa#s.jE-T"
end
test "it gets dimensions for grayscale images" do
upload = %Pleroma.Upload{
name: "break_analyze.png",
content_type: "image/png",
path: Path.absname("test/fixtures/break_analyze.png"),
tempfile: Path.absname("test/fixtures/break_analyze.png")
}
{:ok, :filtered, meta} = AnalyzeMetadata.filter(upload)
assert %{width: 1410, height: 2048} = meta
assert is_nil(meta.blurhash)
end
test "adds the dimensions for videos" do
upload = %Pleroma.Upload{
name: "coolvideo.mp4",

View file

@ -185,13 +185,13 @@ defmodule Pleroma.User.BackupTest do
%{"@language" => "und"}
],
"bookmarks" => "bookmarks.json",
"followers" => "http://cofe.io/users/cofe/followers",
"following" => "http://cofe.io/users/cofe/following",
"followers" => "followers.json",
"following" => "following.json",
"id" => "http://cofe.io/users/cofe",
"inbox" => "http://cofe.io/users/cofe/inbox",
"likes" => "likes.json",
"name" => "Cofe",
"outbox" => "http://cofe.io/users/cofe/outbox",
"outbox" => "outbox.json",
"preferredUsername" => "cofe",
"publicKey" => %{
"id" => "http://cofe.io/users/cofe#main-key",

View file

@ -2919,4 +2919,74 @@ defmodule Pleroma.UserTest do
assert [%{"verified_at" => ^verified_at}] = user.fields
end
describe "follow_hashtag/2" do
test "should follow a hashtag" do
user = insert(:user)
hashtag = insert(:hashtag)
assert {:ok, _} = user |> User.follow_hashtag(hashtag)
user = User.get_cached_by_ap_id(user.ap_id)
assert user.followed_hashtags |> Enum.count() == 1
assert hashtag.name in Enum.map(user.followed_hashtags, fn %{name: name} -> name end)
end
test "should not follow a hashtag twice" do
user = insert(:user)
hashtag = insert(:hashtag)
assert {:ok, _} = user |> User.follow_hashtag(hashtag)
assert {:ok, _} = user |> User.follow_hashtag(hashtag)
user = User.get_cached_by_ap_id(user.ap_id)
assert user.followed_hashtags |> Enum.count() == 1
assert hashtag.name in Enum.map(user.followed_hashtags, fn %{name: name} -> name end)
end
test "can follow multiple hashtags" do
user = insert(:user)
hashtag = insert(:hashtag)
other_hashtag = insert(:hashtag)
assert {:ok, _} = user |> User.follow_hashtag(hashtag)
assert {:ok, _} = user |> User.follow_hashtag(other_hashtag)
user = User.get_cached_by_ap_id(user.ap_id)
assert user.followed_hashtags |> Enum.count() == 2
assert hashtag.name in Enum.map(user.followed_hashtags, fn %{name: name} -> name end)
assert other_hashtag.name in Enum.map(user.followed_hashtags, fn %{name: name} -> name end)
end
end
describe "unfollow_hashtag/2" do
test "should unfollow a hashtag" do
user = insert(:user)
hashtag = insert(:hashtag)
assert {:ok, _} = user |> User.follow_hashtag(hashtag)
assert {:ok, _} = user |> User.unfollow_hashtag(hashtag)
user = User.get_cached_by_ap_id(user.ap_id)
assert user.followed_hashtags |> Enum.count() == 0
end
test "should not error when trying to unfollow a hashtag twice" do
user = insert(:user)
hashtag = insert(:hashtag)
assert {:ok, _} = user |> User.follow_hashtag(hashtag)
assert {:ok, _} = user |> User.unfollow_hashtag(hashtag)
assert {:ok, _} = user |> User.unfollow_hashtag(hashtag)
user = User.get_cached_by_ap_id(user.ap_id)
assert user.followed_hashtags |> Enum.count() == 0
end
end
end

View file

@ -867,6 +867,33 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
end
end
describe "fetch activities for followed hashtags" do
test "it should return public activities that reference a given hashtag" do
hashtag = insert(:hashtag, name: "tenshi")
user = insert(:user)
other_user = insert(:user)
{:ok, normally_visible} =
CommonAPI.post(other_user, %{status: "hello :)", visibility: "public"})
{:ok, public} = CommonAPI.post(user, %{status: "maji #tenshi", visibility: "public"})
{:ok, _unrelated} = CommonAPI.post(user, %{status: "dai #tensh", visibility: "public"})
{:ok, unlisted} = CommonAPI.post(user, %{status: "maji #tenshi", visibility: "unlisted"})
{:ok, _private} = CommonAPI.post(user, %{status: "maji #tenshi", visibility: "private"})
activities =
ActivityPub.fetch_activities([other_user.follower_address], %{
followed_hashtags: [hashtag.id]
})
assert length(activities) == 3
normal_id = normally_visible.id
public_id = public.id
unlisted_id = unlisted.id
assert [%{id: ^normal_id}, %{id: ^public_id}, %{id: ^unlisted_id}] = activities
end
end
describe "fetch activities in context" do
test "retrieves activities that have a given context" do
{:ok, activity} = ActivityBuilder.insert(%{"type" => "Create", "context" => "2hu"})

View file

@ -128,6 +128,17 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidatorTest
%{valid?: true} = ArticleNotePageValidator.cast_and_validate(note)
end
test "a Note with validated likes collection validates" do
insert(:user, ap_id: "https://pol.social/users/mkljczk")
%{"object" => note} =
"test/fixtures/mastodon-update-with-likes.json"
|> File.read!()
|> Jason.decode!()
%{valid?: true} = ArticleNotePageValidator.cast_and_validate(note)
end
test "Fedibird quote post" do
insert(:user, ap_id: "https://fedibird.com/users/noellabo")

View file

@ -639,5 +639,14 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
processed = Transmogrifier.prepare_object(original)
assert processed["formerRepresentations"] == original["formerRepresentations"]
end
test "it uses contentMap to specify post language" do
user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{status: "Cześć", language: "pl"})
object = Transmogrifier.prepare_object(activity.object.data)
assert %{"contentMap" => %{"pl" => "Cześć"}} = object
end
end
end

View file

@ -0,0 +1,159 @@
defmodule Pleroma.Web.MastodonAPI.TagControllerTest do
use Pleroma.Web.ConnCase
import Pleroma.Factory
import Tesla.Mock
alias Pleroma.User
setup do
mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
:ok
end
describe "GET /api/v1/tags/:id" do
test "returns 200 with tag" do
%{user: user, conn: conn} = oauth_access(["read"])
tag = insert(:hashtag, name: "jubjub")
{:ok, _user} = User.follow_hashtag(user, tag)
response =
conn
|> get("/api/v1/tags/jubjub")
|> json_response_and_validate_schema(200)
assert %{
"name" => "jubjub",
"url" => "http://localhost:4001/tags/jubjub",
"history" => [],
"following" => true
} = response
end
test "returns 404 with unknown tag" do
%{conn: conn} = oauth_access(["read"])
conn
|> get("/api/v1/tags/jubjub")
|> json_response_and_validate_schema(404)
end
end
describe "POST /api/v1/tags/:id/follow" do
test "should follow a hashtag" do
%{user: user, conn: conn} = oauth_access(["write:follows"])
hashtag = insert(:hashtag, name: "jubjub")
response =
conn
|> post("/api/v1/tags/jubjub/follow")
|> json_response_and_validate_schema(200)
assert response["following"] == true
user = User.get_cached_by_ap_id(user.ap_id)
assert User.following_hashtag?(user, hashtag)
end
test "should 404 if hashtag doesn't exist" do
%{conn: conn} = oauth_access(["write:follows"])
response =
conn
|> post("/api/v1/tags/rubrub/follow")
|> json_response_and_validate_schema(404)
assert response["error"] == "Hashtag not found"
end
end
describe "POST /api/v1/tags/:id/unfollow" do
test "should unfollow a hashtag" do
%{user: user, conn: conn} = oauth_access(["write:follows"])
hashtag = insert(:hashtag, name: "jubjub")
{:ok, user} = User.follow_hashtag(user, hashtag)
response =
conn
|> post("/api/v1/tags/jubjub/unfollow")
|> json_response_and_validate_schema(200)
assert response["following"] == false
user = User.get_cached_by_ap_id(user.ap_id)
refute User.following_hashtag?(user, hashtag)
end
test "should 404 if hashtag doesn't exist" do
%{conn: conn} = oauth_access(["write:follows"])
response =
conn
|> post("/api/v1/tags/rubrub/unfollow")
|> json_response_and_validate_schema(404)
assert response["error"] == "Hashtag not found"
end
end
describe "GET /api/v1/followed_tags" do
test "should list followed tags" do
%{user: user, conn: conn} = oauth_access(["read:follows"])
response =
conn
|> get("/api/v1/followed_tags")
|> json_response_and_validate_schema(200)
assert Enum.empty?(response)
hashtag = insert(:hashtag, name: "jubjub")
{:ok, _user} = User.follow_hashtag(user, hashtag)
response =
conn
|> get("/api/v1/followed_tags")
|> json_response_and_validate_schema(200)
assert [%{"name" => "jubjub"}] = response
end
test "should include a link header to paginate" do
%{user: user, conn: conn} = oauth_access(["read:follows"])
for i <- 1..21 do
hashtag = insert(:hashtag, name: "jubjub#{i}}")
{:ok, _user} = User.follow_hashtag(user, hashtag)
end
response =
conn
|> get("/api/v1/followed_tags")
json = json_response_and_validate_schema(response, 200)
assert Enum.count(json) == 20
assert [link_header] = get_resp_header(response, "link")
assert link_header =~ "rel=\"next\""
next_link = extract_next_link_header(link_header)
response =
conn
|> get(next_link)
|> json_response_and_validate_schema(200)
assert Enum.count(response) == 1
end
test "should refuse access without read:follows scope" do
%{conn: conn} = oauth_access(["write"])
conn
|> get("/api/v1/followed_tags")
|> json_response_and_validate_schema(403)
end
end
defp extract_next_link_header(header) do
[_, next_link] = Regex.run(~r{<(?<next_link>.*)>; rel="next"}, header)
next_link
end
end

View file

@ -248,8 +248,8 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do
response = get(conn, url)
assert response.status == 302
assert redirected_to(response) == media_proxy_url
assert response.status == 301
assert redirected_to(response, 301) == media_proxy_url
end
test "with `static` param and non-GIF image preview requested, " <>
@ -290,8 +290,8 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do
response = get(conn, url)
assert response.status == 302
assert redirected_to(response) == media_proxy_url
assert response.status == 301
assert redirected_to(response, 301) == media_proxy_url
end
test "thumbnails PNG images into PNG", %{
@ -356,5 +356,32 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do
assert response.status == 302
assert redirected_to(response) == media_proxy_url
end
test "redirects to media proxy URI with 301 when image is too small for preview", %{
conn: conn,
url: url,
media_proxy_url: media_proxy_url
} do
clear_config([:media_preview_proxy],
enabled: true,
min_content_length: 1000,
image_quality: 85,
thumbnail_max_width: 100,
thumbnail_max_height: 100
)
Tesla.Mock.mock(fn
%{method: :head, url: ^media_proxy_url} ->
%Tesla.Env{
status: 200,
body: "",
headers: [{"content-type", "image/png"}, {"content-length", "500"}]
}
end)
response = get(conn, url)
assert response.status == 301
assert redirected_to(response, 301) == media_proxy_url
end
end
end

View file

@ -83,4 +83,23 @@ defmodule Pleroma.Web.RichMedia.CardTest do
Card.get_by_activity(activity)
)
end
test "refuses to crawl URL in activity from ignored host/domain" do
clear_config([:rich_media, :ignore_hosts], ["example.com"])
user = insert(:user)
url = "https://example.com/ogp"
{:ok, activity} =
CommonAPI.post(user, %{
status: "[test](#{url})",
content_type: "text/markdown"
})
refute_enqueued(
worker: RichMediaWorker,
args: %{"url" => url, "activity_id" => activity.id}
)
end
end

View file

@ -558,6 +558,36 @@ defmodule Pleroma.Web.StreamerTest do
assert_receive {:render_with_user, _, "status_update.json", ^create, _}
refute Streamer.filtered_by_user?(user, edited)
end
test "it streams posts containing followed hashtags on the 'user' stream", %{
user: user,
token: oauth_token
} do
hashtag = insert(:hashtag, %{name: "tenshi"})
other_user = insert(:user)
{:ok, user} = User.follow_hashtag(user, hashtag)
Streamer.get_topic_and_add_socket("user", user, oauth_token)
{:ok, activity} = CommonAPI.post(other_user, %{status: "hey #tenshi"})
assert_receive {:render_with_user, _, "update.json", ^activity, _}
end
test "should not stream private posts containing followed hashtags on the 'user' stream", %{
user: user,
token: oauth_token
} do
hashtag = insert(:hashtag, %{name: "tenshi"})
other_user = insert(:user)
{:ok, user} = User.follow_hashtag(user, hashtag)
Streamer.get_topic_and_add_socket("user", user, oauth_token)
{:ok, activity} =
CommonAPI.post(other_user, %{status: "hey #tenshi", visibility: "private"})
refute_receive {:render_with_user, _, "update.json", ^activity, _}
end
end
describe "public streams" do

View file

@ -668,4 +668,11 @@ defmodule Pleroma.Factory do
|> Map.merge(params)
|> Pleroma.Announcement.add_rendered_properties()
end
def hashtag_factory(params \\ %{}) do
%Pleroma.Hashtag{
name: "test #{sequence(:hashtag_name, & &1)}"
}
|> Map.merge(params)
end
end