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