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