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