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