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