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