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, user, ":dinosaur:")
38 {:ok, _} = CommonAPI.react_with_emoji(activity.id, third_user, "🍵")
39 {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕")
40 {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, ":dinosaur:")
42 activity = Repo.get(Activity, activity.id)
43 status = StatusView.render("show.json", activity: activity)
45 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
47 assert status[:pleroma][:emoji_reactions] == [
48 %{name: "☕", count: 2, me: false, url: nil},
53 url: "http://localhost:4001/emoji/dino walking.gif"
55 %{name: "🍵", count: 1, me: false, url: nil}
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},
68 url: "http://localhost:4001/emoji/dino walking.gif"
70 %{name: "🍵", count: 1, me: false, url: nil}
74 test "works correctly with badly formatted emojis" do
76 {:ok, activity} = CommonAPI.post(user, %{status: "yo"})
79 |> Object.normalize(fetch: false)
80 |> Object.update_data(%{"reactions" => %{"☕" => [user.ap_id], "x" => 1}})
82 activity = Activity.get_by_id(activity.id)
83 status = StatusView.render("show.json", activity: activity, for: user)
85 assert status[:pleroma][:emoji_reactions] == [
86 %{name: "☕", count: 1, me: true, url: nil}
90 test "doesn't show reactions from muted and blocked users" do
92 other_user = insert(:user)
93 third_user = insert(:user)
95 {:ok, activity} = CommonAPI.post(user, %{status: "dae cofe??"})
97 {:ok, _} = User.mute(user, other_user)
98 {:ok, _} = User.block(other_user, third_user)
100 {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕")
102 activity = Repo.get(Activity, activity.id)
103 status = StatusView.render("show.json", activity: activity)
105 assert status[:pleroma][:emoji_reactions] == [
106 %{name: "☕", count: 1, me: false, url: nil}
109 status = StatusView.render("show.json", activity: activity, for: user)
111 assert status[:pleroma][:emoji_reactions] == []
113 {:ok, _} = CommonAPI.react_with_emoji(activity.id, third_user, "☕")
115 status = StatusView.render("show.json", activity: activity)
117 assert status[:pleroma][:emoji_reactions] == [
118 %{name: "☕", count: 2, me: false, url: nil}
121 status = StatusView.render("show.json", activity: activity, for: user)
123 assert status[:pleroma][:emoji_reactions] == [
124 %{name: "☕", count: 1, me: false, url: nil}
127 status = StatusView.render("show.json", activity: activity, for: other_user)
129 assert status[:pleroma][:emoji_reactions] == [
130 %{name: "☕", count: 1, me: true, url: nil}
134 test "loads and returns the direct conversation id when given the `with_direct_conversation_id` option" do
137 {:ok, activity} = CommonAPI.post(user, %{status: "Hey @shp!", visibility: "direct"})
138 [participation] = Participation.for_user(user)
141 StatusView.render("show.json",
143 with_direct_conversation_id: true,
147 assert status[:pleroma][:direct_conversation_id] == participation.id
149 status = StatusView.render("show.json", activity: activity, for: user)
150 assert status[:pleroma][:direct_conversation_id] == nil
151 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
154 test "returns the direct conversation id when given the `direct_conversation_id` option" do
157 {:ok, activity} = CommonAPI.post(user, %{status: "Hey @shp!", visibility: "direct"})
158 [participation] = Participation.for_user(user)
161 StatusView.render("show.json",
163 direct_conversation_id: participation.id,
167 assert status[:pleroma][:direct_conversation_id] == participation.id
168 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
171 test "returns a temporary ap_id based user for activities missing db users" do
174 {:ok, activity} = CommonAPI.post(user, %{status: "Hey @shp!", visibility: "direct"})
177 User.invalidate_cache(user)
180 "https://localhost/.well-known/webfinger?resource=acct:#{user.nickname}@localhost"
182 Tesla.Mock.mock_global(fn
183 %{method: :get, url: "http://localhost/.well-known/host-meta"} ->
184 %Tesla.Env{status: 404, body: ""}
186 %{method: :get, url: "https://localhost/.well-known/host-meta"} ->
187 %Tesla.Env{status: 404, body: ""}
193 %Tesla.Env{status: 404, body: ""}
196 %{account: ms_user} = StatusView.render("show.json", activity: activity)
198 assert ms_user.acct == "erroruser@example.com"
201 test "tries to get a user by nickname if fetching by ap_id doesn't work" do
204 {:ok, activity} = CommonAPI.post(user, %{status: "Hey @shp!", visibility: "direct"})
208 |> Ecto.Changeset.change(%{ap_id: "#{user.ap_id}/extension/#{user.nickname}"})
211 User.invalidate_cache(user)
213 result = StatusView.render("show.json", activity: activity)
215 assert result[:account][:id] == to_string(user.id)
216 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
219 test "a note with null content" do
220 note = insert(:note_activity)
221 note_object = Object.normalize(note, fetch: false)
225 |> Map.put("content", nil)
227 Object.change(note_object, %{data: data})
228 |> Object.update_and_set_cache()
230 User.get_cached_by_ap_id(note.data["actor"])
232 status = StatusView.render("show.json", %{activity: note})
234 assert status.content == ""
235 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
238 test "a note activity" do
239 note = insert(:note_activity)
240 object_data = Object.normalize(note, fetch: false).data
241 user = User.get_cached_by_ap_id(note.data["actor"])
243 convo_id = Utils.context_to_conversation_id(object_data["context"])
245 status = StatusView.render("show.json", %{activity: note})
248 (object_data["published"] || "")
249 |> String.replace(~r/\.\d+Z/, ".000Z")
252 id: to_string(note.id),
253 uri: object_data["id"],
254 url: Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, note),
255 account: AccountView.render("show.json", %{user: user, skip_visibility_check: true}),
257 in_reply_to_account_id: nil,
260 content: HTML.filter_tags(object_data["content"]),
262 created_at: created_at,
273 spoiler_text: HTML.filter_tags(object_data["summary"]),
274 visibility: "public",
275 media_attachments: [],
279 name: "#{hd(object_data["tag"])}",
280 url: "http://localhost:4001/tag/#{hd(object_data["tag"])}"
289 static_url: "corndog.png",
290 visible_in_picker: false
295 conversation_id: convo_id,
296 in_reply_to_account_acct: nil,
297 content: %{"text/plain" => HTML.strip_tags(object_data["content"])},
298 spoiler_text: %{"text/plain" => HTML.strip_tags(object_data["summary"])},
300 direct_conversation_id: nil,
303 parent_visible: false,
307 source: HTML.filter_tags(object_data["content"])
311 assert status == expected
312 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
315 test "tells if the message is muted for some reason" do
317 other_user = insert(:user)
319 {:ok, _user_relationships} = User.mute(user, other_user)
321 {:ok, activity} = CommonAPI.post(other_user, %{status: "test"})
323 relationships_opt = UserRelationship.view_relationships_option(user, [other_user])
325 opts = %{activity: activity}
326 status = StatusView.render("show.json", opts)
327 assert status.muted == false
328 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
330 status = StatusView.render("show.json", Map.put(opts, :relationships, relationships_opt))
331 assert status.muted == false
333 for_opts = %{activity: activity, for: user}
334 status = StatusView.render("show.json", for_opts)
335 assert status.muted == true
337 status = StatusView.render("show.json", Map.put(for_opts, :relationships, relationships_opt))
338 assert status.muted == true
339 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
342 test "tells if the message is thread muted" do
344 other_user = insert(:user)
346 {:ok, _user_relationships} = User.mute(user, other_user)
348 {:ok, activity} = CommonAPI.post(other_user, %{status: "test"})
349 status = StatusView.render("show.json", %{activity: activity, for: user})
351 assert status.pleroma.thread_muted == false
353 {:ok, activity} = CommonAPI.add_mute(user, activity)
355 status = StatusView.render("show.json", %{activity: activity, for: user})
357 assert status.pleroma.thread_muted == true
360 test "tells if the status is bookmarked" do
363 {:ok, activity} = CommonAPI.post(user, %{status: "Cute girls doing cute things"})
364 status = StatusView.render("show.json", %{activity: activity})
366 assert status.bookmarked == false
368 status = StatusView.render("show.json", %{activity: activity, for: user})
370 assert status.bookmarked == false
372 {:ok, _bookmark} = Bookmark.create(user.id, activity.id)
374 activity = Activity.get_by_id_with_object(activity.id)
376 status = StatusView.render("show.json", %{activity: activity, for: user})
378 assert status.bookmarked == true
382 note = insert(:note_activity)
385 {:ok, activity} = CommonAPI.post(user, %{status: "he", in_reply_to_status_id: note.id})
387 status = StatusView.render("show.json", %{activity: activity})
389 assert status.in_reply_to_id == to_string(note.id)
391 [status] = StatusView.render("index.json", %{activities: [activity], as: :activity})
393 assert status.in_reply_to_id == to_string(note.id)
396 test "contains mentions" do
398 mentioned = insert(:user)
400 {:ok, activity} = CommonAPI.post(user, %{status: "hi @#{mentioned.nickname}"})
402 status = StatusView.render("show.json", %{activity: activity})
404 assert status.mentions ==
405 Enum.map([mentioned], fn u -> AccountView.render("mention.json", %{user: u}) end)
407 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
410 test "create mentions from the 'to' field" do
411 %User{ap_id: recipient_ap_id} = insert(:user)
412 cc = insert_pair(:user) |> Enum.map(& &1.ap_id)
417 "to" => [recipient_ap_id],
423 insert(:note_activity, %{
425 recipients: [recipient_ap_id | cc]
428 assert length(activity.recipients) == 3
430 %{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity})
432 assert length(mentions) == 1
433 assert mention.url == recipient_ap_id
436 test "create mentions from the 'tag' field" do
437 recipient = insert(:user)
438 cc = insert_pair(:user) |> Enum.map(& &1.ap_id)
446 "href" => recipient.ap_id,
447 "name" => recipient.nickname,
451 "href" => "https://example.com/search?tag=test",
460 insert(:note_activity, %{
462 recipients: [recipient.ap_id | cc]
465 assert length(activity.recipients) == 3
467 %{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity})
469 assert length(mentions) == 1
470 assert mention.url == recipient.ap_id
473 test "attachments" do
478 "mediaType" => "image/png",
484 "blurhash" => "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn",
492 remote_url: "someurl",
493 preview_url: "someurl",
496 pleroma: %{mime_type: "image/png"},
497 meta: %{original: %{width: 200, height: 100, aspect: 2}},
498 blurhash: "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn"
501 api_spec = Pleroma.Web.ApiSpec.spec()
503 assert expected == StatusView.render("attachment.json", %{attachment: object})
504 assert_schema(expected, "Attachment", api_spec)
506 # If theres a "id", use that instead of the generated one
507 object = Map.put(object, "id", 2)
508 result = StatusView.render("attachment.json", %{attachment: object})
510 assert %{id: "2"} = result
511 assert_schema(result, "Attachment", api_spec)
514 test "put the url advertised in the Activity in to the url attribute" do
515 id = "https://wedistribute.org/wp-json/pterotype/v1/object/85810"
516 [activity] = Activity.search(nil, id)
518 status = StatusView.render("show.json", %{activity: activity})
520 assert status.uri == id
521 assert status.url == "https://wedistribute.org/2019/07/mastodon-drops-ostatus/"
526 activity = insert(:note_activity)
528 {:ok, reblog} = CommonAPI.repeat(activity.id, user)
530 represented = StatusView.render("show.json", %{for: user, activity: reblog})
532 assert represented[:id] == to_string(reblog.id)
533 assert represented[:reblog][:id] == to_string(activity.id)
534 assert represented[:emojis] == []
535 assert_schema(represented, "Status", Pleroma.Web.ApiSpec.spec())
538 test "a peertube video" do
542 Pleroma.Object.Fetcher.fetch_object_from_id(
543 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
546 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
548 represented = StatusView.render("show.json", %{for: user, activity: activity})
550 assert represented[:id] == to_string(activity.id)
551 assert length(represented[:media_attachments]) == 1
552 assert_schema(represented, "Status", Pleroma.Web.ApiSpec.spec())
555 test "funkwhale audio" do
559 Pleroma.Object.Fetcher.fetch_object_from_id(
560 "https://channels.tests.funkwhale.audio/federation/music/uploads/42342395-0208-4fee-a38d-259a6dae0871"
563 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
565 represented = StatusView.render("show.json", %{for: user, activity: activity})
567 assert represented[:id] == to_string(activity.id)
568 assert length(represented[:media_attachments]) == 1
571 test "a Mobilizon event" do
575 Pleroma.Object.Fetcher.fetch_object_from_id(
576 "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
579 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
581 represented = StatusView.render("show.json", %{for: user, activity: activity})
583 assert represented[:id] == to_string(activity.id)
585 assert represented[:url] ==
586 "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
588 assert represented[:content] ==
589 "<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>"
592 describe "build_tags/1" do
593 test "it returns a a dictionary tags" do
599 "href" => "https://kawen.space/users/lain",
600 "name" => "@lain@kawen.space",
605 assert StatusView.build_tags(object_tags) == [
606 %{name: "fediverse", url: "http://localhost:4001/tag/fediverse"},
607 %{name: "mastodon", url: "http://localhost:4001/tag/mastodon"},
608 %{name: "nextcloud", url: "http://localhost:4001/tag/nextcloud"}
613 describe "rich media cards" do
614 test "a rich media card without a site name renders correctly" do
615 page_url = "http://example.com"
619 image: page_url <> "/example.jpg",
620 title: "Example website"
623 %{provider_name: "example.com"} =
624 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
627 test "a rich media card without a site name or image renders correctly" do
628 page_url = "http://example.com"
632 title: "Example website"
635 %{provider_name: "example.com"} =
636 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
639 test "a rich media card without an image renders correctly" do
640 page_url = "http://example.com"
644 site_name: "Example site name",
645 title: "Example website"
648 %{provider_name: "example.com"} =
649 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
652 test "a rich media card with all relevant data renders correctly" do
653 page_url = "http://example.com"
657 site_name: "Example site name",
658 title: "Example website",
659 image: page_url <> "/example.jpg",
660 description: "Example description"
663 %{provider_name: "example.com"} =
664 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
668 test "does not embed a relationship in the account" do
670 other_user = insert(:user)
673 CommonAPI.post(user, %{
674 status: "drink more water"
677 result = StatusView.render("show.json", %{activity: activity, for: other_user})
679 assert result[:account][:pleroma][:relationship] == %{}
680 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
683 test "does not embed a relationship in the account in reposts" do
685 other_user = insert(:user)
688 CommonAPI.post(user, %{
692 {:ok, activity} = CommonAPI.repeat(activity.id, other_user)
694 result = StatusView.render("show.json", %{activity: activity, for: user})
696 assert result[:account][:pleroma][:relationship] == %{}
697 assert result[:reblog][:account][:pleroma][:relationship] == %{}
698 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
701 test "visibility/list" do
704 {:ok, list} = Pleroma.List.create("foo", user)
706 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
708 status = StatusView.render("show.json", activity: activity)
710 assert status.visibility == "list"
713 test "has a field for parent visibility" do
715 poster = insert(:user)
717 {:ok, invisible} = CommonAPI.post(poster, %{status: "hey", visibility: "private"})
720 CommonAPI.post(poster, %{status: "hey", visibility: "private", in_reply_to_id: invisible.id})
722 status = StatusView.render("show.json", activity: visible, for: user)
723 refute status.pleroma.parent_visible
725 status = StatusView.render("show.json", activity: visible, for: poster)
726 assert status.pleroma.parent_visible