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