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