Merge branch 'frontendstatic-ignore-api-calls' 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 "with expiration" do
387 %{conn: conn} = oauth_access(["write:statuses", "read:statuses"])
388
389 scheduled_at =
390 NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(6), :millisecond)
391 |> NaiveDateTime.to_iso8601()
392 |> Kernel.<>("Z")
393
394 assert %{"id" => status_id, "params" => %{"expires_in" => 300}} =
395 conn
396 |> put_req_header("content-type", "application/json")
397 |> post("/api/v1/statuses", %{
398 "status" => "scheduled",
399 "scheduled_at" => scheduled_at,
400 "expires_in" => 300
401 })
402 |> json_response_and_validate_schema(200)
403
404 assert %{"id" => ^status_id, "params" => %{"expires_in" => 300}} =
405 conn
406 |> put_req_header("content-type", "application/json")
407 |> get("/api/v1/scheduled_statuses/#{status_id}")
408 |> json_response_and_validate_schema(200)
409 end
410
411 test "ignores nil values", %{conn: conn} do
412 conn =
413 conn
414 |> put_req_header("content-type", "application/json")
415 |> post("/api/v1/statuses", %{
416 "status" => "not scheduled",
417 "scheduled_at" => nil
418 })
419
420 assert result = json_response_and_validate_schema(conn, 200)
421 assert Activity.get_by_id(result["id"])
422 end
423
424 test "creates a scheduled activity with a media attachment", %{user: user, conn: conn} do
425 scheduled_at =
426 NaiveDateTime.utc_now()
427 |> NaiveDateTime.add(:timer.minutes(120), :millisecond)
428 |> NaiveDateTime.to_iso8601()
429 |> Kernel.<>("Z")
430
431 file = %Plug.Upload{
432 content_type: "image/jpeg",
433 path: Path.absname("test/fixtures/image.jpg"),
434 filename: "an_image.jpg"
435 }
436
437 {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
438
439 conn =
440 conn
441 |> put_req_header("content-type", "application/json")
442 |> post("/api/v1/statuses", %{
443 "media_ids" => [to_string(upload.id)],
444 "status" => "scheduled",
445 "scheduled_at" => scheduled_at
446 })
447
448 assert %{"media_attachments" => [media_attachment]} =
449 json_response_and_validate_schema(conn, 200)
450
451 assert %{"type" => "image"} = media_attachment
452 end
453
454 test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now",
455 %{conn: conn} do
456 scheduled_at =
457 NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond)
458 |> NaiveDateTime.to_iso8601()
459 |> Kernel.<>("Z")
460
461 conn =
462 conn
463 |> put_req_header("content-type", "application/json")
464 |> post("/api/v1/statuses", %{
465 "status" => "not scheduled",
466 "scheduled_at" => scheduled_at
467 })
468
469 assert %{"content" => "not scheduled"} = json_response_and_validate_schema(conn, 200)
470 assert [] == Repo.all(ScheduledActivity)
471 end
472
473 test "returns error when daily user limit is exceeded", %{user: user, conn: conn} do
474 today =
475 NaiveDateTime.utc_now()
476 |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
477 |> NaiveDateTime.to_iso8601()
478 # TODO
479 |> Kernel.<>("Z")
480
481 attrs = %{params: %{}, scheduled_at: today}
482 {:ok, _} = ScheduledActivity.create(user, attrs)
483 {:ok, _} = ScheduledActivity.create(user, attrs)
484
485 conn =
486 conn
487 |> put_req_header("content-type", "application/json")
488 |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today})
489
490 assert %{"error" => "daily limit exceeded"} == json_response_and_validate_schema(conn, 422)
491 end
492
493 test "returns error when total user limit is exceeded", %{user: user, conn: conn} do
494 today =
495 NaiveDateTime.utc_now()
496 |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
497 |> NaiveDateTime.to_iso8601()
498 |> Kernel.<>("Z")
499
500 tomorrow =
501 NaiveDateTime.utc_now()
502 |> NaiveDateTime.add(:timer.hours(36), :millisecond)
503 |> NaiveDateTime.to_iso8601()
504 |> Kernel.<>("Z")
505
506 attrs = %{params: %{}, scheduled_at: today}
507 {:ok, _} = ScheduledActivity.create(user, attrs)
508 {:ok, _} = ScheduledActivity.create(user, attrs)
509 {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
510
511 conn =
512 conn
513 |> put_req_header("content-type", "application/json")
514 |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow})
515
516 assert %{"error" => "total limit exceeded"} == json_response_and_validate_schema(conn, 422)
517 end
518 end
519
520 describe "posting polls" do
521 setup do: oauth_access(["write:statuses"])
522
523 test "posting a poll", %{conn: conn} do
524 time = NaiveDateTime.utc_now()
525
526 conn =
527 conn
528 |> put_req_header("content-type", "application/json")
529 |> post("/api/v1/statuses", %{
530 "status" => "Who is the #bestgrill?",
531 "poll" => %{
532 "options" => ["Rei", "Asuka", "Misato"],
533 "expires_in" => 420
534 }
535 })
536
537 response = json_response_and_validate_schema(conn, 200)
538
539 assert Enum.all?(response["poll"]["options"], fn %{"title" => title} ->
540 title in ["Rei", "Asuka", "Misato"]
541 end)
542
543 assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430
544 assert response["poll"]["expired"] == false
545
546 question = Object.get_by_id(response["poll"]["id"])
547
548 # closed contains utc timezone
549 assert question.data["closed"] =~ "Z"
550 end
551
552 test "option limit is enforced", %{conn: conn} do
553 limit = Config.get([:instance, :poll_limits, :max_options])
554
555 conn =
556 conn
557 |> put_req_header("content-type", "application/json")
558 |> post("/api/v1/statuses", %{
559 "status" => "desu~",
560 "poll" => %{"options" => Enum.map(0..limit, fn _ -> "desu" end), "expires_in" => 1}
561 })
562
563 %{"error" => error} = json_response_and_validate_schema(conn, 422)
564 assert error == "Poll can't contain more than #{limit} options"
565 end
566
567 test "option character limit is enforced", %{conn: conn} do
568 limit = Config.get([:instance, :poll_limits, :max_option_chars])
569
570 conn =
571 conn
572 |> put_req_header("content-type", "application/json")
573 |> post("/api/v1/statuses", %{
574 "status" => "...",
575 "poll" => %{
576 "options" => [Enum.reduce(0..limit, "", fn _, acc -> acc <> "." end)],
577 "expires_in" => 1
578 }
579 })
580
581 %{"error" => error} = json_response_and_validate_schema(conn, 422)
582 assert error == "Poll options cannot be longer than #{limit} characters each"
583 end
584
585 test "minimal date limit is enforced", %{conn: conn} do
586 limit = Config.get([:instance, :poll_limits, :min_expiration])
587
588 conn =
589 conn
590 |> put_req_header("content-type", "application/json")
591 |> post("/api/v1/statuses", %{
592 "status" => "imagine arbitrary limits",
593 "poll" => %{
594 "options" => ["this post was made by pleroma gang"],
595 "expires_in" => limit - 1
596 }
597 })
598
599 %{"error" => error} = json_response_and_validate_schema(conn, 422)
600 assert error == "Expiration date is too soon"
601 end
602
603 test "maximum date limit is enforced", %{conn: conn} do
604 limit = Config.get([:instance, :poll_limits, :max_expiration])
605
606 conn =
607 conn
608 |> put_req_header("content-type", "application/json")
609 |> post("/api/v1/statuses", %{
610 "status" => "imagine arbitrary limits",
611 "poll" => %{
612 "options" => ["this post was made by pleroma gang"],
613 "expires_in" => limit + 1
614 }
615 })
616
617 %{"error" => error} = json_response_and_validate_schema(conn, 422)
618 assert error == "Expiration date is too far in the future"
619 end
620
621 test "scheduled poll", %{conn: conn} do
622 clear_config([ScheduledActivity, :enabled], true)
623
624 scheduled_at =
625 NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(6), :millisecond)
626 |> NaiveDateTime.to_iso8601()
627 |> Kernel.<>("Z")
628
629 %{"id" => scheduled_id} =
630 conn
631 |> put_req_header("content-type", "application/json")
632 |> post("/api/v1/statuses", %{
633 "status" => "very cool poll",
634 "poll" => %{
635 "options" => ~w(a b c),
636 "expires_in" => 420
637 },
638 "scheduled_at" => scheduled_at
639 })
640 |> json_response_and_validate_schema(200)
641
642 assert {:ok, %{id: activity_id}} =
643 perform_job(Pleroma.Workers.ScheduledActivityWorker, %{
644 activity_id: scheduled_id
645 })
646
647 assert Repo.all(Oban.Job) == []
648
649 object =
650 Activity
651 |> Repo.get(activity_id)
652 |> Object.normalize()
653
654 assert object.data["content"] == "very cool poll"
655 assert object.data["type"] == "Question"
656 assert length(object.data["oneOf"]) == 3
657 end
658 end
659
660 test "get a status" do
661 %{conn: conn} = oauth_access(["read:statuses"])
662 activity = insert(:note_activity)
663
664 conn = get(conn, "/api/v1/statuses/#{activity.id}")
665
666 assert %{"id" => id} = json_response_and_validate_schema(conn, 200)
667 assert id == to_string(activity.id)
668 end
669
670 defp local_and_remote_activities do
671 local = insert(:note_activity)
672 remote = insert(:note_activity, local: false)
673 {:ok, local: local, remote: remote}
674 end
675
676 describe "status with restrict unauthenticated activities for local and remote" do
677 setup do: local_and_remote_activities()
678
679 setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
680
681 setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
682
683 test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
684 res_conn = get(conn, "/api/v1/statuses/#{local.id}")
685
686 assert json_response_and_validate_schema(res_conn, :not_found) == %{
687 "error" => "Record not found"
688 }
689
690 res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
691
692 assert json_response_and_validate_schema(res_conn, :not_found) == %{
693 "error" => "Record not found"
694 }
695 end
696
697 test "if user is authenticated", %{local: local, remote: remote} do
698 %{conn: conn} = oauth_access(["read"])
699 res_conn = get(conn, "/api/v1/statuses/#{local.id}")
700 assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
701
702 res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
703 assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
704 end
705 end
706
707 describe "status with restrict unauthenticated activities for local" do
708 setup do: local_and_remote_activities()
709
710 setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
711
712 test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
713 res_conn = get(conn, "/api/v1/statuses/#{local.id}")
714
715 assert json_response_and_validate_schema(res_conn, :not_found) == %{
716 "error" => "Record not found"
717 }
718
719 res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
720 assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
721 end
722
723 test "if user is authenticated", %{local: local, remote: remote} do
724 %{conn: conn} = oauth_access(["read"])
725 res_conn = get(conn, "/api/v1/statuses/#{local.id}")
726 assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
727
728 res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
729 assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
730 end
731 end
732
733 describe "status with restrict unauthenticated activities for remote" do
734 setup do: local_and_remote_activities()
735
736 setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
737
738 test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
739 res_conn = get(conn, "/api/v1/statuses/#{local.id}")
740 assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
741
742 res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
743
744 assert json_response_and_validate_schema(res_conn, :not_found) == %{
745 "error" => "Record not found"
746 }
747 end
748
749 test "if user is authenticated", %{local: local, remote: remote} do
750 %{conn: conn} = oauth_access(["read"])
751 res_conn = get(conn, "/api/v1/statuses/#{local.id}")
752 assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
753
754 res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
755 assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
756 end
757 end
758
759 test "getting a status that doesn't exist returns 404" do
760 %{conn: conn} = oauth_access(["read:statuses"])
761 activity = insert(:note_activity)
762
763 conn = get(conn, "/api/v1/statuses/#{String.downcase(activity.id)}")
764
765 assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
766 end
767
768 test "get a direct status" do
769 %{user: user, conn: conn} = oauth_access(["read:statuses"])
770 other_user = insert(:user)
771
772 {:ok, activity} =
773 CommonAPI.post(user, %{status: "@#{other_user.nickname}", visibility: "direct"})
774
775 conn =
776 conn
777 |> assign(:user, user)
778 |> get("/api/v1/statuses/#{activity.id}")
779
780 [participation] = Participation.for_user(user)
781
782 res = json_response_and_validate_schema(conn, 200)
783 assert res["pleroma"]["direct_conversation_id"] == participation.id
784 end
785
786 test "get statuses by IDs" do
787 %{conn: conn} = oauth_access(["read:statuses"])
788 %{id: id1} = insert(:note_activity)
789 %{id: id2} = insert(:note_activity)
790
791 query_string = "ids[]=#{id1}&ids[]=#{id2}"
792 conn = get(conn, "/api/v1/statuses/?#{query_string}")
793
794 assert [%{"id" => ^id1}, %{"id" => ^id2}] =
795 Enum.sort_by(json_response_and_validate_schema(conn, :ok), & &1["id"])
796 end
797
798 describe "getting statuses by ids with restricted unauthenticated for local and remote" do
799 setup do: local_and_remote_activities()
800
801 setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
802
803 setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
804
805 test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
806 res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
807
808 assert json_response_and_validate_schema(res_conn, 200) == []
809 end
810
811 test "if user is authenticated", %{local: local, remote: remote} do
812 %{conn: conn} = oauth_access(["read"])
813
814 res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
815
816 assert length(json_response_and_validate_schema(res_conn, 200)) == 2
817 end
818 end
819
820 describe "getting statuses by ids with restricted unauthenticated for local" do
821 setup do: local_and_remote_activities()
822
823 setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
824
825 test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
826 res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
827
828 remote_id = remote.id
829 assert [%{"id" => ^remote_id}] = json_response_and_validate_schema(res_conn, 200)
830 end
831
832 test "if user is authenticated", %{local: local, remote: remote} do
833 %{conn: conn} = oauth_access(["read"])
834
835 res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
836
837 assert length(json_response_and_validate_schema(res_conn, 200)) == 2
838 end
839 end
840
841 describe "getting statuses by ids with restricted unauthenticated for remote" do
842 setup do: local_and_remote_activities()
843
844 setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
845
846 test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
847 res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
848
849 local_id = local.id
850 assert [%{"id" => ^local_id}] = json_response_and_validate_schema(res_conn, 200)
851 end
852
853 test "if user is authenticated", %{local: local, remote: remote} do
854 %{conn: conn} = oauth_access(["read"])
855
856 res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
857
858 assert length(json_response_and_validate_schema(res_conn, 200)) == 2
859 end
860 end
861
862 describe "deleting a status" do
863 test "when you created it" do
864 %{user: author, conn: conn} = oauth_access(["write:statuses"])
865 activity = insert(:note_activity, user: author)
866 object = Object.normalize(activity, fetch: false)
867
868 content = object.data["content"]
869 source = object.data["source"]
870
871 result =
872 conn
873 |> assign(:user, author)
874 |> delete("/api/v1/statuses/#{activity.id}")
875 |> json_response_and_validate_schema(200)
876
877 assert match?(%{"content" => ^content, "text" => ^source}, result)
878
879 refute Activity.get_by_id(activity.id)
880 end
881
882 test "when it doesn't exist" do
883 %{user: author, conn: conn} = oauth_access(["write:statuses"])
884 activity = insert(:note_activity, user: author)
885
886 conn =
887 conn
888 |> assign(:user, author)
889 |> delete("/api/v1/statuses/#{String.downcase(activity.id)}")
890
891 assert %{"error" => "Record not found"} == json_response_and_validate_schema(conn, 404)
892 end
893
894 test "when you didn't create it" do
895 %{conn: conn} = oauth_access(["write:statuses"])
896 activity = insert(:note_activity)
897
898 conn = delete(conn, "/api/v1/statuses/#{activity.id}")
899
900 assert %{"error" => "Record not found"} == json_response_and_validate_schema(conn, 404)
901
902 assert Activity.get_by_id(activity.id) == activity
903 end
904
905 test "when you're an admin or moderator", %{conn: conn} do
906 activity1 = insert(:note_activity)
907 activity2 = insert(:note_activity)
908 admin = insert(:user, is_admin: true)
909 moderator = insert(:user, is_moderator: true)
910
911 res_conn =
912 conn
913 |> assign(:user, admin)
914 |> assign(:token, insert(:oauth_token, user: admin, scopes: ["write:statuses"]))
915 |> delete("/api/v1/statuses/#{activity1.id}")
916
917 assert %{} = json_response_and_validate_schema(res_conn, 200)
918
919 res_conn =
920 conn
921 |> assign(:user, moderator)
922 |> assign(:token, insert(:oauth_token, user: moderator, scopes: ["write:statuses"]))
923 |> delete("/api/v1/statuses/#{activity2.id}")
924
925 assert %{} = json_response_and_validate_schema(res_conn, 200)
926
927 refute Activity.get_by_id(activity1.id)
928 refute Activity.get_by_id(activity2.id)
929 end
930 end
931
932 describe "reblogging" do
933 setup do: oauth_access(["write:statuses"])
934
935 test "reblogs and returns the reblogged status", %{conn: conn} do
936 activity = insert(:note_activity)
937
938 conn =
939 conn
940 |> put_req_header("content-type", "application/json")
941 |> post("/api/v1/statuses/#{activity.id}/reblog")
942
943 assert %{
944 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
945 "reblogged" => true
946 } = json_response_and_validate_schema(conn, 200)
947
948 assert to_string(activity.id) == id
949 end
950
951 test "returns 404 if the reblogged status doesn't exist", %{conn: conn} do
952 activity = insert(:note_activity)
953
954 conn =
955 conn
956 |> put_req_header("content-type", "application/json")
957 |> post("/api/v1/statuses/#{String.downcase(activity.id)}/reblog")
958
959 assert %{"error" => "Record not found"} = json_response_and_validate_schema(conn, 404)
960 end
961
962 test "reblogs privately and returns the reblogged status", %{conn: conn} do
963 activity = insert(:note_activity)
964
965 conn =
966 conn
967 |> put_req_header("content-type", "application/json")
968 |> post(
969 "/api/v1/statuses/#{activity.id}/reblog",
970 %{"visibility" => "private"}
971 )
972
973 assert %{
974 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
975 "reblogged" => true,
976 "visibility" => "private"
977 } = json_response_and_validate_schema(conn, 200)
978
979 assert to_string(activity.id) == id
980 end
981
982 test "reblogged status for another user" do
983 activity = insert(:note_activity)
984 user1 = insert(:user)
985 user2 = insert(:user)
986 user3 = insert(:user)
987 {:ok, _} = CommonAPI.favorite(user2, activity.id)
988 {:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id)
989 {:ok, reblog_activity1} = CommonAPI.repeat(activity.id, user1)
990 {:ok, _} = CommonAPI.repeat(activity.id, user2)
991
992 conn_res =
993 build_conn()
994 |> assign(:user, user3)
995 |> assign(:token, insert(:oauth_token, user: user3, scopes: ["read:statuses"]))
996 |> get("/api/v1/statuses/#{reblog_activity1.id}")
997
998 assert %{
999 "reblog" => %{"id" => _id, "reblogged" => false, "reblogs_count" => 2},
1000 "reblogged" => false,
1001 "favourited" => false,
1002 "bookmarked" => false
1003 } = json_response_and_validate_schema(conn_res, 200)
1004
1005 conn_res =
1006 build_conn()
1007 |> assign(:user, user2)
1008 |> assign(:token, insert(:oauth_token, user: user2, scopes: ["read:statuses"]))
1009 |> get("/api/v1/statuses/#{reblog_activity1.id}")
1010
1011 assert %{
1012 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 2},
1013 "reblogged" => true,
1014 "favourited" => true,
1015 "bookmarked" => true
1016 } = json_response_and_validate_schema(conn_res, 200)
1017
1018 assert to_string(activity.id) == id
1019 end
1020
1021 test "author can reblog own private status", %{conn: conn, user: user} do
1022 {:ok, activity} = CommonAPI.post(user, %{status: "cofe", visibility: "private"})
1023
1024 conn =
1025 conn
1026 |> put_req_header("content-type", "application/json")
1027 |> post("/api/v1/statuses/#{activity.id}/reblog")
1028
1029 assert %{
1030 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
1031 "reblogged" => true,
1032 "visibility" => "private"
1033 } = json_response_and_validate_schema(conn, 200)
1034
1035 assert to_string(activity.id) == id
1036 end
1037 end
1038
1039 describe "unreblogging" do
1040 setup do: oauth_access(["write:statuses"])
1041
1042 test "unreblogs and returns the unreblogged status", %{user: user, conn: conn} do
1043 activity = insert(:note_activity)
1044
1045 {:ok, _} = CommonAPI.repeat(activity.id, user)
1046
1047 conn =
1048 conn
1049 |> put_req_header("content-type", "application/json")
1050 |> post("/api/v1/statuses/#{activity.id}/unreblog")
1051
1052 assert %{"id" => id, "reblogged" => false, "reblogs_count" => 0} =
1053 json_response_and_validate_schema(conn, 200)
1054
1055 assert to_string(activity.id) == id
1056 end
1057
1058 test "returns 404 error when activity does not exist", %{conn: conn} do
1059 conn =
1060 conn
1061 |> put_req_header("content-type", "application/json")
1062 |> post("/api/v1/statuses/foo/unreblog")
1063
1064 assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
1065 end
1066 end
1067
1068 describe "favoriting" do
1069 setup do: oauth_access(["write:favourites"])
1070
1071 test "favs a status and returns it", %{conn: conn} do
1072 activity = insert(:note_activity)
1073
1074 conn =
1075 conn
1076 |> put_req_header("content-type", "application/json")
1077 |> post("/api/v1/statuses/#{activity.id}/favourite")
1078
1079 assert %{"id" => id, "favourites_count" => 1, "favourited" => true} =
1080 json_response_and_validate_schema(conn, 200)
1081
1082 assert to_string(activity.id) == id
1083 end
1084
1085 test "favoriting twice will just return 200", %{conn: conn} do
1086 activity = insert(:note_activity)
1087
1088 conn
1089 |> put_req_header("content-type", "application/json")
1090 |> post("/api/v1/statuses/#{activity.id}/favourite")
1091
1092 assert conn
1093 |> put_req_header("content-type", "application/json")
1094 |> post("/api/v1/statuses/#{activity.id}/favourite")
1095 |> json_response_and_validate_schema(200)
1096 end
1097
1098 test "returns 404 error for a wrong id", %{conn: conn} do
1099 conn =
1100 conn
1101 |> put_req_header("content-type", "application/json")
1102 |> post("/api/v1/statuses/1/favourite")
1103
1104 assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
1105 end
1106 end
1107
1108 describe "unfavoriting" do
1109 setup do: oauth_access(["write:favourites"])
1110
1111 test "unfavorites a status and returns it", %{user: user, conn: conn} do
1112 activity = insert(:note_activity)
1113
1114 {:ok, _} = CommonAPI.favorite(user, activity.id)
1115
1116 conn =
1117 conn
1118 |> put_req_header("content-type", "application/json")
1119 |> post("/api/v1/statuses/#{activity.id}/unfavourite")
1120
1121 assert %{"id" => id, "favourites_count" => 0, "favourited" => false} =
1122 json_response_and_validate_schema(conn, 200)
1123
1124 assert to_string(activity.id) == id
1125 end
1126
1127 test "returns 404 error for a wrong id", %{conn: conn} do
1128 conn =
1129 conn
1130 |> put_req_header("content-type", "application/json")
1131 |> post("/api/v1/statuses/1/unfavourite")
1132
1133 assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
1134 end
1135 end
1136
1137 describe "pinned statuses" do
1138 setup do: oauth_access(["write:accounts"])
1139
1140 setup %{user: user} do
1141 {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!"})
1142
1143 %{activity: activity}
1144 end
1145
1146 setup do: clear_config([:instance, :max_pinned_statuses], 1)
1147
1148 test "pin status", %{conn: conn, user: user, activity: activity} do
1149 id_str = to_string(activity.id)
1150
1151 assert %{"id" => ^id_str, "pinned" => true} =
1152 conn
1153 |> put_req_header("content-type", "application/json")
1154 |> post("/api/v1/statuses/#{activity.id}/pin")
1155 |> json_response_and_validate_schema(200)
1156
1157 assert [%{"id" => ^id_str, "pinned" => true}] =
1158 conn
1159 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
1160 |> json_response_and_validate_schema(200)
1161 end
1162
1163 test "/pin: returns 400 error when activity is not public", %{conn: conn, user: user} do
1164 {:ok, dm} = CommonAPI.post(user, %{status: "test", visibility: "direct"})
1165
1166 conn =
1167 conn
1168 |> put_req_header("content-type", "application/json")
1169 |> post("/api/v1/statuses/#{dm.id}/pin")
1170
1171 assert json_response_and_validate_schema(conn, 400) == %{"error" => "Could not pin"}
1172 end
1173
1174 test "unpin status", %{conn: conn, user: user, activity: activity} do
1175 {:ok, _} = CommonAPI.pin(activity.id, user)
1176 user = refresh_record(user)
1177
1178 id_str = to_string(activity.id)
1179
1180 assert %{"id" => ^id_str, "pinned" => false} =
1181 conn
1182 |> assign(:user, user)
1183 |> post("/api/v1/statuses/#{activity.id}/unpin")
1184 |> json_response_and_validate_schema(200)
1185
1186 assert [] =
1187 conn
1188 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
1189 |> json_response_and_validate_schema(200)
1190 end
1191
1192 test "/unpin: returns 400 error when activity is not exist", %{conn: conn} do
1193 conn =
1194 conn
1195 |> put_req_header("content-type", "application/json")
1196 |> post("/api/v1/statuses/1/unpin")
1197
1198 assert json_response_and_validate_schema(conn, 400) == %{"error" => "Could not unpin"}
1199 end
1200
1201 test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
1202 {:ok, activity_two} = CommonAPI.post(user, %{status: "HI!!!"})
1203
1204 id_str_one = to_string(activity_one.id)
1205
1206 assert %{"id" => ^id_str_one, "pinned" => true} =
1207 conn
1208 |> put_req_header("content-type", "application/json")
1209 |> post("/api/v1/statuses/#{id_str_one}/pin")
1210 |> json_response_and_validate_schema(200)
1211
1212 user = refresh_record(user)
1213
1214 assert %{"error" => "You have already pinned the maximum number of statuses"} =
1215 conn
1216 |> assign(:user, user)
1217 |> post("/api/v1/statuses/#{activity_two.id}/pin")
1218 |> json_response_and_validate_schema(400)
1219 end
1220
1221 test "on pin removes deletion job, on unpin reschedule deletion" do
1222 %{conn: conn} = oauth_access(["write:accounts", "write:statuses"])
1223 expires_in = 2 * 60 * 60
1224
1225 expires_at = DateTime.add(DateTime.utc_now(), expires_in)
1226
1227 assert %{"id" => id} =
1228 conn
1229 |> put_req_header("content-type", "application/json")
1230 |> post("api/v1/statuses", %{
1231 "status" => "oolong",
1232 "expires_in" => expires_in
1233 })
1234 |> json_response_and_validate_schema(200)
1235
1236 assert_enqueued(
1237 worker: Pleroma.Workers.PurgeExpiredActivity,
1238 args: %{activity_id: id},
1239 scheduled_at: expires_at
1240 )
1241
1242 assert %{"id" => ^id, "pinned" => true} =
1243 conn
1244 |> put_req_header("content-type", "application/json")
1245 |> post("/api/v1/statuses/#{id}/pin")
1246 |> json_response_and_validate_schema(200)
1247
1248 refute_enqueued(
1249 worker: Pleroma.Workers.PurgeExpiredActivity,
1250 args: %{activity_id: id},
1251 scheduled_at: expires_at
1252 )
1253
1254 assert %{"id" => ^id, "pinned" => false} =
1255 conn
1256 |> put_req_header("content-type", "application/json")
1257 |> post("/api/v1/statuses/#{id}/unpin")
1258 |> json_response_and_validate_schema(200)
1259
1260 assert_enqueued(
1261 worker: Pleroma.Workers.PurgeExpiredActivity,
1262 args: %{activity_id: id},
1263 scheduled_at: expires_at
1264 )
1265 end
1266 end
1267
1268 describe "cards" do
1269 setup do
1270 clear_config([:rich_media, :enabled], true)
1271
1272 oauth_access(["read:statuses"])
1273 end
1274
1275 test "returns rich-media card", %{conn: conn, user: user} do
1276 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
1277
1278 {:ok, activity} = CommonAPI.post(user, %{status: "https://example.com/ogp"})
1279
1280 card_data = %{
1281 "image" => "http://ia.media-imdb.com/images/rock.jpg",
1282 "provider_name" => "example.com",
1283 "provider_url" => "https://example.com",
1284 "title" => "The Rock",
1285 "type" => "link",
1286 "url" => "https://example.com/ogp",
1287 "description" =>
1288 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
1289 "pleroma" => %{
1290 "opengraph" => %{
1291 "image" => "http://ia.media-imdb.com/images/rock.jpg",
1292 "title" => "The Rock",
1293 "type" => "video.movie",
1294 "url" => "https://example.com/ogp",
1295 "description" =>
1296 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer."
1297 }
1298 }
1299 }
1300
1301 response =
1302 conn
1303 |> get("/api/v1/statuses/#{activity.id}/card")
1304 |> json_response_and_validate_schema(200)
1305
1306 assert response == card_data
1307
1308 # works with private posts
1309 {:ok, activity} =
1310 CommonAPI.post(user, %{status: "https://example.com/ogp", visibility: "direct"})
1311
1312 response_two =
1313 conn
1314 |> get("/api/v1/statuses/#{activity.id}/card")
1315 |> json_response_and_validate_schema(200)
1316
1317 assert response_two == card_data
1318 end
1319
1320 test "replaces missing description with an empty string", %{conn: conn, user: user} do
1321 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
1322
1323 {:ok, activity} = CommonAPI.post(user, %{status: "https://example.com/ogp-missing-data"})
1324
1325 response =
1326 conn
1327 |> get("/api/v1/statuses/#{activity.id}/card")
1328 |> json_response_and_validate_schema(:ok)
1329
1330 assert response == %{
1331 "type" => "link",
1332 "title" => "Pleroma",
1333 "description" => "",
1334 "image" => nil,
1335 "provider_name" => "example.com",
1336 "provider_url" => "https://example.com",
1337 "url" => "https://example.com/ogp-missing-data",
1338 "pleroma" => %{
1339 "opengraph" => %{
1340 "title" => "Pleroma",
1341 "type" => "website",
1342 "url" => "https://example.com/ogp-missing-data"
1343 }
1344 }
1345 }
1346 end
1347 end
1348
1349 test "bookmarks" do
1350 bookmarks_uri = "/api/v1/bookmarks"
1351
1352 %{conn: conn} = oauth_access(["write:bookmarks", "read:bookmarks"])
1353 author = insert(:user)
1354
1355 {:ok, activity1} = CommonAPI.post(author, %{status: "heweoo?"})
1356 {:ok, activity2} = CommonAPI.post(author, %{status: "heweoo!"})
1357
1358 response1 =
1359 conn
1360 |> put_req_header("content-type", "application/json")
1361 |> post("/api/v1/statuses/#{activity1.id}/bookmark")
1362
1363 assert json_response_and_validate_schema(response1, 200)["bookmarked"] == true
1364
1365 response2 =
1366 conn
1367 |> put_req_header("content-type", "application/json")
1368 |> post("/api/v1/statuses/#{activity2.id}/bookmark")
1369
1370 assert json_response_and_validate_schema(response2, 200)["bookmarked"] == true
1371
1372 bookmarks = get(conn, bookmarks_uri)
1373
1374 assert [
1375 json_response_and_validate_schema(response2, 200),
1376 json_response_and_validate_schema(response1, 200)
1377 ] ==
1378 json_response_and_validate_schema(bookmarks, 200)
1379
1380 response1 =
1381 conn
1382 |> put_req_header("content-type", "application/json")
1383 |> post("/api/v1/statuses/#{activity1.id}/unbookmark")
1384
1385 assert json_response_and_validate_schema(response1, 200)["bookmarked"] == false
1386
1387 bookmarks = get(conn, bookmarks_uri)
1388
1389 assert [json_response_and_validate_schema(response2, 200)] ==
1390 json_response_and_validate_schema(bookmarks, 200)
1391 end
1392
1393 describe "conversation muting" do
1394 setup do: oauth_access(["write:mutes"])
1395
1396 setup do
1397 post_user = insert(:user)
1398 {:ok, activity} = CommonAPI.post(post_user, %{status: "HIE"})
1399 %{activity: activity}
1400 end
1401
1402 test "mute conversation", %{conn: conn, activity: activity} do
1403 id_str = to_string(activity.id)
1404
1405 assert %{"id" => ^id_str, "muted" => true} =
1406 conn
1407 |> put_req_header("content-type", "application/json")
1408 |> post("/api/v1/statuses/#{activity.id}/mute")
1409 |> json_response_and_validate_schema(200)
1410 end
1411
1412 test "cannot mute already muted conversation", %{conn: conn, user: user, activity: activity} do
1413 {:ok, _} = CommonAPI.add_mute(user, activity)
1414
1415 conn =
1416 conn
1417 |> put_req_header("content-type", "application/json")
1418 |> post("/api/v1/statuses/#{activity.id}/mute")
1419
1420 assert json_response_and_validate_schema(conn, 400) == %{
1421 "error" => "conversation is already muted"
1422 }
1423 end
1424
1425 test "unmute conversation", %{conn: conn, user: user, activity: activity} do
1426 {:ok, _} = CommonAPI.add_mute(user, activity)
1427
1428 id_str = to_string(activity.id)
1429
1430 assert %{"id" => ^id_str, "muted" => false} =
1431 conn
1432 # |> assign(:user, user)
1433 |> post("/api/v1/statuses/#{activity.id}/unmute")
1434 |> json_response_and_validate_schema(200)
1435 end
1436 end
1437
1438 test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do
1439 user1 = insert(:user)
1440 user2 = insert(:user)
1441 user3 = insert(:user)
1442
1443 {:ok, replied_to} = CommonAPI.post(user1, %{status: "cofe"})
1444
1445 # Reply to status from another user
1446 conn1 =
1447 conn
1448 |> assign(:user, user2)
1449 |> assign(:token, insert(:oauth_token, user: user2, scopes: ["write:statuses"]))
1450 |> put_req_header("content-type", "application/json")
1451 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
1452
1453 assert %{"content" => "xD", "id" => id} = json_response_and_validate_schema(conn1, 200)
1454
1455 activity = Activity.get_by_id_with_object(id)
1456
1457 assert Object.normalize(activity, fetch: false).data["inReplyTo"] ==
1458 Object.normalize(replied_to, fetch: false).data["id"]
1459
1460 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
1461
1462 # Reblog from the third user
1463 conn2 =
1464 conn
1465 |> assign(:user, user3)
1466 |> assign(:token, insert(:oauth_token, user: user3, scopes: ["write:statuses"]))
1467 |> put_req_header("content-type", "application/json")
1468 |> post("/api/v1/statuses/#{activity.id}/reblog")
1469
1470 assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} =
1471 json_response_and_validate_schema(conn2, 200)
1472
1473 assert to_string(activity.id) == id
1474
1475 # Getting third user status
1476 conn3 =
1477 conn
1478 |> assign(:user, user3)
1479 |> assign(:token, insert(:oauth_token, user: user3, scopes: ["read:statuses"]))
1480 |> get("api/v1/timelines/home")
1481
1482 [reblogged_activity] = json_response(conn3, 200)
1483
1484 assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id
1485
1486 replied_to_user = User.get_by_ap_id(replied_to.data["actor"])
1487 assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id
1488 end
1489
1490 describe "GET /api/v1/statuses/:id/favourited_by" do
1491 setup do: oauth_access(["read:accounts"])
1492
1493 setup %{user: user} do
1494 {:ok, activity} = CommonAPI.post(user, %{status: "test"})
1495
1496 %{activity: activity}
1497 end
1498
1499 test "returns users who have favorited the status", %{conn: conn, activity: activity} do
1500 other_user = insert(:user)
1501 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1502
1503 response =
1504 conn
1505 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1506 |> json_response_and_validate_schema(:ok)
1507
1508 [%{"id" => id}] = response
1509
1510 assert id == other_user.id
1511 end
1512
1513 test "returns empty array when status has not been favorited yet", %{
1514 conn: conn,
1515 activity: activity
1516 } do
1517 response =
1518 conn
1519 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1520 |> json_response_and_validate_schema(:ok)
1521
1522 assert Enum.empty?(response)
1523 end
1524
1525 test "does not return users who have favorited the status but are blocked", %{
1526 conn: %{assigns: %{user: user}} = conn,
1527 activity: activity
1528 } do
1529 other_user = insert(:user)
1530 {:ok, _user_relationship} = User.block(user, other_user)
1531
1532 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1533
1534 response =
1535 conn
1536 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1537 |> json_response_and_validate_schema(:ok)
1538
1539 assert Enum.empty?(response)
1540 end
1541
1542 test "does not fail on an unauthenticated request", %{activity: activity} do
1543 other_user = insert(:user)
1544 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1545
1546 response =
1547 build_conn()
1548 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1549 |> json_response_and_validate_schema(:ok)
1550
1551 [%{"id" => id}] = response
1552 assert id == other_user.id
1553 end
1554
1555 test "requires authentication for private posts", %{user: user} do
1556 other_user = insert(:user)
1557
1558 {:ok, activity} =
1559 CommonAPI.post(user, %{
1560 status: "@#{other_user.nickname} wanna get some #cofe together?",
1561 visibility: "direct"
1562 })
1563
1564 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1565
1566 favourited_by_url = "/api/v1/statuses/#{activity.id}/favourited_by"
1567
1568 build_conn()
1569 |> get(favourited_by_url)
1570 |> json_response_and_validate_schema(404)
1571
1572 conn =
1573 build_conn()
1574 |> assign(:user, other_user)
1575 |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"]))
1576
1577 conn
1578 |> assign(:token, nil)
1579 |> get(favourited_by_url)
1580 |> json_response_and_validate_schema(404)
1581
1582 response =
1583 conn
1584 |> get(favourited_by_url)
1585 |> json_response_and_validate_schema(200)
1586
1587 [%{"id" => id}] = response
1588 assert id == other_user.id
1589 end
1590
1591 test "returns empty array when :show_reactions is disabled", %{conn: conn, activity: activity} do
1592 clear_config([:instance, :show_reactions], false)
1593
1594 other_user = insert(:user)
1595 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1596
1597 response =
1598 conn
1599 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1600 |> json_response_and_validate_schema(:ok)
1601
1602 assert Enum.empty?(response)
1603 end
1604 end
1605
1606 describe "GET /api/v1/statuses/:id/reblogged_by" do
1607 setup do: oauth_access(["read:accounts"])
1608
1609 setup %{user: user} do
1610 {:ok, activity} = CommonAPI.post(user, %{status: "test"})
1611
1612 %{activity: activity}
1613 end
1614
1615 test "returns users who have reblogged the status", %{conn: conn, activity: activity} do
1616 other_user = insert(:user)
1617 {:ok, _} = CommonAPI.repeat(activity.id, other_user)
1618
1619 response =
1620 conn
1621 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1622 |> json_response_and_validate_schema(:ok)
1623
1624 [%{"id" => id}] = response
1625
1626 assert id == other_user.id
1627 end
1628
1629 test "returns empty array when status has not been reblogged yet", %{
1630 conn: conn,
1631 activity: activity
1632 } do
1633 response =
1634 conn
1635 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1636 |> json_response_and_validate_schema(:ok)
1637
1638 assert Enum.empty?(response)
1639 end
1640
1641 test "does not return users who have reblogged the status but are blocked", %{
1642 conn: %{assigns: %{user: user}} = conn,
1643 activity: activity
1644 } do
1645 other_user = insert(:user)
1646 {:ok, _user_relationship} = User.block(user, other_user)
1647
1648 {:ok, _} = CommonAPI.repeat(activity.id, other_user)
1649
1650 response =
1651 conn
1652 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1653 |> json_response_and_validate_schema(:ok)
1654
1655 assert Enum.empty?(response)
1656 end
1657
1658 test "does not return users who have reblogged the status privately", %{
1659 conn: conn
1660 } do
1661 other_user = insert(:user)
1662 {:ok, activity} = CommonAPI.post(other_user, %{status: "my secret post"})
1663
1664 {:ok, _} = CommonAPI.repeat(activity.id, other_user, %{visibility: "private"})
1665
1666 response =
1667 conn
1668 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1669 |> json_response_and_validate_schema(:ok)
1670
1671 assert Enum.empty?(response)
1672 end
1673
1674 test "does not fail on an unauthenticated request", %{activity: activity} do
1675 other_user = insert(:user)
1676 {:ok, _} = CommonAPI.repeat(activity.id, other_user)
1677
1678 response =
1679 build_conn()
1680 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1681 |> json_response_and_validate_schema(:ok)
1682
1683 [%{"id" => id}] = response
1684 assert id == other_user.id
1685 end
1686
1687 test "requires authentication for private posts", %{user: user} do
1688 other_user = insert(:user)
1689
1690 {:ok, activity} =
1691 CommonAPI.post(user, %{
1692 status: "@#{other_user.nickname} wanna get some #cofe together?",
1693 visibility: "direct"
1694 })
1695
1696 build_conn()
1697 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1698 |> json_response_and_validate_schema(404)
1699
1700 response =
1701 build_conn()
1702 |> assign(:user, other_user)
1703 |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"]))
1704 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1705 |> json_response_and_validate_schema(200)
1706
1707 assert [] == response
1708 end
1709 end
1710
1711 test "context" do
1712 user = insert(:user)
1713
1714 {:ok, %{id: id1}} = CommonAPI.post(user, %{status: "1"})
1715 {:ok, %{id: id2}} = CommonAPI.post(user, %{status: "2", in_reply_to_status_id: id1})
1716 {:ok, %{id: id3}} = CommonAPI.post(user, %{status: "3", in_reply_to_status_id: id2})
1717 {:ok, %{id: id4}} = CommonAPI.post(user, %{status: "4", in_reply_to_status_id: id3})
1718 {:ok, %{id: id5}} = CommonAPI.post(user, %{status: "5", in_reply_to_status_id: id4})
1719
1720 response =
1721 build_conn()
1722 |> get("/api/v1/statuses/#{id3}/context")
1723 |> json_response_and_validate_schema(:ok)
1724
1725 assert %{
1726 "ancestors" => [%{"id" => ^id1}, %{"id" => ^id2}],
1727 "descendants" => [%{"id" => ^id4}, %{"id" => ^id5}]
1728 } = response
1729 end
1730
1731 test "favorites paginate correctly" do
1732 %{user: user, conn: conn} = oauth_access(["read:favourites"])
1733 other_user = insert(:user)
1734 {:ok, first_post} = CommonAPI.post(other_user, %{status: "bla"})
1735 {:ok, second_post} = CommonAPI.post(other_user, %{status: "bla"})
1736 {:ok, third_post} = CommonAPI.post(other_user, %{status: "bla"})
1737
1738 {:ok, _first_favorite} = CommonAPI.favorite(user, third_post.id)
1739 {:ok, _second_favorite} = CommonAPI.favorite(user, first_post.id)
1740 {:ok, third_favorite} = CommonAPI.favorite(user, second_post.id)
1741
1742 result =
1743 conn
1744 |> get("/api/v1/favourites?limit=1")
1745
1746 assert [%{"id" => post_id}] = json_response_and_validate_schema(result, 200)
1747 assert post_id == second_post.id
1748
1749 # Using the header for pagination works correctly
1750 [next, _] = get_resp_header(result, "link") |> hd() |> String.split(", ")
1751 [_, max_id] = Regex.run(~r/max_id=([^&]+)/, next)
1752
1753 assert max_id == third_favorite.id
1754
1755 result =
1756 conn
1757 |> get("/api/v1/favourites?max_id=#{max_id}")
1758
1759 assert [%{"id" => first_post_id}, %{"id" => third_post_id}] =
1760 json_response_and_validate_schema(result, 200)
1761
1762 assert first_post_id == first_post.id
1763 assert third_post_id == third_post.id
1764 end
1765
1766 test "returns the favorites of a user" do
1767 %{user: user, conn: conn} = oauth_access(["read:favourites"])
1768 other_user = insert(:user)
1769
1770 {:ok, _} = CommonAPI.post(other_user, %{status: "bla"})
1771 {:ok, activity} = CommonAPI.post(other_user, %{status: "trees are happy"})
1772
1773 {:ok, last_like} = CommonAPI.favorite(user, activity.id)
1774
1775 first_conn = get(conn, "/api/v1/favourites")
1776
1777 assert [status] = json_response_and_validate_schema(first_conn, 200)
1778 assert status["id"] == to_string(activity.id)
1779
1780 assert [{"link", _link_header}] =
1781 Enum.filter(first_conn.resp_headers, fn element -> match?({"link", _}, element) end)
1782
1783 # Honours query params
1784 {:ok, second_activity} =
1785 CommonAPI.post(other_user, %{
1786 status: "Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful."
1787 })
1788
1789 {:ok, _} = CommonAPI.favorite(user, second_activity.id)
1790
1791 second_conn = get(conn, "/api/v1/favourites?since_id=#{last_like.id}")
1792
1793 assert [second_status] = json_response_and_validate_schema(second_conn, 200)
1794 assert second_status["id"] == to_string(second_activity.id)
1795
1796 third_conn = get(conn, "/api/v1/favourites?limit=0")
1797
1798 assert [] = json_response_and_validate_schema(third_conn, 200)
1799 end
1800
1801 test "expires_at is nil for another user" do
1802 %{conn: conn, user: user} = oauth_access(["read:statuses"])
1803 expires_at = DateTime.add(DateTime.utc_now(), 1_000_000)
1804 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", expires_in: 1_000_000})
1805
1806 assert %{"pleroma" => %{"expires_at" => a_expires_at}} =
1807 conn
1808 |> get("/api/v1/statuses/#{activity.id}")
1809 |> json_response_and_validate_schema(:ok)
1810
1811 {:ok, a_expires_at, 0} = DateTime.from_iso8601(a_expires_at)
1812 assert DateTime.diff(expires_at, a_expires_at) == 0
1813
1814 %{conn: conn} = oauth_access(["read:statuses"])
1815
1816 assert %{"pleroma" => %{"expires_at" => nil}} =
1817 conn
1818 |> get("/api/v1/statuses/#{activity.id}")
1819 |> json_response_and_validate_schema(:ok)
1820 end
1821
1822 test "posting a local only status" do
1823 %{user: _user, conn: conn} = oauth_access(["write:statuses"])
1824
1825 conn_one =
1826 conn
1827 |> put_req_header("content-type", "application/json")
1828 |> post("/api/v1/statuses", %{
1829 "status" => "cofe",
1830 "visibility" => "local"
1831 })
1832
1833 local = Pleroma.Constants.as_local_public()
1834
1835 assert %{"content" => "cofe", "id" => id, "visibility" => "local"} =
1836 json_response(conn_one, 200)
1837
1838 assert %Activity{id: ^id, data: %{"to" => [^local]}} = Activity.get_by_id(id)
1839 end
1840
1841 describe "muted reactions" do
1842 test "index" do
1843 %{conn: conn, user: user} = oauth_access(["read:statuses"])
1844
1845 other_user = insert(:user)
1846 {:ok, activity} = CommonAPI.post(user, %{status: "test"})
1847
1848 {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
1849 User.mute(user, other_user)
1850
1851 result =
1852 conn
1853 |> get("/api/v1/statuses/?ids[]=#{activity.id}")
1854 |> json_response_and_validate_schema(200)
1855
1856 assert [
1857 %{
1858 "pleroma" => %{
1859 "emoji_reactions" => []
1860 }
1861 }
1862 ] = result
1863
1864 result =
1865 conn
1866 |> get("/api/v1/statuses/?ids[]=#{activity.id}&with_muted=true")
1867 |> json_response_and_validate_schema(200)
1868
1869 assert [
1870 %{
1871 "pleroma" => %{
1872 "emoji_reactions" => [%{"count" => 1, "me" => false, "name" => "🎅"}]
1873 }
1874 }
1875 ] = result
1876 end
1877
1878 test "show" do
1879 # %{conn: conn, user: user, token: token} = oauth_access(["read:statuses"])
1880 %{conn: conn, user: user, token: _token} = oauth_access(["read:statuses"])
1881
1882 other_user = insert(:user)
1883 {:ok, activity} = CommonAPI.post(user, %{status: "test"})
1884
1885 {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
1886 User.mute(user, other_user)
1887
1888 result =
1889 conn
1890 |> get("/api/v1/statuses/#{activity.id}")
1891 |> json_response_and_validate_schema(200)
1892
1893 assert %{
1894 "pleroma" => %{
1895 "emoji_reactions" => []
1896 }
1897 } = result
1898
1899 result =
1900 conn
1901 |> get("/api/v1/statuses/#{activity.id}?with_muted=true")
1902 |> json_response_and_validate_schema(200)
1903
1904 assert %{
1905 "pleroma" => %{
1906 "emoji_reactions" => [%{"count" => 1, "me" => false, "name" => "🎅"}]
1907 }
1908 } = result
1909 end
1910 end
1911 end