Merge branch 'split-masto-api/polls' into 'develop'
[akkoma] / test / web / mastodon_api / mastodon_api_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.MastodonAPIControllerTest do
6 use Pleroma.Web.ConnCase
7
8 alias Ecto.Changeset
9 alias Pleroma.Config
10 alias Pleroma.Notification
11 alias Pleroma.Object
12 alias Pleroma.Repo
13 alias Pleroma.Tests.ObanHelpers
14 alias Pleroma.User
15 alias Pleroma.Web.ActivityPub.ActivityPub
16 alias Pleroma.Web.CommonAPI
17 alias Pleroma.Web.OAuth.App
18 alias Pleroma.Web.Push
19
20 import ExUnit.CaptureLog
21 import Pleroma.Factory
22 import Swoosh.TestAssertions
23 import Tesla.Mock
24
25 setup do
26 mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
27 :ok
28 end
29
30 clear_config([:instance, :public])
31 clear_config([:rich_media, :enabled])
32
33 test "apps/verify_credentials", %{conn: conn} do
34 token = insert(:oauth_token)
35
36 conn =
37 conn
38 |> assign(:user, token.user)
39 |> assign(:token, token)
40 |> get("/api/v1/apps/verify_credentials")
41
42 app = Repo.preload(token, :app).app
43
44 expected = %{
45 "name" => app.client_name,
46 "website" => app.website,
47 "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
48 }
49
50 assert expected == json_response(conn, 200)
51 end
52
53 test "creates an oauth app", %{conn: conn} do
54 user = insert(:user)
55 app_attrs = build(:oauth_app)
56
57 conn =
58 conn
59 |> assign(:user, user)
60 |> post("/api/v1/apps", %{
61 client_name: app_attrs.client_name,
62 redirect_uris: app_attrs.redirect_uris
63 })
64
65 [app] = Repo.all(App)
66
67 expected = %{
68 "name" => app.client_name,
69 "website" => app.website,
70 "client_id" => app.client_id,
71 "client_secret" => app.client_secret,
72 "id" => app.id |> to_string(),
73 "redirect_uri" => app.redirect_uris,
74 "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
75 }
76
77 assert expected == json_response(conn, 200)
78 end
79
80 describe "media upload" do
81 setup do
82 user = insert(:user)
83
84 conn =
85 build_conn()
86 |> assign(:user, user)
87
88 image = %Plug.Upload{
89 content_type: "image/jpg",
90 path: Path.absname("test/fixtures/image.jpg"),
91 filename: "an_image.jpg"
92 }
93
94 [conn: conn, image: image]
95 end
96
97 clear_config([:media_proxy])
98 clear_config([Pleroma.Upload])
99
100 test "returns uploaded image", %{conn: conn, image: image} do
101 desc = "Description of the image"
102
103 media =
104 conn
105 |> post("/api/v1/media", %{"file" => image, "description" => desc})
106 |> json_response(:ok)
107
108 assert media["type"] == "image"
109 assert media["description"] == desc
110 assert media["id"]
111
112 object = Repo.get(Object, media["id"])
113 assert object.data["actor"] == User.ap_id(conn.assigns[:user])
114 end
115 end
116
117 test "getting a list of mutes", %{conn: conn} do
118 user = insert(:user)
119 other_user = insert(:user)
120
121 {:ok, user} = User.mute(user, other_user)
122
123 conn =
124 conn
125 |> assign(:user, user)
126 |> get("/api/v1/mutes")
127
128 other_user_id = to_string(other_user.id)
129 assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
130 end
131
132 test "getting a list of blocks", %{conn: conn} do
133 user = insert(:user)
134 other_user = insert(:user)
135
136 {:ok, user} = User.block(user, other_user)
137
138 conn =
139 conn
140 |> assign(:user, user)
141 |> get("/api/v1/blocks")
142
143 other_user_id = to_string(other_user.id)
144 assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
145 end
146
147 test "unimplemented follow_requests, blocks, domain blocks" do
148 user = insert(:user)
149
150 ["blocks", "domain_blocks", "follow_requests"]
151 |> Enum.each(fn endpoint ->
152 conn =
153 build_conn()
154 |> assign(:user, user)
155 |> get("/api/v1/#{endpoint}")
156
157 assert [] = json_response(conn, 200)
158 end)
159 end
160
161 test "returns the favorites of a user", %{conn: conn} do
162 user = insert(:user)
163 other_user = insert(:user)
164
165 {:ok, _} = CommonAPI.post(other_user, %{"status" => "bla"})
166 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "traps are happy"})
167
168 {:ok, _, _} = CommonAPI.favorite(activity.id, user)
169
170 first_conn =
171 conn
172 |> assign(:user, user)
173 |> get("/api/v1/favourites")
174
175 assert [status] = json_response(first_conn, 200)
176 assert status["id"] == to_string(activity.id)
177
178 assert [{"link", _link_header}] =
179 Enum.filter(first_conn.resp_headers, fn element -> match?({"link", _}, element) end)
180
181 # Honours query params
182 {:ok, second_activity} =
183 CommonAPI.post(other_user, %{
184 "status" =>
185 "Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful."
186 })
187
188 {:ok, _, _} = CommonAPI.favorite(second_activity.id, user)
189
190 last_like = status["id"]
191
192 second_conn =
193 conn
194 |> assign(:user, user)
195 |> get("/api/v1/favourites?since_id=#{last_like}")
196
197 assert [second_status] = json_response(second_conn, 200)
198 assert second_status["id"] == to_string(second_activity.id)
199
200 third_conn =
201 conn
202 |> assign(:user, user)
203 |> get("/api/v1/favourites?limit=0")
204
205 assert [] = json_response(third_conn, 200)
206 end
207
208 test "get instance information", %{conn: conn} do
209 conn = get(conn, "/api/v1/instance")
210 assert result = json_response(conn, 200)
211
212 email = Config.get([:instance, :email])
213 # Note: not checking for "max_toot_chars" since it's optional
214 assert %{
215 "uri" => _,
216 "title" => _,
217 "description" => _,
218 "version" => _,
219 "email" => from_config_email,
220 "urls" => %{
221 "streaming_api" => _
222 },
223 "stats" => _,
224 "thumbnail" => _,
225 "languages" => _,
226 "registrations" => _,
227 "poll_limits" => _
228 } = result
229
230 assert email == from_config_email
231 end
232
233 test "get instance stats", %{conn: conn} do
234 user = insert(:user, %{local: true})
235
236 user2 = insert(:user, %{local: true})
237 {:ok, _user2} = User.deactivate(user2, !user2.info.deactivated)
238
239 insert(:user, %{local: false, nickname: "u@peer1.com"})
240 insert(:user, %{local: false, nickname: "u@peer2.com"})
241
242 {:ok, _} = CommonAPI.post(user, %{"status" => "cofe"})
243
244 # Stats should count users with missing or nil `info.deactivated` value
245
246 {:ok, _user} =
247 user.id
248 |> User.get_cached_by_id()
249 |> User.update_info(&Changeset.change(&1, %{deactivated: nil}))
250
251 Pleroma.Stats.force_update()
252
253 conn = get(conn, "/api/v1/instance")
254
255 assert result = json_response(conn, 200)
256
257 stats = result["stats"]
258
259 assert stats
260 assert stats["user_count"] == 1
261 assert stats["status_count"] == 1
262 assert stats["domain_count"] == 2
263 end
264
265 test "get peers", %{conn: conn} do
266 insert(:user, %{local: false, nickname: "u@peer1.com"})
267 insert(:user, %{local: false, nickname: "u@peer2.com"})
268
269 Pleroma.Stats.force_update()
270
271 conn = get(conn, "/api/v1/instance/peers")
272
273 assert result = json_response(conn, 200)
274
275 assert ["peer1.com", "peer2.com"] == Enum.sort(result)
276 end
277
278 test "put settings", %{conn: conn} do
279 user = insert(:user)
280
281 conn =
282 conn
283 |> assign(:user, user)
284 |> put("/api/web/settings", %{"data" => %{"programming" => "socks"}})
285
286 assert _result = json_response(conn, 200)
287
288 user = User.get_cached_by_ap_id(user.ap_id)
289 assert user.info.settings == %{"programming" => "socks"}
290 end
291
292 describe "link headers" do
293 test "preserves parameters in link headers", %{conn: conn} do
294 user = insert(:user)
295 other_user = insert(:user)
296
297 {:ok, activity1} =
298 CommonAPI.post(other_user, %{
299 "status" => "hi @#{user.nickname}",
300 "visibility" => "public"
301 })
302
303 {:ok, activity2} =
304 CommonAPI.post(other_user, %{
305 "status" => "hi @#{user.nickname}",
306 "visibility" => "public"
307 })
308
309 notification1 = Repo.get_by(Notification, activity_id: activity1.id)
310 notification2 = Repo.get_by(Notification, activity_id: activity2.id)
311
312 conn =
313 conn
314 |> assign(:user, user)
315 |> get("/api/v1/notifications", %{media_only: true})
316
317 assert [link_header] = get_resp_header(conn, "link")
318 assert link_header =~ ~r/media_only=true/
319 assert link_header =~ ~r/min_id=#{notification2.id}/
320 assert link_header =~ ~r/max_id=#{notification1.id}/
321 end
322 end
323
324 describe "custom emoji" do
325 test "with tags", %{conn: conn} do
326 [emoji | _body] =
327 conn
328 |> get("/api/v1/custom_emojis")
329 |> json_response(200)
330
331 assert Map.has_key?(emoji, "shortcode")
332 assert Map.has_key?(emoji, "static_url")
333 assert Map.has_key?(emoji, "tags")
334 assert is_list(emoji["tags"])
335 assert Map.has_key?(emoji, "category")
336 assert Map.has_key?(emoji, "url")
337 assert Map.has_key?(emoji, "visible_in_picker")
338 end
339 end
340
341 describe "index/2 redirections" do
342 setup %{conn: conn} do
343 session_opts = [
344 store: :cookie,
345 key: "_test",
346 signing_salt: "cooldude"
347 ]
348
349 conn =
350 conn
351 |> Plug.Session.call(Plug.Session.init(session_opts))
352 |> fetch_session()
353
354 test_path = "/web/statuses/test"
355 %{conn: conn, path: test_path}
356 end
357
358 test "redirects not logged-in users to the login page", %{conn: conn, path: path} do
359 conn = get(conn, path)
360
361 assert conn.status == 302
362 assert redirected_to(conn) == "/web/login"
363 end
364
365 test "redirects not logged-in users to the login page on private instances", %{
366 conn: conn,
367 path: path
368 } do
369 Config.put([:instance, :public], false)
370
371 conn = get(conn, path)
372
373 assert conn.status == 302
374 assert redirected_to(conn) == "/web/login"
375 end
376
377 test "does not redirect logged in users to the login page", %{conn: conn, path: path} do
378 token = insert(:oauth_token)
379
380 conn =
381 conn
382 |> assign(:user, token.user)
383 |> put_session(:oauth_token, token.token)
384 |> get(path)
385
386 assert conn.status == 200
387 end
388
389 test "saves referer path to session", %{conn: conn, path: path} do
390 conn = get(conn, path)
391 return_to = Plug.Conn.get_session(conn, :return_to)
392
393 assert return_to == path
394 end
395
396 test "redirects to the saved path after log in", %{conn: conn, path: path} do
397 app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".")
398 auth = insert(:oauth_authorization, app: app)
399
400 conn =
401 conn
402 |> put_session(:return_to, path)
403 |> get("/web/login", %{code: auth.token})
404
405 assert conn.status == 302
406 assert redirected_to(conn) == path
407 end
408
409 test "redirects to the getting-started page when referer is not present", %{conn: conn} do
410 app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".")
411 auth = insert(:oauth_authorization, app: app)
412
413 conn = get(conn, "/web/login", %{code: auth.token})
414
415 assert conn.status == 302
416 assert redirected_to(conn) == "/web/getting-started"
417 end
418 end
419
420 describe "POST /auth/password, with valid parameters" do
421 setup %{conn: conn} do
422 user = insert(:user)
423 conn = post(conn, "/auth/password?email=#{user.email}")
424 %{conn: conn, user: user}
425 end
426
427 test "it returns 204", %{conn: conn} do
428 assert json_response(conn, :no_content)
429 end
430
431 test "it creates a PasswordResetToken record for user", %{user: user} do
432 token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id)
433 assert token_record
434 end
435
436 test "it sends an email to user", %{user: user} do
437 ObanHelpers.perform_all()
438 token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id)
439
440 email = Pleroma.Emails.UserEmail.password_reset_email(user, token_record.token)
441 notify_email = Config.get([:instance, :notify_email])
442 instance_name = Config.get([:instance, :name])
443
444 assert_email_sent(
445 from: {instance_name, notify_email},
446 to: {user.name, user.email},
447 html_body: email.html_body
448 )
449 end
450 end
451
452 describe "POST /auth/password, with invalid parameters" do
453 setup do
454 user = insert(:user)
455 {:ok, user: user}
456 end
457
458 test "it returns 404 when user is not found", %{conn: conn, user: user} do
459 conn = post(conn, "/auth/password?email=nonexisting_#{user.email}")
460 assert conn.status == 404
461 assert conn.resp_body == ""
462 end
463
464 test "it returns 400 when user is not local", %{conn: conn, user: user} do
465 {:ok, user} = Repo.update(Changeset.change(user, local: false))
466 conn = post(conn, "/auth/password?email=#{user.email}")
467 assert conn.status == 400
468 assert conn.resp_body == ""
469 end
470 end
471
472 describe "GET /api/v1/suggestions" do
473 setup do
474 user = insert(:user)
475 other_user = insert(:user)
476 host = Config.get([Pleroma.Web.Endpoint, :url, :host])
477 url500 = "http://test500?#{host}&#{user.nickname}"
478 url200 = "http://test200?#{host}&#{user.nickname}"
479
480 mock(fn
481 %{method: :get, url: ^url500} ->
482 %Tesla.Env{status: 500, body: "bad request"}
483
484 %{method: :get, url: ^url200} ->
485 %Tesla.Env{
486 status: 200,
487 body:
488 ~s([{"acct":"yj455","avatar":"https://social.heldscal.la/avatar/201.jpeg","avatar_static":"https://social.heldscal.la/avatar/s/201.jpeg"}, {"acct":"#{
489 other_user.ap_id
490 }","avatar":"https://social.heldscal.la/avatar/202.jpeg","avatar_static":"https://social.heldscal.la/avatar/s/202.jpeg"}])
491 }
492 end)
493
494 [user: user, other_user: other_user]
495 end
496
497 clear_config(:suggestions)
498
499 test "returns empty result when suggestions disabled", %{conn: conn, user: user} do
500 Config.put([:suggestions, :enabled], false)
501
502 res =
503 conn
504 |> assign(:user, user)
505 |> get("/api/v1/suggestions")
506 |> json_response(200)
507
508 assert res == []
509 end
510
511 test "returns error", %{conn: conn, user: user} do
512 Config.put([:suggestions, :enabled], true)
513 Config.put([:suggestions, :third_party_engine], "http://test500?{{host}}&{{user}}")
514
515 assert capture_log(fn ->
516 res =
517 conn
518 |> assign(:user, user)
519 |> get("/api/v1/suggestions")
520 |> json_response(500)
521
522 assert res == "Something went wrong"
523 end) =~ "Could not retrieve suggestions"
524 end
525
526 test "returns suggestions", %{conn: conn, user: user, other_user: other_user} do
527 Config.put([:suggestions, :enabled], true)
528 Config.put([:suggestions, :third_party_engine], "http://test200?{{host}}&{{user}}")
529
530 res =
531 conn
532 |> assign(:user, user)
533 |> get("/api/v1/suggestions")
534 |> json_response(200)
535
536 assert res == [
537 %{
538 "acct" => "yj455",
539 "avatar" => "https://social.heldscal.la/avatar/201.jpeg",
540 "avatar_static" => "https://social.heldscal.la/avatar/s/201.jpeg",
541 "id" => 0
542 },
543 %{
544 "acct" => other_user.ap_id,
545 "avatar" => "https://social.heldscal.la/avatar/202.jpeg",
546 "avatar_static" => "https://social.heldscal.la/avatar/s/202.jpeg",
547 "id" => other_user.id
548 }
549 ]
550 end
551 end
552
553 describe "PUT /api/v1/media/:id" do
554 setup do
555 actor = insert(:user)
556
557 file = %Plug.Upload{
558 content_type: "image/jpg",
559 path: Path.absname("test/fixtures/image.jpg"),
560 filename: "an_image.jpg"
561 }
562
563 {:ok, %Object{} = object} =
564 ActivityPub.upload(
565 file,
566 actor: User.ap_id(actor),
567 description: "test-m"
568 )
569
570 [actor: actor, object: object]
571 end
572
573 test "updates name of media", %{conn: conn, actor: actor, object: object} do
574 media =
575 conn
576 |> assign(:user, actor)
577 |> put("/api/v1/media/#{object.id}", %{"description" => "test-media"})
578 |> json_response(:ok)
579
580 assert media["description"] == "test-media"
581 assert refresh_record(object).data["name"] == "test-media"
582 end
583
584 test "returns error wheb request is bad", %{conn: conn, actor: actor, object: object} do
585 media =
586 conn
587 |> assign(:user, actor)
588 |> put("/api/v1/media/#{object.id}", %{})
589 |> json_response(400)
590
591 assert media == %{"error" => "bad_request"}
592 end
593 end
594
595 describe "DELETE /auth/sign_out" do
596 test "redirect to root page", %{conn: conn} do
597 user = insert(:user)
598
599 conn =
600 conn
601 |> assign(:user, user)
602 |> delete("/auth/sign_out")
603
604 assert conn.status == 302
605 assert redirected_to(conn) == "/"
606 end
607 end
608
609 describe "empty_array, stubs for mastodon api" do
610 test "GET /api/v1/accounts/:id/identity_proofs", %{conn: conn} do
611 user = insert(:user)
612
613 res =
614 conn
615 |> assign(:user, user)
616 |> get("/api/v1/accounts/#{user.id}/identity_proofs")
617 |> json_response(200)
618
619 assert res == []
620 end
621
622 test "GET /api/v1/endorsements", %{conn: conn} do
623 user = insert(:user)
624
625 res =
626 conn
627 |> assign(:user, user)
628 |> get("/api/v1/endorsements")
629 |> json_response(200)
630
631 assert res == []
632 end
633
634 test "GET /api/v1/trends", %{conn: conn} do
635 user = insert(:user)
636
637 res =
638 conn
639 |> assign(:user, user)
640 |> get("/api/v1/trends")
641 |> json_response(200)
642
643 assert res == []
644 end
645 end
646 end