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