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