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