Restricted embedding of relationships where applicable (statuses / notifications...
[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 assert expected == StatusView.render("attachment.json", %{attachment: object})
406
407 # If theres a "id", use that instead of the generated one
408 object = Map.put(object, "id", 2)
409 assert %{id: "2"} = StatusView.render("attachment.json", %{attachment: object})
410 end
411
412 test "put the url advertised in the Activity in to the url attribute" do
413 id = "https://wedistribute.org/wp-json/pterotype/v1/object/85810"
414 [activity] = Activity.search(nil, id)
415
416 status = StatusView.render("show.json", %{activity: activity})
417
418 assert status.uri == id
419 assert status.url == "https://wedistribute.org/2019/07/mastodon-drops-ostatus/"
420 end
421
422 test "a reblog" do
423 user = insert(:user)
424 activity = insert(:note_activity)
425
426 {:ok, reblog, _} = CommonAPI.repeat(activity.id, user)
427
428 represented = StatusView.render("show.json", %{for: user, activity: reblog})
429
430 assert represented[:id] == to_string(reblog.id)
431 assert represented[:reblog][:id] == to_string(activity.id)
432 assert represented[:emojis] == []
433 end
434
435 test "a peertube video" do
436 user = insert(:user)
437
438 {:ok, object} =
439 Pleroma.Object.Fetcher.fetch_object_from_id(
440 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
441 )
442
443 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
444
445 represented = StatusView.render("show.json", %{for: user, activity: activity})
446
447 assert represented[:id] == to_string(activity.id)
448 assert length(represented[:media_attachments]) == 1
449 end
450
451 test "funkwhale audio" do
452 user = insert(:user)
453
454 {:ok, object} =
455 Pleroma.Object.Fetcher.fetch_object_from_id(
456 "https://channels.tests.funkwhale.audio/federation/music/uploads/42342395-0208-4fee-a38d-259a6dae0871"
457 )
458
459 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
460
461 represented = StatusView.render("show.json", %{for: user, activity: activity})
462
463 assert represented[:id] == to_string(activity.id)
464 assert length(represented[:media_attachments]) == 1
465 end
466
467 test "a Mobilizon event" do
468 user = insert(:user)
469
470 {:ok, object} =
471 Pleroma.Object.Fetcher.fetch_object_from_id(
472 "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
473 )
474
475 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
476
477 represented = StatusView.render("show.json", %{for: user, activity: activity})
478
479 assert represented[:id] == to_string(activity.id)
480 end
481
482 describe "build_tags/1" do
483 test "it returns a a dictionary tags" do
484 object_tags = [
485 "fediverse",
486 "mastodon",
487 "nextcloud",
488 %{
489 "href" => "https://kawen.space/users/lain",
490 "name" => "@lain@kawen.space",
491 "type" => "Mention"
492 }
493 ]
494
495 assert StatusView.build_tags(object_tags) == [
496 %{name: "fediverse", url: "/tag/fediverse"},
497 %{name: "mastodon", url: "/tag/mastodon"},
498 %{name: "nextcloud", url: "/tag/nextcloud"}
499 ]
500 end
501 end
502
503 describe "rich media cards" do
504 test "a rich media card without a site name renders correctly" do
505 page_url = "http://example.com"
506
507 card = %{
508 url: page_url,
509 image: page_url <> "/example.jpg",
510 title: "Example website"
511 }
512
513 %{provider_name: "example.com"} =
514 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
515 end
516
517 test "a rich media card without a site name or image renders correctly" do
518 page_url = "http://example.com"
519
520 card = %{
521 url: page_url,
522 title: "Example website"
523 }
524
525 %{provider_name: "example.com"} =
526 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
527 end
528
529 test "a rich media card without an image renders correctly" do
530 page_url = "http://example.com"
531
532 card = %{
533 url: page_url,
534 site_name: "Example site name",
535 title: "Example website"
536 }
537
538 %{provider_name: "example.com"} =
539 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
540 end
541
542 test "a rich media card with all relevant data renders correctly" do
543 page_url = "http://example.com"
544
545 card = %{
546 url: page_url,
547 site_name: "Example site name",
548 title: "Example website",
549 image: page_url <> "/example.jpg",
550 description: "Example description"
551 }
552
553 %{provider_name: "example.com"} =
554 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
555 end
556 end
557
558 test "does not embed a relationship in the account" do
559 user = insert(:user)
560 other_user = insert(:user)
561
562 {:ok, activity} =
563 CommonAPI.post(user, %{
564 "status" => "drink more water"
565 })
566
567 result = StatusView.render("show.json", %{activity: activity, for: other_user})
568
569 assert result[:account][:pleroma][:relationship] == %{}
570 end
571
572 test "does not embed a relationship in the account in reposts" do
573 user = insert(:user)
574 other_user = insert(:user)
575
576 {:ok, activity} =
577 CommonAPI.post(user, %{
578 "status" => "˙˙ɐʎns"
579 })
580
581 {:ok, activity, _object} = CommonAPI.repeat(activity.id, other_user)
582
583 result = StatusView.render("show.json", %{activity: activity, for: user})
584
585 assert result[:account][:pleroma][:relationship] == %{}
586 assert result[:reblog][:account][:pleroma][:relationship] == %{}
587 end
588
589 test "visibility/list" do
590 user = insert(:user)
591
592 {:ok, list} = Pleroma.List.create("foo", user)
593
594 {:ok, activity} =
595 CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"})
596
597 status = StatusView.render("show.json", activity: activity)
598
599 assert status.visibility == "list"
600 end
601
602 test "successfully renders a Listen activity (pleroma extension)" do
603 listen_activity = insert(:listen)
604
605 status = StatusView.render("listen.json", activity: listen_activity)
606
607 assert status.length == listen_activity.data["object"]["length"]
608 assert status.title == listen_activity.data["object"]["title"]
609 end
610 end