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