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: "#{object_data["tag"]}",
266 url: "http://localhost:4001/tag/#{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",
466 "blurhash" => "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn",
474 remote_url: "someurl",
475 preview_url: "someurl",
478 pleroma: %{mime_type: "image/png"},
479 meta: %{original: %{width: 200, height: 100, aspect: 2}},
480 blurhash: "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn"
483 api_spec = Pleroma.Web.ApiSpec.spec()
485 assert expected == StatusView.render("attachment.json", %{attachment: object})
486 assert_schema(expected, "Attachment", api_spec)
488 # If theres a "id", use that instead of the generated one
489 object = Map.put(object, "id", 2)
490 result = StatusView.render("attachment.json", %{attachment: object})
492 assert %{id: "2"} = result
493 assert_schema(result, "Attachment", api_spec)
496 test "put the url advertised in the Activity in to the url attribute" do
497 id = "https://wedistribute.org/wp-json/pterotype/v1/object/85810"
498 [activity] = Activity.search(nil, id)
500 status = StatusView.render("show.json", %{activity: activity})
502 assert status.uri == id
503 assert status.url == "https://wedistribute.org/2019/07/mastodon-drops-ostatus/"
508 activity = insert(:note_activity)
510 {:ok, reblog} = CommonAPI.repeat(activity.id, user)
512 represented = StatusView.render("show.json", %{for: user, activity: reblog})
514 assert represented[:id] == to_string(reblog.id)
515 assert represented[:reblog][:id] == to_string(activity.id)
516 assert represented[:emojis] == []
517 assert_schema(represented, "Status", Pleroma.Web.ApiSpec.spec())
520 test "a peertube video" do
524 Pleroma.Object.Fetcher.fetch_object_from_id(
525 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
528 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
530 represented = StatusView.render("show.json", %{for: user, activity: activity})
532 assert represented[:id] == to_string(activity.id)
533 assert length(represented[:media_attachments]) == 1
534 assert_schema(represented, "Status", Pleroma.Web.ApiSpec.spec())
537 test "funkwhale audio" do
541 Pleroma.Object.Fetcher.fetch_object_from_id(
542 "https://channels.tests.funkwhale.audio/federation/music/uploads/42342395-0208-4fee-a38d-259a6dae0871"
545 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
547 represented = StatusView.render("show.json", %{for: user, activity: activity})
549 assert represented[:id] == to_string(activity.id)
550 assert length(represented[:media_attachments]) == 1
553 test "a Mobilizon event" do
557 Pleroma.Object.Fetcher.fetch_object_from_id(
558 "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
561 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
563 represented = StatusView.render("show.json", %{for: user, activity: activity})
565 assert represented[:id] == to_string(activity.id)
567 assert represented[:url] ==
568 "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
570 assert represented[:content] ==
571 "<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>"
574 describe "build_tags/1" do
575 test "it returns a a dictionary tags" do
581 "href" => "https://kawen.space/users/lain",
582 "name" => "@lain@kawen.space",
587 assert StatusView.build_tags(object_tags) == [
588 %{name: "fediverse", url: "http://localhost:4001/tag/fediverse"},
589 %{name: "mastodon", url: "http://localhost:4001/tag/mastodon"},
590 %{name: "nextcloud", url: "http://localhost:4001/tag/nextcloud"}
595 describe "rich media cards" do
596 test "a rich media card without a site name renders correctly" do
597 page_url = "http://example.com"
601 image: page_url <> "/example.jpg",
602 title: "Example website"
605 %{provider_name: "example.com"} =
606 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
609 test "a rich media card without a site name or image renders correctly" do
610 page_url = "http://example.com"
614 title: "Example website"
617 %{provider_name: "example.com"} =
618 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
621 test "a rich media card without an image renders correctly" do
622 page_url = "http://example.com"
626 site_name: "Example site name",
627 title: "Example website"
630 %{provider_name: "example.com"} =
631 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
634 test "a rich media card with all relevant data renders correctly" do
635 page_url = "http://example.com"
639 site_name: "Example site name",
640 title: "Example website",
641 image: page_url <> "/example.jpg",
642 description: "Example description"
645 %{provider_name: "example.com"} =
646 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
650 test "does not embed a relationship in the account" do
652 other_user = insert(:user)
655 CommonAPI.post(user, %{
656 status: "drink more water"
659 result = StatusView.render("show.json", %{activity: activity, for: other_user})
661 assert result[:account][:pleroma][:relationship] == %{}
662 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
665 test "does not embed a relationship in the account in reposts" do
667 other_user = insert(:user)
670 CommonAPI.post(user, %{
674 {:ok, activity} = CommonAPI.repeat(activity.id, other_user)
676 result = StatusView.render("show.json", %{activity: activity, for: user})
678 assert result[:account][:pleroma][:relationship] == %{}
679 assert result[:reblog][:account][:pleroma][:relationship] == %{}
680 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
683 test "visibility/list" do
686 {:ok, list} = Pleroma.List.create("foo", user)
688 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
690 status = StatusView.render("show.json", activity: activity)
692 assert status.visibility == "list"
695 test "has a field for parent visibility" do
697 poster = insert(:user)
699 {:ok, invisible} = CommonAPI.post(poster, %{status: "hey", visibility: "private"})
702 CommonAPI.post(poster, %{status: "hey", visibility: "private", in_reply_to_id: invisible.id})
704 status = StatusView.render("show.json", activity: visible, for: user)
705 refute status.pleroma.parent_visible
707 status = StatusView.render("show.json", activity: visible, for: poster)
708 assert status.pleroma.parent_visible