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