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