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