Insert text representation of hashtags into object["hashtags"]
[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 "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 Cachex.clear(:user_cache)
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 Cachex.clear(:user_cache)
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)
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).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: "2hu",
266 url: "/tag/2hu"
267 }
268 ],
269 application: %{
270 name: "Web",
271 website: nil
272 },
273 language: nil,
274 emojis: [
275 %{
276 shortcode: "2hu",
277 url: "corndog.png",
278 static_url: "corndog.png",
279 visible_in_picker: false
280 }
281 ],
282 pleroma: %{
283 local: true,
284 conversation_id: convo_id,
285 in_reply_to_account_acct: nil,
286 content: %{"text/plain" => HTML.strip_tags(object_data["content"])},
287 spoiler_text: %{"text/plain" => HTML.strip_tags(object_data["summary"])},
288 expires_at: nil,
289 direct_conversation_id: nil,
290 thread_muted: false,
291 emoji_reactions: [],
292 parent_visible: false
293 }
294 }
295
296 assert status == expected
297 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
298 end
299
300 test "tells if the message is muted for some reason" do
301 user = insert(:user)
302 other_user = insert(:user)
303
304 {:ok, _user_relationships} = User.mute(user, other_user)
305
306 {:ok, activity} = CommonAPI.post(other_user, %{status: "test"})
307
308 relationships_opt = UserRelationship.view_relationships_option(user, [other_user])
309
310 opts = %{activity: activity}
311 status = StatusView.render("show.json", opts)
312 assert status.muted == false
313 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
314
315 status = StatusView.render("show.json", Map.put(opts, :relationships, relationships_opt))
316 assert status.muted == false
317
318 for_opts = %{activity: activity, for: user}
319 status = StatusView.render("show.json", for_opts)
320 assert status.muted == true
321
322 status = StatusView.render("show.json", Map.put(for_opts, :relationships, relationships_opt))
323 assert status.muted == true
324 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
325 end
326
327 test "tells if the message is thread muted" do
328 user = insert(:user)
329 other_user = insert(:user)
330
331 {:ok, _user_relationships} = User.mute(user, other_user)
332
333 {:ok, activity} = CommonAPI.post(other_user, %{status: "test"})
334 status = StatusView.render("show.json", %{activity: activity, for: user})
335
336 assert status.pleroma.thread_muted == false
337
338 {:ok, activity} = CommonAPI.add_mute(user, activity)
339
340 status = StatusView.render("show.json", %{activity: activity, for: user})
341
342 assert status.pleroma.thread_muted == true
343 end
344
345 test "tells if the status is bookmarked" do
346 user = insert(:user)
347
348 {:ok, activity} = CommonAPI.post(user, %{status: "Cute girls doing cute things"})
349 status = StatusView.render("show.json", %{activity: activity})
350
351 assert status.bookmarked == false
352
353 status = StatusView.render("show.json", %{activity: activity, for: user})
354
355 assert status.bookmarked == false
356
357 {:ok, _bookmark} = Bookmark.create(user.id, activity.id)
358
359 activity = Activity.get_by_id_with_object(activity.id)
360
361 status = StatusView.render("show.json", %{activity: activity, for: user})
362
363 assert status.bookmarked == true
364 end
365
366 test "a reply" do
367 note = insert(:note_activity)
368 user = insert(:user)
369
370 {:ok, activity} = CommonAPI.post(user, %{status: "he", in_reply_to_status_id: note.id})
371
372 status = StatusView.render("show.json", %{activity: activity})
373
374 assert status.in_reply_to_id == to_string(note.id)
375
376 [status] = StatusView.render("index.json", %{activities: [activity], as: :activity})
377
378 assert status.in_reply_to_id == to_string(note.id)
379 end
380
381 test "contains mentions" do
382 user = insert(:user)
383 mentioned = insert(:user)
384
385 {:ok, activity} = CommonAPI.post(user, %{status: "hi @#{mentioned.nickname}"})
386
387 status = StatusView.render("show.json", %{activity: activity})
388
389 assert status.mentions ==
390 Enum.map([mentioned], fn u -> AccountView.render("mention.json", %{user: u}) end)
391
392 assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
393 end
394
395 test "create mentions from the 'to' field" do
396 %User{ap_id: recipient_ap_id} = insert(:user)
397 cc = insert_pair(:user) |> Enum.map(& &1.ap_id)
398
399 object =
400 insert(:note, %{
401 data: %{
402 "to" => [recipient_ap_id],
403 "cc" => cc
404 }
405 })
406
407 activity =
408 insert(:note_activity, %{
409 note: object,
410 recipients: [recipient_ap_id | cc]
411 })
412
413 assert length(activity.recipients) == 3
414
415 %{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity})
416
417 assert length(mentions) == 1
418 assert mention.url == recipient_ap_id
419 end
420
421 test "create mentions from the 'tag' field" do
422 recipient = insert(:user)
423 cc = insert_pair(:user) |> Enum.map(& &1.ap_id)
424
425 object =
426 insert(:note, %{
427 data: %{
428 "cc" => cc,
429 "tag" => [
430 %{
431 "href" => recipient.ap_id,
432 "name" => recipient.nickname,
433 "type" => "Mention"
434 },
435 %{
436 "href" => "https://example.com/search?tag=test",
437 "name" => "#test",
438 "type" => "Hashtag"
439 }
440 ]
441 }
442 })
443
444 activity =
445 insert(:note_activity, %{
446 note: object,
447 recipients: [recipient.ap_id | cc]
448 })
449
450 assert length(activity.recipients) == 3
451
452 %{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity})
453
454 assert length(mentions) == 1
455 assert mention.url == recipient.ap_id
456 end
457
458 test "attachments" do
459 object = %{
460 "type" => "Image",
461 "url" => [
462 %{
463 "mediaType" => "image/png",
464 "href" => "someurl"
465 }
466 ],
467 "blurhash" => "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn",
468 "uuid" => 6
469 }
470
471 expected = %{
472 id: "1638338801",
473 type: "image",
474 url: "someurl",
475 remote_url: "someurl",
476 preview_url: "someurl",
477 text_url: "someurl",
478 description: nil,
479 pleroma: %{mime_type: "image/png"},
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: "/tag/fediverse"},
589 %{name: "mastodon", url: "/tag/mastodon"},
590 %{name: "nextcloud", url: "/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