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