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