maintenance: dependency upgrade (#81)
[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 describe "cards" do
1353 setup do
1354 clear_config([:rich_media, :enabled], true)
1355
1356 oauth_access(["read:statuses"])
1357 end
1358
1359 test "returns rich-media card", %{conn: conn, user: user} do
1360 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
1361
1362 {:ok, activity} = CommonAPI.post(user, %{status: "https://example.com/ogp"})
1363
1364 card_data = %{
1365 "image" => "http://ia.media-imdb.com/images/rock.jpg",
1366 "provider_name" => "example.com",
1367 "provider_url" => "https://example.com",
1368 "title" => "The Rock",
1369 "type" => "link",
1370 "url" => "https://example.com/ogp",
1371 "description" =>
1372 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
1373 "pleroma" => %{
1374 "opengraph" => %{
1375 "image" => "http://ia.media-imdb.com/images/rock.jpg",
1376 "title" => "The Rock",
1377 "type" => "video.movie",
1378 "url" => "https://example.com/ogp",
1379 "description" =>
1380 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer."
1381 }
1382 }
1383 }
1384
1385 response =
1386 conn
1387 |> get("/api/v1/statuses/#{activity.id}/card")
1388 |> json_response_and_validate_schema(200)
1389
1390 assert response == card_data
1391
1392 # works with private posts
1393 {:ok, activity} =
1394 CommonAPI.post(user, %{status: "https://example.com/ogp", visibility: "direct"})
1395
1396 response_two =
1397 conn
1398 |> get("/api/v1/statuses/#{activity.id}/card")
1399 |> json_response_and_validate_schema(200)
1400
1401 assert response_two == card_data
1402 end
1403
1404 test "replaces missing description with an empty string", %{conn: conn, user: user} do
1405 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
1406
1407 {:ok, activity} = CommonAPI.post(user, %{status: "https://example.com/ogp-missing-data"})
1408
1409 response =
1410 conn
1411 |> get("/api/v1/statuses/#{activity.id}/card")
1412 |> json_response_and_validate_schema(:ok)
1413
1414 assert response == %{
1415 "type" => "link",
1416 "title" => "Pleroma",
1417 "description" => "",
1418 "image" => nil,
1419 "provider_name" => "example.com",
1420 "provider_url" => "https://example.com",
1421 "url" => "https://example.com/ogp-missing-data",
1422 "pleroma" => %{
1423 "opengraph" => %{
1424 "title" => "Pleroma",
1425 "type" => "website",
1426 "url" => "https://example.com/ogp-missing-data"
1427 }
1428 }
1429 }
1430 end
1431 end
1432
1433 test "bookmarks" do
1434 bookmarks_uri = "/api/v1/bookmarks"
1435
1436 %{conn: conn} = oauth_access(["write:bookmarks", "read:bookmarks"])
1437 author = insert(:user)
1438
1439 {:ok, activity1} = CommonAPI.post(author, %{status: "heweoo?"})
1440 {:ok, activity2} = CommonAPI.post(author, %{status: "heweoo!"})
1441
1442 response1 =
1443 conn
1444 |> put_req_header("content-type", "application/json")
1445 |> post("/api/v1/statuses/#{activity1.id}/bookmark")
1446
1447 assert json_response_and_validate_schema(response1, 200)["bookmarked"] == true
1448
1449 response2 =
1450 conn
1451 |> put_req_header("content-type", "application/json")
1452 |> post("/api/v1/statuses/#{activity2.id}/bookmark")
1453
1454 assert json_response_and_validate_schema(response2, 200)["bookmarked"] == true
1455
1456 bookmarks = get(conn, bookmarks_uri)
1457
1458 assert [
1459 json_response_and_validate_schema(response2, 200),
1460 json_response_and_validate_schema(response1, 200)
1461 ] ==
1462 json_response_and_validate_schema(bookmarks, 200)
1463
1464 response1 =
1465 conn
1466 |> put_req_header("content-type", "application/json")
1467 |> post("/api/v1/statuses/#{activity1.id}/unbookmark")
1468
1469 assert json_response_and_validate_schema(response1, 200)["bookmarked"] == false
1470
1471 bookmarks = get(conn, bookmarks_uri)
1472
1473 assert [json_response_and_validate_schema(response2, 200)] ==
1474 json_response_and_validate_schema(bookmarks, 200)
1475 end
1476
1477 describe "conversation muting" do
1478 setup do: oauth_access(["write:mutes"])
1479
1480 setup do
1481 post_user = insert(:user)
1482 {:ok, activity} = CommonAPI.post(post_user, %{status: "HIE"})
1483 %{activity: activity}
1484 end
1485
1486 test "mute conversation", %{conn: conn, activity: activity} do
1487 id_str = to_string(activity.id)
1488
1489 assert %{"id" => ^id_str, "muted" => true} =
1490 conn
1491 |> put_req_header("content-type", "application/json")
1492 |> post("/api/v1/statuses/#{activity.id}/mute")
1493 |> json_response_and_validate_schema(200)
1494 end
1495
1496 test "cannot mute already muted conversation", %{conn: conn, user: user, activity: activity} do
1497 {:ok, _} = CommonAPI.add_mute(user, activity)
1498
1499 conn =
1500 conn
1501 |> put_req_header("content-type", "application/json")
1502 |> post("/api/v1/statuses/#{activity.id}/mute")
1503
1504 assert json_response_and_validate_schema(conn, 400) == %{
1505 "error" => "conversation is already muted"
1506 }
1507 end
1508
1509 test "unmute conversation", %{conn: conn, user: user, activity: activity} do
1510 {:ok, _} = CommonAPI.add_mute(user, activity)
1511
1512 id_str = to_string(activity.id)
1513
1514 assert %{"id" => ^id_str, "muted" => false} =
1515 conn
1516 # |> assign(:user, user)
1517 |> post("/api/v1/statuses/#{activity.id}/unmute")
1518 |> json_response_and_validate_schema(200)
1519 end
1520 end
1521
1522 test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do
1523 user1 = insert(:user)
1524 user2 = insert(:user)
1525 user3 = insert(:user)
1526
1527 {:ok, replied_to} = CommonAPI.post(user1, %{status: "cofe"})
1528
1529 # Reply to status from another user
1530 conn1 =
1531 conn
1532 |> assign(:user, user2)
1533 |> assign(:token, insert(:oauth_token, user: user2, scopes: ["write:statuses"]))
1534 |> put_req_header("content-type", "application/json")
1535 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
1536
1537 assert %{"content" => "xD", "id" => id} = json_response_and_validate_schema(conn1, 200)
1538
1539 activity = Activity.get_by_id_with_object(id)
1540
1541 assert Object.normalize(activity, fetch: false).data["inReplyTo"] ==
1542 Object.normalize(replied_to, fetch: false).data["id"]
1543
1544 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
1545
1546 # Reblog from the third user
1547 conn2 =
1548 conn
1549 |> assign(:user, user3)
1550 |> assign(:token, insert(:oauth_token, user: user3, scopes: ["write:statuses"]))
1551 |> put_req_header("content-type", "application/json")
1552 |> post("/api/v1/statuses/#{activity.id}/reblog")
1553
1554 assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} =
1555 json_response_and_validate_schema(conn2, 200)
1556
1557 assert to_string(activity.id) == id
1558
1559 # Getting third user status
1560 conn3 =
1561 conn
1562 |> assign(:user, user3)
1563 |> assign(:token, insert(:oauth_token, user: user3, scopes: ["read:statuses"]))
1564 |> get("api/v1/timelines/home")
1565
1566 [reblogged_activity] = json_response_and_validate_schema(conn3, 200)
1567
1568 assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id
1569
1570 replied_to_user = User.get_by_ap_id(replied_to.data["actor"])
1571 assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id
1572 end
1573
1574 describe "GET /api/v1/statuses/:id/favourited_by" do
1575 setup do: oauth_access(["read:accounts"])
1576
1577 setup %{user: user} do
1578 {:ok, activity} = CommonAPI.post(user, %{status: "test"})
1579
1580 %{activity: activity}
1581 end
1582
1583 test "returns users who have favorited the status", %{conn: conn, activity: activity} do
1584 other_user = insert(:user)
1585 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1586
1587 response =
1588 conn
1589 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1590 |> json_response_and_validate_schema(:ok)
1591
1592 [%{"id" => id}] = response
1593
1594 assert id == other_user.id
1595 end
1596
1597 test "returns empty array when status has not been favorited yet", %{
1598 conn: conn,
1599 activity: activity
1600 } do
1601 response =
1602 conn
1603 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1604 |> json_response_and_validate_schema(:ok)
1605
1606 assert Enum.empty?(response)
1607 end
1608
1609 test "does not return users who have favorited the status but are blocked", %{
1610 conn: %{assigns: %{user: user}} = conn,
1611 activity: activity
1612 } do
1613 other_user = insert(:user)
1614 {:ok, _user_relationship} = User.block(user, other_user)
1615
1616 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1617
1618 response =
1619 conn
1620 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1621 |> json_response_and_validate_schema(:ok)
1622
1623 assert Enum.empty?(response)
1624 end
1625
1626 test "does not fail on an unauthenticated request", %{activity: activity} do
1627 other_user = insert(:user)
1628 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1629
1630 response =
1631 build_conn()
1632 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1633 |> json_response_and_validate_schema(:ok)
1634
1635 [%{"id" => id}] = response
1636 assert id == other_user.id
1637 end
1638
1639 test "requires authentication for private posts", %{user: user} do
1640 other_user = insert(:user)
1641
1642 {:ok, activity} =
1643 CommonAPI.post(user, %{
1644 status: "@#{other_user.nickname} wanna get some #cofe together?",
1645 visibility: "direct"
1646 })
1647
1648 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1649
1650 favourited_by_url = "/api/v1/statuses/#{activity.id}/favourited_by"
1651
1652 build_conn()
1653 |> get(favourited_by_url)
1654 |> json_response_and_validate_schema(404)
1655
1656 conn =
1657 build_conn()
1658 |> assign(:user, other_user)
1659 |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"]))
1660
1661 conn
1662 |> assign(:token, nil)
1663 |> get(favourited_by_url)
1664 |> json_response_and_validate_schema(404)
1665
1666 response =
1667 conn
1668 |> get(favourited_by_url)
1669 |> json_response_and_validate_schema(200)
1670
1671 [%{"id" => id}] = response
1672 assert id == other_user.id
1673 end
1674
1675 test "returns empty array when :show_reactions is disabled", %{conn: conn, activity: activity} do
1676 clear_config([:instance, :show_reactions], false)
1677
1678 other_user = insert(:user)
1679 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1680
1681 response =
1682 conn
1683 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1684 |> json_response_and_validate_schema(:ok)
1685
1686 assert Enum.empty?(response)
1687 end
1688 end
1689
1690 describe "GET /api/v1/statuses/:id/reblogged_by" do
1691 setup do: oauth_access(["read:accounts"])
1692
1693 setup %{user: user} do
1694 {:ok, activity} = CommonAPI.post(user, %{status: "test"})
1695
1696 %{activity: activity}
1697 end
1698
1699 test "returns users who have reblogged the status", %{conn: conn, activity: activity} do
1700 other_user = insert(:user)
1701 {:ok, _} = CommonAPI.repeat(activity.id, other_user)
1702
1703 response =
1704 conn
1705 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1706 |> json_response_and_validate_schema(:ok)
1707
1708 [%{"id" => id}] = response
1709
1710 assert id == other_user.id
1711 end
1712
1713 test "returns empty array when status has not been reblogged yet", %{
1714 conn: conn,
1715 activity: activity
1716 } do
1717 response =
1718 conn
1719 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1720 |> json_response_and_validate_schema(:ok)
1721
1722 assert Enum.empty?(response)
1723 end
1724
1725 test "does not return users who have reblogged the status but are blocked", %{
1726 conn: %{assigns: %{user: user}} = conn,
1727 activity: activity
1728 } do
1729 other_user = insert(:user)
1730 {:ok, _user_relationship} = User.block(user, other_user)
1731
1732 {:ok, _} = CommonAPI.repeat(activity.id, other_user)
1733
1734 response =
1735 conn
1736 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1737 |> json_response_and_validate_schema(:ok)
1738
1739 assert Enum.empty?(response)
1740 end
1741
1742 test "does not return users who have reblogged the status privately", %{
1743 conn: conn
1744 } do
1745 other_user = insert(:user)
1746 {:ok, activity} = CommonAPI.post(other_user, %{status: "my secret post"})
1747
1748 {:ok, _} = CommonAPI.repeat(activity.id, other_user, %{visibility: "private"})
1749
1750 response =
1751 conn
1752 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1753 |> json_response_and_validate_schema(:ok)
1754
1755 assert Enum.empty?(response)
1756 end
1757
1758 test "does not fail on an unauthenticated request", %{activity: activity} do
1759 other_user = insert(:user)
1760 {:ok, _} = CommonAPI.repeat(activity.id, other_user)
1761
1762 response =
1763 build_conn()
1764 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1765 |> json_response_and_validate_schema(:ok)
1766
1767 [%{"id" => id}] = response
1768 assert id == other_user.id
1769 end
1770
1771 test "requires authentication for private posts", %{user: user} do
1772 other_user = insert(:user)
1773
1774 {:ok, activity} =
1775 CommonAPI.post(user, %{
1776 status: "@#{other_user.nickname} wanna get some #cofe together?",
1777 visibility: "direct"
1778 })
1779
1780 build_conn()
1781 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1782 |> json_response_and_validate_schema(404)
1783
1784 response =
1785 build_conn()
1786 |> assign(:user, other_user)
1787 |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"]))
1788 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1789 |> json_response_and_validate_schema(200)
1790
1791 assert [] == response
1792 end
1793 end
1794
1795 test "context" do
1796 user = insert(:user)
1797
1798 {:ok, %{id: id1}} = CommonAPI.post(user, %{status: "1"})
1799 {:ok, %{id: id2}} = CommonAPI.post(user, %{status: "2", in_reply_to_status_id: id1})
1800 {:ok, %{id: id3}} = CommonAPI.post(user, %{status: "3", in_reply_to_status_id: id2})
1801 {:ok, %{id: id4}} = CommonAPI.post(user, %{status: "4", in_reply_to_status_id: id3})
1802 {:ok, %{id: id5}} = CommonAPI.post(user, %{status: "5", in_reply_to_status_id: id4})
1803
1804 response =
1805 build_conn()
1806 |> get("/api/v1/statuses/#{id3}/context")
1807 |> json_response_and_validate_schema(:ok)
1808
1809 assert %{
1810 "ancestors" => [%{"id" => ^id1}, %{"id" => ^id2}],
1811 "descendants" => [%{"id" => ^id4}, %{"id" => ^id5}]
1812 } = response
1813 end
1814
1815 test "context when restrict_unauthenticated is on" do
1816 user = insert(:user)
1817 remote_user = insert(:user, local: false)
1818
1819 {:ok, %{id: id1}} = CommonAPI.post(user, %{status: "1"})
1820 {:ok, %{id: id2}} = CommonAPI.post(user, %{status: "2", in_reply_to_status_id: id1})
1821
1822 {:ok, %{id: id3}} =
1823 CommonAPI.post(remote_user, %{status: "3", in_reply_to_status_id: id2, local: false})
1824
1825 response =
1826 build_conn()
1827 |> get("/api/v1/statuses/#{id2}/context")
1828 |> json_response_and_validate_schema(:ok)
1829
1830 assert %{
1831 "ancestors" => [%{"id" => ^id1}],
1832 "descendants" => [%{"id" => ^id3}]
1833 } = response
1834
1835 clear_config([:restrict_unauthenticated, :activities, :local], true)
1836
1837 response =
1838 build_conn()
1839 |> get("/api/v1/statuses/#{id2}/context")
1840 |> json_response_and_validate_schema(:ok)
1841
1842 assert %{
1843 "ancestors" => [],
1844 "descendants" => []
1845 } = response
1846 end
1847
1848 test "favorites paginate correctly" do
1849 %{user: user, conn: conn} = oauth_access(["read:favourites"])
1850 other_user = insert(:user)
1851 {:ok, first_post} = CommonAPI.post(other_user, %{status: "bla"})
1852 {:ok, second_post} = CommonAPI.post(other_user, %{status: "bla"})
1853 {:ok, third_post} = CommonAPI.post(other_user, %{status: "bla"})
1854
1855 {:ok, _first_favorite} = CommonAPI.favorite(user, third_post.id)
1856 {:ok, _second_favorite} = CommonAPI.favorite(user, first_post.id)
1857 {:ok, third_favorite} = CommonAPI.favorite(user, second_post.id)
1858
1859 result =
1860 conn
1861 |> get("/api/v1/favourites?limit=1")
1862
1863 assert [%{"id" => post_id}] = json_response_and_validate_schema(result, 200)
1864 assert post_id == second_post.id
1865
1866 # Using the header for pagination works correctly
1867 [next, _] = get_resp_header(result, "link") |> hd() |> String.split(", ")
1868 [_, max_id] = Regex.run(~r/max_id=([^&]+)/, next)
1869
1870 assert max_id == third_favorite.id
1871
1872 result =
1873 conn
1874 |> get("/api/v1/favourites?max_id=#{max_id}")
1875
1876 assert [%{"id" => first_post_id}, %{"id" => third_post_id}] =
1877 json_response_and_validate_schema(result, 200)
1878
1879 assert first_post_id == first_post.id
1880 assert third_post_id == third_post.id
1881 end
1882
1883 test "returns the favorites of a user" do
1884 %{user: user, conn: conn} = oauth_access(["read:favourites"])
1885 other_user = insert(:user)
1886
1887 {:ok, _} = CommonAPI.post(other_user, %{status: "bla"})
1888 {:ok, activity} = CommonAPI.post(other_user, %{status: "trees are happy"})
1889
1890 {:ok, last_like} = CommonAPI.favorite(user, activity.id)
1891
1892 first_conn = get(conn, "/api/v1/favourites")
1893
1894 assert [status] = json_response_and_validate_schema(first_conn, 200)
1895 assert status["id"] == to_string(activity.id)
1896
1897 assert [{"link", _link_header}] =
1898 Enum.filter(first_conn.resp_headers, fn element -> match?({"link", _}, element) end)
1899
1900 # Honours query params
1901 {:ok, second_activity} =
1902 CommonAPI.post(other_user, %{
1903 status: "Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful."
1904 })
1905
1906 {:ok, _} = CommonAPI.favorite(user, second_activity.id)
1907
1908 second_conn = get(conn, "/api/v1/favourites?since_id=#{last_like.id}")
1909
1910 assert [second_status] = json_response_and_validate_schema(second_conn, 200)
1911 assert second_status["id"] == to_string(second_activity.id)
1912
1913 third_conn = get(conn, "/api/v1/favourites?limit=0")
1914
1915 assert [] = json_response_and_validate_schema(third_conn, 200)
1916 end
1917
1918 test "expires_at is nil for another user" do
1919 %{conn: conn, user: user} = oauth_access(["read:statuses"])
1920 expires_at = DateTime.add(DateTime.utc_now(), 1_000_000)
1921 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", expires_in: 1_000_000})
1922
1923 assert %{"pleroma" => %{"expires_at" => a_expires_at}} =
1924 conn
1925 |> get("/api/v1/statuses/#{activity.id}")
1926 |> json_response_and_validate_schema(:ok)
1927
1928 {:ok, a_expires_at, 0} = DateTime.from_iso8601(a_expires_at)
1929 assert DateTime.diff(expires_at, a_expires_at) == 0
1930
1931 %{conn: conn} = oauth_access(["read:statuses"])
1932
1933 assert %{"pleroma" => %{"expires_at" => nil}} =
1934 conn
1935 |> get("/api/v1/statuses/#{activity.id}")
1936 |> json_response_and_validate_schema(:ok)
1937 end
1938
1939 test "posting a local only status" do
1940 %{user: _user, conn: conn} = oauth_access(["write:statuses"])
1941
1942 conn_one =
1943 conn
1944 |> put_req_header("content-type", "application/json")
1945 |> post("/api/v1/statuses", %{
1946 "status" => "cofe",
1947 "visibility" => "local"
1948 })
1949
1950 local = Utils.as_local_public()
1951
1952 assert %{"content" => "cofe", "id" => id, "visibility" => "local"} =
1953 json_response_and_validate_schema(conn_one, 200)
1954
1955 assert %Activity{id: ^id, data: %{"to" => [^local]}} = Activity.get_by_id(id)
1956 end
1957
1958 describe "muted reactions" do
1959 test "index" do
1960 %{conn: conn, user: user} = oauth_access(["read:statuses"])
1961
1962 other_user = insert(:user)
1963 {:ok, activity} = CommonAPI.post(user, %{status: "test"})
1964
1965 {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
1966 User.mute(user, other_user)
1967
1968 result =
1969 conn
1970 |> get("/api/v1/statuses/?ids[]=#{activity.id}")
1971 |> json_response_and_validate_schema(200)
1972
1973 assert [
1974 %{
1975 "pleroma" => %{
1976 "emoji_reactions" => []
1977 }
1978 }
1979 ] = result
1980
1981 result =
1982 conn
1983 |> get("/api/v1/statuses/?ids[]=#{activity.id}&with_muted=true")
1984 |> json_response_and_validate_schema(200)
1985
1986 assert [
1987 %{
1988 "pleroma" => %{
1989 "emoji_reactions" => [%{"count" => 1, "me" => false, "name" => "🎅"}]
1990 }
1991 }
1992 ] = result
1993 end
1994
1995 test "show" do
1996 # %{conn: conn, user: user, token: token} = oauth_access(["read:statuses"])
1997 %{conn: conn, user: user, token: _token} = oauth_access(["read:statuses"])
1998
1999 other_user = insert(:user)
2000 {:ok, activity} = CommonAPI.post(user, %{status: "test"})
2001
2002 {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
2003 User.mute(user, other_user)
2004
2005 result =
2006 conn
2007 |> get("/api/v1/statuses/#{activity.id}")
2008 |> json_response_and_validate_schema(200)
2009
2010 assert %{
2011 "pleroma" => %{
2012 "emoji_reactions" => []
2013 }
2014 } = result
2015
2016 result =
2017 conn
2018 |> get("/api/v1/statuses/#{activity.id}?with_muted=true")
2019 |> json_response_and_validate_schema(200)
2020
2021 assert %{
2022 "pleroma" => %{
2023 "emoji_reactions" => [%{"count" => 1, "me" => false, "name" => "🎅"}]
2024 }
2025 } = result
2026 end
2027 end
2028 end