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