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