Merge remote-tracking branch 'upstream/develop' into attachment-meta
[akkoma] / test / pleroma / web / mastodon_api / views / status_view_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
6 use Pleroma.DataCase
7
8 alias Pleroma.Activity
9 alias Pleroma.Bookmark
10 alias Pleroma.Conversation.Participation
11 alias Pleroma.HTML
12 alias Pleroma.Object
13 alias Pleroma.Repo
14 alias Pleroma.User
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
20
21 import Pleroma.Factory
22 import Tesla.Mock
23 import OpenApiSpex.TestAssertions
24
25 setup do
26 mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
27 :ok
28 end
29
30 test "has an emoji reaction list" do
31 user = insert(:user)
32 other_user = insert(:user)
33 third_user = insert(:user)
34 {:ok, activity} = CommonAPI.post(user, %{status: "dae cofe??"})
35
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)
41
42 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
43
44 assert status[:pleroma][:emoji_reactions] == [
45 %{name: "☕", count: 2, me: false},
46 %{name: "🍵", count: 1, me: false}
47 ]
48
49 status = StatusView.render("show.json", activity: activity, for: user)
50
51 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
52
53 assert status[:pleroma][:emoji_reactions] == [
54 %{name: "☕", count: 2, me: true},
55 %{name: "🍵", count: 1, me: false}
56 ]
57 end
58
59 test "works correctly with badly formatted emojis" do
60 user = insert(:user)
61 {:ok, activity} = CommonAPI.post(user, %{status: "yo"})
62
63 activity
64 |> Object.normalize(fetch: false)
65 |> Object.update_data(%{"reactions" => %{"☕" => [user.ap_id], "x" => 1}})
66
67 activity = Activity.get_by_id(activity.id)
68
69 status = StatusView.render("show.json", activity: activity, for: user)
70
71 assert status[:pleroma][:emoji_reactions] == [
72 %{name: "☕", count: 1, me: true}
73 ]
74 end
75
76 test "doesn't show reactions from muted and blocked users" do
77 user = insert(:user)
78 other_user = insert(:user)
79 third_user = insert(:user)
80
81 {:ok, activity} = CommonAPI.post(user, %{status: "dae cofe??"})
82
83 {:ok, _} = User.mute(user, other_user)
84 {:ok, _} = User.block(other_user, third_user)
85
86 {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕")
87
88 activity = Repo.get(Activity, activity.id)
89 status = StatusView.render("show.json", activity: activity)
90
91 assert status[:pleroma][:emoji_reactions] == [
92 %{name: "☕", count: 1, me: false}
93 ]
94
95 status = StatusView.render("show.json", activity: activity, for: user)
96
97 assert status[:pleroma][:emoji_reactions] == []
98
99 {:ok, _} = CommonAPI.react_with_emoji(activity.id, third_user, "☕")
100
101 status = StatusView.render("show.json", activity: activity)
102
103 assert status[:pleroma][:emoji_reactions] == [
104 %{name: "☕", count: 2, me: false}
105 ]
106
107 status = StatusView.render("show.json", activity: activity, for: user)
108
109 assert status[:pleroma][:emoji_reactions] == [
110 %{name: "☕", count: 1, me: false}
111 ]
112
113 status = StatusView.render("show.json", activity: activity, for: other_user)
114
115 assert status[:pleroma][:emoji_reactions] == [
116 %{name: "☕", count: 1, me: true}
117 ]
118 end
119
120 test "loads and returns the direct conversation id when given the `with_direct_conversation_id` option" do
121 user = insert(:user)
122
123 {:ok, activity} = CommonAPI.post(user, %{status: "Hey @shp!", visibility: "direct"})
124 [participation] = Participation.for_user(user)
125
126 status =
127 StatusView.render("show.json",
128 activity: activity,
129 with_direct_conversation_id: true,
130 for: user
131 )
132
133 assert status[:pleroma][:direct_conversation_id] == participation.id
134
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())
138 end
139
140 test "returns the direct conversation id when given the `direct_conversation_id` option" do
141 user = insert(:user)
142
143 {:ok, activity} = CommonAPI.post(user, %{status: "Hey @shp!", visibility: "direct"})
144 [participation] = Participation.for_user(user)
145
146 status =
147 StatusView.render("show.json",
148 activity: activity,
149 direct_conversation_id: participation.id,
150 for: user
151 )
152
153 assert status[:pleroma][:direct_conversation_id] == participation.id
154 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
155 end
156
157 test "returns a temporary ap_id based user for activities missing db users" do
158 user = insert(:user)
159
160 {:ok, activity} = CommonAPI.post(user, %{status: "Hey @shp!", visibility: "direct"})
161
162 Repo.delete(user)
163 User.invalidate_cache(user)
164
165 finger_url =
166 "https://localhost/.well-known/webfinger?resource=acct:#{user.nickname}@localhost"
167
168 Tesla.Mock.mock_global(fn
169 %{method: :get, url: "http://localhost/.well-known/host-meta"} ->
170 %Tesla.Env{status: 404, body: ""}
171
172 %{method: :get, url: "https://localhost/.well-known/host-meta"} ->
173 %Tesla.Env{status: 404, body: ""}
174
175 %{
176 method: :get,
177 url: ^finger_url
178 } ->
179 %Tesla.Env{status: 404, body: ""}
180 end)
181
182 %{account: ms_user} = StatusView.render("show.json", activity: activity)
183
184 assert ms_user.acct == "erroruser@example.com"
185 end
186
187 test "tries to get a user by nickname if fetching by ap_id doesn't work" do
188 user = insert(:user)
189
190 {:ok, activity} = CommonAPI.post(user, %{status: "Hey @shp!", visibility: "direct"})
191
192 {:ok, user} =
193 user
194 |> Ecto.Changeset.change(%{ap_id: "#{user.ap_id}/extension/#{user.nickname}"})
195 |> Repo.update()
196
197 User.invalidate_cache(user)
198
199 result = StatusView.render("show.json", activity: activity)
200
201 assert result[:account][:id] == to_string(user.id)
202 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
203 end
204
205 test "a note with null content" do
206 note = insert(:note_activity)
207 note_object = Object.normalize(note, fetch: false)
208
209 data =
210 note_object.data
211 |> Map.put("content", nil)
212
213 Object.change(note_object, %{data: data})
214 |> Object.update_and_set_cache()
215
216 User.get_cached_by_ap_id(note.data["actor"])
217
218 status = StatusView.render("show.json", %{activity: note})
219
220 assert status.content == ""
221 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
222 end
223
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"])
228
229 convo_id = Utils.context_to_conversation_id(object_data["context"])
230
231 status = StatusView.render("show.json", %{activity: note})
232
233 created_at =
234 (object_data["published"] || "")
235 |> String.replace(~r/\.\d+Z/, ".000Z")
236
237 expected = %{
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}),
242 in_reply_to_id: nil,
243 in_reply_to_account_id: nil,
244 card: nil,
245 reblog: nil,
246 content: HTML.filter_tags(object_data["content"]),
247 text: nil,
248 created_at: created_at,
249 reblogs_count: 0,
250 replies_count: 0,
251 favourites_count: 0,
252 reblogged: false,
253 bookmarked: false,
254 favourited: false,
255 muted: false,
256 pinned: false,
257 sensitive: false,
258 poll: nil,
259 spoiler_text: HTML.filter_tags(object_data["summary"]),
260 visibility: "public",
261 media_attachments: [],
262 mentions: [],
263 tags: [
264 %{
265 name: "#{hd(object_data["tag"])}",
266 url: "http://localhost:4001/tag/#{hd(object_data["tag"])}"
267 }
268 ],
269 application: nil,
270 language: nil,
271 emojis: [
272 %{
273 shortcode: "2hu",
274 url: "corndog.png",
275 static_url: "corndog.png",
276 visible_in_picker: false
277 }
278 ],
279 pleroma: %{
280 local: true,
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"])},
285 expires_at: nil,
286 direct_conversation_id: nil,
287 thread_muted: false,
288 emoji_reactions: [],
289 parent_visible: false,
290 pinned_at: nil
291 }
292 }
293
294 assert status == expected
295 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
296 end
297
298 test "tells if the message is muted for some reason" do
299 user = insert(:user)
300 other_user = insert(:user)
301
302 {:ok, _user_relationships} = User.mute(user, other_user)
303
304 {:ok, activity} = CommonAPI.post(other_user, %{status: "test"})
305
306 relationships_opt = UserRelationship.view_relationships_option(user, [other_user])
307
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())
312
313 status = StatusView.render("show.json", Map.put(opts, :relationships, relationships_opt))
314 assert status.muted == false
315
316 for_opts = %{activity: activity, for: user}
317 status = StatusView.render("show.json", for_opts)
318 assert status.muted == true
319
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())
323 end
324
325 test "tells if the message is thread muted" do
326 user = insert(:user)
327 other_user = insert(:user)
328
329 {:ok, _user_relationships} = User.mute(user, other_user)
330
331 {:ok, activity} = CommonAPI.post(other_user, %{status: "test"})
332 status = StatusView.render("show.json", %{activity: activity, for: user})
333
334 assert status.pleroma.thread_muted == false
335
336 {:ok, activity} = CommonAPI.add_mute(user, activity)
337
338 status = StatusView.render("show.json", %{activity: activity, for: user})
339
340 assert status.pleroma.thread_muted == true
341 end
342
343 test "tells if the status is bookmarked" do
344 user = insert(:user)
345
346 {:ok, activity} = CommonAPI.post(user, %{status: "Cute girls doing cute things"})
347 status = StatusView.render("show.json", %{activity: activity})
348
349 assert status.bookmarked == false
350
351 status = StatusView.render("show.json", %{activity: activity, for: user})
352
353 assert status.bookmarked == false
354
355 {:ok, _bookmark} = Bookmark.create(user.id, activity.id)
356
357 activity = Activity.get_by_id_with_object(activity.id)
358
359 status = StatusView.render("show.json", %{activity: activity, for: user})
360
361 assert status.bookmarked == true
362 end
363
364 test "a reply" do
365 note = insert(:note_activity)
366 user = insert(:user)
367
368 {:ok, activity} = CommonAPI.post(user, %{status: "he", in_reply_to_status_id: note.id})
369
370 status = StatusView.render("show.json", %{activity: activity})
371
372 assert status.in_reply_to_id == to_string(note.id)
373
374 [status] = StatusView.render("index.json", %{activities: [activity], as: :activity})
375
376 assert status.in_reply_to_id == to_string(note.id)
377 end
378
379 test "contains mentions" do
380 user = insert(:user)
381 mentioned = insert(:user)
382
383 {:ok, activity} = CommonAPI.post(user, %{status: "hi @#{mentioned.nickname}"})
384
385 status = StatusView.render("show.json", %{activity: activity})
386
387 assert status.mentions ==
388 Enum.map([mentioned], fn u -> AccountView.render("mention.json", %{user: u}) end)
389
390 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
391 end
392
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)
396
397 object =
398 insert(:note, %{
399 data: %{
400 "to" => [recipient_ap_id],
401 "cc" => cc
402 }
403 })
404
405 activity =
406 insert(:note_activity, %{
407 note: object,
408 recipients: [recipient_ap_id | cc]
409 })
410
411 assert length(activity.recipients) == 3
412
413 %{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity})
414
415 assert length(mentions) == 1
416 assert mention.url == recipient_ap_id
417 end
418
419 test "create mentions from the 'tag' field" do
420 recipient = insert(:user)
421 cc = insert_pair(:user) |> Enum.map(& &1.ap_id)
422
423 object =
424 insert(:note, %{
425 data: %{
426 "cc" => cc,
427 "tag" => [
428 %{
429 "href" => recipient.ap_id,
430 "name" => recipient.nickname,
431 "type" => "Mention"
432 },
433 %{
434 "href" => "https://example.com/search?tag=test",
435 "name" => "#test",
436 "type" => "Hashtag"
437 }
438 ]
439 }
440 })
441
442 activity =
443 insert(:note_activity, %{
444 note: object,
445 recipients: [recipient.ap_id | cc]
446 })
447
448 assert length(activity.recipients) == 3
449
450 %{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity})
451
452 assert length(mentions) == 1
453 assert mention.url == recipient.ap_id
454 end
455
456 test "attachments" do
457 object = %{
458 "type" => "Image",
459 "url" => [
460 %{
461 "mediaType" => "image/png",
462 "href" => "someurl",
463 "width" => 200,
464 "height" => 100
465 }
466 ],
467 "blurhash" => "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn",
468 "uuid" => 6
469 }
470
471 expected = %{
472 id: "1638338801",
473 type: "image",
474 url: "someurl",
475 remote_url: "someurl",
476 preview_url: "someurl",
477 text_url: "someurl",
478 description: nil,
479 pleroma: %{mime_type: "image/png"},
480 meta: %{original: %{width: 200, height: 100, aspect: 2}},
481 blurhash: "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn"
482 }
483
484 api_spec = Pleroma.Web.ApiSpec.spec()
485
486 assert expected == StatusView.render("attachment.json", %{attachment: object})
487 assert_schema(expected, "Attachment", api_spec)
488
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})
492
493 assert %{id: "2"} = result
494 assert_schema(result, "Attachment", api_spec)
495 end
496
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)
500
501 status = StatusView.render("show.json", %{activity: activity})
502
503 assert status.uri == id
504 assert status.url == "https://wedistribute.org/2019/07/mastodon-drops-ostatus/"
505 end
506
507 test "a reblog" do
508 user = insert(:user)
509 activity = insert(:note_activity)
510
511 {:ok, reblog} = CommonAPI.repeat(activity.id, user)
512
513 represented = StatusView.render("show.json", %{for: user, activity: reblog})
514
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())
519 end
520
521 test "a peertube video" do
522 user = insert(:user)
523
524 {:ok, object} =
525 Pleroma.Object.Fetcher.fetch_object_from_id(
526 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
527 )
528
529 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
530
531 represented = StatusView.render("show.json", %{for: user, activity: activity})
532
533 assert represented[:id] == to_string(activity.id)
534 assert length(represented[:media_attachments]) == 1
535 assert_schema(represented, "Status", Pleroma.Web.ApiSpec.spec())
536 end
537
538 test "funkwhale audio" do
539 user = insert(:user)
540
541 {:ok, object} =
542 Pleroma.Object.Fetcher.fetch_object_from_id(
543 "https://channels.tests.funkwhale.audio/federation/music/uploads/42342395-0208-4fee-a38d-259a6dae0871"
544 )
545
546 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
547
548 represented = StatusView.render("show.json", %{for: user, activity: activity})
549
550 assert represented[:id] == to_string(activity.id)
551 assert length(represented[:media_attachments]) == 1
552 end
553
554 test "a Mobilizon event" do
555 user = insert(:user)
556
557 {:ok, object} =
558 Pleroma.Object.Fetcher.fetch_object_from_id(
559 "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
560 )
561
562 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
563
564 represented = StatusView.render("show.json", %{for: user, activity: activity})
565
566 assert represented[:id] == to_string(activity.id)
567
568 assert represented[:url] ==
569 "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
570
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&#39;s still <u>a work in progress</u>: if reports made from an instance on events and comments can be federated, you can&#39;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&#39;t advise to do that for now, we have a little documentation but it&#39;s quite the early days and you&#39;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>"
573 end
574
575 describe "build_tags/1" do
576 test "it returns a a dictionary tags" do
577 object_tags = [
578 "fediverse",
579 "mastodon",
580 "nextcloud",
581 %{
582 "href" => "https://kawen.space/users/lain",
583 "name" => "@lain@kawen.space",
584 "type" => "Mention"
585 }
586 ]
587
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"}
592 ]
593 end
594 end
595
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"
599
600 card = %{
601 url: page_url,
602 image: page_url <> "/example.jpg",
603 title: "Example website"
604 }
605
606 %{provider_name: "example.com"} =
607 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
608 end
609
610 test "a rich media card without a site name or image renders correctly" do
611 page_url = "http://example.com"
612
613 card = %{
614 url: page_url,
615 title: "Example website"
616 }
617
618 %{provider_name: "example.com"} =
619 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
620 end
621
622 test "a rich media card without an image renders correctly" do
623 page_url = "http://example.com"
624
625 card = %{
626 url: page_url,
627 site_name: "Example site name",
628 title: "Example website"
629 }
630
631 %{provider_name: "example.com"} =
632 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
633 end
634
635 test "a rich media card with all relevant data renders correctly" do
636 page_url = "http://example.com"
637
638 card = %{
639 url: page_url,
640 site_name: "Example site name",
641 title: "Example website",
642 image: page_url <> "/example.jpg",
643 description: "Example description"
644 }
645
646 %{provider_name: "example.com"} =
647 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
648 end
649 end
650
651 test "does not embed a relationship in the account" do
652 user = insert(:user)
653 other_user = insert(:user)
654
655 {:ok, activity} =
656 CommonAPI.post(user, %{
657 status: "drink more water"
658 })
659
660 result = StatusView.render("show.json", %{activity: activity, for: other_user})
661
662 assert result[:account][:pleroma][:relationship] == %{}
663 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
664 end
665
666 test "does not embed a relationship in the account in reposts" do
667 user = insert(:user)
668 other_user = insert(:user)
669
670 {:ok, activity} =
671 CommonAPI.post(user, %{
672 status: "˙˙ɐʎns"
673 })
674
675 {:ok, activity} = CommonAPI.repeat(activity.id, other_user)
676
677 result = StatusView.render("show.json", %{activity: activity, for: user})
678
679 assert result[:account][:pleroma][:relationship] == %{}
680 assert result[:reblog][:account][:pleroma][:relationship] == %{}
681 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
682 end
683
684 test "visibility/list" do
685 user = insert(:user)
686
687 {:ok, list} = Pleroma.List.create("foo", user)
688
689 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
690
691 status = StatusView.render("show.json", activity: activity)
692
693 assert status.visibility == "list"
694 end
695
696 test "has a field for parent visibility" do
697 user = insert(:user)
698 poster = insert(:user)
699
700 {:ok, invisible} = CommonAPI.post(poster, %{status: "hey", visibility: "private"})
701
702 {:ok, visible} =
703 CommonAPI.post(poster, %{status: "hey", visibility: "private", in_reply_to_id: invisible.id})
704
705 status = StatusView.render("show.json", activity: visible, for: user)
706 refute status.pleroma.parent_visible
707
708 status = StatusView.render("show.json", activity: visible, for: poster)
709 assert status.pleroma.parent_visible
710 end
711 end