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