StatusControllerTest: Capture log.
[akkoma] / test / web / mastodon_api / controllers / status_controller_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.StatusControllerTest do
6 use Pleroma.Web.ConnCase
7
8 alias Pleroma.Activity
9 alias Pleroma.ActivityExpiration
10 alias Pleroma.Config
11 alias Pleroma.Conversation.Participation
12 alias Pleroma.Object
13 alias Pleroma.Repo
14 alias Pleroma.ScheduledActivity
15 alias Pleroma.User
16 alias Pleroma.Web.ActivityPub.ActivityPub
17 alias Pleroma.Web.CommonAPI
18
19 import Pleroma.Factory
20 import ExUnit.CaptureLog
21
22 describe "posting statuses" do
23 setup do
24 user = insert(:user)
25
26 conn =
27 build_conn()
28 |> assign(:user, user)
29
30 [conn: conn]
31 end
32
33 test "posting a status", %{conn: conn} do
34 idempotency_key = "Pikachu rocks!"
35
36 conn_one =
37 conn
38 |> put_req_header("idempotency-key", idempotency_key)
39 |> post("/api/v1/statuses", %{
40 "status" => "cofe",
41 "spoiler_text" => "2hu",
42 "sensitive" => "false"
43 })
44
45 {:ok, ttl} = Cachex.ttl(:idempotency_cache, idempotency_key)
46 # Six hours
47 assert ttl > :timer.seconds(6 * 60 * 60 - 1)
48
49 assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} =
50 json_response(conn_one, 200)
51
52 assert Activity.get_by_id(id)
53
54 conn_two =
55 conn
56 |> put_req_header("idempotency-key", idempotency_key)
57 |> post("/api/v1/statuses", %{
58 "status" => "cofe",
59 "spoiler_text" => "2hu",
60 "sensitive" => "false"
61 })
62
63 assert %{"id" => second_id} = json_response(conn_two, 200)
64 assert id == second_id
65
66 conn_three =
67 conn
68 |> post("/api/v1/statuses", %{
69 "status" => "cofe",
70 "spoiler_text" => "2hu",
71 "sensitive" => "false"
72 })
73
74 assert %{"id" => third_id} = json_response(conn_three, 200)
75 refute id == third_id
76
77 # An activity that will expire:
78 # 2 hours
79 expires_in = 120 * 60
80
81 conn_four =
82 conn
83 |> post("api/v1/statuses", %{
84 "status" => "oolong",
85 "expires_in" => expires_in
86 })
87
88 assert fourth_response = %{"id" => fourth_id} = json_response(conn_four, 200)
89 assert activity = Activity.get_by_id(fourth_id)
90 assert expiration = ActivityExpiration.get_by_activity_id(fourth_id)
91
92 estimated_expires_at =
93 NaiveDateTime.utc_now()
94 |> NaiveDateTime.add(expires_in)
95 |> NaiveDateTime.truncate(:second)
96
97 # This assert will fail if the test takes longer than a minute. I sure hope it never does:
98 assert abs(NaiveDateTime.diff(expiration.scheduled_at, estimated_expires_at, :second)) < 60
99
100 assert fourth_response["pleroma"]["expires_at"] ==
101 NaiveDateTime.to_iso8601(expiration.scheduled_at)
102 end
103
104 test "posting an undefined status with an attachment", %{conn: conn} do
105 user = insert(:user)
106
107 file = %Plug.Upload{
108 content_type: "image/jpg",
109 path: Path.absname("test/fixtures/image.jpg"),
110 filename: "an_image.jpg"
111 }
112
113 {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
114
115 conn =
116 conn
117 |> assign(:user, user)
118 |> post("/api/v1/statuses", %{
119 "media_ids" => [to_string(upload.id)]
120 })
121
122 assert json_response(conn, 200)
123 end
124
125 test "replying to a status", %{conn: conn} do
126 user = insert(:user)
127 {:ok, replied_to} = CommonAPI.post(user, %{"status" => "cofe"})
128
129 conn =
130 conn
131 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
132
133 assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
134
135 activity = Activity.get_by_id(id)
136
137 assert activity.data["context"] == replied_to.data["context"]
138 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
139 end
140
141 test "replying to a direct message with visibility other than direct", %{conn: conn} do
142 user = insert(:user)
143 {:ok, replied_to} = CommonAPI.post(user, %{"status" => "suya..", "visibility" => "direct"})
144
145 Enum.each(["public", "private", "unlisted"], fn visibility ->
146 conn =
147 conn
148 |> post("/api/v1/statuses", %{
149 "status" => "@#{user.nickname} hey",
150 "in_reply_to_id" => replied_to.id,
151 "visibility" => visibility
152 })
153
154 assert json_response(conn, 422) == %{"error" => "The message visibility must be direct"}
155 end)
156 end
157
158 test "posting a status with an invalid in_reply_to_id", %{conn: conn} do
159 conn =
160 conn
161 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => ""})
162
163 assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
164 assert Activity.get_by_id(id)
165 end
166
167 test "posting a sensitive status", %{conn: conn} do
168 conn =
169 conn
170 |> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true})
171
172 assert %{"content" => "cofe", "id" => id, "sensitive" => true} = json_response(conn, 200)
173 assert Activity.get_by_id(id)
174 end
175
176 test "posting a fake status", %{conn: conn} do
177 real_conn =
178 conn
179 |> post("/api/v1/statuses", %{
180 "status" =>
181 "\"Tenshi Eating a Corndog\" is a much discussed concept on /jp/. The significance of it is disputed, so I will focus on one core concept: the symbolism behind it"
182 })
183
184 real_status = json_response(real_conn, 200)
185
186 assert real_status
187 assert Object.get_by_ap_id(real_status["uri"])
188
189 real_status =
190 real_status
191 |> Map.put("id", nil)
192 |> Map.put("url", nil)
193 |> Map.put("uri", nil)
194 |> Map.put("created_at", nil)
195 |> Kernel.put_in(["pleroma", "conversation_id"], nil)
196
197 fake_conn =
198 conn
199 |> post("/api/v1/statuses", %{
200 "status" =>
201 "\"Tenshi Eating a Corndog\" is a much discussed concept on /jp/. The significance of it is disputed, so I will focus on one core concept: the symbolism behind it",
202 "preview" => true
203 })
204
205 fake_status = json_response(fake_conn, 200)
206
207 assert fake_status
208 refute Object.get_by_ap_id(fake_status["uri"])
209
210 fake_status =
211 fake_status
212 |> Map.put("id", nil)
213 |> Map.put("url", nil)
214 |> Map.put("uri", nil)
215 |> Map.put("created_at", nil)
216 |> Kernel.put_in(["pleroma", "conversation_id"], nil)
217
218 assert real_status == fake_status
219 end
220
221 test "posting a status with OGP link preview", %{conn: conn} do
222 Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
223 Config.put([:rich_media, :enabled], true)
224
225 conn =
226 conn
227 |> post("/api/v1/statuses", %{
228 "status" => "https://example.com/ogp"
229 })
230
231 assert %{"id" => id, "card" => %{"title" => "The Rock"}} = json_response(conn, 200)
232 assert Activity.get_by_id(id)
233 end
234
235 test "posting a direct status", %{conn: conn} do
236 user2 = insert(:user)
237 content = "direct cofe @#{user2.nickname}"
238
239 conn =
240 conn
241 |> post("api/v1/statuses", %{"status" => content, "visibility" => "direct"})
242
243 assert %{"id" => id} = response = json_response(conn, 200)
244 assert response["visibility"] == "direct"
245 assert response["pleroma"]["direct_conversation_id"]
246 assert activity = Activity.get_by_id(id)
247 assert activity.recipients == [user2.ap_id, conn.assigns[:user].ap_id]
248 assert activity.data["to"] == [user2.ap_id]
249 assert activity.data["cc"] == []
250 end
251 end
252
253 describe "posting scheduled statuses" do
254 test "creates a scheduled activity", %{conn: conn} do
255 user = insert(:user)
256 scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
257
258 conn =
259 conn
260 |> assign(:user, user)
261 |> post("/api/v1/statuses", %{
262 "status" => "scheduled",
263 "scheduled_at" => scheduled_at
264 })
265
266 assert %{"scheduled_at" => expected_scheduled_at} = json_response(conn, 200)
267 assert expected_scheduled_at == CommonAPI.Utils.to_masto_date(scheduled_at)
268 assert [] == Repo.all(Activity)
269 end
270
271 test "creates a scheduled activity with a media attachment", %{conn: conn} do
272 user = insert(:user)
273 scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
274
275 file = %Plug.Upload{
276 content_type: "image/jpg",
277 path: Path.absname("test/fixtures/image.jpg"),
278 filename: "an_image.jpg"
279 }
280
281 {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
282
283 conn =
284 conn
285 |> assign(:user, user)
286 |> post("/api/v1/statuses", %{
287 "media_ids" => [to_string(upload.id)],
288 "status" => "scheduled",
289 "scheduled_at" => scheduled_at
290 })
291
292 assert %{"media_attachments" => [media_attachment]} = json_response(conn, 200)
293 assert %{"type" => "image"} = media_attachment
294 end
295
296 test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now",
297 %{conn: conn} do
298 user = insert(:user)
299
300 scheduled_at =
301 NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond)
302
303 conn =
304 conn
305 |> assign(:user, user)
306 |> post("/api/v1/statuses", %{
307 "status" => "not scheduled",
308 "scheduled_at" => scheduled_at
309 })
310
311 assert %{"content" => "not scheduled"} = json_response(conn, 200)
312 assert [] == Repo.all(ScheduledActivity)
313 end
314
315 test "returns error when daily user limit is exceeded", %{conn: conn} do
316 user = insert(:user)
317
318 today =
319 NaiveDateTime.utc_now()
320 |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
321 |> NaiveDateTime.to_iso8601()
322
323 attrs = %{params: %{}, scheduled_at: today}
324 {:ok, _} = ScheduledActivity.create(user, attrs)
325 {:ok, _} = ScheduledActivity.create(user, attrs)
326
327 conn =
328 conn
329 |> assign(:user, user)
330 |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today})
331
332 assert %{"error" => "daily limit exceeded"} == json_response(conn, 422)
333 end
334
335 test "returns error when total user limit is exceeded", %{conn: conn} do
336 user = insert(:user)
337
338 today =
339 NaiveDateTime.utc_now()
340 |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
341 |> NaiveDateTime.to_iso8601()
342
343 tomorrow =
344 NaiveDateTime.utc_now()
345 |> NaiveDateTime.add(:timer.hours(36), :millisecond)
346 |> NaiveDateTime.to_iso8601()
347
348 attrs = %{params: %{}, scheduled_at: today}
349 {:ok, _} = ScheduledActivity.create(user, attrs)
350 {:ok, _} = ScheduledActivity.create(user, attrs)
351 {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
352
353 conn =
354 conn
355 |> assign(:user, user)
356 |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow})
357
358 assert %{"error" => "total limit exceeded"} == json_response(conn, 422)
359 end
360 end
361
362 describe "posting polls" do
363 test "posting a poll", %{conn: conn} do
364 user = insert(:user)
365 time = NaiveDateTime.utc_now()
366
367 conn =
368 conn
369 |> assign(:user, user)
370 |> post("/api/v1/statuses", %{
371 "status" => "Who is the #bestgrill?",
372 "poll" => %{"options" => ["Rei", "Asuka", "Misato"], "expires_in" => 420}
373 })
374
375 response = json_response(conn, 200)
376
377 assert Enum.all?(response["poll"]["options"], fn %{"title" => title} ->
378 title in ["Rei", "Asuka", "Misato"]
379 end)
380
381 assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430
382 refute response["poll"]["expred"]
383 end
384
385 test "option limit is enforced", %{conn: conn} do
386 user = insert(:user)
387 limit = Config.get([:instance, :poll_limits, :max_options])
388
389 conn =
390 conn
391 |> assign(:user, user)
392 |> post("/api/v1/statuses", %{
393 "status" => "desu~",
394 "poll" => %{"options" => Enum.map(0..limit, fn _ -> "desu" end), "expires_in" => 1}
395 })
396
397 %{"error" => error} = json_response(conn, 422)
398 assert error == "Poll can't contain more than #{limit} options"
399 end
400
401 test "option character limit is enforced", %{conn: conn} do
402 user = insert(:user)
403 limit = Config.get([:instance, :poll_limits, :max_option_chars])
404
405 conn =
406 conn
407 |> assign(:user, user)
408 |> post("/api/v1/statuses", %{
409 "status" => "...",
410 "poll" => %{
411 "options" => [Enum.reduce(0..limit, "", fn _, acc -> acc <> "." end)],
412 "expires_in" => 1
413 }
414 })
415
416 %{"error" => error} = json_response(conn, 422)
417 assert error == "Poll options cannot be longer than #{limit} characters each"
418 end
419
420 test "minimal date limit is enforced", %{conn: conn} do
421 user = insert(:user)
422 limit = Config.get([:instance, :poll_limits, :min_expiration])
423
424 conn =
425 conn
426 |> assign(:user, user)
427 |> post("/api/v1/statuses", %{
428 "status" => "imagine arbitrary limits",
429 "poll" => %{
430 "options" => ["this post was made by pleroma gang"],
431 "expires_in" => limit - 1
432 }
433 })
434
435 %{"error" => error} = json_response(conn, 422)
436 assert error == "Expiration date is too soon"
437 end
438
439 test "maximum date limit is enforced", %{conn: conn} do
440 user = insert(:user)
441 limit = Config.get([:instance, :poll_limits, :max_expiration])
442
443 conn =
444 conn
445 |> assign(:user, user)
446 |> post("/api/v1/statuses", %{
447 "status" => "imagine arbitrary limits",
448 "poll" => %{
449 "options" => ["this post was made by pleroma gang"],
450 "expires_in" => limit + 1
451 }
452 })
453
454 %{"error" => error} = json_response(conn, 422)
455 assert error == "Expiration date is too far in the future"
456 end
457 end
458
459 test "get a status", %{conn: conn} do
460 activity = insert(:note_activity)
461
462 conn =
463 conn
464 |> get("/api/v1/statuses/#{activity.id}")
465
466 assert %{"id" => id} = json_response(conn, 200)
467 assert id == to_string(activity.id)
468 end
469
470 test "get a direct status", %{conn: conn} do
471 user = insert(:user)
472 other_user = insert(:user)
473
474 {:ok, activity} =
475 CommonAPI.post(user, %{"status" => "@#{other_user.nickname}", "visibility" => "direct"})
476
477 conn =
478 conn
479 |> assign(:user, user)
480 |> get("/api/v1/statuses/#{activity.id}")
481
482 [participation] = Participation.for_user(user)
483
484 res = json_response(conn, 200)
485 assert res["pleroma"]["direct_conversation_id"] == participation.id
486 end
487
488 test "get statuses by IDs", %{conn: conn} do
489 %{id: id1} = insert(:note_activity)
490 %{id: id2} = insert(:note_activity)
491
492 query_string = "ids[]=#{id1}&ids[]=#{id2}"
493 conn = get(conn, "/api/v1/statuses/?#{query_string}")
494
495 assert [%{"id" => ^id1}, %{"id" => ^id2}] = Enum.sort_by(json_response(conn, :ok), & &1["id"])
496 end
497
498 describe "deleting a status" do
499 test "when you created it", %{conn: conn} do
500 activity = insert(:note_activity)
501 author = User.get_cached_by_ap_id(activity.data["actor"])
502
503 conn =
504 conn
505 |> assign(:user, author)
506 |> delete("/api/v1/statuses/#{activity.id}")
507
508 assert %{} = json_response(conn, 200)
509
510 refute Activity.get_by_id(activity.id)
511 end
512
513 test "when you didn't create it", %{conn: conn} do
514 activity = insert(:note_activity)
515 user = insert(:user)
516
517 conn =
518 conn
519 |> assign(:user, user)
520 |> delete("/api/v1/statuses/#{activity.id}")
521
522 assert %{"error" => _} = json_response(conn, 403)
523
524 assert Activity.get_by_id(activity.id) == activity
525 end
526
527 test "when you're an admin or moderator", %{conn: conn} do
528 activity1 = insert(:note_activity)
529 activity2 = insert(:note_activity)
530 admin = insert(:user, info: %{is_admin: true})
531 moderator = insert(:user, info: %{is_moderator: true})
532
533 res_conn =
534 conn
535 |> assign(:user, admin)
536 |> delete("/api/v1/statuses/#{activity1.id}")
537
538 assert %{} = json_response(res_conn, 200)
539
540 res_conn =
541 conn
542 |> assign(:user, moderator)
543 |> delete("/api/v1/statuses/#{activity2.id}")
544
545 assert %{} = json_response(res_conn, 200)
546
547 refute Activity.get_by_id(activity1.id)
548 refute Activity.get_by_id(activity2.id)
549 end
550 end
551
552 describe "reblogging" do
553 test "reblogs and returns the reblogged status", %{conn: conn} do
554 activity = insert(:note_activity)
555 user = insert(:user)
556
557 conn =
558 conn
559 |> assign(:user, user)
560 |> post("/api/v1/statuses/#{activity.id}/reblog")
561
562 assert %{
563 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
564 "reblogged" => true
565 } = json_response(conn, 200)
566
567 assert to_string(activity.id) == id
568 end
569
570 test "reblogs privately and returns the reblogged status", %{conn: conn} do
571 activity = insert(:note_activity)
572 user = insert(:user)
573
574 conn =
575 conn
576 |> assign(:user, user)
577 |> post("/api/v1/statuses/#{activity.id}/reblog", %{"visibility" => "private"})
578
579 assert %{
580 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
581 "reblogged" => true,
582 "visibility" => "private"
583 } = json_response(conn, 200)
584
585 assert to_string(activity.id) == id
586 end
587
588 test "reblogged status for another user", %{conn: conn} do
589 activity = insert(:note_activity)
590 user1 = insert(:user)
591 user2 = insert(:user)
592 user3 = insert(:user)
593 {:ok, _} = CommonAPI.favorite(user2, activity.id)
594 {:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id)
595 {:ok, reblog_activity1, _object} = CommonAPI.repeat(activity.id, user1)
596 {:ok, _, _object} = CommonAPI.repeat(activity.id, user2)
597
598 conn_res =
599 conn
600 |> assign(:user, user3)
601 |> get("/api/v1/statuses/#{reblog_activity1.id}")
602
603 assert %{
604 "reblog" => %{"id" => id, "reblogged" => false, "reblogs_count" => 2},
605 "reblogged" => false,
606 "favourited" => false,
607 "bookmarked" => false
608 } = json_response(conn_res, 200)
609
610 conn_res =
611 conn
612 |> assign(:user, user2)
613 |> get("/api/v1/statuses/#{reblog_activity1.id}")
614
615 assert %{
616 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 2},
617 "reblogged" => true,
618 "favourited" => true,
619 "bookmarked" => true
620 } = json_response(conn_res, 200)
621
622 assert to_string(activity.id) == id
623 end
624
625 test "returns 400 error when activity is not exist", %{conn: conn} do
626 user = insert(:user)
627
628 conn =
629 conn
630 |> assign(:user, user)
631 |> post("/api/v1/statuses/foo/reblog")
632
633 assert json_response(conn, 400) == %{"error" => "Could not repeat"}
634 end
635 end
636
637 describe "unreblogging" do
638 test "unreblogs and returns the unreblogged status", %{conn: conn} do
639 activity = insert(:note_activity)
640 user = insert(:user)
641
642 {:ok, _, _} = CommonAPI.repeat(activity.id, user)
643
644 conn =
645 conn
646 |> assign(:user, user)
647 |> post("/api/v1/statuses/#{activity.id}/unreblog")
648
649 assert %{"id" => id, "reblogged" => false, "reblogs_count" => 0} = json_response(conn, 200)
650
651 assert to_string(activity.id) == id
652 end
653
654 test "returns 400 error when activity is not exist", %{conn: conn} do
655 user = insert(:user)
656
657 conn =
658 conn
659 |> assign(:user, user)
660 |> post("/api/v1/statuses/foo/unreblog")
661
662 assert json_response(conn, 400) == %{"error" => "Could not unrepeat"}
663 end
664 end
665
666 describe "favoriting" do
667 test "favs a status and returns it", %{conn: conn} do
668 activity = insert(:note_activity)
669 user = insert(:user)
670
671 conn =
672 conn
673 |> assign(:user, user)
674 |> post("/api/v1/statuses/#{activity.id}/favourite")
675
676 assert %{"id" => id, "favourites_count" => 1, "favourited" => true} =
677 json_response(conn, 200)
678
679 assert to_string(activity.id) == id
680 end
681
682 test "returns 400 error for a wrong id", %{conn: conn} do
683 user = insert(:user)
684
685 assert capture_log(fn ->
686 conn =
687 conn
688 |> assign(:user, user)
689 |> post("/api/v1/statuses/1/favourite")
690
691 assert json_response(conn, 400) == %{"error" => "Could not favorite"}
692 end) =~ "[error]"
693 end
694 end
695
696 describe "unfavoriting" do
697 test "unfavorites a status and returns it", %{conn: conn} do
698 activity = insert(:note_activity)
699 user = insert(:user)
700
701 {:ok, _} = CommonAPI.favorite(user, activity.id)
702
703 conn =
704 conn
705 |> assign(:user, user)
706 |> post("/api/v1/statuses/#{activity.id}/unfavourite")
707
708 assert %{"id" => id, "favourites_count" => 0, "favourited" => false} =
709 json_response(conn, 200)
710
711 assert to_string(activity.id) == id
712 end
713
714 test "returns 400 error for a wrong id", %{conn: conn} do
715 user = insert(:user)
716
717 conn =
718 conn
719 |> assign(:user, user)
720 |> post("/api/v1/statuses/1/unfavourite")
721
722 assert json_response(conn, 400) == %{"error" => "Could not unfavorite"}
723 end
724 end
725
726 describe "pinned statuses" do
727 setup do
728 user = insert(:user)
729 {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"})
730
731 [user: user, activity: activity]
732 end
733
734 clear_config([:instance, :max_pinned_statuses]) do
735 Config.put([:instance, :max_pinned_statuses], 1)
736 end
737
738 test "pin status", %{conn: conn, user: user, activity: activity} do
739 id_str = to_string(activity.id)
740
741 assert %{"id" => ^id_str, "pinned" => true} =
742 conn
743 |> assign(:user, user)
744 |> post("/api/v1/statuses/#{activity.id}/pin")
745 |> json_response(200)
746
747 assert [%{"id" => ^id_str, "pinned" => true}] =
748 conn
749 |> assign(:user, user)
750 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
751 |> json_response(200)
752 end
753
754 test "/pin: returns 400 error when activity is not public", %{conn: conn, user: user} do
755 {:ok, dm} = CommonAPI.post(user, %{"status" => "test", "visibility" => "direct"})
756
757 conn =
758 conn
759 |> assign(:user, user)
760 |> post("/api/v1/statuses/#{dm.id}/pin")
761
762 assert json_response(conn, 400) == %{"error" => "Could not pin"}
763 end
764
765 test "unpin status", %{conn: conn, user: user, activity: activity} do
766 {:ok, _} = CommonAPI.pin(activity.id, user)
767
768 id_str = to_string(activity.id)
769 user = refresh_record(user)
770
771 assert %{"id" => ^id_str, "pinned" => false} =
772 conn
773 |> assign(:user, user)
774 |> post("/api/v1/statuses/#{activity.id}/unpin")
775 |> json_response(200)
776
777 assert [] =
778 conn
779 |> assign(:user, user)
780 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
781 |> json_response(200)
782 end
783
784 test "/unpin: returns 400 error when activity is not exist", %{conn: conn, user: user} do
785 conn =
786 conn
787 |> assign(:user, user)
788 |> post("/api/v1/statuses/1/unpin")
789
790 assert json_response(conn, 400) == %{"error" => "Could not unpin"}
791 end
792
793 test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
794 {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
795
796 id_str_one = to_string(activity_one.id)
797
798 assert %{"id" => ^id_str_one, "pinned" => true} =
799 conn
800 |> assign(:user, user)
801 |> post("/api/v1/statuses/#{id_str_one}/pin")
802 |> json_response(200)
803
804 user = refresh_record(user)
805
806 assert %{"error" => "You have already pinned the maximum number of statuses"} =
807 conn
808 |> assign(:user, user)
809 |> post("/api/v1/statuses/#{activity_two.id}/pin")
810 |> json_response(400)
811 end
812 end
813
814 describe "cards" do
815 setup do
816 Config.put([:rich_media, :enabled], true)
817
818 user = insert(:user)
819 %{user: user}
820 end
821
822 test "returns rich-media card", %{conn: conn, user: user} do
823 Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
824
825 {:ok, activity} = CommonAPI.post(user, %{"status" => "https://example.com/ogp"})
826
827 card_data = %{
828 "image" => "http://ia.media-imdb.com/images/rock.jpg",
829 "provider_name" => "example.com",
830 "provider_url" => "https://example.com",
831 "title" => "The Rock",
832 "type" => "link",
833 "url" => "https://example.com/ogp",
834 "description" =>
835 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
836 "pleroma" => %{
837 "opengraph" => %{
838 "image" => "http://ia.media-imdb.com/images/rock.jpg",
839 "title" => "The Rock",
840 "type" => "video.movie",
841 "url" => "https://example.com/ogp",
842 "description" =>
843 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer."
844 }
845 }
846 }
847
848 response =
849 conn
850 |> get("/api/v1/statuses/#{activity.id}/card")
851 |> json_response(200)
852
853 assert response == card_data
854
855 # works with private posts
856 {:ok, activity} =
857 CommonAPI.post(user, %{"status" => "https://example.com/ogp", "visibility" => "direct"})
858
859 response_two =
860 conn
861 |> assign(:user, user)
862 |> get("/api/v1/statuses/#{activity.id}/card")
863 |> json_response(200)
864
865 assert response_two == card_data
866 end
867
868 test "replaces missing description with an empty string", %{conn: conn, user: user} do
869 Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
870
871 {:ok, activity} =
872 CommonAPI.post(user, %{"status" => "https://example.com/ogp-missing-data"})
873
874 response =
875 conn
876 |> get("/api/v1/statuses/#{activity.id}/card")
877 |> json_response(:ok)
878
879 assert response == %{
880 "type" => "link",
881 "title" => "Pleroma",
882 "description" => "",
883 "image" => nil,
884 "provider_name" => "example.com",
885 "provider_url" => "https://example.com",
886 "url" => "https://example.com/ogp-missing-data",
887 "pleroma" => %{
888 "opengraph" => %{
889 "title" => "Pleroma",
890 "type" => "website",
891 "url" => "https://example.com/ogp-missing-data"
892 }
893 }
894 }
895 end
896 end
897
898 test "bookmarks" do
899 user = insert(:user)
900 for_user = insert(:user)
901
902 {:ok, activity1} =
903 CommonAPI.post(user, %{
904 "status" => "heweoo?"
905 })
906
907 {:ok, activity2} =
908 CommonAPI.post(user, %{
909 "status" => "heweoo!"
910 })
911
912 response1 =
913 build_conn()
914 |> assign(:user, for_user)
915 |> post("/api/v1/statuses/#{activity1.id}/bookmark")
916
917 assert json_response(response1, 200)["bookmarked"] == true
918
919 response2 =
920 build_conn()
921 |> assign(:user, for_user)
922 |> post("/api/v1/statuses/#{activity2.id}/bookmark")
923
924 assert json_response(response2, 200)["bookmarked"] == true
925
926 bookmarks =
927 build_conn()
928 |> assign(:user, for_user)
929 |> get("/api/v1/bookmarks")
930
931 assert [json_response(response2, 200), json_response(response1, 200)] ==
932 json_response(bookmarks, 200)
933
934 response1 =
935 build_conn()
936 |> assign(:user, for_user)
937 |> post("/api/v1/statuses/#{activity1.id}/unbookmark")
938
939 assert json_response(response1, 200)["bookmarked"] == false
940
941 bookmarks =
942 build_conn()
943 |> assign(:user, for_user)
944 |> get("/api/v1/bookmarks")
945
946 assert [json_response(response2, 200)] == json_response(bookmarks, 200)
947 end
948
949 describe "conversation muting" do
950 setup do
951 post_user = insert(:user)
952 user = insert(:user)
953
954 {:ok, activity} = CommonAPI.post(post_user, %{"status" => "HIE"})
955
956 [user: user, activity: activity]
957 end
958
959 test "mute conversation", %{conn: conn, user: user, activity: activity} do
960 id_str = to_string(activity.id)
961
962 assert %{"id" => ^id_str, "muted" => true} =
963 conn
964 |> assign(:user, user)
965 |> post("/api/v1/statuses/#{activity.id}/mute")
966 |> json_response(200)
967 end
968
969 test "cannot mute already muted conversation", %{conn: conn, user: user, activity: activity} do
970 {:ok, _} = CommonAPI.add_mute(user, activity)
971
972 conn =
973 conn
974 |> assign(:user, user)
975 |> post("/api/v1/statuses/#{activity.id}/mute")
976
977 assert json_response(conn, 400) == %{"error" => "conversation is already muted"}
978 end
979
980 test "unmute conversation", %{conn: conn, user: user, activity: activity} do
981 {:ok, _} = CommonAPI.add_mute(user, activity)
982
983 id_str = to_string(activity.id)
984 user = refresh_record(user)
985
986 assert %{"id" => ^id_str, "muted" => false} =
987 conn
988 |> assign(:user, user)
989 |> post("/api/v1/statuses/#{activity.id}/unmute")
990 |> json_response(200)
991 end
992 end
993
994 test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do
995 user1 = insert(:user)
996 user2 = insert(:user)
997 user3 = insert(:user)
998
999 {:ok, replied_to} = CommonAPI.post(user1, %{"status" => "cofe"})
1000
1001 # Reply to status from another user
1002 conn1 =
1003 conn
1004 |> assign(:user, user2)
1005 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
1006
1007 assert %{"content" => "xD", "id" => id} = json_response(conn1, 200)
1008
1009 activity = Activity.get_by_id_with_object(id)
1010
1011 assert Object.normalize(activity).data["inReplyTo"] == Object.normalize(replied_to).data["id"]
1012 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
1013
1014 # Reblog from the third user
1015 conn2 =
1016 conn
1017 |> assign(:user, user3)
1018 |> post("/api/v1/statuses/#{activity.id}/reblog")
1019
1020 assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} =
1021 json_response(conn2, 200)
1022
1023 assert to_string(activity.id) == id
1024
1025 # Getting third user status
1026 conn3 =
1027 conn
1028 |> assign(:user, user3)
1029 |> get("api/v1/timelines/home")
1030
1031 [reblogged_activity] = json_response(conn3, 200)
1032
1033 assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id
1034
1035 replied_to_user = User.get_by_ap_id(replied_to.data["actor"])
1036 assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id
1037 end
1038
1039 describe "GET /api/v1/statuses/:id/favourited_by" do
1040 setup do
1041 user = insert(:user)
1042 {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
1043
1044 conn =
1045 build_conn()
1046 |> assign(:user, user)
1047
1048 [conn: conn, activity: activity, user: user]
1049 end
1050
1051 test "returns users who have favorited the status", %{conn: conn, activity: activity} do
1052 other_user = insert(:user)
1053 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1054
1055 response =
1056 conn
1057 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1058 |> json_response(:ok)
1059
1060 [%{"id" => id}] = response
1061
1062 assert id == other_user.id
1063 end
1064
1065 test "returns empty array when status has not been favorited yet", %{
1066 conn: conn,
1067 activity: activity
1068 } do
1069 response =
1070 conn
1071 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1072 |> json_response(:ok)
1073
1074 assert Enum.empty?(response)
1075 end
1076
1077 test "does not return users who have favorited the status but are blocked", %{
1078 conn: %{assigns: %{user: user}} = conn,
1079 activity: activity
1080 } do
1081 other_user = insert(:user)
1082 {:ok, user} = User.block(user, other_user)
1083
1084 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1085
1086 response =
1087 conn
1088 |> assign(:user, user)
1089 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1090 |> json_response(:ok)
1091
1092 assert Enum.empty?(response)
1093 end
1094
1095 test "does not fail on an unauthenticated request", %{conn: conn, activity: activity} do
1096 other_user = insert(:user)
1097 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1098
1099 response =
1100 conn
1101 |> assign(:user, nil)
1102 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1103 |> json_response(:ok)
1104
1105 [%{"id" => id}] = response
1106 assert id == other_user.id
1107 end
1108
1109 test "requires authentification for private posts", %{conn: conn, user: user} do
1110 other_user = insert(:user)
1111
1112 {:ok, activity} =
1113 CommonAPI.post(user, %{
1114 "status" => "@#{other_user.nickname} wanna get some #cofe together?",
1115 "visibility" => "direct"
1116 })
1117
1118 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1119
1120 conn
1121 |> assign(:user, nil)
1122 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1123 |> json_response(404)
1124
1125 response =
1126 build_conn()
1127 |> assign(:user, other_user)
1128 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1129 |> json_response(200)
1130
1131 [%{"id" => id}] = response
1132 assert id == other_user.id
1133 end
1134 end
1135
1136 describe "GET /api/v1/statuses/:id/reblogged_by" do
1137 setup do
1138 user = insert(:user)
1139 {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
1140
1141 conn =
1142 build_conn()
1143 |> assign(:user, user)
1144
1145 [conn: conn, activity: activity, user: user]
1146 end
1147
1148 test "returns users who have reblogged the status", %{conn: conn, activity: activity} do
1149 other_user = insert(:user)
1150 {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
1151
1152 response =
1153 conn
1154 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1155 |> json_response(:ok)
1156
1157 [%{"id" => id}] = response
1158
1159 assert id == other_user.id
1160 end
1161
1162 test "returns empty array when status has not been reblogged yet", %{
1163 conn: conn,
1164 activity: activity
1165 } do
1166 response =
1167 conn
1168 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1169 |> json_response(:ok)
1170
1171 assert Enum.empty?(response)
1172 end
1173
1174 test "does not return users who have reblogged the status but are blocked", %{
1175 conn: %{assigns: %{user: user}} = conn,
1176 activity: activity
1177 } do
1178 other_user = insert(:user)
1179 {:ok, user} = User.block(user, other_user)
1180
1181 {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
1182
1183 response =
1184 conn
1185 |> assign(:user, user)
1186 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1187 |> json_response(:ok)
1188
1189 assert Enum.empty?(response)
1190 end
1191
1192 test "does not return users who have reblogged the status privately", %{
1193 conn: %{assigns: %{user: user}} = conn,
1194 activity: activity
1195 } do
1196 other_user = insert(:user)
1197
1198 {:ok, _, _} = CommonAPI.repeat(activity.id, other_user, %{"visibility" => "private"})
1199
1200 response =
1201 conn
1202 |> assign(:user, user)
1203 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1204 |> json_response(:ok)
1205
1206 assert Enum.empty?(response)
1207 end
1208
1209 test "does not fail on an unauthenticated request", %{conn: conn, activity: activity} do
1210 other_user = insert(:user)
1211 {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
1212
1213 response =
1214 conn
1215 |> assign(:user, nil)
1216 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1217 |> json_response(:ok)
1218
1219 [%{"id" => id}] = response
1220 assert id == other_user.id
1221 end
1222
1223 test "requires authentification for private posts", %{conn: conn, user: user} do
1224 other_user = insert(:user)
1225
1226 {:ok, activity} =
1227 CommonAPI.post(user, %{
1228 "status" => "@#{other_user.nickname} wanna get some #cofe together?",
1229 "visibility" => "direct"
1230 })
1231
1232 conn
1233 |> assign(:user, nil)
1234 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1235 |> json_response(404)
1236
1237 response =
1238 build_conn()
1239 |> assign(:user, other_user)
1240 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1241 |> json_response(200)
1242
1243 assert [] == response
1244 end
1245 end
1246
1247 test "context" do
1248 user = insert(:user)
1249
1250 {:ok, %{id: id1}} = CommonAPI.post(user, %{"status" => "1"})
1251 {:ok, %{id: id2}} = CommonAPI.post(user, %{"status" => "2", "in_reply_to_status_id" => id1})
1252 {:ok, %{id: id3}} = CommonAPI.post(user, %{"status" => "3", "in_reply_to_status_id" => id2})
1253 {:ok, %{id: id4}} = CommonAPI.post(user, %{"status" => "4", "in_reply_to_status_id" => id3})
1254 {:ok, %{id: id5}} = CommonAPI.post(user, %{"status" => "5", "in_reply_to_status_id" => id4})
1255
1256 response =
1257 build_conn()
1258 |> assign(:user, nil)
1259 |> get("/api/v1/statuses/#{id3}/context")
1260 |> json_response(:ok)
1261
1262 assert %{
1263 "ancestors" => [%{"id" => ^id1}, %{"id" => ^id2}],
1264 "descendants" => [%{"id" => ^id4}, %{"id" => ^id5}]
1265 } = response
1266 end
1267
1268 test "returns the favorites of a user", %{conn: conn} do
1269 user = insert(:user)
1270 other_user = insert(:user)
1271
1272 {:ok, _} = CommonAPI.post(other_user, %{"status" => "bla"})
1273 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "traps are happy"})
1274
1275 {:ok, _} = CommonAPI.favorite(user, activity.id)
1276
1277 first_conn =
1278 conn
1279 |> assign(:user, user)
1280 |> get("/api/v1/favourites")
1281
1282 assert [status] = json_response(first_conn, 200)
1283 assert status["id"] == to_string(activity.id)
1284
1285 assert [{"link", _link_header}] =
1286 Enum.filter(first_conn.resp_headers, fn element -> match?({"link", _}, element) end)
1287
1288 # Honours query params
1289 {:ok, second_activity} =
1290 CommonAPI.post(other_user, %{
1291 "status" =>
1292 "Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful."
1293 })
1294
1295 {:ok, _} = CommonAPI.favorite(user, second_activity.id)
1296
1297 last_like = status["id"]
1298
1299 second_conn =
1300 conn
1301 |> assign(:user, user)
1302 |> get("/api/v1/favourites?since_id=#{last_like}")
1303
1304 assert [second_status] = json_response(second_conn, 200)
1305 assert second_status["id"] == to_string(second_activity.id)
1306
1307 third_conn =
1308 conn
1309 |> assign(:user, user)
1310 |> get("/api/v1/favourites?limit=0")
1311
1312 assert [] = json_response(third_conn, 200)
1313 end
1314 end