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