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