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