Merge branch 'docs/kyclos' into 'develop'
[akkoma] / test / web / mastodon_api / controllers / status_controller_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 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
8 alias Pleroma.Activity
9 alias Pleroma.ActivityExpiration
10 alias Pleroma.Config
11 alias Pleroma.Conversation.Participation
12 alias Pleroma.Object
13 alias Pleroma.Repo
14 alias Pleroma.ScheduledActivity
15 alias Pleroma.Tests.ObanHelpers
16 alias Pleroma.User
17 alias Pleroma.Web.ActivityPub.ActivityPub
18 alias Pleroma.Web.CommonAPI
19
20 import Pleroma.Factory
21
22 clear_config([:instance, :federating])
23 clear_config([:instance, :allow_relay])
24
25 describe "posting statuses" do
26 setup do: oauth_access(["write:statuses"])
27
28 test "posting a status does not increment reblog_count when relaying", %{conn: conn} do
29 Pleroma.Config.put([:instance, :federating], true)
30 Pleroma.Config.get([:instance, :allow_relay], true)
31
32 response =
33 conn
34 |> post("api/v1/statuses", %{
35 "content_type" => "text/plain",
36 "source" => "Pleroma FE",
37 "status" => "Hello world",
38 "visibility" => "public"
39 })
40 |> json_response(200)
41
42 assert response["reblogs_count"] == 0
43 ObanHelpers.perform_all()
44
45 response =
46 conn
47 |> get("api/v1/statuses/#{response["id"]}", %{})
48 |> json_response(200)
49
50 assert response["reblogs_count"] == 0
51 end
52
53 test "posting a status", %{conn: conn} do
54 idempotency_key = "Pikachu rocks!"
55
56 conn_one =
57 conn
58 |> put_req_header("idempotency-key", idempotency_key)
59 |> post("/api/v1/statuses", %{
60 "status" => "cofe",
61 "spoiler_text" => "2hu",
62 "sensitive" => "false"
63 })
64
65 {:ok, ttl} = Cachex.ttl(:idempotency_cache, idempotency_key)
66 # Six hours
67 assert ttl > :timer.seconds(6 * 60 * 60 - 1)
68
69 assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} =
70 json_response(conn_one, 200)
71
72 assert Activity.get_by_id(id)
73
74 conn_two =
75 conn
76 |> put_req_header("idempotency-key", idempotency_key)
77 |> post("/api/v1/statuses", %{
78 "status" => "cofe",
79 "spoiler_text" => "2hu",
80 "sensitive" => "false"
81 })
82
83 assert %{"id" => second_id} = json_response(conn_two, 200)
84 assert id == second_id
85
86 conn_three =
87 conn
88 |> post("/api/v1/statuses", %{
89 "status" => "cofe",
90 "spoiler_text" => "2hu",
91 "sensitive" => "false"
92 })
93
94 assert %{"id" => third_id} = json_response(conn_three, 200)
95 refute id == third_id
96
97 # An activity that will expire:
98 # 2 hours
99 expires_in = 120 * 60
100
101 conn_four =
102 conn
103 |> post("api/v1/statuses", %{
104 "status" => "oolong",
105 "expires_in" => expires_in
106 })
107
108 assert fourth_response = %{"id" => fourth_id} = json_response(conn_four, 200)
109 assert activity = Activity.get_by_id(fourth_id)
110 assert expiration = ActivityExpiration.get_by_activity_id(fourth_id)
111
112 estimated_expires_at =
113 NaiveDateTime.utc_now()
114 |> NaiveDateTime.add(expires_in)
115 |> NaiveDateTime.truncate(:second)
116
117 # This assert will fail if the test takes longer than a minute. I sure hope it never does:
118 assert abs(NaiveDateTime.diff(expiration.scheduled_at, estimated_expires_at, :second)) < 60
119
120 assert fourth_response["pleroma"]["expires_at"] ==
121 NaiveDateTime.to_iso8601(expiration.scheduled_at)
122 end
123
124 test "posting an undefined status with an attachment", %{user: user, conn: conn} do
125 file = %Plug.Upload{
126 content_type: "image/jpg",
127 path: Path.absname("test/fixtures/image.jpg"),
128 filename: "an_image.jpg"
129 }
130
131 {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
132
133 conn =
134 post(conn, "/api/v1/statuses", %{
135 "media_ids" => [to_string(upload.id)]
136 })
137
138 assert json_response(conn, 200)
139 end
140
141 test "replying to a status", %{user: user, conn: conn} do
142 {:ok, replied_to} = CommonAPI.post(user, %{"status" => "cofe"})
143
144 conn =
145 conn
146 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
147
148 assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
149
150 activity = Activity.get_by_id(id)
151
152 assert activity.data["context"] == replied_to.data["context"]
153 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
154 end
155
156 test "replying to a direct message with visibility other than direct", %{
157 user: user,
158 conn: conn
159 } do
160 {:ok, replied_to} = CommonAPI.post(user, %{"status" => "suya..", "visibility" => "direct"})
161
162 Enum.each(["public", "private", "unlisted"], fn visibility ->
163 conn =
164 conn
165 |> post("/api/v1/statuses", %{
166 "status" => "@#{user.nickname} hey",
167 "in_reply_to_id" => replied_to.id,
168 "visibility" => visibility
169 })
170
171 assert json_response(conn, 422) == %{"error" => "The message visibility must be direct"}
172 end)
173 end
174
175 test "posting a status with an invalid in_reply_to_id", %{conn: conn} do
176 conn = post(conn, "/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => ""})
177
178 assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
179 assert Activity.get_by_id(id)
180 end
181
182 test "posting a sensitive status", %{conn: conn} do
183 conn = post(conn, "/api/v1/statuses", %{"status" => "cofe", "sensitive" => true})
184
185 assert %{"content" => "cofe", "id" => id, "sensitive" => true} = json_response(conn, 200)
186 assert Activity.get_by_id(id)
187 end
188
189 test "posting a fake status", %{conn: conn} do
190 real_conn =
191 post(conn, "/api/v1/statuses", %{
192 "status" =>
193 "\"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"
194 })
195
196 real_status = json_response(real_conn, 200)
197
198 assert real_status
199 assert Object.get_by_ap_id(real_status["uri"])
200
201 real_status =
202 real_status
203 |> Map.put("id", nil)
204 |> Map.put("url", nil)
205 |> Map.put("uri", nil)
206 |> Map.put("created_at", nil)
207 |> Kernel.put_in(["pleroma", "conversation_id"], nil)
208
209 fake_conn =
210 post(conn, "/api/v1/statuses", %{
211 "status" =>
212 "\"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",
213 "preview" => true
214 })
215
216 fake_status = json_response(fake_conn, 200)
217
218 assert fake_status
219 refute Object.get_by_ap_id(fake_status["uri"])
220
221 fake_status =
222 fake_status
223 |> Map.put("id", nil)
224 |> Map.put("url", nil)
225 |> Map.put("uri", nil)
226 |> Map.put("created_at", nil)
227 |> Kernel.put_in(["pleroma", "conversation_id"], nil)
228
229 assert real_status == fake_status
230 end
231
232 test "posting a status with OGP link preview", %{conn: conn} do
233 Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
234 Config.put([:rich_media, :enabled], true)
235
236 conn =
237 post(conn, "/api/v1/statuses", %{
238 "status" => "https://example.com/ogp"
239 })
240
241 assert %{"id" => id, "card" => %{"title" => "The Rock"}} = json_response(conn, 200)
242 assert Activity.get_by_id(id)
243 end
244
245 test "posting a direct status", %{conn: conn} do
246 user2 = insert(:user)
247 content = "direct cofe @#{user2.nickname}"
248
249 conn = post(conn, "api/v1/statuses", %{"status" => content, "visibility" => "direct"})
250
251 assert %{"id" => id} = response = json_response(conn, 200)
252 assert response["visibility"] == "direct"
253 assert response["pleroma"]["direct_conversation_id"]
254 assert activity = Activity.get_by_id(id)
255 assert activity.recipients == [user2.ap_id, conn.assigns[:user].ap_id]
256 assert activity.data["to"] == [user2.ap_id]
257 assert activity.data["cc"] == []
258 end
259 end
260
261 describe "posting scheduled statuses" do
262 setup do: oauth_access(["write:statuses"])
263
264 test "creates a scheduled activity", %{conn: conn} do
265 scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
266
267 conn =
268 post(conn, "/api/v1/statuses", %{
269 "status" => "scheduled",
270 "scheduled_at" => scheduled_at
271 })
272
273 assert %{"scheduled_at" => expected_scheduled_at} = json_response(conn, 200)
274 assert expected_scheduled_at == CommonAPI.Utils.to_masto_date(scheduled_at)
275 assert [] == Repo.all(Activity)
276 end
277
278 test "creates a scheduled activity with a media attachment", %{user: user, conn: conn} do
279 scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
280
281 file = %Plug.Upload{
282 content_type: "image/jpg",
283 path: Path.absname("test/fixtures/image.jpg"),
284 filename: "an_image.jpg"
285 }
286
287 {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
288
289 conn =
290 post(conn, "/api/v1/statuses", %{
291 "media_ids" => [to_string(upload.id)],
292 "status" => "scheduled",
293 "scheduled_at" => scheduled_at
294 })
295
296 assert %{"media_attachments" => [media_attachment]} = json_response(conn, 200)
297 assert %{"type" => "image"} = media_attachment
298 end
299
300 test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now",
301 %{conn: conn} do
302 scheduled_at =
303 NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond)
304
305 conn =
306 post(conn, "/api/v1/statuses", %{
307 "status" => "not scheduled",
308 "scheduled_at" => scheduled_at
309 })
310
311 assert %{"content" => "not scheduled"} = json_response(conn, 200)
312 assert [] == Repo.all(ScheduledActivity)
313 end
314
315 test "returns error when daily user limit is exceeded", %{user: user, conn: conn} do
316 today =
317 NaiveDateTime.utc_now()
318 |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
319 |> NaiveDateTime.to_iso8601()
320
321 attrs = %{params: %{}, scheduled_at: today}
322 {:ok, _} = ScheduledActivity.create(user, attrs)
323 {:ok, _} = ScheduledActivity.create(user, attrs)
324
325 conn = post(conn, "/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today})
326
327 assert %{"error" => "daily limit exceeded"} == json_response(conn, 422)
328 end
329
330 test "returns error when total user limit is exceeded", %{user: user, conn: conn} do
331 today =
332 NaiveDateTime.utc_now()
333 |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
334 |> NaiveDateTime.to_iso8601()
335
336 tomorrow =
337 NaiveDateTime.utc_now()
338 |> NaiveDateTime.add(:timer.hours(36), :millisecond)
339 |> NaiveDateTime.to_iso8601()
340
341 attrs = %{params: %{}, scheduled_at: today}
342 {:ok, _} = ScheduledActivity.create(user, attrs)
343 {:ok, _} = ScheduledActivity.create(user, attrs)
344 {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
345
346 conn =
347 post(conn, "/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow})
348
349 assert %{"error" => "total limit exceeded"} == json_response(conn, 422)
350 end
351 end
352
353 describe "posting polls" do
354 setup do: oauth_access(["write:statuses"])
355
356 test "posting a poll", %{conn: conn} do
357 time = NaiveDateTime.utc_now()
358
359 conn =
360 post(conn, "/api/v1/statuses", %{
361 "status" => "Who is the #bestgrill?",
362 "poll" => %{"options" => ["Rei", "Asuka", "Misato"], "expires_in" => 420}
363 })
364
365 response = json_response(conn, 200)
366
367 assert Enum.all?(response["poll"]["options"], fn %{"title" => title} ->
368 title in ["Rei", "Asuka", "Misato"]
369 end)
370
371 assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430
372 refute response["poll"]["expred"]
373
374 question = Object.get_by_id(response["poll"]["id"])
375
376 # closed contains utc timezone
377 assert question.data["closed"] =~ "Z"
378 end
379
380 test "option limit is enforced", %{conn: conn} do
381 limit = Config.get([:instance, :poll_limits, :max_options])
382
383 conn =
384 post(conn, "/api/v1/statuses", %{
385 "status" => "desu~",
386 "poll" => %{"options" => Enum.map(0..limit, fn _ -> "desu" end), "expires_in" => 1}
387 })
388
389 %{"error" => error} = json_response(conn, 422)
390 assert error == "Poll can't contain more than #{limit} options"
391 end
392
393 test "option character limit is enforced", %{conn: conn} do
394 limit = Config.get([:instance, :poll_limits, :max_option_chars])
395
396 conn =
397 post(conn, "/api/v1/statuses", %{
398 "status" => "...",
399 "poll" => %{
400 "options" => [Enum.reduce(0..limit, "", fn _, acc -> acc <> "." end)],
401 "expires_in" => 1
402 }
403 })
404
405 %{"error" => error} = json_response(conn, 422)
406 assert error == "Poll options cannot be longer than #{limit} characters each"
407 end
408
409 test "minimal date limit is enforced", %{conn: conn} do
410 limit = Config.get([:instance, :poll_limits, :min_expiration])
411
412 conn =
413 post(conn, "/api/v1/statuses", %{
414 "status" => "imagine arbitrary limits",
415 "poll" => %{
416 "options" => ["this post was made by pleroma gang"],
417 "expires_in" => limit - 1
418 }
419 })
420
421 %{"error" => error} = json_response(conn, 422)
422 assert error == "Expiration date is too soon"
423 end
424
425 test "maximum date limit is enforced", %{conn: conn} do
426 limit = Config.get([:instance, :poll_limits, :max_expiration])
427
428 conn =
429 post(conn, "/api/v1/statuses", %{
430 "status" => "imagine arbitrary limits",
431 "poll" => %{
432 "options" => ["this post was made by pleroma gang"],
433 "expires_in" => limit + 1
434 }
435 })
436
437 %{"error" => error} = json_response(conn, 422)
438 assert error == "Expiration date is too far in the future"
439 end
440 end
441
442 test "get a status" do
443 %{conn: conn} = oauth_access(["read:statuses"])
444 activity = insert(:note_activity)
445
446 conn = get(conn, "/api/v1/statuses/#{activity.id}")
447
448 assert %{"id" => id} = json_response(conn, 200)
449 assert id == to_string(activity.id)
450 end
451
452 test "get a direct status" do
453 %{user: user, conn: conn} = oauth_access(["read:statuses"])
454 other_user = insert(:user)
455
456 {:ok, activity} =
457 CommonAPI.post(user, %{"status" => "@#{other_user.nickname}", "visibility" => "direct"})
458
459 conn =
460 conn
461 |> assign(:user, user)
462 |> get("/api/v1/statuses/#{activity.id}")
463
464 [participation] = Participation.for_user(user)
465
466 res = json_response(conn, 200)
467 assert res["pleroma"]["direct_conversation_id"] == participation.id
468 end
469
470 test "get statuses by IDs" do
471 %{conn: conn} = oauth_access(["read:statuses"])
472 %{id: id1} = insert(:note_activity)
473 %{id: id2} = insert(:note_activity)
474
475 query_string = "ids[]=#{id1}&ids[]=#{id2}"
476 conn = get(conn, "/api/v1/statuses/?#{query_string}")
477
478 assert [%{"id" => ^id1}, %{"id" => ^id2}] = Enum.sort_by(json_response(conn, :ok), & &1["id"])
479 end
480
481 describe "deleting a status" do
482 test "when you created it" do
483 %{user: author, conn: conn} = oauth_access(["write:statuses"])
484 activity = insert(:note_activity, user: author)
485
486 conn =
487 conn
488 |> assign(:user, author)
489 |> delete("/api/v1/statuses/#{activity.id}")
490
491 assert %{} = json_response(conn, 200)
492
493 refute Activity.get_by_id(activity.id)
494 end
495
496 test "when you didn't create it" do
497 %{conn: conn} = oauth_access(["write:statuses"])
498 activity = insert(:note_activity)
499
500 conn = delete(conn, "/api/v1/statuses/#{activity.id}")
501
502 assert %{"error" => _} = json_response(conn, 403)
503
504 assert Activity.get_by_id(activity.id) == activity
505 end
506
507 test "when you're an admin or moderator", %{conn: conn} do
508 activity1 = insert(:note_activity)
509 activity2 = insert(:note_activity)
510 admin = insert(:user, is_admin: true)
511 moderator = insert(:user, is_moderator: true)
512
513 res_conn =
514 conn
515 |> assign(:user, admin)
516 |> assign(:token, insert(:oauth_token, user: admin, scopes: ["write:statuses"]))
517 |> delete("/api/v1/statuses/#{activity1.id}")
518
519 assert %{} = json_response(res_conn, 200)
520
521 res_conn =
522 conn
523 |> assign(:user, moderator)
524 |> assign(:token, insert(:oauth_token, user: moderator, scopes: ["write:statuses"]))
525 |> delete("/api/v1/statuses/#{activity2.id}")
526
527 assert %{} = json_response(res_conn, 200)
528
529 refute Activity.get_by_id(activity1.id)
530 refute Activity.get_by_id(activity2.id)
531 end
532 end
533
534 describe "reblogging" do
535 setup do: oauth_access(["write:statuses"])
536
537 test "reblogs and returns the reblogged status", %{conn: conn} do
538 activity = insert(:note_activity)
539
540 conn = post(conn, "/api/v1/statuses/#{activity.id}/reblog")
541
542 assert %{
543 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
544 "reblogged" => true
545 } = json_response(conn, 200)
546
547 assert to_string(activity.id) == id
548 end
549
550 test "reblogs privately and returns the reblogged status", %{conn: conn} do
551 activity = insert(:note_activity)
552
553 conn = post(conn, "/api/v1/statuses/#{activity.id}/reblog", %{"visibility" => "private"})
554
555 assert %{
556 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
557 "reblogged" => true,
558 "visibility" => "private"
559 } = json_response(conn, 200)
560
561 assert to_string(activity.id) == id
562 end
563
564 test "reblogged status for another user" do
565 activity = insert(:note_activity)
566 user1 = insert(:user)
567 user2 = insert(:user)
568 user3 = insert(:user)
569 CommonAPI.favorite(activity.id, user2)
570 {:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id)
571 {:ok, reblog_activity1, _object} = CommonAPI.repeat(activity.id, user1)
572 {:ok, _, _object} = CommonAPI.repeat(activity.id, user2)
573
574 conn_res =
575 build_conn()
576 |> assign(:user, user3)
577 |> assign(:token, insert(:oauth_token, user: user3, scopes: ["read:statuses"]))
578 |> get("/api/v1/statuses/#{reblog_activity1.id}")
579
580 assert %{
581 "reblog" => %{"id" => id, "reblogged" => false, "reblogs_count" => 2},
582 "reblogged" => false,
583 "favourited" => false,
584 "bookmarked" => false
585 } = json_response(conn_res, 200)
586
587 conn_res =
588 build_conn()
589 |> assign(:user, user2)
590 |> assign(:token, insert(:oauth_token, user: user2, scopes: ["read:statuses"]))
591 |> get("/api/v1/statuses/#{reblog_activity1.id}")
592
593 assert %{
594 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 2},
595 "reblogged" => true,
596 "favourited" => true,
597 "bookmarked" => true
598 } = json_response(conn_res, 200)
599
600 assert to_string(activity.id) == id
601 end
602
603 test "returns 400 error when activity is not exist", %{conn: conn} do
604 conn = post(conn, "/api/v1/statuses/foo/reblog")
605
606 assert json_response(conn, 400) == %{"error" => "Could not repeat"}
607 end
608 end
609
610 describe "unreblogging" do
611 setup do: oauth_access(["write:statuses"])
612
613 test "unreblogs and returns the unreblogged status", %{user: user, conn: conn} do
614 activity = insert(:note_activity)
615
616 {:ok, _, _} = CommonAPI.repeat(activity.id, user)
617
618 conn = post(conn, "/api/v1/statuses/#{activity.id}/unreblog")
619
620 assert %{"id" => id, "reblogged" => false, "reblogs_count" => 0} = json_response(conn, 200)
621
622 assert to_string(activity.id) == id
623 end
624
625 test "returns 400 error when activity is not exist", %{conn: conn} do
626 conn = post(conn, "/api/v1/statuses/foo/unreblog")
627
628 assert json_response(conn, 400) == %{"error" => "Could not unrepeat"}
629 end
630 end
631
632 describe "favoriting" do
633 setup do: oauth_access(["write:favourites"])
634
635 test "favs a status and returns it", %{conn: conn} do
636 activity = insert(:note_activity)
637
638 conn = post(conn, "/api/v1/statuses/#{activity.id}/favourite")
639
640 assert %{"id" => id, "favourites_count" => 1, "favourited" => true} =
641 json_response(conn, 200)
642
643 assert to_string(activity.id) == id
644 end
645
646 test "favoriting twice will just return 200", %{conn: conn} do
647 activity = insert(:note_activity)
648
649 post(conn, "/api/v1/statuses/#{activity.id}/favourite")
650 assert post(conn, "/api/v1/statuses/#{activity.id}/favourite") |> json_response(200)
651 end
652
653 test "returns 400 error for a wrong id", %{conn: conn} do
654 conn = post(conn, "/api/v1/statuses/1/favourite")
655
656 assert json_response(conn, 400) == %{"error" => "Could not favorite"}
657 end
658 end
659
660 describe "unfavoriting" do
661 setup do: oauth_access(["write:favourites"])
662
663 test "unfavorites a status and returns it", %{user: user, conn: conn} do
664 activity = insert(:note_activity)
665
666 {:ok, _, _} = CommonAPI.favorite(activity.id, user)
667
668 conn = post(conn, "/api/v1/statuses/#{activity.id}/unfavourite")
669
670 assert %{"id" => id, "favourites_count" => 0, "favourited" => false} =
671 json_response(conn, 200)
672
673 assert to_string(activity.id) == id
674 end
675
676 test "returns 400 error for a wrong id", %{conn: conn} do
677 conn = post(conn, "/api/v1/statuses/1/unfavourite")
678
679 assert json_response(conn, 400) == %{"error" => "Could not unfavorite"}
680 end
681 end
682
683 describe "pinned statuses" do
684 setup do: oauth_access(["write:accounts"])
685
686 setup %{user: user} do
687 {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"})
688
689 %{activity: activity}
690 end
691
692 clear_config([:instance, :max_pinned_statuses]) do
693 Config.put([:instance, :max_pinned_statuses], 1)
694 end
695
696 test "pin status", %{conn: conn, user: user, activity: activity} do
697 id_str = to_string(activity.id)
698
699 assert %{"id" => ^id_str, "pinned" => true} =
700 conn
701 |> post("/api/v1/statuses/#{activity.id}/pin")
702 |> json_response(200)
703
704 assert [%{"id" => ^id_str, "pinned" => true}] =
705 conn
706 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
707 |> json_response(200)
708 end
709
710 test "/pin: returns 400 error when activity is not public", %{conn: conn, user: user} do
711 {:ok, dm} = CommonAPI.post(user, %{"status" => "test", "visibility" => "direct"})
712
713 conn = post(conn, "/api/v1/statuses/#{dm.id}/pin")
714
715 assert json_response(conn, 400) == %{"error" => "Could not pin"}
716 end
717
718 test "unpin status", %{conn: conn, user: user, activity: activity} do
719 {:ok, _} = CommonAPI.pin(activity.id, user)
720 user = refresh_record(user)
721
722 id_str = to_string(activity.id)
723
724 assert %{"id" => ^id_str, "pinned" => false} =
725 conn
726 |> assign(:user, user)
727 |> post("/api/v1/statuses/#{activity.id}/unpin")
728 |> json_response(200)
729
730 assert [] =
731 conn
732 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
733 |> json_response(200)
734 end
735
736 test "/unpin: returns 400 error when activity is not exist", %{conn: conn} do
737 conn = post(conn, "/api/v1/statuses/1/unpin")
738
739 assert json_response(conn, 400) == %{"error" => "Could not unpin"}
740 end
741
742 test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
743 {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
744
745 id_str_one = to_string(activity_one.id)
746
747 assert %{"id" => ^id_str_one, "pinned" => true} =
748 conn
749 |> post("/api/v1/statuses/#{id_str_one}/pin")
750 |> json_response(200)
751
752 user = refresh_record(user)
753
754 assert %{"error" => "You have already pinned the maximum number of statuses"} =
755 conn
756 |> assign(:user, user)
757 |> post("/api/v1/statuses/#{activity_two.id}/pin")
758 |> json_response(400)
759 end
760 end
761
762 describe "cards" do
763 setup do
764 Config.put([:rich_media, :enabled], true)
765
766 oauth_access(["read:statuses"])
767 end
768
769 test "returns rich-media card", %{conn: conn, user: user} do
770 Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
771
772 {:ok, activity} = CommonAPI.post(user, %{"status" => "https://example.com/ogp"})
773
774 card_data = %{
775 "image" => "http://ia.media-imdb.com/images/rock.jpg",
776 "provider_name" => "example.com",
777 "provider_url" => "https://example.com",
778 "title" => "The Rock",
779 "type" => "link",
780 "url" => "https://example.com/ogp",
781 "description" =>
782 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
783 "pleroma" => %{
784 "opengraph" => %{
785 "image" => "http://ia.media-imdb.com/images/rock.jpg",
786 "title" => "The Rock",
787 "type" => "video.movie",
788 "url" => "https://example.com/ogp",
789 "description" =>
790 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer."
791 }
792 }
793 }
794
795 response =
796 conn
797 |> get("/api/v1/statuses/#{activity.id}/card")
798 |> json_response(200)
799
800 assert response == card_data
801
802 # works with private posts
803 {:ok, activity} =
804 CommonAPI.post(user, %{"status" => "https://example.com/ogp", "visibility" => "direct"})
805
806 response_two =
807 conn
808 |> get("/api/v1/statuses/#{activity.id}/card")
809 |> json_response(200)
810
811 assert response_two == card_data
812 end
813
814 test "replaces missing description with an empty string", %{conn: conn, user: user} do
815 Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
816
817 {:ok, activity} =
818 CommonAPI.post(user, %{"status" => "https://example.com/ogp-missing-data"})
819
820 response =
821 conn
822 |> get("/api/v1/statuses/#{activity.id}/card")
823 |> json_response(:ok)
824
825 assert response == %{
826 "type" => "link",
827 "title" => "Pleroma",
828 "description" => "",
829 "image" => nil,
830 "provider_name" => "example.com",
831 "provider_url" => "https://example.com",
832 "url" => "https://example.com/ogp-missing-data",
833 "pleroma" => %{
834 "opengraph" => %{
835 "title" => "Pleroma",
836 "type" => "website",
837 "url" => "https://example.com/ogp-missing-data"
838 }
839 }
840 }
841 end
842 end
843
844 test "bookmarks" do
845 %{conn: conn} = oauth_access(["write:bookmarks", "read:bookmarks"])
846 author = insert(:user)
847
848 {:ok, activity1} =
849 CommonAPI.post(author, %{
850 "status" => "heweoo?"
851 })
852
853 {:ok, activity2} =
854 CommonAPI.post(author, %{
855 "status" => "heweoo!"
856 })
857
858 response1 = post(conn, "/api/v1/statuses/#{activity1.id}/bookmark")
859
860 assert json_response(response1, 200)["bookmarked"] == true
861
862 response2 = post(conn, "/api/v1/statuses/#{activity2.id}/bookmark")
863
864 assert json_response(response2, 200)["bookmarked"] == true
865
866 bookmarks = get(conn, "/api/v1/bookmarks")
867
868 assert [json_response(response2, 200), json_response(response1, 200)] ==
869 json_response(bookmarks, 200)
870
871 response1 = post(conn, "/api/v1/statuses/#{activity1.id}/unbookmark")
872
873 assert json_response(response1, 200)["bookmarked"] == false
874
875 bookmarks = get(conn, "/api/v1/bookmarks")
876
877 assert [json_response(response2, 200)] == json_response(bookmarks, 200)
878 end
879
880 describe "conversation muting" do
881 setup do: oauth_access(["write:mutes"])
882
883 setup do
884 post_user = insert(:user)
885 {:ok, activity} = CommonAPI.post(post_user, %{"status" => "HIE"})
886 %{activity: activity}
887 end
888
889 test "mute conversation", %{conn: conn, activity: activity} do
890 id_str = to_string(activity.id)
891
892 assert %{"id" => ^id_str, "muted" => true} =
893 conn
894 |> post("/api/v1/statuses/#{activity.id}/mute")
895 |> json_response(200)
896 end
897
898 test "cannot mute already muted conversation", %{conn: conn, user: user, activity: activity} do
899 {:ok, _} = CommonAPI.add_mute(user, activity)
900
901 conn = post(conn, "/api/v1/statuses/#{activity.id}/mute")
902
903 assert json_response(conn, 400) == %{"error" => "conversation is already muted"}
904 end
905
906 test "unmute conversation", %{conn: conn, user: user, activity: activity} do
907 {:ok, _} = CommonAPI.add_mute(user, activity)
908
909 id_str = to_string(activity.id)
910
911 assert %{"id" => ^id_str, "muted" => false} =
912 conn
913 # |> assign(:user, user)
914 |> post("/api/v1/statuses/#{activity.id}/unmute")
915 |> json_response(200)
916 end
917 end
918
919 test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do
920 user1 = insert(:user)
921 user2 = insert(:user)
922 user3 = insert(:user)
923
924 {:ok, replied_to} = CommonAPI.post(user1, %{"status" => "cofe"})
925
926 # Reply to status from another user
927 conn1 =
928 conn
929 |> assign(:user, user2)
930 |> assign(:token, insert(:oauth_token, user: user2, scopes: ["write:statuses"]))
931 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
932
933 assert %{"content" => "xD", "id" => id} = json_response(conn1, 200)
934
935 activity = Activity.get_by_id_with_object(id)
936
937 assert Object.normalize(activity).data["inReplyTo"] == Object.normalize(replied_to).data["id"]
938 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
939
940 # Reblog from the third user
941 conn2 =
942 conn
943 |> assign(:user, user3)
944 |> assign(:token, insert(:oauth_token, user: user3, scopes: ["write:statuses"]))
945 |> post("/api/v1/statuses/#{activity.id}/reblog")
946
947 assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} =
948 json_response(conn2, 200)
949
950 assert to_string(activity.id) == id
951
952 # Getting third user status
953 conn3 =
954 conn
955 |> assign(:user, user3)
956 |> assign(:token, insert(:oauth_token, user: user3, scopes: ["read:statuses"]))
957 |> get("api/v1/timelines/home")
958
959 [reblogged_activity] = json_response(conn3, 200)
960
961 assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id
962
963 replied_to_user = User.get_by_ap_id(replied_to.data["actor"])
964 assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id
965 end
966
967 describe "GET /api/v1/statuses/:id/favourited_by" do
968 setup do: oauth_access(["read:accounts"])
969
970 setup %{user: user} do
971 {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
972
973 %{activity: activity}
974 end
975
976 test "returns users who have favorited the status", %{conn: conn, activity: activity} do
977 other_user = insert(:user)
978 {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
979
980 response =
981 conn
982 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
983 |> json_response(:ok)
984
985 [%{"id" => id}] = response
986
987 assert id == other_user.id
988 end
989
990 test "returns empty array when status has not been favorited yet", %{
991 conn: conn,
992 activity: activity
993 } do
994 response =
995 conn
996 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
997 |> json_response(:ok)
998
999 assert Enum.empty?(response)
1000 end
1001
1002 test "does not return users who have favorited the status but are blocked", %{
1003 conn: %{assigns: %{user: user}} = conn,
1004 activity: activity
1005 } do
1006 other_user = insert(:user)
1007 {:ok, _user_relationship} = User.block(user, other_user)
1008
1009 {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
1010
1011 response =
1012 conn
1013 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1014 |> json_response(:ok)
1015
1016 assert Enum.empty?(response)
1017 end
1018
1019 test "does not fail on an unauthenticated request", %{activity: activity} do
1020 other_user = insert(:user)
1021 {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
1022
1023 response =
1024 build_conn()
1025 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1026 |> json_response(:ok)
1027
1028 [%{"id" => id}] = response
1029 assert id == other_user.id
1030 end
1031
1032 test "requires authentication for private posts", %{user: user} do
1033 other_user = insert(:user)
1034
1035 {:ok, activity} =
1036 CommonAPI.post(user, %{
1037 "status" => "@#{other_user.nickname} wanna get some #cofe together?",
1038 "visibility" => "direct"
1039 })
1040
1041 {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
1042
1043 favourited_by_url = "/api/v1/statuses/#{activity.id}/favourited_by"
1044
1045 build_conn()
1046 |> get(favourited_by_url)
1047 |> json_response(404)
1048
1049 conn =
1050 build_conn()
1051 |> assign(:user, other_user)
1052 |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"]))
1053
1054 conn
1055 |> assign(:token, nil)
1056 |> get(favourited_by_url)
1057 |> json_response(404)
1058
1059 response =
1060 conn
1061 |> get(favourited_by_url)
1062 |> json_response(200)
1063
1064 [%{"id" => id}] = response
1065 assert id == other_user.id
1066 end
1067 end
1068
1069 describe "GET /api/v1/statuses/:id/reblogged_by" do
1070 setup do: oauth_access(["read:accounts"])
1071
1072 setup %{user: user} do
1073 {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
1074
1075 %{activity: activity}
1076 end
1077
1078 test "returns users who have reblogged the status", %{conn: conn, activity: activity} do
1079 other_user = insert(:user)
1080 {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
1081
1082 response =
1083 conn
1084 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1085 |> json_response(:ok)
1086
1087 [%{"id" => id}] = response
1088
1089 assert id == other_user.id
1090 end
1091
1092 test "returns empty array when status has not been reblogged yet", %{
1093 conn: conn,
1094 activity: activity
1095 } do
1096 response =
1097 conn
1098 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1099 |> json_response(:ok)
1100
1101 assert Enum.empty?(response)
1102 end
1103
1104 test "does not return users who have reblogged the status but are blocked", %{
1105 conn: %{assigns: %{user: user}} = conn,
1106 activity: activity
1107 } do
1108 other_user = insert(:user)
1109 {:ok, _user_relationship} = User.block(user, other_user)
1110
1111 {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
1112
1113 response =
1114 conn
1115 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1116 |> json_response(:ok)
1117
1118 assert Enum.empty?(response)
1119 end
1120
1121 test "does not return users who have reblogged the status privately", %{
1122 conn: conn,
1123 activity: activity
1124 } do
1125 other_user = insert(:user)
1126
1127 {:ok, _, _} = CommonAPI.repeat(activity.id, other_user, %{"visibility" => "private"})
1128
1129 response =
1130 conn
1131 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1132 |> json_response(:ok)
1133
1134 assert Enum.empty?(response)
1135 end
1136
1137 test "does not fail on an unauthenticated request", %{activity: activity} do
1138 other_user = insert(:user)
1139 {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
1140
1141 response =
1142 build_conn()
1143 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1144 |> json_response(:ok)
1145
1146 [%{"id" => id}] = response
1147 assert id == other_user.id
1148 end
1149
1150 test "requires authentication for private posts", %{user: user} do
1151 other_user = insert(:user)
1152
1153 {:ok, activity} =
1154 CommonAPI.post(user, %{
1155 "status" => "@#{other_user.nickname} wanna get some #cofe together?",
1156 "visibility" => "direct"
1157 })
1158
1159 build_conn()
1160 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1161 |> json_response(404)
1162
1163 response =
1164 build_conn()
1165 |> assign(:user, other_user)
1166 |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"]))
1167 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1168 |> json_response(200)
1169
1170 assert [] == response
1171 end
1172 end
1173
1174 test "context" do
1175 user = insert(:user)
1176
1177 {:ok, %{id: id1}} = CommonAPI.post(user, %{"status" => "1"})
1178 {:ok, %{id: id2}} = CommonAPI.post(user, %{"status" => "2", "in_reply_to_status_id" => id1})
1179 {:ok, %{id: id3}} = CommonAPI.post(user, %{"status" => "3", "in_reply_to_status_id" => id2})
1180 {:ok, %{id: id4}} = CommonAPI.post(user, %{"status" => "4", "in_reply_to_status_id" => id3})
1181 {:ok, %{id: id5}} = CommonAPI.post(user, %{"status" => "5", "in_reply_to_status_id" => id4})
1182
1183 response =
1184 build_conn()
1185 |> get("/api/v1/statuses/#{id3}/context")
1186 |> json_response(:ok)
1187
1188 assert %{
1189 "ancestors" => [%{"id" => ^id1}, %{"id" => ^id2}],
1190 "descendants" => [%{"id" => ^id4}, %{"id" => ^id5}]
1191 } = response
1192 end
1193
1194 test "returns the favorites of a user" do
1195 %{user: user, conn: conn} = oauth_access(["read:favourites"])
1196 other_user = insert(:user)
1197
1198 {:ok, _} = CommonAPI.post(other_user, %{"status" => "bla"})
1199 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "traps are happy"})
1200
1201 {:ok, _, _} = CommonAPI.favorite(activity.id, user)
1202
1203 first_conn = get(conn, "/api/v1/favourites")
1204
1205 assert [status] = json_response(first_conn, 200)
1206 assert status["id"] == to_string(activity.id)
1207
1208 assert [{"link", _link_header}] =
1209 Enum.filter(first_conn.resp_headers, fn element -> match?({"link", _}, element) end)
1210
1211 # Honours query params
1212 {:ok, second_activity} =
1213 CommonAPI.post(other_user, %{
1214 "status" =>
1215 "Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful."
1216 })
1217
1218 {:ok, _, _} = CommonAPI.favorite(second_activity.id, user)
1219
1220 last_like = status["id"]
1221
1222 second_conn = get(conn, "/api/v1/favourites?since_id=#{last_like}")
1223
1224 assert [second_status] = json_response(second_conn, 200)
1225 assert second_status["id"] == to_string(second_activity.id)
1226
1227 third_conn = get(conn, "/api/v1/favourites?limit=0")
1228
1229 assert [] = json_response(third_conn, 200)
1230 end
1231 end