abe3e6f8d6e91c5515d10fbd1f5a77aa9372822b
[akkoma] / test / web / common_api / common_api_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.CommonAPITest do
6 use Pleroma.DataCase
7 alias Pleroma.Activity
8 alias Pleroma.Chat
9 alias Pleroma.Conversation.Participation
10 alias Pleroma.Object
11 alias Pleroma.User
12 alias Pleroma.Web.ActivityPub.ActivityPub
13 alias Pleroma.Web.ActivityPub.Visibility
14 alias Pleroma.Web.AdminAPI.AccountView
15 alias Pleroma.Web.CommonAPI
16
17 import Pleroma.Factory
18
19 require Pleroma.Constants
20
21 setup do: clear_config([:instance, :safe_dm_mentions])
22 setup do: clear_config([:instance, :limit])
23 setup do: clear_config([:instance, :max_pinned_statuses])
24
25 describe "posting chat messages" do
26 test "it posts a chat message" do
27 author = insert(:user)
28 recipient = insert(:user)
29
30 {:ok, activity} =
31 CommonAPI.post_chat_message(
32 author,
33 recipient,
34 "a test message <script>alert('uuu')</script>"
35 )
36
37 assert activity.data["type"] == "Create"
38 assert activity.local
39 object = Object.normalize(activity)
40
41 assert object.data["type"] == "ChatMessage"
42 assert object.data["to"] == [recipient.ap_id]
43
44 assert object.data["content"] ==
45 "a test message &lt;script&gt;alert(&#39;uuu&#39;)&lt;/script&gt;"
46
47 assert Chat.get(author.id, recipient.ap_id)
48 assert Chat.get(recipient.id, author.ap_id)
49 end
50 end
51
52 test "when replying to a conversation / participation, it will set the correct context id even if no explicit reply_to is given" do
53 user = insert(:user)
54 {:ok, activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"})
55
56 [participation] = Participation.for_user(user)
57
58 {:ok, convo_reply} =
59 CommonAPI.post(user, %{"status" => ".", "in_reply_to_conversation_id" => participation.id})
60
61 assert Visibility.is_direct?(convo_reply)
62
63 assert activity.data["context"] == convo_reply.data["context"]
64 end
65
66 test "when replying to a conversation / participation, it only mentions the recipients explicitly declared in the participation" do
67 har = insert(:user)
68 jafnhar = insert(:user)
69 tridi = insert(:user)
70
71 {:ok, activity} =
72 CommonAPI.post(har, %{
73 "status" => "@#{jafnhar.nickname} hey",
74 "visibility" => "direct"
75 })
76
77 assert har.ap_id in activity.recipients
78 assert jafnhar.ap_id in activity.recipients
79
80 [participation] = Participation.for_user(har)
81
82 {:ok, activity} =
83 CommonAPI.post(har, %{
84 "status" => "I don't really like @#{tridi.nickname}",
85 "visibility" => "direct",
86 "in_reply_to_status_id" => activity.id,
87 "in_reply_to_conversation_id" => participation.id
88 })
89
90 assert har.ap_id in activity.recipients
91 assert jafnhar.ap_id in activity.recipients
92 refute tridi.ap_id in activity.recipients
93 end
94
95 test "with the safe_dm_mention option set, it does not mention people beyond the initial tags" do
96 har = insert(:user)
97 jafnhar = insert(:user)
98 tridi = insert(:user)
99
100 Pleroma.Config.put([:instance, :safe_dm_mentions], true)
101
102 {:ok, activity} =
103 CommonAPI.post(har, %{
104 "status" => "@#{jafnhar.nickname} hey, i never want to see @#{tridi.nickname} again",
105 "visibility" => "direct"
106 })
107
108 refute tridi.ap_id in activity.recipients
109 assert jafnhar.ap_id in activity.recipients
110 end
111
112 test "it de-duplicates tags" do
113 user = insert(:user)
114 {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu #2HU"})
115
116 object = Object.normalize(activity)
117
118 assert object.data["tag"] == ["2hu"]
119 end
120
121 test "it adds emoji in the object" do
122 user = insert(:user)
123 {:ok, activity} = CommonAPI.post(user, %{"status" => ":firefox:"})
124
125 assert Object.normalize(activity).data["emoji"]["firefox"]
126 end
127
128 test "it adds emoji when updating profiles" do
129 user = insert(:user, %{name: ":firefox:"})
130
131 {:ok, activity} = CommonAPI.update(user)
132 user = User.get_cached_by_ap_id(user.ap_id)
133 [firefox] = user.source_data["tag"]
134
135 assert firefox["name"] == ":firefox:"
136
137 assert Pleroma.Constants.as_public() in activity.recipients
138 end
139
140 describe "posting" do
141 test "it supports explicit addressing" do
142 user = insert(:user)
143 user_two = insert(:user)
144 user_three = insert(:user)
145 user_four = insert(:user)
146
147 {:ok, activity} =
148 CommonAPI.post(user, %{
149 "status" =>
150 "Hey, I think @#{user_three.nickname} is ugly. @#{user_four.nickname} is alright though.",
151 "to" => [user_two.nickname, user_four.nickname, "nonexistent"]
152 })
153
154 assert user.ap_id in activity.recipients
155 assert user_two.ap_id in activity.recipients
156 assert user_four.ap_id in activity.recipients
157 refute user_three.ap_id in activity.recipients
158 end
159
160 test "it filters out obviously bad tags when accepting a post as HTML" do
161 user = insert(:user)
162
163 post = "<p><b>2hu</b></p><script>alert('xss')</script>"
164
165 {:ok, activity} =
166 CommonAPI.post(user, %{
167 "status" => post,
168 "content_type" => "text/html"
169 })
170
171 object = Object.normalize(activity)
172
173 assert object.data["content"] == "<p><b>2hu</b></p>alert(&#39;xss&#39;)"
174 end
175
176 test "it filters out obviously bad tags when accepting a post as Markdown" do
177 user = insert(:user)
178
179 post = "<p><b>2hu</b></p><script>alert('xss')</script>"
180
181 {:ok, activity} =
182 CommonAPI.post(user, %{
183 "status" => post,
184 "content_type" => "text/markdown"
185 })
186
187 object = Object.normalize(activity)
188
189 assert object.data["content"] == "<p><b>2hu</b></p>alert(&#39;xss&#39;)"
190 end
191
192 test "it does not allow replies to direct messages that are not direct messages themselves" do
193 user = insert(:user)
194
195 {:ok, activity} = CommonAPI.post(user, %{"status" => "suya..", "visibility" => "direct"})
196
197 assert {:ok, _} =
198 CommonAPI.post(user, %{
199 "status" => "suya..",
200 "visibility" => "direct",
201 "in_reply_to_status_id" => activity.id
202 })
203
204 Enum.each(["public", "private", "unlisted"], fn visibility ->
205 assert {:error, "The message visibility must be direct"} =
206 CommonAPI.post(user, %{
207 "status" => "suya..",
208 "visibility" => visibility,
209 "in_reply_to_status_id" => activity.id
210 })
211 end)
212 end
213
214 test "it allows to address a list" do
215 user = insert(:user)
216 {:ok, list} = Pleroma.List.create("foo", user)
217
218 {:ok, activity} =
219 CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"})
220
221 assert activity.data["bcc"] == [list.ap_id]
222 assert activity.recipients == [list.ap_id, user.ap_id]
223 assert activity.data["listMessage"] == list.ap_id
224 end
225
226 test "it returns error when status is empty and no attachments" do
227 user = insert(:user)
228
229 assert {:error, "Cannot post an empty status without attachments"} =
230 CommonAPI.post(user, %{"status" => ""})
231 end
232
233 test "it validates character limits are correctly enforced" do
234 Pleroma.Config.put([:instance, :limit], 5)
235
236 user = insert(:user)
237
238 assert {:error, "The status is over the character limit"} =
239 CommonAPI.post(user, %{"status" => "foobar"})
240
241 assert {:ok, activity} = CommonAPI.post(user, %{"status" => "12345"})
242 end
243
244 test "it can handle activities that expire" do
245 user = insert(:user)
246
247 expires_at =
248 NaiveDateTime.utc_now()
249 |> NaiveDateTime.truncate(:second)
250 |> NaiveDateTime.add(1_000_000, :second)
251
252 assert {:ok, activity} =
253 CommonAPI.post(user, %{"status" => "chai", "expires_in" => 1_000_000})
254
255 assert expiration = Pleroma.ActivityExpiration.get_by_activity_id(activity.id)
256 assert expiration.scheduled_at == expires_at
257 end
258 end
259
260 describe "reactions" do
261 test "reacting to a status with an emoji" do
262 user = insert(:user)
263 other_user = insert(:user)
264
265 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
266
267 {:ok, reaction, _} = CommonAPI.react_with_emoji(activity.id, user, "👍")
268
269 assert reaction.data["actor"] == user.ap_id
270 assert reaction.data["content"] == "👍"
271
272 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
273
274 {:error, _} = CommonAPI.react_with_emoji(activity.id, user, ".")
275 end
276
277 test "unreacting to a status with an emoji" do
278 user = insert(:user)
279 other_user = insert(:user)
280
281 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
282 {:ok, reaction, _} = CommonAPI.react_with_emoji(activity.id, user, "👍")
283
284 {:ok, unreaction, _} = CommonAPI.unreact_with_emoji(activity.id, user, "👍")
285
286 assert unreaction.data["type"] == "Undo"
287 assert unreaction.data["object"] == reaction.data["id"]
288 end
289
290 test "repeating a status" do
291 user = insert(:user)
292 other_user = insert(:user)
293
294 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
295
296 {:ok, %Activity{}, _} = CommonAPI.repeat(activity.id, user)
297 end
298
299 test "repeating a status privately" do
300 user = insert(:user)
301 other_user = insert(:user)
302
303 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
304
305 {:ok, %Activity{} = announce_activity, _} =
306 CommonAPI.repeat(activity.id, user, %{"visibility" => "private"})
307
308 assert Visibility.is_private?(announce_activity)
309 end
310
311 test "favoriting a status" do
312 user = insert(:user)
313 other_user = insert(:user)
314
315 {:ok, post_activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
316
317 {:ok, %Activity{data: data}} = CommonAPI.favorite(user, post_activity.id)
318 assert data["type"] == "Like"
319 assert data["actor"] == user.ap_id
320 assert data["object"] == post_activity.data["object"]
321 end
322
323 test "retweeting a status twice returns the status" do
324 user = insert(:user)
325 other_user = insert(:user)
326
327 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
328 {:ok, %Activity{} = activity, object} = CommonAPI.repeat(activity.id, user)
329 {:ok, ^activity, ^object} = CommonAPI.repeat(activity.id, user)
330 end
331
332 test "favoriting a status twice returns ok, but without the like activity" do
333 user = insert(:user)
334 other_user = insert(:user)
335
336 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
337 {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id)
338 assert {:ok, :already_liked} = CommonAPI.favorite(user, activity.id)
339 end
340 end
341
342 describe "pinned statuses" do
343 setup do
344 Pleroma.Config.put([:instance, :max_pinned_statuses], 1)
345
346 user = insert(:user)
347 {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"})
348
349 [user: user, activity: activity]
350 end
351
352 test "pin status", %{user: user, activity: activity} do
353 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
354
355 id = activity.id
356 user = refresh_record(user)
357
358 assert %User{pinned_activities: [^id]} = user
359 end
360
361 test "pin poll", %{user: user} do
362 {:ok, activity} =
363 CommonAPI.post(user, %{
364 "status" => "How is fediverse today?",
365 "poll" => %{"options" => ["Absolutely outstanding", "Not good"], "expires_in" => 20}
366 })
367
368 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
369
370 id = activity.id
371 user = refresh_record(user)
372
373 assert %User{pinned_activities: [^id]} = user
374 end
375
376 test "unlisted statuses can be pinned", %{user: user} do
377 {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!", "visibility" => "unlisted"})
378 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
379 end
380
381 test "only self-authored can be pinned", %{activity: activity} do
382 user = insert(:user)
383
384 assert {:error, "Could not pin"} = CommonAPI.pin(activity.id, user)
385 end
386
387 test "max pinned statuses", %{user: user, activity: activity_one} do
388 {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
389
390 assert {:ok, ^activity_one} = CommonAPI.pin(activity_one.id, user)
391
392 user = refresh_record(user)
393
394 assert {:error, "You have already pinned the maximum number of statuses"} =
395 CommonAPI.pin(activity_two.id, user)
396 end
397
398 test "unpin status", %{user: user, activity: activity} do
399 {:ok, activity} = CommonAPI.pin(activity.id, user)
400
401 user = refresh_record(user)
402
403 assert {:ok, ^activity} = CommonAPI.unpin(activity.id, user)
404
405 user = refresh_record(user)
406
407 assert %User{pinned_activities: []} = user
408 end
409
410 test "should unpin when deleting a status", %{user: user, activity: activity} do
411 {:ok, activity} = CommonAPI.pin(activity.id, user)
412
413 user = refresh_record(user)
414
415 assert {:ok, _} = CommonAPI.delete(activity.id, user)
416
417 user = refresh_record(user)
418
419 assert %User{pinned_activities: []} = user
420 end
421 end
422
423 describe "mute tests" do
424 setup do
425 user = insert(:user)
426
427 activity = insert(:note_activity)
428
429 [user: user, activity: activity]
430 end
431
432 test "add mute", %{user: user, activity: activity} do
433 {:ok, _} = CommonAPI.add_mute(user, activity)
434 assert CommonAPI.thread_muted?(user, activity)
435 end
436
437 test "remove mute", %{user: user, activity: activity} do
438 CommonAPI.add_mute(user, activity)
439 {:ok, _} = CommonAPI.remove_mute(user, activity)
440 refute CommonAPI.thread_muted?(user, activity)
441 end
442
443 test "check that mutes can't be duplicate", %{user: user, activity: activity} do
444 CommonAPI.add_mute(user, activity)
445 {:error, _} = CommonAPI.add_mute(user, activity)
446 end
447 end
448
449 describe "reports" do
450 test "creates a report" do
451 reporter = insert(:user)
452 target_user = insert(:user)
453
454 {:ok, activity} = CommonAPI.post(target_user, %{"status" => "foobar"})
455
456 reporter_ap_id = reporter.ap_id
457 target_ap_id = target_user.ap_id
458 activity_ap_id = activity.data["id"]
459 comment = "foobar"
460
461 report_data = %{
462 "account_id" => target_user.id,
463 "comment" => comment,
464 "status_ids" => [activity.id]
465 }
466
467 note_obj = %{
468 "type" => "Note",
469 "id" => activity_ap_id,
470 "content" => "foobar",
471 "published" => activity.object.data["published"],
472 "actor" => AccountView.render("show.json", %{user: target_user})
473 }
474
475 assert {:ok, flag_activity} = CommonAPI.report(reporter, report_data)
476
477 assert %Activity{
478 actor: ^reporter_ap_id,
479 data: %{
480 "type" => "Flag",
481 "content" => ^comment,
482 "object" => [^target_ap_id, ^note_obj],
483 "state" => "open"
484 }
485 } = flag_activity
486 end
487
488 test "updates report state" do
489 [reporter, target_user] = insert_pair(:user)
490 activity = insert(:note_activity, user: target_user)
491
492 {:ok, %Activity{id: report_id}} =
493 CommonAPI.report(reporter, %{
494 "account_id" => target_user.id,
495 "comment" => "I feel offended",
496 "status_ids" => [activity.id]
497 })
498
499 {:ok, report} = CommonAPI.update_report_state(report_id, "resolved")
500
501 assert report.data["state"] == "resolved"
502
503 [reported_user, activity_id] = report.data["object"]
504
505 assert reported_user == target_user.ap_id
506 assert activity_id == activity.data["id"]
507 end
508
509 test "does not update report state when state is unsupported" do
510 [reporter, target_user] = insert_pair(:user)
511 activity = insert(:note_activity, user: target_user)
512
513 {:ok, %Activity{id: report_id}} =
514 CommonAPI.report(reporter, %{
515 "account_id" => target_user.id,
516 "comment" => "I feel offended",
517 "status_ids" => [activity.id]
518 })
519
520 assert CommonAPI.update_report_state(report_id, "test") == {:error, "Unsupported state"}
521 end
522
523 test "updates state of multiple reports" do
524 [reporter, target_user] = insert_pair(:user)
525 activity = insert(:note_activity, user: target_user)
526
527 {:ok, %Activity{id: first_report_id}} =
528 CommonAPI.report(reporter, %{
529 "account_id" => target_user.id,
530 "comment" => "I feel offended",
531 "status_ids" => [activity.id]
532 })
533
534 {:ok, %Activity{id: second_report_id}} =
535 CommonAPI.report(reporter, %{
536 "account_id" => target_user.id,
537 "comment" => "I feel very offended!",
538 "status_ids" => [activity.id]
539 })
540
541 {:ok, report_ids} =
542 CommonAPI.update_report_state([first_report_id, second_report_id], "resolved")
543
544 first_report = Activity.get_by_id(first_report_id)
545 second_report = Activity.get_by_id(second_report_id)
546
547 assert report_ids -- [first_report_id, second_report_id] == []
548 assert first_report.data["state"] == "resolved"
549 assert second_report.data["state"] == "resolved"
550 end
551 end
552
553 describe "reblog muting" do
554 setup do
555 muter = insert(:user)
556
557 muted = insert(:user)
558
559 [muter: muter, muted: muted]
560 end
561
562 test "add a reblog mute", %{muter: muter, muted: muted} do
563 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
564
565 assert User.showing_reblogs?(muter, muted) == false
566 end
567
568 test "remove a reblog mute", %{muter: muter, muted: muted} do
569 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
570 {:ok, _reblog_mute} = CommonAPI.show_reblogs(muter, muted)
571
572 assert User.showing_reblogs?(muter, muted) == true
573 end
574 end
575
576 describe "unfollow/2" do
577 test "also unsubscribes a user" do
578 [follower, followed] = insert_pair(:user)
579 {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
580 {:ok, _subscription} = User.subscribe(follower, followed)
581
582 assert User.subscribed_to?(follower, followed)
583
584 {:ok, follower} = CommonAPI.unfollow(follower, followed)
585
586 refute User.subscribed_to?(follower, followed)
587 end
588
589 test "cancels a pending follow for a local user" do
590 follower = insert(:user)
591 followed = insert(:user, locked: true)
592
593 assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
594 CommonAPI.follow(follower, followed)
595
596 assert User.get_follow_state(follower, followed) == :follow_pending
597 assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
598 assert User.get_follow_state(follower, followed) == nil
599
600 assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
601 Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
602
603 assert %{
604 data: %{
605 "type" => "Undo",
606 "object" => %{"type" => "Follow", "state" => "cancelled"}
607 }
608 } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
609 end
610
611 test "cancels a pending follow for a remote user" do
612 follower = insert(:user)
613 followed = insert(:user, locked: true, local: false, ap_enabled: true)
614
615 assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
616 CommonAPI.follow(follower, followed)
617
618 assert User.get_follow_state(follower, followed) == :follow_pending
619 assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
620 assert User.get_follow_state(follower, followed) == nil
621
622 assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
623 Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
624
625 assert %{
626 data: %{
627 "type" => "Undo",
628 "object" => %{"type" => "Follow", "state" => "cancelled"}
629 }
630 } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
631 end
632 end
633
634 describe "accept_follow_request/2" do
635 test "after acceptance, it sets all existing pending follow request states to 'accept'" do
636 user = insert(:user, locked: true)
637 follower = insert(:user)
638 follower_two = insert(:user)
639
640 {:ok, follow_activity} = ActivityPub.follow(follower, user)
641 {:ok, follow_activity_two} = ActivityPub.follow(follower, user)
642 {:ok, follow_activity_three} = ActivityPub.follow(follower_two, user)
643
644 assert follow_activity.data["state"] == "pending"
645 assert follow_activity_two.data["state"] == "pending"
646 assert follow_activity_three.data["state"] == "pending"
647
648 {:ok, _follower} = CommonAPI.accept_follow_request(follower, user)
649
650 assert Repo.get(Activity, follow_activity.id).data["state"] == "accept"
651 assert Repo.get(Activity, follow_activity_two.id).data["state"] == "accept"
652 assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
653 end
654
655 test "after rejection, it sets all existing pending follow request states to 'reject'" do
656 user = insert(:user, locked: true)
657 follower = insert(:user)
658 follower_two = insert(:user)
659
660 {:ok, follow_activity} = ActivityPub.follow(follower, user)
661 {:ok, follow_activity_two} = ActivityPub.follow(follower, user)
662 {:ok, follow_activity_three} = ActivityPub.follow(follower_two, user)
663
664 assert follow_activity.data["state"] == "pending"
665 assert follow_activity_two.data["state"] == "pending"
666 assert follow_activity_three.data["state"] == "pending"
667
668 {:ok, _follower} = CommonAPI.reject_follow_request(follower, user)
669
670 assert Repo.get(Activity, follow_activity.id).data["state"] == "reject"
671 assert Repo.get(Activity, follow_activity_two.id).data["state"] == "reject"
672 assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
673 end
674 end
675
676 describe "vote/3" do
677 test "does not allow to vote twice" do
678 user = insert(:user)
679 other_user = insert(:user)
680
681 {:ok, activity} =
682 CommonAPI.post(user, %{
683 "status" => "Am I cute?",
684 "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20}
685 })
686
687 object = Object.normalize(activity)
688
689 {:ok, _, object} = CommonAPI.vote(other_user, object, [0])
690
691 assert {:error, "Already voted"} == CommonAPI.vote(other_user, object, [1])
692 end
693 end
694
695 describe "listen/2" do
696 test "returns a valid activity" do
697 user = insert(:user)
698
699 {:ok, activity} =
700 CommonAPI.listen(user, %{
701 "title" => "lain radio episode 1",
702 "album" => "lain radio",
703 "artist" => "lain",
704 "length" => 180_000
705 })
706
707 object = Object.normalize(activity)
708
709 assert object.data["title"] == "lain radio episode 1"
710
711 assert Visibility.get_visibility(activity) == "public"
712 end
713
714 test "respects visibility=private" do
715 user = insert(:user)
716
717 {:ok, activity} =
718 CommonAPI.listen(user, %{
719 "title" => "lain radio episode 1",
720 "album" => "lain radio",
721 "artist" => "lain",
722 "length" => 180_000,
723 "visibility" => "private"
724 })
725
726 object = Object.normalize(activity)
727
728 assert object.data["title"] == "lain radio episode 1"
729
730 assert Visibility.get_visibility(activity) == "private"
731 end
732 end
733 end