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