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.CommonAPI.Utils
18 alias Pleroma.Web.MastodonAPI.AccountView
19 alias Pleroma.Web.MastodonAPI.StatusView
21 import Pleroma.Factory
23 import OpenApiSpex.TestAssertions
26 mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
30 test "has an emoji reaction list" do
32 other_user = insert(:user)
33 third_user = insert(:user)
34 {:ok, activity} = CommonAPI.post(user, %{status: "dae cofe??"})
36 {:ok, _} = CommonAPI.react_with_emoji(activity.id, user, "☕")
37 {:ok, _} = CommonAPI.react_with_emoji(activity.id, third_user, "🍵")
38 {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕")
39 activity = Repo.get(Activity, activity.id)
40 status = StatusView.render("show.json", activity: activity)
42 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
44 assert status[:pleroma][:emoji_reactions] == [
45 %{name: "☕", count: 2, me: false},
46 %{name: "🍵", count: 1, me: false}
49 status = StatusView.render("show.json", activity: activity, for: user)
51 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
53 assert status[:pleroma][:emoji_reactions] == [
54 %{name: "☕", count: 2, me: true},
55 %{name: "🍵", count: 1, me: false}
59 test "works correctly with badly formatted emojis" do
61 {:ok, activity} = CommonAPI.post(user, %{status: "yo"})
64 |> Object.normalize(fetch: false)
65 |> Object.update_data(%{"reactions" => %{"☕" => [user.ap_id], "x" => 1}})
67 activity = Activity.get_by_id(activity.id)
69 status = StatusView.render("show.json", activity: activity, for: user)
71 assert status[:pleroma][:emoji_reactions] == [
72 %{name: "☕", count: 1, me: true}
76 test "doesn't show reactions from muted and blocked users" do
78 other_user = insert(:user)
79 third_user = insert(:user)
81 {:ok, activity} = CommonAPI.post(user, %{status: "dae cofe??"})
83 {:ok, _} = User.mute(user, other_user)
84 {:ok, _} = User.block(other_user, third_user)
86 {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕")
88 activity = Repo.get(Activity, activity.id)
89 status = StatusView.render("show.json", activity: activity)
91 assert status[:pleroma][:emoji_reactions] == [
92 %{name: "☕", count: 1, me: false}
95 status = StatusView.render("show.json", activity: activity, for: user)
97 assert status[:pleroma][:emoji_reactions] == []
99 {:ok, _} = CommonAPI.react_with_emoji(activity.id, third_user, "☕")
101 status = StatusView.render("show.json", activity: activity)
103 assert status[:pleroma][:emoji_reactions] == [
104 %{name: "☕", count: 2, me: false}
107 status = StatusView.render("show.json", activity: activity, for: user)
109 assert status[:pleroma][:emoji_reactions] == [
110 %{name: "☕", count: 1, me: false}
113 status = StatusView.render("show.json", activity: activity, for: other_user)
115 assert status[:pleroma][:emoji_reactions] == [
116 %{name: "☕", count: 1, me: true}
120 test "loads and returns the direct conversation id when given the `with_direct_conversation_id` option" do
123 {:ok, activity} = CommonAPI.post(user, %{status: "Hey @shp!", visibility: "direct"})
124 [participation] = Participation.for_user(user)
127 StatusView.render("show.json",
129 with_direct_conversation_id: true,
133 assert status[:pleroma][:direct_conversation_id] == participation.id
135 status = StatusView.render("show.json", activity: activity, for: user)
136 assert status[:pleroma][:direct_conversation_id] == nil
137 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
140 test "returns the direct conversation id when given the `direct_conversation_id` option" do
143 {:ok, activity} = CommonAPI.post(user, %{status: "Hey @shp!", visibility: "direct"})
144 [participation] = Participation.for_user(user)
147 StatusView.render("show.json",
149 direct_conversation_id: participation.id,
153 assert status[:pleroma][:direct_conversation_id] == participation.id
154 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
157 test "returns a temporary ap_id based user for activities missing db users" do
160 {:ok, activity} = CommonAPI.post(user, %{status: "Hey @shp!", visibility: "direct"})
163 User.invalidate_cache(user)
166 "https://localhost/.well-known/webfinger?resource=acct:#{user.nickname}@localhost"
168 Tesla.Mock.mock_global(fn
169 %{method: :get, url: "http://localhost/.well-known/host-meta"} ->
170 %Tesla.Env{status: 404, body: ""}
172 %{method: :get, url: "https://localhost/.well-known/host-meta"} ->
173 %Tesla.Env{status: 404, body: ""}
179 %Tesla.Env{status: 404, body: ""}
182 %{account: ms_user} = StatusView.render("show.json", activity: activity)
184 assert ms_user.acct == "erroruser@example.com"
187 test "tries to get a user by nickname if fetching by ap_id doesn't work" do
190 {:ok, activity} = CommonAPI.post(user, %{status: "Hey @shp!", visibility: "direct"})
194 |> Ecto.Changeset.change(%{ap_id: "#{user.ap_id}/extension/#{user.nickname}"})
197 User.invalidate_cache(user)
199 result = StatusView.render("show.json", activity: activity)
201 assert result[:account][:id] == to_string(user.id)
202 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
205 test "a note with null content" do
206 note = insert(:note_activity)
207 note_object = Object.normalize(note, fetch: false)
211 |> Map.put("content", nil)
213 Object.change(note_object, %{data: data})
214 |> Object.update_and_set_cache()
216 User.get_cached_by_ap_id(note.data["actor"])
218 status = StatusView.render("show.json", %{activity: note})
220 assert status.content == ""
221 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
224 test "a note activity" do
225 note = insert(:note_activity)
226 object_data = Object.normalize(note, fetch: false).data
227 user = User.get_cached_by_ap_id(note.data["actor"])
229 convo_id = Utils.context_to_conversation_id(object_data["context"])
231 status = StatusView.render("show.json", %{activity: note})
234 (object_data["published"] || "")
235 |> String.replace(~r/\.\d+Z/, ".000Z")
238 id: to_string(note.id),
239 uri: object_data["id"],
240 url: Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, note),
241 account: AccountView.render("show.json", %{user: user, skip_visibility_check: true}),
243 in_reply_to_account_id: nil,
246 content: HTML.filter_tags(object_data["content"]),
248 created_at: created_at,
259 spoiler_text: HTML.filter_tags(object_data["summary"]),
260 visibility: "public",
261 media_attachments: [],
265 name: "#{hd(object_data["tag"])}",
266 url: "http://localhost:4001/tag/#{hd(object_data["tag"])}"
275 static_url: "corndog.png",
276 visible_in_picker: false
281 conversation_id: convo_id,
282 in_reply_to_account_acct: nil,
283 content: %{"text/plain" => HTML.strip_tags(object_data["content"])},
284 spoiler_text: %{"text/plain" => HTML.strip_tags(object_data["summary"])},
286 direct_conversation_id: nil,
289 parent_visible: false
293 assert status == expected
294 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
297 test "tells if the message is muted for some reason" do
299 other_user = insert(:user)
301 {:ok, _user_relationships} = User.mute(user, other_user)
303 {:ok, activity} = CommonAPI.post(other_user, %{status: "test"})
305 relationships_opt = UserRelationship.view_relationships_option(user, [other_user])
307 opts = %{activity: activity}
308 status = StatusView.render("show.json", opts)
309 assert status.muted == false
310 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
312 status = StatusView.render("show.json", Map.put(opts, :relationships, relationships_opt))
313 assert status.muted == false
315 for_opts = %{activity: activity, for: user}
316 status = StatusView.render("show.json", for_opts)
317 assert status.muted == true
319 status = StatusView.render("show.json", Map.put(for_opts, :relationships, relationships_opt))
320 assert status.muted == true
321 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
324 test "tells if the message is thread muted" do
326 other_user = insert(:user)
328 {:ok, _user_relationships} = User.mute(user, other_user)
330 {:ok, activity} = CommonAPI.post(other_user, %{status: "test"})
331 status = StatusView.render("show.json", %{activity: activity, for: user})
333 assert status.pleroma.thread_muted == false
335 {:ok, activity} = CommonAPI.add_mute(user, activity)
337 status = StatusView.render("show.json", %{activity: activity, for: user})
339 assert status.pleroma.thread_muted == true
342 test "tells if the status is bookmarked" do
345 {:ok, activity} = CommonAPI.post(user, %{status: "Cute girls doing cute things"})
346 status = StatusView.render("show.json", %{activity: activity})
348 assert status.bookmarked == false
350 status = StatusView.render("show.json", %{activity: activity, for: user})
352 assert status.bookmarked == false
354 {:ok, _bookmark} = Bookmark.create(user.id, activity.id)
356 activity = Activity.get_by_id_with_object(activity.id)
358 status = StatusView.render("show.json", %{activity: activity, for: user})
360 assert status.bookmarked == true
364 note = insert(:note_activity)
367 {:ok, activity} = CommonAPI.post(user, %{status: "he", in_reply_to_status_id: note.id})
369 status = StatusView.render("show.json", %{activity: activity})
371 assert status.in_reply_to_id == to_string(note.id)
373 [status] = StatusView.render("index.json", %{activities: [activity], as: :activity})
375 assert status.in_reply_to_id == to_string(note.id)
378 test "contains mentions" do
380 mentioned = insert(:user)
382 {:ok, activity} = CommonAPI.post(user, %{status: "hi @#{mentioned.nickname}"})
384 status = StatusView.render("show.json", %{activity: activity})
386 assert status.mentions ==
387 Enum.map([mentioned], fn u -> AccountView.render("mention.json", %{user: u}) end)
389 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
392 test "create mentions from the 'to' field" do
393 %User{ap_id: recipient_ap_id} = insert(:user)
394 cc = insert_pair(:user) |> Enum.map(& &1.ap_id)
399 "to" => [recipient_ap_id],
405 insert(:note_activity, %{
407 recipients: [recipient_ap_id | cc]
410 assert length(activity.recipients) == 3
412 %{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity})
414 assert length(mentions) == 1
415 assert mention.url == recipient_ap_id
418 test "create mentions from the 'tag' field" do
419 recipient = insert(:user)
420 cc = insert_pair(:user) |> Enum.map(& &1.ap_id)
428 "href" => recipient.ap_id,
429 "name" => recipient.nickname,
433 "href" => "https://example.com/search?tag=test",
442 insert(:note_activity, %{
444 recipients: [recipient.ap_id | cc]
447 assert length(activity.recipients) == 3
449 %{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity})
451 assert length(mentions) == 1
452 assert mention.url == recipient.ap_id
455 test "attachments" do
460 "mediaType" => "image/png",
464 "blurhash" => "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn",
472 remote_url: "someurl",
473 preview_url: "someurl",
476 pleroma: %{mime_type: "image/png"},
477 blurhash: "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn"
480 api_spec = Pleroma.Web.ApiSpec.spec()
482 assert expected == StatusView.render("attachment.json", %{attachment: object})
483 assert_schema(expected, "Attachment", api_spec)
485 # If theres a "id", use that instead of the generated one
486 object = Map.put(object, "id", 2)
487 result = StatusView.render("attachment.json", %{attachment: object})
489 assert %{id: "2"} = result
490 assert_schema(result, "Attachment", api_spec)
493 test "put the url advertised in the Activity in to the url attribute" do
494 id = "https://wedistribute.org/wp-json/pterotype/v1/object/85810"
495 [activity] = Activity.search(nil, id)
497 status = StatusView.render("show.json", %{activity: activity})
499 assert status.uri == id
500 assert status.url == "https://wedistribute.org/2019/07/mastodon-drops-ostatus/"
505 activity = insert(:note_activity)
507 {:ok, reblog} = CommonAPI.repeat(activity.id, user)
509 represented = StatusView.render("show.json", %{for: user, activity: reblog})
511 assert represented[:id] == to_string(reblog.id)
512 assert represented[:reblog][:id] == to_string(activity.id)
513 assert represented[:emojis] == []
514 assert_schema(represented, "Status", Pleroma.Web.ApiSpec.spec())
517 test "a peertube video" do
521 Pleroma.Object.Fetcher.fetch_object_from_id(
522 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
525 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
527 represented = StatusView.render("show.json", %{for: user, activity: activity})
529 assert represented[:id] == to_string(activity.id)
530 assert length(represented[:media_attachments]) == 1
531 assert_schema(represented, "Status", Pleroma.Web.ApiSpec.spec())
534 test "funkwhale audio" do
538 Pleroma.Object.Fetcher.fetch_object_from_id(
539 "https://channels.tests.funkwhale.audio/federation/music/uploads/42342395-0208-4fee-a38d-259a6dae0871"
542 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
544 represented = StatusView.render("show.json", %{for: user, activity: activity})
546 assert represented[:id] == to_string(activity.id)
547 assert length(represented[:media_attachments]) == 1
550 test "a Mobilizon event" do
554 Pleroma.Object.Fetcher.fetch_object_from_id(
555 "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
558 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
560 represented = StatusView.render("show.json", %{for: user, activity: activity})
562 assert represented[:id] == to_string(activity.id)
564 assert represented[:url] ==
565 "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
567 assert represented[:content] ==
568 "<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>"
571 describe "build_tags/1" do
572 test "it returns a a dictionary tags" do
578 "href" => "https://kawen.space/users/lain",
579 "name" => "@lain@kawen.space",
584 assert StatusView.build_tags(object_tags) == [
585 %{name: "fediverse", url: "http://localhost:4001/tag/fediverse"},
586 %{name: "mastodon", url: "http://localhost:4001/tag/mastodon"},
587 %{name: "nextcloud", url: "http://localhost:4001/tag/nextcloud"}
592 describe "rich media cards" do
593 test "a rich media card without a site name renders correctly" do
594 page_url = "http://example.com"
598 image: page_url <> "/example.jpg",
599 title: "Example website"
602 %{provider_name: "example.com"} =
603 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
606 test "a rich media card without a site name or image renders correctly" do
607 page_url = "http://example.com"
611 title: "Example website"
614 %{provider_name: "example.com"} =
615 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
618 test "a rich media card without an image renders correctly" do
619 page_url = "http://example.com"
623 site_name: "Example site name",
624 title: "Example website"
627 %{provider_name: "example.com"} =
628 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
631 test "a rich media card with all relevant data renders correctly" do
632 page_url = "http://example.com"
636 site_name: "Example site name",
637 title: "Example website",
638 image: page_url <> "/example.jpg",
639 description: "Example description"
642 %{provider_name: "example.com"} =
643 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
647 test "does not embed a relationship in the account" do
649 other_user = insert(:user)
652 CommonAPI.post(user, %{
653 status: "drink more water"
656 result = StatusView.render("show.json", %{activity: activity, for: other_user})
658 assert result[:account][:pleroma][:relationship] == %{}
659 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
662 test "does not embed a relationship in the account in reposts" do
664 other_user = insert(:user)
667 CommonAPI.post(user, %{
671 {:ok, activity} = CommonAPI.repeat(activity.id, other_user)
673 result = StatusView.render("show.json", %{activity: activity, for: user})
675 assert result[:account][:pleroma][:relationship] == %{}
676 assert result[:reblog][:account][:pleroma][:relationship] == %{}
677 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
680 test "visibility/list" do
683 {:ok, list} = Pleroma.List.create("foo", user)
685 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
687 status = StatusView.render("show.json", activity: activity)
689 assert status.visibility == "list"
692 test "has a field for parent visibility" do
694 poster = insert(:user)
696 {:ok, invisible} = CommonAPI.post(poster, %{status: "hey", visibility: "private"})
699 CommonAPI.post(poster, %{status: "hey", visibility: "private", in_reply_to_id: invisible.id})
701 status = StatusView.render("show.json", activity: visible, for: user)
702 refute status.pleroma.parent_visible
704 status = StatusView.render("show.json", activity: visible, for: poster)
705 assert status.pleroma.parent_visible