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