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,
294 assert status == expected
295 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
298 test "tells if the message is muted for some reason" do
300 other_user = insert(:user)
302 {:ok, _user_relationships} = User.mute(user, other_user)
304 {:ok, activity} = CommonAPI.post(other_user, %{status: "test"})
306 relationships_opt = UserRelationship.view_relationships_option(user, [other_user])
308 opts = %{activity: activity}
309 status = StatusView.render("show.json", opts)
310 assert status.muted == false
311 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
313 status = StatusView.render("show.json", Map.put(opts, :relationships, relationships_opt))
314 assert status.muted == false
316 for_opts = %{activity: activity, for: user}
317 status = StatusView.render("show.json", for_opts)
318 assert status.muted == true
320 status = StatusView.render("show.json", Map.put(for_opts, :relationships, relationships_opt))
321 assert status.muted == true
322 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
325 test "tells if the message is thread muted" do
327 other_user = insert(:user)
329 {:ok, _user_relationships} = User.mute(user, other_user)
331 {:ok, activity} = CommonAPI.post(other_user, %{status: "test"})
332 status = StatusView.render("show.json", %{activity: activity, for: user})
334 assert status.pleroma.thread_muted == false
336 {:ok, activity} = CommonAPI.add_mute(user, activity)
338 status = StatusView.render("show.json", %{activity: activity, for: user})
340 assert status.pleroma.thread_muted == true
343 test "tells if the status is bookmarked" do
346 {:ok, activity} = CommonAPI.post(user, %{status: "Cute girls doing cute things"})
347 status = StatusView.render("show.json", %{activity: activity})
349 assert status.bookmarked == false
351 status = StatusView.render("show.json", %{activity: activity, for: user})
353 assert status.bookmarked == false
355 {:ok, _bookmark} = Bookmark.create(user.id, activity.id)
357 activity = Activity.get_by_id_with_object(activity.id)
359 status = StatusView.render("show.json", %{activity: activity, for: user})
361 assert status.bookmarked == true
365 note = insert(:note_activity)
368 {:ok, activity} = CommonAPI.post(user, %{status: "he", in_reply_to_status_id: note.id})
370 status = StatusView.render("show.json", %{activity: activity})
372 assert status.in_reply_to_id == to_string(note.id)
374 [status] = StatusView.render("index.json", %{activities: [activity], as: :activity})
376 assert status.in_reply_to_id == to_string(note.id)
379 test "contains mentions" do
381 mentioned = insert(:user)
383 {:ok, activity} = CommonAPI.post(user, %{status: "hi @#{mentioned.nickname}"})
385 status = StatusView.render("show.json", %{activity: activity})
387 assert status.mentions ==
388 Enum.map([mentioned], fn u -> AccountView.render("mention.json", %{user: u}) end)
390 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
393 test "create mentions from the 'to' field" do
394 %User{ap_id: recipient_ap_id} = insert(:user)
395 cc = insert_pair(:user) |> Enum.map(& &1.ap_id)
400 "to" => [recipient_ap_id],
406 insert(:note_activity, %{
408 recipients: [recipient_ap_id | cc]
411 assert length(activity.recipients) == 3
413 %{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity})
415 assert length(mentions) == 1
416 assert mention.url == recipient_ap_id
419 test "create mentions from the 'tag' field" do
420 recipient = insert(:user)
421 cc = insert_pair(:user) |> Enum.map(& &1.ap_id)
429 "href" => recipient.ap_id,
430 "name" => recipient.nickname,
434 "href" => "https://example.com/search?tag=test",
443 insert(:note_activity, %{
445 recipients: [recipient.ap_id | cc]
448 assert length(activity.recipients) == 3
450 %{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity})
452 assert length(mentions) == 1
453 assert mention.url == recipient.ap_id
456 test "attachments" do
461 "mediaType" => "image/png",
465 "blurhash" => "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn",
473 remote_url: "someurl",
474 preview_url: "someurl",
477 pleroma: %{mime_type: "image/png"},
478 blurhash: "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn"
481 api_spec = Pleroma.Web.ApiSpec.spec()
483 assert expected == StatusView.render("attachment.json", %{attachment: object})
484 assert_schema(expected, "Attachment", api_spec)
486 # If theres a "id", use that instead of the generated one
487 object = Map.put(object, "id", 2)
488 result = StatusView.render("attachment.json", %{attachment: object})
490 assert %{id: "2"} = result
491 assert_schema(result, "Attachment", api_spec)
494 test "put the url advertised in the Activity in to the url attribute" do
495 id = "https://wedistribute.org/wp-json/pterotype/v1/object/85810"
496 [activity] = Activity.search(nil, id)
498 status = StatusView.render("show.json", %{activity: activity})
500 assert status.uri == id
501 assert status.url == "https://wedistribute.org/2019/07/mastodon-drops-ostatus/"
506 activity = insert(:note_activity)
508 {:ok, reblog} = CommonAPI.repeat(activity.id, user)
510 represented = StatusView.render("show.json", %{for: user, activity: reblog})
512 assert represented[:id] == to_string(reblog.id)
513 assert represented[:reblog][:id] == to_string(activity.id)
514 assert represented[:emojis] == []
515 assert_schema(represented, "Status", Pleroma.Web.ApiSpec.spec())
518 test "a peertube video" do
522 Pleroma.Object.Fetcher.fetch_object_from_id(
523 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
526 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
528 represented = StatusView.render("show.json", %{for: user, activity: activity})
530 assert represented[:id] == to_string(activity.id)
531 assert length(represented[:media_attachments]) == 1
532 assert_schema(represented, "Status", Pleroma.Web.ApiSpec.spec())
535 test "funkwhale audio" do
539 Pleroma.Object.Fetcher.fetch_object_from_id(
540 "https://channels.tests.funkwhale.audio/federation/music/uploads/42342395-0208-4fee-a38d-259a6dae0871"
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
551 test "a Mobilizon event" do
555 Pleroma.Object.Fetcher.fetch_object_from_id(
556 "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
559 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
561 represented = StatusView.render("show.json", %{for: user, activity: activity})
563 assert represented[:id] == to_string(activity.id)
565 assert represented[:url] ==
566 "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
568 assert represented[:content] ==
569 "<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>"
572 describe "build_tags/1" do
573 test "it returns a a dictionary tags" do
579 "href" => "https://kawen.space/users/lain",
580 "name" => "@lain@kawen.space",
585 assert StatusView.build_tags(object_tags) == [
586 %{name: "fediverse", url: "http://localhost:4001/tag/fediverse"},
587 %{name: "mastodon", url: "http://localhost:4001/tag/mastodon"},
588 %{name: "nextcloud", url: "http://localhost:4001/tag/nextcloud"}
593 describe "rich media cards" do
594 test "a rich media card without a site name renders correctly" do
595 page_url = "http://example.com"
599 image: page_url <> "/example.jpg",
600 title: "Example website"
603 %{provider_name: "example.com"} =
604 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
607 test "a rich media card without a site name or image renders correctly" do
608 page_url = "http://example.com"
612 title: "Example website"
615 %{provider_name: "example.com"} =
616 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
619 test "a rich media card without an image renders correctly" do
620 page_url = "http://example.com"
624 site_name: "Example site name",
625 title: "Example website"
628 %{provider_name: "example.com"} =
629 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
632 test "a rich media card with all relevant data renders correctly" do
633 page_url = "http://example.com"
637 site_name: "Example site name",
638 title: "Example website",
639 image: page_url <> "/example.jpg",
640 description: "Example description"
643 %{provider_name: "example.com"} =
644 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
648 test "does not embed a relationship in the account" do
650 other_user = insert(:user)
653 CommonAPI.post(user, %{
654 status: "drink more water"
657 result = StatusView.render("show.json", %{activity: activity, for: other_user})
659 assert result[:account][:pleroma][:relationship] == %{}
660 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
663 test "does not embed a relationship in the account in reposts" do
665 other_user = insert(:user)
668 CommonAPI.post(user, %{
672 {:ok, activity} = CommonAPI.repeat(activity.id, other_user)
674 result = StatusView.render("show.json", %{activity: activity, for: user})
676 assert result[:account][:pleroma][:relationship] == %{}
677 assert result[:reblog][:account][:pleroma][:relationship] == %{}
678 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
681 test "visibility/list" do
684 {:ok, list} = Pleroma.List.create("foo", user)
686 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
688 status = StatusView.render("show.json", activity: activity)
690 assert status.visibility == "list"
693 test "has a field for parent visibility" do
695 poster = insert(:user)
697 {:ok, invisible} = CommonAPI.post(poster, %{status: "hey", visibility: "private"})
700 CommonAPI.post(poster, %{status: "hey", visibility: "private", in_reply_to_id: invisible.id})
702 status = StatusView.render("show.json", activity: visible, for: user)
703 refute status.pleroma.parent_visible
705 status = StatusView.render("show.json", activity: visible, for: poster)
706 assert status.pleroma.parent_visible