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