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"]}"
278 static_url: "corndog.png",
279 visible_in_picker: false
284 conversation_id: convo_id,
285 in_reply_to_account_acct: nil,
286 content: %{"text/plain" => HTML.strip_tags(object_data["content"])},
287 spoiler_text: %{"text/plain" => HTML.strip_tags(object_data["summary"])},
289 direct_conversation_id: nil,
292 parent_visible: false
296 assert status == expected
297 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
300 test "tells if the message is muted for some reason" do
302 other_user = insert(:user)
304 {:ok, _user_relationships} = User.mute(user, other_user)
306 {:ok, activity} = CommonAPI.post(other_user, %{status: "test"})
308 relationships_opt = UserRelationship.view_relationships_option(user, [other_user])
310 opts = %{activity: activity}
311 status = StatusView.render("show.json", opts)
312 assert status.muted == false
313 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
315 status = StatusView.render("show.json", Map.put(opts, :relationships, relationships_opt))
316 assert status.muted == false
318 for_opts = %{activity: activity, for: user}
319 status = StatusView.render("show.json", for_opts)
320 assert status.muted == true
322 status = StatusView.render("show.json", Map.put(for_opts, :relationships, relationships_opt))
323 assert status.muted == true
324 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
327 test "tells if the message is thread muted" do
329 other_user = insert(:user)
331 {:ok, _user_relationships} = User.mute(user, other_user)
333 {:ok, activity} = CommonAPI.post(other_user, %{status: "test"})
334 status = StatusView.render("show.json", %{activity: activity, for: user})
336 assert status.pleroma.thread_muted == false
338 {:ok, activity} = CommonAPI.add_mute(user, activity)
340 status = StatusView.render("show.json", %{activity: activity, for: user})
342 assert status.pleroma.thread_muted == true
345 test "tells if the status is bookmarked" do
348 {:ok, activity} = CommonAPI.post(user, %{status: "Cute girls doing cute things"})
349 status = StatusView.render("show.json", %{activity: activity})
351 assert status.bookmarked == false
353 status = StatusView.render("show.json", %{activity: activity, for: user})
355 assert status.bookmarked == false
357 {:ok, _bookmark} = Bookmark.create(user.id, activity.id)
359 activity = Activity.get_by_id_with_object(activity.id)
361 status = StatusView.render("show.json", %{activity: activity, for: user})
363 assert status.bookmarked == true
367 note = insert(:note_activity)
370 {:ok, activity} = CommonAPI.post(user, %{status: "he", in_reply_to_status_id: note.id})
372 status = StatusView.render("show.json", %{activity: activity})
374 assert status.in_reply_to_id == to_string(note.id)
376 [status] = StatusView.render("index.json", %{activities: [activity], as: :activity})
378 assert status.in_reply_to_id == to_string(note.id)
381 test "contains mentions" do
383 mentioned = insert(:user)
385 {:ok, activity} = CommonAPI.post(user, %{status: "hi @#{mentioned.nickname}"})
387 status = StatusView.render("show.json", %{activity: activity})
389 assert status.mentions ==
390 Enum.map([mentioned], fn u -> AccountView.render("mention.json", %{user: u}) end)
392 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
395 test "create mentions from the 'to' field" do
396 %User{ap_id: recipient_ap_id} = insert(:user)
397 cc = insert_pair(:user) |> Enum.map(& &1.ap_id)
402 "to" => [recipient_ap_id],
408 insert(:note_activity, %{
410 recipients: [recipient_ap_id | cc]
413 assert length(activity.recipients) == 3
415 %{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity})
417 assert length(mentions) == 1
418 assert mention.url == recipient_ap_id
421 test "create mentions from the 'tag' field" do
422 recipient = insert(:user)
423 cc = insert_pair(:user) |> Enum.map(& &1.ap_id)
431 "href" => recipient.ap_id,
432 "name" => recipient.nickname,
436 "href" => "https://example.com/search?tag=test",
445 insert(:note_activity, %{
447 recipients: [recipient.ap_id | cc]
450 assert length(activity.recipients) == 3
452 %{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity})
454 assert length(mentions) == 1
455 assert mention.url == recipient.ap_id
458 test "attachments" do
463 "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 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