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