1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 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
22 import Pleroma.Factory
24 import OpenApiSpex.TestAssertions
27 mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
31 test "has an emoji reaction list" do
33 other_user = insert(:user)
34 third_user = insert(:user)
35 {:ok, activity} = CommonAPI.post(user, %{status: "dae cofe??"})
37 {:ok, _} = CommonAPI.react_with_emoji(activity.id, user, "☕")
38 {:ok, _} = CommonAPI.react_with_emoji(activity.id, third_user, "🍵")
39 {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕")
40 activity = Repo.get(Activity, activity.id)
41 status = StatusView.render("show.json", activity: activity)
43 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
45 assert status[:pleroma][:emoji_reactions] == [
46 %{name: "☕", count: 2, me: false},
47 %{name: "🍵", count: 1, me: false}
50 status = StatusView.render("show.json", activity: activity, for: user)
52 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
54 assert status[:pleroma][:emoji_reactions] == [
55 %{name: "☕", count: 2, me: true},
56 %{name: "🍵", count: 1, me: false}
60 test "works correctly with badly formatted emojis" do
62 {:ok, activity} = CommonAPI.post(user, %{status: "yo"})
65 |> Object.normalize(fetch: false)
66 |> Object.update_data(%{"reactions" => %{"☕" => [user.ap_id], "x" => 1}})
68 activity = Activity.get_by_id(activity.id)
70 status = StatusView.render("show.json", activity: activity, for: user)
72 assert status[:pleroma][:emoji_reactions] == [
73 %{name: "☕", count: 1, me: true}
77 test "doesn't show reactions from muted and blocked users" do
79 other_user = insert(:user)
80 third_user = insert(:user)
82 {:ok, activity} = CommonAPI.post(user, %{status: "dae cofe??"})
84 {:ok, _} = User.mute(user, other_user)
85 {:ok, _} = User.block(other_user, third_user)
87 {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕")
89 activity = Repo.get(Activity, activity.id)
90 status = StatusView.render("show.json", activity: activity)
92 assert status[:pleroma][:emoji_reactions] == [
93 %{name: "☕", count: 1, me: false}
96 status = StatusView.render("show.json", activity: activity, for: user)
98 assert status[:pleroma][:emoji_reactions] == []
100 {:ok, _} = CommonAPI.react_with_emoji(activity.id, third_user, "☕")
102 status = StatusView.render("show.json", activity: activity)
104 assert status[:pleroma][:emoji_reactions] == [
105 %{name: "☕", count: 2, me: false}
108 status = StatusView.render("show.json", activity: activity, for: user)
110 assert status[:pleroma][:emoji_reactions] == [
111 %{name: "☕", count: 1, me: false}
114 status = StatusView.render("show.json", activity: activity, for: other_user)
116 assert status[:pleroma][:emoji_reactions] == [
117 %{name: "☕", count: 1, me: true}
121 test "loads and returns the direct conversation id when given the `with_direct_conversation_id` option" do
124 {:ok, activity} = CommonAPI.post(user, %{status: "Hey @shp!", visibility: "direct"})
125 [participation] = Participation.for_user(user)
128 StatusView.render("show.json",
130 with_direct_conversation_id: true,
134 assert status[:pleroma][:direct_conversation_id] == participation.id
136 status = StatusView.render("show.json", activity: activity, for: user)
137 assert status[:pleroma][:direct_conversation_id] == nil
138 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
141 test "returns the direct conversation id when given the `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 direct_conversation_id: participation.id,
154 assert status[:pleroma][:direct_conversation_id] == participation.id
155 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
158 test "returns a temporary ap_id based user for activities missing db users" do
161 {:ok, activity} = CommonAPI.post(user, %{status: "Hey @shp!", visibility: "direct"})
164 User.invalidate_cache(user)
167 "https://localhost/.well-known/webfinger?resource=acct:#{user.nickname}@localhost"
169 Tesla.Mock.mock_global(fn
170 %{method: :get, url: "http://localhost/.well-known/host-meta"} ->
171 %Tesla.Env{status: 404, body: ""}
173 %{method: :get, url: "https://localhost/.well-known/host-meta"} ->
174 %Tesla.Env{status: 404, body: ""}
180 %Tesla.Env{status: 404, body: ""}
183 %{account: ms_user} = StatusView.render("show.json", activity: activity)
185 assert ms_user.acct == "erroruser@example.com"
188 test "tries to get a user by nickname if fetching by ap_id doesn't work" do
191 {:ok, activity} = CommonAPI.post(user, %{status: "Hey @shp!", visibility: "direct"})
195 |> Ecto.Changeset.change(%{ap_id: "#{user.ap_id}/extension/#{user.nickname}"})
198 User.invalidate_cache(user)
200 result = StatusView.render("show.json", activity: activity)
202 assert result[:account][:id] == to_string(user.id)
203 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
206 test "a note with null content" do
207 note = insert(:note_activity)
208 note_object = Object.normalize(note, fetch: false)
212 |> Map.put("content", nil)
214 Object.change(note_object, %{data: data})
215 |> Object.update_and_set_cache()
217 User.get_cached_by_ap_id(note.data["actor"])
219 status = StatusView.render("show.json", %{activity: note})
221 assert status.content == ""
222 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
225 test "a note activity" do
226 note = insert(:note_activity)
227 object_data = Object.normalize(note, fetch: false).data
228 user = User.get_cached_by_ap_id(note.data["actor"])
230 convo_id = :erlang.crc32(object_data["context"]) |> Bitwise.band(Bitwise.bnot(0x8000_0000))
232 status = StatusView.render("show.json", %{activity: note})
235 (object_data["published"] || "")
236 |> String.replace(~r/\.\d+Z/, ".000Z")
239 id: to_string(note.id),
240 uri: object_data["id"],
241 url: Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, note),
242 account: AccountView.render("show.json", %{user: user, skip_visibility_check: true}),
244 in_reply_to_account_id: nil,
247 content: HTML.filter_tags(object_data["content"]),
249 created_at: created_at,
261 spoiler_text: HTML.filter_tags(object_data["summary"]),
262 visibility: "public",
263 media_attachments: [],
267 name: "#{hd(object_data["tag"])}",
268 url: "http://localhost:4001/tag/#{hd(object_data["tag"])}"
277 static_url: "corndog.png",
278 visible_in_picker: false
283 conversation_id: convo_id,
284 context: object_data["context"],
285 in_reply_to_account_acct: nil,
286 content: %{"text/plain" => HTML.strip_tags(object_data["content"])},
287 spoiler_text: %{"text/plain" => HTML.strip_tags(object_data["summary"])},
289 direct_conversation_id: nil,
292 parent_visible: false,
297 assert status == expected
298 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
301 test "tells if the message is muted for some reason" do
303 other_user = insert(:user)
305 {:ok, _user_relationships} = User.mute(user, other_user)
307 {:ok, activity} = CommonAPI.post(other_user, %{status: "test"})
309 relationships_opt = UserRelationship.view_relationships_option(user, [other_user])
311 opts = %{activity: activity}
312 status = StatusView.render("show.json", opts)
313 assert status.muted == false
314 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
316 status = StatusView.render("show.json", Map.put(opts, :relationships, relationships_opt))
317 assert status.muted == false
319 for_opts = %{activity: activity, for: user}
320 status = StatusView.render("show.json", for_opts)
321 assert status.muted == true
323 status = StatusView.render("show.json", Map.put(for_opts, :relationships, relationships_opt))
324 assert status.muted == true
325 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
328 test "tells if the message is thread muted" do
330 other_user = insert(:user)
332 {:ok, _user_relationships} = User.mute(user, other_user)
334 {:ok, activity} = CommonAPI.post(other_user, %{status: "test"})
335 status = StatusView.render("show.json", %{activity: activity, for: user})
337 assert status.pleroma.thread_muted == false
339 {:ok, activity} = CommonAPI.add_mute(user, activity)
341 status = StatusView.render("show.json", %{activity: activity, for: user})
343 assert status.pleroma.thread_muted == true
346 test "tells if the status is bookmarked" do
349 {:ok, activity} = CommonAPI.post(user, %{status: "Cute girls doing cute things"})
350 status = StatusView.render("show.json", %{activity: activity})
352 assert status.bookmarked == false
354 status = StatusView.render("show.json", %{activity: activity, for: user})
356 assert status.bookmarked == false
358 {:ok, _bookmark} = Bookmark.create(user.id, activity.id)
360 activity = Activity.get_by_id_with_object(activity.id)
362 status = StatusView.render("show.json", %{activity: activity, for: user})
364 assert status.bookmarked == true
368 note = insert(:note_activity)
371 {:ok, activity} = CommonAPI.post(user, %{status: "he", in_reply_to_status_id: note.id})
373 status = StatusView.render("show.json", %{activity: activity})
375 assert status.in_reply_to_id == to_string(note.id)
377 [status] = StatusView.render("index.json", %{activities: [activity], as: :activity})
379 assert status.in_reply_to_id == to_string(note.id)
382 test "contains mentions" do
384 mentioned = insert(:user)
386 {:ok, activity} = CommonAPI.post(user, %{status: "hi @#{mentioned.nickname}"})
388 status = StatusView.render("show.json", %{activity: activity})
390 assert status.mentions ==
391 Enum.map([mentioned], fn u -> AccountView.render("mention.json", %{user: u}) end)
393 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
396 test "create mentions from the 'to' field" do
397 %User{ap_id: recipient_ap_id} = insert(:user)
398 cc = insert_pair(:user) |> Enum.map(& &1.ap_id)
403 "to" => [recipient_ap_id],
409 insert(:note_activity, %{
411 recipients: [recipient_ap_id | cc]
414 assert length(activity.recipients) == 3
416 %{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity})
418 assert length(mentions) == 1
419 assert mention.url == recipient_ap_id
422 test "create mentions from the 'tag' field" do
423 recipient = insert(:user)
424 cc = insert_pair(:user) |> Enum.map(& &1.ap_id)
432 "href" => recipient.ap_id,
433 "name" => recipient.nickname,
437 "href" => "https://example.com/search?tag=test",
446 insert(:note_activity, %{
448 recipients: [recipient.ap_id | cc]
451 assert length(activity.recipients) == 3
453 %{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity})
455 assert length(mentions) == 1
456 assert mention.url == recipient.ap_id
459 test "attachments" do
464 "mediaType" => "image/png",
470 "blurhash" => "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn",
478 remote_url: "someurl",
479 preview_url: "someurl",
482 pleroma: %{mime_type: "image/png"},
483 meta: %{original: %{width: 200, height: 100, aspect: 2}},
484 blurhash: "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn"
487 api_spec = Pleroma.Web.ApiSpec.spec()
489 assert expected == StatusView.render("attachment.json", %{attachment: object})
490 assert_schema(expected, "Attachment", api_spec)
492 # If theres a "id", use that instead of the generated one
493 object = Map.put(object, "id", 2)
494 result = StatusView.render("attachment.json", %{attachment: object})
496 assert %{id: "2"} = result
497 assert_schema(result, "Attachment", api_spec)
500 test "put the url advertised in the Activity in to the url attribute" do
501 id = "https://wedistribute.org/wp-json/pterotype/v1/object/85810"
502 [activity] = Activity.search(nil, id)
504 status = StatusView.render("show.json", %{activity: activity})
506 assert status.uri == id
507 assert status.url == "https://wedistribute.org/2019/07/mastodon-drops-ostatus/"
512 activity = insert(:note_activity)
514 {:ok, reblog} = CommonAPI.repeat(activity.id, user)
516 represented = StatusView.render("show.json", %{for: user, activity: reblog})
518 assert represented[:id] == to_string(reblog.id)
519 assert represented[:reblog][:id] == to_string(activity.id)
520 assert represented[:emojis] == []
521 assert_schema(represented, "Status", Pleroma.Web.ApiSpec.spec())
524 test "a peertube video" do
528 Pleroma.Object.Fetcher.fetch_object_from_id(
529 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
532 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
534 represented = StatusView.render("show.json", %{for: user, activity: activity})
536 assert represented[:id] == to_string(activity.id)
537 assert length(represented[:media_attachments]) == 1
538 assert_schema(represented, "Status", Pleroma.Web.ApiSpec.spec())
541 test "funkwhale audio" do
545 Pleroma.Object.Fetcher.fetch_object_from_id(
546 "https://channels.tests.funkwhale.audio/federation/music/uploads/42342395-0208-4fee-a38d-259a6dae0871"
549 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
551 represented = StatusView.render("show.json", %{for: user, activity: activity})
553 assert represented[:id] == to_string(activity.id)
554 assert length(represented[:media_attachments]) == 1
557 test "a Mobilizon event" do
561 Pleroma.Object.Fetcher.fetch_object_from_id(
562 "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
565 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
567 represented = StatusView.render("show.json", %{for: user, activity: activity})
569 assert represented[:id] == to_string(activity.id)
571 assert represented[:url] ==
572 "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
574 assert represented[:content] ==
575 "<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>"
578 describe "build_tags/1" do
579 test "it returns a a dictionary tags" do
585 "href" => "https://kawen.space/users/lain",
586 "name" => "@lain@kawen.space",
591 assert StatusView.build_tags(object_tags) == [
592 %{name: "fediverse", url: "http://localhost:4001/tag/fediverse"},
593 %{name: "mastodon", url: "http://localhost:4001/tag/mastodon"},
594 %{name: "nextcloud", url: "http://localhost:4001/tag/nextcloud"}
599 describe "rich media cards" do
600 test "a rich media card without a site name renders correctly" do
601 page_url = "http://example.com"
605 image: page_url <> "/example.jpg",
606 title: "Example website"
609 %{provider_name: "example.com"} =
610 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
613 test "a rich media card without a site name or image renders correctly" do
614 page_url = "http://example.com"
618 title: "Example website"
621 %{provider_name: "example.com"} =
622 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
625 test "a rich media card without an image renders correctly" do
626 page_url = "http://example.com"
630 site_name: "Example site name",
631 title: "Example website"
634 %{provider_name: "example.com"} =
635 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
638 test "a rich media card with all relevant data renders correctly" do
639 page_url = "http://example.com"
643 site_name: "Example site name",
644 title: "Example website",
645 image: page_url <> "/example.jpg",
646 description: "Example description"
649 %{provider_name: "example.com"} =
650 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
654 test "does not embed a relationship in the account" do
656 other_user = insert(:user)
659 CommonAPI.post(user, %{
660 status: "drink more water"
663 result = StatusView.render("show.json", %{activity: activity, for: other_user})
665 assert result[:account][:pleroma][:relationship] == %{}
666 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
669 test "does not embed a relationship in the account in reposts" do
671 other_user = insert(:user)
674 CommonAPI.post(user, %{
678 {:ok, activity} = CommonAPI.repeat(activity.id, other_user)
680 result = StatusView.render("show.json", %{activity: activity, for: user})
682 assert result[:account][:pleroma][:relationship] == %{}
683 assert result[:reblog][:account][:pleroma][:relationship] == %{}
684 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
687 test "visibility/list" do
690 {:ok, list} = Pleroma.List.create("foo", user)
692 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
694 status = StatusView.render("show.json", activity: activity)
696 assert status.visibility == "list"
699 test "has a field for parent visibility" do
701 poster = insert(:user)
703 {:ok, invisible} = CommonAPI.post(poster, %{status: "hey", visibility: "private"})
706 CommonAPI.post(poster, %{status: "hey", visibility: "private", in_reply_to_id: invisible.id})
708 status = StatusView.render("show.json", activity: visible, for: user)
709 refute status.pleroma.parent_visible
711 status = StatusView.render("show.json", activity: visible, for: poster)
712 assert status.pleroma.parent_visible
715 test "it shows edited_at" do
716 poster = insert(:user)
718 {:ok, post} = CommonAPI.post(poster, %{status: "hey"})
720 status = StatusView.render("show.json", activity: post)
721 refute status.edited_at
723 {:ok, _} = CommonAPI.update(poster, post, %{status: "mew mew"})
724 edited = Pleroma.Activity.normalize(post)
726 status = StatusView.render("show.json", activity: edited)
727 assert status.edited_at
730 test "with a source object" do
733 data: %{"source" => %{"content" => "object source", "mediaType" => "text/markdown"}}
736 activity = insert(:note_activity, note: note)
738 status = StatusView.render("show.json", activity: activity, with_source: true)
739 assert status.text == "object source"
742 describe "source.json" do
743 test "with a source object, renders both source and content type" do
746 data: %{"source" => %{"content" => "object source", "mediaType" => "text/markdown"}}
749 activity = insert(:note_activity, note: note)
751 status = StatusView.render("source.json", activity: activity)
752 assert status.text == "object source"
753 assert status.content_type == "text/markdown"
756 test "with a source string, renders source and put text/plain as the content type" do
757 note = insert(:note, data: %{"source" => "string source"})
758 activity = insert(:note_activity, note: note)
760 status = StatusView.render("source.json", activity: activity)
761 assert status.text == "string source"
762 assert status.content_type == "text/plain"