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