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