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