Merge branch 'cleanup/unused-proxy-opts' into 'develop'
[akkoma] / test / pleroma / web / mastodon_api / controllers / status_controller_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 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 end
958
959 describe "unreblogging" do
960 setup do: oauth_access(["write:statuses"])
961
962 test "unreblogs and returns the unreblogged status", %{user: user, conn: conn} do
963 activity = insert(:note_activity)
964
965 {:ok, _} = CommonAPI.repeat(activity.id, user)
966
967 conn =
968 conn
969 |> put_req_header("content-type", "application/json")
970 |> post("/api/v1/statuses/#{activity.id}/unreblog")
971
972 assert %{"id" => id, "reblogged" => false, "reblogs_count" => 0} =
973 json_response_and_validate_schema(conn, 200)
974
975 assert to_string(activity.id) == id
976 end
977
978 test "returns 404 error when activity does not exist", %{conn: conn} do
979 conn =
980 conn
981 |> put_req_header("content-type", "application/json")
982 |> post("/api/v1/statuses/foo/unreblog")
983
984 assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
985 end
986 end
987
988 describe "favoriting" do
989 setup do: oauth_access(["write:favourites"])
990
991 test "favs a status and returns it", %{conn: conn} do
992 activity = insert(:note_activity)
993
994 conn =
995 conn
996 |> put_req_header("content-type", "application/json")
997 |> post("/api/v1/statuses/#{activity.id}/favourite")
998
999 assert %{"id" => id, "favourites_count" => 1, "favourited" => true} =
1000 json_response_and_validate_schema(conn, 200)
1001
1002 assert to_string(activity.id) == id
1003 end
1004
1005 test "favoriting twice will just return 200", %{conn: conn} do
1006 activity = insert(:note_activity)
1007
1008 conn
1009 |> put_req_header("content-type", "application/json")
1010 |> post("/api/v1/statuses/#{activity.id}/favourite")
1011
1012 assert conn
1013 |> put_req_header("content-type", "application/json")
1014 |> post("/api/v1/statuses/#{activity.id}/favourite")
1015 |> json_response_and_validate_schema(200)
1016 end
1017
1018 test "returns 404 error for a wrong id", %{conn: conn} do
1019 conn =
1020 conn
1021 |> put_req_header("content-type", "application/json")
1022 |> post("/api/v1/statuses/1/favourite")
1023
1024 assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
1025 end
1026 end
1027
1028 describe "unfavoriting" do
1029 setup do: oauth_access(["write:favourites"])
1030
1031 test "unfavorites a status and returns it", %{user: user, conn: conn} do
1032 activity = insert(:note_activity)
1033
1034 {:ok, _} = CommonAPI.favorite(user, activity.id)
1035
1036 conn =
1037 conn
1038 |> put_req_header("content-type", "application/json")
1039 |> post("/api/v1/statuses/#{activity.id}/unfavourite")
1040
1041 assert %{"id" => id, "favourites_count" => 0, "favourited" => false} =
1042 json_response_and_validate_schema(conn, 200)
1043
1044 assert to_string(activity.id) == id
1045 end
1046
1047 test "returns 404 error for a wrong id", %{conn: conn} do
1048 conn =
1049 conn
1050 |> put_req_header("content-type", "application/json")
1051 |> post("/api/v1/statuses/1/unfavourite")
1052
1053 assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
1054 end
1055 end
1056
1057 describe "pinned statuses" do
1058 setup do: oauth_access(["write:accounts"])
1059
1060 setup %{user: user} do
1061 {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!"})
1062
1063 %{activity: activity}
1064 end
1065
1066 setup do: clear_config([:instance, :max_pinned_statuses], 1)
1067
1068 test "pin status", %{conn: conn, user: user, activity: activity} do
1069 id_str = to_string(activity.id)
1070
1071 assert %{"id" => ^id_str, "pinned" => true} =
1072 conn
1073 |> put_req_header("content-type", "application/json")
1074 |> post("/api/v1/statuses/#{activity.id}/pin")
1075 |> json_response_and_validate_schema(200)
1076
1077 assert [%{"id" => ^id_str, "pinned" => true}] =
1078 conn
1079 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
1080 |> json_response_and_validate_schema(200)
1081 end
1082
1083 test "/pin: returns 400 error when activity is not public", %{conn: conn, user: user} do
1084 {:ok, dm} = CommonAPI.post(user, %{status: "test", visibility: "direct"})
1085
1086 conn =
1087 conn
1088 |> put_req_header("content-type", "application/json")
1089 |> post("/api/v1/statuses/#{dm.id}/pin")
1090
1091 assert json_response_and_validate_schema(conn, 400) == %{"error" => "Could not pin"}
1092 end
1093
1094 test "unpin status", %{conn: conn, user: user, activity: activity} do
1095 {:ok, _} = CommonAPI.pin(activity.id, user)
1096 user = refresh_record(user)
1097
1098 id_str = to_string(activity.id)
1099
1100 assert %{"id" => ^id_str, "pinned" => false} =
1101 conn
1102 |> assign(:user, user)
1103 |> post("/api/v1/statuses/#{activity.id}/unpin")
1104 |> json_response_and_validate_schema(200)
1105
1106 assert [] =
1107 conn
1108 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
1109 |> json_response_and_validate_schema(200)
1110 end
1111
1112 test "/unpin: returns 400 error when activity is not exist", %{conn: conn} do
1113 conn =
1114 conn
1115 |> put_req_header("content-type", "application/json")
1116 |> post("/api/v1/statuses/1/unpin")
1117
1118 assert json_response_and_validate_schema(conn, 400) == %{"error" => "Could not unpin"}
1119 end
1120
1121 test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
1122 {:ok, activity_two} = CommonAPI.post(user, %{status: "HI!!!"})
1123
1124 id_str_one = to_string(activity_one.id)
1125
1126 assert %{"id" => ^id_str_one, "pinned" => true} =
1127 conn
1128 |> put_req_header("content-type", "application/json")
1129 |> post("/api/v1/statuses/#{id_str_one}/pin")
1130 |> json_response_and_validate_schema(200)
1131
1132 user = refresh_record(user)
1133
1134 assert %{"error" => "You have already pinned the maximum number of statuses"} =
1135 conn
1136 |> assign(:user, user)
1137 |> post("/api/v1/statuses/#{activity_two.id}/pin")
1138 |> json_response_and_validate_schema(400)
1139 end
1140
1141 test "on pin removes deletion job, on unpin reschedule deletion" do
1142 %{conn: conn} = oauth_access(["write:accounts", "write:statuses"])
1143 expires_in = 2 * 60 * 60
1144
1145 expires_at = DateTime.add(DateTime.utc_now(), expires_in)
1146
1147 assert %{"id" => id} =
1148 conn
1149 |> put_req_header("content-type", "application/json")
1150 |> post("api/v1/statuses", %{
1151 "status" => "oolong",
1152 "expires_in" => expires_in
1153 })
1154 |> json_response_and_validate_schema(200)
1155
1156 assert_enqueued(
1157 worker: Pleroma.Workers.PurgeExpiredActivity,
1158 args: %{activity_id: id},
1159 scheduled_at: expires_at
1160 )
1161
1162 assert %{"id" => ^id, "pinned" => true} =
1163 conn
1164 |> put_req_header("content-type", "application/json")
1165 |> post("/api/v1/statuses/#{id}/pin")
1166 |> json_response_and_validate_schema(200)
1167
1168 refute_enqueued(
1169 worker: Pleroma.Workers.PurgeExpiredActivity,
1170 args: %{activity_id: id},
1171 scheduled_at: expires_at
1172 )
1173
1174 assert %{"id" => ^id, "pinned" => false} =
1175 conn
1176 |> put_req_header("content-type", "application/json")
1177 |> post("/api/v1/statuses/#{id}/unpin")
1178 |> json_response_and_validate_schema(200)
1179
1180 assert_enqueued(
1181 worker: Pleroma.Workers.PurgeExpiredActivity,
1182 args: %{activity_id: id},
1183 scheduled_at: expires_at
1184 )
1185 end
1186 end
1187
1188 describe "cards" do
1189 setup do
1190 Config.put([:rich_media, :enabled], true)
1191
1192 oauth_access(["read:statuses"])
1193 end
1194
1195 test "returns rich-media card", %{conn: conn, user: user} do
1196 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
1197
1198 {:ok, activity} = CommonAPI.post(user, %{status: "https://example.com/ogp"})
1199
1200 card_data = %{
1201 "image" => "http://ia.media-imdb.com/images/rock.jpg",
1202 "provider_name" => "example.com",
1203 "provider_url" => "https://example.com",
1204 "title" => "The Rock",
1205 "type" => "link",
1206 "url" => "https://example.com/ogp",
1207 "description" =>
1208 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
1209 "pleroma" => %{
1210 "opengraph" => %{
1211 "image" => "http://ia.media-imdb.com/images/rock.jpg",
1212 "title" => "The Rock",
1213 "type" => "video.movie",
1214 "url" => "https://example.com/ogp",
1215 "description" =>
1216 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer."
1217 }
1218 }
1219 }
1220
1221 response =
1222 conn
1223 |> get("/api/v1/statuses/#{activity.id}/card")
1224 |> json_response_and_validate_schema(200)
1225
1226 assert response == card_data
1227
1228 # works with private posts
1229 {:ok, activity} =
1230 CommonAPI.post(user, %{status: "https://example.com/ogp", visibility: "direct"})
1231
1232 response_two =
1233 conn
1234 |> get("/api/v1/statuses/#{activity.id}/card")
1235 |> json_response_and_validate_schema(200)
1236
1237 assert response_two == card_data
1238 end
1239
1240 test "replaces missing description with an empty string", %{conn: conn, user: user} do
1241 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
1242
1243 {:ok, activity} = CommonAPI.post(user, %{status: "https://example.com/ogp-missing-data"})
1244
1245 response =
1246 conn
1247 |> get("/api/v1/statuses/#{activity.id}/card")
1248 |> json_response_and_validate_schema(:ok)
1249
1250 assert response == %{
1251 "type" => "link",
1252 "title" => "Pleroma",
1253 "description" => "",
1254 "image" => nil,
1255 "provider_name" => "example.com",
1256 "provider_url" => "https://example.com",
1257 "url" => "https://example.com/ogp-missing-data",
1258 "pleroma" => %{
1259 "opengraph" => %{
1260 "title" => "Pleroma",
1261 "type" => "website",
1262 "url" => "https://example.com/ogp-missing-data"
1263 }
1264 }
1265 }
1266 end
1267 end
1268
1269 test "bookmarks" do
1270 bookmarks_uri = "/api/v1/bookmarks"
1271
1272 %{conn: conn} = oauth_access(["write:bookmarks", "read:bookmarks"])
1273 author = insert(:user)
1274
1275 {:ok, activity1} = CommonAPI.post(author, %{status: "heweoo?"})
1276 {:ok, activity2} = CommonAPI.post(author, %{status: "heweoo!"})
1277
1278 response1 =
1279 conn
1280 |> put_req_header("content-type", "application/json")
1281 |> post("/api/v1/statuses/#{activity1.id}/bookmark")
1282
1283 assert json_response_and_validate_schema(response1, 200)["bookmarked"] == true
1284
1285 response2 =
1286 conn
1287 |> put_req_header("content-type", "application/json")
1288 |> post("/api/v1/statuses/#{activity2.id}/bookmark")
1289
1290 assert json_response_and_validate_schema(response2, 200)["bookmarked"] == true
1291
1292 bookmarks = get(conn, bookmarks_uri)
1293
1294 assert [
1295 json_response_and_validate_schema(response2, 200),
1296 json_response_and_validate_schema(response1, 200)
1297 ] ==
1298 json_response_and_validate_schema(bookmarks, 200)
1299
1300 response1 =
1301 conn
1302 |> put_req_header("content-type", "application/json")
1303 |> post("/api/v1/statuses/#{activity1.id}/unbookmark")
1304
1305 assert json_response_and_validate_schema(response1, 200)["bookmarked"] == false
1306
1307 bookmarks = get(conn, bookmarks_uri)
1308
1309 assert [json_response_and_validate_schema(response2, 200)] ==
1310 json_response_and_validate_schema(bookmarks, 200)
1311 end
1312
1313 describe "conversation muting" do
1314 setup do: oauth_access(["write:mutes"])
1315
1316 setup do
1317 post_user = insert(:user)
1318 {:ok, activity} = CommonAPI.post(post_user, %{status: "HIE"})
1319 %{activity: activity}
1320 end
1321
1322 test "mute conversation", %{conn: conn, activity: activity} do
1323 id_str = to_string(activity.id)
1324
1325 assert %{"id" => ^id_str, "muted" => true} =
1326 conn
1327 |> put_req_header("content-type", "application/json")
1328 |> post("/api/v1/statuses/#{activity.id}/mute")
1329 |> json_response_and_validate_schema(200)
1330 end
1331
1332 test "cannot mute already muted conversation", %{conn: conn, user: user, activity: activity} do
1333 {:ok, _} = CommonAPI.add_mute(user, activity)
1334
1335 conn =
1336 conn
1337 |> put_req_header("content-type", "application/json")
1338 |> post("/api/v1/statuses/#{activity.id}/mute")
1339
1340 assert json_response_and_validate_schema(conn, 400) == %{
1341 "error" => "conversation is already muted"
1342 }
1343 end
1344
1345 test "unmute conversation", %{conn: conn, user: user, activity: activity} do
1346 {:ok, _} = CommonAPI.add_mute(user, activity)
1347
1348 id_str = to_string(activity.id)
1349
1350 assert %{"id" => ^id_str, "muted" => false} =
1351 conn
1352 # |> assign(:user, user)
1353 |> post("/api/v1/statuses/#{activity.id}/unmute")
1354 |> json_response_and_validate_schema(200)
1355 end
1356 end
1357
1358 test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do
1359 user1 = insert(:user)
1360 user2 = insert(:user)
1361 user3 = insert(:user)
1362
1363 {:ok, replied_to} = CommonAPI.post(user1, %{status: "cofe"})
1364
1365 # Reply to status from another user
1366 conn1 =
1367 conn
1368 |> assign(:user, user2)
1369 |> assign(:token, insert(:oauth_token, user: user2, scopes: ["write:statuses"]))
1370 |> put_req_header("content-type", "application/json")
1371 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
1372
1373 assert %{"content" => "xD", "id" => id} = json_response_and_validate_schema(conn1, 200)
1374
1375 activity = Activity.get_by_id_with_object(id)
1376
1377 assert Object.normalize(activity, fetch: false).data["inReplyTo"] ==
1378 Object.normalize(replied_to, fetch: false).data["id"]
1379
1380 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
1381
1382 # Reblog from the third user
1383 conn2 =
1384 conn
1385 |> assign(:user, user3)
1386 |> assign(:token, insert(:oauth_token, user: user3, scopes: ["write:statuses"]))
1387 |> put_req_header("content-type", "application/json")
1388 |> post("/api/v1/statuses/#{activity.id}/reblog")
1389
1390 assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} =
1391 json_response_and_validate_schema(conn2, 200)
1392
1393 assert to_string(activity.id) == id
1394
1395 # Getting third user status
1396 conn3 =
1397 conn
1398 |> assign(:user, user3)
1399 |> assign(:token, insert(:oauth_token, user: user3, scopes: ["read:statuses"]))
1400 |> get("api/v1/timelines/home")
1401
1402 [reblogged_activity] = json_response(conn3, 200)
1403
1404 assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id
1405
1406 replied_to_user = User.get_by_ap_id(replied_to.data["actor"])
1407 assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id
1408 end
1409
1410 describe "GET /api/v1/statuses/:id/favourited_by" do
1411 setup do: oauth_access(["read:accounts"])
1412
1413 setup %{user: user} do
1414 {:ok, activity} = CommonAPI.post(user, %{status: "test"})
1415
1416 %{activity: activity}
1417 end
1418
1419 test "returns users who have favorited the status", %{conn: conn, activity: activity} do
1420 other_user = insert(:user)
1421 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1422
1423 response =
1424 conn
1425 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1426 |> json_response_and_validate_schema(:ok)
1427
1428 [%{"id" => id}] = response
1429
1430 assert id == other_user.id
1431 end
1432
1433 test "returns empty array when status has not been favorited yet", %{
1434 conn: conn,
1435 activity: activity
1436 } do
1437 response =
1438 conn
1439 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1440 |> json_response_and_validate_schema(:ok)
1441
1442 assert Enum.empty?(response)
1443 end
1444
1445 test "does not return users who have favorited the status but are blocked", %{
1446 conn: %{assigns: %{user: user}} = conn,
1447 activity: activity
1448 } do
1449 other_user = insert(:user)
1450 {:ok, _user_relationship} = User.block(user, other_user)
1451
1452 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1453
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 fail on an unauthenticated request", %{activity: activity} do
1463 other_user = insert(:user)
1464 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1465
1466 response =
1467 build_conn()
1468 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1469 |> json_response_and_validate_schema(:ok)
1470
1471 [%{"id" => id}] = response
1472 assert id == other_user.id
1473 end
1474
1475 test "requires authentication for private posts", %{user: user} do
1476 other_user = insert(:user)
1477
1478 {:ok, activity} =
1479 CommonAPI.post(user, %{
1480 status: "@#{other_user.nickname} wanna get some #cofe together?",
1481 visibility: "direct"
1482 })
1483
1484 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1485
1486 favourited_by_url = "/api/v1/statuses/#{activity.id}/favourited_by"
1487
1488 build_conn()
1489 |> get(favourited_by_url)
1490 |> json_response_and_validate_schema(404)
1491
1492 conn =
1493 build_conn()
1494 |> assign(:user, other_user)
1495 |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"]))
1496
1497 conn
1498 |> assign(:token, nil)
1499 |> get(favourited_by_url)
1500 |> json_response_and_validate_schema(404)
1501
1502 response =
1503 conn
1504 |> get(favourited_by_url)
1505 |> json_response_and_validate_schema(200)
1506
1507 [%{"id" => id}] = response
1508 assert id == other_user.id
1509 end
1510
1511 test "returns empty array when :show_reactions is disabled", %{conn: conn, activity: activity} do
1512 clear_config([:instance, :show_reactions], false)
1513
1514 other_user = insert(:user)
1515 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1516
1517 response =
1518 conn
1519 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1520 |> json_response_and_validate_schema(:ok)
1521
1522 assert Enum.empty?(response)
1523 end
1524 end
1525
1526 describe "GET /api/v1/statuses/:id/reblogged_by" do
1527 setup do: oauth_access(["read:accounts"])
1528
1529 setup %{user: user} do
1530 {:ok, activity} = CommonAPI.post(user, %{status: "test"})
1531
1532 %{activity: activity}
1533 end
1534
1535 test "returns users who have reblogged the status", %{conn: conn, activity: activity} do
1536 other_user = insert(:user)
1537 {:ok, _} = CommonAPI.repeat(activity.id, other_user)
1538
1539 response =
1540 conn
1541 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1542 |> json_response_and_validate_schema(:ok)
1543
1544 [%{"id" => id}] = response
1545
1546 assert id == other_user.id
1547 end
1548
1549 test "returns empty array when status has not been reblogged yet", %{
1550 conn: conn,
1551 activity: activity
1552 } do
1553 response =
1554 conn
1555 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1556 |> json_response_and_validate_schema(:ok)
1557
1558 assert Enum.empty?(response)
1559 end
1560
1561 test "does not return users who have reblogged the status but are blocked", %{
1562 conn: %{assigns: %{user: user}} = conn,
1563 activity: activity
1564 } do
1565 other_user = insert(:user)
1566 {:ok, _user_relationship} = User.block(user, other_user)
1567
1568 {:ok, _} = CommonAPI.repeat(activity.id, other_user)
1569
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 privately", %{
1579 conn: conn
1580 } do
1581 other_user = insert(:user)
1582 {:ok, activity} = CommonAPI.post(other_user, %{status: "my secret post"})
1583
1584 {:ok, _} = CommonAPI.repeat(activity.id, other_user, %{visibility: "private"})
1585
1586 response =
1587 conn
1588 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1589 |> json_response_and_validate_schema(:ok)
1590
1591 assert Enum.empty?(response)
1592 end
1593
1594 test "does not fail on an unauthenticated request", %{activity: activity} do
1595 other_user = insert(:user)
1596 {:ok, _} = CommonAPI.repeat(activity.id, other_user)
1597
1598 response =
1599 build_conn()
1600 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1601 |> json_response_and_validate_schema(:ok)
1602
1603 [%{"id" => id}] = response
1604 assert id == other_user.id
1605 end
1606
1607 test "requires authentication for private posts", %{user: user} do
1608 other_user = insert(:user)
1609
1610 {:ok, activity} =
1611 CommonAPI.post(user, %{
1612 status: "@#{other_user.nickname} wanna get some #cofe together?",
1613 visibility: "direct"
1614 })
1615
1616 build_conn()
1617 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1618 |> json_response_and_validate_schema(404)
1619
1620 response =
1621 build_conn()
1622 |> assign(:user, other_user)
1623 |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"]))
1624 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1625 |> json_response_and_validate_schema(200)
1626
1627 assert [] == response
1628 end
1629 end
1630
1631 test "context" do
1632 user = insert(:user)
1633
1634 {:ok, %{id: id1}} = CommonAPI.post(user, %{status: "1"})
1635 {:ok, %{id: id2}} = CommonAPI.post(user, %{status: "2", in_reply_to_status_id: id1})
1636 {:ok, %{id: id3}} = CommonAPI.post(user, %{status: "3", in_reply_to_status_id: id2})
1637 {:ok, %{id: id4}} = CommonAPI.post(user, %{status: "4", in_reply_to_status_id: id3})
1638 {:ok, %{id: id5}} = CommonAPI.post(user, %{status: "5", in_reply_to_status_id: id4})
1639
1640 response =
1641 build_conn()
1642 |> get("/api/v1/statuses/#{id3}/context")
1643 |> json_response_and_validate_schema(:ok)
1644
1645 assert %{
1646 "ancestors" => [%{"id" => ^id1}, %{"id" => ^id2}],
1647 "descendants" => [%{"id" => ^id4}, %{"id" => ^id5}]
1648 } = response
1649 end
1650
1651 test "favorites paginate correctly" do
1652 %{user: user, conn: conn} = oauth_access(["read:favourites"])
1653 other_user = insert(:user)
1654 {:ok, first_post} = CommonAPI.post(other_user, %{status: "bla"})
1655 {:ok, second_post} = CommonAPI.post(other_user, %{status: "bla"})
1656 {:ok, third_post} = CommonAPI.post(other_user, %{status: "bla"})
1657
1658 {:ok, _first_favorite} = CommonAPI.favorite(user, third_post.id)
1659 {:ok, _second_favorite} = CommonAPI.favorite(user, first_post.id)
1660 {:ok, third_favorite} = CommonAPI.favorite(user, second_post.id)
1661
1662 result =
1663 conn
1664 |> get("/api/v1/favourites?limit=1")
1665
1666 assert [%{"id" => post_id}] = json_response_and_validate_schema(result, 200)
1667 assert post_id == second_post.id
1668
1669 # Using the header for pagination works correctly
1670 [next, _] = get_resp_header(result, "link") |> hd() |> String.split(", ")
1671 [_, max_id] = Regex.run(~r/max_id=([^&]+)/, next)
1672
1673 assert max_id == third_favorite.id
1674
1675 result =
1676 conn
1677 |> get("/api/v1/favourites?max_id=#{max_id}")
1678
1679 assert [%{"id" => first_post_id}, %{"id" => third_post_id}] =
1680 json_response_and_validate_schema(result, 200)
1681
1682 assert first_post_id == first_post.id
1683 assert third_post_id == third_post.id
1684 end
1685
1686 test "returns the favorites of a user" do
1687 %{user: user, conn: conn} = oauth_access(["read:favourites"])
1688 other_user = insert(:user)
1689
1690 {:ok, _} = CommonAPI.post(other_user, %{status: "bla"})
1691 {:ok, activity} = CommonAPI.post(other_user, %{status: "trees are happy"})
1692
1693 {:ok, last_like} = CommonAPI.favorite(user, activity.id)
1694
1695 first_conn = get(conn, "/api/v1/favourites")
1696
1697 assert [status] = json_response_and_validate_schema(first_conn, 200)
1698 assert status["id"] == to_string(activity.id)
1699
1700 assert [{"link", _link_header}] =
1701 Enum.filter(first_conn.resp_headers, fn element -> match?({"link", _}, element) end)
1702
1703 # Honours query params
1704 {:ok, second_activity} =
1705 CommonAPI.post(other_user, %{
1706 status: "Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful."
1707 })
1708
1709 {:ok, _} = CommonAPI.favorite(user, second_activity.id)
1710
1711 second_conn = get(conn, "/api/v1/favourites?since_id=#{last_like.id}")
1712
1713 assert [second_status] = json_response_and_validate_schema(second_conn, 200)
1714 assert second_status["id"] == to_string(second_activity.id)
1715
1716 third_conn = get(conn, "/api/v1/favourites?limit=0")
1717
1718 assert [] = json_response_and_validate_schema(third_conn, 200)
1719 end
1720
1721 test "expires_at is nil for another user" do
1722 %{conn: conn, user: user} = oauth_access(["read:statuses"])
1723 expires_at = DateTime.add(DateTime.utc_now(), 1_000_000)
1724 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", expires_in: 1_000_000})
1725
1726 assert %{"pleroma" => %{"expires_at" => a_expires_at}} =
1727 conn
1728 |> get("/api/v1/statuses/#{activity.id}")
1729 |> json_response_and_validate_schema(:ok)
1730
1731 {:ok, a_expires_at, 0} = DateTime.from_iso8601(a_expires_at)
1732 assert DateTime.diff(expires_at, a_expires_at) == 0
1733
1734 %{conn: conn} = oauth_access(["read:statuses"])
1735
1736 assert %{"pleroma" => %{"expires_at" => nil}} =
1737 conn
1738 |> get("/api/v1/statuses/#{activity.id}")
1739 |> json_response_and_validate_schema(:ok)
1740 end
1741
1742 test "posting a local only status" do
1743 %{user: _user, conn: conn} = oauth_access(["write:statuses"])
1744
1745 conn_one =
1746 conn
1747 |> put_req_header("content-type", "application/json")
1748 |> post("/api/v1/statuses", %{
1749 "status" => "cofe",
1750 "visibility" => "local"
1751 })
1752
1753 local = Pleroma.Constants.as_local_public()
1754
1755 assert %{"content" => "cofe", "id" => id, "visibility" => "local"} =
1756 json_response(conn_one, 200)
1757
1758 assert %Activity{id: ^id, data: %{"to" => [^local]}} = Activity.get_by_id(id)
1759 end
1760
1761 describe "muted reactions" do
1762 test "index" do
1763 %{conn: conn, user: user} = oauth_access(["read:statuses"])
1764
1765 other_user = insert(:user)
1766 {:ok, activity} = CommonAPI.post(user, %{status: "test"})
1767
1768 {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
1769 User.mute(user, other_user)
1770
1771 result =
1772 conn
1773 |> get("/api/v1/statuses/?ids[]=#{activity.id}")
1774 |> json_response_and_validate_schema(200)
1775
1776 assert [
1777 %{
1778 "pleroma" => %{
1779 "emoji_reactions" => []
1780 }
1781 }
1782 ] = result
1783
1784 result =
1785 conn
1786 |> get("/api/v1/statuses/?ids[]=#{activity.id}&with_muted=true")
1787 |> json_response_and_validate_schema(200)
1788
1789 assert [
1790 %{
1791 "pleroma" => %{
1792 "emoji_reactions" => [%{"count" => 1, "me" => false, "name" => "🎅"}]
1793 }
1794 }
1795 ] = result
1796 end
1797
1798 test "show" do
1799 # %{conn: conn, user: user, token: token} = oauth_access(["read:statuses"])
1800 %{conn: conn, user: user, token: _token} = oauth_access(["read:statuses"])
1801
1802 other_user = insert(:user)
1803 {:ok, activity} = CommonAPI.post(user, %{status: "test"})
1804
1805 {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
1806 User.mute(user, other_user)
1807
1808 result =
1809 conn
1810 |> get("/api/v1/statuses/#{activity.id}")
1811 |> json_response_and_validate_schema(200)
1812
1813 assert %{
1814 "pleroma" => %{
1815 "emoji_reactions" => []
1816 }
1817 } = result
1818
1819 result =
1820 conn
1821 |> get("/api/v1/statuses/#{activity.id}?with_muted=true")
1822 |> json_response_and_validate_schema(200)
1823
1824 assert %{
1825 "pleroma" => %{
1826 "emoji_reactions" => [%{"count" => 1, "me" => false, "name" => "🎅"}]
1827 }
1828 } = result
1829 end
1830 end
1831 end