move to 2.5.5
[anni] / test / pleroma / web / mastodon_api / controllers / status_controller_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 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, async: false
7   use Oban.Testing, repo: Pleroma.Repo
8
9   alias Pleroma.Activity
10   alias Pleroma.Conversation.Participation
11   alias Pleroma.ModerationLog
12   alias Pleroma.Object
13   alias Pleroma.Repo
14   alias Pleroma.ScheduledActivity
15   alias Pleroma.Tests.ObanHelpers
16   alias Pleroma.User
17   alias Pleroma.Web.ActivityPub.ActivityPub
18   alias Pleroma.Web.ActivityPub.Utils
19   alias Pleroma.Web.CommonAPI
20   alias Pleroma.Workers.ScheduledActivityWorker
21
22   import Pleroma.Factory
23
24   setup do: clear_config([:instance, :federating])
25   setup do: clear_config([:instance, :allow_relay])
26   setup do: clear_config([:rich_media, :enabled])
27   setup do: clear_config([:mrf, :policies])
28   setup do: clear_config([:mrf_keyword, :reject])
29
30   describe "posting statuses" do
31     setup do: oauth_access(["write:statuses"])
32
33     test "posting a status does not increment reblog_count when relaying", %{conn: conn} do
34       clear_config([:instance, :federating], true)
35       Config.get([:instance, :allow_relay], true)
36
37       response =
38         conn
39         |> put_req_header("content-type", "application/json")
40         |> post("api/v1/statuses", %{
41           "content_type" => "text/plain",
42           "source" => "Pleroma FE",
43           "status" => "Hello world",
44           "visibility" => "public"
45         })
46         |> json_response_and_validate_schema(200)
47
48       assert response["reblogs_count"] == 0
49       ObanHelpers.perform_all()
50
51       response =
52         conn
53         |> get("api/v1/statuses/#{response["id"]}", %{})
54         |> json_response_and_validate_schema(200)
55
56       assert response["reblogs_count"] == 0
57     end
58
59     test "posting a status", %{conn: conn} do
60       idempotency_key = "Pikachu rocks!"
61
62       conn_one =
63         conn
64         |> put_req_header("content-type", "application/json")
65         |> put_req_header("idempotency-key", idempotency_key)
66         |> post("/api/v1/statuses", %{
67           "status" => "cofe",
68           "spoiler_text" => "2hu",
69           "sensitive" => "0"
70         })
71
72       assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} =
73                json_response_and_validate_schema(conn_one, 200)
74
75       assert Activity.get_by_id(id)
76
77       conn_two =
78         conn
79         |> put_req_header("content-type", "application/json")
80         |> put_req_header("idempotency-key", idempotency_key)
81         |> post("/api/v1/statuses", %{
82           "status" => "cofe",
83           "spoiler_text" => "2hu",
84           "sensitive" => 0
85         })
86
87       # Idempotency plug response means detection fail
88       assert %{"id" => second_id} = json_response(conn_two, 200)
89       assert id == second_id
90
91       conn_three =
92         conn
93         |> put_req_header("content-type", "application/json")
94         |> post("/api/v1/statuses", %{
95           "status" => "cofe",
96           "spoiler_text" => "2hu",
97           "sensitive" => "False"
98         })
99
100       assert %{"id" => third_id} = json_response_and_validate_schema(conn_three, 200)
101       refute id == third_id
102
103       # An activity that will expire:
104       # 2 hours
105       expires_in = 2 * 60 * 60
106
107       expires_at = DateTime.add(DateTime.utc_now(), expires_in)
108
109       conn_four =
110         conn
111         |> put_req_header("content-type", "application/json")
112         |> post("api/v1/statuses", %{
113           "status" => "oolong",
114           "expires_in" => expires_in
115         })
116
117       assert %{"id" => fourth_id} = json_response_and_validate_schema(conn_four, 200)
118
119       assert Activity.get_by_id(fourth_id)
120
121       assert_enqueued(
122         worker: Pleroma.Workers.PurgeExpiredActivity,
123         args: %{activity_id: fourth_id},
124         scheduled_at: expires_at
125       )
126     end
127
128     test "it fails to create a status if `expires_in` is less or equal than an hour", %{
129       conn: conn
130     } do
131       # 1 minute
132       expires_in = 1 * 60
133
134       assert %{"error" => "Expiry date is too soon"} =
135                conn
136                |> put_req_header("content-type", "application/json")
137                |> post("api/v1/statuses", %{
138                  "status" => "oolong",
139                  "expires_in" => expires_in
140                })
141                |> json_response_and_validate_schema(422)
142
143       # 5 minutes
144       expires_in = 5 * 60
145
146       assert %{"error" => "Expiry date is too soon"} =
147                conn
148                |> put_req_header("content-type", "application/json")
149                |> post("api/v1/statuses", %{
150                  "status" => "oolong",
151                  "expires_in" => expires_in
152                })
153                |> json_response_and_validate_schema(422)
154     end
155
156     test "Get MRF reason when posting a status is rejected by one", %{conn: conn} do
157       clear_config([:mrf_keyword, :reject], ["GNO"])
158       clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy])
159
160       assert %{"error" => "[KeywordPolicy] Matches with rejected keyword"} =
161                conn
162                |> put_req_header("content-type", "application/json")
163                |> post("api/v1/statuses", %{"status" => "GNO/Linux"})
164                |> json_response_and_validate_schema(422)
165     end
166
167     test "posting an undefined status with an attachment", %{user: user, conn: conn} do
168       file = %Plug.Upload{
169         content_type: "image/jpeg",
170         path: Path.absname("test/fixtures/image.jpg"),
171         filename: "an_image.jpg"
172       }
173
174       {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
175
176       conn =
177         conn
178         |> put_req_header("content-type", "application/json")
179         |> post("/api/v1/statuses", %{
180           "media_ids" => [to_string(upload.id)]
181         })
182
183       assert json_response_and_validate_schema(conn, 200)
184     end
185
186     test "replying to a status", %{user: user, conn: conn} do
187       {:ok, replied_to} = CommonAPI.post(user, %{status: "cofe"})
188
189       conn =
190         conn
191         |> put_req_header("content-type", "application/json")
192         |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
193
194       assert %{"content" => "xD", "id" => id} = json_response_and_validate_schema(conn, 200)
195
196       activity = Activity.get_by_id(id)
197
198       assert activity.data["context"] == replied_to.data["context"]
199       assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
200     end
201
202     test "replying to a direct message with visibility other than direct", %{
203       user: user,
204       conn: conn
205     } do
206       {:ok, replied_to} = CommonAPI.post(user, %{status: "suya..", visibility: "direct"})
207
208       Enum.each(["public", "private", "unlisted"], fn visibility ->
209         conn =
210           conn
211           |> put_req_header("content-type", "application/json")
212           |> post("/api/v1/statuses", %{
213             "status" => "@#{user.nickname} hey",
214             "in_reply_to_id" => replied_to.id,
215             "visibility" => visibility
216           })
217
218         assert json_response_and_validate_schema(conn, 422) == %{
219                  "error" => "The message visibility must be direct"
220                }
221       end)
222     end
223
224     test "posting a status with an invalid in_reply_to_id", %{conn: conn} do
225       conn =
226         conn
227         |> put_req_header("content-type", "application/json")
228         |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => ""})
229
230       assert %{"content" => "xD", "id" => id} = json_response_and_validate_schema(conn, 200)
231       assert Activity.get_by_id(id)
232     end
233
234     test "posting a sensitive status", %{conn: conn} do
235       conn =
236         conn
237         |> put_req_header("content-type", "application/json")
238         |> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true})
239
240       assert %{"content" => "cofe", "id" => id, "sensitive" => true} =
241                json_response_and_validate_schema(conn, 200)
242
243       assert Activity.get_by_id(id)
244     end
245
246     test "posting a fake status", %{conn: conn} do
247       real_conn =
248         conn
249         |> put_req_header("content-type", "application/json")
250         |> post("/api/v1/statuses", %{
251           "status" =>
252             "\"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"
253         })
254
255       real_status = json_response_and_validate_schema(real_conn, 200)
256
257       assert real_status
258       assert Object.get_by_ap_id(real_status["uri"])
259
260       real_status =
261         real_status
262         |> Map.put("id", nil)
263         |> Map.put("url", nil)
264         |> Map.put("uri", nil)
265         |> Map.put("created_at", nil)
266         |> Kernel.put_in(["pleroma", "context"], nil)
267         |> Kernel.put_in(["pleroma", "conversation_id"], nil)
268
269       fake_conn =
270         conn
271         |> assign(:user, refresh_record(conn.assigns.user))
272         |> put_req_header("content-type", "application/json")
273         |> post("/api/v1/statuses", %{
274           "status" =>
275             "\"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",
276           "preview" => true
277         })
278
279       fake_status = json_response_and_validate_schema(fake_conn, 200)
280
281       assert fake_status
282       refute Object.get_by_ap_id(fake_status["uri"])
283
284       fake_status =
285         fake_status
286         |> Map.put("id", nil)
287         |> Map.put("url", nil)
288         |> Map.put("uri", nil)
289         |> Map.put("created_at", nil)
290         |> Kernel.put_in(["pleroma", "context"], nil)
291         |> Kernel.put_in(["pleroma", "conversation_id"], nil)
292
293       assert real_status == fake_status
294     end
295
296     test "fake statuses' preview card is not cached", %{conn: conn} do
297       clear_config([:rich_media, :enabled], true)
298
299       Tesla.Mock.mock(fn
300         %{
301           method: :get,
302           url: "https://example.com/twitter-card"
303         } ->
304           %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/twitter_card.html")}
305
306         env ->
307           apply(HttpRequestMock, :request, [env])
308       end)
309
310       conn1 =
311         conn
312         |> put_req_header("content-type", "application/json")
313         |> post("/api/v1/statuses", %{
314           "status" => "https://example.com/ogp",
315           "preview" => true
316         })
317
318       conn2 =
319         conn
320         |> put_req_header("content-type", "application/json")
321         |> post("/api/v1/statuses", %{
322           "status" => "https://example.com/twitter-card",
323           "preview" => true
324         })
325
326       assert %{"card" => %{"title" => "The Rock"}} = json_response_and_validate_schema(conn1, 200)
327
328       assert %{"card" => %{"title" => "Small Island Developing States Photo Submission"}} =
329                json_response_and_validate_schema(conn2, 200)
330     end
331
332     test "posting a status with OGP link preview", %{conn: conn} do
333       Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
334       clear_config([:rich_media, :enabled], true)
335
336       conn =
337         conn
338         |> put_req_header("content-type", "application/json")
339         |> post("/api/v1/statuses", %{
340           "status" => "https://example.com/ogp"
341         })
342
343       assert %{"id" => id, "card" => %{"title" => "The Rock"}} =
344                json_response_and_validate_schema(conn, 200)
345
346       assert Activity.get_by_id(id)
347     end
348
349     test "posting a direct status", %{conn: conn} do
350       user2 = insert(:user)
351       content = "direct cofe @#{user2.nickname}"
352
353       conn =
354         conn
355         |> put_req_header("content-type", "application/json")
356         |> post("api/v1/statuses", %{"status" => content, "visibility" => "direct"})
357
358       assert %{"id" => id} = response = json_response_and_validate_schema(conn, 200)
359       assert response["visibility"] == "direct"
360       assert response["pleroma"]["direct_conversation_id"]
361       assert activity = Activity.get_by_id(id)
362       assert activity.recipients == [user2.ap_id, conn.assigns[:user].ap_id]
363       assert activity.data["to"] == [user2.ap_id]
364       assert activity.data["cc"] == []
365     end
366
367     test "discloses application metadata when enabled" do
368       user = insert(:user, disclose_client: true)
369       %{user: _user, token: token, conn: conn} = oauth_access(["write:statuses"], user: user)
370
371       %Pleroma.Web.OAuth.Token{
372         app: %Pleroma.Web.OAuth.App{
373           client_name: app_name,
374           website: app_website
375         }
376       } = token
377
378       result =
379         conn
380         |> put_req_header("content-type", "application/json")
381         |> post("/api/v1/statuses", %{
382           "status" => "cofe is my copilot"
383         })
384
385       assert %{
386                "content" => "cofe is my copilot"
387              } = json_response_and_validate_schema(result, 200)
388
389       activity = result.assigns.activity.id
390
391       result =
392         conn
393         |> get("api/v1/statuses/#{activity}")
394
395       assert %{
396                "content" => "cofe is my copilot",
397                "application" => %{
398                  "name" => ^app_name,
399                  "website" => ^app_website
400                }
401              } = json_response_and_validate_schema(result, 200)
402     end
403
404     test "hides application metadata when disabled" do
405       user = insert(:user, disclose_client: false)
406       %{user: _user, token: _token, conn: conn} = oauth_access(["write:statuses"], user: user)
407
408       result =
409         conn
410         |> put_req_header("content-type", "application/json")
411         |> post("/api/v1/statuses", %{
412           "status" => "club mate is my wingman"
413         })
414
415       assert %{"content" => "club mate is my wingman"} =
416                json_response_and_validate_schema(result, 200)
417
418       activity = result.assigns.activity.id
419
420       result =
421         conn
422         |> get("api/v1/statuses/#{activity}")
423
424       assert %{
425                "content" => "club mate is my wingman",
426                "application" => nil
427              } = json_response_and_validate_schema(result, 200)
428     end
429   end
430
431   describe "posting scheduled statuses" do
432     setup do: oauth_access(["write:statuses"])
433
434     test "creates a scheduled activity", %{conn: conn} do
435       scheduled_at =
436         NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
437         |> NaiveDateTime.to_iso8601()
438         |> Kernel.<>("Z")
439
440       conn =
441         conn
442         |> put_req_header("content-type", "application/json")
443         |> post("/api/v1/statuses", %{
444           "status" => "scheduled",
445           "scheduled_at" => scheduled_at
446         })
447
448       assert %{"scheduled_at" => expected_scheduled_at} =
449                json_response_and_validate_schema(conn, 200)
450
451       assert expected_scheduled_at == CommonAPI.Utils.to_masto_date(scheduled_at)
452       assert [] == Repo.all(Activity)
453     end
454
455     test "with expiration" do
456       %{conn: conn} = oauth_access(["write:statuses", "read:statuses"])
457
458       scheduled_at =
459         NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(6), :millisecond)
460         |> NaiveDateTime.to_iso8601()
461         |> Kernel.<>("Z")
462
463       assert %{"id" => status_id, "params" => %{"expires_in" => 300}} =
464                conn
465                |> put_req_header("content-type", "application/json")
466                |> post("/api/v1/statuses", %{
467                  "status" => "scheduled",
468                  "scheduled_at" => scheduled_at,
469                  "expires_in" => 300
470                })
471                |> json_response_and_validate_schema(200)
472
473       assert %{"id" => ^status_id, "params" => %{"expires_in" => 300}} =
474                conn
475                |> put_req_header("content-type", "application/json")
476                |> get("/api/v1/scheduled_statuses/#{status_id}")
477                |> json_response_and_validate_schema(200)
478     end
479
480     test "ignores nil values", %{conn: conn} do
481       conn =
482         conn
483         |> put_req_header("content-type", "application/json")
484         |> post("/api/v1/statuses", %{
485           "status" => "not scheduled",
486           "scheduled_at" => nil
487         })
488
489       assert result = json_response_and_validate_schema(conn, 200)
490       assert Activity.get_by_id(result["id"])
491     end
492
493     test "creates a scheduled activity with a media attachment", %{user: user, conn: conn} do
494       scheduled_at =
495         NaiveDateTime.utc_now()
496         |> NaiveDateTime.add(:timer.minutes(120), :millisecond)
497         |> NaiveDateTime.to_iso8601()
498         |> Kernel.<>("Z")
499
500       file = %Plug.Upload{
501         content_type: "image/jpeg",
502         path: Path.absname("test/fixtures/image.jpg"),
503         filename: "an_image.jpg"
504       }
505
506       {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
507
508       conn =
509         conn
510         |> put_req_header("content-type", "application/json")
511         |> post("/api/v1/statuses", %{
512           "media_ids" => [to_string(upload.id)],
513           "status" => "scheduled",
514           "scheduled_at" => scheduled_at
515         })
516
517       assert %{"media_attachments" => [media_attachment]} =
518                json_response_and_validate_schema(conn, 200)
519
520       assert %{"type" => "image"} = media_attachment
521     end
522
523     test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now",
524          %{conn: conn} do
525       scheduled_at =
526         NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond)
527         |> NaiveDateTime.to_iso8601()
528         |> Kernel.<>("Z")
529
530       conn =
531         conn
532         |> put_req_header("content-type", "application/json")
533         |> post("/api/v1/statuses", %{
534           "status" => "not scheduled",
535           "scheduled_at" => scheduled_at
536         })
537
538       assert %{"content" => "not scheduled"} = json_response_and_validate_schema(conn, 200)
539       assert [] == Repo.all(ScheduledActivity)
540     end
541
542     test "returns error when daily user limit is exceeded", %{user: user, conn: conn} do
543       today =
544         NaiveDateTime.utc_now()
545         |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
546         |> NaiveDateTime.to_iso8601()
547         # TODO
548         |> Kernel.<>("Z")
549
550       attrs = %{params: %{}, scheduled_at: today}
551       {:ok, _} = ScheduledActivity.create(user, attrs)
552       {:ok, _} = ScheduledActivity.create(user, attrs)
553
554       conn =
555         conn
556         |> put_req_header("content-type", "application/json")
557         |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today})
558
559       assert %{"error" => "daily limit exceeded"} == json_response_and_validate_schema(conn, 422)
560     end
561
562     test "returns error when total user limit is exceeded", %{user: user, conn: conn} do
563       today =
564         NaiveDateTime.utc_now()
565         |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
566         |> NaiveDateTime.to_iso8601()
567         |> Kernel.<>("Z")
568
569       tomorrow =
570         NaiveDateTime.utc_now()
571         |> NaiveDateTime.add(:timer.hours(36), :millisecond)
572         |> NaiveDateTime.to_iso8601()
573         |> Kernel.<>("Z")
574
575       attrs = %{params: %{}, scheduled_at: today}
576       {:ok, _} = ScheduledActivity.create(user, attrs)
577       {:ok, _} = ScheduledActivity.create(user, attrs)
578       {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
579
580       conn =
581         conn
582         |> put_req_header("content-type", "application/json")
583         |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow})
584
585       assert %{"error" => "total limit exceeded"} == json_response_and_validate_schema(conn, 422)
586     end
587   end
588
589   describe "posting polls" do
590     setup do: oauth_access(["write:statuses"])
591
592     test "posting a poll", %{conn: conn} do
593       time = NaiveDateTime.utc_now()
594
595       conn =
596         conn
597         |> put_req_header("content-type", "application/json")
598         |> post("/api/v1/statuses", %{
599           "status" => "Who is the #bestgrill?",
600           "poll" => %{
601             "options" => ["Rei", "Asuka", "Misato"],
602             "expires_in" => 420
603           }
604         })
605
606       response = json_response_and_validate_schema(conn, 200)
607
608       assert Enum.all?(response["poll"]["options"], fn %{"title" => title} ->
609                title in ["Rei", "Asuka", "Misato"]
610              end)
611
612       assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430
613       assert response["poll"]["expired"] == false
614
615       question = Object.get_by_id(response["poll"]["id"])
616
617       # closed contains utc timezone
618       assert question.data["closed"] =~ "Z"
619     end
620
621     test "option limit is enforced", %{conn: conn} do
622       limit = Config.get([:instance, :poll_limits, :max_options])
623
624       conn =
625         conn
626         |> put_req_header("content-type", "application/json")
627         |> post("/api/v1/statuses", %{
628           "status" => "desu~",
629           "poll" => %{"options" => Enum.map(0..limit, fn _ -> "desu" end), "expires_in" => 1}
630         })
631
632       %{"error" => error} = json_response_and_validate_schema(conn, 422)
633       assert error == "Poll can't contain more than #{limit} options"
634     end
635
636     test "option character limit is enforced", %{conn: conn} do
637       limit = Config.get([:instance, :poll_limits, :max_option_chars])
638
639       conn =
640         conn
641         |> put_req_header("content-type", "application/json")
642         |> post("/api/v1/statuses", %{
643           "status" => "...",
644           "poll" => %{
645             "options" => [Enum.reduce(0..limit, "", fn _, acc -> acc <> "." end)],
646             "expires_in" => 1
647           }
648         })
649
650       %{"error" => error} = json_response_and_validate_schema(conn, 422)
651       assert error == "Poll options cannot be longer than #{limit} characters each"
652     end
653
654     test "minimal date limit is enforced", %{conn: conn} do
655       limit = Config.get([:instance, :poll_limits, :min_expiration])
656
657       conn =
658         conn
659         |> put_req_header("content-type", "application/json")
660         |> post("/api/v1/statuses", %{
661           "status" => "imagine arbitrary limits",
662           "poll" => %{
663             "options" => ["this post was made by pleroma gang"],
664             "expires_in" => limit - 1
665           }
666         })
667
668       %{"error" => error} = json_response_and_validate_schema(conn, 422)
669       assert error == "Expiration date is too soon"
670     end
671
672     test "maximum date limit is enforced", %{conn: conn} do
673       limit = Config.get([:instance, :poll_limits, :max_expiration])
674
675       conn =
676         conn
677         |> put_req_header("content-type", "application/json")
678         |> post("/api/v1/statuses", %{
679           "status" => "imagine arbitrary limits",
680           "poll" => %{
681             "options" => ["this post was made by pleroma gang"],
682             "expires_in" => limit + 1
683           }
684         })
685
686       %{"error" => error} = json_response_and_validate_schema(conn, 422)
687       assert error == "Expiration date is too far in the future"
688     end
689
690     test "scheduled poll", %{conn: conn} do
691       clear_config([ScheduledActivity, :enabled], true)
692
693       scheduled_at =
694         NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(6), :millisecond)
695         |> NaiveDateTime.to_iso8601()
696         |> Kernel.<>("Z")
697
698       %{"id" => scheduled_id} =
699         conn
700         |> put_req_header("content-type", "application/json")
701         |> post("/api/v1/statuses", %{
702           "status" => "very cool poll",
703           "poll" => %{
704             "options" => ~w(a b c),
705             "expires_in" => 420
706           },
707           "scheduled_at" => scheduled_at
708         })
709         |> json_response_and_validate_schema(200)
710
711       assert {:ok, %{id: activity_id}} =
712                perform_job(ScheduledActivityWorker, %{
713                  activity_id: scheduled_id
714                })
715
716       refute_enqueued(worker: ScheduledActivityWorker)
717
718       object =
719         Activity
720         |> Repo.get(activity_id)
721         |> Object.normalize()
722
723       assert object.data["content"] == "very cool poll"
724       assert object.data["type"] == "Question"
725       assert length(object.data["oneOf"]) == 3
726     end
727   end
728
729   test "get a status" do
730     %{conn: conn} = oauth_access(["read:statuses"])
731     activity = insert(:note_activity)
732
733     conn = get(conn, "/api/v1/statuses/#{activity.id}")
734
735     assert %{"id" => id} = json_response_and_validate_schema(conn, 200)
736     assert id == to_string(activity.id)
737   end
738
739   defp local_and_remote_activities do
740     local = insert(:note_activity)
741     remote = insert(:note_activity, local: false)
742     {:ok, local: local, remote: remote}
743   end
744
745   describe "status with restrict unauthenticated activities for local and remote" do
746     setup do: local_and_remote_activities()
747
748     setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
749
750     setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
751
752     test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
753       res_conn = get(conn, "/api/v1/statuses/#{local.id}")
754
755       assert json_response_and_validate_schema(res_conn, :not_found) == %{
756                "error" => "Record not found"
757              }
758
759       res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
760
761       assert json_response_and_validate_schema(res_conn, :not_found) == %{
762                "error" => "Record not found"
763              }
764     end
765
766     test "if user is authenticated", %{local: local, remote: remote} do
767       %{conn: conn} = oauth_access(["read"])
768       res_conn = get(conn, "/api/v1/statuses/#{local.id}")
769       assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
770
771       res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
772       assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
773     end
774   end
775
776   describe "status with restrict unauthenticated activities for local" do
777     setup do: local_and_remote_activities()
778
779     setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
780
781     test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
782       res_conn = get(conn, "/api/v1/statuses/#{local.id}")
783
784       assert json_response_and_validate_schema(res_conn, :not_found) == %{
785                "error" => "Record not found"
786              }
787
788       res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
789       assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
790     end
791
792     test "if user is authenticated", %{local: local, remote: remote} do
793       %{conn: conn} = oauth_access(["read"])
794       res_conn = get(conn, "/api/v1/statuses/#{local.id}")
795       assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
796
797       res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
798       assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
799     end
800   end
801
802   describe "status with restrict unauthenticated activities for remote" do
803     setup do: local_and_remote_activities()
804
805     setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
806
807     test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
808       res_conn = get(conn, "/api/v1/statuses/#{local.id}")
809       assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
810
811       res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
812
813       assert json_response_and_validate_schema(res_conn, :not_found) == %{
814                "error" => "Record not found"
815              }
816     end
817
818     test "if user is authenticated", %{local: local, remote: remote} do
819       %{conn: conn} = oauth_access(["read"])
820       res_conn = get(conn, "/api/v1/statuses/#{local.id}")
821       assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
822
823       res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
824       assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
825     end
826   end
827
828   test "getting a status that doesn't exist returns 404" do
829     %{conn: conn} = oauth_access(["read:statuses"])
830     activity = insert(:note_activity)
831
832     conn = get(conn, "/api/v1/statuses/#{String.downcase(activity.id)}")
833
834     assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
835   end
836
837   test "get a direct status" do
838     %{user: user, conn: conn} = oauth_access(["read:statuses"])
839     other_user = insert(:user)
840
841     {:ok, activity} =
842       CommonAPI.post(user, %{status: "@#{other_user.nickname}", visibility: "direct"})
843
844     conn =
845       conn
846       |> assign(:user, user)
847       |> get("/api/v1/statuses/#{activity.id}")
848
849     [participation] = Participation.for_user(user)
850
851     res = json_response_and_validate_schema(conn, 200)
852     assert res["pleroma"]["direct_conversation_id"] == participation.id
853   end
854
855   test "get statuses by IDs" do
856     %{conn: conn} = oauth_access(["read:statuses"])
857     %{id: id1} = insert(:note_activity)
858     %{id: id2} = insert(:note_activity)
859
860     query_string = "ids[]=#{id1}&ids[]=#{id2}"
861     conn = get(conn, "/api/v1/statuses/?#{query_string}")
862
863     assert [%{"id" => ^id1}, %{"id" => ^id2}] =
864              Enum.sort_by(json_response_and_validate_schema(conn, :ok), & &1["id"])
865   end
866
867   describe "getting statuses by ids with restricted unauthenticated for local and remote" do
868     setup do: local_and_remote_activities()
869
870     setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
871
872     setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
873
874     test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
875       res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
876
877       assert json_response_and_validate_schema(res_conn, 200) == []
878     end
879
880     test "if user is authenticated", %{local: local, remote: remote} do
881       %{conn: conn} = oauth_access(["read"])
882
883       res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
884
885       assert length(json_response_and_validate_schema(res_conn, 200)) == 2
886     end
887   end
888
889   describe "getting statuses by ids with restricted unauthenticated for local" do
890     setup do: local_and_remote_activities()
891
892     setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
893
894     test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
895       res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
896
897       remote_id = remote.id
898       assert [%{"id" => ^remote_id}] = json_response_and_validate_schema(res_conn, 200)
899     end
900
901     test "if user is authenticated", %{local: local, remote: remote} do
902       %{conn: conn} = oauth_access(["read"])
903
904       res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
905
906       assert length(json_response_and_validate_schema(res_conn, 200)) == 2
907     end
908   end
909
910   describe "getting statuses by ids with restricted unauthenticated for remote" do
911     setup do: local_and_remote_activities()
912
913     setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
914
915     test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
916       res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
917
918       local_id = local.id
919       assert [%{"id" => ^local_id}] = json_response_and_validate_schema(res_conn, 200)
920     end
921
922     test "if user is authenticated", %{local: local, remote: remote} do
923       %{conn: conn} = oauth_access(["read"])
924
925       res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
926
927       assert length(json_response_and_validate_schema(res_conn, 200)) == 2
928     end
929   end
930
931   describe "deleting a status" do
932     test "when you created it" do
933       %{user: author, conn: conn} = oauth_access(["write:statuses"])
934       activity = insert(:note_activity, user: author)
935       object = Object.normalize(activity, fetch: false)
936
937       content = object.data["content"]
938       source = object.data["source"]
939
940       result =
941         conn
942         |> assign(:user, author)
943         |> delete("/api/v1/statuses/#{activity.id}")
944         |> json_response_and_validate_schema(200)
945
946       assert match?(%{"content" => ^content, "text" => ^source}, result)
947
948       refute Activity.get_by_id(activity.id)
949     end
950
951     test "when it doesn't exist" do
952       %{user: author, conn: conn} = oauth_access(["write:statuses"])
953       activity = insert(:note_activity, user: author)
954
955       conn =
956         conn
957         |> assign(:user, author)
958         |> delete("/api/v1/statuses/#{String.downcase(activity.id)}")
959
960       assert %{"error" => "Record not found"} == json_response_and_validate_schema(conn, 404)
961     end
962
963     test "when you didn't create it" do
964       %{conn: conn} = oauth_access(["write:statuses"])
965       activity = insert(:note_activity)
966
967       conn = delete(conn, "/api/v1/statuses/#{activity.id}")
968
969       assert %{"error" => "Record not found"} == json_response_and_validate_schema(conn, 404)
970
971       assert Activity.get_by_id(activity.id) == activity
972     end
973
974     test "when you're privileged to", %{conn: conn} do
975       clear_config([:instance, :moderator_privileges], [:messages_delete])
976       activity = insert(:note_activity)
977       user = insert(:user, is_moderator: true)
978
979       res_conn =
980         conn
981         |> assign(:user, user)
982         |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:statuses"]))
983         |> delete("/api/v1/statuses/#{activity.id}")
984
985       assert %{} = json_response_and_validate_schema(res_conn, 200)
986
987       assert ModerationLog |> Repo.one() |> ModerationLog.get_log_entry_message() ==
988                "@#{user.nickname} deleted status ##{activity.id}"
989
990       refute Activity.get_by_id(activity.id)
991     end
992   end
993
994   describe "reblogging" do
995     setup do: oauth_access(["write:statuses"])
996
997     test "reblogs and returns the reblogged status", %{conn: conn} do
998       activity = insert(:note_activity)
999
1000       conn =
1001         conn
1002         |> put_req_header("content-type", "application/json")
1003         |> post("/api/v1/statuses/#{activity.id}/reblog")
1004
1005       assert %{
1006                "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
1007                "reblogged" => true
1008              } = json_response_and_validate_schema(conn, 200)
1009
1010       assert to_string(activity.id) == id
1011     end
1012
1013     test "returns 404 if the reblogged status doesn't exist", %{conn: conn} do
1014       activity = insert(:note_activity)
1015
1016       conn =
1017         conn
1018         |> put_req_header("content-type", "application/json")
1019         |> post("/api/v1/statuses/#{String.downcase(activity.id)}/reblog")
1020
1021       assert %{"error" => "Record not found"} = json_response_and_validate_schema(conn, 404)
1022     end
1023
1024     test "reblogs privately and returns the reblogged status", %{conn: conn} do
1025       activity = insert(:note_activity)
1026
1027       conn =
1028         conn
1029         |> put_req_header("content-type", "application/json")
1030         |> post(
1031           "/api/v1/statuses/#{activity.id}/reblog",
1032           %{"visibility" => "private"}
1033         )
1034
1035       assert %{
1036                "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
1037                "reblogged" => true,
1038                "visibility" => "private"
1039              } = json_response_and_validate_schema(conn, 200)
1040
1041       assert to_string(activity.id) == id
1042     end
1043
1044     test "reblogged status for another user" do
1045       activity = insert(:note_activity)
1046       user1 = insert(:user)
1047       user2 = insert(:user)
1048       user3 = insert(:user)
1049       {:ok, _} = CommonAPI.favorite(user2, activity.id)
1050       {:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id)
1051       {:ok, reblog_activity1} = CommonAPI.repeat(activity.id, user1)
1052       {:ok, _} = CommonAPI.repeat(activity.id, user2)
1053
1054       conn_res =
1055         build_conn()
1056         |> assign(:user, user3)
1057         |> assign(:token, insert(:oauth_token, user: user3, scopes: ["read:statuses"]))
1058         |> get("/api/v1/statuses/#{reblog_activity1.id}")
1059
1060       assert %{
1061                "reblog" => %{"id" => _id, "reblogged" => false, "reblogs_count" => 2},
1062                "reblogged" => false,
1063                "favourited" => false,
1064                "bookmarked" => false
1065              } = json_response_and_validate_schema(conn_res, 200)
1066
1067       conn_res =
1068         build_conn()
1069         |> assign(:user, user2)
1070         |> assign(:token, insert(:oauth_token, user: user2, scopes: ["read:statuses"]))
1071         |> get("/api/v1/statuses/#{reblog_activity1.id}")
1072
1073       assert %{
1074                "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 2},
1075                "reblogged" => true,
1076                "favourited" => true,
1077                "bookmarked" => true
1078              } = json_response_and_validate_schema(conn_res, 200)
1079
1080       assert to_string(activity.id) == id
1081     end
1082
1083     test "author can reblog own private status", %{conn: conn, user: user} do
1084       {:ok, activity} = CommonAPI.post(user, %{status: "cofe", visibility: "private"})
1085
1086       conn =
1087         conn
1088         |> put_req_header("content-type", "application/json")
1089         |> post("/api/v1/statuses/#{activity.id}/reblog")
1090
1091       assert %{
1092                "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
1093                "reblogged" => true,
1094                "visibility" => "private"
1095              } = json_response_and_validate_schema(conn, 200)
1096
1097       assert to_string(activity.id) == id
1098     end
1099   end
1100
1101   describe "unreblogging" do
1102     setup do: oauth_access(["write:statuses"])
1103
1104     test "unreblogs and returns the unreblogged status", %{user: user, conn: conn} do
1105       activity = insert(:note_activity)
1106
1107       {:ok, _} = CommonAPI.repeat(activity.id, user)
1108
1109       conn =
1110         conn
1111         |> put_req_header("content-type", "application/json")
1112         |> post("/api/v1/statuses/#{activity.id}/unreblog")
1113
1114       assert %{"id" => id, "reblogged" => false, "reblogs_count" => 0} =
1115                json_response_and_validate_schema(conn, 200)
1116
1117       assert to_string(activity.id) == id
1118     end
1119
1120     test "returns 404 error when activity does not exist", %{conn: conn} do
1121       conn =
1122         conn
1123         |> put_req_header("content-type", "application/json")
1124         |> post("/api/v1/statuses/foo/unreblog")
1125
1126       assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
1127     end
1128   end
1129
1130   describe "favoriting" do
1131     setup do: oauth_access(["write:favourites"])
1132
1133     test "favs a status and returns it", %{conn: conn} do
1134       activity = insert(:note_activity)
1135
1136       conn =
1137         conn
1138         |> put_req_header("content-type", "application/json")
1139         |> post("/api/v1/statuses/#{activity.id}/favourite")
1140
1141       assert %{"id" => id, "favourites_count" => 1, "favourited" => true} =
1142                json_response_and_validate_schema(conn, 200)
1143
1144       assert to_string(activity.id) == id
1145     end
1146
1147     test "favoriting twice will just return 200", %{conn: conn} do
1148       activity = insert(:note_activity)
1149
1150       conn
1151       |> put_req_header("content-type", "application/json")
1152       |> post("/api/v1/statuses/#{activity.id}/favourite")
1153
1154       assert conn
1155              |> put_req_header("content-type", "application/json")
1156              |> post("/api/v1/statuses/#{activity.id}/favourite")
1157              |> json_response_and_validate_schema(200)
1158     end
1159
1160     test "returns 404 error for a wrong id", %{conn: conn} do
1161       conn =
1162         conn
1163         |> put_req_header("content-type", "application/json")
1164         |> post("/api/v1/statuses/1/favourite")
1165
1166       assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
1167     end
1168   end
1169
1170   describe "unfavoriting" do
1171     setup do: oauth_access(["write:favourites"])
1172
1173     test "unfavorites a status and returns it", %{user: user, conn: conn} do
1174       activity = insert(:note_activity)
1175
1176       {:ok, _} = CommonAPI.favorite(user, activity.id)
1177
1178       conn =
1179         conn
1180         |> put_req_header("content-type", "application/json")
1181         |> post("/api/v1/statuses/#{activity.id}/unfavourite")
1182
1183       assert %{"id" => id, "favourites_count" => 0, "favourited" => false} =
1184                json_response_and_validate_schema(conn, 200)
1185
1186       assert to_string(activity.id) == id
1187     end
1188
1189     test "returns 404 error for a wrong id", %{conn: conn} do
1190       conn =
1191         conn
1192         |> put_req_header("content-type", "application/json")
1193         |> post("/api/v1/statuses/1/unfavourite")
1194
1195       assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
1196     end
1197   end
1198
1199   describe "pinned statuses" do
1200     setup do: oauth_access(["write:accounts"])
1201
1202     setup %{user: user} do
1203       {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!"})
1204
1205       %{activity: activity}
1206     end
1207
1208     setup do: clear_config([:instance, :max_pinned_statuses], 1)
1209
1210     test "pin status", %{conn: conn, user: user, activity: activity} do
1211       id = activity.id
1212
1213       assert %{"id" => ^id, "pinned" => true} =
1214                conn
1215                |> put_req_header("content-type", "application/json")
1216                |> post("/api/v1/statuses/#{activity.id}/pin")
1217                |> json_response_and_validate_schema(200)
1218
1219       assert [%{"id" => ^id, "pinned" => true}] =
1220                conn
1221                |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
1222                |> json_response_and_validate_schema(200)
1223     end
1224
1225     test "non authenticated user", %{activity: activity} do
1226       assert build_conn()
1227              |> put_req_header("content-type", "application/json")
1228              |> post("/api/v1/statuses/#{activity.id}/pin")
1229              |> json_response(403) == %{"error" => "Invalid credentials."}
1230     end
1231
1232     test "/pin: returns 400 error when activity is not public", %{conn: conn, user: user} do
1233       {:ok, dm} = CommonAPI.post(user, %{status: "test", visibility: "direct"})
1234
1235       conn =
1236         conn
1237         |> put_req_header("content-type", "application/json")
1238         |> post("/api/v1/statuses/#{dm.id}/pin")
1239
1240       assert json_response_and_validate_schema(conn, 422) == %{
1241                "error" => "Non-public status cannot be pinned"
1242              }
1243     end
1244
1245     test "pin by another user", %{activity: activity} do
1246       %{conn: conn} = oauth_access(["write:accounts"])
1247
1248       assert conn
1249              |> put_req_header("content-type", "application/json")
1250              |> post("/api/v1/statuses/#{activity.id}/pin")
1251              |> json_response(422) == %{"error" => "Someone else's status cannot be pinned"}
1252     end
1253
1254     test "unpin status", %{conn: conn, user: user, activity: activity} do
1255       {:ok, _} = CommonAPI.pin(activity.id, user)
1256       user = refresh_record(user)
1257
1258       id_str = to_string(activity.id)
1259
1260       assert %{"id" => ^id_str, "pinned" => false} =
1261                conn
1262                |> assign(:user, user)
1263                |> post("/api/v1/statuses/#{activity.id}/unpin")
1264                |> json_response_and_validate_schema(200)
1265
1266       assert [] =
1267                conn
1268                |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
1269                |> json_response_and_validate_schema(200)
1270     end
1271
1272     test "/unpin: returns 404 error when activity doesn't exist", %{conn: conn} do
1273       assert conn
1274              |> put_req_header("content-type", "application/json")
1275              |> post("/api/v1/statuses/1/unpin")
1276              |> json_response_and_validate_schema(404) == %{"error" => "Record not found"}
1277     end
1278
1279     test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
1280       {:ok, activity_two} = CommonAPI.post(user, %{status: "HI!!!"})
1281
1282       id_str_one = to_string(activity_one.id)
1283
1284       assert %{"id" => ^id_str_one, "pinned" => true} =
1285                conn
1286                |> put_req_header("content-type", "application/json")
1287                |> post("/api/v1/statuses/#{id_str_one}/pin")
1288                |> json_response_and_validate_schema(200)
1289
1290       user = refresh_record(user)
1291
1292       assert %{"error" => "You have already pinned the maximum number of statuses"} =
1293                conn
1294                |> assign(:user, user)
1295                |> post("/api/v1/statuses/#{activity_two.id}/pin")
1296                |> json_response_and_validate_schema(400)
1297     end
1298
1299     test "on pin removes deletion job, on unpin reschedule deletion" do
1300       %{conn: conn} = oauth_access(["write:accounts", "write:statuses"])
1301       expires_in = 2 * 60 * 60
1302
1303       expires_at = DateTime.add(DateTime.utc_now(), expires_in)
1304
1305       assert %{"id" => id} =
1306                conn
1307                |> put_req_header("content-type", "application/json")
1308                |> post("api/v1/statuses", %{
1309                  "status" => "oolong",
1310                  "expires_in" => expires_in
1311                })
1312                |> json_response_and_validate_schema(200)
1313
1314       assert_enqueued(
1315         worker: Pleroma.Workers.PurgeExpiredActivity,
1316         args: %{activity_id: id},
1317         scheduled_at: expires_at
1318       )
1319
1320       assert %{"id" => ^id, "pinned" => true} =
1321                conn
1322                |> put_req_header("content-type", "application/json")
1323                |> post("/api/v1/statuses/#{id}/pin")
1324                |> json_response_and_validate_schema(200)
1325
1326       refute_enqueued(
1327         worker: Pleroma.Workers.PurgeExpiredActivity,
1328         args: %{activity_id: id},
1329         scheduled_at: expires_at
1330       )
1331
1332       assert %{"id" => ^id, "pinned" => false} =
1333                conn
1334                |> put_req_header("content-type", "application/json")
1335                |> post("/api/v1/statuses/#{id}/unpin")
1336                |> json_response_and_validate_schema(200)
1337
1338       assert_enqueued(
1339         worker: Pleroma.Workers.PurgeExpiredActivity,
1340         args: %{activity_id: id},
1341         scheduled_at: expires_at
1342       )
1343     end
1344   end
1345
1346   describe "cards" do
1347     setup do
1348       clear_config([:rich_media, :enabled], true)
1349
1350       oauth_access(["read:statuses"])
1351     end
1352
1353     test "returns rich-media card", %{conn: conn, user: user} do
1354       Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
1355
1356       {:ok, activity} = CommonAPI.post(user, %{status: "https://example.com/ogp"})
1357
1358       card_data = %{
1359         "image" => "http://ia.media-imdb.com/images/rock.jpg",
1360         "provider_name" => "example.com",
1361         "provider_url" => "https://example.com",
1362         "title" => "The Rock",
1363         "type" => "link",
1364         "url" => "https://example.com/ogp",
1365         "description" =>
1366           "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
1367         "pleroma" => %{
1368           "opengraph" => %{
1369             "image" => "http://ia.media-imdb.com/images/rock.jpg",
1370             "title" => "The Rock",
1371             "type" => "video.movie",
1372             "url" => "https://example.com/ogp",
1373             "description" =>
1374               "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer."
1375           }
1376         }
1377       }
1378
1379       response =
1380         conn
1381         |> get("/api/v1/statuses/#{activity.id}/card")
1382         |> json_response_and_validate_schema(200)
1383
1384       assert response == card_data
1385
1386       # works with private posts
1387       {:ok, activity} =
1388         CommonAPI.post(user, %{status: "https://example.com/ogp", visibility: "direct"})
1389
1390       response_two =
1391         conn
1392         |> get("/api/v1/statuses/#{activity.id}/card")
1393         |> json_response_and_validate_schema(200)
1394
1395       assert response_two == card_data
1396     end
1397
1398     test "replaces missing description with an empty string", %{conn: conn, user: user} do
1399       Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
1400
1401       {:ok, activity} = CommonAPI.post(user, %{status: "https://example.com/ogp-missing-data"})
1402
1403       response =
1404         conn
1405         |> get("/api/v1/statuses/#{activity.id}/card")
1406         |> json_response_and_validate_schema(:ok)
1407
1408       assert response == %{
1409                "type" => "link",
1410                "title" => "Pleroma",
1411                "description" => "",
1412                "image" => nil,
1413                "provider_name" => "example.com",
1414                "provider_url" => "https://example.com",
1415                "url" => "https://example.com/ogp-missing-data",
1416                "pleroma" => %{
1417                  "opengraph" => %{
1418                    "title" => "Pleroma",
1419                    "type" => "website",
1420                    "url" => "https://example.com/ogp-missing-data"
1421                  }
1422                }
1423              }
1424     end
1425   end
1426
1427   test "bookmarks" do
1428     bookmarks_uri = "/api/v1/bookmarks"
1429
1430     %{conn: conn} = oauth_access(["write:bookmarks", "read:bookmarks"])
1431     author = insert(:user)
1432
1433     {:ok, activity1} = CommonAPI.post(author, %{status: "heweoo?"})
1434     {:ok, activity2} = CommonAPI.post(author, %{status: "heweoo!"})
1435
1436     response1 =
1437       conn
1438       |> put_req_header("content-type", "application/json")
1439       |> post("/api/v1/statuses/#{activity1.id}/bookmark")
1440
1441     assert json_response_and_validate_schema(response1, 200)["bookmarked"] == true
1442
1443     response2 =
1444       conn
1445       |> put_req_header("content-type", "application/json")
1446       |> post("/api/v1/statuses/#{activity2.id}/bookmark")
1447
1448     assert json_response_and_validate_schema(response2, 200)["bookmarked"] == true
1449
1450     bookmarks = get(conn, bookmarks_uri)
1451
1452     assert [
1453              json_response_and_validate_schema(response2, 200),
1454              json_response_and_validate_schema(response1, 200)
1455            ] ==
1456              json_response_and_validate_schema(bookmarks, 200)
1457
1458     response1 =
1459       conn
1460       |> put_req_header("content-type", "application/json")
1461       |> post("/api/v1/statuses/#{activity1.id}/unbookmark")
1462
1463     assert json_response_and_validate_schema(response1, 200)["bookmarked"] == false
1464
1465     bookmarks = get(conn, bookmarks_uri)
1466
1467     assert [json_response_and_validate_schema(response2, 200)] ==
1468              json_response_and_validate_schema(bookmarks, 200)
1469   end
1470
1471   describe "conversation muting" do
1472     setup do: oauth_access(["write:mutes"])
1473
1474     setup do
1475       post_user = insert(:user)
1476       {:ok, activity} = CommonAPI.post(post_user, %{status: "HIE"})
1477       %{activity: activity}
1478     end
1479
1480     test "mute conversation", %{conn: conn, activity: activity} do
1481       id_str = to_string(activity.id)
1482
1483       assert %{"id" => ^id_str, "muted" => true} =
1484                conn
1485                |> put_req_header("content-type", "application/json")
1486                |> post("/api/v1/statuses/#{activity.id}/mute")
1487                |> json_response_and_validate_schema(200)
1488     end
1489
1490     test "cannot mute already muted conversation", %{conn: conn, user: user, activity: activity} do
1491       {:ok, _} = CommonAPI.add_mute(user, activity)
1492
1493       conn =
1494         conn
1495         |> put_req_header("content-type", "application/json")
1496         |> post("/api/v1/statuses/#{activity.id}/mute")
1497
1498       assert json_response_and_validate_schema(conn, 400) == %{
1499                "error" => "conversation is already muted"
1500              }
1501     end
1502
1503     test "unmute conversation", %{conn: conn, user: user, activity: activity} do
1504       {:ok, _} = CommonAPI.add_mute(user, activity)
1505
1506       id_str = to_string(activity.id)
1507
1508       assert %{"id" => ^id_str, "muted" => false} =
1509                conn
1510                # |> assign(:user, user)
1511                |> post("/api/v1/statuses/#{activity.id}/unmute")
1512                |> json_response_and_validate_schema(200)
1513     end
1514   end
1515
1516   test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do
1517     user1 = insert(:user)
1518     user2 = insert(:user)
1519     user3 = insert(:user)
1520
1521     {:ok, replied_to} = CommonAPI.post(user1, %{status: "cofe"})
1522
1523     # Reply to status from another user
1524     conn1 =
1525       conn
1526       |> assign(:user, user2)
1527       |> assign(:token, insert(:oauth_token, user: user2, scopes: ["write:statuses"]))
1528       |> put_req_header("content-type", "application/json")
1529       |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
1530
1531     assert %{"content" => "xD", "id" => id} = json_response_and_validate_schema(conn1, 200)
1532
1533     activity = Activity.get_by_id_with_object(id)
1534
1535     assert Object.normalize(activity, fetch: false).data["inReplyTo"] ==
1536              Object.normalize(replied_to, fetch: false).data["id"]
1537
1538     assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
1539
1540     # Reblog from the third user
1541     conn2 =
1542       conn
1543       |> assign(:user, user3)
1544       |> assign(:token, insert(:oauth_token, user: user3, scopes: ["write:statuses"]))
1545       |> put_req_header("content-type", "application/json")
1546       |> post("/api/v1/statuses/#{activity.id}/reblog")
1547
1548     assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} =
1549              json_response_and_validate_schema(conn2, 200)
1550
1551     assert to_string(activity.id) == id
1552
1553     # Getting third user status
1554     conn3 =
1555       conn
1556       |> assign(:user, user3)
1557       |> assign(:token, insert(:oauth_token, user: user3, scopes: ["read:statuses"]))
1558       |> get("api/v1/timelines/home")
1559
1560     [reblogged_activity] = json_response_and_validate_schema(conn3, 200)
1561
1562     assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id
1563
1564     replied_to_user = User.get_by_ap_id(replied_to.data["actor"])
1565     assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id
1566   end
1567
1568   describe "GET /api/v1/statuses/:id/favourited_by" do
1569     setup do: oauth_access(["read:accounts"])
1570
1571     setup %{user: user} do
1572       {:ok, activity} = CommonAPI.post(user, %{status: "test"})
1573
1574       %{activity: activity}
1575     end
1576
1577     test "returns users who have favorited the status", %{conn: conn, activity: activity} do
1578       other_user = insert(:user)
1579       {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1580
1581       response =
1582         conn
1583         |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1584         |> json_response_and_validate_schema(:ok)
1585
1586       [%{"id" => id}] = response
1587
1588       assert id == other_user.id
1589     end
1590
1591     test "returns empty array when status has not been favorited yet", %{
1592       conn: conn,
1593       activity: activity
1594     } do
1595       response =
1596         conn
1597         |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1598         |> json_response_and_validate_schema(:ok)
1599
1600       assert Enum.empty?(response)
1601     end
1602
1603     test "does not return users who have favorited the status but are blocked", %{
1604       conn: %{assigns: %{user: user}} = conn,
1605       activity: activity
1606     } do
1607       other_user = insert(:user)
1608       {:ok, _user_relationship} = User.block(user, other_user)
1609
1610       {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1611
1612       response =
1613         conn
1614         |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1615         |> json_response_and_validate_schema(:ok)
1616
1617       assert Enum.empty?(response)
1618     end
1619
1620     test "does not fail on an unauthenticated request", %{activity: activity} do
1621       other_user = insert(:user)
1622       {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1623
1624       response =
1625         build_conn()
1626         |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1627         |> json_response_and_validate_schema(:ok)
1628
1629       [%{"id" => id}] = response
1630       assert id == other_user.id
1631     end
1632
1633     test "requires authentication for private posts", %{user: user} do
1634       other_user = insert(:user)
1635
1636       {:ok, activity} =
1637         CommonAPI.post(user, %{
1638           status: "@#{other_user.nickname} wanna get some #cofe together?",
1639           visibility: "direct"
1640         })
1641
1642       {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1643
1644       favourited_by_url = "/api/v1/statuses/#{activity.id}/favourited_by"
1645
1646       build_conn()
1647       |> get(favourited_by_url)
1648       |> json_response_and_validate_schema(404)
1649
1650       conn =
1651         build_conn()
1652         |> assign(:user, other_user)
1653         |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"]))
1654
1655       conn
1656       |> assign(:token, nil)
1657       |> get(favourited_by_url)
1658       |> json_response_and_validate_schema(404)
1659
1660       response =
1661         conn
1662         |> get(favourited_by_url)
1663         |> json_response_and_validate_schema(200)
1664
1665       [%{"id" => id}] = response
1666       assert id == other_user.id
1667     end
1668
1669     test "returns empty array when :show_reactions is disabled", %{conn: conn, activity: activity} do
1670       clear_config([:instance, :show_reactions], false)
1671
1672       other_user = insert(:user)
1673       {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1674
1675       response =
1676         conn
1677         |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1678         |> json_response_and_validate_schema(:ok)
1679
1680       assert Enum.empty?(response)
1681     end
1682   end
1683
1684   describe "GET /api/v1/statuses/:id/reblogged_by" do
1685     setup do: oauth_access(["read:accounts"])
1686
1687     setup %{user: user} do
1688       {:ok, activity} = CommonAPI.post(user, %{status: "test"})
1689
1690       %{activity: activity}
1691     end
1692
1693     test "returns users who have reblogged the status", %{conn: conn, activity: activity} do
1694       other_user = insert(:user)
1695       {:ok, _} = CommonAPI.repeat(activity.id, other_user)
1696
1697       response =
1698         conn
1699         |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1700         |> json_response_and_validate_schema(:ok)
1701
1702       [%{"id" => id}] = response
1703
1704       assert id == other_user.id
1705     end
1706
1707     test "returns empty array when status has not been reblogged yet", %{
1708       conn: conn,
1709       activity: activity
1710     } do
1711       response =
1712         conn
1713         |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1714         |> json_response_and_validate_schema(:ok)
1715
1716       assert Enum.empty?(response)
1717     end
1718
1719     test "does not return users who have reblogged the status but are blocked", %{
1720       conn: %{assigns: %{user: user}} = conn,
1721       activity: activity
1722     } do
1723       other_user = insert(:user)
1724       {:ok, _user_relationship} = User.block(user, other_user)
1725
1726       {:ok, _} = CommonAPI.repeat(activity.id, other_user)
1727
1728       response =
1729         conn
1730         |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1731         |> json_response_and_validate_schema(:ok)
1732
1733       assert Enum.empty?(response)
1734     end
1735
1736     test "does not return users who have reblogged the status privately", %{
1737       conn: conn
1738     } do
1739       other_user = insert(:user)
1740       {:ok, activity} = CommonAPI.post(other_user, %{status: "my secret post"})
1741
1742       {:ok, _} = CommonAPI.repeat(activity.id, other_user, %{visibility: "private"})
1743
1744       response =
1745         conn
1746         |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1747         |> json_response_and_validate_schema(:ok)
1748
1749       assert Enum.empty?(response)
1750     end
1751
1752     test "does not fail on an unauthenticated request", %{activity: activity} do
1753       other_user = insert(:user)
1754       {:ok, _} = CommonAPI.repeat(activity.id, other_user)
1755
1756       response =
1757         build_conn()
1758         |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1759         |> json_response_and_validate_schema(:ok)
1760
1761       [%{"id" => id}] = response
1762       assert id == other_user.id
1763     end
1764
1765     test "requires authentication for private posts", %{user: user} do
1766       other_user = insert(:user)
1767
1768       {:ok, activity} =
1769         CommonAPI.post(user, %{
1770           status: "@#{other_user.nickname} wanna get some #cofe together?",
1771           visibility: "direct"
1772         })
1773
1774       build_conn()
1775       |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1776       |> json_response_and_validate_schema(404)
1777
1778       response =
1779         build_conn()
1780         |> assign(:user, other_user)
1781         |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"]))
1782         |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1783         |> json_response_and_validate_schema(200)
1784
1785       assert [] == response
1786     end
1787   end
1788
1789   test "context" do
1790     user = insert(:user)
1791
1792     {:ok, %{id: id1}} = CommonAPI.post(user, %{status: "1"})
1793     {:ok, %{id: id2}} = CommonAPI.post(user, %{status: "2", in_reply_to_status_id: id1})
1794     {:ok, %{id: id3}} = CommonAPI.post(user, %{status: "3", in_reply_to_status_id: id2})
1795     {:ok, %{id: id4}} = CommonAPI.post(user, %{status: "4", in_reply_to_status_id: id3})
1796     {:ok, %{id: id5}} = CommonAPI.post(user, %{status: "5", in_reply_to_status_id: id4})
1797
1798     response =
1799       build_conn()
1800       |> get("/api/v1/statuses/#{id3}/context")
1801       |> json_response_and_validate_schema(:ok)
1802
1803     assert %{
1804              "ancestors" => [%{"id" => ^id1}, %{"id" => ^id2}],
1805              "descendants" => [%{"id" => ^id4}, %{"id" => ^id5}]
1806            } = response
1807   end
1808
1809   test "favorites paginate correctly" do
1810     %{user: user, conn: conn} = oauth_access(["read:favourites"])
1811     other_user = insert(:user)
1812     {:ok, first_post} = CommonAPI.post(other_user, %{status: "bla"})
1813     {:ok, second_post} = CommonAPI.post(other_user, %{status: "bla"})
1814     {:ok, third_post} = CommonAPI.post(other_user, %{status: "bla"})
1815
1816     {:ok, _first_favorite} = CommonAPI.favorite(user, third_post.id)
1817     {:ok, _second_favorite} = CommonAPI.favorite(user, first_post.id)
1818     {:ok, third_favorite} = CommonAPI.favorite(user, second_post.id)
1819
1820     result =
1821       conn
1822       |> get("/api/v1/favourites?limit=1")
1823
1824     assert [%{"id" => post_id}] = json_response_and_validate_schema(result, 200)
1825     assert post_id == second_post.id
1826
1827     # Using the header for pagination works correctly
1828     [next, _] = get_resp_header(result, "link") |> hd() |> String.split(", ")
1829     [_, max_id] = Regex.run(~r/max_id=([^&]+)/, next)
1830
1831     assert max_id == third_favorite.id
1832
1833     result =
1834       conn
1835       |> get("/api/v1/favourites?max_id=#{max_id}")
1836
1837     assert [%{"id" => first_post_id}, %{"id" => third_post_id}] =
1838              json_response_and_validate_schema(result, 200)
1839
1840     assert first_post_id == first_post.id
1841     assert third_post_id == third_post.id
1842   end
1843
1844   test "returns the favorites of a user" do
1845     %{user: user, conn: conn} = oauth_access(["read:favourites"])
1846     other_user = insert(:user)
1847
1848     {:ok, _} = CommonAPI.post(other_user, %{status: "bla"})
1849     {:ok, activity} = CommonAPI.post(other_user, %{status: "trees are happy"})
1850
1851     {:ok, last_like} = CommonAPI.favorite(user, activity.id)
1852
1853     first_conn = get(conn, "/api/v1/favourites")
1854
1855     assert [status] = json_response_and_validate_schema(first_conn, 200)
1856     assert status["id"] == to_string(activity.id)
1857
1858     assert [{"link", _link_header}] =
1859              Enum.filter(first_conn.resp_headers, fn element -> match?({"link", _}, element) end)
1860
1861     # Honours query params
1862     {:ok, second_activity} =
1863       CommonAPI.post(other_user, %{
1864         status: "Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful."
1865       })
1866
1867     {:ok, _} = CommonAPI.favorite(user, second_activity.id)
1868
1869     second_conn = get(conn, "/api/v1/favourites?since_id=#{last_like.id}")
1870
1871     assert [second_status] = json_response_and_validate_schema(second_conn, 200)
1872     assert second_status["id"] == to_string(second_activity.id)
1873
1874     third_conn = get(conn, "/api/v1/favourites?limit=0")
1875
1876     assert [] = json_response_and_validate_schema(third_conn, 200)
1877   end
1878
1879   test "expires_at is nil for another user" do
1880     %{conn: conn, user: user} = oauth_access(["read:statuses"])
1881     expires_at = DateTime.add(DateTime.utc_now(), 1_000_000)
1882     {:ok, activity} = CommonAPI.post(user, %{status: "foobar", expires_in: 1_000_000})
1883
1884     assert %{"pleroma" => %{"expires_at" => a_expires_at}} =
1885              conn
1886              |> get("/api/v1/statuses/#{activity.id}")
1887              |> json_response_and_validate_schema(:ok)
1888
1889     {:ok, a_expires_at, 0} = DateTime.from_iso8601(a_expires_at)
1890     assert DateTime.diff(expires_at, a_expires_at) == 0
1891
1892     %{conn: conn} = oauth_access(["read:statuses"])
1893
1894     assert %{"pleroma" => %{"expires_at" => nil}} =
1895              conn
1896              |> get("/api/v1/statuses/#{activity.id}")
1897              |> json_response_and_validate_schema(:ok)
1898   end
1899
1900   describe "local-only statuses" do
1901     test "posting a local only status" do
1902       %{user: _user, conn: conn} = oauth_access(["write:statuses"])
1903
1904       conn_one =
1905         conn
1906         |> put_req_header("content-type", "application/json")
1907         |> post("/api/v1/statuses", %{
1908           "status" => "cofe",
1909           "visibility" => "local"
1910         })
1911
1912       local = Utils.as_local_public()
1913
1914       assert %{"content" => "cofe", "id" => id, "visibility" => "local"} =
1915                json_response_and_validate_schema(conn_one, 200)
1916
1917       assert %Activity{id: ^id, data: %{"to" => [^local]}} = Activity.get_by_id(id)
1918     end
1919
1920     test "other users can read local-only posts" do
1921       user = insert(:user)
1922       %{user: _reader, conn: conn} = oauth_access(["read:statuses"])
1923
1924       {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
1925
1926       received =
1927         conn
1928         |> get("/api/v1/statuses/#{activity.id}")
1929         |> json_response_and_validate_schema(:ok)
1930
1931       assert received["id"] == activity.id
1932     end
1933
1934     test "anonymous users cannot see local-only posts" do
1935       user = insert(:user)
1936
1937       {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
1938
1939       _received =
1940         build_conn()
1941         |> get("/api/v1/statuses/#{activity.id}")
1942         |> json_response_and_validate_schema(:not_found)
1943     end
1944   end
1945
1946   describe "muted reactions" do
1947     test "index" do
1948       %{conn: conn, user: user} = oauth_access(["read:statuses"])
1949
1950       other_user = insert(:user)
1951       {:ok, activity} = CommonAPI.post(user, %{status: "test"})
1952
1953       {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
1954       User.mute(user, other_user)
1955
1956       result =
1957         conn
1958         |> get("/api/v1/statuses/?ids[]=#{activity.id}")
1959         |> json_response_and_validate_schema(200)
1960
1961       assert [
1962                %{
1963                  "pleroma" => %{
1964                    "emoji_reactions" => []
1965                  }
1966                }
1967              ] = result
1968
1969       result =
1970         conn
1971         |> get("/api/v1/statuses/?ids[]=#{activity.id}&with_muted=true")
1972         |> json_response_and_validate_schema(200)
1973
1974       assert [
1975                %{
1976                  "pleroma" => %{
1977                    "emoji_reactions" => [%{"count" => 1, "me" => false, "name" => "🎅"}]
1978                  }
1979                }
1980              ] = result
1981     end
1982
1983     test "show" do
1984       # %{conn: conn, user: user, token: token} = oauth_access(["read:statuses"])
1985       %{conn: conn, user: user, token: _token} = oauth_access(["read:statuses"])
1986
1987       other_user = insert(:user)
1988       {:ok, activity} = CommonAPI.post(user, %{status: "test"})
1989
1990       {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
1991       User.mute(user, other_user)
1992
1993       result =
1994         conn
1995         |> get("/api/v1/statuses/#{activity.id}")
1996         |> json_response_and_validate_schema(200)
1997
1998       assert %{
1999                "pleroma" => %{
2000                  "emoji_reactions" => []
2001                }
2002              } = result
2003
2004       result =
2005         conn
2006         |> get("/api/v1/statuses/#{activity.id}?with_muted=true")
2007         |> json_response_and_validate_schema(200)
2008
2009       assert %{
2010                "pleroma" => %{
2011                  "emoji_reactions" => [%{"count" => 1, "me" => false, "name" => "🎅"}]
2012                }
2013              } = result
2014     end
2015   end
2016
2017   describe "get status history" do
2018     setup do
2019       %{conn: build_conn()}
2020     end
2021
2022     test "unedited post", %{conn: conn} do
2023       activity = insert(:note_activity)
2024
2025       conn = get(conn, "/api/v1/statuses/#{activity.id}/history")
2026
2027       assert [_] = json_response_and_validate_schema(conn, 200)
2028     end
2029
2030     test "edited post", %{conn: conn} do
2031       note =
2032         insert(
2033           :note,
2034           data: %{
2035             "formerRepresentations" => %{
2036               "type" => "OrderedCollection",
2037               "orderedItems" => [
2038                 %{
2039                   "type" => "Note",
2040                   "content" => "mew mew 2",
2041                   "summary" => "title 2"
2042                 },
2043                 %{
2044                   "type" => "Note",
2045                   "content" => "mew mew 1",
2046                   "summary" => "title 1"
2047                 }
2048               ],
2049               "totalItems" => 2
2050             }
2051           }
2052         )
2053
2054       activity = insert(:note_activity, note: note)
2055
2056       conn = get(conn, "/api/v1/statuses/#{activity.id}/history")
2057
2058       assert [%{"spoiler_text" => "title 1"}, %{"spoiler_text" => "title 2"}, _] =
2059                json_response_and_validate_schema(conn, 200)
2060     end
2061   end
2062
2063   describe "get status source" do
2064     setup do
2065       %{conn: build_conn()}
2066     end
2067
2068     test "it returns the source", %{conn: conn} do
2069       user = insert(:user)
2070
2071       {:ok, activity} = CommonAPI.post(user, %{status: "mew mew #abc", spoiler_text: "#def"})
2072
2073       conn = get(conn, "/api/v1/statuses/#{activity.id}/source")
2074
2075       id = activity.id
2076
2077       assert %{"id" => ^id, "text" => "mew mew #abc", "spoiler_text" => "#def"} =
2078                json_response_and_validate_schema(conn, 200)
2079     end
2080   end
2081
2082   describe "update status" do
2083     setup do
2084       oauth_access(["write:statuses"])
2085     end
2086
2087     test "it updates the status" do
2088       %{conn: conn, user: user} = oauth_access(["write:statuses", "read:statuses"])
2089
2090       {:ok, activity} = CommonAPI.post(user, %{status: "mew mew #abc", spoiler_text: "#def"})
2091
2092       conn
2093       |> get("/api/v1/statuses/#{activity.id}")
2094       |> json_response_and_validate_schema(200)
2095
2096       response =
2097         conn
2098         |> put_req_header("content-type", "application/json")
2099         |> put("/api/v1/statuses/#{activity.id}", %{
2100           "status" => "edited",
2101           "spoiler_text" => "lol"
2102         })
2103         |> json_response_and_validate_schema(200)
2104
2105       assert response["content"] == "edited"
2106       assert response["spoiler_text"] == "lol"
2107
2108       response =
2109         conn
2110         |> get("/api/v1/statuses/#{activity.id}")
2111         |> json_response_and_validate_schema(200)
2112
2113       assert response["content"] == "edited"
2114       assert response["spoiler_text"] == "lol"
2115     end
2116
2117     test "it updates the attachments", %{conn: conn, user: user} do
2118       attachment = insert(:attachment, user: user)
2119       attachment_id = to_string(attachment.id)
2120
2121       {:ok, activity} = CommonAPI.post(user, %{status: "mew mew #abc", spoiler_text: "#def"})
2122
2123       response =
2124         conn
2125         |> put_req_header("content-type", "application/json")
2126         |> put("/api/v1/statuses/#{activity.id}", %{
2127           "status" => "mew mew #abc",
2128           "spoiler_text" => "#def",
2129           "media_ids" => [attachment_id]
2130         })
2131         |> json_response_and_validate_schema(200)
2132
2133       assert [%{"id" => ^attachment_id}] = response["media_attachments"]
2134     end
2135
2136     test "it does not update visibility", %{conn: conn, user: user} do
2137       {:ok, activity} =
2138         CommonAPI.post(user, %{
2139           status: "mew mew #abc",
2140           spoiler_text: "#def",
2141           visibility: "private"
2142         })
2143
2144       response =
2145         conn
2146         |> put_req_header("content-type", "application/json")
2147         |> put("/api/v1/statuses/#{activity.id}", %{
2148           "status" => "edited",
2149           "spoiler_text" => "lol"
2150         })
2151         |> json_response_and_validate_schema(200)
2152
2153       assert response["visibility"] == "private"
2154     end
2155
2156     test "it refuses to update when original post is not by the user", %{conn: conn} do
2157       another_user = insert(:user)
2158
2159       {:ok, activity} =
2160         CommonAPI.post(another_user, %{status: "mew mew #abc", spoiler_text: "#def"})
2161
2162       conn
2163       |> put_req_header("content-type", "application/json")
2164       |> put("/api/v1/statuses/#{activity.id}", %{
2165         "status" => "edited",
2166         "spoiler_text" => "lol"
2167       })
2168       |> json_response_and_validate_schema(:forbidden)
2169     end
2170
2171     test "it returns 404 if the user cannot see the post", %{conn: conn} do
2172       another_user = insert(:user)
2173
2174       {:ok, activity} =
2175         CommonAPI.post(another_user, %{
2176           status: "mew mew #abc",
2177           spoiler_text: "#def",
2178           visibility: "private"
2179         })
2180
2181       conn
2182       |> put_req_header("content-type", "application/json")
2183       |> put("/api/v1/statuses/#{activity.id}", %{
2184         "status" => "edited",
2185         "spoiler_text" => "lol"
2186       })
2187       |> json_response_and_validate_schema(:not_found)
2188     end
2189   end
2190 end