d9bcbf5a9e43a3f996e036f5e110ac7eb3003786
[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.Activity
10 alias Pleroma.Notification
11 alias Pleroma.Object
12 alias Pleroma.Repo
13 alias Pleroma.User
14 alias Pleroma.Web.ActivityPub.ActivityPub
15 alias Pleroma.Web.CommonAPI
16 alias Pleroma.Web.MastodonAPI.FilterView
17 alias Pleroma.Web.OAuth.App
18 alias Pleroma.Web.OStatus
19 alias Pleroma.Web.Push
20 alias Pleroma.Web.TwitterAPI.TwitterAPI
21 import Pleroma.Factory
22 import ExUnit.CaptureLog
23 import Tesla.Mock
24
25 setup do
26 mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
27 :ok
28 end
29
30 test "the home timeline", %{conn: conn} do
31 user = insert(:user)
32 following = insert(:user)
33
34 {:ok, _activity} = TwitterAPI.create_status(following, %{"status" => "test"})
35
36 conn =
37 conn
38 |> assign(:user, user)
39 |> get("/api/v1/timelines/home")
40
41 assert Enum.empty?(json_response(conn, 200))
42
43 {:ok, user} = User.follow(user, following)
44
45 conn =
46 build_conn()
47 |> assign(:user, user)
48 |> get("/api/v1/timelines/home")
49
50 assert [%{"content" => "test"}] = json_response(conn, 200)
51 end
52
53 test "the public timeline", %{conn: conn} do
54 following = insert(:user)
55
56 capture_log(fn ->
57 {:ok, _activity} = TwitterAPI.create_status(following, %{"status" => "test"})
58
59 {:ok, [_activity]} =
60 OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873")
61
62 conn =
63 conn
64 |> get("/api/v1/timelines/public", %{"local" => "False"})
65
66 assert length(json_response(conn, 200)) == 2
67
68 conn =
69 build_conn()
70 |> get("/api/v1/timelines/public", %{"local" => "True"})
71
72 assert [%{"content" => "test"}] = json_response(conn, 200)
73
74 conn =
75 build_conn()
76 |> get("/api/v1/timelines/public", %{"local" => "1"})
77
78 assert [%{"content" => "test"}] = json_response(conn, 200)
79 end)
80 end
81
82 test "posting a status", %{conn: conn} do
83 user = insert(:user)
84
85 idempotency_key = "Pikachu rocks!"
86
87 conn_one =
88 conn
89 |> assign(:user, user)
90 |> put_req_header("idempotency-key", idempotency_key)
91 |> post("/api/v1/statuses", %{
92 "status" => "cofe",
93 "spoiler_text" => "2hu",
94 "sensitive" => "false"
95 })
96
97 {:ok, ttl} = Cachex.ttl(:idempotency_cache, idempotency_key)
98 # Six hours
99 assert ttl > :timer.seconds(6 * 60 * 60 - 1)
100
101 assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} =
102 json_response(conn_one, 200)
103
104 assert Repo.get(Activity, id)
105
106 conn_two =
107 conn
108 |> assign(:user, user)
109 |> put_req_header("idempotency-key", idempotency_key)
110 |> post("/api/v1/statuses", %{
111 "status" => "cofe",
112 "spoiler_text" => "2hu",
113 "sensitive" => "false"
114 })
115
116 assert %{"id" => second_id} = json_response(conn_two, 200)
117
118 assert id == second_id
119
120 conn_three =
121 conn
122 |> assign(:user, user)
123 |> post("/api/v1/statuses", %{
124 "status" => "cofe",
125 "spoiler_text" => "2hu",
126 "sensitive" => "false"
127 })
128
129 assert %{"id" => third_id} = json_response(conn_three, 200)
130
131 refute id == third_id
132 end
133
134 test "posting a sensitive status", %{conn: conn} do
135 user = insert(:user)
136
137 conn =
138 conn
139 |> assign(:user, user)
140 |> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true})
141
142 assert %{"content" => "cofe", "id" => id, "sensitive" => true} = json_response(conn, 200)
143 assert Repo.get(Activity, id)
144 end
145
146 test "posting a status with OGP link preview", %{conn: conn} do
147 Pleroma.Config.put([:rich_media, :enabled], true)
148 user = insert(:user)
149
150 conn =
151 conn
152 |> assign(:user, user)
153 |> post("/api/v1/statuses", %{
154 "status" => "http://example.com/ogp"
155 })
156
157 assert %{"id" => id, "card" => %{"title" => "The Rock"}} = json_response(conn, 200)
158 assert Repo.get(Activity, id)
159 Pleroma.Config.put([:rich_media, :enabled], false)
160 end
161
162 test "posting a direct status", %{conn: conn} do
163 user1 = insert(:user)
164 user2 = insert(:user)
165 content = "direct cofe @#{user2.nickname}"
166
167 conn =
168 conn
169 |> assign(:user, user1)
170 |> post("api/v1/statuses", %{"status" => content, "visibility" => "direct"})
171
172 assert %{"id" => id, "visibility" => "direct"} = json_response(conn, 200)
173 assert activity = Repo.get(Activity, id)
174 assert activity.recipients == [user2.ap_id, user1.ap_id]
175 assert activity.data["to"] == [user2.ap_id]
176 assert activity.data["cc"] == []
177 end
178
179 test "direct timeline", %{conn: conn} do
180 user_one = insert(:user)
181 user_two = insert(:user)
182
183 {:ok, user_two} = User.follow(user_two, user_one)
184
185 {:ok, direct} =
186 CommonAPI.post(user_one, %{
187 "status" => "Hi @#{user_two.nickname}!",
188 "visibility" => "direct"
189 })
190
191 {:ok, _follower_only} =
192 CommonAPI.post(user_one, %{
193 "status" => "Hi @#{user_two.nickname}!",
194 "visibility" => "private"
195 })
196
197 # Only direct should be visible here
198 res_conn =
199 conn
200 |> assign(:user, user_two)
201 |> get("api/v1/timelines/direct")
202
203 [status] = json_response(res_conn, 200)
204
205 assert %{"visibility" => "direct"} = status
206 assert status["url"] != direct.data["id"]
207
208 # User should be able to see his own direct message
209 res_conn =
210 build_conn()
211 |> assign(:user, user_one)
212 |> get("api/v1/timelines/direct")
213
214 [status] = json_response(res_conn, 200)
215
216 assert %{"visibility" => "direct"} = status
217
218 # Both should be visible here
219 res_conn =
220 conn
221 |> assign(:user, user_two)
222 |> get("api/v1/timelines/home")
223
224 [_s1, _s2] = json_response(res_conn, 200)
225
226 # Test pagination
227 Enum.each(1..20, fn _ ->
228 {:ok, _} =
229 CommonAPI.post(user_one, %{
230 "status" => "Hi @#{user_two.nickname}!",
231 "visibility" => "direct"
232 })
233 end)
234
235 res_conn =
236 conn
237 |> assign(:user, user_two)
238 |> get("api/v1/timelines/direct")
239
240 statuses = json_response(res_conn, 200)
241 assert length(statuses) == 20
242
243 res_conn =
244 conn
245 |> assign(:user, user_two)
246 |> get("api/v1/timelines/direct", %{max_id: List.last(statuses)["id"]})
247
248 [status] = json_response(res_conn, 200)
249
250 assert status["url"] != direct.data["id"]
251 end
252
253 test "doesn't include DMs from blocked users", %{conn: conn} do
254 blocker = insert(:user)
255 blocked = insert(:user)
256 user = insert(:user)
257 {:ok, blocker} = User.block(blocker, blocked)
258
259 {:ok, _blocked_direct} =
260 CommonAPI.post(blocked, %{
261 "status" => "Hi @#{blocker.nickname}!",
262 "visibility" => "direct"
263 })
264
265 {:ok, direct} =
266 CommonAPI.post(user, %{
267 "status" => "Hi @#{blocker.nickname}!",
268 "visibility" => "direct"
269 })
270
271 res_conn =
272 conn
273 |> assign(:user, user)
274 |> get("api/v1/timelines/direct")
275
276 [status] = json_response(res_conn, 200)
277 assert status["id"] == direct.id
278 end
279
280 test "replying to a status", %{conn: conn} do
281 user = insert(:user)
282
283 {:ok, replied_to} = TwitterAPI.create_status(user, %{"status" => "cofe"})
284
285 conn =
286 conn
287 |> assign(:user, user)
288 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
289
290 assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
291
292 activity = Repo.get(Activity, id)
293
294 assert activity.data["context"] == replied_to.data["context"]
295 assert activity.data["object"]["inReplyToStatusId"] == replied_to.id
296 end
297
298 test "posting a status with an invalid in_reply_to_id", %{conn: conn} do
299 user = insert(:user)
300
301 conn =
302 conn
303 |> assign(:user, user)
304 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => ""})
305
306 assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
307
308 activity = Repo.get(Activity, id)
309
310 assert activity
311 end
312
313 test "verify_credentials", %{conn: conn} do
314 user = insert(:user)
315
316 conn =
317 conn
318 |> assign(:user, user)
319 |> get("/api/v1/accounts/verify_credentials")
320
321 assert %{"id" => id, "source" => %{"privacy" => "public"}} = json_response(conn, 200)
322 assert id == to_string(user.id)
323 end
324
325 test "verify_credentials default scope unlisted", %{conn: conn} do
326 user = insert(:user, %{info: %Pleroma.User.Info{default_scope: "unlisted"}})
327
328 conn =
329 conn
330 |> assign(:user, user)
331 |> get("/api/v1/accounts/verify_credentials")
332
333 assert %{"id" => id, "source" => %{"privacy" => "unlisted"}} = json_response(conn, 200)
334 assert id == to_string(user.id)
335 end
336
337 test "apps/verify_credentials", %{conn: conn} do
338 token = insert(:oauth_token)
339
340 conn =
341 conn
342 |> assign(:user, token.user)
343 |> assign(:token, token)
344 |> get("/api/v1/apps/verify_credentials")
345
346 app = Repo.preload(token, :app).app
347
348 expected = %{
349 "name" => app.client_name,
350 "website" => app.website,
351 "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
352 }
353
354 assert expected == json_response(conn, 200)
355 end
356
357 test "creates an oauth app", %{conn: conn} do
358 user = insert(:user)
359 app_attrs = build(:oauth_app)
360
361 conn =
362 conn
363 |> assign(:user, user)
364 |> post("/api/v1/apps", %{
365 client_name: app_attrs.client_name,
366 redirect_uris: app_attrs.redirect_uris
367 })
368
369 [app] = Repo.all(App)
370
371 expected = %{
372 "name" => app.client_name,
373 "website" => app.website,
374 "client_id" => app.client_id,
375 "client_secret" => app.client_secret,
376 "id" => app.id |> to_string(),
377 "redirect_uri" => app.redirect_uris,
378 "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
379 }
380
381 assert expected == json_response(conn, 200)
382 end
383
384 test "get a status", %{conn: conn} do
385 activity = insert(:note_activity)
386
387 conn =
388 conn
389 |> get("/api/v1/statuses/#{activity.id}")
390
391 assert %{"id" => id} = json_response(conn, 200)
392 assert id == to_string(activity.id)
393 end
394
395 describe "deleting a status" do
396 test "when you created it", %{conn: conn} do
397 activity = insert(:note_activity)
398 author = User.get_by_ap_id(activity.data["actor"])
399
400 conn =
401 conn
402 |> assign(:user, author)
403 |> delete("/api/v1/statuses/#{activity.id}")
404
405 assert %{} = json_response(conn, 200)
406
407 refute Repo.get(Activity, activity.id)
408 end
409
410 test "when you didn't create it", %{conn: conn} do
411 activity = insert(:note_activity)
412 user = insert(:user)
413
414 conn =
415 conn
416 |> assign(:user, user)
417 |> delete("/api/v1/statuses/#{activity.id}")
418
419 assert %{"error" => _} = json_response(conn, 403)
420
421 assert Repo.get(Activity, activity.id) == activity
422 end
423
424 test "when you're an admin or moderator", %{conn: conn} do
425 activity1 = insert(:note_activity)
426 activity2 = insert(:note_activity)
427 admin = insert(:user, info: %{is_admin: true})
428 moderator = insert(:user, info: %{is_moderator: true})
429
430 res_conn =
431 conn
432 |> assign(:user, admin)
433 |> delete("/api/v1/statuses/#{activity1.id}")
434
435 assert %{} = json_response(res_conn, 200)
436
437 res_conn =
438 conn
439 |> assign(:user, moderator)
440 |> delete("/api/v1/statuses/#{activity2.id}")
441
442 assert %{} = json_response(res_conn, 200)
443
444 refute Repo.get(Activity, activity1.id)
445 refute Repo.get(Activity, activity2.id)
446 end
447 end
448
449 describe "filters" do
450 test "creating a filter", %{conn: conn} do
451 user = insert(:user)
452
453 filter = %Pleroma.Filter{
454 phrase: "knights",
455 context: ["home"]
456 }
457
458 conn =
459 conn
460 |> assign(:user, user)
461 |> post("/api/v1/filters", %{"phrase" => filter.phrase, context: filter.context})
462
463 assert response = json_response(conn, 200)
464 assert response["phrase"] == filter.phrase
465 assert response["context"] == filter.context
466 assert response["id"] != nil
467 assert response["id"] != ""
468 end
469
470 test "fetching a list of filters", %{conn: conn} do
471 user = insert(:user)
472
473 query_one = %Pleroma.Filter{
474 user_id: user.id,
475 filter_id: 1,
476 phrase: "knights",
477 context: ["home"]
478 }
479
480 query_two = %Pleroma.Filter{
481 user_id: user.id,
482 filter_id: 2,
483 phrase: "who",
484 context: ["home"]
485 }
486
487 {:ok, filter_one} = Pleroma.Filter.create(query_one)
488 {:ok, filter_two} = Pleroma.Filter.create(query_two)
489
490 response =
491 conn
492 |> assign(:user, user)
493 |> get("/api/v1/filters")
494 |> json_response(200)
495
496 assert response ==
497 render_json(
498 FilterView,
499 "filters.json",
500 filters: [filter_two, filter_one]
501 )
502 end
503
504 test "get a filter", %{conn: conn} do
505 user = insert(:user)
506
507 query = %Pleroma.Filter{
508 user_id: user.id,
509 filter_id: 2,
510 phrase: "knight",
511 context: ["home"]
512 }
513
514 {:ok, filter} = Pleroma.Filter.create(query)
515
516 conn =
517 conn
518 |> assign(:user, user)
519 |> get("/api/v1/filters/#{filter.filter_id}")
520
521 assert _response = json_response(conn, 200)
522 end
523
524 test "update a filter", %{conn: conn} do
525 user = insert(:user)
526
527 query = %Pleroma.Filter{
528 user_id: user.id,
529 filter_id: 2,
530 phrase: "knight",
531 context: ["home"]
532 }
533
534 {:ok, _filter} = Pleroma.Filter.create(query)
535
536 new = %Pleroma.Filter{
537 phrase: "nii",
538 context: ["home"]
539 }
540
541 conn =
542 conn
543 |> assign(:user, user)
544 |> put("/api/v1/filters/#{query.filter_id}", %{
545 phrase: new.phrase,
546 context: new.context
547 })
548
549 assert response = json_response(conn, 200)
550 assert response["phrase"] == new.phrase
551 assert response["context"] == new.context
552 end
553
554 test "delete a filter", %{conn: conn} do
555 user = insert(:user)
556
557 query = %Pleroma.Filter{
558 user_id: user.id,
559 filter_id: 2,
560 phrase: "knight",
561 context: ["home"]
562 }
563
564 {:ok, filter} = Pleroma.Filter.create(query)
565
566 conn =
567 conn
568 |> assign(:user, user)
569 |> delete("/api/v1/filters/#{filter.filter_id}")
570
571 assert response = json_response(conn, 200)
572 assert response == %{}
573 end
574 end
575
576 describe "lists" do
577 test "creating a list", %{conn: conn} do
578 user = insert(:user)
579
580 conn =
581 conn
582 |> assign(:user, user)
583 |> post("/api/v1/lists", %{"title" => "cuties"})
584
585 assert %{"title" => title} = json_response(conn, 200)
586 assert title == "cuties"
587 end
588
589 test "adding users to a list", %{conn: conn} do
590 user = insert(:user)
591 other_user = insert(:user)
592 {:ok, list} = Pleroma.List.create("name", user)
593
594 conn =
595 conn
596 |> assign(:user, user)
597 |> post("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
598
599 assert %{} == json_response(conn, 200)
600 %Pleroma.List{following: following} = Pleroma.List.get(list.id, user)
601 assert following == [other_user.follower_address]
602 end
603
604 test "removing users from a list", %{conn: conn} do
605 user = insert(:user)
606 other_user = insert(:user)
607 third_user = insert(:user)
608 {:ok, list} = Pleroma.List.create("name", user)
609 {:ok, list} = Pleroma.List.follow(list, other_user)
610 {:ok, list} = Pleroma.List.follow(list, third_user)
611
612 conn =
613 conn
614 |> assign(:user, user)
615 |> delete("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
616
617 assert %{} == json_response(conn, 200)
618 %Pleroma.List{following: following} = Pleroma.List.get(list.id, user)
619 assert following == [third_user.follower_address]
620 end
621
622 test "listing users in a list", %{conn: conn} do
623 user = insert(:user)
624 other_user = insert(:user)
625 {:ok, list} = Pleroma.List.create("name", user)
626 {:ok, list} = Pleroma.List.follow(list, other_user)
627
628 conn =
629 conn
630 |> assign(:user, user)
631 |> get("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
632
633 assert [%{"id" => id}] = json_response(conn, 200)
634 assert id == to_string(other_user.id)
635 end
636
637 test "retrieving a list", %{conn: conn} do
638 user = insert(:user)
639 {:ok, list} = Pleroma.List.create("name", user)
640
641 conn =
642 conn
643 |> assign(:user, user)
644 |> get("/api/v1/lists/#{list.id}")
645
646 assert %{"id" => id} = json_response(conn, 200)
647 assert id == to_string(list.id)
648 end
649
650 test "renaming a list", %{conn: conn} do
651 user = insert(:user)
652 {:ok, list} = Pleroma.List.create("name", user)
653
654 conn =
655 conn
656 |> assign(:user, user)
657 |> put("/api/v1/lists/#{list.id}", %{"title" => "newname"})
658
659 assert %{"title" => name} = json_response(conn, 200)
660 assert name == "newname"
661 end
662
663 test "deleting a list", %{conn: conn} do
664 user = insert(:user)
665 {:ok, list} = Pleroma.List.create("name", user)
666
667 conn =
668 conn
669 |> assign(:user, user)
670 |> delete("/api/v1/lists/#{list.id}")
671
672 assert %{} = json_response(conn, 200)
673 assert is_nil(Repo.get(Pleroma.List, list.id))
674 end
675
676 test "list timeline", %{conn: conn} do
677 user = insert(:user)
678 other_user = insert(:user)
679 {:ok, _activity_one} = TwitterAPI.create_status(user, %{"status" => "Marisa is cute."})
680 {:ok, activity_two} = TwitterAPI.create_status(other_user, %{"status" => "Marisa is cute."})
681 {:ok, list} = Pleroma.List.create("name", user)
682 {:ok, list} = Pleroma.List.follow(list, other_user)
683
684 conn =
685 conn
686 |> assign(:user, user)
687 |> get("/api/v1/timelines/list/#{list.id}")
688
689 assert [%{"id" => id}] = json_response(conn, 200)
690
691 assert id == to_string(activity_two.id)
692 end
693
694 test "list timeline does not leak non-public statuses for unfollowed users", %{conn: conn} do
695 user = insert(:user)
696 other_user = insert(:user)
697 {:ok, activity_one} = TwitterAPI.create_status(other_user, %{"status" => "Marisa is cute."})
698
699 {:ok, _activity_two} =
700 TwitterAPI.create_status(other_user, %{
701 "status" => "Marisa is cute.",
702 "visibility" => "private"
703 })
704
705 {:ok, list} = Pleroma.List.create("name", user)
706 {:ok, list} = Pleroma.List.follow(list, other_user)
707
708 conn =
709 conn
710 |> assign(:user, user)
711 |> get("/api/v1/timelines/list/#{list.id}")
712
713 assert [%{"id" => id}] = json_response(conn, 200)
714
715 assert id == to_string(activity_one.id)
716 end
717 end
718
719 describe "notifications" do
720 test "list of notifications", %{conn: conn} do
721 user = insert(:user)
722 other_user = insert(:user)
723
724 {:ok, activity} =
725 TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
726
727 {:ok, [_notification]} = Notification.create_notifications(activity)
728
729 conn =
730 conn
731 |> assign(:user, user)
732 |> get("/api/v1/notifications")
733
734 expected_response =
735 "hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{
736 user.ap_id
737 }\">@<span>#{user.nickname}</span></a></span>"
738
739 assert [%{"status" => %{"content" => response}} | _rest] = json_response(conn, 200)
740 assert response == expected_response
741 end
742
743 test "getting a single notification", %{conn: conn} do
744 user = insert(:user)
745 other_user = insert(:user)
746
747 {:ok, activity} =
748 TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
749
750 {:ok, [notification]} = Notification.create_notifications(activity)
751
752 conn =
753 conn
754 |> assign(:user, user)
755 |> get("/api/v1/notifications/#{notification.id}")
756
757 expected_response =
758 "hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{
759 user.ap_id
760 }\">@<span>#{user.nickname}</span></a></span>"
761
762 assert %{"status" => %{"content" => response}} = json_response(conn, 200)
763 assert response == expected_response
764 end
765
766 test "dismissing a single notification", %{conn: conn} do
767 user = insert(:user)
768 other_user = insert(:user)
769
770 {:ok, activity} =
771 TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
772
773 {:ok, [notification]} = Notification.create_notifications(activity)
774
775 conn =
776 conn
777 |> assign(:user, user)
778 |> post("/api/v1/notifications/dismiss", %{"id" => notification.id})
779
780 assert %{} = json_response(conn, 200)
781 end
782
783 test "clearing all notifications", %{conn: conn} do
784 user = insert(:user)
785 other_user = insert(:user)
786
787 {:ok, activity} =
788 TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
789
790 {:ok, [_notification]} = Notification.create_notifications(activity)
791
792 conn =
793 conn
794 |> assign(:user, user)
795 |> post("/api/v1/notifications/clear")
796
797 assert %{} = json_response(conn, 200)
798
799 conn =
800 build_conn()
801 |> assign(:user, user)
802 |> get("/api/v1/notifications")
803
804 assert all = json_response(conn, 200)
805 assert all == []
806 end
807
808 test "paginates notifications using min_id, since_id, max_id, and limit", %{conn: conn} do
809 user = insert(:user)
810 other_user = insert(:user)
811
812 {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
813 {:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
814 {:ok, activity3} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
815 {:ok, activity4} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
816
817 notification1_id = Repo.get_by(Notification, activity_id: activity1.id).id |> to_string()
818 notification2_id = Repo.get_by(Notification, activity_id: activity2.id).id |> to_string()
819 notification3_id = Repo.get_by(Notification, activity_id: activity3.id).id |> to_string()
820 notification4_id = Repo.get_by(Notification, activity_id: activity4.id).id |> to_string()
821
822 conn =
823 conn
824 |> assign(:user, user)
825
826 # min_id
827 conn_res =
828 conn
829 |> get("/api/v1/notifications?limit=2&min_id=#{notification1_id}")
830
831 result = json_response(conn_res, 200)
832 assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
833
834 # since_id
835 conn_res =
836 conn
837 |> get("/api/v1/notifications?limit=2&since_id=#{notification1_id}")
838
839 result = json_response(conn_res, 200)
840 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
841
842 # max_id
843 conn_res =
844 conn
845 |> get("/api/v1/notifications?limit=2&max_id=#{notification4_id}")
846
847 result = json_response(conn_res, 200)
848 assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
849 end
850
851 test "filters notifications using exclude_types", %{conn: conn} do
852 user = insert(:user)
853 other_user = insert(:user)
854
855 {:ok, mention_activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"})
856 {:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"})
857 {:ok, favorite_activity, _} = CommonAPI.favorite(create_activity.id, other_user)
858 {:ok, reblog_activity, _} = CommonAPI.repeat(create_activity.id, other_user)
859 {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user)
860
861 mention_notification_id =
862 Repo.get_by(Notification, activity_id: mention_activity.id).id |> to_string()
863
864 favorite_notification_id =
865 Repo.get_by(Notification, activity_id: favorite_activity.id).id |> to_string()
866
867 reblog_notification_id =
868 Repo.get_by(Notification, activity_id: reblog_activity.id).id |> to_string()
869
870 follow_notification_id =
871 Repo.get_by(Notification, activity_id: follow_activity.id).id |> to_string()
872
873 conn =
874 conn
875 |> assign(:user, user)
876
877 conn_res =
878 get(conn, "/api/v1/notifications", %{exclude_types: ["mention", "favourite", "reblog"]})
879
880 assert [%{"id" => ^follow_notification_id}] = json_response(conn_res, 200)
881
882 conn_res =
883 get(conn, "/api/v1/notifications", %{exclude_types: ["favourite", "reblog", "follow"]})
884
885 assert [%{"id" => ^mention_notification_id}] = json_response(conn_res, 200)
886
887 conn_res =
888 get(conn, "/api/v1/notifications", %{exclude_types: ["reblog", "follow", "mention"]})
889
890 assert [%{"id" => ^favorite_notification_id}] = json_response(conn_res, 200)
891
892 conn_res =
893 get(conn, "/api/v1/notifications", %{exclude_types: ["follow", "mention", "favourite"]})
894
895 assert [%{"id" => ^reblog_notification_id}] = json_response(conn_res, 200)
896 end
897 end
898
899 describe "reblogging" do
900 test "reblogs and returns the reblogged status", %{conn: conn} do
901 activity = insert(:note_activity)
902 user = insert(:user)
903
904 conn =
905 conn
906 |> assign(:user, user)
907 |> post("/api/v1/statuses/#{activity.id}/reblog")
908
909 assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} =
910 json_response(conn, 200)
911
912 assert to_string(activity.id) == id
913 end
914 end
915
916 describe "unreblogging" do
917 test "unreblogs and returns the unreblogged status", %{conn: conn} do
918 activity = insert(:note_activity)
919 user = insert(:user)
920
921 {:ok, _, _} = CommonAPI.repeat(activity.id, user)
922
923 conn =
924 conn
925 |> assign(:user, user)
926 |> post("/api/v1/statuses/#{activity.id}/unreblog")
927
928 assert %{"id" => id, "reblogged" => false, "reblogs_count" => 0} = json_response(conn, 200)
929
930 assert to_string(activity.id) == id
931 end
932 end
933
934 describe "favoriting" do
935 test "favs a status and returns it", %{conn: conn} do
936 activity = insert(:note_activity)
937 user = insert(:user)
938
939 conn =
940 conn
941 |> assign(:user, user)
942 |> post("/api/v1/statuses/#{activity.id}/favourite")
943
944 assert %{"id" => id, "favourites_count" => 1, "favourited" => true} =
945 json_response(conn, 200)
946
947 assert to_string(activity.id) == id
948 end
949
950 test "returns 500 for a wrong id", %{conn: conn} do
951 user = insert(:user)
952
953 resp =
954 conn
955 |> assign(:user, user)
956 |> post("/api/v1/statuses/1/favourite")
957 |> json_response(500)
958
959 assert resp == "Something went wrong"
960 end
961 end
962
963 describe "unfavoriting" do
964 test "unfavorites a status and returns it", %{conn: conn} do
965 activity = insert(:note_activity)
966 user = insert(:user)
967
968 {:ok, _, _} = CommonAPI.favorite(activity.id, user)
969
970 conn =
971 conn
972 |> assign(:user, user)
973 |> post("/api/v1/statuses/#{activity.id}/unfavourite")
974
975 assert %{"id" => id, "favourites_count" => 0, "favourited" => false} =
976 json_response(conn, 200)
977
978 assert to_string(activity.id) == id
979 end
980 end
981
982 describe "user timelines" do
983 test "gets a users statuses", %{conn: conn} do
984 user_one = insert(:user)
985 user_two = insert(:user)
986 user_three = insert(:user)
987
988 {:ok, user_three} = User.follow(user_three, user_one)
989
990 {:ok, activity} = CommonAPI.post(user_one, %{"status" => "HI!!!"})
991
992 {:ok, direct_activity} =
993 CommonAPI.post(user_one, %{
994 "status" => "Hi, @#{user_two.nickname}.",
995 "visibility" => "direct"
996 })
997
998 {:ok, private_activity} =
999 CommonAPI.post(user_one, %{"status" => "private", "visibility" => "private"})
1000
1001 resp =
1002 conn
1003 |> get("/api/v1/accounts/#{user_one.id}/statuses")
1004
1005 assert [%{"id" => id}] = json_response(resp, 200)
1006 assert id == to_string(activity.id)
1007
1008 resp =
1009 conn
1010 |> assign(:user, user_two)
1011 |> get("/api/v1/accounts/#{user_one.id}/statuses")
1012
1013 assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200)
1014 assert id_one == to_string(direct_activity.id)
1015 assert id_two == to_string(activity.id)
1016
1017 resp =
1018 conn
1019 |> assign(:user, user_three)
1020 |> get("/api/v1/accounts/#{user_one.id}/statuses")
1021
1022 assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200)
1023 assert id_one == to_string(private_activity.id)
1024 assert id_two == to_string(activity.id)
1025 end
1026
1027 test "unimplemented pinned statuses feature", %{conn: conn} do
1028 note = insert(:note_activity)
1029 user = User.get_by_ap_id(note.data["actor"])
1030
1031 conn =
1032 conn
1033 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
1034
1035 assert json_response(conn, 200) == []
1036 end
1037
1038 test "gets an users media", %{conn: conn} do
1039 note = insert(:note_activity)
1040 user = User.get_by_ap_id(note.data["actor"])
1041
1042 file = %Plug.Upload{
1043 content_type: "image/jpg",
1044 path: Path.absname("test/fixtures/image.jpg"),
1045 filename: "an_image.jpg"
1046 }
1047
1048 media =
1049 TwitterAPI.upload(file, user, "json")
1050 |> Poison.decode!()
1051
1052 {:ok, image_post} =
1053 TwitterAPI.create_status(user, %{"status" => "cofe", "media_ids" => [media["media_id"]]})
1054
1055 conn =
1056 conn
1057 |> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "true"})
1058
1059 assert [%{"id" => id}] = json_response(conn, 200)
1060 assert id == to_string(image_post.id)
1061
1062 conn =
1063 build_conn()
1064 |> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "1"})
1065
1066 assert [%{"id" => id}] = json_response(conn, 200)
1067 assert id == to_string(image_post.id)
1068 end
1069
1070 test "gets a user's statuses without reblogs", %{conn: conn} do
1071 user = insert(:user)
1072 {:ok, post} = CommonAPI.post(user, %{"status" => "HI!!!"})
1073 {:ok, _, _} = CommonAPI.repeat(post.id, user)
1074
1075 conn =
1076 conn
1077 |> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "true"})
1078
1079 assert [%{"id" => id}] = json_response(conn, 200)
1080 assert id == to_string(post.id)
1081
1082 conn =
1083 conn
1084 |> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "1"})
1085
1086 assert [%{"id" => id}] = json_response(conn, 200)
1087 assert id == to_string(post.id)
1088 end
1089 end
1090
1091 describe "user relationships" do
1092 test "returns the relationships for the current user", %{conn: conn} do
1093 user = insert(:user)
1094 other_user = insert(:user)
1095 {:ok, user} = User.follow(user, other_user)
1096
1097 conn =
1098 conn
1099 |> assign(:user, user)
1100 |> get("/api/v1/accounts/relationships", %{"id" => [other_user.id]})
1101
1102 assert [relationship] = json_response(conn, 200)
1103
1104 assert to_string(other_user.id) == relationship["id"]
1105 end
1106 end
1107
1108 describe "locked accounts" do
1109 test "/api/v1/follow_requests works" do
1110 user = insert(:user, %{info: %Pleroma.User.Info{locked: true}})
1111 other_user = insert(:user)
1112
1113 {:ok, _activity} = ActivityPub.follow(other_user, user)
1114
1115 user = Repo.get(User, user.id)
1116 other_user = Repo.get(User, other_user.id)
1117
1118 assert User.following?(other_user, user) == false
1119
1120 conn =
1121 build_conn()
1122 |> assign(:user, user)
1123 |> get("/api/v1/follow_requests")
1124
1125 assert [relationship] = json_response(conn, 200)
1126 assert to_string(other_user.id) == relationship["id"]
1127 end
1128
1129 test "/api/v1/follow_requests/:id/authorize works" do
1130 user = insert(:user, %{info: %User.Info{locked: true}})
1131 other_user = insert(:user)
1132
1133 {:ok, _activity} = ActivityPub.follow(other_user, user)
1134
1135 user = Repo.get(User, user.id)
1136 other_user = Repo.get(User, other_user.id)
1137
1138 assert User.following?(other_user, user) == false
1139
1140 conn =
1141 build_conn()
1142 |> assign(:user, user)
1143 |> post("/api/v1/follow_requests/#{other_user.id}/authorize")
1144
1145 assert relationship = json_response(conn, 200)
1146 assert to_string(other_user.id) == relationship["id"]
1147
1148 user = Repo.get(User, user.id)
1149 other_user = Repo.get(User, other_user.id)
1150
1151 assert User.following?(other_user, user) == true
1152 end
1153
1154 test "verify_credentials", %{conn: conn} do
1155 user = insert(:user, %{info: %Pleroma.User.Info{default_scope: "private"}})
1156
1157 conn =
1158 conn
1159 |> assign(:user, user)
1160 |> get("/api/v1/accounts/verify_credentials")
1161
1162 assert %{"id" => id, "source" => %{"privacy" => "private"}} = json_response(conn, 200)
1163 assert id == to_string(user.id)
1164 end
1165
1166 test "/api/v1/follow_requests/:id/reject works" do
1167 user = insert(:user, %{info: %Pleroma.User.Info{locked: true}})
1168 other_user = insert(:user)
1169
1170 {:ok, _activity} = ActivityPub.follow(other_user, user)
1171
1172 user = Repo.get(User, user.id)
1173
1174 conn =
1175 build_conn()
1176 |> assign(:user, user)
1177 |> post("/api/v1/follow_requests/#{other_user.id}/reject")
1178
1179 assert relationship = json_response(conn, 200)
1180 assert to_string(other_user.id) == relationship["id"]
1181
1182 user = Repo.get(User, user.id)
1183 other_user = Repo.get(User, other_user.id)
1184
1185 assert User.following?(other_user, user) == false
1186 end
1187 end
1188
1189 test "account fetching", %{conn: conn} do
1190 user = insert(:user)
1191
1192 conn =
1193 conn
1194 |> get("/api/v1/accounts/#{user.id}")
1195
1196 assert %{"id" => id} = json_response(conn, 200)
1197 assert id == to_string(user.id)
1198
1199 conn =
1200 build_conn()
1201 |> get("/api/v1/accounts/-1")
1202
1203 assert %{"error" => "Can't find user"} = json_response(conn, 404)
1204 end
1205
1206 test "account fetching also works nickname", %{conn: conn} do
1207 user = insert(:user)
1208
1209 conn =
1210 conn
1211 |> get("/api/v1/accounts/#{user.nickname}")
1212
1213 assert %{"id" => id} = json_response(conn, 200)
1214 assert id == user.id
1215 end
1216
1217 test "media upload", %{conn: conn} do
1218 file = %Plug.Upload{
1219 content_type: "image/jpg",
1220 path: Path.absname("test/fixtures/image.jpg"),
1221 filename: "an_image.jpg"
1222 }
1223
1224 desc = "Description of the image"
1225
1226 user = insert(:user)
1227
1228 conn =
1229 conn
1230 |> assign(:user, user)
1231 |> post("/api/v1/media", %{"file" => file, "description" => desc})
1232
1233 assert media = json_response(conn, 200)
1234
1235 assert media["type"] == "image"
1236 assert media["description"] == desc
1237 assert media["id"]
1238
1239 object = Repo.get(Object, media["id"])
1240 assert object.data["actor"] == User.ap_id(user)
1241 end
1242
1243 test "hashtag timeline", %{conn: conn} do
1244 following = insert(:user)
1245
1246 capture_log(fn ->
1247 {:ok, activity} = TwitterAPI.create_status(following, %{"status" => "test #2hu"})
1248
1249 {:ok, [_activity]} =
1250 OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873")
1251
1252 nconn =
1253 conn
1254 |> get("/api/v1/timelines/tag/2hu")
1255
1256 assert [%{"id" => id}] = json_response(nconn, 200)
1257
1258 assert id == to_string(activity.id)
1259
1260 # works for different capitalization too
1261 nconn =
1262 conn
1263 |> get("/api/v1/timelines/tag/2HU")
1264
1265 assert [%{"id" => id}] = json_response(nconn, 200)
1266
1267 assert id == to_string(activity.id)
1268 end)
1269 end
1270
1271 test "multi-hashtag timeline", %{conn: conn} do
1272 user = insert(:user)
1273
1274 {:ok, activity_test} = CommonAPI.post(user, %{"status" => "#test"})
1275 {:ok, activity_test1} = CommonAPI.post(user, %{"status" => "#test #test1"})
1276 {:ok, activity_none} = CommonAPI.post(user, %{"status" => "#test #none"})
1277
1278 any_test =
1279 conn
1280 |> get("/api/v1/timelines/tag/test", %{"any" => ["test1"]})
1281
1282 [status_none, status_test1, status_test] = json_response(any_test, 200)
1283
1284 assert to_string(activity_test.id) == status_test["id"]
1285 assert to_string(activity_test1.id) == status_test1["id"]
1286 assert to_string(activity_none.id) == status_none["id"]
1287
1288 restricted_test =
1289 conn
1290 |> get("/api/v1/timelines/tag/test", %{"all" => ["test1"], "none" => ["none"]})
1291
1292 assert [status_test1] == json_response(restricted_test, 200)
1293
1294 all_test = conn |> get("/api/v1/timelines/tag/test", %{"all" => ["none"]})
1295
1296 assert [status_none] == json_response(all_test, 200)
1297 end
1298
1299 test "getting followers", %{conn: conn} do
1300 user = insert(:user)
1301 other_user = insert(:user)
1302 {:ok, user} = User.follow(user, other_user)
1303
1304 conn =
1305 conn
1306 |> get("/api/v1/accounts/#{other_user.id}/followers")
1307
1308 assert [%{"id" => id}] = json_response(conn, 200)
1309 assert id == to_string(user.id)
1310 end
1311
1312 test "getting followers, hide_followers", %{conn: conn} do
1313 user = insert(:user)
1314 other_user = insert(:user, %{info: %{hide_followers: true}})
1315 {:ok, _user} = User.follow(user, other_user)
1316
1317 conn =
1318 conn
1319 |> get("/api/v1/accounts/#{other_user.id}/followers")
1320
1321 assert [] == json_response(conn, 200)
1322 end
1323
1324 test "getting followers, hide_followers, same user requesting", %{conn: conn} do
1325 user = insert(:user)
1326 other_user = insert(:user, %{info: %{hide_followers: true}})
1327 {:ok, _user} = User.follow(user, other_user)
1328
1329 conn =
1330 conn
1331 |> assign(:user, other_user)
1332 |> get("/api/v1/accounts/#{other_user.id}/followers")
1333
1334 refute [] == json_response(conn, 200)
1335 end
1336
1337 test "getting followers, pagination", %{conn: conn} do
1338 user = insert(:user)
1339 follower1 = insert(:user)
1340 follower2 = insert(:user)
1341 follower3 = insert(:user)
1342 {:ok, _} = User.follow(follower1, user)
1343 {:ok, _} = User.follow(follower2, user)
1344 {:ok, _} = User.follow(follower3, user)
1345
1346 conn =
1347 conn
1348 |> assign(:user, user)
1349
1350 res_conn =
1351 conn
1352 |> get("/api/v1/accounts/#{user.id}/followers?since_id=#{follower1.id}")
1353
1354 assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200)
1355 assert id3 == follower3.id
1356 assert id2 == follower2.id
1357
1358 res_conn =
1359 conn
1360 |> get("/api/v1/accounts/#{user.id}/followers?max_id=#{follower3.id}")
1361
1362 assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200)
1363 assert id2 == follower2.id
1364 assert id1 == follower1.id
1365
1366 res_conn =
1367 conn
1368 |> get("/api/v1/accounts/#{user.id}/followers?limit=1&max_id=#{follower3.id}")
1369
1370 assert [%{"id" => id2}] = json_response(res_conn, 200)
1371 assert id2 == follower2.id
1372
1373 assert [link_header] = get_resp_header(res_conn, "link")
1374 assert link_header =~ ~r/since_id=#{follower2.id}/
1375 assert link_header =~ ~r/max_id=#{follower2.id}/
1376 end
1377
1378 test "getting following", %{conn: conn} do
1379 user = insert(:user)
1380 other_user = insert(:user)
1381 {:ok, user} = User.follow(user, other_user)
1382
1383 conn =
1384 conn
1385 |> get("/api/v1/accounts/#{user.id}/following")
1386
1387 assert [%{"id" => id}] = json_response(conn, 200)
1388 assert id == to_string(other_user.id)
1389 end
1390
1391 test "getting following, hide_follows", %{conn: conn} do
1392 user = insert(:user, %{info: %{hide_follows: true}})
1393 other_user = insert(:user)
1394 {:ok, user} = User.follow(user, other_user)
1395
1396 conn =
1397 conn
1398 |> get("/api/v1/accounts/#{user.id}/following")
1399
1400 assert [] == json_response(conn, 200)
1401 end
1402
1403 test "getting following, hide_follows, same user requesting", %{conn: conn} do
1404 user = insert(:user, %{info: %{hide_follows: true}})
1405 other_user = insert(:user)
1406 {:ok, user} = User.follow(user, other_user)
1407
1408 conn =
1409 conn
1410 |> assign(:user, user)
1411 |> get("/api/v1/accounts/#{user.id}/following")
1412
1413 refute [] == json_response(conn, 200)
1414 end
1415
1416 test "getting following, pagination", %{conn: conn} do
1417 user = insert(:user)
1418 following1 = insert(:user)
1419 following2 = insert(:user)
1420 following3 = insert(:user)
1421 {:ok, _} = User.follow(user, following1)
1422 {:ok, _} = User.follow(user, following2)
1423 {:ok, _} = User.follow(user, following3)
1424
1425 conn =
1426 conn
1427 |> assign(:user, user)
1428
1429 res_conn =
1430 conn
1431 |> get("/api/v1/accounts/#{user.id}/following?since_id=#{following1.id}")
1432
1433 assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200)
1434 assert id3 == following3.id
1435 assert id2 == following2.id
1436
1437 res_conn =
1438 conn
1439 |> get("/api/v1/accounts/#{user.id}/following?max_id=#{following3.id}")
1440
1441 assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200)
1442 assert id2 == following2.id
1443 assert id1 == following1.id
1444
1445 res_conn =
1446 conn
1447 |> get("/api/v1/accounts/#{user.id}/following?limit=1&max_id=#{following3.id}")
1448
1449 assert [%{"id" => id2}] = json_response(res_conn, 200)
1450 assert id2 == following2.id
1451
1452 assert [link_header] = get_resp_header(res_conn, "link")
1453 assert link_header =~ ~r/since_id=#{following2.id}/
1454 assert link_header =~ ~r/max_id=#{following2.id}/
1455 end
1456
1457 test "following / unfollowing a user", %{conn: conn} do
1458 user = insert(:user)
1459 other_user = insert(:user)
1460
1461 conn =
1462 conn
1463 |> assign(:user, user)
1464 |> post("/api/v1/accounts/#{other_user.id}/follow")
1465
1466 assert %{"id" => _id, "following" => true} = json_response(conn, 200)
1467
1468 user = Repo.get(User, user.id)
1469
1470 conn =
1471 build_conn()
1472 |> assign(:user, user)
1473 |> post("/api/v1/accounts/#{other_user.id}/unfollow")
1474
1475 assert %{"id" => _id, "following" => false} = json_response(conn, 200)
1476
1477 user = Repo.get(User, user.id)
1478
1479 conn =
1480 build_conn()
1481 |> assign(:user, user)
1482 |> post("/api/v1/follows", %{"uri" => other_user.nickname})
1483
1484 assert %{"id" => id} = json_response(conn, 200)
1485 assert id == to_string(other_user.id)
1486 end
1487
1488 test "muting / unmuting a user", %{conn: conn} do
1489 user = insert(:user)
1490 other_user = insert(:user)
1491
1492 conn =
1493 conn
1494 |> assign(:user, user)
1495 |> post("/api/v1/accounts/#{other_user.id}/mute")
1496
1497 assert %{"id" => _id, "muting" => true} = json_response(conn, 200)
1498
1499 user = Repo.get(User, user.id)
1500
1501 conn =
1502 build_conn()
1503 |> assign(:user, user)
1504 |> post("/api/v1/accounts/#{other_user.id}/unmute")
1505
1506 assert %{"id" => _id, "muting" => false} = json_response(conn, 200)
1507 end
1508
1509 test "getting a list of mutes", %{conn: conn} do
1510 user = insert(:user)
1511 other_user = insert(:user)
1512
1513 {:ok, user} = User.mute(user, other_user)
1514
1515 conn =
1516 conn
1517 |> assign(:user, user)
1518 |> get("/api/v1/mutes")
1519
1520 other_user_id = to_string(other_user.id)
1521 assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
1522 end
1523
1524 test "blocking / unblocking a user", %{conn: conn} do
1525 user = insert(:user)
1526 other_user = insert(:user)
1527
1528 conn =
1529 conn
1530 |> assign(:user, user)
1531 |> post("/api/v1/accounts/#{other_user.id}/block")
1532
1533 assert %{"id" => _id, "blocking" => true} = json_response(conn, 200)
1534
1535 user = Repo.get(User, user.id)
1536
1537 conn =
1538 build_conn()
1539 |> assign(:user, user)
1540 |> post("/api/v1/accounts/#{other_user.id}/unblock")
1541
1542 assert %{"id" => _id, "blocking" => false} = json_response(conn, 200)
1543 end
1544
1545 test "getting a list of blocks", %{conn: conn} do
1546 user = insert(:user)
1547 other_user = insert(:user)
1548
1549 {:ok, user} = User.block(user, other_user)
1550
1551 conn =
1552 conn
1553 |> assign(:user, user)
1554 |> get("/api/v1/blocks")
1555
1556 other_user_id = to_string(other_user.id)
1557 assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
1558 end
1559
1560 test "blocking / unblocking a domain", %{conn: conn} do
1561 user = insert(:user)
1562 other_user = insert(:user, %{ap_id: "https://dogwhistle.zone/@pundit"})
1563
1564 conn =
1565 conn
1566 |> assign(:user, user)
1567 |> post("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
1568
1569 assert %{} = json_response(conn, 200)
1570 user = User.get_cached_by_ap_id(user.ap_id)
1571 assert User.blocks?(user, other_user)
1572
1573 conn =
1574 build_conn()
1575 |> assign(:user, user)
1576 |> delete("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
1577
1578 assert %{} = json_response(conn, 200)
1579 user = User.get_cached_by_ap_id(user.ap_id)
1580 refute User.blocks?(user, other_user)
1581 end
1582
1583 test "getting a list of domain blocks", %{conn: conn} do
1584 user = insert(:user)
1585
1586 {:ok, user} = User.block_domain(user, "bad.site")
1587 {:ok, user} = User.block_domain(user, "even.worse.site")
1588
1589 conn =
1590 conn
1591 |> assign(:user, user)
1592 |> get("/api/v1/domain_blocks")
1593
1594 domain_blocks = json_response(conn, 200)
1595
1596 assert "bad.site" in domain_blocks
1597 assert "even.worse.site" in domain_blocks
1598 end
1599
1600 test "unimplemented follow_requests, blocks, domain blocks" do
1601 user = insert(:user)
1602
1603 ["blocks", "domain_blocks", "follow_requests"]
1604 |> Enum.each(fn endpoint ->
1605 conn =
1606 build_conn()
1607 |> assign(:user, user)
1608 |> get("/api/v1/#{endpoint}")
1609
1610 assert [] = json_response(conn, 200)
1611 end)
1612 end
1613
1614 test "account search", %{conn: conn} do
1615 user = insert(:user)
1616 user_two = insert(:user, %{nickname: "shp@shitposter.club"})
1617 user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
1618
1619 results =
1620 conn
1621 |> assign(:user, user)
1622 |> get("/api/v1/accounts/search", %{"q" => "shp"})
1623 |> json_response(200)
1624
1625 result_ids = for result <- results, do: result["acct"]
1626
1627 assert user_two.nickname in result_ids
1628 assert user_three.nickname in result_ids
1629
1630 results =
1631 conn
1632 |> assign(:user, user)
1633 |> get("/api/v1/accounts/search", %{"q" => "2hu"})
1634 |> json_response(200)
1635
1636 result_ids = for result <- results, do: result["acct"]
1637
1638 assert user_three.nickname in result_ids
1639 end
1640
1641 test "search", %{conn: conn} do
1642 user = insert(:user)
1643 user_two = insert(:user, %{nickname: "shp@shitposter.club"})
1644 user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
1645
1646 {:ok, activity} = CommonAPI.post(user, %{"status" => "This is about 2hu"})
1647
1648 {:ok, _activity} =
1649 CommonAPI.post(user, %{
1650 "status" => "This is about 2hu, but private",
1651 "visibility" => "private"
1652 })
1653
1654 {:ok, _} = CommonAPI.post(user_two, %{"status" => "This isn't"})
1655
1656 conn =
1657 conn
1658 |> get("/api/v1/search", %{"q" => "2hu"})
1659
1660 assert results = json_response(conn, 200)
1661
1662 [account | _] = results["accounts"]
1663 assert account["id"] == to_string(user_three.id)
1664
1665 assert results["hashtags"] == []
1666
1667 [status] = results["statuses"]
1668 assert status["id"] == to_string(activity.id)
1669 end
1670
1671 test "search fetches remote statuses", %{conn: conn} do
1672 capture_log(fn ->
1673 conn =
1674 conn
1675 |> get("/api/v1/search", %{"q" => "https://shitposter.club/notice/2827873"})
1676
1677 assert results = json_response(conn, 200)
1678
1679 [status] = results["statuses"]
1680 assert status["uri"] == "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
1681 end)
1682 end
1683
1684 test "search doesn't show statuses that it shouldn't", %{conn: conn} do
1685 {:ok, activity} =
1686 CommonAPI.post(insert(:user), %{
1687 "status" => "This is about 2hu, but private",
1688 "visibility" => "private"
1689 })
1690
1691 capture_log(fn ->
1692 conn =
1693 conn
1694 |> get("/api/v1/search", %{"q" => activity.data["object"]["id"]})
1695
1696 assert results = json_response(conn, 200)
1697
1698 [] = results["statuses"]
1699 end)
1700 end
1701
1702 test "search fetches remote accounts", %{conn: conn} do
1703 conn =
1704 conn
1705 |> get("/api/v1/search", %{"q" => "shp@social.heldscal.la", "resolve" => "true"})
1706
1707 assert results = json_response(conn, 200)
1708 [account] = results["accounts"]
1709 assert account["acct"] == "shp@social.heldscal.la"
1710 end
1711
1712 test "returns the favorites of a user", %{conn: conn} do
1713 user = insert(:user)
1714 other_user = insert(:user)
1715
1716 {:ok, _} = CommonAPI.post(other_user, %{"status" => "bla"})
1717 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "traps are happy"})
1718
1719 {:ok, _, _} = CommonAPI.favorite(activity.id, user)
1720
1721 first_conn =
1722 conn
1723 |> assign(:user, user)
1724 |> get("/api/v1/favourites")
1725
1726 assert [status] = json_response(first_conn, 200)
1727 assert status["id"] == to_string(activity.id)
1728
1729 assert [{"link", _link_header}] =
1730 Enum.filter(first_conn.resp_headers, fn element -> match?({"link", _}, element) end)
1731
1732 # Honours query params
1733 {:ok, second_activity} =
1734 CommonAPI.post(other_user, %{
1735 "status" =>
1736 "Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful."
1737 })
1738
1739 {:ok, _, _} = CommonAPI.favorite(second_activity.id, user)
1740
1741 last_like = status["id"]
1742
1743 second_conn =
1744 conn
1745 |> assign(:user, user)
1746 |> get("/api/v1/favourites?since_id=#{last_like}")
1747
1748 assert [second_status] = json_response(second_conn, 200)
1749 assert second_status["id"] == to_string(second_activity.id)
1750
1751 third_conn =
1752 conn
1753 |> assign(:user, user)
1754 |> get("/api/v1/favourites?limit=0")
1755
1756 assert [] = json_response(third_conn, 200)
1757 end
1758
1759 describe "updating credentials" do
1760 test "updates the user's bio", %{conn: conn} do
1761 user = insert(:user)
1762 user2 = insert(:user)
1763
1764 conn =
1765 conn
1766 |> assign(:user, user)
1767 |> patch("/api/v1/accounts/update_credentials", %{
1768 "note" => "I drink #cofe with @#{user2.nickname}"
1769 })
1770
1771 assert user = json_response(conn, 200)
1772
1773 assert user["note"] ==
1774 ~s(I drink <a class="hashtag" data-tag="cofe" href="http://localhost:4001/tag/cofe" rel="tag">#cofe</a> with <span class="h-card"><a data-user=") <>
1775 user2.id <>
1776 ~s(" class="u-url mention" href=") <>
1777 user2.ap_id <> ~s(">@<span>) <> user2.nickname <> ~s(</span></a></span>)
1778 end
1779
1780 test "updates the user's locking status", %{conn: conn} do
1781 user = insert(:user)
1782
1783 conn =
1784 conn
1785 |> assign(:user, user)
1786 |> patch("/api/v1/accounts/update_credentials", %{locked: "true"})
1787
1788 assert user = json_response(conn, 200)
1789 assert user["locked"] == true
1790 end
1791
1792 test "updates the user's name", %{conn: conn} do
1793 user = insert(:user)
1794
1795 conn =
1796 conn
1797 |> assign(:user, user)
1798 |> patch("/api/v1/accounts/update_credentials", %{"display_name" => "markorepairs"})
1799
1800 assert user = json_response(conn, 200)
1801 assert user["display_name"] == "markorepairs"
1802 end
1803
1804 test "updates the user's avatar", %{conn: conn} do
1805 user = insert(:user)
1806
1807 new_avatar = %Plug.Upload{
1808 content_type: "image/jpg",
1809 path: Path.absname("test/fixtures/image.jpg"),
1810 filename: "an_image.jpg"
1811 }
1812
1813 conn =
1814 conn
1815 |> assign(:user, user)
1816 |> patch("/api/v1/accounts/update_credentials", %{"avatar" => new_avatar})
1817
1818 assert user_response = json_response(conn, 200)
1819 assert user_response["avatar"] != User.avatar_url(user)
1820 end
1821
1822 test "updates the user's banner", %{conn: conn} do
1823 user = insert(:user)
1824
1825 new_header = %Plug.Upload{
1826 content_type: "image/jpg",
1827 path: Path.absname("test/fixtures/image.jpg"),
1828 filename: "an_image.jpg"
1829 }
1830
1831 conn =
1832 conn
1833 |> assign(:user, user)
1834 |> patch("/api/v1/accounts/update_credentials", %{"header" => new_header})
1835
1836 assert user_response = json_response(conn, 200)
1837 assert user_response["header"] != User.banner_url(user)
1838 end
1839
1840 test "requires 'write' permission", %{conn: conn} do
1841 token1 = insert(:oauth_token, scopes: ["read"])
1842 token2 = insert(:oauth_token, scopes: ["write", "follow"])
1843
1844 for token <- [token1, token2] do
1845 conn =
1846 conn
1847 |> put_req_header("authorization", "Bearer #{token.token}")
1848 |> patch("/api/v1/accounts/update_credentials", %{})
1849
1850 if token == token1 do
1851 assert %{"error" => "Insufficient permissions: write."} == json_response(conn, 403)
1852 else
1853 assert json_response(conn, 200)
1854 end
1855 end
1856 end
1857 end
1858
1859 test "get instance information", %{conn: conn} do
1860 conn = get(conn, "/api/v1/instance")
1861 assert result = json_response(conn, 200)
1862
1863 # Note: not checking for "max_toot_chars" since it's optional
1864 assert %{
1865 "uri" => _,
1866 "title" => _,
1867 "description" => _,
1868 "version" => _,
1869 "email" => _,
1870 "urls" => %{
1871 "streaming_api" => _
1872 },
1873 "stats" => _,
1874 "thumbnail" => _,
1875 "languages" => _,
1876 "registrations" => _
1877 } = result
1878 end
1879
1880 test "get instance stats", %{conn: conn} do
1881 user = insert(:user, %{local: true})
1882
1883 user2 = insert(:user, %{local: true})
1884 {:ok, _user2} = User.deactivate(user2, !user2.info.deactivated)
1885
1886 insert(:user, %{local: false, nickname: "u@peer1.com"})
1887 insert(:user, %{local: false, nickname: "u@peer2.com"})
1888
1889 {:ok, _} = TwitterAPI.create_status(user, %{"status" => "cofe"})
1890
1891 # Stats should count users with missing or nil `info.deactivated` value
1892 user = Repo.get(User, user.id)
1893 info_change = Changeset.change(user.info, %{deactivated: nil})
1894
1895 {:ok, _user} =
1896 user
1897 |> Changeset.change()
1898 |> Changeset.put_embed(:info, info_change)
1899 |> User.update_and_set_cache()
1900
1901 Pleroma.Stats.update_stats()
1902
1903 conn = get(conn, "/api/v1/instance")
1904
1905 assert result = json_response(conn, 200)
1906
1907 stats = result["stats"]
1908
1909 assert stats
1910 assert stats["user_count"] == 1
1911 assert stats["status_count"] == 1
1912 assert stats["domain_count"] == 2
1913 end
1914
1915 test "get peers", %{conn: conn} do
1916 insert(:user, %{local: false, nickname: "u@peer1.com"})
1917 insert(:user, %{local: false, nickname: "u@peer2.com"})
1918
1919 Pleroma.Stats.update_stats()
1920
1921 conn = get(conn, "/api/v1/instance/peers")
1922
1923 assert result = json_response(conn, 200)
1924
1925 assert ["peer1.com", "peer2.com"] == Enum.sort(result)
1926 end
1927
1928 test "put settings", %{conn: conn} do
1929 user = insert(:user)
1930
1931 conn =
1932 conn
1933 |> assign(:user, user)
1934 |> put("/api/web/settings", %{"data" => %{"programming" => "socks"}})
1935
1936 assert _result = json_response(conn, 200)
1937
1938 user = User.get_cached_by_ap_id(user.ap_id)
1939 assert user.info.settings == %{"programming" => "socks"}
1940 end
1941
1942 describe "pinned statuses" do
1943 setup do
1944 Pleroma.Config.put([:instance, :max_pinned_statuses], 1)
1945
1946 user = insert(:user)
1947 {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"})
1948
1949 [user: user, activity: activity]
1950 end
1951
1952 test "returns pinned statuses", %{conn: conn, user: user, activity: activity} do
1953 {:ok, _} = CommonAPI.pin(activity.id, user)
1954
1955 result =
1956 conn
1957 |> assign(:user, user)
1958 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
1959 |> json_response(200)
1960
1961 id_str = to_string(activity.id)
1962
1963 assert [%{"id" => ^id_str, "pinned" => true}] = result
1964 end
1965
1966 test "pin status", %{conn: conn, user: user, activity: activity} do
1967 id_str = to_string(activity.id)
1968
1969 assert %{"id" => ^id_str, "pinned" => true} =
1970 conn
1971 |> assign(:user, user)
1972 |> post("/api/v1/statuses/#{activity.id}/pin")
1973 |> json_response(200)
1974
1975 assert [%{"id" => ^id_str, "pinned" => true}] =
1976 conn
1977 |> assign(:user, user)
1978 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
1979 |> json_response(200)
1980 end
1981
1982 test "unpin status", %{conn: conn, user: user, activity: activity} do
1983 {:ok, _} = CommonAPI.pin(activity.id, user)
1984
1985 id_str = to_string(activity.id)
1986 user = refresh_record(user)
1987
1988 assert %{"id" => ^id_str, "pinned" => false} =
1989 conn
1990 |> assign(:user, user)
1991 |> post("/api/v1/statuses/#{activity.id}/unpin")
1992 |> json_response(200)
1993
1994 assert [] =
1995 conn
1996 |> assign(:user, user)
1997 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
1998 |> json_response(200)
1999 end
2000
2001 test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
2002 {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
2003
2004 id_str_one = to_string(activity_one.id)
2005
2006 assert %{"id" => ^id_str_one, "pinned" => true} =
2007 conn
2008 |> assign(:user, user)
2009 |> post("/api/v1/statuses/#{id_str_one}/pin")
2010 |> json_response(200)
2011
2012 user = refresh_record(user)
2013
2014 assert %{"error" => "You have already pinned the maximum number of statuses"} =
2015 conn
2016 |> assign(:user, user)
2017 |> post("/api/v1/statuses/#{activity_two.id}/pin")
2018 |> json_response(400)
2019 end
2020
2021 test "Status rich-media Card", %{conn: conn, user: user} do
2022 Pleroma.Config.put([:rich_media, :enabled], true)
2023 {:ok, activity} = CommonAPI.post(user, %{"status" => "http://example.com/ogp"})
2024
2025 response =
2026 conn
2027 |> get("/api/v1/statuses/#{activity.id}/card")
2028 |> json_response(200)
2029
2030 assert response == %{
2031 "image" => "http://ia.media-imdb.com/images/rock.jpg",
2032 "provider_name" => "www.imdb.com",
2033 "provider_url" => "http://www.imdb.com",
2034 "title" => "The Rock",
2035 "type" => "link",
2036 "url" => "http://www.imdb.com/title/tt0117500/",
2037 "description" => nil,
2038 "pleroma" => %{
2039 "opengraph" => %{
2040 "image" => "http://ia.media-imdb.com/images/rock.jpg",
2041 "title" => "The Rock",
2042 "type" => "video.movie",
2043 "url" => "http://www.imdb.com/title/tt0117500/"
2044 }
2045 }
2046 }
2047
2048 # works with private posts
2049 {:ok, activity} =
2050 CommonAPI.post(user, %{"status" => "http://example.com/ogp", "visibility" => "direct"})
2051
2052 response_two =
2053 conn
2054 |> assign(:user, user)
2055 |> get("/api/v1/statuses/#{activity.id}/card")
2056 |> json_response(200)
2057
2058 assert response_two == response
2059
2060 Pleroma.Config.put([:rich_media, :enabled], false)
2061 end
2062 end
2063
2064 test "bookmarks" do
2065 user = insert(:user)
2066 for_user = insert(:user)
2067
2068 {:ok, activity1} =
2069 CommonAPI.post(user, %{
2070 "status" => "heweoo?"
2071 })
2072
2073 {:ok, activity2} =
2074 CommonAPI.post(user, %{
2075 "status" => "heweoo!"
2076 })
2077
2078 response1 =
2079 build_conn()
2080 |> assign(:user, for_user)
2081 |> post("/api/v1/statuses/#{activity1.id}/bookmark")
2082
2083 assert json_response(response1, 200)["bookmarked"] == true
2084
2085 response2 =
2086 build_conn()
2087 |> assign(:user, for_user)
2088 |> post("/api/v1/statuses/#{activity2.id}/bookmark")
2089
2090 assert json_response(response2, 200)["bookmarked"] == true
2091
2092 bookmarks =
2093 build_conn()
2094 |> assign(:user, for_user)
2095 |> get("/api/v1/bookmarks")
2096
2097 assert [json_response(response2, 200), json_response(response1, 200)] ==
2098 json_response(bookmarks, 200)
2099
2100 response1 =
2101 build_conn()
2102 |> assign(:user, for_user)
2103 |> post("/api/v1/statuses/#{activity1.id}/unbookmark")
2104
2105 assert json_response(response1, 200)["bookmarked"] == false
2106
2107 bookmarks =
2108 build_conn()
2109 |> assign(:user, for_user)
2110 |> get("/api/v1/bookmarks")
2111
2112 assert [json_response(response2, 200)] == json_response(bookmarks, 200)
2113 end
2114
2115 describe "conversation muting" do
2116 setup do
2117 user = insert(:user)
2118 {:ok, activity} = CommonAPI.post(user, %{"status" => "HIE"})
2119
2120 [user: user, activity: activity]
2121 end
2122
2123 test "mute conversation", %{conn: conn, user: user, activity: activity} do
2124 id_str = to_string(activity.id)
2125
2126 assert %{"id" => ^id_str, "muted" => true} =
2127 conn
2128 |> assign(:user, user)
2129 |> post("/api/v1/statuses/#{activity.id}/mute")
2130 |> json_response(200)
2131 end
2132
2133 test "unmute conversation", %{conn: conn, user: user, activity: activity} do
2134 {:ok, _} = CommonAPI.add_mute(user, activity)
2135
2136 id_str = to_string(activity.id)
2137 user = refresh_record(user)
2138
2139 assert %{"id" => ^id_str, "muted" => false} =
2140 conn
2141 |> assign(:user, user)
2142 |> post("/api/v1/statuses/#{activity.id}/unmute")
2143 |> json_response(200)
2144 end
2145 end
2146
2147 test "flavours switching (Pleroma Extension)", %{conn: conn} do
2148 user = insert(:user)
2149
2150 get_old_flavour =
2151 conn
2152 |> assign(:user, user)
2153 |> get("/api/v1/pleroma/flavour")
2154
2155 assert "glitch" == json_response(get_old_flavour, 200)
2156
2157 set_flavour =
2158 conn
2159 |> assign(:user, user)
2160 |> post("/api/v1/pleroma/flavour/vanilla")
2161
2162 assert "vanilla" == json_response(set_flavour, 200)
2163
2164 get_new_flavour =
2165 conn
2166 |> assign(:user, user)
2167 |> post("/api/v1/pleroma/flavour/vanilla")
2168
2169 assert json_response(set_flavour, 200) == json_response(get_new_flavour, 200)
2170 end
2171
2172 describe "reports" do
2173 setup do
2174 reporter = insert(:user)
2175 target_user = insert(:user)
2176
2177 {:ok, activity} = CommonAPI.post(target_user, %{"status" => "foobar"})
2178
2179 [reporter: reporter, target_user: target_user, activity: activity]
2180 end
2181
2182 test "submit a basic report", %{conn: conn, reporter: reporter, target_user: target_user} do
2183 assert %{"action_taken" => false, "id" => _} =
2184 conn
2185 |> assign(:user, reporter)
2186 |> post("/api/v1/reports", %{"account_id" => target_user.id})
2187 |> json_response(200)
2188 end
2189
2190 test "submit a report with statuses and comment", %{
2191 conn: conn,
2192 reporter: reporter,
2193 target_user: target_user,
2194 activity: activity
2195 } do
2196 assert %{"action_taken" => false, "id" => _} =
2197 conn
2198 |> assign(:user, reporter)
2199 |> post("/api/v1/reports", %{
2200 "account_id" => target_user.id,
2201 "status_ids" => [activity.id],
2202 "comment" => "bad status!"
2203 })
2204 |> json_response(200)
2205 end
2206
2207 test "account_id is required", %{
2208 conn: conn,
2209 reporter: reporter,
2210 activity: activity
2211 } do
2212 assert %{"error" => "Valid `account_id` required"} =
2213 conn
2214 |> assign(:user, reporter)
2215 |> post("/api/v1/reports", %{"status_ids" => [activity.id]})
2216 |> json_response(400)
2217 end
2218
2219 test "comment must be up to the size specified in the config", %{
2220 conn: conn,
2221 reporter: reporter,
2222 target_user: target_user
2223 } do
2224 max_size = Pleroma.Config.get([:instance, :max_report_comment_size], 1000)
2225 comment = String.pad_trailing("a", max_size + 1, "a")
2226
2227 error = %{"error" => "Comment must be up to #{max_size} characters"}
2228
2229 assert ^error =
2230 conn
2231 |> assign(:user, reporter)
2232 |> post("/api/v1/reports", %{"account_id" => target_user.id, "comment" => comment})
2233 |> json_response(400)
2234 end
2235 end
2236
2237 describe "link headers" do
2238 test "preserves parameters in link headers", %{conn: conn} do
2239 user = insert(:user)
2240 other_user = insert(:user)
2241
2242 {:ok, activity1} =
2243 CommonAPI.post(other_user, %{
2244 "status" => "hi @#{user.nickname}",
2245 "visibility" => "public"
2246 })
2247
2248 {:ok, activity2} =
2249 CommonAPI.post(other_user, %{
2250 "status" => "hi @#{user.nickname}",
2251 "visibility" => "public"
2252 })
2253
2254 notification1 = Repo.get_by(Notification, activity_id: activity1.id)
2255 notification2 = Repo.get_by(Notification, activity_id: activity2.id)
2256
2257 conn =
2258 conn
2259 |> assign(:user, user)
2260 |> get("/api/v1/notifications", %{media_only: true})
2261
2262 assert [link_header] = get_resp_header(conn, "link")
2263 assert link_header =~ ~r/media_only=true/
2264 assert link_header =~ ~r/since_id=#{notification2.id}/
2265 assert link_header =~ ~r/max_id=#{notification1.id}/
2266 end
2267 end
2268 end