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