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"]) |> Bitwise.band(Bitwise.bnot(0x8000_0000))
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 context: object_data["context"],
296 in_reply_to_account_acct: nil,
297 content: %{"text/plain" => HTML.strip_tags(object_data["content"])},
298 spoiler_text: %{"text/plain" => HTML.strip_tags(object_data["summary"])},
300 direct_conversation_id: nil,
303 parent_visible: false,
307 source: HTML.filter_tags(object_data["content"])
313 assert status == expected
314 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
317 test "tells if the message is muted for some reason" do
319 other_user = insert(:user)
321 {:ok, _user_relationships} = User.mute(user, other_user)
323 {:ok, activity} = CommonAPI.post(other_user, %{status: "test"})
325 relationships_opt = UserRelationship.view_relationships_option(user, [other_user])
327 opts = %{activity: activity}
328 status = StatusView.render("show.json", opts)
329 assert status.muted == false
330 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
332 status = StatusView.render("show.json", Map.put(opts, :relationships, relationships_opt))
333 assert status.muted == false
335 for_opts = %{activity: activity, for: user}
336 status = StatusView.render("show.json", for_opts)
337 assert status.muted == true
339 status = StatusView.render("show.json", Map.put(for_opts, :relationships, relationships_opt))
340 assert status.muted == true
341 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
344 test "tells if the message is thread muted" do
346 other_user = insert(:user)
348 {:ok, _user_relationships} = User.mute(user, other_user)
350 {:ok, activity} = CommonAPI.post(other_user, %{status: "test"})
351 status = StatusView.render("show.json", %{activity: activity, for: user})
353 assert status.pleroma.thread_muted == false
355 {:ok, activity} = CommonAPI.add_mute(user, activity)
357 status = StatusView.render("show.json", %{activity: activity, for: user})
359 assert status.pleroma.thread_muted == true
362 test "tells if the status is bookmarked" do
365 {:ok, activity} = CommonAPI.post(user, %{status: "Cute girls doing cute things"})
366 status = StatusView.render("show.json", %{activity: activity})
368 assert status.bookmarked == false
370 status = StatusView.render("show.json", %{activity: activity, for: user})
372 assert status.bookmarked == false
374 {:ok, _bookmark} = Bookmark.create(user.id, activity.id)
376 activity = Activity.get_by_id_with_object(activity.id)
378 status = StatusView.render("show.json", %{activity: activity, for: user})
380 assert status.bookmarked == true
384 note = insert(:note_activity)
387 {:ok, activity} = CommonAPI.post(user, %{status: "he", in_reply_to_status_id: note.id})
389 status = StatusView.render("show.json", %{activity: activity})
391 assert status.in_reply_to_id == to_string(note.id)
393 [status] = StatusView.render("index.json", %{activities: [activity], as: :activity})
395 assert status.in_reply_to_id == to_string(note.id)
399 note = insert(:note_activity)
402 {:ok, activity} = CommonAPI.post(user, %{status: "hehe", quote_id: note.id})
404 status = StatusView.render("show.json", %{activity: activity})
406 assert status.quote_id == to_string(note.id)
408 [status] = StatusView.render("index.json", %{activities: [activity], as: :activity})
410 assert status.quote_id == to_string(note.id)
413 test "a quote that we can't resolve" do
414 note = insert(:note_activity, quoteUri: "oopsie")
416 status = StatusView.render("show.json", %{activity: note})
418 assert is_nil(status.quote_id)
419 assert is_nil(status.quote)
422 test "contains mentions" do
424 mentioned = insert(:user)
426 {:ok, activity} = CommonAPI.post(user, %{status: "hi @#{mentioned.nickname}"})
428 status = StatusView.render("show.json", %{activity: activity})
430 assert status.mentions ==
431 Enum.map([mentioned], fn u -> AccountView.render("mention.json", %{user: u}) end)
433 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
436 test "create mentions from the 'to' field" do
437 %User{ap_id: recipient_ap_id} = insert(:user)
438 cc = insert_pair(:user) |> Enum.map(& &1.ap_id)
443 "to" => [recipient_ap_id],
449 insert(:note_activity, %{
451 recipients: [recipient_ap_id | cc]
454 assert length(activity.recipients) == 3
456 %{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity})
458 assert length(mentions) == 1
459 assert mention.url == recipient_ap_id
462 test "create mentions from the 'tag' field" do
463 recipient = insert(:user)
464 cc = insert_pair(:user) |> Enum.map(& &1.ap_id)
472 "href" => recipient.ap_id,
473 "name" => recipient.nickname,
477 "href" => "https://example.com/search?tag=test",
486 insert(:note_activity, %{
488 recipients: [recipient.ap_id | cc]
491 assert length(activity.recipients) == 3
493 %{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity})
495 assert length(mentions) == 1
496 assert mention.url == recipient.ap_id
499 test "attachments" do
504 "mediaType" => "image/png",
510 "blurhash" => "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn",
518 remote_url: "someurl",
519 preview_url: "someurl",
522 pleroma: %{mime_type: "image/png"},
523 meta: %{original: %{width: 200, height: 100, aspect: 2}},
524 blurhash: "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn"
527 api_spec = Pleroma.Web.ApiSpec.spec()
529 assert expected == StatusView.render("attachment.json", %{attachment: object})
530 assert_schema(expected, "Attachment", api_spec)
532 # If theres a "id", use that instead of the generated one
533 object = Map.put(object, "id", 2)
534 result = StatusView.render("attachment.json", %{attachment: object})
536 assert %{id: "2"} = result
537 assert_schema(result, "Attachment", api_spec)
540 test "put the url advertised in the Activity in to the url attribute" do
541 id = "https://wedistribute.org/wp-json/pterotype/v1/object/85810"
542 [activity] = Activity.search(nil, id)
544 status = StatusView.render("show.json", %{activity: activity})
546 assert status.uri == id
547 assert status.url == "https://wedistribute.org/2019/07/mastodon-drops-ostatus/"
552 activity = insert(:note_activity)
554 {:ok, reblog} = CommonAPI.repeat(activity.id, user)
556 represented = StatusView.render("show.json", %{for: user, activity: reblog})
558 assert represented[:id] == to_string(reblog.id)
559 assert represented[:reblog][:id] == to_string(activity.id)
560 assert represented[:emojis] == []
561 assert_schema(represented, "Status", Pleroma.Web.ApiSpec.spec())
564 test "a peertube video" do
568 Pleroma.Object.Fetcher.fetch_object_from_id(
569 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
572 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
574 represented = StatusView.render("show.json", %{for: user, activity: activity})
576 assert represented[:id] == to_string(activity.id)
577 assert length(represented[:media_attachments]) == 1
578 assert_schema(represented, "Status", Pleroma.Web.ApiSpec.spec())
581 test "funkwhale audio" do
585 Pleroma.Object.Fetcher.fetch_object_from_id(
586 "https://channels.tests.funkwhale.audio/federation/music/uploads/42342395-0208-4fee-a38d-259a6dae0871"
589 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
591 represented = StatusView.render("show.json", %{for: user, activity: activity})
593 assert represented[:id] == to_string(activity.id)
594 assert length(represented[:media_attachments]) == 1
597 test "a Mobilizon event" do
601 Pleroma.Object.Fetcher.fetch_object_from_id(
602 "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
605 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
607 represented = StatusView.render("show.json", %{for: user, activity: activity})
609 assert represented[:id] == to_string(activity.id)
611 assert represented[:url] ==
612 "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
614 assert represented[:content] ==
615 "<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>"
618 describe "build_tags/1" do
619 test "it returns a a dictionary tags" do
625 "href" => "https://kawen.space/users/lain",
626 "name" => "@lain@kawen.space",
631 assert StatusView.build_tags(object_tags) == [
632 %{name: "fediverse", url: "http://localhost:4001/tag/fediverse"},
633 %{name: "mastodon", url: "http://localhost:4001/tag/mastodon"},
634 %{name: "nextcloud", url: "http://localhost:4001/tag/nextcloud"}
639 describe "rich media cards" do
640 test "a rich media card without a site name renders correctly" do
641 page_url = "http://example.com"
645 image: page_url <> "/example.jpg",
646 title: "Example website"
649 %{provider_name: "example.com"} =
650 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
653 test "a rich media card without a site name or image renders correctly" do
654 page_url = "http://example.com"
658 title: "Example website"
661 %{provider_name: "example.com"} =
662 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
665 test "a rich media card without an image renders correctly" do
666 page_url = "http://example.com"
670 site_name: "Example site name",
671 title: "Example website"
674 %{provider_name: "example.com"} =
675 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
678 test "a rich media card with all relevant data renders correctly" do
679 page_url = "http://example.com"
683 site_name: "Example site name",
684 title: "Example website",
685 image: page_url <> "/example.jpg",
686 description: "Example description"
689 %{provider_name: "example.com"} =
690 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
694 test "does not embed a relationship in the account" do
696 other_user = insert(:user)
699 CommonAPI.post(user, %{
700 status: "drink more water"
703 result = StatusView.render("show.json", %{activity: activity, for: other_user})
705 assert result[:account][:pleroma][:relationship] == %{}
706 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
709 test "does not embed a relationship in the account in reposts" do
711 other_user = insert(:user)
714 CommonAPI.post(user, %{
718 {:ok, activity} = CommonAPI.repeat(activity.id, other_user)
720 result = StatusView.render("show.json", %{activity: activity, for: user})
722 assert result[:account][:pleroma][:relationship] == %{}
723 assert result[:reblog][:account][:pleroma][:relationship] == %{}
724 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
727 test "visibility/list" do
730 {:ok, list} = Pleroma.List.create("foo", user)
732 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
734 status = StatusView.render("show.json", activity: activity)
736 assert status.visibility == "list"
739 test "has a field for parent visibility" do
741 poster = insert(:user)
743 {:ok, invisible} = CommonAPI.post(poster, %{status: "hey", visibility: "private"})
746 CommonAPI.post(poster, %{status: "hey", visibility: "private", in_reply_to_id: invisible.id})
748 status = StatusView.render("show.json", activity: visible, for: user)
749 refute status.pleroma.parent_visible
751 status = StatusView.render("show.json", activity: visible, for: poster)
752 assert status.pleroma.parent_visible