Merge remote-tracking branch 'upstream/develop' into by-approval
[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 end
521
522 describe "build_tags/1" do
523 test "it returns a a dictionary tags" do
524 object_tags = [
525 "fediverse",
526 "mastodon",
527 "nextcloud",
528 %{
529 "href" => "https://kawen.space/users/lain",
530 "name" => "@lain@kawen.space",
531 "type" => "Mention"
532 }
533 ]
534
535 assert StatusView.build_tags(object_tags) == [
536 %{name: "fediverse", url: "/tag/fediverse"},
537 %{name: "mastodon", url: "/tag/mastodon"},
538 %{name: "nextcloud", url: "/tag/nextcloud"}
539 ]
540 end
541 end
542
543 describe "rich media cards" do
544 test "a rich media card without a site name renders correctly" do
545 page_url = "http://example.com"
546
547 card = %{
548 url: page_url,
549 image: page_url <> "/example.jpg",
550 title: "Example website"
551 }
552
553 %{provider_name: "example.com"} =
554 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
555 end
556
557 test "a rich media card without a site name or image renders correctly" do
558 page_url = "http://example.com"
559
560 card = %{
561 url: page_url,
562 title: "Example website"
563 }
564
565 %{provider_name: "example.com"} =
566 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
567 end
568
569 test "a rich media card without an image renders correctly" do
570 page_url = "http://example.com"
571
572 card = %{
573 url: page_url,
574 site_name: "Example site name",
575 title: "Example website"
576 }
577
578 %{provider_name: "example.com"} =
579 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
580 end
581
582 test "a rich media card with all relevant data renders correctly" do
583 page_url = "http://example.com"
584
585 card = %{
586 url: page_url,
587 site_name: "Example site name",
588 title: "Example website",
589 image: page_url <> "/example.jpg",
590 description: "Example description"
591 }
592
593 %{provider_name: "example.com"} =
594 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
595 end
596 end
597
598 test "does not embed a relationship in the account" do
599 user = insert(:user)
600 other_user = insert(:user)
601
602 {:ok, activity} =
603 CommonAPI.post(user, %{
604 status: "drink more water"
605 })
606
607 result = StatusView.render("show.json", %{activity: activity, for: other_user})
608
609 assert result[:account][:pleroma][:relationship] == %{}
610 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
611 end
612
613 test "does not embed a relationship in the account in reposts" do
614 user = insert(:user)
615 other_user = insert(:user)
616
617 {:ok, activity} =
618 CommonAPI.post(user, %{
619 status: "˙˙ɐʎns"
620 })
621
622 {:ok, activity} = CommonAPI.repeat(activity.id, other_user)
623
624 result = StatusView.render("show.json", %{activity: activity, for: user})
625
626 assert result[:account][:pleroma][:relationship] == %{}
627 assert result[:reblog][:account][:pleroma][:relationship] == %{}
628 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
629 end
630
631 test "visibility/list" do
632 user = insert(:user)
633
634 {:ok, list} = Pleroma.List.create("foo", user)
635
636 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
637
638 status = StatusView.render("show.json", activity: activity)
639
640 assert status.visibility == "list"
641 end
642
643 test "has a field for parent visibility" do
644 user = insert(:user)
645 poster = insert(:user)
646
647 {:ok, invisible} = CommonAPI.post(poster, %{status: "hey", visibility: "private"})
648
649 {:ok, visible} =
650 CommonAPI.post(poster, %{status: "hey", visibility: "private", in_reply_to_id: invisible.id})
651
652 status = StatusView.render("show.json", activity: visible, for: user)
653 refute status.pleroma.parent_visible
654
655 status = StatusView.render("show.json", activity: visible, for: poster)
656 assert status.pleroma.parent_visible
657 end
658 end