Merge remote-tracking branch 'remotes/origin/develop' into 1478-oauth-admin-scopes...
[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 end
374
375 test "option limit is enforced", %{conn: conn} do
376 limit = Config.get([:instance, :poll_limits, :max_options])
377
378 conn =
379 post(conn, "/api/v1/statuses", %{
380 "status" => "desu~",
381 "poll" => %{"options" => Enum.map(0..limit, fn _ -> "desu" end), "expires_in" => 1}
382 })
383
384 %{"error" => error} = json_response(conn, 422)
385 assert error == "Poll can't contain more than #{limit} options"
386 end
387
388 test "option character limit is enforced", %{conn: conn} do
389 limit = Config.get([:instance, :poll_limits, :max_option_chars])
390
391 conn =
392 post(conn, "/api/v1/statuses", %{
393 "status" => "...",
394 "poll" => %{
395 "options" => [Enum.reduce(0..limit, "", fn _, acc -> acc <> "." end)],
396 "expires_in" => 1
397 }
398 })
399
400 %{"error" => error} = json_response(conn, 422)
401 assert error == "Poll options cannot be longer than #{limit} characters each"
402 end
403
404 test "minimal date limit is enforced", %{conn: conn} do
405 limit = Config.get([:instance, :poll_limits, :min_expiration])
406
407 conn =
408 post(conn, "/api/v1/statuses", %{
409 "status" => "imagine arbitrary limits",
410 "poll" => %{
411 "options" => ["this post was made by pleroma gang"],
412 "expires_in" => limit - 1
413 }
414 })
415
416 %{"error" => error} = json_response(conn, 422)
417 assert error == "Expiration date is too soon"
418 end
419
420 test "maximum date limit is enforced", %{conn: conn} do
421 limit = Config.get([:instance, :poll_limits, :max_expiration])
422
423 conn =
424 post(conn, "/api/v1/statuses", %{
425 "status" => "imagine arbitrary limits",
426 "poll" => %{
427 "options" => ["this post was made by pleroma gang"],
428 "expires_in" => limit + 1
429 }
430 })
431
432 %{"error" => error} = json_response(conn, 422)
433 assert error == "Expiration date is too far in the future"
434 end
435 end
436
437 test "get a status" do
438 %{conn: conn} = oauth_access(["read:statuses"])
439 activity = insert(:note_activity)
440
441 conn = get(conn, "/api/v1/statuses/#{activity.id}")
442
443 assert %{"id" => id} = json_response(conn, 200)
444 assert id == to_string(activity.id)
445 end
446
447 test "get a direct status" do
448 %{user: user, conn: conn} = oauth_access(["read:statuses"])
449 other_user = insert(:user)
450
451 {:ok, activity} =
452 CommonAPI.post(user, %{"status" => "@#{other_user.nickname}", "visibility" => "direct"})
453
454 conn =
455 conn
456 |> assign(:user, user)
457 |> get("/api/v1/statuses/#{activity.id}")
458
459 [participation] = Participation.for_user(user)
460
461 res = json_response(conn, 200)
462 assert res["pleroma"]["direct_conversation_id"] == participation.id
463 end
464
465 test "get statuses by IDs" do
466 %{conn: conn} = oauth_access(["read:statuses"])
467 %{id: id1} = insert(:note_activity)
468 %{id: id2} = insert(:note_activity)
469
470 query_string = "ids[]=#{id1}&ids[]=#{id2}"
471 conn = get(conn, "/api/v1/statuses/?#{query_string}")
472
473 assert [%{"id" => ^id1}, %{"id" => ^id2}] = Enum.sort_by(json_response(conn, :ok), & &1["id"])
474 end
475
476 describe "deleting a status" do
477 test "when you created it" do
478 %{user: author, conn: conn} = oauth_access(["write:statuses"])
479 activity = insert(:note_activity, user: author)
480
481 conn =
482 conn
483 |> assign(:user, author)
484 |> delete("/api/v1/statuses/#{activity.id}")
485
486 assert %{} = json_response(conn, 200)
487
488 refute Activity.get_by_id(activity.id)
489 end
490
491 test "when you didn't create it" do
492 %{conn: conn} = oauth_access(["write:statuses"])
493 activity = insert(:note_activity)
494
495 conn = delete(conn, "/api/v1/statuses/#{activity.id}")
496
497 assert %{"error" => _} = json_response(conn, 403)
498
499 assert Activity.get_by_id(activity.id) == activity
500 end
501
502 test "when you're an admin or moderator", %{conn: conn} do
503 activity1 = insert(:note_activity)
504 activity2 = insert(:note_activity)
505 admin = insert(:user, is_admin: true)
506 moderator = insert(:user, is_moderator: true)
507
508 res_conn =
509 conn
510 |> assign(:user, admin)
511 |> assign(:token, insert(:oauth_token, user: admin, scopes: ["write:statuses"]))
512 |> delete("/api/v1/statuses/#{activity1.id}")
513
514 assert %{} = json_response(res_conn, 200)
515
516 res_conn =
517 conn
518 |> assign(:user, moderator)
519 |> assign(:token, insert(:oauth_token, user: moderator, scopes: ["write:statuses"]))
520 |> delete("/api/v1/statuses/#{activity2.id}")
521
522 assert %{} = json_response(res_conn, 200)
523
524 refute Activity.get_by_id(activity1.id)
525 refute Activity.get_by_id(activity2.id)
526 end
527 end
528
529 describe "reblogging" do
530 setup do: oauth_access(["write:statuses"])
531
532 test "reblogs and returns the reblogged status", %{conn: conn} do
533 activity = insert(:note_activity)
534
535 conn = post(conn, "/api/v1/statuses/#{activity.id}/reblog")
536
537 assert %{
538 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
539 "reblogged" => true
540 } = json_response(conn, 200)
541
542 assert to_string(activity.id) == id
543 end
544
545 test "reblogs privately and returns the reblogged status", %{conn: conn} do
546 activity = insert(:note_activity)
547
548 conn = post(conn, "/api/v1/statuses/#{activity.id}/reblog", %{"visibility" => "private"})
549
550 assert %{
551 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
552 "reblogged" => true,
553 "visibility" => "private"
554 } = json_response(conn, 200)
555
556 assert to_string(activity.id) == id
557 end
558
559 test "reblogged status for another user" do
560 activity = insert(:note_activity)
561 user1 = insert(:user)
562 user2 = insert(:user)
563 user3 = insert(:user)
564 CommonAPI.favorite(activity.id, user2)
565 {:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id)
566 {:ok, reblog_activity1, _object} = CommonAPI.repeat(activity.id, user1)
567 {:ok, _, _object} = CommonAPI.repeat(activity.id, user2)
568
569 conn_res =
570 build_conn()
571 |> assign(:user, user3)
572 |> assign(:token, insert(:oauth_token, user: user3, scopes: ["read:statuses"]))
573 |> get("/api/v1/statuses/#{reblog_activity1.id}")
574
575 assert %{
576 "reblog" => %{"id" => id, "reblogged" => false, "reblogs_count" => 2},
577 "reblogged" => false,
578 "favourited" => false,
579 "bookmarked" => false
580 } = json_response(conn_res, 200)
581
582 conn_res =
583 build_conn()
584 |> assign(:user, user2)
585 |> assign(:token, insert(:oauth_token, user: user2, scopes: ["read:statuses"]))
586 |> get("/api/v1/statuses/#{reblog_activity1.id}")
587
588 assert %{
589 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 2},
590 "reblogged" => true,
591 "favourited" => true,
592 "bookmarked" => true
593 } = json_response(conn_res, 200)
594
595 assert to_string(activity.id) == id
596 end
597
598 test "returns 400 error when activity is not exist", %{conn: conn} do
599 conn = post(conn, "/api/v1/statuses/foo/reblog")
600
601 assert json_response(conn, 400) == %{"error" => "Could not repeat"}
602 end
603 end
604
605 describe "unreblogging" do
606 setup do: oauth_access(["write:statuses"])
607
608 test "unreblogs and returns the unreblogged status", %{user: user, conn: conn} do
609 activity = insert(:note_activity)
610
611 {:ok, _, _} = CommonAPI.repeat(activity.id, user)
612
613 conn = post(conn, "/api/v1/statuses/#{activity.id}/unreblog")
614
615 assert %{"id" => id, "reblogged" => false, "reblogs_count" => 0} = json_response(conn, 200)
616
617 assert to_string(activity.id) == id
618 end
619
620 test "returns 400 error when activity is not exist", %{conn: conn} do
621 conn = post(conn, "/api/v1/statuses/foo/unreblog")
622
623 assert json_response(conn, 400) == %{"error" => "Could not unrepeat"}
624 end
625 end
626
627 describe "favoriting" do
628 setup do: oauth_access(["write:favourites"])
629
630 test "favs a status and returns it", %{conn: conn} do
631 activity = insert(:note_activity)
632
633 conn = post(conn, "/api/v1/statuses/#{activity.id}/favourite")
634
635 assert %{"id" => id, "favourites_count" => 1, "favourited" => true} =
636 json_response(conn, 200)
637
638 assert to_string(activity.id) == id
639 end
640
641 test "returns 400 error for a wrong id", %{conn: conn} do
642 conn = post(conn, "/api/v1/statuses/1/favourite")
643
644 assert json_response(conn, 400) == %{"error" => "Could not favorite"}
645 end
646 end
647
648 describe "unfavoriting" do
649 setup do: oauth_access(["write:favourites"])
650
651 test "unfavorites a status and returns it", %{user: user, conn: conn} do
652 activity = insert(:note_activity)
653
654 {:ok, _, _} = CommonAPI.favorite(activity.id, user)
655
656 conn = post(conn, "/api/v1/statuses/#{activity.id}/unfavourite")
657
658 assert %{"id" => id, "favourites_count" => 0, "favourited" => false} =
659 json_response(conn, 200)
660
661 assert to_string(activity.id) == id
662 end
663
664 test "returns 400 error for a wrong id", %{conn: conn} do
665 conn = post(conn, "/api/v1/statuses/1/unfavourite")
666
667 assert json_response(conn, 400) == %{"error" => "Could not unfavorite"}
668 end
669 end
670
671 describe "pinned statuses" do
672 setup do: oauth_access(["write:accounts"])
673
674 setup %{user: user} do
675 {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"})
676
677 %{activity: activity}
678 end
679
680 clear_config([:instance, :max_pinned_statuses]) do
681 Config.put([:instance, :max_pinned_statuses], 1)
682 end
683
684 test "pin status", %{conn: conn, user: user, activity: activity} do
685 id_str = to_string(activity.id)
686
687 assert %{"id" => ^id_str, "pinned" => true} =
688 conn
689 |> post("/api/v1/statuses/#{activity.id}/pin")
690 |> json_response(200)
691
692 assert [%{"id" => ^id_str, "pinned" => true}] =
693 conn
694 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
695 |> json_response(200)
696 end
697
698 test "/pin: returns 400 error when activity is not public", %{conn: conn, user: user} do
699 {:ok, dm} = CommonAPI.post(user, %{"status" => "test", "visibility" => "direct"})
700
701 conn = post(conn, "/api/v1/statuses/#{dm.id}/pin")
702
703 assert json_response(conn, 400) == %{"error" => "Could not pin"}
704 end
705
706 test "unpin status", %{conn: conn, user: user, activity: activity} do
707 {:ok, _} = CommonAPI.pin(activity.id, user)
708 user = refresh_record(user)
709
710 id_str = to_string(activity.id)
711
712 assert %{"id" => ^id_str, "pinned" => false} =
713 conn
714 |> assign(:user, user)
715 |> post("/api/v1/statuses/#{activity.id}/unpin")
716 |> json_response(200)
717
718 assert [] =
719 conn
720 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
721 |> json_response(200)
722 end
723
724 test "/unpin: returns 400 error when activity is not exist", %{conn: conn} do
725 conn = post(conn, "/api/v1/statuses/1/unpin")
726
727 assert json_response(conn, 400) == %{"error" => "Could not unpin"}
728 end
729
730 test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
731 {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
732
733 id_str_one = to_string(activity_one.id)
734
735 assert %{"id" => ^id_str_one, "pinned" => true} =
736 conn
737 |> post("/api/v1/statuses/#{id_str_one}/pin")
738 |> json_response(200)
739
740 user = refresh_record(user)
741
742 assert %{"error" => "You have already pinned the maximum number of statuses"} =
743 conn
744 |> assign(:user, user)
745 |> post("/api/v1/statuses/#{activity_two.id}/pin")
746 |> json_response(400)
747 end
748 end
749
750 describe "cards" do
751 setup do
752 Config.put([:rich_media, :enabled], true)
753
754 oauth_access(["read:statuses"])
755 end
756
757 test "returns rich-media card", %{conn: conn, user: user} do
758 Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
759
760 {:ok, activity} = CommonAPI.post(user, %{"status" => "https://example.com/ogp"})
761
762 card_data = %{
763 "image" => "http://ia.media-imdb.com/images/rock.jpg",
764 "provider_name" => "example.com",
765 "provider_url" => "https://example.com",
766 "title" => "The Rock",
767 "type" => "link",
768 "url" => "https://example.com/ogp",
769 "description" =>
770 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
771 "pleroma" => %{
772 "opengraph" => %{
773 "image" => "http://ia.media-imdb.com/images/rock.jpg",
774 "title" => "The Rock",
775 "type" => "video.movie",
776 "url" => "https://example.com/ogp",
777 "description" =>
778 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer."
779 }
780 }
781 }
782
783 response =
784 conn
785 |> get("/api/v1/statuses/#{activity.id}/card")
786 |> json_response(200)
787
788 assert response == card_data
789
790 # works with private posts
791 {:ok, activity} =
792 CommonAPI.post(user, %{"status" => "https://example.com/ogp", "visibility" => "direct"})
793
794 response_two =
795 conn
796 |> get("/api/v1/statuses/#{activity.id}/card")
797 |> json_response(200)
798
799 assert response_two == card_data
800 end
801
802 test "replaces missing description with an empty string", %{conn: conn, user: user} do
803 Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
804
805 {:ok, activity} =
806 CommonAPI.post(user, %{"status" => "https://example.com/ogp-missing-data"})
807
808 response =
809 conn
810 |> get("/api/v1/statuses/#{activity.id}/card")
811 |> json_response(:ok)
812
813 assert response == %{
814 "type" => "link",
815 "title" => "Pleroma",
816 "description" => "",
817 "image" => nil,
818 "provider_name" => "example.com",
819 "provider_url" => "https://example.com",
820 "url" => "https://example.com/ogp-missing-data",
821 "pleroma" => %{
822 "opengraph" => %{
823 "title" => "Pleroma",
824 "type" => "website",
825 "url" => "https://example.com/ogp-missing-data"
826 }
827 }
828 }
829 end
830 end
831
832 test "bookmarks" do
833 %{conn: conn} = oauth_access(["write:bookmarks", "read:bookmarks"])
834 author = insert(:user)
835
836 {:ok, activity1} =
837 CommonAPI.post(author, %{
838 "status" => "heweoo?"
839 })
840
841 {:ok, activity2} =
842 CommonAPI.post(author, %{
843 "status" => "heweoo!"
844 })
845
846 response1 = post(conn, "/api/v1/statuses/#{activity1.id}/bookmark")
847
848 assert json_response(response1, 200)["bookmarked"] == true
849
850 response2 = post(conn, "/api/v1/statuses/#{activity2.id}/bookmark")
851
852 assert json_response(response2, 200)["bookmarked"] == true
853
854 bookmarks = get(conn, "/api/v1/bookmarks")
855
856 assert [json_response(response2, 200), json_response(response1, 200)] ==
857 json_response(bookmarks, 200)
858
859 response1 = post(conn, "/api/v1/statuses/#{activity1.id}/unbookmark")
860
861 assert json_response(response1, 200)["bookmarked"] == false
862
863 bookmarks = get(conn, "/api/v1/bookmarks")
864
865 assert [json_response(response2, 200)] == json_response(bookmarks, 200)
866 end
867
868 describe "conversation muting" do
869 setup do: oauth_access(["write:mutes"])
870
871 setup do
872 post_user = insert(:user)
873 {:ok, activity} = CommonAPI.post(post_user, %{"status" => "HIE"})
874 %{activity: activity}
875 end
876
877 test "mute conversation", %{conn: conn, activity: activity} do
878 id_str = to_string(activity.id)
879
880 assert %{"id" => ^id_str, "muted" => true} =
881 conn
882 |> post("/api/v1/statuses/#{activity.id}/mute")
883 |> json_response(200)
884 end
885
886 test "cannot mute already muted conversation", %{conn: conn, user: user, activity: activity} do
887 {:ok, _} = CommonAPI.add_mute(user, activity)
888
889 conn = post(conn, "/api/v1/statuses/#{activity.id}/mute")
890
891 assert json_response(conn, 400) == %{"error" => "conversation is already muted"}
892 end
893
894 test "unmute conversation", %{conn: conn, user: user, activity: activity} do
895 {:ok, _} = CommonAPI.add_mute(user, activity)
896
897 id_str = to_string(activity.id)
898
899 assert %{"id" => ^id_str, "muted" => false} =
900 conn
901 # |> assign(:user, user)
902 |> post("/api/v1/statuses/#{activity.id}/unmute")
903 |> json_response(200)
904 end
905 end
906
907 test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do
908 user1 = insert(:user)
909 user2 = insert(:user)
910 user3 = insert(:user)
911
912 {:ok, replied_to} = CommonAPI.post(user1, %{"status" => "cofe"})
913
914 # Reply to status from another user
915 conn1 =
916 conn
917 |> assign(:user, user2)
918 |> assign(:token, insert(:oauth_token, user: user2, scopes: ["write:statuses"]))
919 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
920
921 assert %{"content" => "xD", "id" => id} = json_response(conn1, 200)
922
923 activity = Activity.get_by_id_with_object(id)
924
925 assert Object.normalize(activity).data["inReplyTo"] == Object.normalize(replied_to).data["id"]
926 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
927
928 # Reblog from the third user
929 conn2 =
930 conn
931 |> assign(:user, user3)
932 |> assign(:token, insert(:oauth_token, user: user3, scopes: ["write:statuses"]))
933 |> post("/api/v1/statuses/#{activity.id}/reblog")
934
935 assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} =
936 json_response(conn2, 200)
937
938 assert to_string(activity.id) == id
939
940 # Getting third user status
941 conn3 =
942 conn
943 |> assign(:user, user3)
944 |> assign(:token, insert(:oauth_token, user: user3, scopes: ["read:statuses"]))
945 |> get("api/v1/timelines/home")
946
947 [reblogged_activity] = json_response(conn3, 200)
948
949 assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id
950
951 replied_to_user = User.get_by_ap_id(replied_to.data["actor"])
952 assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id
953 end
954
955 describe "GET /api/v1/statuses/:id/favourited_by" do
956 setup do: oauth_access(["read:accounts"])
957
958 setup %{user: user} do
959 {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
960
961 %{activity: activity}
962 end
963
964 test "returns users who have favorited the status", %{conn: conn, activity: activity} do
965 other_user = insert(:user)
966 {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
967
968 response =
969 conn
970 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
971 |> json_response(:ok)
972
973 [%{"id" => id}] = response
974
975 assert id == other_user.id
976 end
977
978 test "returns empty array when status has not been favorited yet", %{
979 conn: conn,
980 activity: activity
981 } do
982 response =
983 conn
984 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
985 |> json_response(:ok)
986
987 assert Enum.empty?(response)
988 end
989
990 test "does not return users who have favorited the status but are blocked", %{
991 conn: %{assigns: %{user: user}} = conn,
992 activity: activity
993 } do
994 other_user = insert(:user)
995 {:ok, _user_relationship} = User.block(user, other_user)
996
997 {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
998
999 response =
1000 conn
1001 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1002 |> json_response(:ok)
1003
1004 assert Enum.empty?(response)
1005 end
1006
1007 test "does not fail on an unauthenticated request", %{activity: activity} do
1008 other_user = insert(:user)
1009 {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
1010
1011 response =
1012 build_conn()
1013 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1014 |> json_response(:ok)
1015
1016 [%{"id" => id}] = response
1017 assert id == other_user.id
1018 end
1019
1020 test "requires authentication for private posts", %{user: user} do
1021 other_user = insert(:user)
1022
1023 {:ok, activity} =
1024 CommonAPI.post(user, %{
1025 "status" => "@#{other_user.nickname} wanna get some #cofe together?",
1026 "visibility" => "direct"
1027 })
1028
1029 {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
1030
1031 favourited_by_url = "/api/v1/statuses/#{activity.id}/favourited_by"
1032
1033 build_conn()
1034 |> get(favourited_by_url)
1035 |> json_response(404)
1036
1037 conn =
1038 build_conn()
1039 |> assign(:user, other_user)
1040 |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"]))
1041
1042 conn
1043 |> assign(:token, nil)
1044 |> get(favourited_by_url)
1045 |> json_response(404)
1046
1047 response =
1048 conn
1049 |> get(favourited_by_url)
1050 |> json_response(200)
1051
1052 [%{"id" => id}] = response
1053 assert id == other_user.id
1054 end
1055 end
1056
1057 describe "GET /api/v1/statuses/:id/reblogged_by" do
1058 setup do: oauth_access(["read:accounts"])
1059
1060 setup %{user: user} do
1061 {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
1062
1063 %{activity: activity}
1064 end
1065
1066 test "returns users who have reblogged the status", %{conn: conn, activity: activity} do
1067 other_user = insert(:user)
1068 {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
1069
1070 response =
1071 conn
1072 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1073 |> json_response(:ok)
1074
1075 [%{"id" => id}] = response
1076
1077 assert id == other_user.id
1078 end
1079
1080 test "returns empty array when status has not been reblogged yet", %{
1081 conn: conn,
1082 activity: activity
1083 } do
1084 response =
1085 conn
1086 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1087 |> json_response(:ok)
1088
1089 assert Enum.empty?(response)
1090 end
1091
1092 test "does not return users who have reblogged the status but are blocked", %{
1093 conn: %{assigns: %{user: user}} = conn,
1094 activity: activity
1095 } do
1096 other_user = insert(:user)
1097 {:ok, _user_relationship} = User.block(user, other_user)
1098
1099 {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
1100
1101 response =
1102 conn
1103 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1104 |> json_response(:ok)
1105
1106 assert Enum.empty?(response)
1107 end
1108
1109 test "does not return users who have reblogged the status privately", %{
1110 conn: conn,
1111 activity: activity
1112 } do
1113 other_user = insert(:user)
1114
1115 {:ok, _, _} = CommonAPI.repeat(activity.id, other_user, %{"visibility" => "private"})
1116
1117 response =
1118 conn
1119 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1120 |> json_response(:ok)
1121
1122 assert Enum.empty?(response)
1123 end
1124
1125 test "does not fail on an unauthenticated request", %{activity: activity} do
1126 other_user = insert(:user)
1127 {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
1128
1129 response =
1130 build_conn()
1131 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1132 |> json_response(:ok)
1133
1134 [%{"id" => id}] = response
1135 assert id == other_user.id
1136 end
1137
1138 test "requires authentication for private posts", %{user: user} do
1139 other_user = insert(:user)
1140
1141 {:ok, activity} =
1142 CommonAPI.post(user, %{
1143 "status" => "@#{other_user.nickname} wanna get some #cofe together?",
1144 "visibility" => "direct"
1145 })
1146
1147 build_conn()
1148 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1149 |> json_response(404)
1150
1151 response =
1152 build_conn()
1153 |> assign(:user, other_user)
1154 |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"]))
1155 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1156 |> json_response(200)
1157
1158 assert [] == response
1159 end
1160 end
1161
1162 test "context" do
1163 user = insert(:user)
1164
1165 {:ok, %{id: id1}} = CommonAPI.post(user, %{"status" => "1"})
1166 {:ok, %{id: id2}} = CommonAPI.post(user, %{"status" => "2", "in_reply_to_status_id" => id1})
1167 {:ok, %{id: id3}} = CommonAPI.post(user, %{"status" => "3", "in_reply_to_status_id" => id2})
1168 {:ok, %{id: id4}} = CommonAPI.post(user, %{"status" => "4", "in_reply_to_status_id" => id3})
1169 {:ok, %{id: id5}} = CommonAPI.post(user, %{"status" => "5", "in_reply_to_status_id" => id4})
1170
1171 response =
1172 build_conn()
1173 |> get("/api/v1/statuses/#{id3}/context")
1174 |> json_response(:ok)
1175
1176 assert %{
1177 "ancestors" => [%{"id" => ^id1}, %{"id" => ^id2}],
1178 "descendants" => [%{"id" => ^id4}, %{"id" => ^id5}]
1179 } = response
1180 end
1181
1182 test "returns the favorites of a user" do
1183 %{user: user, conn: conn} = oauth_access(["read:favourites"])
1184 other_user = insert(:user)
1185
1186 {:ok, _} = CommonAPI.post(other_user, %{"status" => "bla"})
1187 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "traps are happy"})
1188
1189 {:ok, _, _} = CommonAPI.favorite(activity.id, user)
1190
1191 first_conn = get(conn, "/api/v1/favourites")
1192
1193 assert [status] = json_response(first_conn, 200)
1194 assert status["id"] == to_string(activity.id)
1195
1196 assert [{"link", _link_header}] =
1197 Enum.filter(first_conn.resp_headers, fn element -> match?({"link", _}, element) end)
1198
1199 # Honours query params
1200 {:ok, second_activity} =
1201 CommonAPI.post(other_user, %{
1202 "status" =>
1203 "Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful."
1204 })
1205
1206 {:ok, _, _} = CommonAPI.favorite(second_activity.id, user)
1207
1208 last_like = status["id"]
1209
1210 second_conn = get(conn, "/api/v1/favourites?since_id=#{last_like}")
1211
1212 assert [second_status] = json_response(second_conn, 200)
1213 assert second_status["id"] == to_string(second_activity.id)
1214
1215 third_conn = get(conn, "/api/v1/favourites?limit=0")
1216
1217 assert [] = json_response(third_conn, 200)
1218 end
1219 end