Apply lanodan's suggestion(s) to 1 file(s)
[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: "#{object_data["tag"]}",
266 url: "http://localhost:4001/tag/#{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 }
291 }
292
293 assert status == expected
294 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
295 end
296
297 test "tells if the message is muted for some reason" do
298 user = insert(:user)
299 other_user = insert(:user)
300
301 {:ok, _user_relationships} = User.mute(user, other_user)
302
303 {:ok, activity} = CommonAPI.post(other_user, %{status: "test"})
304
305 relationships_opt = UserRelationship.view_relationships_option(user, [other_user])
306
307 opts = %{activity: activity}
308 status = StatusView.render("show.json", opts)
309 assert status.muted == false
310 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
311
312 status = StatusView.render("show.json", Map.put(opts, :relationships, relationships_opt))
313 assert status.muted == false
314
315 for_opts = %{activity: activity, for: user}
316 status = StatusView.render("show.json", for_opts)
317 assert status.muted == true
318
319 status = StatusView.render("show.json", Map.put(for_opts, :relationships, relationships_opt))
320 assert status.muted == true
321 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
322 end
323
324 test "tells if the message is thread muted" do
325 user = insert(:user)
326 other_user = insert(:user)
327
328 {:ok, _user_relationships} = User.mute(user, other_user)
329
330 {:ok, activity} = CommonAPI.post(other_user, %{status: "test"})
331 status = StatusView.render("show.json", %{activity: activity, for: user})
332
333 assert status.pleroma.thread_muted == false
334
335 {:ok, activity} = CommonAPI.add_mute(user, activity)
336
337 status = StatusView.render("show.json", %{activity: activity, for: user})
338
339 assert status.pleroma.thread_muted == true
340 end
341
342 test "tells if the status is bookmarked" do
343 user = insert(:user)
344
345 {:ok, activity} = CommonAPI.post(user, %{status: "Cute girls doing cute things"})
346 status = StatusView.render("show.json", %{activity: activity})
347
348 assert status.bookmarked == false
349
350 status = StatusView.render("show.json", %{activity: activity, for: user})
351
352 assert status.bookmarked == false
353
354 {:ok, _bookmark} = Bookmark.create(user.id, activity.id)
355
356 activity = Activity.get_by_id_with_object(activity.id)
357
358 status = StatusView.render("show.json", %{activity: activity, for: user})
359
360 assert status.bookmarked == true
361 end
362
363 test "a reply" do
364 note = insert(:note_activity)
365 user = insert(:user)
366
367 {:ok, activity} = CommonAPI.post(user, %{status: "he", in_reply_to_status_id: note.id})
368
369 status = StatusView.render("show.json", %{activity: activity})
370
371 assert status.in_reply_to_id == to_string(note.id)
372
373 [status] = StatusView.render("index.json", %{activities: [activity], as: :activity})
374
375 assert status.in_reply_to_id == to_string(note.id)
376 end
377
378 test "contains mentions" do
379 user = insert(:user)
380 mentioned = insert(:user)
381
382 {:ok, activity} = CommonAPI.post(user, %{status: "hi @#{mentioned.nickname}"})
383
384 status = StatusView.render("show.json", %{activity: activity})
385
386 assert status.mentions ==
387 Enum.map([mentioned], fn u -> AccountView.render("mention.json", %{user: u}) end)
388
389 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
390 end
391
392 test "create mentions from the 'to' field" do
393 %User{ap_id: recipient_ap_id} = insert(:user)
394 cc = insert_pair(:user) |> Enum.map(& &1.ap_id)
395
396 object =
397 insert(:note, %{
398 data: %{
399 "to" => [recipient_ap_id],
400 "cc" => cc
401 }
402 })
403
404 activity =
405 insert(:note_activity, %{
406 note: object,
407 recipients: [recipient_ap_id | cc]
408 })
409
410 assert length(activity.recipients) == 3
411
412 %{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity})
413
414 assert length(mentions) == 1
415 assert mention.url == recipient_ap_id
416 end
417
418 test "create mentions from the 'tag' field" do
419 recipient = insert(:user)
420 cc = insert_pair(:user) |> Enum.map(& &1.ap_id)
421
422 object =
423 insert(:note, %{
424 data: %{
425 "cc" => cc,
426 "tag" => [
427 %{
428 "href" => recipient.ap_id,
429 "name" => recipient.nickname,
430 "type" => "Mention"
431 },
432 %{
433 "href" => "https://example.com/search?tag=test",
434 "name" => "#test",
435 "type" => "Hashtag"
436 }
437 ]
438 }
439 })
440
441 activity =
442 insert(:note_activity, %{
443 note: object,
444 recipients: [recipient.ap_id | cc]
445 })
446
447 assert length(activity.recipients) == 3
448
449 %{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity})
450
451 assert length(mentions) == 1
452 assert mention.url == recipient.ap_id
453 end
454
455 test "attachments" do
456 object = %{
457 "type" => "Image",
458 "url" => [
459 %{
460 "mediaType" => "image/png",
461 "href" => "someurl"
462 }
463 ],
464 "blurhash" => "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn",
465 "uuid" => 6
466 }
467
468 expected = %{
469 id: "1638338801",
470 type: "image",
471 url: "someurl",
472 remote_url: "someurl",
473 preview_url: "someurl",
474 text_url: "someurl",
475 description: nil,
476 pleroma: %{mime_type: "image/png"},
477 blurhash: "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn"
478 }
479
480 api_spec = Pleroma.Web.ApiSpec.spec()
481
482 assert expected == StatusView.render("attachment.json", %{attachment: object})
483 assert_schema(expected, "Attachment", api_spec)
484
485 # If theres a "id", use that instead of the generated one
486 object = Map.put(object, "id", 2)
487 result = StatusView.render("attachment.json", %{attachment: object})
488
489 assert %{id: "2"} = result
490 assert_schema(result, "Attachment", api_spec)
491 end
492
493 test "put the url advertised in the Activity in to the url attribute" do
494 id = "https://wedistribute.org/wp-json/pterotype/v1/object/85810"
495 [activity] = Activity.search(nil, id)
496
497 status = StatusView.render("show.json", %{activity: activity})
498
499 assert status.uri == id
500 assert status.url == "https://wedistribute.org/2019/07/mastodon-drops-ostatus/"
501 end
502
503 test "a reblog" do
504 user = insert(:user)
505 activity = insert(:note_activity)
506
507 {:ok, reblog} = CommonAPI.repeat(activity.id, user)
508
509 represented = StatusView.render("show.json", %{for: user, activity: reblog})
510
511 assert represented[:id] == to_string(reblog.id)
512 assert represented[:reblog][:id] == to_string(activity.id)
513 assert represented[:emojis] == []
514 assert_schema(represented, "Status", Pleroma.Web.ApiSpec.spec())
515 end
516
517 test "a peertube video" do
518 user = insert(:user)
519
520 {:ok, object} =
521 Pleroma.Object.Fetcher.fetch_object_from_id(
522 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
523 )
524
525 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
526
527 represented = StatusView.render("show.json", %{for: user, activity: activity})
528
529 assert represented[:id] == to_string(activity.id)
530 assert length(represented[:media_attachments]) == 1
531 assert_schema(represented, "Status", Pleroma.Web.ApiSpec.spec())
532 end
533
534 test "funkwhale audio" do
535 user = insert(:user)
536
537 {:ok, object} =
538 Pleroma.Object.Fetcher.fetch_object_from_id(
539 "https://channels.tests.funkwhale.audio/federation/music/uploads/42342395-0208-4fee-a38d-259a6dae0871"
540 )
541
542 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
543
544 represented = StatusView.render("show.json", %{for: user, activity: activity})
545
546 assert represented[:id] == to_string(activity.id)
547 assert length(represented[:media_attachments]) == 1
548 end
549
550 test "a Mobilizon event" do
551 user = insert(:user)
552
553 {:ok, object} =
554 Pleroma.Object.Fetcher.fetch_object_from_id(
555 "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
556 )
557
558 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
559
560 represented = StatusView.render("show.json", %{for: user, activity: activity})
561
562 assert represented[:id] == to_string(activity.id)
563
564 assert represented[:url] ==
565 "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
566
567 assert represented[:content] ==
568 "<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>"
569 end
570
571 describe "build_tags/1" do
572 test "it returns a a dictionary tags" do
573 object_tags = [
574 "fediverse",
575 "mastodon",
576 "nextcloud",
577 %{
578 "href" => "https://kawen.space/users/lain",
579 "name" => "@lain@kawen.space",
580 "type" => "Mention"
581 }
582 ]
583
584 assert StatusView.build_tags(object_tags) == [
585 %{name: "fediverse", url: "http://localhost:4001/tag/fediverse"},
586 %{name: "mastodon", url: "http://localhost:4001/tag/mastodon"},
587 %{name: "nextcloud", url: "http://localhost:4001/tag/nextcloud"}
588 ]
589 end
590 end
591
592 describe "rich media cards" do
593 test "a rich media card without a site name renders correctly" do
594 page_url = "http://example.com"
595
596 card = %{
597 url: page_url,
598 image: page_url <> "/example.jpg",
599 title: "Example website"
600 }
601
602 %{provider_name: "example.com"} =
603 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
604 end
605
606 test "a rich media card without a site name or image renders correctly" do
607 page_url = "http://example.com"
608
609 card = %{
610 url: page_url,
611 title: "Example website"
612 }
613
614 %{provider_name: "example.com"} =
615 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
616 end
617
618 test "a rich media card without an image renders correctly" do
619 page_url = "http://example.com"
620
621 card = %{
622 url: page_url,
623 site_name: "Example site name",
624 title: "Example website"
625 }
626
627 %{provider_name: "example.com"} =
628 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
629 end
630
631 test "a rich media card with all relevant data renders correctly" do
632 page_url = "http://example.com"
633
634 card = %{
635 url: page_url,
636 site_name: "Example site name",
637 title: "Example website",
638 image: page_url <> "/example.jpg",
639 description: "Example description"
640 }
641
642 %{provider_name: "example.com"} =
643 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
644 end
645 end
646
647 test "does not embed a relationship in the account" do
648 user = insert(:user)
649 other_user = insert(:user)
650
651 {:ok, activity} =
652 CommonAPI.post(user, %{
653 status: "drink more water"
654 })
655
656 result = StatusView.render("show.json", %{activity: activity, for: other_user})
657
658 assert result[:account][:pleroma][:relationship] == %{}
659 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
660 end
661
662 test "does not embed a relationship in the account in reposts" do
663 user = insert(:user)
664 other_user = insert(:user)
665
666 {:ok, activity} =
667 CommonAPI.post(user, %{
668 status: "˙˙ɐʎns"
669 })
670
671 {:ok, activity} = CommonAPI.repeat(activity.id, other_user)
672
673 result = StatusView.render("show.json", %{activity: activity, for: user})
674
675 assert result[:account][:pleroma][:relationship] == %{}
676 assert result[:reblog][:account][:pleroma][:relationship] == %{}
677 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
678 end
679
680 test "visibility/list" do
681 user = insert(:user)
682
683 {:ok, list} = Pleroma.List.create("foo", user)
684
685 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
686
687 status = StatusView.render("show.json", activity: activity)
688
689 assert status.visibility == "list"
690 end
691
692 test "has a field for parent visibility" do
693 user = insert(:user)
694 poster = insert(:user)
695
696 {:ok, invisible} = CommonAPI.post(poster, %{status: "hey", visibility: "private"})
697
698 {:ok, visible} =
699 CommonAPI.post(poster, %{status: "hey", visibility: "private", in_reply_to_id: invisible.id})
700
701 status = StatusView.render("show.json", activity: visible, for: user)
702 refute status.pleroma.parent_visible
703
704 status = StatusView.render("show.json", activity: visible, for: poster)
705 assert status.pleroma.parent_visible
706 end
707 end