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,
308 assert status == expected
309 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
312 test "tells if the message is muted for some reason" do
314 other_user = insert(:user)
316 {:ok, _user_relationships} = User.mute(user, other_user)
318 {:ok, activity} = CommonAPI.post(other_user, %{status: "test"})
320 relationships_opt = UserRelationship.view_relationships_option(user, [other_user])
322 opts = %{activity: activity}
323 status = StatusView.render("show.json", opts)
324 assert status.muted == false
325 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
327 status = StatusView.render("show.json", Map.put(opts, :relationships, relationships_opt))
328 assert status.muted == false
330 for_opts = %{activity: activity, for: user}
331 status = StatusView.render("show.json", for_opts)
332 assert status.muted == true
334 status = StatusView.render("show.json", Map.put(for_opts, :relationships, relationships_opt))
335 assert status.muted == true
336 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
339 test "tells if the message is thread muted" do
341 other_user = insert(:user)
343 {:ok, _user_relationships} = User.mute(user, other_user)
345 {:ok, activity} = CommonAPI.post(other_user, %{status: "test"})
346 status = StatusView.render("show.json", %{activity: activity, for: user})
348 assert status.pleroma.thread_muted == false
350 {:ok, activity} = CommonAPI.add_mute(user, activity)
352 status = StatusView.render("show.json", %{activity: activity, for: user})
354 assert status.pleroma.thread_muted == true
357 test "tells if the status is bookmarked" do
360 {:ok, activity} = CommonAPI.post(user, %{status: "Cute girls doing cute things"})
361 status = StatusView.render("show.json", %{activity: activity})
363 assert status.bookmarked == false
365 status = StatusView.render("show.json", %{activity: activity, for: user})
367 assert status.bookmarked == false
369 {:ok, _bookmark} = Bookmark.create(user.id, activity.id)
371 activity = Activity.get_by_id_with_object(activity.id)
373 status = StatusView.render("show.json", %{activity: activity, for: user})
375 assert status.bookmarked == true
379 note = insert(:note_activity)
382 {:ok, activity} = CommonAPI.post(user, %{status: "he", in_reply_to_status_id: note.id})
384 status = StatusView.render("show.json", %{activity: activity})
386 assert status.in_reply_to_id == to_string(note.id)
388 [status] = StatusView.render("index.json", %{activities: [activity], as: :activity})
390 assert status.in_reply_to_id == to_string(note.id)
393 test "contains mentions" do
395 mentioned = insert(:user)
397 {:ok, activity} = CommonAPI.post(user, %{status: "hi @#{mentioned.nickname}"})
399 status = StatusView.render("show.json", %{activity: activity})
401 assert status.mentions ==
402 Enum.map([mentioned], fn u -> AccountView.render("mention.json", %{user: u}) end)
404 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
407 test "create mentions from the 'to' field" do
408 %User{ap_id: recipient_ap_id} = insert(:user)
409 cc = insert_pair(:user) |> Enum.map(& &1.ap_id)
414 "to" => [recipient_ap_id],
420 insert(:note_activity, %{
422 recipients: [recipient_ap_id | cc]
425 assert length(activity.recipients) == 3
427 %{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity})
429 assert length(mentions) == 1
430 assert mention.url == recipient_ap_id
433 test "create mentions from the 'tag' field" do
434 recipient = insert(:user)
435 cc = insert_pair(:user) |> Enum.map(& &1.ap_id)
443 "href" => recipient.ap_id,
444 "name" => recipient.nickname,
448 "href" => "https://example.com/search?tag=test",
457 insert(:note_activity, %{
459 recipients: [recipient.ap_id | cc]
462 assert length(activity.recipients) == 3
464 %{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity})
466 assert length(mentions) == 1
467 assert mention.url == recipient.ap_id
470 test "attachments" do
475 "mediaType" => "image/png",
481 "blurhash" => "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn",
489 remote_url: "someurl",
490 preview_url: "someurl",
493 pleroma: %{mime_type: "image/png"},
494 meta: %{original: %{width: 200, height: 100, aspect: 2}},
495 blurhash: "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn"
498 api_spec = Pleroma.Web.ApiSpec.spec()
500 assert expected == StatusView.render("attachment.json", %{attachment: object})
501 assert_schema(expected, "Attachment", api_spec)
503 # If theres a "id", use that instead of the generated one
504 object = Map.put(object, "id", 2)
505 result = StatusView.render("attachment.json", %{attachment: object})
507 assert %{id: "2"} = result
508 assert_schema(result, "Attachment", api_spec)
511 test "put the url advertised in the Activity in to the url attribute" do
512 id = "https://wedistribute.org/wp-json/pterotype/v1/object/85810"
513 [activity] = Activity.search(nil, id)
515 status = StatusView.render("show.json", %{activity: activity})
517 assert status.uri == id
518 assert status.url == "https://wedistribute.org/2019/07/mastodon-drops-ostatus/"
523 activity = insert(:note_activity)
525 {:ok, reblog} = CommonAPI.repeat(activity.id, user)
527 represented = StatusView.render("show.json", %{for: user, activity: reblog})
529 assert represented[:id] == to_string(reblog.id)
530 assert represented[:reblog][:id] == to_string(activity.id)
531 assert represented[:emojis] == []
532 assert_schema(represented, "Status", Pleroma.Web.ApiSpec.spec())
535 test "a peertube video" do
539 Pleroma.Object.Fetcher.fetch_object_from_id(
540 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
543 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
545 represented = StatusView.render("show.json", %{for: user, activity: activity})
547 assert represented[:id] == to_string(activity.id)
548 assert length(represented[:media_attachments]) == 1
549 assert_schema(represented, "Status", Pleroma.Web.ApiSpec.spec())
552 test "funkwhale audio" do
556 Pleroma.Object.Fetcher.fetch_object_from_id(
557 "https://channels.tests.funkwhale.audio/federation/music/uploads/42342395-0208-4fee-a38d-259a6dae0871"
560 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
562 represented = StatusView.render("show.json", %{for: user, activity: activity})
564 assert represented[:id] == to_string(activity.id)
565 assert length(represented[:media_attachments]) == 1
568 test "a Mobilizon event" do
572 Pleroma.Object.Fetcher.fetch_object_from_id(
573 "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
576 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
578 represented = StatusView.render("show.json", %{for: user, activity: activity})
580 assert represented[:id] == to_string(activity.id)
582 assert represented[:url] ==
583 "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
585 assert represented[:content] ==
586 "<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>"
589 describe "build_tags/1" do
590 test "it returns a a dictionary tags" do
596 "href" => "https://kawen.space/users/lain",
597 "name" => "@lain@kawen.space",
602 assert StatusView.build_tags(object_tags) == [
603 %{name: "fediverse", url: "http://localhost:4001/tag/fediverse"},
604 %{name: "mastodon", url: "http://localhost:4001/tag/mastodon"},
605 %{name: "nextcloud", url: "http://localhost:4001/tag/nextcloud"}
610 describe "rich media cards" do
611 test "a rich media card without a site name renders correctly" do
612 page_url = "http://example.com"
616 image: page_url <> "/example.jpg",
617 title: "Example website"
620 %{provider_name: "example.com"} =
621 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
624 test "a rich media card without a site name or image renders correctly" do
625 page_url = "http://example.com"
629 title: "Example website"
632 %{provider_name: "example.com"} =
633 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
636 test "a rich media card without an image renders correctly" do
637 page_url = "http://example.com"
641 site_name: "Example site name",
642 title: "Example website"
645 %{provider_name: "example.com"} =
646 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
649 test "a rich media card with all relevant data renders correctly" do
650 page_url = "http://example.com"
654 site_name: "Example site name",
655 title: "Example website",
656 image: page_url <> "/example.jpg",
657 description: "Example description"
660 %{provider_name: "example.com"} =
661 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
665 test "does not embed a relationship in the account" do
667 other_user = insert(:user)
670 CommonAPI.post(user, %{
671 status: "drink more water"
674 result = StatusView.render("show.json", %{activity: activity, for: other_user})
676 assert result[:account][:pleroma][:relationship] == %{}
677 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
680 test "does not embed a relationship in the account in reposts" do
682 other_user = insert(:user)
685 CommonAPI.post(user, %{
689 {:ok, activity} = CommonAPI.repeat(activity.id, other_user)
691 result = StatusView.render("show.json", %{activity: activity, for: user})
693 assert result[:account][:pleroma][:relationship] == %{}
694 assert result[:reblog][:account][:pleroma][:relationship] == %{}
695 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
698 test "visibility/list" do
701 {:ok, list} = Pleroma.List.create("foo", user)
703 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
705 status = StatusView.render("show.json", activity: activity)
707 assert status.visibility == "list"
710 test "has a field for parent visibility" do
712 poster = insert(:user)
714 {:ok, invisible} = CommonAPI.post(poster, %{status: "hey", visibility: "private"})
717 CommonAPI.post(poster, %{status: "hey", visibility: "private", in_reply_to_id: invisible.id})
719 status = StatusView.render("show.json", activity: visible, for: user)
720 refute status.pleroma.parent_visible
722 status = StatusView.render("show.json", activity: visible, for: poster)
723 assert status.pleroma.parent_visible