Merge branch 'features/validators-event' into 'develop'
[akkoma] / test / web / mastodon_api / views / status_view_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 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(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 "loads and returns the direct conversation id when given the `with_direct_conversation_id` option" do
77 user = insert(:user)
78
79 {:ok, activity} = CommonAPI.post(user, %{status: "Hey @shp!", visibility: "direct"})
80 [participation] = Participation.for_user(user)
81
82 status =
83 StatusView.render("show.json",
84 activity: activity,
85 with_direct_conversation_id: true,
86 for: user
87 )
88
89 assert status[:pleroma][:direct_conversation_id] == participation.id
90
91 status = StatusView.render("show.json", activity: activity, for: user)
92 assert status[:pleroma][:direct_conversation_id] == nil
93 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
94 end
95
96 test "returns the direct conversation id when given the `direct_conversation_id` option" do
97 user = insert(:user)
98
99 {:ok, activity} = CommonAPI.post(user, %{status: "Hey @shp!", visibility: "direct"})
100 [participation] = Participation.for_user(user)
101
102 status =
103 StatusView.render("show.json",
104 activity: activity,
105 direct_conversation_id: participation.id,
106 for: user
107 )
108
109 assert status[:pleroma][:direct_conversation_id] == participation.id
110 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
111 end
112
113 test "returns a temporary ap_id based user for activities missing db users" do
114 user = insert(:user)
115
116 {:ok, activity} = CommonAPI.post(user, %{status: "Hey @shp!", visibility: "direct"})
117
118 Repo.delete(user)
119 Cachex.clear(:user_cache)
120
121 finger_url =
122 "https://localhost/.well-known/webfinger?resource=acct:#{user.nickname}@localhost"
123
124 Tesla.Mock.mock_global(fn
125 %{method: :get, url: "http://localhost/.well-known/host-meta"} ->
126 %Tesla.Env{status: 404, body: ""}
127
128 %{method: :get, url: "https://localhost/.well-known/host-meta"} ->
129 %Tesla.Env{status: 404, body: ""}
130
131 %{
132 method: :get,
133 url: ^finger_url
134 } ->
135 %Tesla.Env{status: 404, body: ""}
136 end)
137
138 %{account: ms_user} = StatusView.render("show.json", activity: activity)
139
140 assert ms_user.acct == "erroruser@example.com"
141 end
142
143 test "tries to get a user by nickname if fetching by ap_id doesn't work" do
144 user = insert(:user)
145
146 {:ok, activity} = CommonAPI.post(user, %{status: "Hey @shp!", visibility: "direct"})
147
148 {:ok, user} =
149 user
150 |> Ecto.Changeset.change(%{ap_id: "#{user.ap_id}/extension/#{user.nickname}"})
151 |> Repo.update()
152
153 Cachex.clear(:user_cache)
154
155 result = StatusView.render("show.json", activity: activity)
156
157 assert result[:account][:id] == to_string(user.id)
158 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
159 end
160
161 test "a note with null content" do
162 note = insert(:note_activity)
163 note_object = Object.normalize(note)
164
165 data =
166 note_object.data
167 |> Map.put("content", nil)
168
169 Object.change(note_object, %{data: data})
170 |> Object.update_and_set_cache()
171
172 User.get_cached_by_ap_id(note.data["actor"])
173
174 status = StatusView.render("show.json", %{activity: note})
175
176 assert status.content == ""
177 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
178 end
179
180 test "a note activity" do
181 note = insert(:note_activity)
182 object_data = Object.normalize(note).data
183 user = User.get_cached_by_ap_id(note.data["actor"])
184
185 convo_id = Utils.context_to_conversation_id(object_data["context"])
186
187 status = StatusView.render("show.json", %{activity: note})
188
189 created_at =
190 (object_data["published"] || "")
191 |> String.replace(~r/\.\d+Z/, ".000Z")
192
193 expected = %{
194 id: to_string(note.id),
195 uri: object_data["id"],
196 url: Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, note),
197 account: AccountView.render("show.json", %{user: user, skip_visibility_check: true}),
198 in_reply_to_id: nil,
199 in_reply_to_account_id: nil,
200 card: nil,
201 reblog: nil,
202 content: HTML.filter_tags(object_data["content"]),
203 text: nil,
204 created_at: created_at,
205 reblogs_count: 0,
206 replies_count: 0,
207 favourites_count: 0,
208 reblogged: false,
209 bookmarked: false,
210 favourited: false,
211 muted: false,
212 pinned: false,
213 sensitive: false,
214 poll: nil,
215 spoiler_text: HTML.filter_tags(object_data["summary"]),
216 visibility: "public",
217 media_attachments: [],
218 mentions: [],
219 tags: [
220 %{
221 name: "#{object_data["tag"]}",
222 url: "/tag/#{object_data["tag"]}"
223 }
224 ],
225 application: %{
226 name: "Web",
227 website: nil
228 },
229 language: nil,
230 emojis: [
231 %{
232 shortcode: "2hu",
233 url: "corndog.png",
234 static_url: "corndog.png",
235 visible_in_picker: false
236 }
237 ],
238 pleroma: %{
239 local: true,
240 conversation_id: convo_id,
241 in_reply_to_account_acct: nil,
242 content: %{"text/plain" => HTML.strip_tags(object_data["content"])},
243 spoiler_text: %{"text/plain" => HTML.strip_tags(object_data["summary"])},
244 expires_at: nil,
245 direct_conversation_id: nil,
246 thread_muted: false,
247 emoji_reactions: [],
248 parent_visible: false
249 }
250 }
251
252 assert status == expected
253 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
254 end
255
256 test "tells if the message is muted for some reason" do
257 user = insert(:user)
258 other_user = insert(:user)
259
260 {:ok, _user_relationships} = User.mute(user, other_user)
261
262 {:ok, activity} = CommonAPI.post(other_user, %{status: "test"})
263
264 relationships_opt = UserRelationship.view_relationships_option(user, [other_user])
265
266 opts = %{activity: activity}
267 status = StatusView.render("show.json", opts)
268 assert status.muted == false
269 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
270
271 status = StatusView.render("show.json", Map.put(opts, :relationships, relationships_opt))
272 assert status.muted == false
273
274 for_opts = %{activity: activity, for: user}
275 status = StatusView.render("show.json", for_opts)
276 assert status.muted == true
277
278 status = StatusView.render("show.json", Map.put(for_opts, :relationships, relationships_opt))
279 assert status.muted == true
280 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
281 end
282
283 test "tells if the message is thread muted" do
284 user = insert(:user)
285 other_user = insert(:user)
286
287 {:ok, _user_relationships} = User.mute(user, other_user)
288
289 {:ok, activity} = CommonAPI.post(other_user, %{status: "test"})
290 status = StatusView.render("show.json", %{activity: activity, for: user})
291
292 assert status.pleroma.thread_muted == false
293
294 {:ok, activity} = CommonAPI.add_mute(user, activity)
295
296 status = StatusView.render("show.json", %{activity: activity, for: user})
297
298 assert status.pleroma.thread_muted == true
299 end
300
301 test "tells if the status is bookmarked" do
302 user = insert(:user)
303
304 {:ok, activity} = CommonAPI.post(user, %{status: "Cute girls doing cute things"})
305 status = StatusView.render("show.json", %{activity: activity})
306
307 assert status.bookmarked == false
308
309 status = StatusView.render("show.json", %{activity: activity, for: user})
310
311 assert status.bookmarked == false
312
313 {:ok, _bookmark} = Bookmark.create(user.id, activity.id)
314
315 activity = Activity.get_by_id_with_object(activity.id)
316
317 status = StatusView.render("show.json", %{activity: activity, for: user})
318
319 assert status.bookmarked == true
320 end
321
322 test "a reply" do
323 note = insert(:note_activity)
324 user = insert(:user)
325
326 {:ok, activity} = CommonAPI.post(user, %{status: "he", in_reply_to_status_id: note.id})
327
328 status = StatusView.render("show.json", %{activity: activity})
329
330 assert status.in_reply_to_id == to_string(note.id)
331
332 [status] = StatusView.render("index.json", %{activities: [activity], as: :activity})
333
334 assert status.in_reply_to_id == to_string(note.id)
335 end
336
337 test "contains mentions" do
338 user = insert(:user)
339 mentioned = insert(:user)
340
341 {:ok, activity} = CommonAPI.post(user, %{status: "hi @#{mentioned.nickname}"})
342
343 status = StatusView.render("show.json", %{activity: activity})
344
345 assert status.mentions ==
346 Enum.map([mentioned], fn u -> AccountView.render("mention.json", %{user: u}) end)
347
348 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
349 end
350
351 test "create mentions from the 'to' field" do
352 %User{ap_id: recipient_ap_id} = insert(:user)
353 cc = insert_pair(:user) |> Enum.map(& &1.ap_id)
354
355 object =
356 insert(:note, %{
357 data: %{
358 "to" => [recipient_ap_id],
359 "cc" => cc
360 }
361 })
362
363 activity =
364 insert(:note_activity, %{
365 note: object,
366 recipients: [recipient_ap_id | cc]
367 })
368
369 assert length(activity.recipients) == 3
370
371 %{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity})
372
373 assert length(mentions) == 1
374 assert mention.url == recipient_ap_id
375 end
376
377 test "create mentions from the 'tag' field" do
378 recipient = insert(:user)
379 cc = insert_pair(:user) |> Enum.map(& &1.ap_id)
380
381 object =
382 insert(:note, %{
383 data: %{
384 "cc" => cc,
385 "tag" => [
386 %{
387 "href" => recipient.ap_id,
388 "name" => recipient.nickname,
389 "type" => "Mention"
390 },
391 %{
392 "href" => "https://example.com/search?tag=test",
393 "name" => "#test",
394 "type" => "Hashtag"
395 }
396 ]
397 }
398 })
399
400 activity =
401 insert(:note_activity, %{
402 note: object,
403 recipients: [recipient.ap_id | cc]
404 })
405
406 assert length(activity.recipients) == 3
407
408 %{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity})
409
410 assert length(mentions) == 1
411 assert mention.url == recipient.ap_id
412 end
413
414 test "attachments" do
415 object = %{
416 "type" => "Image",
417 "url" => [
418 %{
419 "mediaType" => "image/png",
420 "href" => "someurl"
421 }
422 ],
423 "uuid" => 6
424 }
425
426 expected = %{
427 id: "1638338801",
428 type: "image",
429 url: "someurl",
430 remote_url: "someurl",
431 preview_url: "someurl",
432 text_url: "someurl",
433 description: nil,
434 pleroma: %{mime_type: "image/png"}
435 }
436
437 api_spec = Pleroma.Web.ApiSpec.spec()
438
439 assert expected == StatusView.render("attachment.json", %{attachment: object})
440 assert_schema(expected, "Attachment", api_spec)
441
442 # If theres a "id", use that instead of the generated one
443 object = Map.put(object, "id", 2)
444 result = StatusView.render("attachment.json", %{attachment: object})
445
446 assert %{id: "2"} = result
447 assert_schema(result, "Attachment", api_spec)
448 end
449
450 test "put the url advertised in the Activity in to the url attribute" do
451 id = "https://wedistribute.org/wp-json/pterotype/v1/object/85810"
452 [activity] = Activity.search(nil, id)
453
454 status = StatusView.render("show.json", %{activity: activity})
455
456 assert status.uri == id
457 assert status.url == "https://wedistribute.org/2019/07/mastodon-drops-ostatus/"
458 end
459
460 test "a reblog" do
461 user = insert(:user)
462 activity = insert(:note_activity)
463
464 {:ok, reblog} = CommonAPI.repeat(activity.id, user)
465
466 represented = StatusView.render("show.json", %{for: user, activity: reblog})
467
468 assert represented[:id] == to_string(reblog.id)
469 assert represented[:reblog][:id] == to_string(activity.id)
470 assert represented[:emojis] == []
471 assert_schema(represented, "Status", Pleroma.Web.ApiSpec.spec())
472 end
473
474 test "a peertube video" do
475 user = insert(:user)
476
477 {:ok, object} =
478 Pleroma.Object.Fetcher.fetch_object_from_id(
479 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
480 )
481
482 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
483
484 represented = StatusView.render("show.json", %{for: user, activity: activity})
485
486 assert represented[:id] == to_string(activity.id)
487 assert length(represented[:media_attachments]) == 1
488 assert_schema(represented, "Status", Pleroma.Web.ApiSpec.spec())
489 end
490
491 test "funkwhale audio" do
492 user = insert(:user)
493
494 {:ok, object} =
495 Pleroma.Object.Fetcher.fetch_object_from_id(
496 "https://channels.tests.funkwhale.audio/federation/music/uploads/42342395-0208-4fee-a38d-259a6dae0871"
497 )
498
499 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
500
501 represented = StatusView.render("show.json", %{for: user, activity: activity})
502
503 assert represented[:id] == to_string(activity.id)
504 assert length(represented[:media_attachments]) == 1
505 end
506
507 test "a Mobilizon event" do
508 user = insert(:user)
509
510 {:ok, object} =
511 Pleroma.Object.Fetcher.fetch_object_from_id(
512 "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
513 )
514
515 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
516
517 represented = StatusView.render("show.json", %{for: user, activity: activity})
518
519 assert represented[:id] == to_string(activity.id)
520
521 assert represented[:url] ==
522 "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
523
524 assert represented[:content] ==
525 "<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>"
526 end
527
528 describe "build_tags/1" do
529 test "it returns a a dictionary tags" do
530 object_tags = [
531 "fediverse",
532 "mastodon",
533 "nextcloud",
534 %{
535 "href" => "https://kawen.space/users/lain",
536 "name" => "@lain@kawen.space",
537 "type" => "Mention"
538 }
539 ]
540
541 assert StatusView.build_tags(object_tags) == [
542 %{name: "fediverse", url: "/tag/fediverse"},
543 %{name: "mastodon", url: "/tag/mastodon"},
544 %{name: "nextcloud", url: "/tag/nextcloud"}
545 ]
546 end
547 end
548
549 describe "rich media cards" do
550 test "a rich media card without a site name renders correctly" do
551 page_url = "http://example.com"
552
553 card = %{
554 url: page_url,
555 image: page_url <> "/example.jpg",
556 title: "Example website"
557 }
558
559 %{provider_name: "example.com"} =
560 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
561 end
562
563 test "a rich media card without a site name or image renders correctly" do
564 page_url = "http://example.com"
565
566 card = %{
567 url: page_url,
568 title: "Example website"
569 }
570
571 %{provider_name: "example.com"} =
572 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
573 end
574
575 test "a rich media card without an image renders correctly" do
576 page_url = "http://example.com"
577
578 card = %{
579 url: page_url,
580 site_name: "Example site name",
581 title: "Example website"
582 }
583
584 %{provider_name: "example.com"} =
585 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
586 end
587
588 test "a rich media card with all relevant data renders correctly" do
589 page_url = "http://example.com"
590
591 card = %{
592 url: page_url,
593 site_name: "Example site name",
594 title: "Example website",
595 image: page_url <> "/example.jpg",
596 description: "Example description"
597 }
598
599 %{provider_name: "example.com"} =
600 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
601 end
602 end
603
604 test "does not embed a relationship in the account" do
605 user = insert(:user)
606 other_user = insert(:user)
607
608 {:ok, activity} =
609 CommonAPI.post(user, %{
610 status: "drink more water"
611 })
612
613 result = StatusView.render("show.json", %{activity: activity, for: other_user})
614
615 assert result[:account][:pleroma][:relationship] == %{}
616 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
617 end
618
619 test "does not embed a relationship in the account in reposts" do
620 user = insert(:user)
621 other_user = insert(:user)
622
623 {:ok, activity} =
624 CommonAPI.post(user, %{
625 status: "˙˙ɐʎns"
626 })
627
628 {:ok, activity} = CommonAPI.repeat(activity.id, other_user)
629
630 result = StatusView.render("show.json", %{activity: activity, for: user})
631
632 assert result[:account][:pleroma][:relationship] == %{}
633 assert result[:reblog][:account][:pleroma][:relationship] == %{}
634 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
635 end
636
637 test "visibility/list" do
638 user = insert(:user)
639
640 {:ok, list} = Pleroma.List.create("foo", user)
641
642 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
643
644 status = StatusView.render("show.json", activity: activity)
645
646 assert status.visibility == "list"
647 end
648
649 test "has a field for parent visibility" do
650 user = insert(:user)
651 poster = insert(:user)
652
653 {:ok, invisible} = CommonAPI.post(poster, %{status: "hey", visibility: "private"})
654
655 {:ok, visible} =
656 CommonAPI.post(poster, %{status: "hey", visibility: "private", in_reply_to_id: invisible.id})
657
658 status = StatusView.render("show.json", activity: visible, for: user)
659 refute status.pleroma.parent_visible
660
661 status = StatusView.render("show.json", activity: visible, for: poster)
662 assert status.pleroma.parent_visible
663 end
664 end