Add SetMeta filter to store uploaded image sizes
[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 "width" => 200,
463 "height" => 100
464 }
465 ],
466 "blurhash" => "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn",
467 "uuid" => 6
468 }
469
470 expected = %{
471 id: "1638338801",
472 type: "image",
473 url: "someurl",
474 remote_url: "someurl",
475 preview_url: "someurl",
476 text_url: "someurl",
477 description: nil,
478 pleroma: %{mime_type: "image/png"},
479 meta: %{original: %{width: 200, height: 100, aspect: 2}},
480 blurhash: "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn"
481 }
482
483 api_spec = Pleroma.Web.ApiSpec.spec()
484
485 assert expected == StatusView.render("attachment.json", %{attachment: object})
486 assert_schema(expected, "Attachment", api_spec)
487
488 # If theres a "id", use that instead of the generated one
489 object = Map.put(object, "id", 2)
490 result = StatusView.render("attachment.json", %{attachment: object})
491
492 assert %{id: "2"} = result
493 assert_schema(result, "Attachment", api_spec)
494 end
495
496 test "put the url advertised in the Activity in to the url attribute" do
497 id = "https://wedistribute.org/wp-json/pterotype/v1/object/85810"
498 [activity] = Activity.search(nil, id)
499
500 status = StatusView.render("show.json", %{activity: activity})
501
502 assert status.uri == id
503 assert status.url == "https://wedistribute.org/2019/07/mastodon-drops-ostatus/"
504 end
505
506 test "a reblog" do
507 user = insert(:user)
508 activity = insert(:note_activity)
509
510 {:ok, reblog} = CommonAPI.repeat(activity.id, user)
511
512 represented = StatusView.render("show.json", %{for: user, activity: reblog})
513
514 assert represented[:id] == to_string(reblog.id)
515 assert represented[:reblog][:id] == to_string(activity.id)
516 assert represented[:emojis] == []
517 assert_schema(represented, "Status", Pleroma.Web.ApiSpec.spec())
518 end
519
520 test "a peertube video" do
521 user = insert(:user)
522
523 {:ok, object} =
524 Pleroma.Object.Fetcher.fetch_object_from_id(
525 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
526 )
527
528 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
529
530 represented = StatusView.render("show.json", %{for: user, activity: activity})
531
532 assert represented[:id] == to_string(activity.id)
533 assert length(represented[:media_attachments]) == 1
534 assert_schema(represented, "Status", Pleroma.Web.ApiSpec.spec())
535 end
536
537 test "funkwhale audio" do
538 user = insert(:user)
539
540 {:ok, object} =
541 Pleroma.Object.Fetcher.fetch_object_from_id(
542 "https://channels.tests.funkwhale.audio/federation/music/uploads/42342395-0208-4fee-a38d-259a6dae0871"
543 )
544
545 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
546
547 represented = StatusView.render("show.json", %{for: user, activity: activity})
548
549 assert represented[:id] == to_string(activity.id)
550 assert length(represented[:media_attachments]) == 1
551 end
552
553 test "a Mobilizon event" do
554 user = insert(:user)
555
556 {:ok, object} =
557 Pleroma.Object.Fetcher.fetch_object_from_id(
558 "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
559 )
560
561 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
562
563 represented = StatusView.render("show.json", %{for: user, activity: activity})
564
565 assert represented[:id] == to_string(activity.id)
566
567 assert represented[:url] ==
568 "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
569
570 assert represented[:content] ==
571 "<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>"
572 end
573
574 describe "build_tags/1" do
575 test "it returns a a dictionary tags" do
576 object_tags = [
577 "fediverse",
578 "mastodon",
579 "nextcloud",
580 %{
581 "href" => "https://kawen.space/users/lain",
582 "name" => "@lain@kawen.space",
583 "type" => "Mention"
584 }
585 ]
586
587 assert StatusView.build_tags(object_tags) == [
588 %{name: "fediverse", url: "http://localhost:4001/tag/fediverse"},
589 %{name: "mastodon", url: "http://localhost:4001/tag/mastodon"},
590 %{name: "nextcloud", url: "http://localhost:4001/tag/nextcloud"}
591 ]
592 end
593 end
594
595 describe "rich media cards" do
596 test "a rich media card without a site name renders correctly" do
597 page_url = "http://example.com"
598
599 card = %{
600 url: page_url,
601 image: page_url <> "/example.jpg",
602 title: "Example website"
603 }
604
605 %{provider_name: "example.com"} =
606 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
607 end
608
609 test "a rich media card without a site name or image renders correctly" do
610 page_url = "http://example.com"
611
612 card = %{
613 url: page_url,
614 title: "Example website"
615 }
616
617 %{provider_name: "example.com"} =
618 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
619 end
620
621 test "a rich media card without an image renders correctly" do
622 page_url = "http://example.com"
623
624 card = %{
625 url: page_url,
626 site_name: "Example site name",
627 title: "Example website"
628 }
629
630 %{provider_name: "example.com"} =
631 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
632 end
633
634 test "a rich media card with all relevant data renders correctly" do
635 page_url = "http://example.com"
636
637 card = %{
638 url: page_url,
639 site_name: "Example site name",
640 title: "Example website",
641 image: page_url <> "/example.jpg",
642 description: "Example description"
643 }
644
645 %{provider_name: "example.com"} =
646 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
647 end
648 end
649
650 test "does not embed a relationship in the account" do
651 user = insert(:user)
652 other_user = insert(:user)
653
654 {:ok, activity} =
655 CommonAPI.post(user, %{
656 status: "drink more water"
657 })
658
659 result = StatusView.render("show.json", %{activity: activity, for: other_user})
660
661 assert result[:account][:pleroma][:relationship] == %{}
662 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
663 end
664
665 test "does not embed a relationship in the account in reposts" do
666 user = insert(:user)
667 other_user = insert(:user)
668
669 {:ok, activity} =
670 CommonAPI.post(user, %{
671 status: "˙˙ɐʎns"
672 })
673
674 {:ok, activity} = CommonAPI.repeat(activity.id, other_user)
675
676 result = StatusView.render("show.json", %{activity: activity, for: user})
677
678 assert result[:account][:pleroma][:relationship] == %{}
679 assert result[:reblog][:account][:pleroma][:relationship] == %{}
680 assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec())
681 end
682
683 test "visibility/list" do
684 user = insert(:user)
685
686 {:ok, list} = Pleroma.List.create("foo", user)
687
688 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
689
690 status = StatusView.render("show.json", activity: activity)
691
692 assert status.visibility == "list"
693 end
694
695 test "has a field for parent visibility" do
696 user = insert(:user)
697 poster = insert(:user)
698
699 {:ok, invisible} = CommonAPI.post(poster, %{status: "hey", visibility: "private"})
700
701 {:ok, visible} =
702 CommonAPI.post(poster, %{status: "hey", visibility: "private", in_reply_to_id: invisible.id})
703
704 status = StatusView.render("show.json", activity: visible, for: user)
705 refute status.pleroma.parent_visible
706
707 status = StatusView.render("show.json", activity: visible, for: poster)
708 assert status.pleroma.parent_visible
709 end
710 end