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