Add language support on /api/v1/statuses
[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.ActivityPub.Utils
18 alias Pleroma.Web.CommonAPI
19 alias Pleroma.Workers.ScheduledActivityWorker
20
21 import Pleroma.Factory
22
23 setup do: clear_config([:instance, :federating])
24 setup do: clear_config([:instance, :allow_relay])
25 setup do: clear_config([:rich_media, :enabled])
26 setup do: clear_config([:mrf, :policies])
27 setup do: clear_config([:mrf_keyword, :reject])
28 setup do: clear_config([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
29 setup do: clear_config([Pleroma.Uploaders.Local, :uploads], "uploads")
30
31 describe "posting statuses" do
32 setup do: oauth_access(["write:statuses"])
33
34 test "posting a status does not increment reblog_count when relaying", %{conn: conn} do
35 clear_config([:instance, :federating], true)
36 Config.get([:instance, :allow_relay], true)
37
38 response =
39 conn
40 |> put_req_header("content-type", "application/json")
41 |> post("/api/v1/statuses", %{
42 "content_type" => "text/plain",
43 "source" => "Pleroma FE",
44 "status" => "Hello world",
45 "visibility" => "public"
46 })
47 |> json_response_and_validate_schema(200)
48
49 assert response["reblogs_count"] == 0
50 ObanHelpers.perform_all()
51
52 response =
53 conn
54 |> get("/api/v1/statuses/#{response["id"]}", %{})
55 |> json_response_and_validate_schema(200)
56
57 assert response["reblogs_count"] == 0
58 end
59
60 test "posting a status", %{conn: conn} do
61 idempotency_key = "Pikachu rocks!"
62
63 conn_one =
64 conn
65 |> put_req_header("content-type", "application/json")
66 |> put_req_header("idempotency-key", idempotency_key)
67 |> post("/api/v1/statuses", %{
68 "status" => "cofe",
69 "spoiler_text" => "2hu",
70 "sensitive" => "0",
71 "language" => "ja"
72 })
73
74 assert %{
75 "content" => "cofe",
76 "id" => id,
77 "spoiler_text" => "2hu",
78 "sensitive" => false,
79 "language" => "ja"
80 } = json_response_and_validate_schema(conn_one, 200)
81
82 assert Activity.get_by_id(id)
83
84 conn_two =
85 conn
86 |> put_req_header("content-type", "application/json")
87 |> put_req_header("idempotency-key", idempotency_key)
88 |> post("/api/v1/statuses", %{
89 "status" => "cofe",
90 "spoiler_text" => "2hu",
91 "sensitive" => 0
92 })
93
94 # Idempotency plug response means detection fail
95 assert %{"id" => second_id} = json_response(conn_two, 200)
96 assert id == second_id
97
98 conn_three =
99 conn
100 |> put_req_header("content-type", "application/json")
101 |> post("/api/v1/statuses", %{
102 "status" => "cofe",
103 "spoiler_text" => "2hu",
104 "sensitive" => "False"
105 })
106
107 assert %{"id" => third_id} = json_response_and_validate_schema(conn_three, 200)
108 refute id == third_id
109
110 # An activity that will expire:
111 # 2 hours
112 expires_in = 2 * 60 * 60
113
114 expires_at = DateTime.add(DateTime.utc_now(), expires_in)
115
116 conn_four =
117 conn
118 |> put_req_header("content-type", "application/json")
119 |> post("/api/v1/statuses", %{
120 "status" => "oolong",
121 "expires_in" => expires_in
122 })
123
124 assert %{"id" => fourth_id} = json_response_and_validate_schema(conn_four, 200)
125
126 assert Activity.get_by_id(fourth_id)
127
128 assert_enqueued(
129 worker: Pleroma.Workers.PurgeExpiredActivity,
130 args: %{activity_id: fourth_id},
131 scheduled_at: expires_at
132 )
133 end
134
135 test "automatically setting a post expiry if status_ttl_days is set" do
136 user = insert(:user, status_ttl_days: 1)
137 %{user: _user, token: _token, conn: conn} = oauth_access(["write:statuses"], user: user)
138
139 conn =
140 conn
141 |> put_req_header("content-type", "application/json")
142 |> post("/api/v1/statuses", %{
143 "status" => "aa chikichiki banban"
144 })
145
146 assert %{"id" => id} = json_response_and_validate_schema(conn, 200)
147
148 activity = Activity.get_by_id_with_object(id)
149 {:ok, expires_at, _} = DateTime.from_iso8601(activity.data["expires_at"])
150
151 assert Timex.diff(
152 expires_at,
153 DateTime.utc_now(),
154 :hours
155 ) == 23
156
157 assert_enqueued(
158 worker: Pleroma.Workers.PurgeExpiredActivity,
159 args: %{activity_id: id},
160 scheduled_at: DateTime.add(DateTime.utc_now(), 1 * 60 * 60 * 24)
161 )
162 end
163
164 test "it fails to create a status if `expires_in` is less or equal than an hour", %{
165 conn: conn
166 } do
167 # 1 minute
168 expires_in = 1 * 60
169
170 assert %{"error" => "Expiry date is too soon"} =
171 conn
172 |> put_req_header("content-type", "application/json")
173 |> post("/api/v1/statuses", %{
174 "status" => "oolong",
175 "expires_in" => expires_in
176 })
177 |> json_response_and_validate_schema(422)
178
179 # 5 minutes
180 expires_in = 5 * 60
181
182 assert %{"error" => "Expiry date is too soon"} =
183 conn
184 |> put_req_header("content-type", "application/json")
185 |> post("/api/v1/statuses", %{
186 "status" => "oolong",
187 "expires_in" => expires_in
188 })
189 |> json_response_and_validate_schema(422)
190 end
191
192 test "Get MRF reason when posting a status is rejected by one", %{conn: conn} do
193 clear_config([:mrf_keyword, :reject], ["GNO"])
194 clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy])
195
196 assert %{"error" => "[KeywordPolicy] Matches with rejected keyword"} =
197 conn
198 |> put_req_header("content-type", "application/json")
199 |> post("/api/v1/statuses", %{"status" => "GNO/Linux"})
200 |> json_response_and_validate_schema(422)
201 end
202
203 test "posting an undefined status with an attachment", %{user: user, conn: conn} do
204 file = %Plug.Upload{
205 content_type: "image/jpeg",
206 path: Path.absname("test/fixtures/image.jpg"),
207 filename: "an_image.jpg"
208 }
209
210 {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
211
212 conn =
213 conn
214 |> put_req_header("content-type", "application/json")
215 |> post("/api/v1/statuses", %{
216 "media_ids" => [to_string(upload.id)]
217 })
218
219 assert json_response_and_validate_schema(conn, 200)
220 end
221
222 test "replying to a status", %{user: user, conn: conn} do
223 {:ok, replied_to} = CommonAPI.post(user, %{status: "cofe"})
224
225 conn =
226 conn
227 |> put_req_header("content-type", "application/json")
228 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
229
230 assert %{"content" => "xD", "id" => id} = json_response_and_validate_schema(conn, 200)
231
232 activity = Activity.get_by_id(id)
233
234 assert activity.data["context"] == replied_to.data["context"]
235 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
236 end
237
238 test "replying to a direct message with visibility other than direct", %{
239 user: user,
240 conn: conn
241 } do
242 {:ok, replied_to} = CommonAPI.post(user, %{status: "suya..", visibility: "direct"})
243
244 Enum.each(["public", "private", "unlisted"], fn visibility ->
245 conn =
246 conn
247 |> put_req_header("content-type", "application/json")
248 |> post("/api/v1/statuses", %{
249 "status" => "@#{user.nickname} hey",
250 "in_reply_to_id" => replied_to.id,
251 "visibility" => visibility
252 })
253
254 assert json_response_and_validate_schema(conn, 422) == %{
255 "error" => "The message visibility must be direct"
256 }
257 end)
258 end
259
260 test "posting a status with an invalid in_reply_to_id", %{conn: conn} do
261 conn =
262 conn
263 |> put_req_header("content-type", "application/json")
264 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => ""})
265
266 assert %{"content" => "xD", "id" => id} = json_response_and_validate_schema(conn, 200)
267 assert Activity.get_by_id(id)
268 end
269
270 test "posting a sensitive status", %{conn: conn} do
271 conn =
272 conn
273 |> put_req_header("content-type", "application/json")
274 |> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true})
275
276 assert %{"content" => "cofe", "id" => id, "sensitive" => true} =
277 json_response_and_validate_schema(conn, 200)
278
279 assert Activity.get_by_id(id)
280 end
281
282 test "posting a fake status", %{conn: conn} do
283 real_conn =
284 conn
285 |> put_req_header("content-type", "application/json")
286 |> post("/api/v1/statuses", %{
287 "status" =>
288 "\"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"
289 })
290
291 real_status = json_response_and_validate_schema(real_conn, 200)
292
293 assert real_status
294 assert Object.get_by_ap_id(real_status["uri"])
295
296 real_status =
297 real_status
298 |> Map.put("id", nil)
299 |> Map.put("url", nil)
300 |> Map.put("uri", nil)
301 |> Map.put("created_at", nil)
302 |> Kernel.put_in(["pleroma", "context"], nil)
303 |> Kernel.put_in(["pleroma", "conversation_id"], nil)
304
305 fake_conn =
306 conn
307 |> assign(:user, refresh_record(conn.assigns.user))
308 |> put_req_header("content-type", "application/json")
309 |> post("/api/v1/statuses", %{
310 "status" =>
311 "\"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",
312 "preview" => true
313 })
314
315 fake_status = json_response_and_validate_schema(fake_conn, 200)
316
317 assert fake_status
318 refute Object.get_by_ap_id(fake_status["uri"])
319
320 fake_status =
321 fake_status
322 |> Map.put("id", nil)
323 |> Map.put("url", nil)
324 |> Map.put("uri", nil)
325 |> Map.put("created_at", nil)
326 |> Kernel.put_in(["pleroma", "context"], nil)
327 |> Kernel.put_in(["pleroma", "conversation_id"], nil)
328
329 assert real_status == fake_status
330 end
331
332 test "fake statuses' preview card is not cached", %{conn: conn} do
333 clear_config([:rich_media, :enabled], true)
334
335 Tesla.Mock.mock_global(fn
336 %{
337 method: :get,
338 url: "https://example.com/twitter-card"
339 } ->
340 %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/twitter_card.html")}
341
342 env ->
343 apply(HttpRequestMock, :request, [env])
344 end)
345
346 conn1 =
347 conn
348 |> put_req_header("content-type", "application/json")
349 |> post("/api/v1/statuses", %{
350 "status" => "https://example.com/ogp",
351 "preview" => true
352 })
353
354 conn2 =
355 conn
356 |> put_req_header("content-type", "application/json")
357 |> post("/api/v1/statuses", %{
358 "status" => "https://example.com/twitter-card",
359 "preview" => true
360 })
361
362 assert %{"card" => %{"title" => "The Rock"}} = json_response_and_validate_schema(conn1, 200)
363
364 assert %{"card" => %{"title" => "Small Island Developing States Photo Submission"}} =
365 json_response_and_validate_schema(conn2, 200)
366 end
367
368 test "posting a status with OGP link preview", %{conn: conn} do
369 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
370 clear_config([:rich_media, :enabled], true)
371
372 conn =
373 conn
374 |> put_req_header("content-type", "application/json")
375 |> post("/api/v1/statuses", %{
376 "status" => "https://example.com/ogp"
377 })
378
379 assert %{"id" => id, "card" => %{"title" => "The Rock"}} =
380 json_response_and_validate_schema(conn, 200)
381
382 assert Activity.get_by_id(id)
383 end
384
385 test "posting a direct status", %{conn: conn} do
386 user2 = insert(:user)
387 content = "direct cofe @#{user2.nickname}"
388
389 conn =
390 conn
391 |> put_req_header("content-type", "application/json")
392 |> post("/api/v1/statuses", %{"status" => content, "visibility" => "direct"})
393
394 assert %{"id" => id} = response = json_response_and_validate_schema(conn, 200)
395 assert response["visibility"] == "direct"
396 assert response["pleroma"]["direct_conversation_id"]
397 assert activity = Activity.get_by_id(id)
398 assert activity.recipients == [user2.ap_id, conn.assigns[:user].ap_id]
399 assert activity.data["to"] == [user2.ap_id]
400 assert activity.data["cc"] == []
401 end
402
403 test "discloses application metadata when enabled" do
404 user = insert(:user, disclose_client: true)
405 %{user: _user, token: token, conn: conn} = oauth_access(["write:statuses"], user: user)
406
407 %Pleroma.Web.OAuth.Token{
408 app: %Pleroma.Web.OAuth.App{
409 client_name: app_name,
410 website: app_website
411 }
412 } = token
413
414 result =
415 conn
416 |> put_req_header("content-type", "application/json")
417 |> post("/api/v1/statuses", %{
418 "status" => "cofe is my copilot"
419 })
420
421 assert %{
422 "content" => "cofe is my copilot"
423 } = json_response_and_validate_schema(result, 200)
424
425 activity = result.assigns.activity.id
426
427 result =
428 conn
429 |> get("/api/v1/statuses/#{activity}")
430
431 assert %{
432 "content" => "cofe is my copilot",
433 "application" => %{
434 "name" => ^app_name,
435 "website" => ^app_website
436 }
437 } = json_response_and_validate_schema(result, 200)
438 end
439
440 test "hides application metadata when disabled" do
441 user = insert(:user, disclose_client: false)
442 %{user: _user, token: _token, conn: conn} = oauth_access(["write:statuses"], user: user)
443
444 result =
445 conn
446 |> put_req_header("content-type", "application/json")
447 |> post("/api/v1/statuses", %{
448 "status" => "club mate is my wingman"
449 })
450
451 assert %{"content" => "club mate is my wingman"} =
452 json_response_and_validate_schema(result, 200)
453
454 activity = result.assigns.activity.id
455
456 result =
457 conn
458 |> get("/api/v1/statuses/#{activity}")
459
460 assert %{
461 "content" => "club mate is my wingman",
462 "application" => nil
463 } = json_response_and_validate_schema(result, 200)
464 end
465 end
466
467 describe "posting scheduled statuses" do
468 setup do: oauth_access(["write:statuses"])
469
470 test "creates a scheduled activity", %{conn: conn} do
471 scheduled_at =
472 NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
473 |> NaiveDateTime.to_iso8601()
474 |> Kernel.<>("Z")
475
476 conn =
477 conn
478 |> put_req_header("content-type", "application/json")
479 |> post("/api/v1/statuses", %{
480 "status" => "scheduled",
481 "scheduled_at" => scheduled_at
482 })
483
484 assert %{"scheduled_at" => expected_scheduled_at} =
485 json_response_and_validate_schema(conn, 200)
486
487 assert expected_scheduled_at == CommonAPI.Utils.to_masto_date(scheduled_at)
488 assert [] == Repo.all(Activity)
489 end
490
491 test "with expiration" do
492 %{conn: conn} = oauth_access(["write:statuses", "read:statuses"])
493
494 scheduled_at =
495 NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(6), :millisecond)
496 |> NaiveDateTime.to_iso8601()
497 |> Kernel.<>("Z")
498
499 assert %{"id" => status_id, "params" => %{"expires_in" => 300}} =
500 conn
501 |> put_req_header("content-type", "application/json")
502 |> post("/api/v1/statuses", %{
503 "status" => "scheduled",
504 "scheduled_at" => scheduled_at,
505 "expires_in" => 300
506 })
507 |> json_response_and_validate_schema(200)
508
509 assert %{"id" => ^status_id, "params" => %{"expires_in" => 300}} =
510 conn
511 |> put_req_header("content-type", "application/json")
512 |> get("/api/v1/scheduled_statuses/#{status_id}")
513 |> json_response_and_validate_schema(200)
514 end
515
516 test "ignores nil values", %{conn: conn} do
517 conn =
518 conn
519 |> put_req_header("content-type", "application/json")
520 |> post("/api/v1/statuses", %{
521 "status" => "not scheduled",
522 "scheduled_at" => nil
523 })
524
525 assert result = json_response_and_validate_schema(conn, 200)
526 assert Activity.get_by_id(result["id"])
527 end
528
529 test "creates a scheduled activity with a media attachment", %{user: user, conn: conn} do
530 scheduled_at =
531 NaiveDateTime.utc_now()
532 |> NaiveDateTime.add(:timer.minutes(120), :millisecond)
533 |> NaiveDateTime.to_iso8601()
534 |> Kernel.<>("Z")
535
536 file = %Plug.Upload{
537 content_type: "image/jpeg",
538 path: Path.absname("test/fixtures/image.jpg"),
539 filename: "an_image.jpg"
540 }
541
542 {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
543
544 conn =
545 conn
546 |> put_req_header("content-type", "application/json")
547 |> post("/api/v1/statuses", %{
548 "media_ids" => [to_string(upload.id)],
549 "status" => "scheduled",
550 "scheduled_at" => scheduled_at
551 })
552
553 assert %{"media_attachments" => [media_attachment]} =
554 json_response_and_validate_schema(conn, 200)
555
556 assert %{"type" => "image"} = media_attachment
557 end
558
559 test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now",
560 %{conn: conn} do
561 scheduled_at =
562 NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond)
563 |> NaiveDateTime.to_iso8601()
564 |> Kernel.<>("Z")
565
566 conn =
567 conn
568 |> put_req_header("content-type", "application/json")
569 |> post("/api/v1/statuses", %{
570 "status" => "not scheduled",
571 "scheduled_at" => scheduled_at
572 })
573
574 assert %{"content" => "not scheduled"} = json_response_and_validate_schema(conn, 200)
575 assert [] == Repo.all(ScheduledActivity)
576 end
577
578 test "returns error when daily user limit is exceeded", %{user: user, conn: conn} do
579 today =
580 NaiveDateTime.utc_now()
581 |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
582 |> NaiveDateTime.to_iso8601()
583 # TODO
584 |> Kernel.<>("Z")
585
586 attrs = %{params: %{}, scheduled_at: today}
587 {:ok, _} = ScheduledActivity.create(user, attrs)
588 {:ok, _} = ScheduledActivity.create(user, attrs)
589
590 conn =
591 conn
592 |> put_req_header("content-type", "application/json")
593 |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today})
594
595 assert %{"error" => "daily limit exceeded"} == json_response_and_validate_schema(conn, 422)
596 end
597
598 test "returns error when total user limit is exceeded", %{user: user, conn: conn} do
599 today =
600 NaiveDateTime.utc_now()
601 |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
602 |> NaiveDateTime.to_iso8601()
603 |> Kernel.<>("Z")
604
605 tomorrow =
606 NaiveDateTime.utc_now()
607 |> NaiveDateTime.add(:timer.hours(36), :millisecond)
608 |> NaiveDateTime.to_iso8601()
609 |> Kernel.<>("Z")
610
611 attrs = %{params: %{}, scheduled_at: today}
612 {:ok, _} = ScheduledActivity.create(user, attrs)
613 {:ok, _} = ScheduledActivity.create(user, attrs)
614 {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
615
616 conn =
617 conn
618 |> put_req_header("content-type", "application/json")
619 |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow})
620
621 assert %{"error" => "total limit exceeded"} == json_response_and_validate_schema(conn, 422)
622 end
623 end
624
625 describe "posting polls" do
626 setup do: oauth_access(["write:statuses"])
627
628 test "posting a poll", %{conn: conn} do
629 time = NaiveDateTime.utc_now()
630
631 conn =
632 conn
633 |> put_req_header("content-type", "application/json")
634 |> post("/api/v1/statuses", %{
635 "status" => "Who is the #bestgrill?",
636 "poll" => %{
637 "options" => ["Rei", "Asuka", "Misato"],
638 "expires_in" => 420
639 }
640 })
641
642 response = json_response_and_validate_schema(conn, 200)
643
644 assert Enum.all?(response["poll"]["options"], fn %{"title" => title} ->
645 title in ["Rei", "Asuka", "Misato"]
646 end)
647
648 assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430
649 assert response["poll"]["expired"] == false
650
651 question = Object.get_by_id(response["poll"]["id"])
652
653 # closed contains utc timezone
654 assert question.data["closed"] =~ "Z"
655 end
656
657 test "option limit is enforced", %{conn: conn} do
658 limit = Config.get([:instance, :poll_limits, :max_options])
659
660 conn =
661 conn
662 |> put_req_header("content-type", "application/json")
663 |> post("/api/v1/statuses", %{
664 "status" => "desu~",
665 "poll" => %{"options" => Enum.map(0..limit, fn _ -> "desu" end), "expires_in" => 1}
666 })
667
668 %{"error" => error} = json_response_and_validate_schema(conn, 422)
669 assert error == "Poll can't contain more than #{limit} options"
670 end
671
672 test "option character limit is enforced", %{conn: conn} do
673 limit = Config.get([:instance, :poll_limits, :max_option_chars])
674
675 conn =
676 conn
677 |> put_req_header("content-type", "application/json")
678 |> post("/api/v1/statuses", %{
679 "status" => "...",
680 "poll" => %{
681 "options" => [Enum.reduce(0..limit, "", fn _, acc -> acc <> "." end)],
682 "expires_in" => 1
683 }
684 })
685
686 %{"error" => error} = json_response_and_validate_schema(conn, 422)
687 assert error == "Poll options cannot be longer than #{limit} characters each"
688 end
689
690 test "minimal date limit is enforced", %{conn: conn} do
691 limit = Config.get([:instance, :poll_limits, :min_expiration])
692
693 conn =
694 conn
695 |> put_req_header("content-type", "application/json")
696 |> post("/api/v1/statuses", %{
697 "status" => "imagine arbitrary limits",
698 "poll" => %{
699 "options" => ["this post was made by pleroma gang"],
700 "expires_in" => limit - 1
701 }
702 })
703
704 %{"error" => error} = json_response_and_validate_schema(conn, 422)
705 assert error == "Expiration date is too soon"
706 end
707
708 test "maximum date limit is enforced", %{conn: conn} do
709 limit = Config.get([:instance, :poll_limits, :max_expiration])
710
711 conn =
712 conn
713 |> put_req_header("content-type", "application/json")
714 |> post("/api/v1/statuses", %{
715 "status" => "imagine arbitrary limits",
716 "poll" => %{
717 "options" => ["this post was made by pleroma gang"],
718 "expires_in" => limit + 1
719 }
720 })
721
722 %{"error" => error} = json_response_and_validate_schema(conn, 422)
723 assert error == "Expiration date is too far in the future"
724 end
725
726 test "scheduled poll", %{conn: conn} do
727 clear_config([ScheduledActivity, :enabled], true)
728
729 scheduled_at =
730 NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(6), :millisecond)
731 |> NaiveDateTime.to_iso8601()
732 |> Kernel.<>("Z")
733
734 %{"id" => scheduled_id} =
735 conn
736 |> put_req_header("content-type", "application/json")
737 |> post("/api/v1/statuses", %{
738 "status" => "very cool poll",
739 "poll" => %{
740 "options" => ~w(a b c),
741 "expires_in" => 420
742 },
743 "scheduled_at" => scheduled_at
744 })
745 |> json_response_and_validate_schema(200)
746
747 assert {:ok, %{id: activity_id}} =
748 perform_job(ScheduledActivityWorker, %{
749 activity_id: scheduled_id
750 })
751
752 refute_enqueued(worker: ScheduledActivityWorker)
753
754 object =
755 Activity
756 |> Repo.get(activity_id)
757 |> Object.normalize()
758
759 assert object.data["content"] == "very cool poll"
760 assert object.data["type"] == "Question"
761 assert length(object.data["oneOf"]) == 3
762 end
763 end
764
765 test "get a status" do
766 %{conn: conn} = oauth_access(["read:statuses"])
767 activity = insert(:note_activity)
768
769 conn = get(conn, "/api/v1/statuses/#{activity.id}")
770
771 assert %{"id" => id} = json_response_and_validate_schema(conn, 200)
772 assert id == to_string(activity.id)
773 end
774
775 defp local_and_remote_activities do
776 local = insert(:note_activity)
777 remote = insert(:note_activity, local: false)
778 {:ok, local: local, remote: remote}
779 end
780
781 describe "status with restrict unauthenticated activities for local and remote" do
782 setup do: local_and_remote_activities()
783
784 setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
785
786 setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
787
788 test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
789 res_conn = get(conn, "/api/v1/statuses/#{local.id}")
790
791 assert json_response_and_validate_schema(res_conn, :not_found) == %{
792 "error" => "Record not found"
793 }
794
795 res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
796
797 assert json_response_and_validate_schema(res_conn, :not_found) == %{
798 "error" => "Record not found"
799 }
800 end
801
802 test "if user is authenticated", %{local: local, remote: remote} do
803 %{conn: conn} = oauth_access(["read"])
804 res_conn = get(conn, "/api/v1/statuses/#{local.id}")
805 assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
806
807 res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
808 assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
809 end
810 end
811
812 describe "status with restrict unauthenticated activities for local" do
813 setup do: local_and_remote_activities()
814
815 setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
816
817 test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
818 res_conn = get(conn, "/api/v1/statuses/#{local.id}")
819
820 assert json_response_and_validate_schema(res_conn, :not_found) == %{
821 "error" => "Record not found"
822 }
823
824 res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
825 assert %{"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 res_conn = get(conn, "/api/v1/statuses/#{local.id}")
831 assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
832
833 res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
834 assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
835 end
836 end
837
838 describe "status with restrict unauthenticated activities for remote" do
839 setup do: local_and_remote_activities()
840
841 setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
842
843 test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
844 res_conn = get(conn, "/api/v1/statuses/#{local.id}")
845 assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
846
847 res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
848
849 assert json_response_and_validate_schema(res_conn, :not_found) == %{
850 "error" => "Record not found"
851 }
852 end
853
854 test "if user is authenticated", %{local: local, remote: remote} do
855 %{conn: conn} = oauth_access(["read"])
856 res_conn = get(conn, "/api/v1/statuses/#{local.id}")
857 assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
858
859 res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
860 assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
861 end
862 end
863
864 test "getting a status that doesn't exist returns 404" do
865 %{conn: conn} = oauth_access(["read:statuses"])
866 activity = insert(:note_activity)
867
868 conn = get(conn, "/api/v1/statuses/#{String.downcase(activity.id)}")
869
870 assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
871 end
872
873 test "get a direct status" do
874 %{user: user, conn: conn} = oauth_access(["read:statuses"])
875 other_user = insert(:user)
876
877 {:ok, activity} =
878 CommonAPI.post(user, %{status: "@#{other_user.nickname}", visibility: "direct"})
879
880 conn =
881 conn
882 |> assign(:user, user)
883 |> get("/api/v1/statuses/#{activity.id}")
884
885 [participation] = Participation.for_user(user)
886
887 res = json_response_and_validate_schema(conn, 200)
888 assert res["pleroma"]["direct_conversation_id"] == participation.id
889 end
890
891 test "get statuses by IDs" do
892 %{conn: conn} = oauth_access(["read:statuses"])
893 %{id: id1} = insert(:note_activity)
894 %{id: id2} = insert(:note_activity)
895
896 query_string = "ids[]=#{id1}&ids[]=#{id2}"
897 conn = get(conn, "/api/v1/statuses/?#{query_string}")
898
899 assert [%{"id" => ^id1}, %{"id" => ^id2}] =
900 Enum.sort_by(json_response_and_validate_schema(conn, :ok), & &1["id"])
901 end
902
903 describe "getting statuses by ids with restricted unauthenticated for local and remote" do
904 setup do: local_and_remote_activities()
905
906 setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
907
908 setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
909
910 test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
911 res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
912
913 assert json_response_and_validate_schema(res_conn, 200) == []
914 end
915
916 test "if user is authenticated", %{local: local, remote: remote} do
917 %{conn: conn} = oauth_access(["read"])
918
919 res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
920
921 assert length(json_response_and_validate_schema(res_conn, 200)) == 2
922 end
923 end
924
925 describe "getting statuses by ids with restricted unauthenticated for local" do
926 setup do: local_and_remote_activities()
927
928 setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
929
930 test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
931 res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
932
933 remote_id = remote.id
934 assert [%{"id" => ^remote_id}] = json_response_and_validate_schema(res_conn, 200)
935 end
936
937 test "if user is authenticated", %{local: local, remote: remote} do
938 %{conn: conn} = oauth_access(["read"])
939
940 res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
941
942 assert length(json_response_and_validate_schema(res_conn, 200)) == 2
943 end
944 end
945
946 describe "getting statuses by ids with restricted unauthenticated for remote" do
947 setup do: local_and_remote_activities()
948
949 setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
950
951 test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
952 res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
953
954 local_id = local.id
955 assert [%{"id" => ^local_id}] = json_response_and_validate_schema(res_conn, 200)
956 end
957
958 test "if user is authenticated", %{local: local, remote: remote} do
959 %{conn: conn} = oauth_access(["read"])
960
961 res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
962
963 assert length(json_response_and_validate_schema(res_conn, 200)) == 2
964 end
965 end
966
967 describe "deleting a status" do
968 test "when you created it" do
969 %{user: author, conn: conn} = oauth_access(["write:statuses"])
970 activity = insert(:note_activity, user: author)
971 object = Object.normalize(activity, fetch: false)
972
973 content = object.data["content"]
974 source = object.data["source"]
975
976 result =
977 conn
978 |> assign(:user, author)
979 |> delete("/api/v1/statuses/#{activity.id}")
980 |> json_response_and_validate_schema(200)
981
982 assert match?(%{"content" => ^content, "text" => ^source}, result)
983
984 refute Activity.get_by_id(activity.id)
985 end
986
987 test "when it doesn't exist" do
988 %{user: author, conn: conn} = oauth_access(["write:statuses"])
989 activity = insert(:note_activity, user: author)
990
991 conn =
992 conn
993 |> assign(:user, author)
994 |> delete("/api/v1/statuses/#{String.downcase(activity.id)}")
995
996 assert %{"error" => "Record not found"} == json_response_and_validate_schema(conn, 404)
997 end
998
999 test "when you didn't create it" do
1000 %{conn: conn} = oauth_access(["write:statuses"])
1001 activity = insert(:note_activity)
1002
1003 conn = delete(conn, "/api/v1/statuses/#{activity.id}")
1004
1005 assert %{"error" => "Record not found"} == json_response_and_validate_schema(conn, 404)
1006
1007 assert Activity.get_by_id(activity.id) == activity
1008 end
1009
1010 test "when you're an admin or moderator", %{conn: conn} do
1011 activity1 = insert(:note_activity)
1012 activity2 = insert(:note_activity)
1013 admin = insert(:user, is_admin: true)
1014 moderator = insert(:user, is_moderator: true)
1015
1016 res_conn =
1017 conn
1018 |> assign(:user, admin)
1019 |> assign(:token, insert(:oauth_token, user: admin, scopes: ["write:statuses"]))
1020 |> delete("/api/v1/statuses/#{activity1.id}")
1021
1022 assert %{} = json_response_and_validate_schema(res_conn, 200)
1023
1024 res_conn =
1025 conn
1026 |> assign(:user, moderator)
1027 |> assign(:token, insert(:oauth_token, user: moderator, scopes: ["write:statuses"]))
1028 |> delete("/api/v1/statuses/#{activity2.id}")
1029
1030 assert %{} = json_response_and_validate_schema(res_conn, 200)
1031
1032 refute Activity.get_by_id(activity1.id)
1033 refute Activity.get_by_id(activity2.id)
1034 end
1035 end
1036
1037 describe "reblogging" do
1038 setup do: oauth_access(["write:statuses"])
1039
1040 test "reblogs and returns the reblogged status", %{conn: conn} do
1041 activity = insert(:note_activity)
1042
1043 conn =
1044 conn
1045 |> put_req_header("content-type", "application/json")
1046 |> post("/api/v1/statuses/#{activity.id}/reblog")
1047
1048 assert %{
1049 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
1050 "reblogged" => true
1051 } = json_response_and_validate_schema(conn, 200)
1052
1053 assert to_string(activity.id) == id
1054 end
1055
1056 test "returns 404 if the reblogged status doesn't exist", %{conn: conn} do
1057 activity = insert(:note_activity)
1058
1059 conn =
1060 conn
1061 |> put_req_header("content-type", "application/json")
1062 |> post("/api/v1/statuses/#{String.downcase(activity.id)}/reblog")
1063
1064 assert %{"error" => "Record not found"} = json_response_and_validate_schema(conn, 404)
1065 end
1066
1067 test "reblogs privately and returns the reblogged status", %{conn: conn} do
1068 activity = insert(:note_activity)
1069
1070 conn =
1071 conn
1072 |> put_req_header("content-type", "application/json")
1073 |> post(
1074 "/api/v1/statuses/#{activity.id}/reblog",
1075 %{"visibility" => "private"}
1076 )
1077
1078 assert %{
1079 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
1080 "reblogged" => true,
1081 "visibility" => "private"
1082 } = json_response_and_validate_schema(conn, 200)
1083
1084 assert to_string(activity.id) == id
1085 end
1086
1087 test "reblogged status for another user" do
1088 activity = insert(:note_activity)
1089 user1 = insert(:user)
1090 user2 = insert(:user)
1091 user3 = insert(:user)
1092 {:ok, _} = CommonAPI.favorite(user2, activity.id)
1093 {:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id)
1094 {:ok, reblog_activity1} = CommonAPI.repeat(activity.id, user1)
1095 {:ok, _} = CommonAPI.repeat(activity.id, user2)
1096
1097 conn_res =
1098 build_conn()
1099 |> assign(:user, user3)
1100 |> assign(:token, insert(:oauth_token, user: user3, scopes: ["read:statuses"]))
1101 |> get("/api/v1/statuses/#{reblog_activity1.id}")
1102
1103 assert %{
1104 "reblog" => %{"id" => _id, "reblogged" => false, "reblogs_count" => 2},
1105 "reblogged" => false,
1106 "favourited" => false,
1107 "bookmarked" => false
1108 } = json_response_and_validate_schema(conn_res, 200)
1109
1110 conn_res =
1111 build_conn()
1112 |> assign(:user, user2)
1113 |> assign(:token, insert(:oauth_token, user: user2, scopes: ["read:statuses"]))
1114 |> get("/api/v1/statuses/#{reblog_activity1.id}")
1115
1116 assert %{
1117 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 2},
1118 "reblogged" => true,
1119 "favourited" => true,
1120 "bookmarked" => true
1121 } = json_response_and_validate_schema(conn_res, 200)
1122
1123 assert to_string(activity.id) == id
1124 end
1125
1126 test "author can reblog own private status", %{conn: conn, user: user} do
1127 {:ok, activity} = CommonAPI.post(user, %{status: "cofe", visibility: "private"})
1128
1129 conn =
1130 conn
1131 |> put_req_header("content-type", "application/json")
1132 |> post("/api/v1/statuses/#{activity.id}/reblog")
1133
1134 assert %{
1135 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
1136 "reblogged" => true,
1137 "visibility" => "private"
1138 } = json_response_and_validate_schema(conn, 200)
1139
1140 assert to_string(activity.id) == id
1141 end
1142 end
1143
1144 describe "unreblogging" do
1145 setup do: oauth_access(["write:statuses"])
1146
1147 test "unreblogs and returns the unreblogged status", %{user: user, conn: conn} do
1148 activity = insert(:note_activity)
1149
1150 {:ok, _} = CommonAPI.repeat(activity.id, user)
1151
1152 conn =
1153 conn
1154 |> put_req_header("content-type", "application/json")
1155 |> post("/api/v1/statuses/#{activity.id}/unreblog")
1156
1157 assert %{"id" => id, "reblogged" => false, "reblogs_count" => 0} =
1158 json_response_and_validate_schema(conn, 200)
1159
1160 assert to_string(activity.id) == id
1161 end
1162
1163 test "returns 404 error when activity does not exist", %{conn: conn} do
1164 conn =
1165 conn
1166 |> put_req_header("content-type", "application/json")
1167 |> post("/api/v1/statuses/foo/unreblog")
1168
1169 assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
1170 end
1171 end
1172
1173 describe "favoriting" do
1174 setup do: oauth_access(["write:favourites"])
1175
1176 test "favs a status and returns it", %{conn: conn} do
1177 activity = insert(:note_activity)
1178
1179 conn =
1180 conn
1181 |> put_req_header("content-type", "application/json")
1182 |> post("/api/v1/statuses/#{activity.id}/favourite")
1183
1184 assert %{"id" => id, "favourites_count" => 1, "favourited" => true} =
1185 json_response_and_validate_schema(conn, 200)
1186
1187 assert to_string(activity.id) == id
1188 end
1189
1190 test "favoriting twice will just return 200", %{conn: conn} do
1191 activity = insert(:note_activity)
1192
1193 conn
1194 |> put_req_header("content-type", "application/json")
1195 |> post("/api/v1/statuses/#{activity.id}/favourite")
1196
1197 assert conn
1198 |> put_req_header("content-type", "application/json")
1199 |> post("/api/v1/statuses/#{activity.id}/favourite")
1200 |> json_response_and_validate_schema(200)
1201 end
1202
1203 test "returns 404 error for a wrong id", %{conn: conn} do
1204 conn =
1205 conn
1206 |> put_req_header("content-type", "application/json")
1207 |> post("/api/v1/statuses/1/favourite")
1208
1209 assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
1210 end
1211 end
1212
1213 describe "unfavoriting" do
1214 setup do: oauth_access(["write:favourites"])
1215
1216 test "unfavorites a status and returns it", %{user: user, conn: conn} do
1217 activity = insert(:note_activity)
1218
1219 {:ok, _} = CommonAPI.favorite(user, activity.id)
1220
1221 conn =
1222 conn
1223 |> put_req_header("content-type", "application/json")
1224 |> post("/api/v1/statuses/#{activity.id}/unfavourite")
1225
1226 assert %{"id" => id, "favourites_count" => 0, "favourited" => false} =
1227 json_response_and_validate_schema(conn, 200)
1228
1229 assert to_string(activity.id) == id
1230 end
1231
1232 test "returns 404 error for a wrong id", %{conn: conn} do
1233 conn =
1234 conn
1235 |> put_req_header("content-type", "application/json")
1236 |> post("/api/v1/statuses/1/unfavourite")
1237
1238 assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
1239 end
1240 end
1241
1242 describe "pinned statuses" do
1243 setup do: oauth_access(["write:accounts"])
1244
1245 setup %{user: user} do
1246 {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!"})
1247
1248 %{activity: activity}
1249 end
1250
1251 setup do: clear_config([:instance, :max_pinned_statuses], 1)
1252
1253 test "pin status", %{conn: conn, user: user, activity: activity} do
1254 id = activity.id
1255
1256 assert %{"id" => ^id, "pinned" => true} =
1257 conn
1258 |> put_req_header("content-type", "application/json")
1259 |> post("/api/v1/statuses/#{activity.id}/pin")
1260 |> json_response_and_validate_schema(200)
1261
1262 assert [%{"id" => ^id, "pinned" => true}] =
1263 conn
1264 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
1265 |> json_response_and_validate_schema(200)
1266 end
1267
1268 test "non authenticated user", %{activity: activity} do
1269 assert build_conn()
1270 |> put_req_header("content-type", "application/json")
1271 |> post("/api/v1/statuses/#{activity.id}/pin")
1272 |> json_response(403) == %{"error" => "Invalid credentials."}
1273 end
1274
1275 test "/pin: returns 400 error when activity is not public", %{conn: conn, user: user} do
1276 {:ok, dm} = CommonAPI.post(user, %{status: "test", visibility: "direct"})
1277
1278 conn =
1279 conn
1280 |> put_req_header("content-type", "application/json")
1281 |> post("/api/v1/statuses/#{dm.id}/pin")
1282
1283 assert json_response_and_validate_schema(conn, 422) == %{
1284 "error" => "Non-public status cannot be pinned"
1285 }
1286 end
1287
1288 test "pin by another user", %{activity: activity} do
1289 %{conn: conn} = oauth_access(["write:accounts"])
1290
1291 assert conn
1292 |> put_req_header("content-type", "application/json")
1293 |> post("/api/v1/statuses/#{activity.id}/pin")
1294 |> json_response(422) == %{"error" => "Someone else's status cannot be pinned"}
1295 end
1296
1297 test "unpin status", %{conn: conn, user: user, activity: activity} do
1298 {:ok, _} = CommonAPI.pin(activity.id, user)
1299 user = refresh_record(user)
1300
1301 id_str = to_string(activity.id)
1302
1303 assert %{"id" => ^id_str, "pinned" => false} =
1304 conn
1305 |> assign(:user, user)
1306 |> post("/api/v1/statuses/#{activity.id}/unpin")
1307 |> json_response_and_validate_schema(200)
1308
1309 assert [] =
1310 conn
1311 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
1312 |> json_response_and_validate_schema(200)
1313 end
1314
1315 test "/unpin: returns 404 error when activity doesn't exist", %{conn: conn} do
1316 assert conn
1317 |> put_req_header("content-type", "application/json")
1318 |> post("/api/v1/statuses/1/unpin")
1319 |> json_response_and_validate_schema(404) == %{"error" => "Record not found"}
1320 end
1321
1322 test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
1323 {:ok, activity_two} = CommonAPI.post(user, %{status: "HI!!!"})
1324
1325 id_str_one = to_string(activity_one.id)
1326
1327 assert %{"id" => ^id_str_one, "pinned" => true} =
1328 conn
1329 |> put_req_header("content-type", "application/json")
1330 |> post("/api/v1/statuses/#{id_str_one}/pin")
1331 |> json_response_and_validate_schema(200)
1332
1333 user = refresh_record(user)
1334
1335 assert %{"error" => "You have already pinned the maximum number of statuses"} =
1336 conn
1337 |> assign(:user, user)
1338 |> post("/api/v1/statuses/#{activity_two.id}/pin")
1339 |> json_response_and_validate_schema(400)
1340 end
1341
1342 test "on pin removes deletion job, on unpin reschedule deletion" do
1343 %{conn: conn} = oauth_access(["write:accounts", "write:statuses"])
1344 expires_in = 2 * 60 * 60
1345
1346 expires_at = DateTime.add(DateTime.utc_now(), expires_in)
1347
1348 assert %{"id" => id} =
1349 conn
1350 |> put_req_header("content-type", "application/json")
1351 |> post("/api/v1/statuses", %{
1352 "status" => "oolong",
1353 "expires_in" => expires_in
1354 })
1355 |> json_response_and_validate_schema(200)
1356
1357 assert_enqueued(
1358 worker: Pleroma.Workers.PurgeExpiredActivity,
1359 args: %{activity_id: id},
1360 scheduled_at: expires_at
1361 )
1362
1363 assert %{"id" => ^id, "pinned" => true} =
1364 conn
1365 |> put_req_header("content-type", "application/json")
1366 |> post("/api/v1/statuses/#{id}/pin")
1367 |> json_response_and_validate_schema(200)
1368
1369 refute_enqueued(
1370 worker: Pleroma.Workers.PurgeExpiredActivity,
1371 args: %{activity_id: id},
1372 scheduled_at: expires_at
1373 )
1374
1375 assert %{"id" => ^id, "pinned" => false} =
1376 conn
1377 |> put_req_header("content-type", "application/json")
1378 |> post("/api/v1/statuses/#{id}/unpin")
1379 |> json_response_and_validate_schema(200)
1380
1381 assert_enqueued(
1382 worker: Pleroma.Workers.PurgeExpiredActivity,
1383 args: %{activity_id: id},
1384 scheduled_at: expires_at
1385 )
1386 end
1387 end
1388
1389 test "bookmarks" do
1390 bookmarks_uri = "/api/v1/bookmarks"
1391
1392 %{conn: conn} = oauth_access(["write:bookmarks", "read:bookmarks"])
1393 author = insert(:user)
1394
1395 {:ok, activity1} = CommonAPI.post(author, %{status: "heweoo?"})
1396 {:ok, activity2} = CommonAPI.post(author, %{status: "heweoo!"})
1397
1398 response1 =
1399 conn
1400 |> put_req_header("content-type", "application/json")
1401 |> post("/api/v1/statuses/#{activity1.id}/bookmark")
1402
1403 assert json_response_and_validate_schema(response1, 200)["bookmarked"] == true
1404
1405 response2 =
1406 conn
1407 |> put_req_header("content-type", "application/json")
1408 |> post("/api/v1/statuses/#{activity2.id}/bookmark")
1409
1410 assert json_response_and_validate_schema(response2, 200)["bookmarked"] == true
1411
1412 bookmarks = get(conn, bookmarks_uri)
1413
1414 assert [
1415 json_response_and_validate_schema(response2, 200),
1416 json_response_and_validate_schema(response1, 200)
1417 ] ==
1418 json_response_and_validate_schema(bookmarks, 200)
1419
1420 response1 =
1421 conn
1422 |> put_req_header("content-type", "application/json")
1423 |> post("/api/v1/statuses/#{activity1.id}/unbookmark")
1424
1425 assert json_response_and_validate_schema(response1, 200)["bookmarked"] == false
1426
1427 bookmarks = get(conn, bookmarks_uri)
1428
1429 assert [json_response_and_validate_schema(response2, 200)] ==
1430 json_response_and_validate_schema(bookmarks, 200)
1431 end
1432
1433 describe "conversation muting" do
1434 setup do: oauth_access(["write:mutes"])
1435
1436 setup do
1437 post_user = insert(:user)
1438 {:ok, activity} = CommonAPI.post(post_user, %{status: "HIE"})
1439 %{activity: activity}
1440 end
1441
1442 test "mute conversation", %{conn: conn, activity: activity} do
1443 id_str = to_string(activity.id)
1444
1445 assert %{"id" => ^id_str, "muted" => true} =
1446 conn
1447 |> put_req_header("content-type", "application/json")
1448 |> post("/api/v1/statuses/#{activity.id}/mute")
1449 |> json_response_and_validate_schema(200)
1450 end
1451
1452 test "cannot mute already muted conversation", %{conn: conn, user: user, activity: activity} do
1453 {:ok, _} = CommonAPI.add_mute(user, activity)
1454
1455 conn =
1456 conn
1457 |> put_req_header("content-type", "application/json")
1458 |> post("/api/v1/statuses/#{activity.id}/mute")
1459
1460 assert json_response_and_validate_schema(conn, 400) == %{
1461 "error" => "conversation is already muted"
1462 }
1463 end
1464
1465 test "unmute conversation", %{conn: conn, user: user, activity: activity} do
1466 {:ok, _} = CommonAPI.add_mute(user, activity)
1467
1468 id_str = to_string(activity.id)
1469
1470 assert %{"id" => ^id_str, "muted" => false} =
1471 conn
1472 # |> assign(:user, user)
1473 |> post("/api/v1/statuses/#{activity.id}/unmute")
1474 |> json_response_and_validate_schema(200)
1475 end
1476 end
1477
1478 test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do
1479 user1 = insert(:user)
1480 user2 = insert(:user)
1481 user3 = insert(:user)
1482
1483 {:ok, replied_to} = CommonAPI.post(user1, %{status: "cofe"})
1484
1485 # Reply to status from another user
1486 conn1 =
1487 conn
1488 |> assign(:user, user2)
1489 |> assign(:token, insert(:oauth_token, user: user2, scopes: ["write:statuses"]))
1490 |> put_req_header("content-type", "application/json")
1491 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
1492
1493 assert %{"content" => "xD", "id" => id} = json_response_and_validate_schema(conn1, 200)
1494
1495 activity = Activity.get_by_id_with_object(id)
1496
1497 assert Object.normalize(activity, fetch: false).data["inReplyTo"] ==
1498 Object.normalize(replied_to, fetch: false).data["id"]
1499
1500 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
1501
1502 # Reblog from the third user
1503 conn2 =
1504 conn
1505 |> assign(:user, user3)
1506 |> assign(:token, insert(:oauth_token, user: user3, scopes: ["write:statuses"]))
1507 |> put_req_header("content-type", "application/json")
1508 |> post("/api/v1/statuses/#{activity.id}/reblog")
1509
1510 assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} =
1511 json_response_and_validate_schema(conn2, 200)
1512
1513 assert to_string(activity.id) == id
1514
1515 # Getting third user status
1516 conn3 =
1517 conn
1518 |> assign(:user, user3)
1519 |> assign(:token, insert(:oauth_token, user: user3, scopes: ["read:statuses"]))
1520 |> get("/api/v1/timelines/home")
1521
1522 [reblogged_activity] = json_response_and_validate_schema(conn3, 200)
1523
1524 assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id
1525
1526 replied_to_user = User.get_by_ap_id(replied_to.data["actor"])
1527 assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id
1528 end
1529
1530 describe "GET /api/v1/statuses/:id/favourited_by" do
1531 setup do: oauth_access(["read:accounts"])
1532
1533 setup %{user: user} do
1534 {:ok, activity} = CommonAPI.post(user, %{status: "test"})
1535
1536 %{activity: activity}
1537 end
1538
1539 test "returns users who have favorited the status", %{conn: conn, activity: activity} do
1540 other_user = insert(:user)
1541 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1542
1543 response =
1544 conn
1545 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1546 |> json_response_and_validate_schema(:ok)
1547
1548 [%{"id" => id}] = response
1549
1550 assert id == other_user.id
1551 end
1552
1553 test "returns empty array when status has not been favorited yet", %{
1554 conn: conn,
1555 activity: activity
1556 } do
1557 response =
1558 conn
1559 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1560 |> json_response_and_validate_schema(:ok)
1561
1562 assert Enum.empty?(response)
1563 end
1564
1565 test "does not return users who have favorited the status but are blocked", %{
1566 conn: %{assigns: %{user: user}} = conn,
1567 activity: activity
1568 } do
1569 other_user = insert(:user)
1570 {:ok, _user_relationship} = User.block(user, other_user)
1571
1572 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1573
1574 response =
1575 conn
1576 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1577 |> json_response_and_validate_schema(:ok)
1578
1579 assert Enum.empty?(response)
1580 end
1581
1582 test "does not fail on an unauthenticated request", %{activity: activity} do
1583 other_user = insert(:user)
1584 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1585
1586 response =
1587 build_conn()
1588 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1589 |> json_response_and_validate_schema(:ok)
1590
1591 [%{"id" => id}] = response
1592 assert id == other_user.id
1593 end
1594
1595 test "requires authentication for private posts", %{user: user} do
1596 other_user = insert(:user)
1597
1598 {:ok, activity} =
1599 CommonAPI.post(user, %{
1600 status: "@#{other_user.nickname} wanna get some #cofe together?",
1601 visibility: "direct"
1602 })
1603
1604 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1605
1606 favourited_by_url = "/api/v1/statuses/#{activity.id}/favourited_by"
1607
1608 build_conn()
1609 |> get(favourited_by_url)
1610 |> json_response_and_validate_schema(404)
1611
1612 conn =
1613 build_conn()
1614 |> assign(:user, other_user)
1615 |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"]))
1616
1617 conn
1618 |> assign(:token, nil)
1619 |> get(favourited_by_url)
1620 |> json_response_and_validate_schema(404)
1621
1622 response =
1623 conn
1624 |> get(favourited_by_url)
1625 |> json_response_and_validate_schema(200)
1626
1627 [%{"id" => id}] = response
1628 assert id == other_user.id
1629 end
1630
1631 test "returns empty array when :show_reactions is disabled", %{conn: conn, activity: activity} do
1632 clear_config([:instance, :show_reactions], false)
1633
1634 other_user = insert(:user)
1635 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1636
1637 response =
1638 conn
1639 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1640 |> json_response_and_validate_schema(:ok)
1641
1642 assert Enum.empty?(response)
1643 end
1644 end
1645
1646 describe "GET /api/v1/statuses/:id/reblogged_by" do
1647 setup do: oauth_access(["read:accounts"])
1648
1649 setup %{user: user} do
1650 {:ok, activity} = CommonAPI.post(user, %{status: "test"})
1651
1652 %{activity: activity}
1653 end
1654
1655 test "returns users who have reblogged the status", %{conn: conn, activity: activity} do
1656 other_user = insert(:user)
1657 {:ok, _} = CommonAPI.repeat(activity.id, other_user)
1658
1659 response =
1660 conn
1661 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1662 |> json_response_and_validate_schema(:ok)
1663
1664 [%{"id" => id}] = response
1665
1666 assert id == other_user.id
1667 end
1668
1669 test "returns empty array when status has not been reblogged yet", %{
1670 conn: conn,
1671 activity: activity
1672 } do
1673 response =
1674 conn
1675 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1676 |> json_response_and_validate_schema(:ok)
1677
1678 assert Enum.empty?(response)
1679 end
1680
1681 test "does not return users who have reblogged the status but are blocked", %{
1682 conn: %{assigns: %{user: user}} = conn,
1683 activity: activity
1684 } do
1685 other_user = insert(:user)
1686 {:ok, _user_relationship} = User.block(user, other_user)
1687
1688 {:ok, _} = CommonAPI.repeat(activity.id, other_user)
1689
1690 response =
1691 conn
1692 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1693 |> json_response_and_validate_schema(:ok)
1694
1695 assert Enum.empty?(response)
1696 end
1697
1698 test "does not return users who have reblogged the status privately", %{
1699 conn: conn
1700 } do
1701 other_user = insert(:user)
1702 {:ok, activity} = CommonAPI.post(other_user, %{status: "my secret post"})
1703
1704 {:ok, _} = CommonAPI.repeat(activity.id, other_user, %{visibility: "private"})
1705
1706 response =
1707 conn
1708 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1709 |> json_response_and_validate_schema(:ok)
1710
1711 assert Enum.empty?(response)
1712 end
1713
1714 test "does not fail on an unauthenticated request", %{activity: activity} do
1715 other_user = insert(:user)
1716 {:ok, _} = CommonAPI.repeat(activity.id, other_user)
1717
1718 response =
1719 build_conn()
1720 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1721 |> json_response_and_validate_schema(:ok)
1722
1723 [%{"id" => id}] = response
1724 assert id == other_user.id
1725 end
1726
1727 test "requires authentication for private posts", %{user: user} do
1728 other_user = insert(:user)
1729
1730 {:ok, activity} =
1731 CommonAPI.post(user, %{
1732 status: "@#{other_user.nickname} wanna get some #cofe together?",
1733 visibility: "direct"
1734 })
1735
1736 build_conn()
1737 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1738 |> json_response_and_validate_schema(404)
1739
1740 response =
1741 build_conn()
1742 |> assign(:user, other_user)
1743 |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"]))
1744 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1745 |> json_response_and_validate_schema(200)
1746
1747 assert [] == response
1748 end
1749 end
1750
1751 test "context" do
1752 user = insert(:user)
1753
1754 {:ok, %{id: id1}} = CommonAPI.post(user, %{status: "1"})
1755 {:ok, %{id: id2}} = CommonAPI.post(user, %{status: "2", in_reply_to_status_id: id1})
1756 {:ok, %{id: id3}} = CommonAPI.post(user, %{status: "3", in_reply_to_status_id: id2})
1757 {:ok, %{id: id4}} = CommonAPI.post(user, %{status: "4", in_reply_to_status_id: id3})
1758 {:ok, %{id: id5}} = CommonAPI.post(user, %{status: "5", in_reply_to_status_id: id4})
1759
1760 response =
1761 build_conn()
1762 |> get("/api/v1/statuses/#{id3}/context")
1763 |> json_response_and_validate_schema(:ok)
1764
1765 assert %{
1766 "ancestors" => [%{"id" => ^id1}, %{"id" => ^id2}],
1767 "descendants" => [%{"id" => ^id4}, %{"id" => ^id5}]
1768 } = response
1769 end
1770
1771 test "context when restrict_unauthenticated is on" do
1772 user = insert(:user)
1773 remote_user = insert(:user, local: false)
1774
1775 {:ok, %{id: id1}} = CommonAPI.post(user, %{status: "1"})
1776 {:ok, %{id: id2}} = CommonAPI.post(user, %{status: "2", in_reply_to_status_id: id1})
1777
1778 {:ok, %{id: id3}} =
1779 CommonAPI.post(remote_user, %{status: "3", in_reply_to_status_id: id2, local: false})
1780
1781 response =
1782 build_conn()
1783 |> get("/api/v1/statuses/#{id2}/context")
1784 |> json_response_and_validate_schema(:ok)
1785
1786 assert %{
1787 "ancestors" => [%{"id" => ^id1}],
1788 "descendants" => [%{"id" => ^id3}]
1789 } = response
1790
1791 clear_config([:restrict_unauthenticated, :activities, :local], true)
1792
1793 response =
1794 build_conn()
1795 |> get("/api/v1/statuses/#{id2}/context")
1796 |> json_response_and_validate_schema(:ok)
1797
1798 assert %{
1799 "ancestors" => [],
1800 "descendants" => []
1801 } = response
1802 end
1803
1804 test "favorites paginate correctly" do
1805 %{user: user, conn: conn} = oauth_access(["read:favourites"])
1806 other_user = insert(:user)
1807 {:ok, first_post} = CommonAPI.post(other_user, %{status: "bla"})
1808 {:ok, second_post} = CommonAPI.post(other_user, %{status: "bla"})
1809 {:ok, third_post} = CommonAPI.post(other_user, %{status: "bla"})
1810
1811 {:ok, _first_favorite} = CommonAPI.favorite(user, third_post.id)
1812 {:ok, _second_favorite} = CommonAPI.favorite(user, first_post.id)
1813 {:ok, third_favorite} = CommonAPI.favorite(user, second_post.id)
1814
1815 result =
1816 conn
1817 |> get("/api/v1/favourites?limit=1")
1818
1819 assert [%{"id" => post_id}] = json_response_and_validate_schema(result, 200)
1820 assert post_id == second_post.id
1821
1822 # Using the header for pagination works correctly
1823 [next, _] = get_resp_header(result, "link") |> hd() |> String.split(", ")
1824 [_, max_id] = Regex.run(~r/max_id=([^&]+)/, next)
1825
1826 assert max_id == third_favorite.id
1827
1828 result =
1829 conn
1830 |> get("/api/v1/favourites?max_id=#{max_id}")
1831
1832 assert [%{"id" => first_post_id}, %{"id" => third_post_id}] =
1833 json_response_and_validate_schema(result, 200)
1834
1835 assert first_post_id == first_post.id
1836 assert third_post_id == third_post.id
1837 end
1838
1839 test "returns the favorites of a user" do
1840 %{user: user, conn: conn} = oauth_access(["read:favourites"])
1841 other_user = insert(:user)
1842
1843 {:ok, _} = CommonAPI.post(other_user, %{status: "bla"})
1844 {:ok, activity} = CommonAPI.post(other_user, %{status: "trees are happy"})
1845
1846 {:ok, last_like} = CommonAPI.favorite(user, activity.id)
1847
1848 first_conn = get(conn, "/api/v1/favourites")
1849
1850 assert [status] = json_response_and_validate_schema(first_conn, 200)
1851 assert status["id"] == to_string(activity.id)
1852
1853 assert [{"link", _link_header}] =
1854 Enum.filter(first_conn.resp_headers, fn element -> match?({"link", _}, element) end)
1855
1856 # Honours query params
1857 {:ok, second_activity} =
1858 CommonAPI.post(other_user, %{
1859 status: "Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful."
1860 })
1861
1862 {:ok, _} = CommonAPI.favorite(user, second_activity.id)
1863
1864 second_conn = get(conn, "/api/v1/favourites?since_id=#{last_like.id}")
1865
1866 assert [second_status] = json_response_and_validate_schema(second_conn, 200)
1867 assert second_status["id"] == to_string(second_activity.id)
1868
1869 third_conn = get(conn, "/api/v1/favourites?limit=0")
1870
1871 assert [] = json_response_and_validate_schema(third_conn, 200)
1872 end
1873
1874 test "expires_at is nil for another user" do
1875 %{conn: conn, user: user} = oauth_access(["read:statuses"])
1876 expires_at = DateTime.add(DateTime.utc_now(), 1_000_000)
1877 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", expires_in: 1_000_000})
1878
1879 assert %{"pleroma" => %{"expires_at" => a_expires_at}} =
1880 conn
1881 |> get("/api/v1/statuses/#{activity.id}")
1882 |> json_response_and_validate_schema(:ok)
1883
1884 {:ok, a_expires_at, 0} = DateTime.from_iso8601(a_expires_at)
1885 assert DateTime.diff(expires_at, a_expires_at) == 0
1886
1887 %{conn: conn} = oauth_access(["read:statuses"])
1888
1889 assert %{"pleroma" => %{"expires_at" => nil}} =
1890 conn
1891 |> get("/api/v1/statuses/#{activity.id}")
1892 |> json_response_and_validate_schema(:ok)
1893 end
1894
1895 describe "local-only statuses" do
1896 test "posting a local only status" do
1897 %{user: _user, conn: conn} = oauth_access(["write:statuses"])
1898
1899 conn_one =
1900 conn
1901 |> put_req_header("content-type", "application/json")
1902 |> post("/api/v1/statuses", %{
1903 "status" => "cofe",
1904 "visibility" => "local"
1905 })
1906
1907 local = Utils.as_local_public()
1908
1909 assert %{"content" => "cofe", "id" => id, "visibility" => "local"} =
1910 json_response_and_validate_schema(conn_one, 200)
1911
1912 assert %Activity{id: ^id, data: %{"to" => [^local]}} = Activity.get_by_id(id)
1913 end
1914
1915 test "other users can read local-only posts" do
1916 user = insert(:user)
1917 %{user: _reader, conn: conn} = oauth_access(["read:statuses"])
1918
1919 {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
1920
1921 received =
1922 conn
1923 |> get("/api/v1/statuses/#{activity.id}")
1924 |> json_response_and_validate_schema(:ok)
1925
1926 assert received["id"] == activity.id
1927 end
1928
1929 test "anonymous users cannot see local-only posts" do
1930 user = insert(:user)
1931
1932 {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
1933
1934 _received =
1935 build_conn()
1936 |> get("/api/v1/statuses/#{activity.id}")
1937 |> json_response_and_validate_schema(:not_found)
1938 end
1939 end
1940
1941 describe "muted reactions" do
1942 test "index" do
1943 %{conn: conn, user: user} = oauth_access(["read:statuses"])
1944
1945 other_user = insert(:user)
1946 {:ok, activity} = CommonAPI.post(user, %{status: "test"})
1947
1948 {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
1949 User.mute(user, other_user)
1950
1951 result =
1952 conn
1953 |> get("/api/v1/statuses/?ids[]=#{activity.id}")
1954 |> json_response_and_validate_schema(200)
1955
1956 assert [
1957 %{
1958 "pleroma" => %{
1959 "emoji_reactions" => []
1960 }
1961 }
1962 ] = result
1963
1964 result =
1965 conn
1966 |> get("/api/v1/statuses/?ids[]=#{activity.id}&with_muted=true")
1967 |> json_response_and_validate_schema(200)
1968
1969 assert [
1970 %{
1971 "pleroma" => %{
1972 "emoji_reactions" => [%{"count" => 1, "me" => false, "name" => "🎅"}]
1973 }
1974 }
1975 ] = result
1976 end
1977
1978 test "show" do
1979 # %{conn: conn, user: user, token: token} = oauth_access(["read:statuses"])
1980 %{conn: conn, user: user, token: _token} = oauth_access(["read:statuses"])
1981
1982 other_user = insert(:user)
1983 {:ok, activity} = CommonAPI.post(user, %{status: "test"})
1984
1985 {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
1986 User.mute(user, other_user)
1987
1988 result =
1989 conn
1990 |> get("/api/v1/statuses/#{activity.id}")
1991 |> json_response_and_validate_schema(200)
1992
1993 assert %{
1994 "pleroma" => %{
1995 "emoji_reactions" => []
1996 }
1997 } = result
1998
1999 result =
2000 conn
2001 |> get("/api/v1/statuses/#{activity.id}?with_muted=true")
2002 |> json_response_and_validate_schema(200)
2003
2004 assert %{
2005 "pleroma" => %{
2006 "emoji_reactions" => [%{"count" => 1, "me" => false, "name" => "🎅"}]
2007 }
2008 } = result
2009 end
2010 end
2011
2012 describe "posting quotes" do
2013 setup do: oauth_access(["write:statuses"])
2014
2015 test "posting a quote", %{conn: conn} do
2016 user = insert(:user)
2017 {:ok, quoted_status} = CommonAPI.post(user, %{status: "tell me, for whom do you fight?"})
2018
2019 conn =
2020 conn
2021 |> put_req_header("content-type", "application/json")
2022 |> post("/api/v1/statuses", %{
2023 "status" => "Hmph, how very glib",
2024 "quote_id" => quoted_status.id
2025 })
2026
2027 response = json_response_and_validate_schema(conn, 200)
2028
2029 assert response["quote_id"] == quoted_status.id
2030 assert response["quote"]["id"] == quoted_status.id
2031 assert response["quote"]["content"] == quoted_status.object.data["content"]
2032 assert response["pleroma"]["context"] == quoted_status.data["context"]
2033 end
2034
2035 test "posting a quote, quoting a status that isn't public", %{conn: conn} do
2036 user = insert(:user)
2037
2038 Enum.each(["private", "local", "direct"], fn visibility ->
2039 {:ok, quoted_status} =
2040 CommonAPI.post(user, %{
2041 status: "tell me, for whom do you fight?",
2042 visibility: visibility
2043 })
2044
2045 assert %{"error" => "You can only quote public or unlisted statuses"} =
2046 conn
2047 |> put_req_header("content-type", "application/json")
2048 |> post("/api/v1/statuses", %{
2049 "status" => "Hmph, how very glib",
2050 "quote_id" => quoted_status.id
2051 })
2052 |> json_response_and_validate_schema(422)
2053 end)
2054 end
2055
2056 test "posting a quote, after quote, the status gets deleted", %{conn: conn} do
2057 user = insert(:user)
2058
2059 {:ok, quoted_status} =
2060 CommonAPI.post(user, %{status: "tell me, for whom do you fight?", visibility: "public"})
2061
2062 resp =
2063 conn
2064 |> put_req_header("content-type", "application/json")
2065 |> post("/api/v1/statuses", %{
2066 "status" => "I fight for eorzea!",
2067 "quote_id" => quoted_status.id
2068 })
2069 |> json_response_and_validate_schema(200)
2070
2071 {:ok, _} = CommonAPI.delete(quoted_status.id, user)
2072
2073 resp =
2074 conn
2075 |> get("/api/v1/statuses/#{resp["id"]}")
2076 |> json_response_and_validate_schema(200)
2077
2078 assert is_nil(resp["quote"])
2079 end
2080
2081 test "posting a quote of a deleted status", %{conn: conn} do
2082 user = insert(:user)
2083
2084 {:ok, quoted_status} =
2085 CommonAPI.post(user, %{status: "tell me, for whom do you fight?", visibility: "public"})
2086
2087 {:ok, _} = CommonAPI.delete(quoted_status.id, user)
2088
2089 assert %{"error" => _} =
2090 conn
2091 |> put_req_header("content-type", "application/json")
2092 |> post("/api/v1/statuses", %{
2093 "status" => "I fight for eorzea!",
2094 "quote_id" => quoted_status.id
2095 })
2096 |> json_response_and_validate_schema(422)
2097 end
2098
2099 test "posting a quote of a status that doesn't exist", %{conn: conn} do
2100 assert %{"error" => "You can't quote a status that doesn't exist"} =
2101 conn
2102 |> put_req_header("content-type", "application/json")
2103 |> post("/api/v1/statuses", %{
2104 "status" => "I fight for eorzea!",
2105 "quote_id" => "oops"
2106 })
2107 |> json_response_and_validate_schema(422)
2108 end
2109 end
2110
2111 describe "get status history" do
2112 setup do
2113 %{conn: build_conn()}
2114 end
2115
2116 test "unedited post", %{conn: conn} do
2117 activity = insert(:note_activity)
2118
2119 conn = get(conn, "/api/v1/statuses/#{activity.id}/history")
2120
2121 assert [_] = json_response_and_validate_schema(conn, 200)
2122 end
2123
2124 test "edited post", %{conn: conn} do
2125 note =
2126 insert(
2127 :note,
2128 data: %{
2129 "formerRepresentations" => %{
2130 "type" => "OrderedCollection",
2131 "orderedItems" => [
2132 %{
2133 "type" => "Note",
2134 "content" => "mew mew 2",
2135 "summary" => "title 2"
2136 },
2137 %{
2138 "type" => "Note",
2139 "content" => "mew mew 1",
2140 "summary" => "title 1"
2141 }
2142 ],
2143 "totalItems" => 2
2144 }
2145 }
2146 )
2147
2148 activity = insert(:note_activity, note: note)
2149
2150 conn = get(conn, "/api/v1/statuses/#{activity.id}/history")
2151
2152 assert [%{"spoiler_text" => "title 1"}, %{"spoiler_text" => "title 2"}, _] =
2153 json_response_and_validate_schema(conn, 200)
2154 end
2155 end
2156
2157 describe "translating statuses" do
2158 setup do
2159 clear_config([:translator, :enabled], true)
2160 clear_config([:translator, :module], Pleroma.Akkoma.Translators.DeepL)
2161 clear_config([:deepl, :api_key], "deepl_api_key")
2162 oauth_access(["read:statuses"])
2163 end
2164
2165 test "listing languages", %{conn: conn} do
2166 Tesla.Mock.mock_global(fn
2167 %{method: :get, url: "https://api-free.deepl.com/v2/languages?type=source"} ->
2168 %Tesla.Env{
2169 status: 200,
2170 body:
2171 Jason.encode!([
2172 %{language: "en", name: "English"}
2173 ])
2174 }
2175
2176 %{method: :get, url: "https://api-free.deepl.com/v2/languages?type=target"} ->
2177 %Tesla.Env{
2178 status: 200,
2179 body:
2180 Jason.encode!([
2181 %{language: "ja", name: "Japanese"}
2182 ])
2183 }
2184 end)
2185
2186 conn =
2187 conn
2188 |> put_req_header("content-type", "application/json")
2189 |> get("/api/v1/akkoma/translation/languages")
2190
2191 response = json_response_and_validate_schema(conn, 200)
2192
2193 assert %{
2194 "source" => [%{"code" => "en", "name" => "English"}],
2195 "target" => [%{"code" => "ja", "name" => "Japanese"}]
2196 } = response
2197 end
2198
2199 test "should return text and detected language", %{conn: conn} do
2200 clear_config([:deepl, :tier], :free)
2201
2202 Tesla.Mock.mock_global(fn
2203 %{method: :post, url: "https://api-free.deepl.com/v2/translate"} ->
2204 %Tesla.Env{
2205 status: 200,
2206 body:
2207 Jason.encode!(%{
2208 translations: [
2209 %{
2210 "text" => "Tell me, for whom do you fight?",
2211 "detected_source_language" => "ja"
2212 }
2213 ]
2214 })
2215 }
2216 end)
2217
2218 user = insert(:user)
2219 {:ok, to_translate} = CommonAPI.post(user, %{status: "何のために闘う?"})
2220
2221 conn =
2222 conn
2223 |> put_req_header("content-type", "application/json")
2224 |> get("/api/v1/statuses/#{to_translate.id}/translations/en")
2225
2226 response = json_response_and_validate_schema(conn, 200)
2227
2228 assert response["text"] == "Tell me, for whom do you fight?"
2229 assert response["detected_language"] == "ja"
2230 end
2231
2232 test "should not allow translating of statuses you cannot see", %{conn: conn} do
2233 clear_config([:deepl, :tier], :free)
2234
2235 Tesla.Mock.mock_global(fn
2236 %{method: :post, url: "https://api-free.deepl.com/v2/translate"} ->
2237 %Tesla.Env{
2238 status: 200,
2239 body:
2240 Jason.encode!(%{
2241 translations: [
2242 %{
2243 "text" => "Tell me, for whom do you fight?",
2244 "detected_source_language" => "ja"
2245 }
2246 ]
2247 })
2248 }
2249 end)
2250
2251 user = insert(:user)
2252 {:ok, to_translate} = CommonAPI.post(user, %{status: "何のために闘う?", visibility: "private"})
2253
2254 conn =
2255 conn
2256 |> put_req_header("content-type", "application/json")
2257 |> get("/api/v1/statuses/#{to_translate.id}/translations/en")
2258
2259 json_response_and_validate_schema(conn, 404)
2260 end
2261 end
2262
2263 describe "get status source" do
2264 setup do
2265 %{conn: build_conn()}
2266 end
2267
2268 test "it returns the source", %{conn: conn} do
2269 user = insert(:user)
2270
2271 {:ok, activity} = CommonAPI.post(user, %{status: "mew mew #abc", spoiler_text: "#def"})
2272
2273 conn = get(conn, "/api/v1/statuses/#{activity.id}/source")
2274
2275 id = activity.id
2276
2277 assert %{"id" => ^id, "text" => "mew mew #abc", "spoiler_text" => "#def"} =
2278 json_response_and_validate_schema(conn, 200)
2279 end
2280 end
2281
2282 describe "update status" do
2283 setup do
2284 oauth_access(["write:statuses"])
2285 end
2286
2287 test "it updates the status" do
2288 %{conn: conn, user: user} = oauth_access(["write:statuses", "read:statuses"])
2289
2290 {:ok, activity} = CommonAPI.post(user, %{status: "mew mew #abc", spoiler_text: "#def"})
2291
2292 conn
2293 |> get("/api/v1/statuses/#{activity.id}")
2294 |> json_response_and_validate_schema(200)
2295
2296 response =
2297 conn
2298 |> put_req_header("content-type", "application/json")
2299 |> put("/api/v1/statuses/#{activity.id}", %{
2300 "status" => "edited",
2301 "spoiler_text" => "lol"
2302 })
2303 |> json_response_and_validate_schema(200)
2304
2305 assert response["content"] == "edited"
2306 assert response["spoiler_text"] == "lol"
2307
2308 response =
2309 conn
2310 |> get("/api/v1/statuses/#{activity.id}")
2311 |> json_response_and_validate_schema(200)
2312
2313 assert response["content"] == "edited"
2314 assert response["spoiler_text"] == "lol"
2315 end
2316
2317 test "it updates the attachments", %{conn: conn, user: user} do
2318 attachment = insert(:attachment, user: user)
2319 attachment_id = to_string(attachment.id)
2320
2321 {:ok, activity} = CommonAPI.post(user, %{status: "mew mew #abc", spoiler_text: "#def"})
2322
2323 response =
2324 conn
2325 |> put_req_header("content-type", "application/json")
2326 |> put("/api/v1/statuses/#{activity.id}", %{
2327 "status" => "mew mew #abc",
2328 "spoiler_text" => "#def",
2329 "media_ids" => [attachment_id]
2330 })
2331 |> json_response_and_validate_schema(200)
2332
2333 assert [%{"id" => ^attachment_id}] = response["media_attachments"]
2334 end
2335
2336 test "it does not update visibility", %{conn: conn, user: user} do
2337 {:ok, activity} =
2338 CommonAPI.post(user, %{
2339 status: "mew mew #abc",
2340 spoiler_text: "#def",
2341 visibility: "private"
2342 })
2343
2344 response =
2345 conn
2346 |> put_req_header("content-type", "application/json")
2347 |> put("/api/v1/statuses/#{activity.id}", %{
2348 "status" => "edited",
2349 "spoiler_text" => "lol"
2350 })
2351 |> json_response_and_validate_schema(200)
2352
2353 assert response["visibility"] == "private"
2354 end
2355
2356 test "it refuses to update when original post is not by the user", %{conn: conn} do
2357 another_user = insert(:user)
2358
2359 {:ok, activity} =
2360 CommonAPI.post(another_user, %{status: "mew mew #abc", spoiler_text: "#def"})
2361
2362 conn
2363 |> put_req_header("content-type", "application/json")
2364 |> put("/api/v1/statuses/#{activity.id}", %{
2365 "status" => "edited",
2366 "spoiler_text" => "lol"
2367 })
2368 |> json_response_and_validate_schema(:forbidden)
2369 end
2370
2371 test "it returns 404 if the user cannot see the post", %{conn: conn} do
2372 another_user = insert(:user)
2373
2374 {:ok, activity} =
2375 CommonAPI.post(another_user, %{
2376 status: "mew mew #abc",
2377 spoiler_text: "#def",
2378 visibility: "private"
2379 })
2380
2381 conn
2382 |> put_req_header("content-type", "application/json")
2383 |> put("/api/v1/statuses/#{activity.id}", %{
2384 "status" => "edited",
2385 "spoiler_text" => "lol"
2386 })
2387 |> json_response_and_validate_schema(:not_found)
2388 end
2389 end
2390 end