1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
10 alias Pleroma.Conversation.Participation
15 alias Pleroma.UserRelationship
16 alias Pleroma.Web.CommonAPI
17 alias Pleroma.Web.MastodonAPI.AccountView
18 alias Pleroma.Web.MastodonAPI.StatusView
20 import Pleroma.Factory
22 import OpenApiSpex.TestAssertions
25 mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
29 test "has an emoji reaction list" do
31 other_user = insert(:user)
32 third_user = insert(:user)
33 {:ok, activity} = CommonAPI.post(user, %{status: "dae cofe??"})
35 {:ok, _} = CommonAPI.react_with_emoji(activity.id, user, "☕")
36 {:ok, _} = CommonAPI.react_with_emoji(activity.id, user, ":dinosaur:")
37 {:ok, _} = CommonAPI.react_with_emoji(activity.id, third_user, "🍵")
38 {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕")
39 {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, ":dinosaur:")
41 activity = Repo.get(Activity, activity.id)
42 status = StatusView.render("show.json", activity: activity)
44 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
46 assert status[:pleroma][:emoji_reactions] == [
47 %{name: "☕", count: 2, me: false, url: nil, account_ids: [other_user.id, user.id]},
52 url: "http://localhost:4001/emoji/dino walking.gif",
53 account_ids: [other_user.id, user.id]
55 %{name: "🍵", count: 1, me: false, url: nil, account_ids: [third_user.id]}
58 status = StatusView.render("show.json", activity: activity, for: user)
60 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
62 assert status[:pleroma][:emoji_reactions] == [
63 %{name: "☕", count: 2, me: true, url: nil, account_ids: [other_user.id, user.id]},
68 url: "http://localhost:4001/emoji/dino walking.gif",
69 account_ids: [other_user.id, user.id]
71 %{name: "🍵", count: 1, me: false, url: nil, account_ids: [third_user.id]}
75 test "works correctly with badly formatted emojis" do
77 {:ok, activity} = CommonAPI.post(user, %{status: "yo"})
80 |> Object.normalize(fetch: false)
81 |> Object.update_data(%{"reactions" => %{"☕" => [user.ap_id], "x" => 1}})
83 activity = Activity.get_by_id(activity.id)
84 status = StatusView.render("show.json", activity: activity, for: user)
86 assert status[:pleroma][:emoji_reactions] == [
87 %{name: "☕", count: 1, me: true, url: nil, account_ids: [user.id]}
91 test "doesn't show reactions from muted and blocked users" do
93 other_user = insert(:user)
94 third_user = insert(:user)
96 {:ok, activity} = CommonAPI.post(user, %{status: "dae cofe??"})
98 {:ok, _} = User.mute(user, other_user)
99 {:ok, _} = User.block(other_user, third_user)
101 {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕")
103 activity = Repo.get(Activity, activity.id)
104 status = StatusView.render("show.json", activity: activity)
106 assert status[:pleroma][:emoji_reactions] == [
107 %{name: "☕", count: 1, me: false, url: nil, account_ids: [other_user.id]}
110 status = StatusView.render("show.json", activity: activity, for: user)
112 assert status[:pleroma][:emoji_reactions] == []
114 {:ok, _} = CommonAPI.react_with_emoji(activity.id, third_user, "☕")
116 status = StatusView.render("show.json", activity: activity)
118 assert status[:pleroma][:emoji_reactions] == [
124 account_ids: [third_user.id, other_user.id]
128 status = StatusView.render("show.json", activity: activity, for: user)
130 assert status[:pleroma][:emoji_reactions] == [
131 %{name: "☕", count: 1, me: false, url: nil, account_ids: [third_user.id]}
134 status = StatusView.render("show.json", activity: activity, for: other_user)
136 assert status[:pleroma][:emoji_reactions] == [
137 %{name: "☕", count: 1, me: true, url: nil, account_ids: [other_user.id]}
141 test "loads and returns the direct conversation id when given the `with_direct_conversation_id` option" do
144 {:ok, activity} = CommonAPI.post(user, %{status: "Hey @shp!", visibility: "direct"})
145 [participation] = Participation.for_user(user)
148 StatusView.render("show.json",
150 with_direct_conversation_id: true,
154 assert status[:pleroma][:direct_conversation_id] == participation.id
156 status = StatusView.render("show.json", activity: activity, for: user)
157 assert status[:pleroma][:direct_conversation_id] == nil
158 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
161 test "returns the direct conversation id when given the `direct_conversation_id` option" do
164 {:ok, activity} = CommonAPI.post(user, %{status: "Hey @shp!", visibility: "direct"})
165 [participation] = Participation.for_user(user)
168 StatusView.render("show.json",
170 direct_conversation_id: participation.id,
174 assert status[:pleroma][:direct_conversation_id] == participation.id
175 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
178 test "returns a temporary ap_id based user for activities missing db users" do
181 {:ok, activity} = CommonAPI.post(user, %{status: "Hey @shp!", visibility: "direct"})
184 User.invalidate_cache(user)
187 "https://localhost/.well-known/webfinger?resource=acct:#{user.nickname}@localhost"
189 Tesla.Mock.mock_global(fn
190 %{method: :get, url: "http://localhost/.well-known/host-meta"} ->
191 %Tesla.Env{status: 404, body: ""}
193 %{method: :get, url: "https://localhost/.well-known/host-meta"} ->
194 %Tesla.Env{status: 404, body: ""}
200 %Tesla.Env{status: 404, body: ""}
203 %{account: ms_user} = StatusView.render("show.json", activity: activity)
205 assert ms_user.acct == "erroruser@example.com"
208 test "tries to get a user by nickname if fetching by ap_id doesn't work" do
211 {:ok, activity} = CommonAPI.post(user, %{status: "Hey @shp!", visibility: "direct"})
215 |> Ecto.Changeset.change(%{ap_id: "#{user.ap_id}/extension/#{user.nickname}"})
218 User.invalidate_cache(user)
220 result = StatusView.render("show.json", activity: activity)
222 assert result[:account][:id] == to_string(user.id)
223 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
226 test "a note with null content" do
227 note = insert(:note_activity)
228 note_object = Object.normalize(note, fetch: false)
232 |> Map.put("content", nil)
234 Object.change(note_object, %{data: data})
235 |> Object.update_and_set_cache()
237 User.get_cached_by_ap_id(note.data["actor"])
239 status = StatusView.render("show.json", %{activity: note})
241 assert status.content == ""
242 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
245 test "a note activity" do
246 note = insert(:note_activity)
247 object_data = Object.normalize(note, fetch: false).data
248 user = User.get_cached_by_ap_id(note.data["actor"])
250 convo_id = :erlang.crc32(object_data["context"]) |> Bitwise.band(Bitwise.bnot(0x8000_0000))
252 status = StatusView.render("show.json", %{activity: note})
255 (object_data["published"] || "")
256 |> String.replace(~r/\.\d+Z/, ".000Z")
259 id: to_string(note.id),
260 uri: object_data["id"],
261 url: Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, note),
262 account: AccountView.render("show.json", %{user: user, skip_visibility_check: true}),
264 in_reply_to_account_id: nil,
267 content: HTML.filter_tags(object_data["content"]),
269 created_at: created_at,
280 spoiler_text: HTML.filter_tags(object_data["summary"]),
281 visibility: "public",
282 media_attachments: [],
287 name: "#{hd(object_data["tag"])}",
288 url: "http://localhost:4001/tag/#{hd(object_data["tag"])}"
297 static_url: "corndog.png",
298 visible_in_picker: false
303 conversation_id: convo_id,
304 context: object_data["context"],
305 in_reply_to_account_acct: nil,
306 content: %{"text/plain" => HTML.strip_tags(object_data["content"])},
307 spoiler_text: %{"text/plain" => HTML.strip_tags(object_data["summary"])},
309 direct_conversation_id: nil,
312 parent_visible: false,
316 source: HTML.filter_tags(object_data["content"])
322 assert status == expected
323 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
326 test "tells if the message is muted for some reason" do
328 other_user = insert(:user)
330 {:ok, _user_relationships} = User.mute(user, other_user)
332 {:ok, activity} = CommonAPI.post(other_user, %{status: "test"})
334 relationships_opt = UserRelationship.view_relationships_option(user, [other_user])
336 opts = %{activity: activity}
337 status = StatusView.render("show.json", opts)
338 assert status.muted == false
339 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
341 status = StatusView.render("show.json", Map.put(opts, :relationships, relationships_opt))
342 assert status.muted == false
344 for_opts = %{activity: activity, for: user}
345 status = StatusView.render("show.json", for_opts)
346 assert status.muted == true
348 status = StatusView.render("show.json", Map.put(for_opts, :relationships, relationships_opt))
349 assert status.muted == true
350 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
353 test "tells if the message is thread muted" do
355 other_user = insert(:user)
357 {:ok, _user_relationships} = User.mute(user, other_user)
359 {:ok, activity} = CommonAPI.post(other_user, %{status: "test"})
360 status = StatusView.render("show.json", %{activity: activity, for: user})
362 assert status.pleroma.thread_muted == false
364 {:ok, activity} = CommonAPI.add_mute(user, activity)
366 status = StatusView.render("show.json", %{activity: activity, for: user})
368 assert status.pleroma.thread_muted == true
371 test "tells if the status is bookmarked" do
374 {:ok, activity} = CommonAPI.post(user, %{status: "Cute girls doing cute things"})
375 status = StatusView.render("show.json", %{activity: activity})
377 assert status.bookmarked == false
379 status = StatusView.render("show.json", %{activity: activity, for: user})
381 assert status.bookmarked == false
383 {:ok, _bookmark} = Bookmark.create(user.id, activity.id)
385 activity = Activity.get_by_id_with_object(activity.id)
387 status = StatusView.render("show.json", %{activity: activity, for: user})
389 assert status.bookmarked == true
393 note = insert(:note_activity)
396 {:ok, activity} = CommonAPI.post(user, %{status: "he", in_reply_to_status_id: note.id})
398 status = StatusView.render("show.json", %{activity: activity})
400 assert status.in_reply_to_id == to_string(note.id)
402 [status] = StatusView.render("index.json", %{activities: [activity], as: :activity})
404 assert status.in_reply_to_id == to_string(note.id)
408 note = insert(:note_activity)
411 {:ok, activity} = CommonAPI.post(user, %{status: "hehe", quote_id: note.id})
413 status = StatusView.render("show.json", %{activity: activity})
415 assert status.quote_id == to_string(note.id)
417 [status] = StatusView.render("index.json", %{activities: [activity], as: :activity})
419 assert status.quote_id == to_string(note.id)
422 test "a quote that we can't resolve" do
423 note = insert(:note_activity, quoteUri: "oopsie")
425 status = StatusView.render("show.json", %{activity: note})
427 assert is_nil(status.quote_id)
428 assert is_nil(status.quote)
431 test "a quote from a user we block" do
433 other_user = insert(:user)
434 blocked_user = insert(:user)
436 {:ok, _relationship} = User.block(user, blocked_user)
438 {:ok, activity} = CommonAPI.post(blocked_user, %{status: ":< i am ANGERY"})
439 {:ok, quote_activity} = CommonAPI.post(other_user, %{status: "hehe", quote_id: activity.id})
441 status = StatusView.render("show.json", %{activity: quote_activity, for: user})
442 assert is_nil(status.quote)
445 test "a quote from a user we mute" do
447 other_user = insert(:user)
448 blocked_user = insert(:user)
450 {:ok, _relationship} = User.mute(user, blocked_user)
452 {:ok, activity} = CommonAPI.post(blocked_user, %{status: ":< i am ANGERY"})
453 {:ok, quote_activity} = CommonAPI.post(other_user, %{status: "hehe", quote_id: activity.id})
455 status = StatusView.render("show.json", %{activity: quote_activity, for: user})
456 assert is_nil(status.quote)
459 test "contains mentions" do
461 mentioned = insert(:user)
463 {:ok, activity} = CommonAPI.post(user, %{status: "hi @#{mentioned.nickname}"})
465 status = StatusView.render("show.json", %{activity: activity})
467 assert status.mentions ==
468 Enum.map([mentioned], fn u -> AccountView.render("mention.json", %{user: u}) end)
470 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
473 test "create mentions from the 'to' field" do
474 %User{ap_id: recipient_ap_id} = insert(:user)
475 cc = insert_pair(:user) |> Enum.map(& &1.ap_id)
480 "to" => [recipient_ap_id],
486 insert(:note_activity, %{
488 recipients: [recipient_ap_id | cc]
491 assert length(activity.recipients) == 3
493 %{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity})
495 assert length(mentions) == 1
496 assert mention.url == recipient_ap_id
499 test "create mentions from the 'tag' field" do
500 recipient = insert(:user)
501 cc = insert_pair(:user) |> Enum.map(& &1.ap_id)
509 "href" => recipient.ap_id,
510 "name" => recipient.nickname,
514 "href" => "https://example.com/search?tag=test",
523 insert(:note_activity, %{
525 recipients: [recipient.ap_id | cc]
528 assert length(activity.recipients) == 3
530 %{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity})
532 assert length(mentions) == 1
533 assert mention.url == recipient.ap_id
536 test "attachments" do
541 "mediaType" => "image/png",
547 "blurhash" => "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn",
555 remote_url: "someurl",
556 preview_url: "someurl",
559 pleroma: %{mime_type: "image/png"},
560 meta: %{original: %{width: 200, height: 100, aspect: 2}},
561 blurhash: "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn"
564 api_spec = Pleroma.Web.ApiSpec.spec()
566 assert expected == StatusView.render("attachment.json", %{attachment: object})
567 assert_schema(expected, "Attachment", api_spec)
569 # If theres a "id", use that instead of the generated one
570 object = Map.put(object, "id", 2)
571 result = StatusView.render("attachment.json", %{attachment: object})
573 assert %{id: "2"} = result
574 assert_schema(result, "Attachment", api_spec)
577 test "put the url advertised in the Activity in to the url attribute" do
578 id = "https://wedistribute.org/wp-json/pterotype/v1/object/85810"
579 [activity] = Activity.search(nil, id)
581 status = StatusView.render("show.json", %{activity: activity})
583 assert status.uri == id
584 assert status.url == "https://wedistribute.org/2019/07/mastodon-drops-ostatus/"
589 activity = insert(:note_activity)
591 {:ok, reblog} = CommonAPI.repeat(activity.id, user)
593 represented = StatusView.render("show.json", %{for: user, activity: reblog})
595 assert represented[:id] == to_string(reblog.id)
596 assert represented[:reblog][:id] == to_string(activity.id)
597 assert represented[:emojis] == []
598 assert_schema(represented, "Status", Pleroma.Web.ApiSpec.spec())
601 test "a peertube video" do
605 Pleroma.Object.Fetcher.fetch_object_from_id(
606 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
609 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
611 represented = StatusView.render("show.json", %{for: user, activity: activity})
613 assert represented[:id] == to_string(activity.id)
614 assert length(represented[:media_attachments]) == 1
615 assert_schema(represented, "Status", Pleroma.Web.ApiSpec.spec())
618 test "funkwhale audio" do
622 Pleroma.Object.Fetcher.fetch_object_from_id(
623 "https://channels.tests.funkwhale.audio/federation/music/uploads/42342395-0208-4fee-a38d-259a6dae0871"
626 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
628 represented = StatusView.render("show.json", %{for: user, activity: activity})
630 assert represented[:id] == to_string(activity.id)
631 assert length(represented[:media_attachments]) == 1
634 test "a Mobilizon event" do
638 Pleroma.Object.Fetcher.fetch_object_from_id(
639 "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
642 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
644 represented = StatusView.render("show.json", %{for: user, activity: activity})
646 assert represented[:id] == to_string(activity.id)
648 assert represented[:url] ==
649 "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
651 assert represented[:content] ==
652 "<p><a href=\"https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39\">Mobilizon Launching Party</a></p><p>Mobilizon is now federated! 🎉</p><p></p><p>You can view this event from other instances if they are subscribed to mobilizon.org, and soon directly from Mastodon and Pleroma. It is possible that you may see some comments from other instances, including Mastodon ones, just below.</p><p></p><p>With a Mobilizon account on an instance, you may <strong>participate</strong> at events from other instances and <strong>add comments</strong> on events.</p><p></p><p>Of course, it's still <u>a work in progress</u>: if reports made from an instance on events and comments can be federated, you can't block people right now, and moderators actions are rather limited, but this <strong>will definitely get fixed over time</strong> until first stable version next year.</p><p></p><p>Anyway, if you want to come up with some feedback, head over to our forum or - if you feel you have technical skills and are familiar with it - on our Gitlab repository.</p><p></p><p>Also, to people that want to set Mobilizon themselves even though we really don't advise to do that for now, we have a little documentation but it's quite the early days and you'll probably need some help. No worries, you can chat with us on our Forum or though our Matrix channel.</p><p></p><p>Check our website for more informations and follow us on Twitter or Mastodon.</p>"
655 describe "build_tags/1" do
656 test "it returns a a dictionary tags" do
662 "href" => "https://kawen.space/users/lain",
663 "name" => "@lain@kawen.space",
668 assert StatusView.build_tags(object_tags) == [
669 %{name: "fediverse", url: "http://localhost:4001/tag/fediverse"},
670 %{name: "mastodon", url: "http://localhost:4001/tag/mastodon"},
671 %{name: "nextcloud", url: "http://localhost:4001/tag/nextcloud"}
676 describe "rich media cards" do
677 test "a rich media card without a site name renders correctly" do
678 page_url = "http://example.com"
682 image: page_url <> "/example.jpg",
683 title: "Example website"
686 %{provider_name: "example.com"} =
687 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
690 test "a rich media card without a site name or image renders correctly" do
691 page_url = "http://example.com"
695 title: "Example website"
698 %{provider_name: "example.com"} =
699 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
702 test "a rich media card without an image renders correctly" do
703 page_url = "http://example.com"
707 site_name: "Example site name",
708 title: "Example website"
711 %{provider_name: "example.com"} =
712 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
715 test "a rich media card with all relevant data renders correctly" do
716 page_url = "http://example.com"
720 site_name: "Example site name",
721 title: "Example website",
722 image: page_url <> "/example.jpg",
723 description: "Example description"
726 %{provider_name: "example.com"} =
727 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
731 test "does not embed a relationship in the account" do
733 other_user = insert(:user)
736 CommonAPI.post(user, %{
737 status: "drink more water"
740 result = StatusView.render("show.json", %{activity: activity, for: other_user})
742 assert result[:account][:pleroma][:relationship] == %{}
743 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
746 test "does not embed a relationship in the account in reposts" do
748 other_user = insert(:user)
751 CommonAPI.post(user, %{
755 {:ok, activity} = CommonAPI.repeat(activity.id, other_user)
757 result = StatusView.render("show.json", %{activity: activity, for: user})
759 assert result[:account][:pleroma][:relationship] == %{}
760 assert result[:reblog][:account][:pleroma][:relationship] == %{}
761 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
764 test "visibility/list" do
767 {:ok, list} = Pleroma.List.create("foo", user)
769 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
771 status = StatusView.render("show.json", activity: activity)
773 assert status.visibility == "list"
776 test "has a field for parent visibility" do
778 poster = insert(:user)
780 {:ok, invisible} = CommonAPI.post(poster, %{status: "hey", visibility: "private"})
783 CommonAPI.post(poster, %{status: "hey", visibility: "private", in_reply_to_id: invisible.id})
785 status = StatusView.render("show.json", activity: visible, for: user)
786 refute status.pleroma.parent_visible
788 status = StatusView.render("show.json", activity: visible, for: poster)
789 assert status.pleroma.parent_visible