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