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