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, account_ids: [other_user.id, user.id]},
52 url: "http://localhost:4001/emoji/dino walking.gif",
53 account_ids: [other_user.id, user.id]
55 %{name: "🍵", count: 1, me: false, url: nil, account_ids: [third_user.id]}
58 status = StatusView.render("show.json", activity: activity, for: user)
60 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
62 assert status[:pleroma][:emoji_reactions] == [
63 %{name: "☕", count: 2, me: true, url: nil, account_ids: [other_user.id, user.id]},
68 url: "http://localhost:4001/emoji/dino walking.gif",
69 account_ids: [other_user.id, user.id]
71 %{name: "🍵", count: 1, me: false, url: nil, account_ids: [third_user.id]}
75 test "works correctly with badly formatted emojis" do
77 {:ok, activity} = CommonAPI.post(user, %{status: "yo"})
80 |> Object.normalize(fetch: false)
81 |> Object.update_data(%{"reactions" => %{"☕" => [user.ap_id], "x" => 1}})
83 activity = Activity.get_by_id(activity.id)
84 status = StatusView.render("show.json", activity: activity, for: user)
86 assert status[:pleroma][:emoji_reactions] == [
87 %{name: "☕", count: 1, me: true, url: nil, account_ids: [user.id]}
91 test "doesn't show reactions from muted and blocked users" do
93 other_user = insert(:user)
94 third_user = insert(:user)
96 {:ok, activity} = CommonAPI.post(user, %{status: "dae cofe??"})
98 {:ok, _} = User.mute(user, other_user)
99 {:ok, _} = User.block(other_user, third_user)
101 {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕")
103 activity = Repo.get(Activity, activity.id)
104 status = StatusView.render("show.json", activity: activity)
106 assert status[:pleroma][:emoji_reactions] == [
107 %{name: "☕", count: 1, me: false, url: nil, account_ids: [other_user.id]}
110 status = StatusView.render("show.json", activity: activity, for: user)
112 assert status[:pleroma][:emoji_reactions] == []
114 {:ok, _} = CommonAPI.react_with_emoji(activity.id, third_user, "☕")
116 status = StatusView.render("show.json", activity: activity)
118 assert status[:pleroma][:emoji_reactions] == [
124 account_ids: [third_user.id, other_user.id]
128 status = StatusView.render("show.json", activity: activity, for: user)
130 assert status[:pleroma][:emoji_reactions] == [
131 %{name: "☕", count: 1, me: false, url: nil, account_ids: [third_user.id]}
134 status = StatusView.render("show.json", activity: activity, for: other_user)
136 assert status[:pleroma][:emoji_reactions] == [
137 %{name: "☕", count: 1, me: true, url: nil, account_ids: [other_user.id]}
141 test "loads and returns the direct conversation id when given the `with_direct_conversation_id` option" do
144 {:ok, activity} = CommonAPI.post(user, %{status: "Hey @shp!", visibility: "direct"})
145 [participation] = Participation.for_user(user)
148 StatusView.render("show.json",
150 with_direct_conversation_id: true,
154 assert status[:pleroma][:direct_conversation_id] == participation.id
156 status = StatusView.render("show.json", activity: activity, for: user)
157 assert status[:pleroma][:direct_conversation_id] == nil
158 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
161 test "returns the direct conversation id when given the `direct_conversation_id` option" do
164 {:ok, activity} = CommonAPI.post(user, %{status: "Hey @shp!", visibility: "direct"})
165 [participation] = Participation.for_user(user)
168 StatusView.render("show.json",
170 direct_conversation_id: participation.id,
174 assert status[:pleroma][:direct_conversation_id] == participation.id
175 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
178 test "returns a temporary ap_id based user for activities missing db users" do
181 {:ok, activity} = CommonAPI.post(user, %{status: "Hey @shp!", visibility: "direct"})
184 User.invalidate_cache(user)
187 "https://localhost/.well-known/webfinger?resource=acct:#{user.nickname}@localhost"
189 Tesla.Mock.mock_global(fn
190 %{method: :get, url: "http://localhost/.well-known/host-meta"} ->
191 %Tesla.Env{status: 404, body: ""}
193 %{method: :get, url: "https://localhost/.well-known/host-meta"} ->
194 %Tesla.Env{status: 404, body: ""}
200 %Tesla.Env{status: 404, body: ""}
203 %{account: ms_user} = StatusView.render("show.json", activity: activity)
205 assert ms_user.acct == "erroruser@example.com"
208 test "tries to get a user by nickname if fetching by ap_id doesn't work" do
211 {:ok, activity} = CommonAPI.post(user, %{status: "Hey @shp!", visibility: "direct"})
215 |> Ecto.Changeset.change(%{ap_id: "#{user.ap_id}/extension/#{user.nickname}"})
218 User.invalidate_cache(user)
220 result = StatusView.render("show.json", activity: activity)
222 assert result[:account][:id] == to_string(user.id)
223 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
226 test "a note with null content" do
227 note = insert(:note_activity)
228 note_object = Object.normalize(note, fetch: false)
232 |> Map.put("content", nil)
234 Object.change(note_object, %{data: data})
235 |> Object.update_and_set_cache()
237 User.get_cached_by_ap_id(note.data["actor"])
239 status = StatusView.render("show.json", %{activity: note})
241 assert status.content == ""
242 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
245 test "a note activity" do
246 note = insert(:note_activity)
247 object_data = Object.normalize(note, fetch: false).data
248 user = User.get_cached_by_ap_id(note.data["actor"])
250 convo_id = :erlang.crc32(object_data["context"]) |> Bitwise.band(Bitwise.bnot(0x8000_0000))
252 status = StatusView.render("show.json", %{activity: note})
255 (object_data["published"] || "")
256 |> String.replace(~r/\.\d+Z/, ".000Z")
259 id: to_string(note.id),
260 uri: object_data["id"],
261 url: Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, note),
262 account: AccountView.render("show.json", %{user: user, skip_visibility_check: true}),
264 in_reply_to_account_id: nil,
267 content: HTML.filter_tags(object_data["content"]),
269 created_at: created_at,
280 spoiler_text: HTML.filter_tags(object_data["summary"]),
281 visibility: "public",
282 media_attachments: [],
287 name: "#{hd(object_data["tag"])}",
288 url: "http://localhost:4001/tag/#{hd(object_data["tag"])}"
297 static_url: "corndog.png",
298 visible_in_picker: false
303 conversation_id: convo_id,
304 context: object_data["context"],
305 in_reply_to_account_acct: nil,
306 content: %{"text/plain" => HTML.strip_tags(object_data["content"])},
307 spoiler_text: %{"text/plain" => HTML.strip_tags(object_data["summary"])},
309 direct_conversation_id: nil,
312 parent_visible: false,
316 source: HTML.filter_tags(object_data["content"])
322 assert status == expected
323 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
326 test "tells if the message is muted for some reason" do
328 other_user = insert(:user)
330 {:ok, _user_relationships} = User.mute(user, other_user)
332 {:ok, activity} = CommonAPI.post(other_user, %{status: "test"})
334 relationships_opt = UserRelationship.view_relationships_option(user, [other_user])
336 opts = %{activity: activity}
337 status = StatusView.render("show.json", opts)
338 assert status.muted == false
339 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
341 status = StatusView.render("show.json", Map.put(opts, :relationships, relationships_opt))
342 assert status.muted == false
344 for_opts = %{activity: activity, for: user}
345 status = StatusView.render("show.json", for_opts)
346 assert status.muted == true
348 status = StatusView.render("show.json", Map.put(for_opts, :relationships, relationships_opt))
349 assert status.muted == true
350 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
353 test "tells if the message is thread muted" do
355 other_user = insert(:user)
357 {:ok, _user_relationships} = User.mute(user, other_user)
359 {:ok, activity} = CommonAPI.post(other_user, %{status: "test"})
360 status = StatusView.render("show.json", %{activity: activity, for: user})
362 assert status.pleroma.thread_muted == false
364 {:ok, activity} = CommonAPI.add_mute(user, activity)
366 status = StatusView.render("show.json", %{activity: activity, for: user})
368 assert status.pleroma.thread_muted == true
371 test "tells if the status is bookmarked" do
374 {:ok, activity} = CommonAPI.post(user, %{status: "Cute girls doing cute things"})
375 status = StatusView.render("show.json", %{activity: activity})
377 assert status.bookmarked == false
379 status = StatusView.render("show.json", %{activity: activity, for: user})
381 assert status.bookmarked == false
383 {:ok, _bookmark} = Bookmark.create(user.id, activity.id)
385 activity = Activity.get_by_id_with_object(activity.id)
387 status = StatusView.render("show.json", %{activity: activity, for: user})
389 assert status.bookmarked == true
393 note = insert(:note_activity)
396 {:ok, activity} = CommonAPI.post(user, %{status: "he", in_reply_to_status_id: note.id})
398 status = StatusView.render("show.json", %{activity: activity})
400 assert status.in_reply_to_id == to_string(note.id)
402 [status] = StatusView.render("index.json", %{activities: [activity], as: :activity})
404 assert status.in_reply_to_id == to_string(note.id)
408 note = insert(:note_activity)
411 {:ok, activity} = CommonAPI.post(user, %{status: "hehe", quote_id: note.id})
413 status = StatusView.render("show.json", %{activity: activity})
415 assert status.quote_id == to_string(note.id)
417 [status] = StatusView.render("index.json", %{activities: [activity], as: :activity})
419 assert status.quote_id == to_string(note.id)
422 test "a quote that we can't resolve" do
423 note = insert(:note_activity, quoteUri: "oopsie")
425 status = StatusView.render("show.json", %{activity: note})
427 assert is_nil(status.quote_id)
428 assert is_nil(status.quote)
431 test "contains mentions" do
433 mentioned = insert(:user)
435 {:ok, activity} = CommonAPI.post(user, %{status: "hi @#{mentioned.nickname}"})
437 status = StatusView.render("show.json", %{activity: activity})
439 assert status.mentions ==
440 Enum.map([mentioned], fn u -> AccountView.render("mention.json", %{user: u}) end)
442 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
445 test "create mentions from the 'to' field" do
446 %User{ap_id: recipient_ap_id} = insert(:user)
447 cc = insert_pair(:user) |> Enum.map(& &1.ap_id)
452 "to" => [recipient_ap_id],
458 insert(:note_activity, %{
460 recipients: [recipient_ap_id | cc]
463 assert length(activity.recipients) == 3
465 %{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity})
467 assert length(mentions) == 1
468 assert mention.url == recipient_ap_id
471 test "create mentions from the 'tag' field" do
472 recipient = insert(:user)
473 cc = insert_pair(:user) |> Enum.map(& &1.ap_id)
481 "href" => recipient.ap_id,
482 "name" => recipient.nickname,
486 "href" => "https://example.com/search?tag=test",
495 insert(:note_activity, %{
497 recipients: [recipient.ap_id | cc]
500 assert length(activity.recipients) == 3
502 %{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity})
504 assert length(mentions) == 1
505 assert mention.url == recipient.ap_id
508 test "attachments" do
513 "mediaType" => "image/png",
519 "blurhash" => "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn",
527 remote_url: "someurl",
528 preview_url: "someurl",
531 pleroma: %{mime_type: "image/png"},
532 meta: %{original: %{width: 200, height: 100, aspect: 2}},
533 blurhash: "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn"
536 api_spec = Pleroma.Web.ApiSpec.spec()
538 assert expected == StatusView.render("attachment.json", %{attachment: object})
539 assert_schema(expected, "Attachment", api_spec)
541 # If theres a "id", use that instead of the generated one
542 object = Map.put(object, "id", 2)
543 result = StatusView.render("attachment.json", %{attachment: object})
545 assert %{id: "2"} = result
546 assert_schema(result, "Attachment", api_spec)
549 test "put the url advertised in the Activity in to the url attribute" do
550 id = "https://wedistribute.org/wp-json/pterotype/v1/object/85810"
551 [activity] = Activity.search(nil, id)
553 status = StatusView.render("show.json", %{activity: activity})
555 assert status.uri == id
556 assert status.url == "https://wedistribute.org/2019/07/mastodon-drops-ostatus/"
561 activity = insert(:note_activity)
563 {:ok, reblog} = CommonAPI.repeat(activity.id, user)
565 represented = StatusView.render("show.json", %{for: user, activity: reblog})
567 assert represented[:id] == to_string(reblog.id)
568 assert represented[:reblog][:id] == to_string(activity.id)
569 assert represented[:emojis] == []
570 assert_schema(represented, "Status", Pleroma.Web.ApiSpec.spec())
573 test "a peertube video" do
577 Pleroma.Object.Fetcher.fetch_object_from_id(
578 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
581 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
583 represented = StatusView.render("show.json", %{for: user, activity: activity})
585 assert represented[:id] == to_string(activity.id)
586 assert length(represented[:media_attachments]) == 1
587 assert_schema(represented, "Status", Pleroma.Web.ApiSpec.spec())
590 test "funkwhale audio" do
594 Pleroma.Object.Fetcher.fetch_object_from_id(
595 "https://channels.tests.funkwhale.audio/federation/music/uploads/42342395-0208-4fee-a38d-259a6dae0871"
598 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
600 represented = StatusView.render("show.json", %{for: user, activity: activity})
602 assert represented[:id] == to_string(activity.id)
603 assert length(represented[:media_attachments]) == 1
606 test "a Mobilizon event" do
610 Pleroma.Object.Fetcher.fetch_object_from_id(
611 "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
614 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
616 represented = StatusView.render("show.json", %{for: user, activity: activity})
618 assert represented[:id] == to_string(activity.id)
620 assert represented[:url] ==
621 "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
623 assert represented[:content] ==
624 "<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>"
627 describe "build_tags/1" do
628 test "it returns a a dictionary tags" do
634 "href" => "https://kawen.space/users/lain",
635 "name" => "@lain@kawen.space",
640 assert StatusView.build_tags(object_tags) == [
641 %{name: "fediverse", url: "http://localhost:4001/tag/fediverse"},
642 %{name: "mastodon", url: "http://localhost:4001/tag/mastodon"},
643 %{name: "nextcloud", url: "http://localhost:4001/tag/nextcloud"}
648 describe "rich media cards" do
649 test "a rich media card without a site name renders correctly" do
650 page_url = "http://example.com"
654 image: page_url <> "/example.jpg",
655 title: "Example website"
658 %{provider_name: "example.com"} =
659 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
662 test "a rich media card without a site name or image renders correctly" do
663 page_url = "http://example.com"
667 title: "Example website"
670 %{provider_name: "example.com"} =
671 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
674 test "a rich media card without an image renders correctly" do
675 page_url = "http://example.com"
679 site_name: "Example site name",
680 title: "Example website"
683 %{provider_name: "example.com"} =
684 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
687 test "a rich media card with all relevant data renders correctly" do
688 page_url = "http://example.com"
692 site_name: "Example site name",
693 title: "Example website",
694 image: page_url <> "/example.jpg",
695 description: "Example description"
698 %{provider_name: "example.com"} =
699 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
703 test "does not embed a relationship in the account" do
705 other_user = insert(:user)
708 CommonAPI.post(user, %{
709 status: "drink more water"
712 result = StatusView.render("show.json", %{activity: activity, for: other_user})
714 assert result[:account][:pleroma][:relationship] == %{}
715 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
718 test "does not embed a relationship in the account in reposts" do
720 other_user = insert(:user)
723 CommonAPI.post(user, %{
727 {:ok, activity} = CommonAPI.repeat(activity.id, other_user)
729 result = StatusView.render("show.json", %{activity: activity, for: user})
731 assert result[:account][:pleroma][:relationship] == %{}
732 assert result[:reblog][:account][:pleroma][:relationship] == %{}
733 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
736 test "visibility/list" do
739 {:ok, list} = Pleroma.List.create("foo", user)
741 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
743 status = StatusView.render("show.json", activity: activity)
745 assert status.visibility == "list"
748 test "has a field for parent visibility" do
750 poster = insert(:user)
752 {:ok, invisible} = CommonAPI.post(poster, %{status: "hey", visibility: "private"})
755 CommonAPI.post(poster, %{status: "hey", visibility: "private", in_reply_to_id: invisible.id})
757 status = StatusView.render("show.json", activity: visible, for: user)
758 refute status.pleroma.parent_visible
760 status = StatusView.render("show.json", activity: visible, for: poster)
761 assert status.pleroma.parent_visible