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.MastodonAPI.AccountView
18 alias Pleroma.Web.MastodonAPI.StatusView
20 import Pleroma.Factory
22 import OpenApiSpex.TestAssertions
25 mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
29 test "has an emoji reaction list" do
31 other_user = insert(:user)
32 third_user = insert(:user)
33 {:ok, activity} = CommonAPI.post(user, %{status: "dae cofe??"})
35 {:ok, _} = CommonAPI.react_with_emoji(activity.id, user, "☕")
36 {:ok, _} = CommonAPI.react_with_emoji(activity.id, user, ":dinosaur:")
37 {:ok, _} = CommonAPI.react_with_emoji(activity.id, third_user, "🍵")
38 {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕")
39 {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, ":dinosaur:")
41 activity = Repo.get(Activity, activity.id)
42 status = StatusView.render("show.json", activity: activity)
44 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
46 assert status[:pleroma][:emoji_reactions] == [
47 %{name: "☕", count: 2, me: false, url: nil},
52 url: "http://localhost:4001/emoji/dino walking.gif"
54 %{name: "🍵", count: 1, me: false, url: nil}
57 status = StatusView.render("show.json", activity: activity, for: user)
59 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
61 assert status[:pleroma][:emoji_reactions] == [
62 %{name: "☕", count: 2, me: true, url: nil},
67 url: "http://localhost:4001/emoji/dino walking.gif"
69 %{name: "🍵", count: 1, me: false, url: nil}
73 test "works correctly with badly formatted emojis" do
75 {:ok, activity} = CommonAPI.post(user, %{status: "yo"})
78 |> Object.normalize(fetch: false)
79 |> Object.update_data(%{"reactions" => %{"☕" => [user.ap_id], "x" => 1}})
81 activity = Activity.get_by_id(activity.id)
82 status = StatusView.render("show.json", activity: activity, for: user)
84 assert status[:pleroma][:emoji_reactions] == [
85 %{name: "☕", count: 1, me: true, url: nil}
89 test "doesn't show reactions from muted and blocked users" do
91 other_user = insert(:user)
92 third_user = insert(:user)
94 {:ok, activity} = CommonAPI.post(user, %{status: "dae cofe??"})
96 {:ok, _} = User.mute(user, other_user)
97 {:ok, _} = User.block(other_user, third_user)
99 {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕")
101 activity = Repo.get(Activity, activity.id)
102 status = StatusView.render("show.json", activity: activity)
104 assert status[:pleroma][:emoji_reactions] == [
105 %{name: "☕", count: 1, me: false, url: nil}
108 status = StatusView.render("show.json", activity: activity, for: user)
110 assert status[:pleroma][:emoji_reactions] == []
112 {:ok, _} = CommonAPI.react_with_emoji(activity.id, third_user, "☕")
114 status = StatusView.render("show.json", activity: activity)
116 assert status[:pleroma][:emoji_reactions] == [
117 %{name: "☕", count: 2, me: false, url: nil}
120 status = StatusView.render("show.json", activity: activity, for: user)
122 assert status[:pleroma][:emoji_reactions] == [
123 %{name: "☕", count: 1, me: false, url: nil}
126 status = StatusView.render("show.json", activity: activity, for: other_user)
128 assert status[:pleroma][:emoji_reactions] == [
129 %{name: "☕", count: 1, me: true, url: nil}
133 test "loads and returns the direct conversation id when given the `with_direct_conversation_id` option" do
136 {:ok, activity} = CommonAPI.post(user, %{status: "Hey @shp!", visibility: "direct"})
137 [participation] = Participation.for_user(user)
140 StatusView.render("show.json",
142 with_direct_conversation_id: true,
146 assert status[:pleroma][:direct_conversation_id] == participation.id
148 status = StatusView.render("show.json", activity: activity, for: user)
149 assert status[:pleroma][:direct_conversation_id] == nil
150 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
153 test "returns the direct conversation id when given the `direct_conversation_id` option" do
156 {:ok, activity} = CommonAPI.post(user, %{status: "Hey @shp!", visibility: "direct"})
157 [participation] = Participation.for_user(user)
160 StatusView.render("show.json",
162 direct_conversation_id: participation.id,
166 assert status[:pleroma][:direct_conversation_id] == participation.id
167 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
170 test "returns a temporary ap_id based user for activities missing db users" do
173 {:ok, activity} = CommonAPI.post(user, %{status: "Hey @shp!", visibility: "direct"})
176 User.invalidate_cache(user)
179 "https://localhost/.well-known/webfinger?resource=acct:#{user.nickname}@localhost"
181 Tesla.Mock.mock_global(fn
182 %{method: :get, url: "http://localhost/.well-known/host-meta"} ->
183 %Tesla.Env{status: 404, body: ""}
185 %{method: :get, url: "https://localhost/.well-known/host-meta"} ->
186 %Tesla.Env{status: 404, body: ""}
192 %Tesla.Env{status: 404, body: ""}
195 %{account: ms_user} = StatusView.render("show.json", activity: activity)
197 assert ms_user.acct == "erroruser@example.com"
200 test "tries to get a user by nickname if fetching by ap_id doesn't work" do
203 {:ok, activity} = CommonAPI.post(user, %{status: "Hey @shp!", visibility: "direct"})
207 |> Ecto.Changeset.change(%{ap_id: "#{user.ap_id}/extension/#{user.nickname}"})
210 User.invalidate_cache(user)
212 result = StatusView.render("show.json", activity: activity)
214 assert result[:account][:id] == to_string(user.id)
215 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
218 test "a note with null content" do
219 note = insert(:note_activity)
220 note_object = Object.normalize(note, fetch: false)
224 |> Map.put("content", nil)
226 Object.change(note_object, %{data: data})
227 |> Object.update_and_set_cache()
229 User.get_cached_by_ap_id(note.data["actor"])
231 status = StatusView.render("show.json", %{activity: note})
233 assert status.content == ""
234 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
237 test "a note activity" do
238 note = insert(:note_activity)
239 object_data = Object.normalize(note, fetch: false).data
240 user = User.get_cached_by_ap_id(note.data["actor"])
242 convo_id = :erlang.crc32(object_data["context"])
244 status = StatusView.render("show.json", %{activity: note})
247 (object_data["published"] || "")
248 |> String.replace(~r/\.\d+Z/, ".000Z")
251 id: to_string(note.id),
252 uri: object_data["id"],
253 url: Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, note),
254 account: AccountView.render("show.json", %{user: user, skip_visibility_check: true}),
256 in_reply_to_account_id: nil,
259 content: HTML.filter_tags(object_data["content"]),
261 created_at: created_at,
272 spoiler_text: HTML.filter_tags(object_data["summary"]),
273 visibility: "public",
274 media_attachments: [],
278 name: "#{hd(object_data["tag"])}",
279 url: "http://localhost:4001/tag/#{hd(object_data["tag"])}"
288 static_url: "corndog.png",
289 visible_in_picker: false
294 conversation_id: convo_id,
295 in_reply_to_account_acct: nil,
296 content: %{"text/plain" => HTML.strip_tags(object_data["content"])},
297 spoiler_text: %{"text/plain" => HTML.strip_tags(object_data["summary"])},
299 direct_conversation_id: nil,
302 parent_visible: false,
306 source: HTML.filter_tags(object_data["content"])
312 assert status == expected
313 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
316 test "tells if the message is muted for some reason" do
318 other_user = insert(:user)
320 {:ok, _user_relationships} = User.mute(user, other_user)
322 {:ok, activity} = CommonAPI.post(other_user, %{status: "test"})
324 relationships_opt = UserRelationship.view_relationships_option(user, [other_user])
326 opts = %{activity: activity}
327 status = StatusView.render("show.json", opts)
328 assert status.muted == false
329 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
331 status = StatusView.render("show.json", Map.put(opts, :relationships, relationships_opt))
332 assert status.muted == false
334 for_opts = %{activity: activity, for: user}
335 status = StatusView.render("show.json", for_opts)
336 assert status.muted == true
338 status = StatusView.render("show.json", Map.put(for_opts, :relationships, relationships_opt))
339 assert status.muted == true
340 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
343 test "tells if the message is thread muted" do
345 other_user = insert(:user)
347 {:ok, _user_relationships} = User.mute(user, other_user)
349 {:ok, activity} = CommonAPI.post(other_user, %{status: "test"})
350 status = StatusView.render("show.json", %{activity: activity, for: user})
352 assert status.pleroma.thread_muted == false
354 {:ok, activity} = CommonAPI.add_mute(user, activity)
356 status = StatusView.render("show.json", %{activity: activity, for: user})
358 assert status.pleroma.thread_muted == true
361 test "tells if the status is bookmarked" do
364 {:ok, activity} = CommonAPI.post(user, %{status: "Cute girls doing cute things"})
365 status = StatusView.render("show.json", %{activity: activity})
367 assert status.bookmarked == false
369 status = StatusView.render("show.json", %{activity: activity, for: user})
371 assert status.bookmarked == false
373 {:ok, _bookmark} = Bookmark.create(user.id, activity.id)
375 activity = Activity.get_by_id_with_object(activity.id)
377 status = StatusView.render("show.json", %{activity: activity, for: user})
379 assert status.bookmarked == true
383 note = insert(:note_activity)
386 {:ok, activity} = CommonAPI.post(user, %{status: "he", in_reply_to_status_id: note.id})
388 status = StatusView.render("show.json", %{activity: activity})
390 assert status.in_reply_to_id == to_string(note.id)
392 [status] = StatusView.render("index.json", %{activities: [activity], as: :activity})
394 assert status.in_reply_to_id == to_string(note.id)
398 note = insert(:note_activity)
401 {:ok, activity} = CommonAPI.post(user, %{status: "hehe", quote_id: note.id})
403 status = StatusView.render("show.json", %{activity: activity})
405 assert status.quote_id == to_string(note.id)
407 [status] = StatusView.render("index.json", %{activities: [activity], as: :activity})
409 assert status.quote_id == to_string(note.id)
412 test "a quote that we can't resolve" do
413 note = insert(:note_activity, quoteUri: "oopsie")
415 status = StatusView.render("show.json", %{activity: note})
417 assert is_nil(status.quote_id)
418 assert is_nil(status.quote)
421 test "contains mentions" do
423 mentioned = insert(:user)
425 {:ok, activity} = CommonAPI.post(user, %{status: "hi @#{mentioned.nickname}"})
427 status = StatusView.render("show.json", %{activity: activity})
429 assert status.mentions ==
430 Enum.map([mentioned], fn u -> AccountView.render("mention.json", %{user: u}) end)
432 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
435 test "create mentions from the 'to' field" do
436 %User{ap_id: recipient_ap_id} = insert(:user)
437 cc = insert_pair(:user) |> Enum.map(& &1.ap_id)
442 "to" => [recipient_ap_id],
448 insert(:note_activity, %{
450 recipients: [recipient_ap_id | cc]
453 assert length(activity.recipients) == 3
455 %{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity})
457 assert length(mentions) == 1
458 assert mention.url == recipient_ap_id
461 test "create mentions from the 'tag' field" do
462 recipient = insert(:user)
463 cc = insert_pair(:user) |> Enum.map(& &1.ap_id)
471 "href" => recipient.ap_id,
472 "name" => recipient.nickname,
476 "href" => "https://example.com/search?tag=test",
485 insert(:note_activity, %{
487 recipients: [recipient.ap_id | cc]
490 assert length(activity.recipients) == 3
492 %{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity})
494 assert length(mentions) == 1
495 assert mention.url == recipient.ap_id
498 test "attachments" do
503 "mediaType" => "image/png",
509 "blurhash" => "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn",
517 remote_url: "someurl",
518 preview_url: "someurl",
521 pleroma: %{mime_type: "image/png"},
522 meta: %{original: %{width: 200, height: 100, aspect: 2}},
523 blurhash: "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn"
526 api_spec = Pleroma.Web.ApiSpec.spec()
528 assert expected == StatusView.render("attachment.json", %{attachment: object})
529 assert_schema(expected, "Attachment", api_spec)
531 # If theres a "id", use that instead of the generated one
532 object = Map.put(object, "id", 2)
533 result = StatusView.render("attachment.json", %{attachment: object})
535 assert %{id: "2"} = result
536 assert_schema(result, "Attachment", api_spec)
539 test "put the url advertised in the Activity in to the url attribute" do
540 id = "https://wedistribute.org/wp-json/pterotype/v1/object/85810"
541 [activity] = Activity.search(nil, id)
543 status = StatusView.render("show.json", %{activity: activity})
545 assert status.uri == id
546 assert status.url == "https://wedistribute.org/2019/07/mastodon-drops-ostatus/"
551 activity = insert(:note_activity)
553 {:ok, reblog} = CommonAPI.repeat(activity.id, user)
555 represented = StatusView.render("show.json", %{for: user, activity: reblog})
557 assert represented[:id] == to_string(reblog.id)
558 assert represented[:reblog][:id] == to_string(activity.id)
559 assert represented[:emojis] == []
560 assert_schema(represented, "Status", Pleroma.Web.ApiSpec.spec())
563 test "a peertube video" do
567 Pleroma.Object.Fetcher.fetch_object_from_id(
568 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
571 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
573 represented = StatusView.render("show.json", %{for: user, activity: activity})
575 assert represented[:id] == to_string(activity.id)
576 assert length(represented[:media_attachments]) == 1
577 assert_schema(represented, "Status", Pleroma.Web.ApiSpec.spec())
580 test "funkwhale audio" do
584 Pleroma.Object.Fetcher.fetch_object_from_id(
585 "https://channels.tests.funkwhale.audio/federation/music/uploads/42342395-0208-4fee-a38d-259a6dae0871"
588 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
590 represented = StatusView.render("show.json", %{for: user, activity: activity})
592 assert represented[:id] == to_string(activity.id)
593 assert length(represented[:media_attachments]) == 1
596 test "a Mobilizon event" do
600 Pleroma.Object.Fetcher.fetch_object_from_id(
601 "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
604 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
606 represented = StatusView.render("show.json", %{for: user, activity: activity})
608 assert represented[:id] == to_string(activity.id)
610 assert represented[:url] ==
611 "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
613 assert represented[:content] ==
614 "<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>"
617 describe "build_tags/1" do
618 test "it returns a a dictionary tags" do
624 "href" => "https://kawen.space/users/lain",
625 "name" => "@lain@kawen.space",
630 assert StatusView.build_tags(object_tags) == [
631 %{name: "fediverse", url: "http://localhost:4001/tag/fediverse"},
632 %{name: "mastodon", url: "http://localhost:4001/tag/mastodon"},
633 %{name: "nextcloud", url: "http://localhost:4001/tag/nextcloud"}
638 describe "rich media cards" do
639 test "a rich media card without a site name renders correctly" do
640 page_url = "http://example.com"
644 image: page_url <> "/example.jpg",
645 title: "Example website"
648 %{provider_name: "example.com"} =
649 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
652 test "a rich media card without a site name or image renders correctly" do
653 page_url = "http://example.com"
657 title: "Example website"
660 %{provider_name: "example.com"} =
661 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
664 test "a rich media card without an image renders correctly" do
665 page_url = "http://example.com"
669 site_name: "Example site name",
670 title: "Example website"
673 %{provider_name: "example.com"} =
674 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
677 test "a rich media card with all relevant data renders correctly" do
678 page_url = "http://example.com"
682 site_name: "Example site name",
683 title: "Example website",
684 image: page_url <> "/example.jpg",
685 description: "Example description"
688 %{provider_name: "example.com"} =
689 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
693 test "does not embed a relationship in the account" do
695 other_user = insert(:user)
698 CommonAPI.post(user, %{
699 status: "drink more water"
702 result = StatusView.render("show.json", %{activity: activity, for: other_user})
704 assert result[:account][:pleroma][:relationship] == %{}
705 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
708 test "does not embed a relationship in the account in reposts" do
710 other_user = insert(:user)
713 CommonAPI.post(user, %{
717 {:ok, activity} = CommonAPI.repeat(activity.id, other_user)
719 result = StatusView.render("show.json", %{activity: activity, for: user})
721 assert result[:account][:pleroma][:relationship] == %{}
722 assert result[:reblog][:account][:pleroma][:relationship] == %{}
723 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
726 test "visibility/list" do
729 {:ok, list} = Pleroma.List.create("foo", user)
731 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
733 status = StatusView.render("show.json", activity: activity)
735 assert status.visibility == "list"
738 test "has a field for parent visibility" do
740 poster = insert(:user)
742 {:ok, invisible} = CommonAPI.post(poster, %{status: "hey", visibility: "private"})
745 CommonAPI.post(poster, %{status: "hey", visibility: "private", in_reply_to_id: invisible.id})
747 status = StatusView.render("show.json", activity: visible, for: user)
748 refute status.pleroma.parent_visible
750 status = StatusView.render("show.json", activity: visible, for: poster)
751 assert status.pleroma.parent_visible