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"])
313 assert status == expected
314 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
317 test "tells if the message is muted for some reason" do
319 other_user = insert(:user)
321 {:ok, _user_relationships} = User.mute(user, other_user)
323 {:ok, activity} = CommonAPI.post(other_user, %{status: "test"})
325 relationships_opt = UserRelationship.view_relationships_option(user, [other_user])
327 opts = %{activity: activity}
328 status = StatusView.render("show.json", opts)
329 assert status.muted == false
330 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
332 status = StatusView.render("show.json", Map.put(opts, :relationships, relationships_opt))
333 assert status.muted == false
335 for_opts = %{activity: activity, for: user}
336 status = StatusView.render("show.json", for_opts)
337 assert status.muted == true
339 status = StatusView.render("show.json", Map.put(for_opts, :relationships, relationships_opt))
340 assert status.muted == true
341 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
344 test "tells if the message is thread muted" do
346 other_user = insert(:user)
348 {:ok, _user_relationships} = User.mute(user, other_user)
350 {:ok, activity} = CommonAPI.post(other_user, %{status: "test"})
351 status = StatusView.render("show.json", %{activity: activity, for: user})
353 assert status.pleroma.thread_muted == false
355 {:ok, activity} = CommonAPI.add_mute(user, activity)
357 status = StatusView.render("show.json", %{activity: activity, for: user})
359 assert status.pleroma.thread_muted == true
362 test "tells if the status is bookmarked" do
365 {:ok, activity} = CommonAPI.post(user, %{status: "Cute girls doing cute things"})
366 status = StatusView.render("show.json", %{activity: activity})
368 assert status.bookmarked == false
370 status = StatusView.render("show.json", %{activity: activity, for: user})
372 assert status.bookmarked == false
374 {:ok, _bookmark} = Bookmark.create(user.id, activity.id)
376 activity = Activity.get_by_id_with_object(activity.id)
378 status = StatusView.render("show.json", %{activity: activity, for: user})
380 assert status.bookmarked == true
384 note = insert(:note_activity)
387 {:ok, activity} = CommonAPI.post(user, %{status: "he", in_reply_to_status_id: note.id})
389 status = StatusView.render("show.json", %{activity: activity})
391 assert status.in_reply_to_id == to_string(note.id)
393 [status] = StatusView.render("index.json", %{activities: [activity], as: :activity})
395 assert status.in_reply_to_id == to_string(note.id)
399 note = insert(:note_activity)
402 {:ok, activity} = CommonAPI.post(user, %{status: "hehe", quote_id: note.id})
404 status = StatusView.render("show.json", %{activity: activity})
406 assert status.quote_id == to_string(note.id)
408 [status] = StatusView.render("index.json", %{activities: [activity], as: :activity})
410 assert status.quote_id == to_string(note.id)
413 test "a quote that we can't resolve" do
414 note = insert(:note_activity, quoteUri: "oopsie")
416 status = StatusView.render("show.json", %{activity: note})
418 assert is_nil(status.quote_id)
419 assert is_nil(status.quote)
422 test "contains mentions" do
424 mentioned = insert(:user)
426 {:ok, activity} = CommonAPI.post(user, %{status: "hi @#{mentioned.nickname}"})
428 status = StatusView.render("show.json", %{activity: activity})
430 assert status.mentions ==
431 Enum.map([mentioned], fn u -> AccountView.render("mention.json", %{user: u}) end)
433 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
436 test "create mentions from the 'to' field" do
437 %User{ap_id: recipient_ap_id} = insert(:user)
438 cc = insert_pair(:user) |> Enum.map(& &1.ap_id)
443 "to" => [recipient_ap_id],
449 insert(:note_activity, %{
451 recipients: [recipient_ap_id | cc]
454 assert length(activity.recipients) == 3
456 %{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity})
458 assert length(mentions) == 1
459 assert mention.url == recipient_ap_id
462 test "create mentions from the 'tag' field" do
463 recipient = insert(:user)
464 cc = insert_pair(:user) |> Enum.map(& &1.ap_id)
472 "href" => recipient.ap_id,
473 "name" => recipient.nickname,
477 "href" => "https://example.com/search?tag=test",
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 "attachments" do
504 "mediaType" => "image/png",
510 "blurhash" => "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn",
518 remote_url: "someurl",
519 preview_url: "someurl",
522 pleroma: %{mime_type: "image/png"},
523 meta: %{original: %{width: 200, height: 100, aspect: 2}},
524 blurhash: "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn"
527 api_spec = Pleroma.Web.ApiSpec.spec()
529 assert expected == StatusView.render("attachment.json", %{attachment: object})
530 assert_schema(expected, "Attachment", api_spec)
532 # If theres a "id", use that instead of the generated one
533 object = Map.put(object, "id", 2)
534 result = StatusView.render("attachment.json", %{attachment: object})
536 assert %{id: "2"} = result
537 assert_schema(result, "Attachment", api_spec)
540 test "put the url advertised in the Activity in to the url attribute" do
541 id = "https://wedistribute.org/wp-json/pterotype/v1/object/85810"
542 [activity] = Activity.search(nil, id)
544 status = StatusView.render("show.json", %{activity: activity})
546 assert status.uri == id
547 assert status.url == "https://wedistribute.org/2019/07/mastodon-drops-ostatus/"
552 activity = insert(:note_activity)
554 {:ok, reblog} = CommonAPI.repeat(activity.id, user)
556 represented = StatusView.render("show.json", %{for: user, activity: reblog})
558 assert represented[:id] == to_string(reblog.id)
559 assert represented[:reblog][:id] == to_string(activity.id)
560 assert represented[:emojis] == []
561 assert_schema(represented, "Status", Pleroma.Web.ApiSpec.spec())
564 test "a peertube video" do
568 Pleroma.Object.Fetcher.fetch_object_from_id(
569 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
572 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
574 represented = StatusView.render("show.json", %{for: user, activity: activity})
576 assert represented[:id] == to_string(activity.id)
577 assert length(represented[:media_attachments]) == 1
578 assert_schema(represented, "Status", Pleroma.Web.ApiSpec.spec())
581 test "funkwhale audio" do
585 Pleroma.Object.Fetcher.fetch_object_from_id(
586 "https://channels.tests.funkwhale.audio/federation/music/uploads/42342395-0208-4fee-a38d-259a6dae0871"
589 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
591 represented = StatusView.render("show.json", %{for: user, activity: activity})
593 assert represented[:id] == to_string(activity.id)
594 assert length(represented[:media_attachments]) == 1
597 test "a Mobilizon event" do
601 Pleroma.Object.Fetcher.fetch_object_from_id(
602 "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
605 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
607 represented = StatusView.render("show.json", %{for: user, activity: activity})
609 assert represented[:id] == to_string(activity.id)
611 assert represented[:url] ==
612 "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
614 assert represented[:content] ==
615 "<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>"
618 describe "build_tags/1" do
619 test "it returns a a dictionary tags" do
625 "href" => "https://kawen.space/users/lain",
626 "name" => "@lain@kawen.space",
631 assert StatusView.build_tags(object_tags) == [
632 %{name: "fediverse", url: "http://localhost:4001/tag/fediverse"},
633 %{name: "mastodon", url: "http://localhost:4001/tag/mastodon"},
634 %{name: "nextcloud", url: "http://localhost:4001/tag/nextcloud"}
639 describe "rich media cards" do
640 test "a rich media card without a site name renders correctly" do
641 page_url = "http://example.com"
645 image: page_url <> "/example.jpg",
646 title: "Example website"
649 %{provider_name: "example.com"} =
650 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
653 test "a rich media card without a site name or image renders correctly" do
654 page_url = "http://example.com"
658 title: "Example website"
661 %{provider_name: "example.com"} =
662 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
665 test "a rich media card without an image renders correctly" do
666 page_url = "http://example.com"
670 site_name: "Example site name",
671 title: "Example website"
674 %{provider_name: "example.com"} =
675 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
678 test "a rich media card with all relevant data renders correctly" do
679 page_url = "http://example.com"
683 site_name: "Example site name",
684 title: "Example website",
685 image: page_url <> "/example.jpg",
686 description: "Example description"
689 %{provider_name: "example.com"} =
690 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
694 test "does not embed a relationship in the account" do
696 other_user = insert(:user)
699 CommonAPI.post(user, %{
700 status: "drink more water"
703 result = StatusView.render("show.json", %{activity: activity, for: other_user})
705 assert result[:account][:pleroma][:relationship] == %{}
706 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
709 test "does not embed a relationship in the account in reposts" do
711 other_user = insert(:user)
714 CommonAPI.post(user, %{
718 {:ok, activity} = CommonAPI.repeat(activity.id, other_user)
720 result = StatusView.render("show.json", %{activity: activity, for: user})
722 assert result[:account][:pleroma][:relationship] == %{}
723 assert result[:reblog][:account][:pleroma][:relationship] == %{}
724 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
727 test "visibility/list" do
730 {:ok, list} = Pleroma.List.create("foo", user)
732 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
734 status = StatusView.render("show.json", activity: activity)
736 assert status.visibility == "list"
739 test "has a field for parent visibility" do
741 poster = insert(:user)
743 {:ok, invisible} = CommonAPI.post(poster, %{status: "hey", visibility: "private"})
746 CommonAPI.post(poster, %{status: "hey", visibility: "private", in_reply_to_id: invisible.id})
748 status = StatusView.render("show.json", activity: visible, for: user)
749 refute status.pleroma.parent_visible
751 status = StatusView.render("show.json", activity: visible, for: poster)
752 assert status.pleroma.parent_visible