Merge pull request 'purge chat and shout endpoints' (#97) from purge-chat into develop
[akkoma] / test / pleroma / web / mastodon_api / controllers / notification_controller_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do
6 use Pleroma.Web.ConnCase
7
8 alias Pleroma.Notification
9 alias Pleroma.Repo
10 alias Pleroma.User
11 alias Pleroma.Web.CommonAPI
12
13 import Pleroma.Factory
14
15 test "does NOT render account/pleroma/relationship by default" do
16 %{user: user, conn: conn} = oauth_access(["read:notifications"])
17 other_user = insert(:user)
18
19 {:ok, activity} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
20 {:ok, [_notification]} = Notification.create_notifications(activity)
21
22 response =
23 conn
24 |> assign(:user, user)
25 |> get("/api/v1/notifications")
26 |> json_response_and_validate_schema(200)
27
28 assert Enum.all?(response, fn n ->
29 get_in(n, ["account", "pleroma", "relationship"]) == %{}
30 end)
31 end
32
33 test "list of notifications" do
34 %{user: user, conn: conn} = oauth_access(["read:notifications"])
35 other_user = insert(:user)
36
37 {:ok, activity} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
38
39 {:ok, [_notification]} = Notification.create_notifications(activity)
40
41 conn =
42 conn
43 |> assign(:user, user)
44 |> get("/api/v1/notifications")
45
46 expected_response =
47 "hi <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{user.id}\" href=\"#{user.ap_id}\" rel=\"ugc\">@<span>#{user.nickname}</span></a></span>"
48
49 assert [%{"status" => %{"content" => response}} | _rest] =
50 json_response_and_validate_schema(conn, 200)
51
52 assert response == expected_response
53 end
54
55 test "by default, does not contain pleroma:report" do
56 %{user: user, conn: conn} = oauth_access(["read:notifications"])
57 other_user = insert(:user)
58 third_user = insert(:user)
59
60 user
61 |> User.admin_api_update(%{is_moderator: true})
62
63 {:ok, activity} = CommonAPI.post(other_user, %{status: "hey"})
64
65 {:ok, _report} =
66 CommonAPI.report(third_user, %{account_id: other_user.id, status_ids: [activity.id]})
67
68 result =
69 conn
70 |> get("/api/v1/notifications")
71 |> json_response_and_validate_schema(200)
72
73 assert [] == result
74
75 result =
76 conn
77 |> get("/api/v1/notifications?include_types[]=pleroma:report")
78 |> json_response_and_validate_schema(200)
79
80 assert [_] = result
81 end
82
83 test "excludes mentions from blockers when blockers_visible is false" do
84 clear_config([:activitypub, :blockers_visible], false)
85
86 %{user: user, conn: conn} = oauth_access(["read:notifications"])
87 blocker = insert(:user)
88
89 {:ok, _} = CommonAPI.block(blocker, user)
90 {:ok, activity} = CommonAPI.post(blocker, %{status: "hi @#{user.nickname}"})
91
92 {:ok, [_notification]} = Notification.create_notifications(activity)
93
94 conn =
95 conn
96 |> assign(:user, user)
97 |> get("/api/v1/notifications")
98
99 assert [] == json_response_and_validate_schema(conn, 200)
100 end
101
102 test "getting a single notification" do
103 %{user: user, conn: conn} = oauth_access(["read:notifications"])
104 other_user = insert(:user)
105
106 {:ok, activity} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
107
108 {:ok, [notification]} = Notification.create_notifications(activity)
109
110 conn = get(conn, "/api/v1/notifications/#{notification.id}")
111
112 expected_response =
113 "hi <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{user.id}\" href=\"#{user.ap_id}\" rel=\"ugc\">@<span>#{user.nickname}</span></a></span>"
114
115 assert %{"status" => %{"content" => response}} = json_response_and_validate_schema(conn, 200)
116 assert response == expected_response
117 end
118
119 test "dismissing a single notification" do
120 %{user: user, conn: conn} = oauth_access(["write:notifications"])
121 other_user = insert(:user)
122
123 {:ok, activity} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
124
125 {:ok, [notification]} = Notification.create_notifications(activity)
126
127 conn =
128 conn
129 |> assign(:user, user)
130 |> post("/api/v1/notifications/#{notification.id}/dismiss")
131
132 assert %{} = json_response_and_validate_schema(conn, 200)
133 end
134
135 test "clearing all notifications" do
136 %{user: user, conn: conn} = oauth_access(["write:notifications", "read:notifications"])
137 other_user = insert(:user)
138
139 {:ok, activity} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
140
141 {:ok, [_notification]} = Notification.create_notifications(activity)
142
143 ret_conn = post(conn, "/api/v1/notifications/clear")
144
145 assert %{} = json_response_and_validate_schema(ret_conn, 200)
146
147 ret_conn = get(conn, "/api/v1/notifications")
148
149 assert all = json_response_and_validate_schema(ret_conn, 200)
150 assert all == []
151 end
152
153 test "paginates notifications using min_id, since_id, max_id, and limit" do
154 %{user: user, conn: conn} = oauth_access(["read:notifications"])
155 other_user = insert(:user)
156
157 {:ok, activity1} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
158 {:ok, activity2} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
159 {:ok, activity3} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
160 {:ok, activity4} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
161
162 notification1_id = get_notification_id_by_activity(activity1)
163 notification2_id = get_notification_id_by_activity(activity2)
164 notification3_id = get_notification_id_by_activity(activity3)
165 notification4_id = get_notification_id_by_activity(activity4)
166
167 conn = assign(conn, :user, user)
168
169 # min_id
170 result =
171 conn
172 |> get("/api/v1/notifications?limit=2&min_id=#{notification1_id}")
173 |> json_response_and_validate_schema(:ok)
174
175 assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
176
177 # since_id
178 result =
179 conn
180 |> get("/api/v1/notifications?limit=2&since_id=#{notification1_id}")
181 |> json_response_and_validate_schema(:ok)
182
183 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
184
185 # max_id
186 result =
187 conn
188 |> get("/api/v1/notifications?limit=2&max_id=#{notification4_id}")
189 |> json_response_and_validate_schema(:ok)
190
191 assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
192 end
193
194 describe "exclude_visibilities" do
195 test "filters notifications for mentions" do
196 %{user: user, conn: conn} = oauth_access(["read:notifications"])
197 other_user = insert(:user)
198
199 {:ok, public_activity} =
200 CommonAPI.post(other_user, %{status: "@#{user.nickname}", visibility: "public"})
201
202 {:ok, direct_activity} =
203 CommonAPI.post(other_user, %{status: "@#{user.nickname}", visibility: "direct"})
204
205 {:ok, unlisted_activity} =
206 CommonAPI.post(other_user, %{status: "@#{user.nickname}", visibility: "unlisted"})
207
208 {:ok, private_activity} =
209 CommonAPI.post(other_user, %{status: "@#{user.nickname}", visibility: "private"})
210
211 query = params_to_query(%{exclude_visibilities: ["public", "unlisted", "private"]})
212 conn_res = get(conn, "/api/v1/notifications?" <> query)
213
214 assert [%{"status" => %{"id" => id}}] = json_response_and_validate_schema(conn_res, 200)
215 assert id == direct_activity.id
216
217 query = params_to_query(%{exclude_visibilities: ["public", "unlisted", "direct"]})
218 conn_res = get(conn, "/api/v1/notifications?" <> query)
219
220 assert [%{"status" => %{"id" => id}}] = json_response_and_validate_schema(conn_res, 200)
221 assert id == private_activity.id
222
223 query = params_to_query(%{exclude_visibilities: ["public", "private", "direct"]})
224 conn_res = get(conn, "/api/v1/notifications?" <> query)
225
226 assert [%{"status" => %{"id" => id}}] = json_response_and_validate_schema(conn_res, 200)
227 assert id == unlisted_activity.id
228
229 query = params_to_query(%{exclude_visibilities: ["unlisted", "private", "direct"]})
230 conn_res = get(conn, "/api/v1/notifications?" <> query)
231
232 assert [%{"status" => %{"id" => id}}] = json_response_and_validate_schema(conn_res, 200)
233 assert id == public_activity.id
234 end
235
236 test "filters notifications for Like activities" do
237 user = insert(:user)
238 %{user: other_user, conn: conn} = oauth_access(["read:notifications"])
239
240 {:ok, public_activity} = CommonAPI.post(other_user, %{status: ".", visibility: "public"})
241
242 {:ok, direct_activity} =
243 CommonAPI.post(other_user, %{status: "@#{user.nickname}", visibility: "direct"})
244
245 {:ok, unlisted_activity} =
246 CommonAPI.post(other_user, %{status: ".", visibility: "unlisted"})
247
248 {:ok, private_activity} = CommonAPI.post(other_user, %{status: ".", visibility: "private"})
249
250 {:ok, _} = CommonAPI.favorite(user, public_activity.id)
251 {:ok, _} = CommonAPI.favorite(user, direct_activity.id)
252 {:ok, _} = CommonAPI.favorite(user, unlisted_activity.id)
253 {:ok, _} = CommonAPI.favorite(user, private_activity.id)
254
255 activity_ids =
256 conn
257 |> get("/api/v1/notifications?exclude_visibilities[]=direct")
258 |> json_response_and_validate_schema(200)
259 |> Enum.map(& &1["status"]["id"])
260
261 assert public_activity.id in activity_ids
262 assert unlisted_activity.id in activity_ids
263 assert private_activity.id in activity_ids
264 refute direct_activity.id in activity_ids
265
266 activity_ids =
267 conn
268 |> get("/api/v1/notifications?exclude_visibilities[]=unlisted")
269 |> json_response_and_validate_schema(200)
270 |> Enum.map(& &1["status"]["id"])
271
272 assert public_activity.id in activity_ids
273 refute unlisted_activity.id in activity_ids
274 assert private_activity.id in activity_ids
275 assert direct_activity.id in activity_ids
276
277 activity_ids =
278 conn
279 |> get("/api/v1/notifications?exclude_visibilities[]=private")
280 |> json_response_and_validate_schema(200)
281 |> Enum.map(& &1["status"]["id"])
282
283 assert public_activity.id in activity_ids
284 assert unlisted_activity.id in activity_ids
285 refute private_activity.id in activity_ids
286 assert direct_activity.id in activity_ids
287
288 activity_ids =
289 conn
290 |> get("/api/v1/notifications?exclude_visibilities[]=public")
291 |> json_response_and_validate_schema(200)
292 |> Enum.map(& &1["status"]["id"])
293
294 refute public_activity.id in activity_ids
295 assert unlisted_activity.id in activity_ids
296 assert private_activity.id in activity_ids
297 assert direct_activity.id in activity_ids
298 end
299
300 test "filters notifications for Announce activities" do
301 user = insert(:user)
302 %{user: other_user, conn: conn} = oauth_access(["read:notifications"])
303
304 {:ok, public_activity} = CommonAPI.post(other_user, %{status: ".", visibility: "public"})
305
306 {:ok, unlisted_activity} =
307 CommonAPI.post(other_user, %{status: ".", visibility: "unlisted"})
308
309 {:ok, _} = CommonAPI.repeat(public_activity.id, user)
310 {:ok, _} = CommonAPI.repeat(unlisted_activity.id, user)
311
312 activity_ids =
313 conn
314 |> get("/api/v1/notifications?exclude_visibilities[]=unlisted")
315 |> json_response_and_validate_schema(200)
316 |> Enum.map(& &1["status"]["id"])
317
318 assert public_activity.id in activity_ids
319 refute unlisted_activity.id in activity_ids
320 end
321
322 test "doesn't return less than the requested amount of records when the user's reply is liked" do
323 user = insert(:user)
324 %{user: other_user, conn: conn} = oauth_access(["read:notifications"])
325
326 {:ok, mention} =
327 CommonAPI.post(user, %{status: "@#{other_user.nickname}", visibility: "public"})
328
329 {:ok, activity} = CommonAPI.post(user, %{status: ".", visibility: "public"})
330
331 {:ok, reply} =
332 CommonAPI.post(other_user, %{
333 status: ".",
334 visibility: "public",
335 in_reply_to_status_id: activity.id
336 })
337
338 {:ok, _favorite} = CommonAPI.favorite(user, reply.id)
339
340 activity_ids =
341 conn
342 |> get("/api/v1/notifications?exclude_visibilities[]=direct&limit=2")
343 |> json_response_and_validate_schema(200)
344 |> Enum.map(& &1["status"]["id"])
345
346 assert [reply.id, mention.id] == activity_ids
347 end
348 end
349
350 test "filters notifications using exclude_types" do
351 %{user: user, conn: conn} = oauth_access(["read:notifications"])
352 other_user = insert(:user)
353
354 {:ok, mention_activity} = CommonAPI.post(other_user, %{status: "hey @#{user.nickname}"})
355 {:ok, create_activity} = CommonAPI.post(user, %{status: "hey"})
356 {:ok, favorite_activity} = CommonAPI.favorite(other_user, create_activity.id)
357 {:ok, reblog_activity} = CommonAPI.repeat(create_activity.id, other_user)
358 {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user)
359
360 mention_notification_id = get_notification_id_by_activity(mention_activity)
361 favorite_notification_id = get_notification_id_by_activity(favorite_activity)
362 reblog_notification_id = get_notification_id_by_activity(reblog_activity)
363 follow_notification_id = get_notification_id_by_activity(follow_activity)
364
365 query = params_to_query(%{exclude_types: ["mention", "favourite", "reblog"]})
366 conn_res = get(conn, "/api/v1/notifications?" <> query)
367
368 assert [%{"id" => ^follow_notification_id}] = json_response_and_validate_schema(conn_res, 200)
369
370 query = params_to_query(%{exclude_types: ["favourite", "reblog", "follow"]})
371 conn_res = get(conn, "/api/v1/notifications?" <> query)
372
373 assert [%{"id" => ^mention_notification_id}] =
374 json_response_and_validate_schema(conn_res, 200)
375
376 query = params_to_query(%{exclude_types: ["reblog", "follow", "mention"]})
377 conn_res = get(conn, "/api/v1/notifications?" <> query)
378
379 assert [%{"id" => ^favorite_notification_id}] =
380 json_response_and_validate_schema(conn_res, 200)
381
382 query = params_to_query(%{exclude_types: ["follow", "mention", "favourite"]})
383 conn_res = get(conn, "/api/v1/notifications?" <> query)
384
385 assert [%{"id" => ^reblog_notification_id}] = json_response_and_validate_schema(conn_res, 200)
386 end
387
388 test "filters notifications using include_types" do
389 %{user: user, conn: conn} = oauth_access(["read:notifications"])
390 other_user = insert(:user)
391
392 {:ok, mention_activity} = CommonAPI.post(other_user, %{status: "hey @#{user.nickname}"})
393 {:ok, create_activity} = CommonAPI.post(user, %{status: "hey"})
394 {:ok, favorite_activity} = CommonAPI.favorite(other_user, create_activity.id)
395 {:ok, reblog_activity} = CommonAPI.repeat(create_activity.id, other_user)
396 {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user)
397
398 mention_notification_id = get_notification_id_by_activity(mention_activity)
399 favorite_notification_id = get_notification_id_by_activity(favorite_activity)
400 reblog_notification_id = get_notification_id_by_activity(reblog_activity)
401 follow_notification_id = get_notification_id_by_activity(follow_activity)
402
403 conn_res = get(conn, "/api/v1/notifications?include_types[]=follow")
404
405 assert [%{"id" => ^follow_notification_id}] = json_response_and_validate_schema(conn_res, 200)
406
407 conn_res = get(conn, "/api/v1/notifications?include_types[]=mention")
408
409 assert [%{"id" => ^mention_notification_id}] =
410 json_response_and_validate_schema(conn_res, 200)
411
412 conn_res = get(conn, "/api/v1/notifications?include_types[]=favourite")
413
414 assert [%{"id" => ^favorite_notification_id}] =
415 json_response_and_validate_schema(conn_res, 200)
416
417 conn_res = get(conn, "/api/v1/notifications?include_types[]=reblog")
418
419 assert [%{"id" => ^reblog_notification_id}] = json_response_and_validate_schema(conn_res, 200)
420
421 result = conn |> get("/api/v1/notifications") |> json_response_and_validate_schema(200)
422
423 assert length(result) == 4
424
425 query = params_to_query(%{include_types: ["follow", "mention", "favourite", "reblog"]})
426
427 result =
428 conn
429 |> get("/api/v1/notifications?" <> query)
430 |> json_response_and_validate_schema(200)
431
432 assert length(result) == 4
433 end
434
435 test "destroy multiple" do
436 %{user: user, conn: conn} = oauth_access(["read:notifications", "write:notifications"])
437 other_user = insert(:user)
438
439 {:ok, activity1} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
440 {:ok, activity2} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
441 {:ok, activity3} = CommonAPI.post(user, %{status: "hi @#{other_user.nickname}"})
442 {:ok, activity4} = CommonAPI.post(user, %{status: "hi @#{other_user.nickname}"})
443
444 notification1_id = get_notification_id_by_activity(activity1)
445 notification2_id = get_notification_id_by_activity(activity2)
446 notification3_id = get_notification_id_by_activity(activity3)
447 notification4_id = get_notification_id_by_activity(activity4)
448
449 result =
450 conn
451 |> get("/api/v1/notifications")
452 |> json_response_and_validate_schema(:ok)
453
454 assert [%{"id" => ^notification2_id}, %{"id" => ^notification1_id}] = result
455
456 conn2 =
457 conn
458 |> assign(:user, other_user)
459 |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:notifications"]))
460
461 result =
462 conn2
463 |> get("/api/v1/notifications")
464 |> json_response_and_validate_schema(:ok)
465
466 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
467
468 query = params_to_query(%{ids: [notification1_id, notification2_id]})
469 conn_destroy = delete(conn, "/api/v1/notifications/destroy_multiple?" <> query)
470
471 assert json_response_and_validate_schema(conn_destroy, 200) == %{}
472
473 result =
474 conn2
475 |> get("/api/v1/notifications")
476 |> json_response_and_validate_schema(:ok)
477
478 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
479 end
480
481 test "doesn't see notifications after muting user with notifications" do
482 %{user: user, conn: conn} = oauth_access(["read:notifications"])
483 user2 = insert(:user)
484
485 {:ok, _, _, _} = CommonAPI.follow(user, user2)
486 {:ok, _} = CommonAPI.post(user2, %{status: "hey @#{user.nickname}"})
487
488 ret_conn = get(conn, "/api/v1/notifications")
489
490 assert length(json_response_and_validate_schema(ret_conn, 200)) == 1
491
492 {:ok, _user_relationships} = User.mute(user, user2)
493
494 conn = get(conn, "/api/v1/notifications")
495
496 assert json_response_and_validate_schema(conn, 200) == []
497 end
498
499 test "see notifications after muting user without notifications" do
500 %{user: user, conn: conn} = oauth_access(["read:notifications"])
501 user2 = insert(:user)
502
503 {:ok, _, _, _} = CommonAPI.follow(user, user2)
504 {:ok, _} = CommonAPI.post(user2, %{status: "hey @#{user.nickname}"})
505
506 ret_conn = get(conn, "/api/v1/notifications")
507
508 assert length(json_response_and_validate_schema(ret_conn, 200)) == 1
509
510 {:ok, _user_relationships} = User.mute(user, user2, %{notifications: false})
511
512 conn = get(conn, "/api/v1/notifications")
513
514 assert length(json_response_and_validate_schema(conn, 200)) == 1
515 end
516
517 test "see notifications after muting user with notifications and with_muted parameter" do
518 %{user: user, conn: conn} = oauth_access(["read:notifications"])
519 user2 = insert(:user)
520
521 {:ok, _, _, _} = CommonAPI.follow(user, user2)
522 {:ok, _} = CommonAPI.post(user2, %{status: "hey @#{user.nickname}"})
523
524 ret_conn = get(conn, "/api/v1/notifications")
525
526 assert length(json_response_and_validate_schema(ret_conn, 200)) == 1
527
528 {:ok, _user_relationships} = User.mute(user, user2)
529
530 conn = get(conn, "/api/v1/notifications?with_muted=true")
531
532 assert length(json_response_and_validate_schema(conn, 200)) == 1
533 end
534
535 test "see move notifications" do
536 old_user = insert(:user)
537 new_user = insert(:user, also_known_as: [old_user.ap_id])
538 %{user: follower, conn: conn} = oauth_access(["read:notifications"])
539
540 User.follow(follower, old_user)
541 Pleroma.Web.ActivityPub.ActivityPub.move(old_user, new_user)
542 Pleroma.Tests.ObanHelpers.perform_all()
543
544 conn = get(conn, "/api/v1/notifications")
545
546 assert length(json_response_and_validate_schema(conn, 200)) == 1
547 end
548
549 describe "link headers" do
550 test "preserves parameters in link headers" do
551 %{user: user, conn: conn} = oauth_access(["read:notifications"])
552 other_user = insert(:user)
553
554 {:ok, activity1} =
555 CommonAPI.post(other_user, %{
556 status: "hi @#{user.nickname}",
557 visibility: "public"
558 })
559
560 {:ok, activity2} =
561 CommonAPI.post(other_user, %{
562 status: "hi @#{user.nickname}",
563 visibility: "public"
564 })
565
566 notification1 = Repo.get_by(Notification, activity_id: activity1.id)
567 notification2 = Repo.get_by(Notification, activity_id: activity2.id)
568
569 conn =
570 conn
571 |> assign(:user, user)
572 |> get("/api/v1/notifications?limit=5")
573
574 assert [link_header] = get_resp_header(conn, "link")
575 assert link_header =~ ~r/limit=5/
576 assert link_header =~ ~r/min_id=#{notification2.id}/
577 assert link_header =~ ~r/max_id=#{notification1.id}/
578 end
579 end
580
581 describe "from specified user" do
582 test "account_id" do
583 %{user: user, conn: conn} = oauth_access(["read:notifications"])
584
585 %{id: account_id} = other_user1 = insert(:user)
586 other_user2 = insert(:user)
587
588 {:ok, _activity} = CommonAPI.post(other_user1, %{status: "hi @#{user.nickname}"})
589 {:ok, _activity} = CommonAPI.post(other_user2, %{status: "bye @#{user.nickname}"})
590
591 assert [%{"account" => %{"id" => ^account_id}}] =
592 conn
593 |> assign(:user, user)
594 |> get("/api/v1/notifications?account_id=#{account_id}")
595 |> json_response_and_validate_schema(200)
596
597 assert %{"error" => "Account is not found"} =
598 conn
599 |> assign(:user, user)
600 |> get("/api/v1/notifications?account_id=cofe")
601 |> json_response_and_validate_schema(404)
602 end
603 end
604
605 defp get_notification_id_by_activity(%{id: id}) do
606 Notification
607 |> Repo.get_by(activity_id: id)
608 |> Map.get(:id)
609 |> to_string()
610 end
611
612 defp params_to_query(%{} = params) do
613 Enum.map_join(params, "&", fn
614 {k, v} when is_list(v) -> Enum.map_join(v, "&", &"#{k}[]=#{&1}")
615 {k, v} -> k <> "=" <> v
616 end)
617 end
618 end