ConnectionPool.Worker: do not stop with an error when there is a timeout
[akkoma] / test / web / mastodon_api / controllers / status_controller_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.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 setup do: clear_config([:instance, :federating])
23 setup do: clear_config([:instance, :allow_relay])
24 setup do: clear_config([:rich_media, :enabled])
25
26 describe "posting statuses" do
27 setup do: oauth_access(["write:statuses"])
28
29 test "posting a status does not increment reblog_count when relaying", %{conn: conn} do
30 Pleroma.Config.put([:instance, :federating], true)
31 Pleroma.Config.get([:instance, :allow_relay], true)
32
33 response =
34 conn
35 |> put_req_header("content-type", "application/json")
36 |> post("api/v1/statuses", %{
37 "content_type" => "text/plain",
38 "source" => "Pleroma FE",
39 "status" => "Hello world",
40 "visibility" => "public"
41 })
42 |> json_response_and_validate_schema(200)
43
44 assert response["reblogs_count"] == 0
45 ObanHelpers.perform_all()
46
47 response =
48 conn
49 |> get("api/v1/statuses/#{response["id"]}", %{})
50 |> json_response_and_validate_schema(200)
51
52 assert response["reblogs_count"] == 0
53 end
54
55 test "posting a status", %{conn: conn} do
56 idempotency_key = "Pikachu rocks!"
57
58 conn_one =
59 conn
60 |> put_req_header("content-type", "application/json")
61 |> put_req_header("idempotency-key", idempotency_key)
62 |> post("/api/v1/statuses", %{
63 "status" => "cofe",
64 "spoiler_text" => "2hu",
65 "sensitive" => "0"
66 })
67
68 {:ok, ttl} = Cachex.ttl(:idempotency_cache, idempotency_key)
69 # Six hours
70 assert ttl > :timer.seconds(6 * 60 * 60 - 1)
71
72 assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} =
73 json_response_and_validate_schema(conn_one, 200)
74
75 assert Activity.get_by_id(id)
76
77 conn_two =
78 conn
79 |> put_req_header("content-type", "application/json")
80 |> put_req_header("idempotency-key", idempotency_key)
81 |> post("/api/v1/statuses", %{
82 "status" => "cofe",
83 "spoiler_text" => "2hu",
84 "sensitive" => 0
85 })
86
87 assert %{"id" => second_id} = json_response(conn_two, 200)
88 assert id == second_id
89
90 conn_three =
91 conn
92 |> put_req_header("content-type", "application/json")
93 |> post("/api/v1/statuses", %{
94 "status" => "cofe",
95 "spoiler_text" => "2hu",
96 "sensitive" => "False"
97 })
98
99 assert %{"id" => third_id} = json_response_and_validate_schema(conn_three, 200)
100 refute id == third_id
101
102 # An activity that will expire:
103 # 2 hours
104 expires_in = 120 * 60
105
106 conn_four =
107 conn
108 |> put_req_header("content-type", "application/json")
109 |> post("api/v1/statuses", %{
110 "status" => "oolong",
111 "expires_in" => expires_in
112 })
113
114 assert fourth_response =
115 %{"id" => fourth_id} = json_response_and_validate_schema(conn_four, 200)
116
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 "it fails to create a status if `expires_in` is less or equal than an hour", %{
133 conn: conn
134 } do
135 # 1 hour
136 expires_in = 60 * 60
137
138 assert %{"error" => "Expiry date is too soon"} =
139 conn
140 |> put_req_header("content-type", "application/json")
141 |> post("api/v1/statuses", %{
142 "status" => "oolong",
143 "expires_in" => expires_in
144 })
145 |> json_response_and_validate_schema(422)
146
147 # 30 minutes
148 expires_in = 30 * 60
149
150 assert %{"error" => "Expiry date is too soon"} =
151 conn
152 |> put_req_header("content-type", "application/json")
153 |> post("api/v1/statuses", %{
154 "status" => "oolong",
155 "expires_in" => expires_in
156 })
157 |> json_response_and_validate_schema(422)
158 end
159
160 test "posting an undefined status with an attachment", %{user: user, conn: conn} do
161 file = %Plug.Upload{
162 content_type: "image/jpg",
163 path: Path.absname("test/fixtures/image.jpg"),
164 filename: "an_image.jpg"
165 }
166
167 {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
168
169 conn =
170 conn
171 |> put_req_header("content-type", "application/json")
172 |> post("/api/v1/statuses", %{
173 "media_ids" => [to_string(upload.id)]
174 })
175
176 assert json_response_and_validate_schema(conn, 200)
177 end
178
179 test "replying to a status", %{user: user, conn: conn} do
180 {:ok, replied_to} = CommonAPI.post(user, %{status: "cofe"})
181
182 conn =
183 conn
184 |> put_req_header("content-type", "application/json")
185 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
186
187 assert %{"content" => "xD", "id" => id} = json_response_and_validate_schema(conn, 200)
188
189 activity = Activity.get_by_id(id)
190
191 assert activity.data["context"] == replied_to.data["context"]
192 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
193 end
194
195 test "replying to a direct message with visibility other than direct", %{
196 user: user,
197 conn: conn
198 } do
199 {:ok, replied_to} = CommonAPI.post(user, %{status: "suya..", visibility: "direct"})
200
201 Enum.each(["public", "private", "unlisted"], fn visibility ->
202 conn =
203 conn
204 |> put_req_header("content-type", "application/json")
205 |> post("/api/v1/statuses", %{
206 "status" => "@#{user.nickname} hey",
207 "in_reply_to_id" => replied_to.id,
208 "visibility" => visibility
209 })
210
211 assert json_response_and_validate_schema(conn, 422) == %{
212 "error" => "The message visibility must be direct"
213 }
214 end)
215 end
216
217 test "posting a status with an invalid in_reply_to_id", %{conn: conn} do
218 conn =
219 conn
220 |> put_req_header("content-type", "application/json")
221 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => ""})
222
223 assert %{"content" => "xD", "id" => id} = json_response_and_validate_schema(conn, 200)
224 assert Activity.get_by_id(id)
225 end
226
227 test "posting a sensitive status", %{conn: conn} do
228 conn =
229 conn
230 |> put_req_header("content-type", "application/json")
231 |> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true})
232
233 assert %{"content" => "cofe", "id" => id, "sensitive" => true} =
234 json_response_and_validate_schema(conn, 200)
235
236 assert Activity.get_by_id(id)
237 end
238
239 test "posting a fake status", %{conn: conn} do
240 real_conn =
241 conn
242 |> put_req_header("content-type", "application/json")
243 |> post("/api/v1/statuses", %{
244 "status" =>
245 "\"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"
246 })
247
248 real_status = json_response_and_validate_schema(real_conn, 200)
249
250 assert real_status
251 assert Object.get_by_ap_id(real_status["uri"])
252
253 real_status =
254 real_status
255 |> Map.put("id", nil)
256 |> Map.put("url", nil)
257 |> Map.put("uri", nil)
258 |> Map.put("created_at", nil)
259 |> Kernel.put_in(["pleroma", "conversation_id"], nil)
260
261 fake_conn =
262 conn
263 |> put_req_header("content-type", "application/json")
264 |> post("/api/v1/statuses", %{
265 "status" =>
266 "\"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",
267 "preview" => true
268 })
269
270 fake_status = json_response_and_validate_schema(fake_conn, 200)
271
272 assert fake_status
273 refute Object.get_by_ap_id(fake_status["uri"])
274
275 fake_status =
276 fake_status
277 |> Map.put("id", nil)
278 |> Map.put("url", nil)
279 |> Map.put("uri", nil)
280 |> Map.put("created_at", nil)
281 |> Kernel.put_in(["pleroma", "conversation_id"], nil)
282
283 assert real_status == fake_status
284 end
285
286 test "posting a status with OGP link preview", %{conn: conn} do
287 Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
288 Config.put([:rich_media, :enabled], true)
289
290 conn =
291 conn
292 |> put_req_header("content-type", "application/json")
293 |> post("/api/v1/statuses", %{
294 "status" => "https://example.com/ogp"
295 })
296
297 assert %{"id" => id, "card" => %{"title" => "The Rock"}} =
298 json_response_and_validate_schema(conn, 200)
299
300 assert Activity.get_by_id(id)
301 end
302
303 test "posting a direct status", %{conn: conn} do
304 user2 = insert(:user)
305 content = "direct cofe @#{user2.nickname}"
306
307 conn =
308 conn
309 |> put_req_header("content-type", "application/json")
310 |> post("api/v1/statuses", %{"status" => content, "visibility" => "direct"})
311
312 assert %{"id" => id} = response = json_response_and_validate_schema(conn, 200)
313 assert response["visibility"] == "direct"
314 assert response["pleroma"]["direct_conversation_id"]
315 assert activity = Activity.get_by_id(id)
316 assert activity.recipients == [user2.ap_id, conn.assigns[:user].ap_id]
317 assert activity.data["to"] == [user2.ap_id]
318 assert activity.data["cc"] == []
319 end
320 end
321
322 describe "posting scheduled statuses" do
323 setup do: oauth_access(["write:statuses"])
324
325 test "creates a scheduled activity", %{conn: conn} do
326 scheduled_at =
327 NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
328 |> NaiveDateTime.to_iso8601()
329 |> Kernel.<>("Z")
330
331 conn =
332 conn
333 |> put_req_header("content-type", "application/json")
334 |> post("/api/v1/statuses", %{
335 "status" => "scheduled",
336 "scheduled_at" => scheduled_at
337 })
338
339 assert %{"scheduled_at" => expected_scheduled_at} =
340 json_response_and_validate_schema(conn, 200)
341
342 assert expected_scheduled_at == CommonAPI.Utils.to_masto_date(scheduled_at)
343 assert [] == Repo.all(Activity)
344 end
345
346 test "ignores nil values", %{conn: conn} do
347 conn =
348 conn
349 |> put_req_header("content-type", "application/json")
350 |> post("/api/v1/statuses", %{
351 "status" => "not scheduled",
352 "scheduled_at" => nil
353 })
354
355 assert result = json_response_and_validate_schema(conn, 200)
356 assert Activity.get_by_id(result["id"])
357 end
358
359 test "creates a scheduled activity with a media attachment", %{user: user, conn: conn} do
360 scheduled_at =
361 NaiveDateTime.utc_now()
362 |> NaiveDateTime.add(:timer.minutes(120), :millisecond)
363 |> NaiveDateTime.to_iso8601()
364 |> Kernel.<>("Z")
365
366 file = %Plug.Upload{
367 content_type: "image/jpg",
368 path: Path.absname("test/fixtures/image.jpg"),
369 filename: "an_image.jpg"
370 }
371
372 {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
373
374 conn =
375 conn
376 |> put_req_header("content-type", "application/json")
377 |> post("/api/v1/statuses", %{
378 "media_ids" => [to_string(upload.id)],
379 "status" => "scheduled",
380 "scheduled_at" => scheduled_at
381 })
382
383 assert %{"media_attachments" => [media_attachment]} =
384 json_response_and_validate_schema(conn, 200)
385
386 assert %{"type" => "image"} = media_attachment
387 end
388
389 test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now",
390 %{conn: conn} do
391 scheduled_at =
392 NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond)
393 |> NaiveDateTime.to_iso8601()
394 |> Kernel.<>("Z")
395
396 conn =
397 conn
398 |> put_req_header("content-type", "application/json")
399 |> post("/api/v1/statuses", %{
400 "status" => "not scheduled",
401 "scheduled_at" => scheduled_at
402 })
403
404 assert %{"content" => "not scheduled"} = json_response_and_validate_schema(conn, 200)
405 assert [] == Repo.all(ScheduledActivity)
406 end
407
408 test "returns error when daily user limit is exceeded", %{user: user, conn: conn} do
409 today =
410 NaiveDateTime.utc_now()
411 |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
412 |> NaiveDateTime.to_iso8601()
413 # TODO
414 |> Kernel.<>("Z")
415
416 attrs = %{params: %{}, scheduled_at: today}
417 {:ok, _} = ScheduledActivity.create(user, attrs)
418 {:ok, _} = ScheduledActivity.create(user, attrs)
419
420 conn =
421 conn
422 |> put_req_header("content-type", "application/json")
423 |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today})
424
425 assert %{"error" => "daily limit exceeded"} == json_response_and_validate_schema(conn, 422)
426 end
427
428 test "returns error when total user limit is exceeded", %{user: user, conn: conn} do
429 today =
430 NaiveDateTime.utc_now()
431 |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
432 |> NaiveDateTime.to_iso8601()
433 |> Kernel.<>("Z")
434
435 tomorrow =
436 NaiveDateTime.utc_now()
437 |> NaiveDateTime.add(:timer.hours(36), :millisecond)
438 |> NaiveDateTime.to_iso8601()
439 |> Kernel.<>("Z")
440
441 attrs = %{params: %{}, scheduled_at: today}
442 {:ok, _} = ScheduledActivity.create(user, attrs)
443 {:ok, _} = ScheduledActivity.create(user, attrs)
444 {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
445
446 conn =
447 conn
448 |> put_req_header("content-type", "application/json")
449 |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow})
450
451 assert %{"error" => "total limit exceeded"} == json_response_and_validate_schema(conn, 422)
452 end
453 end
454
455 describe "posting polls" do
456 setup do: oauth_access(["write:statuses"])
457
458 test "posting a poll", %{conn: conn} do
459 time = NaiveDateTime.utc_now()
460
461 conn =
462 conn
463 |> put_req_header("content-type", "application/json")
464 |> post("/api/v1/statuses", %{
465 "status" => "Who is the #bestgrill?",
466 "poll" => %{
467 "options" => ["Rei", "Asuka", "Misato"],
468 "expires_in" => 420
469 }
470 })
471
472 response = json_response_and_validate_schema(conn, 200)
473
474 assert Enum.all?(response["poll"]["options"], fn %{"title" => title} ->
475 title in ["Rei", "Asuka", "Misato"]
476 end)
477
478 assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430
479 refute response["poll"]["expred"]
480
481 question = Object.get_by_id(response["poll"]["id"])
482
483 # closed contains utc timezone
484 assert question.data["closed"] =~ "Z"
485 end
486
487 test "option limit is enforced", %{conn: conn} do
488 limit = Config.get([:instance, :poll_limits, :max_options])
489
490 conn =
491 conn
492 |> put_req_header("content-type", "application/json")
493 |> post("/api/v1/statuses", %{
494 "status" => "desu~",
495 "poll" => %{"options" => Enum.map(0..limit, fn _ -> "desu" end), "expires_in" => 1}
496 })
497
498 %{"error" => error} = json_response_and_validate_schema(conn, 422)
499 assert error == "Poll can't contain more than #{limit} options"
500 end
501
502 test "option character limit is enforced", %{conn: conn} do
503 limit = Config.get([:instance, :poll_limits, :max_option_chars])
504
505 conn =
506 conn
507 |> put_req_header("content-type", "application/json")
508 |> post("/api/v1/statuses", %{
509 "status" => "...",
510 "poll" => %{
511 "options" => [Enum.reduce(0..limit, "", fn _, acc -> acc <> "." end)],
512 "expires_in" => 1
513 }
514 })
515
516 %{"error" => error} = json_response_and_validate_schema(conn, 422)
517 assert error == "Poll options cannot be longer than #{limit} characters each"
518 end
519
520 test "minimal date limit is enforced", %{conn: conn} do
521 limit = Config.get([:instance, :poll_limits, :min_expiration])
522
523 conn =
524 conn
525 |> put_req_header("content-type", "application/json")
526 |> post("/api/v1/statuses", %{
527 "status" => "imagine arbitrary limits",
528 "poll" => %{
529 "options" => ["this post was made by pleroma gang"],
530 "expires_in" => limit - 1
531 }
532 })
533
534 %{"error" => error} = json_response_and_validate_schema(conn, 422)
535 assert error == "Expiration date is too soon"
536 end
537
538 test "maximum date limit is enforced", %{conn: conn} do
539 limit = Config.get([:instance, :poll_limits, :max_expiration])
540
541 conn =
542 conn
543 |> put_req_header("content-type", "application/json")
544 |> post("/api/v1/statuses", %{
545 "status" => "imagine arbitrary limits",
546 "poll" => %{
547 "options" => ["this post was made by pleroma gang"],
548 "expires_in" => limit + 1
549 }
550 })
551
552 %{"error" => error} = json_response_and_validate_schema(conn, 422)
553 assert error == "Expiration date is too far in the future"
554 end
555 end
556
557 test "get a status" do
558 %{conn: conn} = oauth_access(["read:statuses"])
559 activity = insert(:note_activity)
560
561 conn = get(conn, "/api/v1/statuses/#{activity.id}")
562
563 assert %{"id" => id} = json_response_and_validate_schema(conn, 200)
564 assert id == to_string(activity.id)
565 end
566
567 defp local_and_remote_activities do
568 local = insert(:note_activity)
569 remote = insert(:note_activity, local: false)
570 {:ok, local: local, remote: remote}
571 end
572
573 describe "status with restrict unauthenticated activities for local and remote" do
574 setup do: local_and_remote_activities()
575
576 setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
577
578 setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
579
580 test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
581 res_conn = get(conn, "/api/v1/statuses/#{local.id}")
582
583 assert json_response_and_validate_schema(res_conn, :not_found) == %{
584 "error" => "Record not found"
585 }
586
587 res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
588
589 assert json_response_and_validate_schema(res_conn, :not_found) == %{
590 "error" => "Record not found"
591 }
592 end
593
594 test "if user is authenticated", %{local: local, remote: remote} do
595 %{conn: conn} = oauth_access(["read"])
596 res_conn = get(conn, "/api/v1/statuses/#{local.id}")
597 assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
598
599 res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
600 assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
601 end
602 end
603
604 describe "status with restrict unauthenticated activities for local" do
605 setup do: local_and_remote_activities()
606
607 setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
608
609 test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
610 res_conn = get(conn, "/api/v1/statuses/#{local.id}")
611
612 assert json_response_and_validate_schema(res_conn, :not_found) == %{
613 "error" => "Record not found"
614 }
615
616 res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
617 assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
618 end
619
620 test "if user is authenticated", %{local: local, remote: remote} do
621 %{conn: conn} = oauth_access(["read"])
622 res_conn = get(conn, "/api/v1/statuses/#{local.id}")
623 assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
624
625 res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
626 assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
627 end
628 end
629
630 describe "status with restrict unauthenticated activities for remote" do
631 setup do: local_and_remote_activities()
632
633 setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
634
635 test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
636 res_conn = get(conn, "/api/v1/statuses/#{local.id}")
637 assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
638
639 res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
640
641 assert json_response_and_validate_schema(res_conn, :not_found) == %{
642 "error" => "Record not found"
643 }
644 end
645
646 test "if user is authenticated", %{local: local, remote: remote} do
647 %{conn: conn} = oauth_access(["read"])
648 res_conn = get(conn, "/api/v1/statuses/#{local.id}")
649 assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
650
651 res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
652 assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
653 end
654 end
655
656 test "getting a status that doesn't exist returns 404" do
657 %{conn: conn} = oauth_access(["read:statuses"])
658 activity = insert(:note_activity)
659
660 conn = get(conn, "/api/v1/statuses/#{String.downcase(activity.id)}")
661
662 assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
663 end
664
665 test "get a direct status" do
666 %{user: user, conn: conn} = oauth_access(["read:statuses"])
667 other_user = insert(:user)
668
669 {:ok, activity} =
670 CommonAPI.post(user, %{status: "@#{other_user.nickname}", visibility: "direct"})
671
672 conn =
673 conn
674 |> assign(:user, user)
675 |> get("/api/v1/statuses/#{activity.id}")
676
677 [participation] = Participation.for_user(user)
678
679 res = json_response_and_validate_schema(conn, 200)
680 assert res["pleroma"]["direct_conversation_id"] == participation.id
681 end
682
683 test "get statuses by IDs" do
684 %{conn: conn} = oauth_access(["read:statuses"])
685 %{id: id1} = insert(:note_activity)
686 %{id: id2} = insert(:note_activity)
687
688 query_string = "ids[]=#{id1}&ids[]=#{id2}"
689 conn = get(conn, "/api/v1/statuses/?#{query_string}")
690
691 assert [%{"id" => ^id1}, %{"id" => ^id2}] =
692 Enum.sort_by(json_response_and_validate_schema(conn, :ok), & &1["id"])
693 end
694
695 describe "getting statuses by ids with restricted unauthenticated for local and remote" do
696 setup do: local_and_remote_activities()
697
698 setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
699
700 setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
701
702 test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
703 res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
704
705 assert json_response_and_validate_schema(res_conn, 200) == []
706 end
707
708 test "if user is authenticated", %{local: local, remote: remote} do
709 %{conn: conn} = oauth_access(["read"])
710
711 res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
712
713 assert length(json_response_and_validate_schema(res_conn, 200)) == 2
714 end
715 end
716
717 describe "getting statuses by ids with restricted unauthenticated for local" do
718 setup do: local_and_remote_activities()
719
720 setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
721
722 test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
723 res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
724
725 remote_id = remote.id
726 assert [%{"id" => ^remote_id}] = json_response_and_validate_schema(res_conn, 200)
727 end
728
729 test "if user is authenticated", %{local: local, remote: remote} do
730 %{conn: conn} = oauth_access(["read"])
731
732 res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
733
734 assert length(json_response_and_validate_schema(res_conn, 200)) == 2
735 end
736 end
737
738 describe "getting statuses by ids with restricted unauthenticated for remote" do
739 setup do: local_and_remote_activities()
740
741 setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
742
743 test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
744 res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
745
746 local_id = local.id
747 assert [%{"id" => ^local_id}] = json_response_and_validate_schema(res_conn, 200)
748 end
749
750 test "if user is authenticated", %{local: local, remote: remote} do
751 %{conn: conn} = oauth_access(["read"])
752
753 res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
754
755 assert length(json_response_and_validate_schema(res_conn, 200)) == 2
756 end
757 end
758
759 describe "deleting a status" do
760 test "when you created it" do
761 %{user: author, conn: conn} = oauth_access(["write:statuses"])
762 activity = insert(:note_activity, user: author)
763 object = Object.normalize(activity)
764
765 content = object.data["content"]
766 source = object.data["source"]
767
768 result =
769 conn
770 |> assign(:user, author)
771 |> delete("/api/v1/statuses/#{activity.id}")
772 |> json_response_and_validate_schema(200)
773
774 assert match?(%{"content" => ^content, "text" => ^source}, result)
775
776 refute Activity.get_by_id(activity.id)
777 end
778
779 test "when it doesn't exist" do
780 %{user: author, conn: conn} = oauth_access(["write:statuses"])
781 activity = insert(:note_activity, user: author)
782
783 conn =
784 conn
785 |> assign(:user, author)
786 |> delete("/api/v1/statuses/#{String.downcase(activity.id)}")
787
788 assert %{"error" => "Record not found"} == json_response_and_validate_schema(conn, 404)
789 end
790
791 test "when you didn't create it" do
792 %{conn: conn} = oauth_access(["write:statuses"])
793 activity = insert(:note_activity)
794
795 conn = delete(conn, "/api/v1/statuses/#{activity.id}")
796
797 assert %{"error" => "Record not found"} == json_response_and_validate_schema(conn, 404)
798
799 assert Activity.get_by_id(activity.id) == activity
800 end
801
802 test "when you're an admin or moderator", %{conn: conn} do
803 activity1 = insert(:note_activity)
804 activity2 = insert(:note_activity)
805 admin = insert(:user, is_admin: true)
806 moderator = insert(:user, is_moderator: true)
807
808 res_conn =
809 conn
810 |> assign(:user, admin)
811 |> assign(:token, insert(:oauth_token, user: admin, scopes: ["write:statuses"]))
812 |> delete("/api/v1/statuses/#{activity1.id}")
813
814 assert %{} = json_response_and_validate_schema(res_conn, 200)
815
816 res_conn =
817 conn
818 |> assign(:user, moderator)
819 |> assign(:token, insert(:oauth_token, user: moderator, scopes: ["write:statuses"]))
820 |> delete("/api/v1/statuses/#{activity2.id}")
821
822 assert %{} = json_response_and_validate_schema(res_conn, 200)
823
824 refute Activity.get_by_id(activity1.id)
825 refute Activity.get_by_id(activity2.id)
826 end
827 end
828
829 describe "reblogging" do
830 setup do: oauth_access(["write:statuses"])
831
832 test "reblogs and returns the reblogged status", %{conn: conn} do
833 activity = insert(:note_activity)
834
835 conn =
836 conn
837 |> put_req_header("content-type", "application/json")
838 |> post("/api/v1/statuses/#{activity.id}/reblog")
839
840 assert %{
841 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
842 "reblogged" => true
843 } = json_response_and_validate_schema(conn, 200)
844
845 assert to_string(activity.id) == id
846 end
847
848 test "returns 404 if the reblogged status doesn't exist", %{conn: conn} do
849 activity = insert(:note_activity)
850
851 conn =
852 conn
853 |> put_req_header("content-type", "application/json")
854 |> post("/api/v1/statuses/#{String.downcase(activity.id)}/reblog")
855
856 assert %{"error" => "Record not found"} = json_response_and_validate_schema(conn, 404)
857 end
858
859 test "reblogs privately and returns the reblogged status", %{conn: conn} do
860 activity = insert(:note_activity)
861
862 conn =
863 conn
864 |> put_req_header("content-type", "application/json")
865 |> post(
866 "/api/v1/statuses/#{activity.id}/reblog",
867 %{"visibility" => "private"}
868 )
869
870 assert %{
871 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
872 "reblogged" => true,
873 "visibility" => "private"
874 } = json_response_and_validate_schema(conn, 200)
875
876 assert to_string(activity.id) == id
877 end
878
879 test "reblogged status for another user" do
880 activity = insert(:note_activity)
881 user1 = insert(:user)
882 user2 = insert(:user)
883 user3 = insert(:user)
884 {:ok, _} = CommonAPI.favorite(user2, activity.id)
885 {:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id)
886 {:ok, reblog_activity1} = CommonAPI.repeat(activity.id, user1)
887 {:ok, _} = CommonAPI.repeat(activity.id, user2)
888
889 conn_res =
890 build_conn()
891 |> assign(:user, user3)
892 |> assign(:token, insert(:oauth_token, user: user3, scopes: ["read:statuses"]))
893 |> get("/api/v1/statuses/#{reblog_activity1.id}")
894
895 assert %{
896 "reblog" => %{"id" => id, "reblogged" => false, "reblogs_count" => 2},
897 "reblogged" => false,
898 "favourited" => false,
899 "bookmarked" => false
900 } = json_response_and_validate_schema(conn_res, 200)
901
902 conn_res =
903 build_conn()
904 |> assign(:user, user2)
905 |> assign(:token, insert(:oauth_token, user: user2, scopes: ["read:statuses"]))
906 |> get("/api/v1/statuses/#{reblog_activity1.id}")
907
908 assert %{
909 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 2},
910 "reblogged" => true,
911 "favourited" => true,
912 "bookmarked" => true
913 } = json_response_and_validate_schema(conn_res, 200)
914
915 assert to_string(activity.id) == id
916 end
917 end
918
919 describe "unreblogging" do
920 setup do: oauth_access(["write:statuses"])
921
922 test "unreblogs and returns the unreblogged status", %{user: user, conn: conn} do
923 activity = insert(:note_activity)
924
925 {:ok, _} = CommonAPI.repeat(activity.id, user)
926
927 conn =
928 conn
929 |> put_req_header("content-type", "application/json")
930 |> post("/api/v1/statuses/#{activity.id}/unreblog")
931
932 assert %{"id" => id, "reblogged" => false, "reblogs_count" => 0} =
933 json_response_and_validate_schema(conn, 200)
934
935 assert to_string(activity.id) == id
936 end
937
938 test "returns 404 error when activity does not exist", %{conn: conn} do
939 conn =
940 conn
941 |> put_req_header("content-type", "application/json")
942 |> post("/api/v1/statuses/foo/unreblog")
943
944 assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
945 end
946 end
947
948 describe "favoriting" do
949 setup do: oauth_access(["write:favourites"])
950
951 test "favs a status and returns it", %{conn: conn} do
952 activity = insert(:note_activity)
953
954 conn =
955 conn
956 |> put_req_header("content-type", "application/json")
957 |> post("/api/v1/statuses/#{activity.id}/favourite")
958
959 assert %{"id" => id, "favourites_count" => 1, "favourited" => true} =
960 json_response_and_validate_schema(conn, 200)
961
962 assert to_string(activity.id) == id
963 end
964
965 test "favoriting twice will just return 200", %{conn: conn} do
966 activity = insert(:note_activity)
967
968 conn
969 |> put_req_header("content-type", "application/json")
970 |> post("/api/v1/statuses/#{activity.id}/favourite")
971
972 assert conn
973 |> put_req_header("content-type", "application/json")
974 |> post("/api/v1/statuses/#{activity.id}/favourite")
975 |> json_response_and_validate_schema(200)
976 end
977
978 test "returns 404 error for a wrong id", %{conn: conn} do
979 conn =
980 conn
981 |> put_req_header("content-type", "application/json")
982 |> post("/api/v1/statuses/1/favourite")
983
984 assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
985 end
986 end
987
988 describe "unfavoriting" do
989 setup do: oauth_access(["write:favourites"])
990
991 test "unfavorites a status and returns it", %{user: user, conn: conn} do
992 activity = insert(:note_activity)
993
994 {:ok, _} = CommonAPI.favorite(user, activity.id)
995
996 conn =
997 conn
998 |> put_req_header("content-type", "application/json")
999 |> post("/api/v1/statuses/#{activity.id}/unfavourite")
1000
1001 assert %{"id" => id, "favourites_count" => 0, "favourited" => false} =
1002 json_response_and_validate_schema(conn, 200)
1003
1004 assert to_string(activity.id) == id
1005 end
1006
1007 test "returns 404 error for a wrong id", %{conn: conn} do
1008 conn =
1009 conn
1010 |> put_req_header("content-type", "application/json")
1011 |> post("/api/v1/statuses/1/unfavourite")
1012
1013 assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
1014 end
1015 end
1016
1017 describe "pinned statuses" do
1018 setup do: oauth_access(["write:accounts"])
1019
1020 setup %{user: user} do
1021 {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!"})
1022
1023 %{activity: activity}
1024 end
1025
1026 setup do: clear_config([:instance, :max_pinned_statuses], 1)
1027
1028 test "pin status", %{conn: conn, user: user, activity: activity} do
1029 id_str = to_string(activity.id)
1030
1031 assert %{"id" => ^id_str, "pinned" => true} =
1032 conn
1033 |> put_req_header("content-type", "application/json")
1034 |> post("/api/v1/statuses/#{activity.id}/pin")
1035 |> json_response_and_validate_schema(200)
1036
1037 assert [%{"id" => ^id_str, "pinned" => true}] =
1038 conn
1039 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
1040 |> json_response_and_validate_schema(200)
1041 end
1042
1043 test "/pin: returns 400 error when activity is not public", %{conn: conn, user: user} do
1044 {:ok, dm} = CommonAPI.post(user, %{status: "test", visibility: "direct"})
1045
1046 conn =
1047 conn
1048 |> put_req_header("content-type", "application/json")
1049 |> post("/api/v1/statuses/#{dm.id}/pin")
1050
1051 assert json_response_and_validate_schema(conn, 400) == %{"error" => "Could not pin"}
1052 end
1053
1054 test "unpin status", %{conn: conn, user: user, activity: activity} do
1055 {:ok, _} = CommonAPI.pin(activity.id, user)
1056 user = refresh_record(user)
1057
1058 id_str = to_string(activity.id)
1059
1060 assert %{"id" => ^id_str, "pinned" => false} =
1061 conn
1062 |> assign(:user, user)
1063 |> post("/api/v1/statuses/#{activity.id}/unpin")
1064 |> json_response_and_validate_schema(200)
1065
1066 assert [] =
1067 conn
1068 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
1069 |> json_response_and_validate_schema(200)
1070 end
1071
1072 test "/unpin: returns 400 error when activity is not exist", %{conn: conn} do
1073 conn =
1074 conn
1075 |> put_req_header("content-type", "application/json")
1076 |> post("/api/v1/statuses/1/unpin")
1077
1078 assert json_response_and_validate_schema(conn, 400) == %{"error" => "Could not unpin"}
1079 end
1080
1081 test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
1082 {:ok, activity_two} = CommonAPI.post(user, %{status: "HI!!!"})
1083
1084 id_str_one = to_string(activity_one.id)
1085
1086 assert %{"id" => ^id_str_one, "pinned" => true} =
1087 conn
1088 |> put_req_header("content-type", "application/json")
1089 |> post("/api/v1/statuses/#{id_str_one}/pin")
1090 |> json_response_and_validate_schema(200)
1091
1092 user = refresh_record(user)
1093
1094 assert %{"error" => "You have already pinned the maximum number of statuses"} =
1095 conn
1096 |> assign(:user, user)
1097 |> post("/api/v1/statuses/#{activity_two.id}/pin")
1098 |> json_response_and_validate_schema(400)
1099 end
1100 end
1101
1102 describe "cards" do
1103 setup do
1104 Config.put([:rich_media, :enabled], true)
1105
1106 oauth_access(["read:statuses"])
1107 end
1108
1109 test "returns rich-media card", %{conn: conn, user: user} do
1110 Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
1111
1112 {:ok, activity} = CommonAPI.post(user, %{status: "https://example.com/ogp"})
1113
1114 card_data = %{
1115 "image" => "http://ia.media-imdb.com/images/rock.jpg",
1116 "provider_name" => "example.com",
1117 "provider_url" => "https://example.com",
1118 "title" => "The Rock",
1119 "type" => "link",
1120 "url" => "https://example.com/ogp",
1121 "description" =>
1122 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
1123 "pleroma" => %{
1124 "opengraph" => %{
1125 "image" => "http://ia.media-imdb.com/images/rock.jpg",
1126 "title" => "The Rock",
1127 "type" => "video.movie",
1128 "url" => "https://example.com/ogp",
1129 "description" =>
1130 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer."
1131 }
1132 }
1133 }
1134
1135 response =
1136 conn
1137 |> get("/api/v1/statuses/#{activity.id}/card")
1138 |> json_response_and_validate_schema(200)
1139
1140 assert response == card_data
1141
1142 # works with private posts
1143 {:ok, activity} =
1144 CommonAPI.post(user, %{status: "https://example.com/ogp", visibility: "direct"})
1145
1146 response_two =
1147 conn
1148 |> get("/api/v1/statuses/#{activity.id}/card")
1149 |> json_response_and_validate_schema(200)
1150
1151 assert response_two == card_data
1152 end
1153
1154 test "replaces missing description with an empty string", %{conn: conn, user: user} do
1155 Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
1156
1157 {:ok, activity} = CommonAPI.post(user, %{status: "https://example.com/ogp-missing-data"})
1158
1159 response =
1160 conn
1161 |> get("/api/v1/statuses/#{activity.id}/card")
1162 |> json_response_and_validate_schema(:ok)
1163
1164 assert response == %{
1165 "type" => "link",
1166 "title" => "Pleroma",
1167 "description" => "",
1168 "image" => nil,
1169 "provider_name" => "example.com",
1170 "provider_url" => "https://example.com",
1171 "url" => "https://example.com/ogp-missing-data",
1172 "pleroma" => %{
1173 "opengraph" => %{
1174 "title" => "Pleroma",
1175 "type" => "website",
1176 "url" => "https://example.com/ogp-missing-data"
1177 }
1178 }
1179 }
1180 end
1181 end
1182
1183 test "bookmarks" do
1184 bookmarks_uri = "/api/v1/bookmarks"
1185
1186 %{conn: conn} = oauth_access(["write:bookmarks", "read:bookmarks"])
1187 author = insert(:user)
1188
1189 {:ok, activity1} = CommonAPI.post(author, %{status: "heweoo?"})
1190 {:ok, activity2} = CommonAPI.post(author, %{status: "heweoo!"})
1191
1192 response1 =
1193 conn
1194 |> put_req_header("content-type", "application/json")
1195 |> post("/api/v1/statuses/#{activity1.id}/bookmark")
1196
1197 assert json_response_and_validate_schema(response1, 200)["bookmarked"] == true
1198
1199 response2 =
1200 conn
1201 |> put_req_header("content-type", "application/json")
1202 |> post("/api/v1/statuses/#{activity2.id}/bookmark")
1203
1204 assert json_response_and_validate_schema(response2, 200)["bookmarked"] == true
1205
1206 bookmarks = get(conn, bookmarks_uri)
1207
1208 assert [
1209 json_response_and_validate_schema(response2, 200),
1210 json_response_and_validate_schema(response1, 200)
1211 ] ==
1212 json_response_and_validate_schema(bookmarks, 200)
1213
1214 response1 =
1215 conn
1216 |> put_req_header("content-type", "application/json")
1217 |> post("/api/v1/statuses/#{activity1.id}/unbookmark")
1218
1219 assert json_response_and_validate_schema(response1, 200)["bookmarked"] == false
1220
1221 bookmarks = get(conn, bookmarks_uri)
1222
1223 assert [json_response_and_validate_schema(response2, 200)] ==
1224 json_response_and_validate_schema(bookmarks, 200)
1225 end
1226
1227 describe "conversation muting" do
1228 setup do: oauth_access(["write:mutes"])
1229
1230 setup do
1231 post_user = insert(:user)
1232 {:ok, activity} = CommonAPI.post(post_user, %{status: "HIE"})
1233 %{activity: activity}
1234 end
1235
1236 test "mute conversation", %{conn: conn, activity: activity} do
1237 id_str = to_string(activity.id)
1238
1239 assert %{"id" => ^id_str, "muted" => true} =
1240 conn
1241 |> put_req_header("content-type", "application/json")
1242 |> post("/api/v1/statuses/#{activity.id}/mute")
1243 |> json_response_and_validate_schema(200)
1244 end
1245
1246 test "cannot mute already muted conversation", %{conn: conn, user: user, activity: activity} do
1247 {:ok, _} = CommonAPI.add_mute(user, activity)
1248
1249 conn =
1250 conn
1251 |> put_req_header("content-type", "application/json")
1252 |> post("/api/v1/statuses/#{activity.id}/mute")
1253
1254 assert json_response_and_validate_schema(conn, 400) == %{
1255 "error" => "conversation is already muted"
1256 }
1257 end
1258
1259 test "unmute conversation", %{conn: conn, user: user, activity: activity} do
1260 {:ok, _} = CommonAPI.add_mute(user, activity)
1261
1262 id_str = to_string(activity.id)
1263
1264 assert %{"id" => ^id_str, "muted" => false} =
1265 conn
1266 # |> assign(:user, user)
1267 |> post("/api/v1/statuses/#{activity.id}/unmute")
1268 |> json_response_and_validate_schema(200)
1269 end
1270 end
1271
1272 test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do
1273 user1 = insert(:user)
1274 user2 = insert(:user)
1275 user3 = insert(:user)
1276
1277 {:ok, replied_to} = CommonAPI.post(user1, %{status: "cofe"})
1278
1279 # Reply to status from another user
1280 conn1 =
1281 conn
1282 |> assign(:user, user2)
1283 |> assign(:token, insert(:oauth_token, user: user2, scopes: ["write:statuses"]))
1284 |> put_req_header("content-type", "application/json")
1285 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
1286
1287 assert %{"content" => "xD", "id" => id} = json_response_and_validate_schema(conn1, 200)
1288
1289 activity = Activity.get_by_id_with_object(id)
1290
1291 assert Object.normalize(activity).data["inReplyTo"] == Object.normalize(replied_to).data["id"]
1292 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
1293
1294 # Reblog from the third user
1295 conn2 =
1296 conn
1297 |> assign(:user, user3)
1298 |> assign(:token, insert(:oauth_token, user: user3, scopes: ["write:statuses"]))
1299 |> put_req_header("content-type", "application/json")
1300 |> post("/api/v1/statuses/#{activity.id}/reblog")
1301
1302 assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} =
1303 json_response_and_validate_schema(conn2, 200)
1304
1305 assert to_string(activity.id) == id
1306
1307 # Getting third user status
1308 conn3 =
1309 conn
1310 |> assign(:user, user3)
1311 |> assign(:token, insert(:oauth_token, user: user3, scopes: ["read:statuses"]))
1312 |> get("api/v1/timelines/home")
1313
1314 [reblogged_activity] = json_response(conn3, 200)
1315
1316 assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id
1317
1318 replied_to_user = User.get_by_ap_id(replied_to.data["actor"])
1319 assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id
1320 end
1321
1322 describe "GET /api/v1/statuses/:id/favourited_by" do
1323 setup do: oauth_access(["read:accounts"])
1324
1325 setup %{user: user} do
1326 {:ok, activity} = CommonAPI.post(user, %{status: "test"})
1327
1328 %{activity: activity}
1329 end
1330
1331 test "returns users who have favorited the status", %{conn: conn, activity: activity} do
1332 other_user = insert(:user)
1333 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1334
1335 response =
1336 conn
1337 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1338 |> json_response_and_validate_schema(:ok)
1339
1340 [%{"id" => id}] = response
1341
1342 assert id == other_user.id
1343 end
1344
1345 test "returns empty array when status has not been favorited yet", %{
1346 conn: conn,
1347 activity: activity
1348 } do
1349 response =
1350 conn
1351 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1352 |> json_response_and_validate_schema(:ok)
1353
1354 assert Enum.empty?(response)
1355 end
1356
1357 test "does not return users who have favorited the status but are blocked", %{
1358 conn: %{assigns: %{user: user}} = conn,
1359 activity: activity
1360 } do
1361 other_user = insert(:user)
1362 {:ok, _user_relationship} = User.block(user, other_user)
1363
1364 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1365
1366 response =
1367 conn
1368 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1369 |> json_response_and_validate_schema(:ok)
1370
1371 assert Enum.empty?(response)
1372 end
1373
1374 test "does not fail on an unauthenticated request", %{activity: activity} do
1375 other_user = insert(:user)
1376 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1377
1378 response =
1379 build_conn()
1380 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1381 |> json_response_and_validate_schema(:ok)
1382
1383 [%{"id" => id}] = response
1384 assert id == other_user.id
1385 end
1386
1387 test "requires authentication for private posts", %{user: user} do
1388 other_user = insert(:user)
1389
1390 {:ok, activity} =
1391 CommonAPI.post(user, %{
1392 status: "@#{other_user.nickname} wanna get some #cofe together?",
1393 visibility: "direct"
1394 })
1395
1396 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1397
1398 favourited_by_url = "/api/v1/statuses/#{activity.id}/favourited_by"
1399
1400 build_conn()
1401 |> get(favourited_by_url)
1402 |> json_response_and_validate_schema(404)
1403
1404 conn =
1405 build_conn()
1406 |> assign(:user, other_user)
1407 |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"]))
1408
1409 conn
1410 |> assign(:token, nil)
1411 |> get(favourited_by_url)
1412 |> json_response_and_validate_schema(404)
1413
1414 response =
1415 conn
1416 |> get(favourited_by_url)
1417 |> json_response_and_validate_schema(200)
1418
1419 [%{"id" => id}] = response
1420 assert id == other_user.id
1421 end
1422 end
1423
1424 describe "GET /api/v1/statuses/:id/reblogged_by" do
1425 setup do: oauth_access(["read:accounts"])
1426
1427 setup %{user: user} do
1428 {:ok, activity} = CommonAPI.post(user, %{status: "test"})
1429
1430 %{activity: activity}
1431 end
1432
1433 test "returns users who have reblogged the status", %{conn: conn, activity: activity} do
1434 other_user = insert(:user)
1435 {:ok, _} = CommonAPI.repeat(activity.id, other_user)
1436
1437 response =
1438 conn
1439 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1440 |> json_response_and_validate_schema(:ok)
1441
1442 [%{"id" => id}] = response
1443
1444 assert id == other_user.id
1445 end
1446
1447 test "returns empty array when status has not been reblogged yet", %{
1448 conn: conn,
1449 activity: activity
1450 } do
1451 response =
1452 conn
1453 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1454 |> json_response_and_validate_schema(:ok)
1455
1456 assert Enum.empty?(response)
1457 end
1458
1459 test "does not return users who have reblogged the status but are blocked", %{
1460 conn: %{assigns: %{user: user}} = conn,
1461 activity: activity
1462 } do
1463 other_user = insert(:user)
1464 {:ok, _user_relationship} = User.block(user, other_user)
1465
1466 {:ok, _} = CommonAPI.repeat(activity.id, other_user)
1467
1468 response =
1469 conn
1470 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1471 |> json_response_and_validate_schema(:ok)
1472
1473 assert Enum.empty?(response)
1474 end
1475
1476 test "does not return users who have reblogged the status privately", %{
1477 conn: conn
1478 } do
1479 other_user = insert(:user)
1480 {:ok, activity} = CommonAPI.post(other_user, %{status: "my secret post"})
1481
1482 {:ok, _} = CommonAPI.repeat(activity.id, other_user, %{visibility: "private"})
1483
1484 response =
1485 conn
1486 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1487 |> json_response_and_validate_schema(:ok)
1488
1489 assert Enum.empty?(response)
1490 end
1491
1492 test "does not fail on an unauthenticated request", %{activity: activity} do
1493 other_user = insert(:user)
1494 {:ok, _} = CommonAPI.repeat(activity.id, other_user)
1495
1496 response =
1497 build_conn()
1498 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1499 |> json_response_and_validate_schema(:ok)
1500
1501 [%{"id" => id}] = response
1502 assert id == other_user.id
1503 end
1504
1505 test "requires authentication for private posts", %{user: user} do
1506 other_user = insert(:user)
1507
1508 {:ok, activity} =
1509 CommonAPI.post(user, %{
1510 status: "@#{other_user.nickname} wanna get some #cofe together?",
1511 visibility: "direct"
1512 })
1513
1514 build_conn()
1515 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1516 |> json_response_and_validate_schema(404)
1517
1518 response =
1519 build_conn()
1520 |> assign(:user, other_user)
1521 |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"]))
1522 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1523 |> json_response_and_validate_schema(200)
1524
1525 assert [] == response
1526 end
1527 end
1528
1529 test "context" do
1530 user = insert(:user)
1531
1532 {:ok, %{id: id1}} = CommonAPI.post(user, %{status: "1"})
1533 {:ok, %{id: id2}} = CommonAPI.post(user, %{status: "2", in_reply_to_status_id: id1})
1534 {:ok, %{id: id3}} = CommonAPI.post(user, %{status: "3", in_reply_to_status_id: id2})
1535 {:ok, %{id: id4}} = CommonAPI.post(user, %{status: "4", in_reply_to_status_id: id3})
1536 {:ok, %{id: id5}} = CommonAPI.post(user, %{status: "5", in_reply_to_status_id: id4})
1537
1538 response =
1539 build_conn()
1540 |> get("/api/v1/statuses/#{id3}/context")
1541 |> json_response_and_validate_schema(:ok)
1542
1543 assert %{
1544 "ancestors" => [%{"id" => ^id1}, %{"id" => ^id2}],
1545 "descendants" => [%{"id" => ^id4}, %{"id" => ^id5}]
1546 } = response
1547 end
1548
1549 test "favorites paginate correctly" do
1550 %{user: user, conn: conn} = oauth_access(["read:favourites"])
1551 other_user = insert(:user)
1552 {:ok, first_post} = CommonAPI.post(other_user, %{status: "bla"})
1553 {:ok, second_post} = CommonAPI.post(other_user, %{status: "bla"})
1554 {:ok, third_post} = CommonAPI.post(other_user, %{status: "bla"})
1555
1556 {:ok, _first_favorite} = CommonAPI.favorite(user, third_post.id)
1557 {:ok, _second_favorite} = CommonAPI.favorite(user, first_post.id)
1558 {:ok, third_favorite} = CommonAPI.favorite(user, second_post.id)
1559
1560 result =
1561 conn
1562 |> get("/api/v1/favourites?limit=1")
1563
1564 assert [%{"id" => post_id}] = json_response_and_validate_schema(result, 200)
1565 assert post_id == second_post.id
1566
1567 # Using the header for pagination works correctly
1568 [next, _] = get_resp_header(result, "link") |> hd() |> String.split(", ")
1569 [_, max_id] = Regex.run(~r/max_id=([^&]+)/, next)
1570
1571 assert max_id == third_favorite.id
1572
1573 result =
1574 conn
1575 |> get("/api/v1/favourites?max_id=#{max_id}")
1576
1577 assert [%{"id" => first_post_id}, %{"id" => third_post_id}] =
1578 json_response_and_validate_schema(result, 200)
1579
1580 assert first_post_id == first_post.id
1581 assert third_post_id == third_post.id
1582 end
1583
1584 test "returns the favorites of a user" do
1585 %{user: user, conn: conn} = oauth_access(["read:favourites"])
1586 other_user = insert(:user)
1587
1588 {:ok, _} = CommonAPI.post(other_user, %{status: "bla"})
1589 {:ok, activity} = CommonAPI.post(other_user, %{status: "trees are happy"})
1590
1591 {:ok, last_like} = CommonAPI.favorite(user, activity.id)
1592
1593 first_conn = get(conn, "/api/v1/favourites")
1594
1595 assert [status] = json_response_and_validate_schema(first_conn, 200)
1596 assert status["id"] == to_string(activity.id)
1597
1598 assert [{"link", _link_header}] =
1599 Enum.filter(first_conn.resp_headers, fn element -> match?({"link", _}, element) end)
1600
1601 # Honours query params
1602 {:ok, second_activity} =
1603 CommonAPI.post(other_user, %{
1604 status: "Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful."
1605 })
1606
1607 {:ok, _} = CommonAPI.favorite(user, second_activity.id)
1608
1609 second_conn = get(conn, "/api/v1/favourites?since_id=#{last_like.id}")
1610
1611 assert [second_status] = json_response_and_validate_schema(second_conn, 200)
1612 assert second_status["id"] == to_string(second_activity.id)
1613
1614 third_conn = get(conn, "/api/v1/favourites?limit=0")
1615
1616 assert [] = json_response_and_validate_schema(third_conn, 200)
1617 end
1618
1619 test "expires_at is nil for another user" do
1620 %{conn: conn, user: user} = oauth_access(["read:statuses"])
1621 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", expires_in: 1_000_000})
1622
1623 expires_at =
1624 activity.id
1625 |> ActivityExpiration.get_by_activity_id()
1626 |> Map.get(:scheduled_at)
1627 |> NaiveDateTime.to_iso8601()
1628
1629 assert %{"pleroma" => %{"expires_at" => ^expires_at}} =
1630 conn
1631 |> get("/api/v1/statuses/#{activity.id}")
1632 |> json_response_and_validate_schema(:ok)
1633
1634 %{conn: conn} = oauth_access(["read:statuses"])
1635
1636 assert %{"pleroma" => %{"expires_at" => nil}} =
1637 conn
1638 |> get("/api/v1/statuses/#{activity.id}")
1639 |> json_response_and_validate_schema(:ok)
1640 end
1641 end