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