Merge branch 'develop' into feature/new-registrations-digest
[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 "favoriting twice will just return 200", %{conn: conn} do
642 activity = insert(:note_activity)
643
644 post(conn, "/api/v1/statuses/#{activity.id}/favourite")
645 assert post(conn, "/api/v1/statuses/#{activity.id}/favourite") |> json_response(200)
646 end
647
648 test "returns 400 error for a wrong id", %{conn: conn} do
649 conn = post(conn, "/api/v1/statuses/1/favourite")
650
651 assert json_response(conn, 400) == %{"error" => "Could not favorite"}
652 end
653 end
654
655 describe "unfavoriting" do
656 setup do: oauth_access(["write:favourites"])
657
658 test "unfavorites a status and returns it", %{user: user, conn: conn} do
659 activity = insert(:note_activity)
660
661 {:ok, _, _} = CommonAPI.favorite(activity.id, user)
662
663 conn = post(conn, "/api/v1/statuses/#{activity.id}/unfavourite")
664
665 assert %{"id" => id, "favourites_count" => 0, "favourited" => false} =
666 json_response(conn, 200)
667
668 assert to_string(activity.id) == id
669 end
670
671 test "returns 400 error for a wrong id", %{conn: conn} do
672 conn = post(conn, "/api/v1/statuses/1/unfavourite")
673
674 assert json_response(conn, 400) == %{"error" => "Could not unfavorite"}
675 end
676 end
677
678 describe "pinned statuses" do
679 setup do: oauth_access(["write:accounts"])
680
681 setup %{user: user} do
682 {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"})
683
684 %{activity: activity}
685 end
686
687 clear_config([:instance, :max_pinned_statuses]) do
688 Config.put([:instance, :max_pinned_statuses], 1)
689 end
690
691 test "pin status", %{conn: conn, user: user, activity: activity} do
692 id_str = to_string(activity.id)
693
694 assert %{"id" => ^id_str, "pinned" => true} =
695 conn
696 |> post("/api/v1/statuses/#{activity.id}/pin")
697 |> json_response(200)
698
699 assert [%{"id" => ^id_str, "pinned" => true}] =
700 conn
701 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
702 |> json_response(200)
703 end
704
705 test "/pin: returns 400 error when activity is not public", %{conn: conn, user: user} do
706 {:ok, dm} = CommonAPI.post(user, %{"status" => "test", "visibility" => "direct"})
707
708 conn = post(conn, "/api/v1/statuses/#{dm.id}/pin")
709
710 assert json_response(conn, 400) == %{"error" => "Could not pin"}
711 end
712
713 test "unpin status", %{conn: conn, user: user, activity: activity} do
714 {:ok, _} = CommonAPI.pin(activity.id, user)
715 user = refresh_record(user)
716
717 id_str = to_string(activity.id)
718
719 assert %{"id" => ^id_str, "pinned" => false} =
720 conn
721 |> assign(:user, user)
722 |> post("/api/v1/statuses/#{activity.id}/unpin")
723 |> json_response(200)
724
725 assert [] =
726 conn
727 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
728 |> json_response(200)
729 end
730
731 test "/unpin: returns 400 error when activity is not exist", %{conn: conn} do
732 conn = post(conn, "/api/v1/statuses/1/unpin")
733
734 assert json_response(conn, 400) == %{"error" => "Could not unpin"}
735 end
736
737 test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
738 {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
739
740 id_str_one = to_string(activity_one.id)
741
742 assert %{"id" => ^id_str_one, "pinned" => true} =
743 conn
744 |> post("/api/v1/statuses/#{id_str_one}/pin")
745 |> json_response(200)
746
747 user = refresh_record(user)
748
749 assert %{"error" => "You have already pinned the maximum number of statuses"} =
750 conn
751 |> assign(:user, user)
752 |> post("/api/v1/statuses/#{activity_two.id}/pin")
753 |> json_response(400)
754 end
755 end
756
757 describe "cards" do
758 setup do
759 Config.put([:rich_media, :enabled], true)
760
761 oauth_access(["read:statuses"])
762 end
763
764 test "returns rich-media card", %{conn: conn, user: user} do
765 Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
766
767 {:ok, activity} = CommonAPI.post(user, %{"status" => "https://example.com/ogp"})
768
769 card_data = %{
770 "image" => "http://ia.media-imdb.com/images/rock.jpg",
771 "provider_name" => "example.com",
772 "provider_url" => "https://example.com",
773 "title" => "The Rock",
774 "type" => "link",
775 "url" => "https://example.com/ogp",
776 "description" =>
777 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
778 "pleroma" => %{
779 "opengraph" => %{
780 "image" => "http://ia.media-imdb.com/images/rock.jpg",
781 "title" => "The Rock",
782 "type" => "video.movie",
783 "url" => "https://example.com/ogp",
784 "description" =>
785 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer."
786 }
787 }
788 }
789
790 response =
791 conn
792 |> get("/api/v1/statuses/#{activity.id}/card")
793 |> json_response(200)
794
795 assert response == card_data
796
797 # works with private posts
798 {:ok, activity} =
799 CommonAPI.post(user, %{"status" => "https://example.com/ogp", "visibility" => "direct"})
800
801 response_two =
802 conn
803 |> get("/api/v1/statuses/#{activity.id}/card")
804 |> json_response(200)
805
806 assert response_two == card_data
807 end
808
809 test "replaces missing description with an empty string", %{conn: conn, user: user} do
810 Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
811
812 {:ok, activity} =
813 CommonAPI.post(user, %{"status" => "https://example.com/ogp-missing-data"})
814
815 response =
816 conn
817 |> get("/api/v1/statuses/#{activity.id}/card")
818 |> json_response(:ok)
819
820 assert response == %{
821 "type" => "link",
822 "title" => "Pleroma",
823 "description" => "",
824 "image" => nil,
825 "provider_name" => "example.com",
826 "provider_url" => "https://example.com",
827 "url" => "https://example.com/ogp-missing-data",
828 "pleroma" => %{
829 "opengraph" => %{
830 "title" => "Pleroma",
831 "type" => "website",
832 "url" => "https://example.com/ogp-missing-data"
833 }
834 }
835 }
836 end
837 end
838
839 test "bookmarks" do
840 %{conn: conn} = oauth_access(["write:bookmarks", "read:bookmarks"])
841 author = insert(:user)
842
843 {:ok, activity1} =
844 CommonAPI.post(author, %{
845 "status" => "heweoo?"
846 })
847
848 {:ok, activity2} =
849 CommonAPI.post(author, %{
850 "status" => "heweoo!"
851 })
852
853 response1 = post(conn, "/api/v1/statuses/#{activity1.id}/bookmark")
854
855 assert json_response(response1, 200)["bookmarked"] == true
856
857 response2 = post(conn, "/api/v1/statuses/#{activity2.id}/bookmark")
858
859 assert json_response(response2, 200)["bookmarked"] == true
860
861 bookmarks = get(conn, "/api/v1/bookmarks")
862
863 assert [json_response(response2, 200), json_response(response1, 200)] ==
864 json_response(bookmarks, 200)
865
866 response1 = post(conn, "/api/v1/statuses/#{activity1.id}/unbookmark")
867
868 assert json_response(response1, 200)["bookmarked"] == false
869
870 bookmarks = get(conn, "/api/v1/bookmarks")
871
872 assert [json_response(response2, 200)] == json_response(bookmarks, 200)
873 end
874
875 describe "conversation muting" do
876 setup do: oauth_access(["write:mutes"])
877
878 setup do
879 post_user = insert(:user)
880 {:ok, activity} = CommonAPI.post(post_user, %{"status" => "HIE"})
881 %{activity: activity}
882 end
883
884 test "mute conversation", %{conn: conn, activity: activity} do
885 id_str = to_string(activity.id)
886
887 assert %{"id" => ^id_str, "muted" => true} =
888 conn
889 |> post("/api/v1/statuses/#{activity.id}/mute")
890 |> json_response(200)
891 end
892
893 test "cannot mute already muted conversation", %{conn: conn, user: user, activity: activity} do
894 {:ok, _} = CommonAPI.add_mute(user, activity)
895
896 conn = post(conn, "/api/v1/statuses/#{activity.id}/mute")
897
898 assert json_response(conn, 400) == %{"error" => "conversation is already muted"}
899 end
900
901 test "unmute conversation", %{conn: conn, user: user, activity: activity} do
902 {:ok, _} = CommonAPI.add_mute(user, activity)
903
904 id_str = to_string(activity.id)
905
906 assert %{"id" => ^id_str, "muted" => false} =
907 conn
908 # |> assign(:user, user)
909 |> post("/api/v1/statuses/#{activity.id}/unmute")
910 |> json_response(200)
911 end
912 end
913
914 test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do
915 user1 = insert(:user)
916 user2 = insert(:user)
917 user3 = insert(:user)
918
919 {:ok, replied_to} = CommonAPI.post(user1, %{"status" => "cofe"})
920
921 # Reply to status from another user
922 conn1 =
923 conn
924 |> assign(:user, user2)
925 |> assign(:token, insert(:oauth_token, user: user2, scopes: ["write:statuses"]))
926 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
927
928 assert %{"content" => "xD", "id" => id} = json_response(conn1, 200)
929
930 activity = Activity.get_by_id_with_object(id)
931
932 assert Object.normalize(activity).data["inReplyTo"] == Object.normalize(replied_to).data["id"]
933 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
934
935 # Reblog from the third user
936 conn2 =
937 conn
938 |> assign(:user, user3)
939 |> assign(:token, insert(:oauth_token, user: user3, scopes: ["write:statuses"]))
940 |> post("/api/v1/statuses/#{activity.id}/reblog")
941
942 assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} =
943 json_response(conn2, 200)
944
945 assert to_string(activity.id) == id
946
947 # Getting third user status
948 conn3 =
949 conn
950 |> assign(:user, user3)
951 |> assign(:token, insert(:oauth_token, user: user3, scopes: ["read:statuses"]))
952 |> get("api/v1/timelines/home")
953
954 [reblogged_activity] = json_response(conn3, 200)
955
956 assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id
957
958 replied_to_user = User.get_by_ap_id(replied_to.data["actor"])
959 assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id
960 end
961
962 describe "GET /api/v1/statuses/:id/favourited_by" do
963 setup do: oauth_access(["read:accounts"])
964
965 setup %{user: user} do
966 {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
967
968 %{activity: activity}
969 end
970
971 test "returns users who have favorited the status", %{conn: conn, activity: activity} do
972 other_user = insert(:user)
973 {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
974
975 response =
976 conn
977 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
978 |> json_response(:ok)
979
980 [%{"id" => id}] = response
981
982 assert id == other_user.id
983 end
984
985 test "returns empty array when status has not been favorited yet", %{
986 conn: conn,
987 activity: activity
988 } do
989 response =
990 conn
991 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
992 |> json_response(:ok)
993
994 assert Enum.empty?(response)
995 end
996
997 test "does not return users who have favorited the status but are blocked", %{
998 conn: %{assigns: %{user: user}} = conn,
999 activity: activity
1000 } do
1001 other_user = insert(:user)
1002 {:ok, _user_relationship} = User.block(user, other_user)
1003
1004 {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
1005
1006 response =
1007 conn
1008 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1009 |> json_response(:ok)
1010
1011 assert Enum.empty?(response)
1012 end
1013
1014 test "does not fail on an unauthenticated request", %{activity: activity} do
1015 other_user = insert(:user)
1016 {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
1017
1018 response =
1019 build_conn()
1020 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1021 |> json_response(:ok)
1022
1023 [%{"id" => id}] = response
1024 assert id == other_user.id
1025 end
1026
1027 test "requires authentication for private posts", %{user: user} do
1028 other_user = insert(:user)
1029
1030 {:ok, activity} =
1031 CommonAPI.post(user, %{
1032 "status" => "@#{other_user.nickname} wanna get some #cofe together?",
1033 "visibility" => "direct"
1034 })
1035
1036 {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
1037
1038 favourited_by_url = "/api/v1/statuses/#{activity.id}/favourited_by"
1039
1040 build_conn()
1041 |> get(favourited_by_url)
1042 |> json_response(404)
1043
1044 conn =
1045 build_conn()
1046 |> assign(:user, other_user)
1047 |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"]))
1048
1049 conn
1050 |> assign(:token, nil)
1051 |> get(favourited_by_url)
1052 |> json_response(404)
1053
1054 response =
1055 conn
1056 |> get(favourited_by_url)
1057 |> json_response(200)
1058
1059 [%{"id" => id}] = response
1060 assert id == other_user.id
1061 end
1062 end
1063
1064 describe "GET /api/v1/statuses/:id/reblogged_by" do
1065 setup do: oauth_access(["read:accounts"])
1066
1067 setup %{user: user} do
1068 {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
1069
1070 %{activity: activity}
1071 end
1072
1073 test "returns users who have reblogged the status", %{conn: conn, activity: activity} do
1074 other_user = insert(:user)
1075 {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
1076
1077 response =
1078 conn
1079 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1080 |> json_response(:ok)
1081
1082 [%{"id" => id}] = response
1083
1084 assert id == other_user.id
1085 end
1086
1087 test "returns empty array when status has not been reblogged yet", %{
1088 conn: conn,
1089 activity: activity
1090 } do
1091 response =
1092 conn
1093 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1094 |> json_response(:ok)
1095
1096 assert Enum.empty?(response)
1097 end
1098
1099 test "does not return users who have reblogged the status but are blocked", %{
1100 conn: %{assigns: %{user: user}} = conn,
1101 activity: activity
1102 } do
1103 other_user = insert(:user)
1104 {:ok, _user_relationship} = User.block(user, other_user)
1105
1106 {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
1107
1108 response =
1109 conn
1110 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1111 |> json_response(:ok)
1112
1113 assert Enum.empty?(response)
1114 end
1115
1116 test "does not return users who have reblogged the status privately", %{
1117 conn: conn,
1118 activity: activity
1119 } do
1120 other_user = insert(:user)
1121
1122 {:ok, _, _} = CommonAPI.repeat(activity.id, other_user, %{"visibility" => "private"})
1123
1124 response =
1125 conn
1126 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1127 |> json_response(:ok)
1128
1129 assert Enum.empty?(response)
1130 end
1131
1132 test "does not fail on an unauthenticated request", %{activity: activity} do
1133 other_user = insert(:user)
1134 {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
1135
1136 response =
1137 build_conn()
1138 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1139 |> json_response(:ok)
1140
1141 [%{"id" => id}] = response
1142 assert id == other_user.id
1143 end
1144
1145 test "requires authentication for private posts", %{user: user} do
1146 other_user = insert(:user)
1147
1148 {:ok, activity} =
1149 CommonAPI.post(user, %{
1150 "status" => "@#{other_user.nickname} wanna get some #cofe together?",
1151 "visibility" => "direct"
1152 })
1153
1154 build_conn()
1155 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1156 |> json_response(404)
1157
1158 response =
1159 build_conn()
1160 |> assign(:user, other_user)
1161 |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"]))
1162 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1163 |> json_response(200)
1164
1165 assert [] == response
1166 end
1167 end
1168
1169 test "context" do
1170 user = insert(:user)
1171
1172 {:ok, %{id: id1}} = CommonAPI.post(user, %{"status" => "1"})
1173 {:ok, %{id: id2}} = CommonAPI.post(user, %{"status" => "2", "in_reply_to_status_id" => id1})
1174 {:ok, %{id: id3}} = CommonAPI.post(user, %{"status" => "3", "in_reply_to_status_id" => id2})
1175 {:ok, %{id: id4}} = CommonAPI.post(user, %{"status" => "4", "in_reply_to_status_id" => id3})
1176 {:ok, %{id: id5}} = CommonAPI.post(user, %{"status" => "5", "in_reply_to_status_id" => id4})
1177
1178 response =
1179 build_conn()
1180 |> get("/api/v1/statuses/#{id3}/context")
1181 |> json_response(:ok)
1182
1183 assert %{
1184 "ancestors" => [%{"id" => ^id1}, %{"id" => ^id2}],
1185 "descendants" => [%{"id" => ^id4}, %{"id" => ^id5}]
1186 } = response
1187 end
1188
1189 test "returns the favorites of a user" do
1190 %{user: user, conn: conn} = oauth_access(["read:favourites"])
1191 other_user = insert(:user)
1192
1193 {:ok, _} = CommonAPI.post(other_user, %{"status" => "bla"})
1194 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "traps are happy"})
1195
1196 {:ok, _, _} = CommonAPI.favorite(activity.id, user)
1197
1198 first_conn = get(conn, "/api/v1/favourites")
1199
1200 assert [status] = json_response(first_conn, 200)
1201 assert status["id"] == to_string(activity.id)
1202
1203 assert [{"link", _link_header}] =
1204 Enum.filter(first_conn.resp_headers, fn element -> match?({"link", _}, element) end)
1205
1206 # Honours query params
1207 {:ok, second_activity} =
1208 CommonAPI.post(other_user, %{
1209 "status" =>
1210 "Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful."
1211 })
1212
1213 {:ok, _, _} = CommonAPI.favorite(second_activity.id, user)
1214
1215 last_like = status["id"]
1216
1217 second_conn = get(conn, "/api/v1/favourites?since_id=#{last_like}")
1218
1219 assert [second_status] = json_response(second_conn, 200)
1220 assert second_status["id"] == to_string(second_activity.id)
1221
1222 third_conn = get(conn, "/api/v1/favourites?limit=0")
1223
1224 assert [] = json_response(third_conn, 200)
1225 end
1226 end