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