Tests to validate client disclosure obeys user setting
[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 |> assign(:user, refresh_record(conn.assigns.user))
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
361 test "discloses application metadata when enabled" do
362 user = insert(:user, disclose_client: true)
363 %{user: _user, token: token, conn: conn} = oauth_access(["write:statuses"], user: user)
364
365 %Pleroma.Web.OAuth.Token{
366 app: %Pleroma.Web.OAuth.App{
367 client_name: _app_name,
368 website: _app_website
369 }
370 } = token
371
372 result =
373 conn
374 |> put_req_header("content-type", "application/json")
375 |> post("/api/v1/statuses", %{
376 "status" => "cofe is my copilot"
377 })
378
379 assert %{
380 "content" => "cofe is my copilot",
381 "application" => %{
382 "name" => app_name,
383 "website" => app_website
384 }
385 } = json_response_and_validate_schema(result, 200)
386 end
387
388 test "hides application metadata when disabled" do
389 user = insert(:user, disclose_client: false)
390 %{user: _user, token: _token, conn: conn} = oauth_access(["write:statuses"], user: user)
391
392 result =
393 conn
394 |> put_req_header("content-type", "application/json")
395 |> post("/api/v1/statuses", %{
396 "status" => "club mate is my wingman"
397 })
398
399 assert %{
400 "content" => "club mate is my wingman",
401 "application" => nil
402 } = json_response_and_validate_schema(result, 200)
403 end
404 end
405
406 describe "posting scheduled statuses" do
407 setup do: oauth_access(["write:statuses"])
408
409 test "creates a scheduled activity", %{conn: conn} do
410 scheduled_at =
411 NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
412 |> NaiveDateTime.to_iso8601()
413 |> Kernel.<>("Z")
414
415 conn =
416 conn
417 |> put_req_header("content-type", "application/json")
418 |> post("/api/v1/statuses", %{
419 "status" => "scheduled",
420 "scheduled_at" => scheduled_at
421 })
422
423 assert %{"scheduled_at" => expected_scheduled_at} =
424 json_response_and_validate_schema(conn, 200)
425
426 assert expected_scheduled_at == CommonAPI.Utils.to_masto_date(scheduled_at)
427 assert [] == Repo.all(Activity)
428 end
429
430 test "ignores nil values", %{conn: conn} do
431 conn =
432 conn
433 |> put_req_header("content-type", "application/json")
434 |> post("/api/v1/statuses", %{
435 "status" => "not scheduled",
436 "scheduled_at" => nil
437 })
438
439 assert result = json_response_and_validate_schema(conn, 200)
440 assert Activity.get_by_id(result["id"])
441 end
442
443 test "creates a scheduled activity with a media attachment", %{user: user, conn: conn} do
444 scheduled_at =
445 NaiveDateTime.utc_now()
446 |> NaiveDateTime.add(:timer.minutes(120), :millisecond)
447 |> NaiveDateTime.to_iso8601()
448 |> Kernel.<>("Z")
449
450 file = %Plug.Upload{
451 content_type: "image/jpeg",
452 path: Path.absname("test/fixtures/image.jpg"),
453 filename: "an_image.jpg"
454 }
455
456 {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
457
458 conn =
459 conn
460 |> put_req_header("content-type", "application/json")
461 |> post("/api/v1/statuses", %{
462 "media_ids" => [to_string(upload.id)],
463 "status" => "scheduled",
464 "scheduled_at" => scheduled_at
465 })
466
467 assert %{"media_attachments" => [media_attachment]} =
468 json_response_and_validate_schema(conn, 200)
469
470 assert %{"type" => "image"} = media_attachment
471 end
472
473 test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now",
474 %{conn: conn} do
475 scheduled_at =
476 NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond)
477 |> NaiveDateTime.to_iso8601()
478 |> Kernel.<>("Z")
479
480 conn =
481 conn
482 |> put_req_header("content-type", "application/json")
483 |> post("/api/v1/statuses", %{
484 "status" => "not scheduled",
485 "scheduled_at" => scheduled_at
486 })
487
488 assert %{"content" => "not scheduled"} = json_response_and_validate_schema(conn, 200)
489 assert [] == Repo.all(ScheduledActivity)
490 end
491
492 test "returns error when daily user limit is exceeded", %{user: user, conn: conn} do
493 today =
494 NaiveDateTime.utc_now()
495 |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
496 |> NaiveDateTime.to_iso8601()
497 # TODO
498 |> Kernel.<>("Z")
499
500 attrs = %{params: %{}, scheduled_at: today}
501 {:ok, _} = ScheduledActivity.create(user, attrs)
502 {:ok, _} = ScheduledActivity.create(user, attrs)
503
504 conn =
505 conn
506 |> put_req_header("content-type", "application/json")
507 |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today})
508
509 assert %{"error" => "daily limit exceeded"} == json_response_and_validate_schema(conn, 422)
510 end
511
512 test "returns error when total user limit is exceeded", %{user: user, conn: conn} do
513 today =
514 NaiveDateTime.utc_now()
515 |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
516 |> NaiveDateTime.to_iso8601()
517 |> Kernel.<>("Z")
518
519 tomorrow =
520 NaiveDateTime.utc_now()
521 |> NaiveDateTime.add(:timer.hours(36), :millisecond)
522 |> NaiveDateTime.to_iso8601()
523 |> Kernel.<>("Z")
524
525 attrs = %{params: %{}, scheduled_at: today}
526 {:ok, _} = ScheduledActivity.create(user, attrs)
527 {:ok, _} = ScheduledActivity.create(user, attrs)
528 {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
529
530 conn =
531 conn
532 |> put_req_header("content-type", "application/json")
533 |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow})
534
535 assert %{"error" => "total limit exceeded"} == json_response_and_validate_schema(conn, 422)
536 end
537 end
538
539 describe "posting polls" do
540 setup do: oauth_access(["write:statuses"])
541
542 test "posting a poll", %{conn: conn} do
543 time = NaiveDateTime.utc_now()
544
545 conn =
546 conn
547 |> put_req_header("content-type", "application/json")
548 |> post("/api/v1/statuses", %{
549 "status" => "Who is the #bestgrill?",
550 "poll" => %{
551 "options" => ["Rei", "Asuka", "Misato"],
552 "expires_in" => 420
553 }
554 })
555
556 response = json_response_and_validate_schema(conn, 200)
557
558 assert Enum.all?(response["poll"]["options"], fn %{"title" => title} ->
559 title in ["Rei", "Asuka", "Misato"]
560 end)
561
562 assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430
563 assert response["poll"]["expired"] == false
564
565 question = Object.get_by_id(response["poll"]["id"])
566
567 # closed contains utc timezone
568 assert question.data["closed"] =~ "Z"
569 end
570
571 test "option limit is enforced", %{conn: conn} do
572 limit = Config.get([:instance, :poll_limits, :max_options])
573
574 conn =
575 conn
576 |> put_req_header("content-type", "application/json")
577 |> post("/api/v1/statuses", %{
578 "status" => "desu~",
579 "poll" => %{"options" => Enum.map(0..limit, fn _ -> "desu" end), "expires_in" => 1}
580 })
581
582 %{"error" => error} = json_response_and_validate_schema(conn, 422)
583 assert error == "Poll can't contain more than #{limit} options"
584 end
585
586 test "option character limit is enforced", %{conn: conn} do
587 limit = Config.get([:instance, :poll_limits, :max_option_chars])
588
589 conn =
590 conn
591 |> put_req_header("content-type", "application/json")
592 |> post("/api/v1/statuses", %{
593 "status" => "...",
594 "poll" => %{
595 "options" => [Enum.reduce(0..limit, "", fn _, acc -> acc <> "." end)],
596 "expires_in" => 1
597 }
598 })
599
600 %{"error" => error} = json_response_and_validate_schema(conn, 422)
601 assert error == "Poll options cannot be longer than #{limit} characters each"
602 end
603
604 test "minimal date limit is enforced", %{conn: conn} do
605 limit = Config.get([:instance, :poll_limits, :min_expiration])
606
607 conn =
608 conn
609 |> put_req_header("content-type", "application/json")
610 |> post("/api/v1/statuses", %{
611 "status" => "imagine arbitrary limits",
612 "poll" => %{
613 "options" => ["this post was made by pleroma gang"],
614 "expires_in" => limit - 1
615 }
616 })
617
618 %{"error" => error} = json_response_and_validate_schema(conn, 422)
619 assert error == "Expiration date is too soon"
620 end
621
622 test "maximum date limit is enforced", %{conn: conn} do
623 limit = Config.get([:instance, :poll_limits, :max_expiration])
624
625 conn =
626 conn
627 |> put_req_header("content-type", "application/json")
628 |> post("/api/v1/statuses", %{
629 "status" => "imagine arbitrary limits",
630 "poll" => %{
631 "options" => ["this post was made by pleroma gang"],
632 "expires_in" => limit + 1
633 }
634 })
635
636 %{"error" => error} = json_response_and_validate_schema(conn, 422)
637 assert error == "Expiration date is too far in the future"
638 end
639
640 test "scheduled poll", %{conn: conn} do
641 clear_config([ScheduledActivity, :enabled], true)
642
643 scheduled_at =
644 NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(6), :millisecond)
645 |> NaiveDateTime.to_iso8601()
646 |> Kernel.<>("Z")
647
648 %{"id" => scheduled_id} =
649 conn
650 |> put_req_header("content-type", "application/json")
651 |> post("/api/v1/statuses", %{
652 "status" => "very cool poll",
653 "poll" => %{
654 "options" => ~w(a b c),
655 "expires_in" => 420
656 },
657 "scheduled_at" => scheduled_at
658 })
659 |> json_response_and_validate_schema(200)
660
661 assert {:ok, %{id: activity_id}} =
662 perform_job(Pleroma.Workers.ScheduledActivityWorker, %{
663 activity_id: scheduled_id
664 })
665
666 assert Repo.all(Oban.Job) == []
667
668 object =
669 Activity
670 |> Repo.get(activity_id)
671 |> Object.normalize()
672
673 assert object.data["content"] == "very cool poll"
674 assert object.data["type"] == "Question"
675 assert length(object.data["oneOf"]) == 3
676 end
677 end
678
679 test "get a status" do
680 %{conn: conn} = oauth_access(["read:statuses"])
681 activity = insert(:note_activity)
682
683 conn = get(conn, "/api/v1/statuses/#{activity.id}")
684
685 assert %{"id" => id} = json_response_and_validate_schema(conn, 200)
686 assert id == to_string(activity.id)
687 end
688
689 defp local_and_remote_activities do
690 local = insert(:note_activity)
691 remote = insert(:note_activity, local: false)
692 {:ok, local: local, remote: remote}
693 end
694
695 describe "status with restrict unauthenticated activities for local and remote" do
696 setup do: local_and_remote_activities()
697
698 setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
699
700 setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
701
702 test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
703 res_conn = get(conn, "/api/v1/statuses/#{local.id}")
704
705 assert json_response_and_validate_schema(res_conn, :not_found) == %{
706 "error" => "Record not found"
707 }
708
709 res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
710
711 assert json_response_and_validate_schema(res_conn, :not_found) == %{
712 "error" => "Record not found"
713 }
714 end
715
716 test "if user is authenticated", %{local: local, remote: remote} do
717 %{conn: conn} = oauth_access(["read"])
718 res_conn = get(conn, "/api/v1/statuses/#{local.id}")
719 assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
720
721 res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
722 assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
723 end
724 end
725
726 describe "status with restrict unauthenticated activities for local" do
727 setup do: local_and_remote_activities()
728
729 setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
730
731 test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
732 res_conn = get(conn, "/api/v1/statuses/#{local.id}")
733
734 assert json_response_and_validate_schema(res_conn, :not_found) == %{
735 "error" => "Record not found"
736 }
737
738 res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
739 assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
740 end
741
742 test "if user is authenticated", %{local: local, remote: remote} do
743 %{conn: conn} = oauth_access(["read"])
744 res_conn = get(conn, "/api/v1/statuses/#{local.id}")
745 assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
746
747 res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
748 assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
749 end
750 end
751
752 describe "status with restrict unauthenticated activities for remote" do
753 setup do: local_and_remote_activities()
754
755 setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
756
757 test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
758 res_conn = get(conn, "/api/v1/statuses/#{local.id}")
759 assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
760
761 res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
762
763 assert json_response_and_validate_schema(res_conn, :not_found) == %{
764 "error" => "Record not found"
765 }
766 end
767
768 test "if user is authenticated", %{local: local, remote: remote} do
769 %{conn: conn} = oauth_access(["read"])
770 res_conn = get(conn, "/api/v1/statuses/#{local.id}")
771 assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
772
773 res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
774 assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
775 end
776 end
777
778 test "getting a status that doesn't exist returns 404" do
779 %{conn: conn} = oauth_access(["read:statuses"])
780 activity = insert(:note_activity)
781
782 conn = get(conn, "/api/v1/statuses/#{String.downcase(activity.id)}")
783
784 assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
785 end
786
787 test "get a direct status" do
788 %{user: user, conn: conn} = oauth_access(["read:statuses"])
789 other_user = insert(:user)
790
791 {:ok, activity} =
792 CommonAPI.post(user, %{status: "@#{other_user.nickname}", visibility: "direct"})
793
794 conn =
795 conn
796 |> assign(:user, user)
797 |> get("/api/v1/statuses/#{activity.id}")
798
799 [participation] = Participation.for_user(user)
800
801 res = json_response_and_validate_schema(conn, 200)
802 assert res["pleroma"]["direct_conversation_id"] == participation.id
803 end
804
805 test "get statuses by IDs" do
806 %{conn: conn} = oauth_access(["read:statuses"])
807 %{id: id1} = insert(:note_activity)
808 %{id: id2} = insert(:note_activity)
809
810 query_string = "ids[]=#{id1}&ids[]=#{id2}"
811 conn = get(conn, "/api/v1/statuses/?#{query_string}")
812
813 assert [%{"id" => ^id1}, %{"id" => ^id2}] =
814 Enum.sort_by(json_response_and_validate_schema(conn, :ok), & &1["id"])
815 end
816
817 describe "getting statuses by ids with restricted unauthenticated for local and remote" do
818 setup do: local_and_remote_activities()
819
820 setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
821
822 setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
823
824 test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
825 res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
826
827 assert json_response_and_validate_schema(res_conn, 200) == []
828 end
829
830 test "if user is authenticated", %{local: local, remote: remote} do
831 %{conn: conn} = oauth_access(["read"])
832
833 res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
834
835 assert length(json_response_and_validate_schema(res_conn, 200)) == 2
836 end
837 end
838
839 describe "getting statuses by ids with restricted unauthenticated for local" do
840 setup do: local_and_remote_activities()
841
842 setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
843
844 test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
845 res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
846
847 remote_id = remote.id
848 assert [%{"id" => ^remote_id}] = json_response_and_validate_schema(res_conn, 200)
849 end
850
851 test "if user is authenticated", %{local: local, remote: remote} do
852 %{conn: conn} = oauth_access(["read"])
853
854 res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
855
856 assert length(json_response_and_validate_schema(res_conn, 200)) == 2
857 end
858 end
859
860 describe "getting statuses by ids with restricted unauthenticated for remote" do
861 setup do: local_and_remote_activities()
862
863 setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
864
865 test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
866 res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
867
868 local_id = local.id
869 assert [%{"id" => ^local_id}] = json_response_and_validate_schema(res_conn, 200)
870 end
871
872 test "if user is authenticated", %{local: local, remote: remote} do
873 %{conn: conn} = oauth_access(["read"])
874
875 res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
876
877 assert length(json_response_and_validate_schema(res_conn, 200)) == 2
878 end
879 end
880
881 describe "deleting a status" do
882 test "when you created it" do
883 %{user: author, conn: conn} = oauth_access(["write:statuses"])
884 activity = insert(:note_activity, user: author)
885 object = Object.normalize(activity, fetch: false)
886
887 content = object.data["content"]
888 source = object.data["source"]
889
890 result =
891 conn
892 |> assign(:user, author)
893 |> delete("/api/v1/statuses/#{activity.id}")
894 |> json_response_and_validate_schema(200)
895
896 assert match?(%{"content" => ^content, "text" => ^source}, result)
897
898 refute Activity.get_by_id(activity.id)
899 end
900
901 test "when it doesn't exist" do
902 %{user: author, conn: conn} = oauth_access(["write:statuses"])
903 activity = insert(:note_activity, user: author)
904
905 conn =
906 conn
907 |> assign(:user, author)
908 |> delete("/api/v1/statuses/#{String.downcase(activity.id)}")
909
910 assert %{"error" => "Record not found"} == json_response_and_validate_schema(conn, 404)
911 end
912
913 test "when you didn't create it" do
914 %{conn: conn} = oauth_access(["write:statuses"])
915 activity = insert(:note_activity)
916
917 conn = delete(conn, "/api/v1/statuses/#{activity.id}")
918
919 assert %{"error" => "Record not found"} == json_response_and_validate_schema(conn, 404)
920
921 assert Activity.get_by_id(activity.id) == activity
922 end
923
924 test "when you're an admin or moderator", %{conn: conn} do
925 activity1 = insert(:note_activity)
926 activity2 = insert(:note_activity)
927 admin = insert(:user, is_admin: true)
928 moderator = insert(:user, is_moderator: true)
929
930 res_conn =
931 conn
932 |> assign(:user, admin)
933 |> assign(:token, insert(:oauth_token, user: admin, scopes: ["write:statuses"]))
934 |> delete("/api/v1/statuses/#{activity1.id}")
935
936 assert %{} = json_response_and_validate_schema(res_conn, 200)
937
938 res_conn =
939 conn
940 |> assign(:user, moderator)
941 |> assign(:token, insert(:oauth_token, user: moderator, scopes: ["write:statuses"]))
942 |> delete("/api/v1/statuses/#{activity2.id}")
943
944 assert %{} = json_response_and_validate_schema(res_conn, 200)
945
946 refute Activity.get_by_id(activity1.id)
947 refute Activity.get_by_id(activity2.id)
948 end
949 end
950
951 describe "reblogging" do
952 setup do: oauth_access(["write:statuses"])
953
954 test "reblogs and returns the reblogged status", %{conn: conn} do
955 activity = insert(:note_activity)
956
957 conn =
958 conn
959 |> put_req_header("content-type", "application/json")
960 |> post("/api/v1/statuses/#{activity.id}/reblog")
961
962 assert %{
963 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
964 "reblogged" => true
965 } = json_response_and_validate_schema(conn, 200)
966
967 assert to_string(activity.id) == id
968 end
969
970 test "returns 404 if the reblogged status doesn't exist", %{conn: conn} do
971 activity = insert(:note_activity)
972
973 conn =
974 conn
975 |> put_req_header("content-type", "application/json")
976 |> post("/api/v1/statuses/#{String.downcase(activity.id)}/reblog")
977
978 assert %{"error" => "Record not found"} = json_response_and_validate_schema(conn, 404)
979 end
980
981 test "reblogs privately and returns the reblogged status", %{conn: conn} do
982 activity = insert(:note_activity)
983
984 conn =
985 conn
986 |> put_req_header("content-type", "application/json")
987 |> post(
988 "/api/v1/statuses/#{activity.id}/reblog",
989 %{"visibility" => "private"}
990 )
991
992 assert %{
993 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
994 "reblogged" => true,
995 "visibility" => "private"
996 } = json_response_and_validate_schema(conn, 200)
997
998 assert to_string(activity.id) == id
999 end
1000
1001 test "reblogged status for another user" do
1002 activity = insert(:note_activity)
1003 user1 = insert(:user)
1004 user2 = insert(:user)
1005 user3 = insert(:user)
1006 {:ok, _} = CommonAPI.favorite(user2, activity.id)
1007 {:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id)
1008 {:ok, reblog_activity1} = CommonAPI.repeat(activity.id, user1)
1009 {:ok, _} = CommonAPI.repeat(activity.id, user2)
1010
1011 conn_res =
1012 build_conn()
1013 |> assign(:user, user3)
1014 |> assign(:token, insert(:oauth_token, user: user3, scopes: ["read:statuses"]))
1015 |> get("/api/v1/statuses/#{reblog_activity1.id}")
1016
1017 assert %{
1018 "reblog" => %{"id" => _id, "reblogged" => false, "reblogs_count" => 2},
1019 "reblogged" => false,
1020 "favourited" => false,
1021 "bookmarked" => false
1022 } = json_response_and_validate_schema(conn_res, 200)
1023
1024 conn_res =
1025 build_conn()
1026 |> assign(:user, user2)
1027 |> assign(:token, insert(:oauth_token, user: user2, scopes: ["read:statuses"]))
1028 |> get("/api/v1/statuses/#{reblog_activity1.id}")
1029
1030 assert %{
1031 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 2},
1032 "reblogged" => true,
1033 "favourited" => true,
1034 "bookmarked" => true
1035 } = json_response_and_validate_schema(conn_res, 200)
1036
1037 assert to_string(activity.id) == id
1038 end
1039
1040 test "author can reblog own private status", %{conn: conn, user: user} do
1041 {:ok, activity} = CommonAPI.post(user, %{status: "cofe", visibility: "private"})
1042
1043 conn =
1044 conn
1045 |> put_req_header("content-type", "application/json")
1046 |> post("/api/v1/statuses/#{activity.id}/reblog")
1047
1048 assert %{
1049 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
1050 "reblogged" => true,
1051 "visibility" => "private"
1052 } = json_response_and_validate_schema(conn, 200)
1053
1054 assert to_string(activity.id) == id
1055 end
1056 end
1057
1058 describe "unreblogging" do
1059 setup do: oauth_access(["write:statuses"])
1060
1061 test "unreblogs and returns the unreblogged status", %{user: user, conn: conn} do
1062 activity = insert(:note_activity)
1063
1064 {:ok, _} = CommonAPI.repeat(activity.id, user)
1065
1066 conn =
1067 conn
1068 |> put_req_header("content-type", "application/json")
1069 |> post("/api/v1/statuses/#{activity.id}/unreblog")
1070
1071 assert %{"id" => id, "reblogged" => false, "reblogs_count" => 0} =
1072 json_response_and_validate_schema(conn, 200)
1073
1074 assert to_string(activity.id) == id
1075 end
1076
1077 test "returns 404 error when activity does not exist", %{conn: conn} do
1078 conn =
1079 conn
1080 |> put_req_header("content-type", "application/json")
1081 |> post("/api/v1/statuses/foo/unreblog")
1082
1083 assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
1084 end
1085 end
1086
1087 describe "favoriting" do
1088 setup do: oauth_access(["write:favourites"])
1089
1090 test "favs a status and returns it", %{conn: conn} do
1091 activity = insert(:note_activity)
1092
1093 conn =
1094 conn
1095 |> put_req_header("content-type", "application/json")
1096 |> post("/api/v1/statuses/#{activity.id}/favourite")
1097
1098 assert %{"id" => id, "favourites_count" => 1, "favourited" => true} =
1099 json_response_and_validate_schema(conn, 200)
1100
1101 assert to_string(activity.id) == id
1102 end
1103
1104 test "favoriting twice will just return 200", %{conn: conn} do
1105 activity = insert(:note_activity)
1106
1107 conn
1108 |> put_req_header("content-type", "application/json")
1109 |> post("/api/v1/statuses/#{activity.id}/favourite")
1110
1111 assert conn
1112 |> put_req_header("content-type", "application/json")
1113 |> post("/api/v1/statuses/#{activity.id}/favourite")
1114 |> json_response_and_validate_schema(200)
1115 end
1116
1117 test "returns 404 error for a wrong id", %{conn: conn} do
1118 conn =
1119 conn
1120 |> put_req_header("content-type", "application/json")
1121 |> post("/api/v1/statuses/1/favourite")
1122
1123 assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
1124 end
1125 end
1126
1127 describe "unfavoriting" do
1128 setup do: oauth_access(["write:favourites"])
1129
1130 test "unfavorites a status and returns it", %{user: user, conn: conn} do
1131 activity = insert(:note_activity)
1132
1133 {:ok, _} = CommonAPI.favorite(user, activity.id)
1134
1135 conn =
1136 conn
1137 |> put_req_header("content-type", "application/json")
1138 |> post("/api/v1/statuses/#{activity.id}/unfavourite")
1139
1140 assert %{"id" => id, "favourites_count" => 0, "favourited" => false} =
1141 json_response_and_validate_schema(conn, 200)
1142
1143 assert to_string(activity.id) == id
1144 end
1145
1146 test "returns 404 error for a wrong id", %{conn: conn} do
1147 conn =
1148 conn
1149 |> put_req_header("content-type", "application/json")
1150 |> post("/api/v1/statuses/1/unfavourite")
1151
1152 assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
1153 end
1154 end
1155
1156 describe "pinned statuses" do
1157 setup do: oauth_access(["write:accounts"])
1158
1159 setup %{user: user} do
1160 {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!"})
1161
1162 %{activity: activity}
1163 end
1164
1165 setup do: clear_config([:instance, :max_pinned_statuses], 1)
1166
1167 test "pin status", %{conn: conn, user: user, activity: activity} do
1168 id_str = to_string(activity.id)
1169
1170 assert %{"id" => ^id_str, "pinned" => true} =
1171 conn
1172 |> put_req_header("content-type", "application/json")
1173 |> post("/api/v1/statuses/#{activity.id}/pin")
1174 |> json_response_and_validate_schema(200)
1175
1176 assert [%{"id" => ^id_str, "pinned" => true}] =
1177 conn
1178 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
1179 |> json_response_and_validate_schema(200)
1180 end
1181
1182 test "/pin: returns 400 error when activity is not public", %{conn: conn, user: user} do
1183 {:ok, dm} = CommonAPI.post(user, %{status: "test", visibility: "direct"})
1184
1185 conn =
1186 conn
1187 |> put_req_header("content-type", "application/json")
1188 |> post("/api/v1/statuses/#{dm.id}/pin")
1189
1190 assert json_response_and_validate_schema(conn, 400) == %{"error" => "Could not pin"}
1191 end
1192
1193 test "unpin status", %{conn: conn, user: user, activity: activity} do
1194 {:ok, _} = CommonAPI.pin(activity.id, user)
1195 user = refresh_record(user)
1196
1197 id_str = to_string(activity.id)
1198
1199 assert %{"id" => ^id_str, "pinned" => false} =
1200 conn
1201 |> assign(:user, user)
1202 |> post("/api/v1/statuses/#{activity.id}/unpin")
1203 |> json_response_and_validate_schema(200)
1204
1205 assert [] =
1206 conn
1207 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
1208 |> json_response_and_validate_schema(200)
1209 end
1210
1211 test "/unpin: returns 400 error when activity is not exist", %{conn: conn} do
1212 conn =
1213 conn
1214 |> put_req_header("content-type", "application/json")
1215 |> post("/api/v1/statuses/1/unpin")
1216
1217 assert json_response_and_validate_schema(conn, 400) == %{"error" => "Could not unpin"}
1218 end
1219
1220 test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
1221 {:ok, activity_two} = CommonAPI.post(user, %{status: "HI!!!"})
1222
1223 id_str_one = to_string(activity_one.id)
1224
1225 assert %{"id" => ^id_str_one, "pinned" => true} =
1226 conn
1227 |> put_req_header("content-type", "application/json")
1228 |> post("/api/v1/statuses/#{id_str_one}/pin")
1229 |> json_response_and_validate_schema(200)
1230
1231 user = refresh_record(user)
1232
1233 assert %{"error" => "You have already pinned the maximum number of statuses"} =
1234 conn
1235 |> assign(:user, user)
1236 |> post("/api/v1/statuses/#{activity_two.id}/pin")
1237 |> json_response_and_validate_schema(400)
1238 end
1239
1240 test "on pin removes deletion job, on unpin reschedule deletion" do
1241 %{conn: conn} = oauth_access(["write:accounts", "write:statuses"])
1242 expires_in = 2 * 60 * 60
1243
1244 expires_at = DateTime.add(DateTime.utc_now(), expires_in)
1245
1246 assert %{"id" => id} =
1247 conn
1248 |> put_req_header("content-type", "application/json")
1249 |> post("api/v1/statuses", %{
1250 "status" => "oolong",
1251 "expires_in" => expires_in
1252 })
1253 |> json_response_and_validate_schema(200)
1254
1255 assert_enqueued(
1256 worker: Pleroma.Workers.PurgeExpiredActivity,
1257 args: %{activity_id: id},
1258 scheduled_at: expires_at
1259 )
1260
1261 assert %{"id" => ^id, "pinned" => true} =
1262 conn
1263 |> put_req_header("content-type", "application/json")
1264 |> post("/api/v1/statuses/#{id}/pin")
1265 |> json_response_and_validate_schema(200)
1266
1267 refute_enqueued(
1268 worker: Pleroma.Workers.PurgeExpiredActivity,
1269 args: %{activity_id: id},
1270 scheduled_at: expires_at
1271 )
1272
1273 assert %{"id" => ^id, "pinned" => false} =
1274 conn
1275 |> put_req_header("content-type", "application/json")
1276 |> post("/api/v1/statuses/#{id}/unpin")
1277 |> json_response_and_validate_schema(200)
1278
1279 assert_enqueued(
1280 worker: Pleroma.Workers.PurgeExpiredActivity,
1281 args: %{activity_id: id},
1282 scheduled_at: expires_at
1283 )
1284 end
1285 end
1286
1287 describe "cards" do
1288 setup do
1289 clear_config([:rich_media, :enabled], true)
1290
1291 oauth_access(["read:statuses"])
1292 end
1293
1294 test "returns rich-media card", %{conn: conn, user: user} do
1295 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
1296
1297 {:ok, activity} = CommonAPI.post(user, %{status: "https://example.com/ogp"})
1298
1299 card_data = %{
1300 "image" => "http://ia.media-imdb.com/images/rock.jpg",
1301 "provider_name" => "example.com",
1302 "provider_url" => "https://example.com",
1303 "title" => "The Rock",
1304 "type" => "link",
1305 "url" => "https://example.com/ogp",
1306 "description" =>
1307 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
1308 "pleroma" => %{
1309 "opengraph" => %{
1310 "image" => "http://ia.media-imdb.com/images/rock.jpg",
1311 "title" => "The Rock",
1312 "type" => "video.movie",
1313 "url" => "https://example.com/ogp",
1314 "description" =>
1315 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer."
1316 }
1317 }
1318 }
1319
1320 response =
1321 conn
1322 |> get("/api/v1/statuses/#{activity.id}/card")
1323 |> json_response_and_validate_schema(200)
1324
1325 assert response == card_data
1326
1327 # works with private posts
1328 {:ok, activity} =
1329 CommonAPI.post(user, %{status: "https://example.com/ogp", visibility: "direct"})
1330
1331 response_two =
1332 conn
1333 |> get("/api/v1/statuses/#{activity.id}/card")
1334 |> json_response_and_validate_schema(200)
1335
1336 assert response_two == card_data
1337 end
1338
1339 test "replaces missing description with an empty string", %{conn: conn, user: user} do
1340 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
1341
1342 {:ok, activity} = CommonAPI.post(user, %{status: "https://example.com/ogp-missing-data"})
1343
1344 response =
1345 conn
1346 |> get("/api/v1/statuses/#{activity.id}/card")
1347 |> json_response_and_validate_schema(:ok)
1348
1349 assert response == %{
1350 "type" => "link",
1351 "title" => "Pleroma",
1352 "description" => "",
1353 "image" => nil,
1354 "provider_name" => "example.com",
1355 "provider_url" => "https://example.com",
1356 "url" => "https://example.com/ogp-missing-data",
1357 "pleroma" => %{
1358 "opengraph" => %{
1359 "title" => "Pleroma",
1360 "type" => "website",
1361 "url" => "https://example.com/ogp-missing-data"
1362 }
1363 }
1364 }
1365 end
1366 end
1367
1368 test "bookmarks" do
1369 bookmarks_uri = "/api/v1/bookmarks"
1370
1371 %{conn: conn} = oauth_access(["write:bookmarks", "read:bookmarks"])
1372 author = insert(:user)
1373
1374 {:ok, activity1} = CommonAPI.post(author, %{status: "heweoo?"})
1375 {:ok, activity2} = CommonAPI.post(author, %{status: "heweoo!"})
1376
1377 response1 =
1378 conn
1379 |> put_req_header("content-type", "application/json")
1380 |> post("/api/v1/statuses/#{activity1.id}/bookmark")
1381
1382 assert json_response_and_validate_schema(response1, 200)["bookmarked"] == true
1383
1384 response2 =
1385 conn
1386 |> put_req_header("content-type", "application/json")
1387 |> post("/api/v1/statuses/#{activity2.id}/bookmark")
1388
1389 assert json_response_and_validate_schema(response2, 200)["bookmarked"] == true
1390
1391 bookmarks = get(conn, bookmarks_uri)
1392
1393 assert [
1394 json_response_and_validate_schema(response2, 200),
1395 json_response_and_validate_schema(response1, 200)
1396 ] ==
1397 json_response_and_validate_schema(bookmarks, 200)
1398
1399 response1 =
1400 conn
1401 |> put_req_header("content-type", "application/json")
1402 |> post("/api/v1/statuses/#{activity1.id}/unbookmark")
1403
1404 assert json_response_and_validate_schema(response1, 200)["bookmarked"] == false
1405
1406 bookmarks = get(conn, bookmarks_uri)
1407
1408 assert [json_response_and_validate_schema(response2, 200)] ==
1409 json_response_and_validate_schema(bookmarks, 200)
1410 end
1411
1412 describe "conversation muting" do
1413 setup do: oauth_access(["write:mutes"])
1414
1415 setup do
1416 post_user = insert(:user)
1417 {:ok, activity} = CommonAPI.post(post_user, %{status: "HIE"})
1418 %{activity: activity}
1419 end
1420
1421 test "mute conversation", %{conn: conn, activity: activity} do
1422 id_str = to_string(activity.id)
1423
1424 assert %{"id" => ^id_str, "muted" => true} =
1425 conn
1426 |> put_req_header("content-type", "application/json")
1427 |> post("/api/v1/statuses/#{activity.id}/mute")
1428 |> json_response_and_validate_schema(200)
1429 end
1430
1431 test "cannot mute already muted conversation", %{conn: conn, user: user, activity: activity} do
1432 {:ok, _} = CommonAPI.add_mute(user, activity)
1433
1434 conn =
1435 conn
1436 |> put_req_header("content-type", "application/json")
1437 |> post("/api/v1/statuses/#{activity.id}/mute")
1438
1439 assert json_response_and_validate_schema(conn, 400) == %{
1440 "error" => "conversation is already muted"
1441 }
1442 end
1443
1444 test "unmute conversation", %{conn: conn, user: user, activity: activity} do
1445 {:ok, _} = CommonAPI.add_mute(user, activity)
1446
1447 id_str = to_string(activity.id)
1448
1449 assert %{"id" => ^id_str, "muted" => false} =
1450 conn
1451 # |> assign(:user, user)
1452 |> post("/api/v1/statuses/#{activity.id}/unmute")
1453 |> json_response_and_validate_schema(200)
1454 end
1455 end
1456
1457 test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do
1458 user1 = insert(:user)
1459 user2 = insert(:user)
1460 user3 = insert(:user)
1461
1462 {:ok, replied_to} = CommonAPI.post(user1, %{status: "cofe"})
1463
1464 # Reply to status from another user
1465 conn1 =
1466 conn
1467 |> assign(:user, user2)
1468 |> assign(:token, insert(:oauth_token, user: user2, scopes: ["write:statuses"]))
1469 |> put_req_header("content-type", "application/json")
1470 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
1471
1472 assert %{"content" => "xD", "id" => id} = json_response_and_validate_schema(conn1, 200)
1473
1474 activity = Activity.get_by_id_with_object(id)
1475
1476 assert Object.normalize(activity, fetch: false).data["inReplyTo"] ==
1477 Object.normalize(replied_to, fetch: false).data["id"]
1478
1479 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
1480
1481 # Reblog from the third user
1482 conn2 =
1483 conn
1484 |> assign(:user, user3)
1485 |> assign(:token, insert(:oauth_token, user: user3, scopes: ["write:statuses"]))
1486 |> put_req_header("content-type", "application/json")
1487 |> post("/api/v1/statuses/#{activity.id}/reblog")
1488
1489 assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} =
1490 json_response_and_validate_schema(conn2, 200)
1491
1492 assert to_string(activity.id) == id
1493
1494 # Getting third user status
1495 conn3 =
1496 conn
1497 |> assign(:user, user3)
1498 |> assign(:token, insert(:oauth_token, user: user3, scopes: ["read:statuses"]))
1499 |> get("api/v1/timelines/home")
1500
1501 [reblogged_activity] = json_response(conn3, 200)
1502
1503 assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id
1504
1505 replied_to_user = User.get_by_ap_id(replied_to.data["actor"])
1506 assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id
1507 end
1508
1509 describe "GET /api/v1/statuses/:id/favourited_by" do
1510 setup do: oauth_access(["read:accounts"])
1511
1512 setup %{user: user} do
1513 {:ok, activity} = CommonAPI.post(user, %{status: "test"})
1514
1515 %{activity: activity}
1516 end
1517
1518 test "returns users who have favorited the status", %{conn: conn, activity: activity} do
1519 other_user = insert(:user)
1520 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1521
1522 response =
1523 conn
1524 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1525 |> json_response_and_validate_schema(:ok)
1526
1527 [%{"id" => id}] = response
1528
1529 assert id == other_user.id
1530 end
1531
1532 test "returns empty array when status has not been favorited yet", %{
1533 conn: conn,
1534 activity: activity
1535 } do
1536 response =
1537 conn
1538 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1539 |> json_response_and_validate_schema(:ok)
1540
1541 assert Enum.empty?(response)
1542 end
1543
1544 test "does not return users who have favorited the status but are blocked", %{
1545 conn: %{assigns: %{user: user}} = conn,
1546 activity: activity
1547 } do
1548 other_user = insert(:user)
1549 {:ok, _user_relationship} = User.block(user, other_user)
1550
1551 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1552
1553 response =
1554 conn
1555 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1556 |> json_response_and_validate_schema(:ok)
1557
1558 assert Enum.empty?(response)
1559 end
1560
1561 test "does not fail on an unauthenticated request", %{activity: activity} do
1562 other_user = insert(:user)
1563 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1564
1565 response =
1566 build_conn()
1567 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1568 |> json_response_and_validate_schema(:ok)
1569
1570 [%{"id" => id}] = response
1571 assert id == other_user.id
1572 end
1573
1574 test "requires authentication for private posts", %{user: user} do
1575 other_user = insert(:user)
1576
1577 {:ok, activity} =
1578 CommonAPI.post(user, %{
1579 status: "@#{other_user.nickname} wanna get some #cofe together?",
1580 visibility: "direct"
1581 })
1582
1583 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1584
1585 favourited_by_url = "/api/v1/statuses/#{activity.id}/favourited_by"
1586
1587 build_conn()
1588 |> get(favourited_by_url)
1589 |> json_response_and_validate_schema(404)
1590
1591 conn =
1592 build_conn()
1593 |> assign(:user, other_user)
1594 |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"]))
1595
1596 conn
1597 |> assign(:token, nil)
1598 |> get(favourited_by_url)
1599 |> json_response_and_validate_schema(404)
1600
1601 response =
1602 conn
1603 |> get(favourited_by_url)
1604 |> json_response_and_validate_schema(200)
1605
1606 [%{"id" => id}] = response
1607 assert id == other_user.id
1608 end
1609
1610 test "returns empty array when :show_reactions is disabled", %{conn: conn, activity: activity} do
1611 clear_config([:instance, :show_reactions], false)
1612
1613 other_user = insert(:user)
1614 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1615
1616 response =
1617 conn
1618 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1619 |> json_response_and_validate_schema(:ok)
1620
1621 assert Enum.empty?(response)
1622 end
1623 end
1624
1625 describe "GET /api/v1/statuses/:id/reblogged_by" do
1626 setup do: oauth_access(["read:accounts"])
1627
1628 setup %{user: user} do
1629 {:ok, activity} = CommonAPI.post(user, %{status: "test"})
1630
1631 %{activity: activity}
1632 end
1633
1634 test "returns users who have reblogged the status", %{conn: conn, activity: activity} do
1635 other_user = insert(:user)
1636 {:ok, _} = CommonAPI.repeat(activity.id, other_user)
1637
1638 response =
1639 conn
1640 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1641 |> json_response_and_validate_schema(:ok)
1642
1643 [%{"id" => id}] = response
1644
1645 assert id == other_user.id
1646 end
1647
1648 test "returns empty array when status has not been reblogged yet", %{
1649 conn: conn,
1650 activity: activity
1651 } do
1652 response =
1653 conn
1654 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1655 |> json_response_and_validate_schema(:ok)
1656
1657 assert Enum.empty?(response)
1658 end
1659
1660 test "does not return users who have reblogged the status but are blocked", %{
1661 conn: %{assigns: %{user: user}} = conn,
1662 activity: activity
1663 } do
1664 other_user = insert(:user)
1665 {:ok, _user_relationship} = User.block(user, other_user)
1666
1667 {:ok, _} = CommonAPI.repeat(activity.id, other_user)
1668
1669 response =
1670 conn
1671 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1672 |> json_response_and_validate_schema(:ok)
1673
1674 assert Enum.empty?(response)
1675 end
1676
1677 test "does not return users who have reblogged the status privately", %{
1678 conn: conn
1679 } do
1680 other_user = insert(:user)
1681 {:ok, activity} = CommonAPI.post(other_user, %{status: "my secret post"})
1682
1683 {:ok, _} = CommonAPI.repeat(activity.id, other_user, %{visibility: "private"})
1684
1685 response =
1686 conn
1687 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1688 |> json_response_and_validate_schema(:ok)
1689
1690 assert Enum.empty?(response)
1691 end
1692
1693 test "does not fail on an unauthenticated request", %{activity: activity} do
1694 other_user = insert(:user)
1695 {:ok, _} = CommonAPI.repeat(activity.id, other_user)
1696
1697 response =
1698 build_conn()
1699 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1700 |> json_response_and_validate_schema(:ok)
1701
1702 [%{"id" => id}] = response
1703 assert id == other_user.id
1704 end
1705
1706 test "requires authentication for private posts", %{user: user} do
1707 other_user = insert(:user)
1708
1709 {:ok, activity} =
1710 CommonAPI.post(user, %{
1711 status: "@#{other_user.nickname} wanna get some #cofe together?",
1712 visibility: "direct"
1713 })
1714
1715 build_conn()
1716 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1717 |> json_response_and_validate_schema(404)
1718
1719 response =
1720 build_conn()
1721 |> assign(:user, other_user)
1722 |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"]))
1723 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1724 |> json_response_and_validate_schema(200)
1725
1726 assert [] == response
1727 end
1728 end
1729
1730 test "context" do
1731 user = insert(:user)
1732
1733 {:ok, %{id: id1}} = CommonAPI.post(user, %{status: "1"})
1734 {:ok, %{id: id2}} = CommonAPI.post(user, %{status: "2", in_reply_to_status_id: id1})
1735 {:ok, %{id: id3}} = CommonAPI.post(user, %{status: "3", in_reply_to_status_id: id2})
1736 {:ok, %{id: id4}} = CommonAPI.post(user, %{status: "4", in_reply_to_status_id: id3})
1737 {:ok, %{id: id5}} = CommonAPI.post(user, %{status: "5", in_reply_to_status_id: id4})
1738
1739 response =
1740 build_conn()
1741 |> get("/api/v1/statuses/#{id3}/context")
1742 |> json_response_and_validate_schema(:ok)
1743
1744 assert %{
1745 "ancestors" => [%{"id" => ^id1}, %{"id" => ^id2}],
1746 "descendants" => [%{"id" => ^id4}, %{"id" => ^id5}]
1747 } = response
1748 end
1749
1750 test "favorites paginate correctly" do
1751 %{user: user, conn: conn} = oauth_access(["read:favourites"])
1752 other_user = insert(:user)
1753 {:ok, first_post} = CommonAPI.post(other_user, %{status: "bla"})
1754 {:ok, second_post} = CommonAPI.post(other_user, %{status: "bla"})
1755 {:ok, third_post} = CommonAPI.post(other_user, %{status: "bla"})
1756
1757 {:ok, _first_favorite} = CommonAPI.favorite(user, third_post.id)
1758 {:ok, _second_favorite} = CommonAPI.favorite(user, first_post.id)
1759 {:ok, third_favorite} = CommonAPI.favorite(user, second_post.id)
1760
1761 result =
1762 conn
1763 |> get("/api/v1/favourites?limit=1")
1764
1765 assert [%{"id" => post_id}] = json_response_and_validate_schema(result, 200)
1766 assert post_id == second_post.id
1767
1768 # Using the header for pagination works correctly
1769 [next, _] = get_resp_header(result, "link") |> hd() |> String.split(", ")
1770 [_, max_id] = Regex.run(~r/max_id=([^&]+)/, next)
1771
1772 assert max_id == third_favorite.id
1773
1774 result =
1775 conn
1776 |> get("/api/v1/favourites?max_id=#{max_id}")
1777
1778 assert [%{"id" => first_post_id}, %{"id" => third_post_id}] =
1779 json_response_and_validate_schema(result, 200)
1780
1781 assert first_post_id == first_post.id
1782 assert third_post_id == third_post.id
1783 end
1784
1785 test "returns the favorites of a user" do
1786 %{user: user, conn: conn} = oauth_access(["read:favourites"])
1787 other_user = insert(:user)
1788
1789 {:ok, _} = CommonAPI.post(other_user, %{status: "bla"})
1790 {:ok, activity} = CommonAPI.post(other_user, %{status: "trees are happy"})
1791
1792 {:ok, last_like} = CommonAPI.favorite(user, activity.id)
1793
1794 first_conn = get(conn, "/api/v1/favourites")
1795
1796 assert [status] = json_response_and_validate_schema(first_conn, 200)
1797 assert status["id"] == to_string(activity.id)
1798
1799 assert [{"link", _link_header}] =
1800 Enum.filter(first_conn.resp_headers, fn element -> match?({"link", _}, element) end)
1801
1802 # Honours query params
1803 {:ok, second_activity} =
1804 CommonAPI.post(other_user, %{
1805 status: "Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful."
1806 })
1807
1808 {:ok, _} = CommonAPI.favorite(user, second_activity.id)
1809
1810 second_conn = get(conn, "/api/v1/favourites?since_id=#{last_like.id}")
1811
1812 assert [second_status] = json_response_and_validate_schema(second_conn, 200)
1813 assert second_status["id"] == to_string(second_activity.id)
1814
1815 third_conn = get(conn, "/api/v1/favourites?limit=0")
1816
1817 assert [] = json_response_and_validate_schema(third_conn, 200)
1818 end
1819
1820 test "expires_at is nil for another user" do
1821 %{conn: conn, user: user} = oauth_access(["read:statuses"])
1822 expires_at = DateTime.add(DateTime.utc_now(), 1_000_000)
1823 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", expires_in: 1_000_000})
1824
1825 assert %{"pleroma" => %{"expires_at" => a_expires_at}} =
1826 conn
1827 |> get("/api/v1/statuses/#{activity.id}")
1828 |> json_response_and_validate_schema(:ok)
1829
1830 {:ok, a_expires_at, 0} = DateTime.from_iso8601(a_expires_at)
1831 assert DateTime.diff(expires_at, a_expires_at) == 0
1832
1833 %{conn: conn} = oauth_access(["read:statuses"])
1834
1835 assert %{"pleroma" => %{"expires_at" => nil}} =
1836 conn
1837 |> get("/api/v1/statuses/#{activity.id}")
1838 |> json_response_and_validate_schema(:ok)
1839 end
1840
1841 test "posting a local only status" do
1842 %{user: _user, conn: conn} = oauth_access(["write:statuses"])
1843
1844 conn_one =
1845 conn
1846 |> put_req_header("content-type", "application/json")
1847 |> post("/api/v1/statuses", %{
1848 "status" => "cofe",
1849 "visibility" => "local"
1850 })
1851
1852 local = Pleroma.Constants.as_local_public()
1853
1854 assert %{"content" => "cofe", "id" => id, "visibility" => "local"} =
1855 json_response(conn_one, 200)
1856
1857 assert %Activity{id: ^id, data: %{"to" => [^local]}} = Activity.get_by_id(id)
1858 end
1859
1860 describe "muted reactions" do
1861 test "index" do
1862 %{conn: conn, user: user} = oauth_access(["read:statuses"])
1863
1864 other_user = insert(:user)
1865 {:ok, activity} = CommonAPI.post(user, %{status: "test"})
1866
1867 {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
1868 User.mute(user, other_user)
1869
1870 result =
1871 conn
1872 |> get("/api/v1/statuses/?ids[]=#{activity.id}")
1873 |> json_response_and_validate_schema(200)
1874
1875 assert [
1876 %{
1877 "pleroma" => %{
1878 "emoji_reactions" => []
1879 }
1880 }
1881 ] = result
1882
1883 result =
1884 conn
1885 |> get("/api/v1/statuses/?ids[]=#{activity.id}&with_muted=true")
1886 |> json_response_and_validate_schema(200)
1887
1888 assert [
1889 %{
1890 "pleroma" => %{
1891 "emoji_reactions" => [%{"count" => 1, "me" => false, "name" => "🎅"}]
1892 }
1893 }
1894 ] = result
1895 end
1896
1897 test "show" do
1898 # %{conn: conn, user: user, token: token} = oauth_access(["read:statuses"])
1899 %{conn: conn, user: user, token: _token} = oauth_access(["read:statuses"])
1900
1901 other_user = insert(:user)
1902 {:ok, activity} = CommonAPI.post(user, %{status: "test"})
1903
1904 {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
1905 User.mute(user, other_user)
1906
1907 result =
1908 conn
1909 |> get("/api/v1/statuses/#{activity.id}")
1910 |> json_response_and_validate_schema(200)
1911
1912 assert %{
1913 "pleroma" => %{
1914 "emoji_reactions" => []
1915 }
1916 } = result
1917
1918 result =
1919 conn
1920 |> get("/api/v1/statuses/#{activity.id}?with_muted=true")
1921 |> json_response_and_validate_schema(200)
1922
1923 assert %{
1924 "pleroma" => %{
1925 "emoji_reactions" => [%{"count" => 1, "me" => false, "name" => "🎅"}]
1926 }
1927 } = result
1928 end
1929 end
1930 end