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