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",
467 "blurhash" => "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn",
475 remote_url: "someurl",
476 preview_url: "someurl",
479 pleroma: %{mime_type: "image/png"},
480 meta: %{original: %{width: 200, height: 100, aspect: 2}},
481 blurhash: "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn"
484 api_spec = Pleroma.Web.ApiSpec.spec()
486 assert expected == StatusView.render("attachment.json", %{attachment: object})
487 assert_schema(expected, "Attachment", api_spec)
489 # If theres a "id", use that instead of the generated one
490 object = Map.put(object, "id", 2)
491 result = StatusView.render("attachment.json", %{attachment: object})
493 assert %{id: "2"} = result
494 assert_schema(result, "Attachment", api_spec)
497 test "put the url advertised in the Activity in to the url attribute" do
498 id = "https://wedistribute.org/wp-json/pterotype/v1/object/85810"
499 [activity] = Activity.search(nil, id)
501 status = StatusView.render("show.json", %{activity: activity})
503 assert status.uri == id
504 assert status.url == "https://wedistribute.org/2019/07/mastodon-drops-ostatus/"
509 activity = insert(:note_activity)
511 {:ok, reblog} = CommonAPI.repeat(activity.id, user)
513 represented = StatusView.render("show.json", %{for: user, activity: reblog})
515 assert represented[:id] == to_string(reblog.id)
516 assert represented[:reblog][:id] == to_string(activity.id)
517 assert represented[:emojis] == []
518 assert_schema(represented, "Status", Pleroma.Web.ApiSpec.spec())
521 test "a peertube video" do
525 Pleroma.Object.Fetcher.fetch_object_from_id(
526 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
529 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
531 represented = StatusView.render("show.json", %{for: user, activity: activity})
533 assert represented[:id] == to_string(activity.id)
534 assert length(represented[:media_attachments]) == 1
535 assert_schema(represented, "Status", Pleroma.Web.ApiSpec.spec())
538 test "funkwhale audio" do
542 Pleroma.Object.Fetcher.fetch_object_from_id(
543 "https://channels.tests.funkwhale.audio/federation/music/uploads/42342395-0208-4fee-a38d-259a6dae0871"
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
554 test "a Mobilizon event" do
558 Pleroma.Object.Fetcher.fetch_object_from_id(
559 "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
562 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
564 represented = StatusView.render("show.json", %{for: user, activity: activity})
566 assert represented[:id] == to_string(activity.id)
568 assert represented[:url] ==
569 "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
571 assert represented[:content] ==
572 "<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>"
575 describe "build_tags/1" do
576 test "it returns a a dictionary tags" do
582 "href" => "https://kawen.space/users/lain",
583 "name" => "@lain@kawen.space",
588 assert StatusView.build_tags(object_tags) == [
589 %{name: "fediverse", url: "http://localhost:4001/tag/fediverse"},
590 %{name: "mastodon", url: "http://localhost:4001/tag/mastodon"},
591 %{name: "nextcloud", url: "http://localhost:4001/tag/nextcloud"}
596 describe "rich media cards" do
597 test "a rich media card without a site name renders correctly" do
598 page_url = "http://example.com"
602 image: page_url <> "/example.jpg",
603 title: "Example website"
606 %{provider_name: "example.com"} =
607 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
610 test "a rich media card without a site name or image renders correctly" do
611 page_url = "http://example.com"
615 title: "Example website"
618 %{provider_name: "example.com"} =
619 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
622 test "a rich media card without an image renders correctly" do
623 page_url = "http://example.com"
627 site_name: "Example site name",
628 title: "Example website"
631 %{provider_name: "example.com"} =
632 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
635 test "a rich media card with all relevant data renders correctly" do
636 page_url = "http://example.com"
640 site_name: "Example site name",
641 title: "Example website",
642 image: page_url <> "/example.jpg",
643 description: "Example description"
646 %{provider_name: "example.com"} =
647 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
651 test "does not embed a relationship in the account" do
653 other_user = insert(:user)
656 CommonAPI.post(user, %{
657 status: "drink more water"
660 result = StatusView.render("show.json", %{activity: activity, for: other_user})
662 assert result[:account][:pleroma][:relationship] == %{}
663 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
666 test "does not embed a relationship in the account in reposts" do
668 other_user = insert(:user)
671 CommonAPI.post(user, %{
675 {:ok, activity} = CommonAPI.repeat(activity.id, other_user)
677 result = StatusView.render("show.json", %{activity: activity, for: user})
679 assert result[:account][:pleroma][:relationship] == %{}
680 assert result[:reblog][:account][:pleroma][:relationship] == %{}
681 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
684 test "visibility/list" do
687 {:ok, list} = Pleroma.List.create("foo", user)
689 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
691 status = StatusView.render("show.json", activity: activity)
693 assert status.visibility == "list"
696 test "has a field for parent visibility" do
698 poster = insert(:user)
700 {:ok, invisible} = CommonAPI.post(poster, %{status: "hey", visibility: "private"})
703 CommonAPI.post(poster, %{status: "hey", visibility: "private", in_reply_to_id: invisible.id})
705 status = StatusView.render("show.json", activity: visible, for: user)
706 refute status.pleroma.parent_visible
708 status = StatusView.render("show.json", activity: visible, for: poster)
709 assert status.pleroma.parent_visible