07b7fc74709d904ec50e32d68182cabe4bd86e9f
[oweals/peertube.git] / server / tests / api / users / users.ts
1 /* tslint:disable:no-unused-expression */
2
3 import * as chai from 'chai'
4 import 'mocha'
5 import { User, UserRole, Video } from '../../../../shared/index'
6 import {
7   blockUser,
8   cleanupTests,
9   createUser,
10   deleteMe,
11   flushAndRunServer,
12   getAccountRatings,
13   getBlacklistedVideosList,
14   getMyUserInformation,
15   getMyUserVideoQuotaUsed,
16   getMyUserVideoRating,
17   getUserInformation,
18   getUsersList,
19   getUsersListPaginationAndSort,
20   getVideoChannel,
21   getVideosList, installPlugin,
22   login,
23   makePutBodyRequest,
24   rateVideo,
25   registerUserWithChannel,
26   removeUser,
27   removeVideo,
28   ServerInfo,
29   testImage,
30   unblockUser,
31   updateMyAvatar,
32   updateMyUser,
33   updateUser,
34   uploadVideo,
35   userLogin
36 } from '../../../../shared/extra-utils'
37 import { follow } from '../../../../shared/extra-utils/server/follows'
38 import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login'
39 import { getMyVideos } from '../../../../shared/extra-utils/videos/videos'
40 import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model'
41
42 const expect = chai.expect
43
44 describe('Test users', function () {
45   let server: ServerInfo
46   let accessToken: string
47   let accessTokenUser: string
48   let videoId: number
49   let userId: number
50   const user = {
51     username: 'user_1',
52     password: 'super password'
53   }
54
55   before(async function () {
56     this.timeout(30000)
57     server = await flushAndRunServer(1)
58
59     await setAccessTokensToServers([ server ])
60
61     await installPlugin({ url: server.url, accessToken: server.accessToken, npmName: 'peertube-theme-background-red' })
62   })
63
64   describe('OAuth client', function () {
65     it('Should create a new client')
66
67     it('Should return the first client')
68
69     it('Should remove the last client')
70
71     it('Should not login with an invalid client id', async function () {
72       const client = { id: 'client', secret: server.client.secret }
73       const res = await login(server.url, client, server.user, 400)
74
75       expect(res.body.error).to.contain('client is invalid')
76     })
77
78     it('Should not login with an invalid client secret', async function () {
79       const client = { id: server.client.id, secret: 'coucou' }
80       const res = await login(server.url, client, server.user, 400)
81
82       expect(res.body.error).to.contain('client is invalid')
83     })
84   })
85
86   describe('Login', function () {
87
88     it('Should not login with an invalid username', async function () {
89       const user = { username: 'captain crochet', password: server.user.password }
90       const res = await login(server.url, server.client, user, 400)
91
92       expect(res.body.error).to.contain('credentials are invalid')
93     })
94
95     it('Should not login with an invalid password', async function () {
96       const user = { username: server.user.username, password: 'mew_three' }
97       const res = await login(server.url, server.client, user, 400)
98
99       expect(res.body.error).to.contain('credentials are invalid')
100     })
101
102     it('Should not be able to upload a video', async function () {
103       accessToken = 'my_super_token'
104
105       const videoAttributes = {}
106       await uploadVideo(server.url, accessToken, videoAttributes, 401)
107     })
108
109     it('Should not be able to follow', async function () {
110       accessToken = 'my_super_token'
111       await follow(server.url, [ 'http://example.com' ], accessToken, 401)
112     })
113
114     it('Should not be able to unfollow')
115
116     it('Should be able to login', async function () {
117       const res = await login(server.url, server.client, server.user, 200)
118
119       accessToken = res.body.access_token
120     })
121
122     it('Should be able to login with an insensitive username', async function () {
123       const user = { username: 'RoOt', password: server.user.password }
124       const res = await login(server.url, server.client, user, 200)
125
126       const user2 = { username: 'rOoT', password: server.user.password }
127       const res2 = await login(server.url, server.client, user2, 200)
128
129       const user3 = { username: 'ROOt', password: server.user.password }
130       const res3 = await login(server.url, server.client, user3, 200)
131     })
132   })
133
134   describe('Upload', function () {
135
136     it('Should upload the video with the correct token', async function () {
137       const videoAttributes = {}
138       await uploadVideo(server.url, accessToken, videoAttributes)
139       const res = await getVideosList(server.url)
140       const video = res.body.data[ 0 ]
141
142       expect(video.account.name).to.equal('root')
143       videoId = video.id
144     })
145
146     it('Should upload the video again with the correct token', async function () {
147       const videoAttributes = {}
148       await uploadVideo(server.url, accessToken, videoAttributes)
149     })
150   })
151
152   describe('Ratings', function () {
153
154     it('Should retrieve a video rating', async function () {
155       await rateVideo(server.url, accessToken, videoId, 'like')
156       const res = await getMyUserVideoRating(server.url, accessToken, videoId)
157       const rating = res.body
158
159       expect(rating.videoId).to.equal(videoId)
160       expect(rating.rating).to.equal('like')
161     })
162
163     it('Should retrieve ratings list', async function () {
164       await rateVideo(server.url, accessToken, videoId, 'like')
165
166       const res = await getAccountRatings(server.url, server.user.username, server.accessToken, null, 200)
167       const ratings = res.body
168
169       expect(ratings.total).to.equal(1)
170       expect(ratings.data[ 0 ].video.id).to.equal(videoId)
171       expect(ratings.data[ 0 ].rating).to.equal('like')
172     })
173
174     it('Should retrieve ratings list by rating type', async function () {
175       {
176         const res = await getAccountRatings(server.url, server.user.username, server.accessToken, 'like')
177         const ratings = res.body
178         expect(ratings.data.length).to.equal(1)
179       }
180
181       {
182         const res = await getAccountRatings(server.url, server.user.username, server.accessToken, 'dislike')
183         const ratings = res.body
184         expect(ratings.data.length).to.equal(0)
185       }
186     })
187   })
188
189   describe('Remove video', function () {
190     it('Should not be able to remove the video with an incorrect token', async function () {
191       await removeVideo(server.url, 'bad_token', videoId, 401)
192     })
193
194     it('Should not be able to remove the video with the token of another account')
195
196     it('Should be able to remove the video with the correct token', async function () {
197       await removeVideo(server.url, accessToken, videoId)
198     })
199   })
200
201   describe('Logout', function () {
202     it('Should logout (revoke token)')
203
204     it('Should not be able to get the user information')
205
206     it('Should not be able to upload a video')
207
208     it('Should not be able to remove a video')
209
210     it('Should not be able to rate a video', async function () {
211       const path = '/api/v1/videos/'
212       const data = {
213         rating: 'likes'
214       }
215
216       const options = {
217         url: server.url,
218         path: path + videoId,
219         token: 'wrong token',
220         fields: data,
221         statusCodeExpected: 401
222       }
223       await makePutBodyRequest(options)
224     })
225
226     it('Should be able to login again')
227
228     it('Should have an expired access token')
229
230     it('Should refresh the token')
231
232     it('Should be able to upload a video again')
233   })
234
235   describe('Creating a user', function () {
236
237     it('Should be able to create a new user', async function () {
238       await createUser({
239         url: server.url,
240         accessToken: accessToken,
241         username: user.username,
242         password: user.password,
243         videoQuota: 2 * 1024 * 1024,
244         adminFlags: UserAdminFlag.BY_PASS_VIDEO_AUTO_BLACKLIST
245       })
246     })
247
248     it('Should be able to login with this user', async function () {
249       accessTokenUser = await userLogin(server, user)
250     })
251
252     it('Should be able to get user information', async function () {
253       const res1 = await getMyUserInformation(server.url, accessTokenUser)
254       const userMe: User = res1.body
255
256       const res2 = await getUserInformation(server.url, server.accessToken, userMe.id)
257       const userGet: User = res2.body
258
259       for (const user of [ userMe, userGet ]) {
260         expect(user.username).to.equal('user_1')
261         expect(user.email).to.equal('user_1@example.com')
262         expect(user.nsfwPolicy).to.equal('display')
263         expect(user.videoQuota).to.equal(2 * 1024 * 1024)
264         expect(user.roleLabel).to.equal('User')
265         expect(user.id).to.be.a('number')
266         expect(user.account.displayName).to.equal('user_1')
267         expect(user.account.description).to.be.null
268       }
269
270       expect(userMe.adminFlags).to.be.undefined
271       expect(userGet.adminFlags).to.equal(UserAdminFlag.BY_PASS_VIDEO_AUTO_BLACKLIST)
272     })
273   })
274
275   describe('My videos & quotas', function () {
276
277     it('Should be able to upload a video with this user', async function () {
278       this.timeout(5000)
279
280       const videoAttributes = {
281         name: 'super user video',
282         fixture: 'video_short.webm'
283       }
284       await uploadVideo(server.url, accessTokenUser, videoAttributes)
285     })
286
287     it('Should have video quota updated', async function () {
288       const res = await getMyUserVideoQuotaUsed(server.url, accessTokenUser)
289       const data = res.body
290
291       expect(data.videoQuotaUsed).to.equal(218910)
292
293       const resUsers = await getUsersList(server.url, server.accessToken)
294
295       const users: User[] = resUsers.body.data
296       const tmpUser = users.find(u => u.username === user.username)
297       expect(tmpUser.videoQuotaUsed).to.equal(218910)
298     })
299
300     it('Should be able to list my videos', async function () {
301       const res = await getMyVideos(server.url, accessTokenUser, 0, 5)
302       expect(res.body.total).to.equal(1)
303
304       const videos = res.body.data
305       expect(videos).to.have.lengthOf(1)
306
307       const video: Video = videos[ 0 ]
308       expect(video.name).to.equal('super user video')
309       expect(video.thumbnailPath).to.not.be.null
310       expect(video.previewPath).to.not.be.null
311     })
312
313     it('Should be able to search in my videos', async function () {
314       {
315         const res = await getMyVideos(server.url, accessTokenUser, 0, 5, '-createdAt', 'user video')
316         expect(res.body.total).to.equal(1)
317
318         const videos = res.body.data
319         expect(videos).to.have.lengthOf(1)
320       }
321
322       {
323         const res = await getMyVideos(server.url, accessTokenUser, 0, 5, '-createdAt', 'toto')
324         expect(res.body.total).to.equal(0)
325
326         const videos = res.body.data
327         expect(videos).to.have.lengthOf(0)
328       }
329     })
330   })
331
332   describe('Users listing', function () {
333
334     it('Should list all the users', async function () {
335       const res = await getUsersList(server.url, server.accessToken)
336       const result = res.body
337       const total = result.total
338       const users = result.data
339
340       expect(total).to.equal(2)
341       expect(users).to.be.an('array')
342       expect(users.length).to.equal(2)
343
344       const user = users[ 0 ]
345       expect(user.username).to.equal('user_1')
346       expect(user.email).to.equal('user_1@example.com')
347       expect(user.nsfwPolicy).to.equal('display')
348
349       const rootUser = users[ 1 ]
350       expect(rootUser.username).to.equal('root')
351       expect(rootUser.email).to.equal('admin' + server.internalServerNumber + '@example.com')
352       expect(user.nsfwPolicy).to.equal('display')
353
354       userId = user.id
355     })
356
357     it('Should list only the first user by username asc', async function () {
358       const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 1, 'username')
359
360       const result = res.body
361       const total = result.total
362       const users = result.data
363
364       expect(total).to.equal(2)
365       expect(users.length).to.equal(1)
366
367       const user = users[ 0 ]
368       expect(user.username).to.equal('root')
369       expect(user.email).to.equal('admin' + server.internalServerNumber + '@example.com')
370       expect(user.roleLabel).to.equal('Administrator')
371       expect(user.nsfwPolicy).to.equal('display')
372     })
373
374     it('Should list only the first user by username desc', async function () {
375       const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 1, '-username')
376       const result = res.body
377       const total = result.total
378       const users = result.data
379
380       expect(total).to.equal(2)
381       expect(users.length).to.equal(1)
382
383       const user = users[ 0 ]
384       expect(user.username).to.equal('user_1')
385       expect(user.email).to.equal('user_1@example.com')
386       expect(user.nsfwPolicy).to.equal('display')
387     })
388
389     it('Should list only the second user by createdAt desc', async function () {
390       const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 1, '-createdAt')
391       const result = res.body
392       const total = result.total
393       const users = result.data
394
395       expect(total).to.equal(2)
396       expect(users.length).to.equal(1)
397
398       const user = users[ 0 ]
399       expect(user.username).to.equal('user_1')
400       expect(user.email).to.equal('user_1@example.com')
401       expect(user.nsfwPolicy).to.equal('display')
402     })
403
404     it('Should list all the users by createdAt asc', async function () {
405       const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 2, 'createdAt')
406       const result = res.body
407       const total = result.total
408       const users = result.data
409
410       expect(total).to.equal(2)
411       expect(users.length).to.equal(2)
412
413       expect(users[ 0 ].username).to.equal('root')
414       expect(users[ 0 ].email).to.equal('admin' + server.internalServerNumber + '@example.com')
415       expect(users[ 0 ].nsfwPolicy).to.equal('display')
416
417       expect(users[ 1 ].username).to.equal('user_1')
418       expect(users[ 1 ].email).to.equal('user_1@example.com')
419       expect(users[ 1 ].nsfwPolicy).to.equal('display')
420     })
421
422     it('Should search user by username', async function () {
423       const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 2, 'createdAt', 'oot')
424       const users = res.body.data as User[]
425
426       expect(res.body.total).to.equal(1)
427       expect(users.length).to.equal(1)
428
429       expect(users[ 0 ].username).to.equal('root')
430     })
431
432     it('Should search user by email', async function () {
433       {
434         const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 2, 'createdAt', 'r_1@exam')
435         const users = res.body.data as User[]
436
437         expect(res.body.total).to.equal(1)
438         expect(users.length).to.equal(1)
439
440         expect(users[ 0 ].username).to.equal('user_1')
441         expect(users[ 0 ].email).to.equal('user_1@example.com')
442       }
443
444       {
445         const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 2, 'createdAt', 'example')
446         const users = res.body.data as User[]
447
448         expect(res.body.total).to.equal(2)
449         expect(users.length).to.equal(2)
450
451         expect(users[ 0 ].username).to.equal('root')
452         expect(users[ 1 ].username).to.equal('user_1')
453       }
454     })
455   })
456
457   describe('Update my account', function () {
458     it('Should update my password', async function () {
459       await updateMyUser({
460         url: server.url,
461         accessToken: accessTokenUser,
462         currentPassword: 'super password',
463         password: 'new password'
464       })
465       user.password = 'new password'
466
467       await userLogin(server, user, 200)
468     })
469
470     it('Should be able to change the NSFW display attribute', async function () {
471       await updateMyUser({
472         url: server.url,
473         accessToken: accessTokenUser,
474         nsfwPolicy: 'do_not_list'
475       })
476
477       const res = await getMyUserInformation(server.url, accessTokenUser)
478       const user = res.body
479
480       expect(user.username).to.equal('user_1')
481       expect(user.email).to.equal('user_1@example.com')
482       expect(user.nsfwPolicy).to.equal('do_not_list')
483       expect(user.videoQuota).to.equal(2 * 1024 * 1024)
484       expect(user.id).to.be.a('number')
485       expect(user.account.displayName).to.equal('user_1')
486       expect(user.account.description).to.be.null
487     })
488
489     it('Should be able to change the autoPlayVideo attribute', async function () {
490       await updateMyUser({
491         url: server.url,
492         accessToken: accessTokenUser,
493         autoPlayVideo: false
494       })
495
496       const res = await getMyUserInformation(server.url, accessTokenUser)
497       const user = res.body
498
499       expect(user.autoPlayVideo).to.be.false
500     })
501
502     it('Should be able to change the autoPlayNextVideo attribute', async function () {
503       await updateMyUser({
504         url: server.url,
505         accessToken: accessTokenUser,
506         autoPlayNextVideo: true
507       })
508
509       const res = await getMyUserInformation(server.url, accessTokenUser)
510       const user = res.body
511
512       expect(user.autoPlayNextVideo).to.be.true
513     })
514
515     it('Should be able to change the email attribute', async function () {
516       await updateMyUser({
517         url: server.url,
518         accessToken: accessTokenUser,
519         currentPassword: 'new password',
520         email: 'updated@example.com'
521       })
522
523       const res = await getMyUserInformation(server.url, accessTokenUser)
524       const user = res.body
525
526       expect(user.username).to.equal('user_1')
527       expect(user.email).to.equal('updated@example.com')
528       expect(user.nsfwPolicy).to.equal('do_not_list')
529       expect(user.videoQuota).to.equal(2 * 1024 * 1024)
530       expect(user.id).to.be.a('number')
531       expect(user.account.displayName).to.equal('user_1')
532       expect(user.account.description).to.be.null
533     })
534
535     it('Should be able to update my avatar', async function () {
536       const fixture = 'avatar.png'
537
538       await updateMyAvatar({
539         url: server.url,
540         accessToken: accessTokenUser,
541         fixture
542       })
543
544       const res = await getMyUserInformation(server.url, accessTokenUser)
545       const user = res.body
546
547       await testImage(server.url, 'avatar-resized', user.account.avatar.path, '.png')
548     })
549
550     it('Should be able to update my display name', async function () {
551       await updateMyUser({
552         url: server.url,
553         accessToken: accessTokenUser,
554         displayName: 'new display name'
555       })
556
557       const res = await getMyUserInformation(server.url, accessTokenUser)
558       const user = res.body
559
560       expect(user.username).to.equal('user_1')
561       expect(user.email).to.equal('updated@example.com')
562       expect(user.nsfwPolicy).to.equal('do_not_list')
563       expect(user.videoQuota).to.equal(2 * 1024 * 1024)
564       expect(user.id).to.be.a('number')
565       expect(user.account.displayName).to.equal('new display name')
566       expect(user.account.description).to.be.null
567     })
568
569     it('Should be able to update my description', async function () {
570       await updateMyUser({
571         url: server.url,
572         accessToken: accessTokenUser,
573         description: 'my super description updated'
574       })
575
576       const res = await getMyUserInformation(server.url, accessTokenUser)
577       const user: User = res.body
578
579       expect(user.username).to.equal('user_1')
580       expect(user.email).to.equal('updated@example.com')
581       expect(user.nsfwPolicy).to.equal('do_not_list')
582       expect(user.videoQuota).to.equal(2 * 1024 * 1024)
583       expect(user.id).to.be.a('number')
584       expect(user.account.displayName).to.equal('new display name')
585       expect(user.account.description).to.equal('my super description updated')
586       expect(user.noWelcomeModal).to.be.false
587       expect(user.noInstanceConfigWarningModal).to.be.false
588     })
589
590     it('Should be able to update my theme', async function () {
591       for (const theme of [ 'background-red', 'default', 'instance-default' ]) {
592         await updateMyUser({
593           url: server.url,
594           accessToken: accessTokenUser,
595           theme
596         })
597
598         const res = await getMyUserInformation(server.url, accessTokenUser)
599         const body: User = res.body
600
601         expect(body.theme).to.equal(theme)
602       }
603     })
604
605     it('Should be able to update my modal preferences', async function () {
606       await updateMyUser({
607         url: server.url,
608         accessToken: accessTokenUser,
609         noInstanceConfigWarningModal: true,
610         noWelcomeModal: true
611       })
612
613       const res = await getMyUserInformation(server.url, accessTokenUser)
614       const user: User = res.body
615
616       expect(user.noWelcomeModal).to.be.true
617       expect(user.noInstanceConfigWarningModal).to.be.true
618     })
619   })
620
621   describe('Updating another user', function () {
622
623     it('Should be able to update another user', async function () {
624       await updateUser({
625         url: server.url,
626         userId,
627         accessToken,
628         email: 'updated2@example.com',
629         emailVerified: true,
630         videoQuota: 42,
631         role: UserRole.MODERATOR,
632         adminFlags: UserAdminFlag.NONE
633       })
634
635       const res = await getUserInformation(server.url, accessToken, userId)
636       const user = res.body
637
638       expect(user.username).to.equal('user_1')
639       expect(user.email).to.equal('updated2@example.com')
640       expect(user.emailVerified).to.be.true
641       expect(user.nsfwPolicy).to.equal('do_not_list')
642       expect(user.videoQuota).to.equal(42)
643       expect(user.roleLabel).to.equal('Moderator')
644       expect(user.id).to.be.a('number')
645       expect(user.adminFlags).to.equal(UserAdminFlag.NONE)
646     })
647
648     it('Should have removed the user token', async function () {
649       await getMyUserVideoQuotaUsed(server.url, accessTokenUser, 401)
650
651       accessTokenUser = await userLogin(server, user)
652     })
653
654     it('Should be able to update another user password', async function () {
655       await updateUser({
656         url: server.url,
657         userId,
658         accessToken,
659         password: 'password updated'
660       })
661
662       await getMyUserVideoQuotaUsed(server.url, accessTokenUser, 401)
663
664       await userLogin(server, user, 400)
665
666       user.password = 'password updated'
667       accessTokenUser = await userLogin(server, user)
668     })
669   })
670
671   describe('Video blacklists', function () {
672     it('Should be able to list video blacklist by a moderator', async function () {
673       await getBlacklistedVideosList({ url: server.url, token: accessTokenUser })
674     })
675   })
676
677   describe('Remove a user', function () {
678     it('Should be able to remove this user', async function () {
679       await removeUser(server.url, userId, accessToken)
680     })
681
682     it('Should not be able to login with this user', async function () {
683       await userLogin(server, user, 400)
684     })
685
686     it('Should not have videos of this user', async function () {
687       const res = await getVideosList(server.url)
688
689       expect(res.body.total).to.equal(1)
690
691       const video = res.body.data[ 0 ]
692       expect(video.account.name).to.equal('root')
693     })
694   })
695
696   describe('Registering a new user', function () {
697     it('Should register a new user', async function () {
698       const user = { displayName: 'super user 15', username: 'user_15', password: 'my super password' }
699       const channel = { name: 'my_user_15_channel', displayName: 'my channel rocks' }
700
701       await registerUserWithChannel({ url: server.url, user, channel })
702     })
703
704     it('Should be able to login with this registered user', async function () {
705       const user15 = {
706         username: 'user_15',
707         password: 'my super password'
708       }
709
710       accessToken = await userLogin(server, user15)
711     })
712
713     it('Should have the correct display name', async function () {
714       const res = await getMyUserInformation(server.url, accessToken)
715       const user: User = res.body
716
717       expect(user.account.displayName).to.equal('super user 15')
718     })
719
720     it('Should have the correct video quota', async function () {
721       const res = await getMyUserInformation(server.url, accessToken)
722       const user = res.body
723
724       expect(user.videoQuota).to.equal(5 * 1024 * 1024)
725     })
726
727     it('Should have created the channel', async function () {
728       const res = await getVideoChannel(server.url, 'my_user_15_channel')
729
730       expect(res.body.displayName).to.equal('my channel rocks')
731     })
732
733     it('Should remove me', async function () {
734       {
735         const res = await getUsersList(server.url, server.accessToken)
736         expect(res.body.data.find(u => u.username === 'user_15')).to.not.be.undefined
737       }
738
739       await deleteMe(server.url, accessToken)
740
741       {
742         const res = await getUsersList(server.url, server.accessToken)
743         expect(res.body.data.find(u => u.username === 'user_15')).to.be.undefined
744       }
745     })
746   })
747
748   describe('User blocking', function () {
749     it('Should block and unblock a user', async function () {
750       const user16 = {
751         username: 'user_16',
752         password: 'my super password'
753       }
754       const resUser = await createUser({
755         url: server.url,
756         accessToken: server.accessToken,
757         username: user16.username,
758         password: user16.password
759       })
760       const user16Id = resUser.body.user.id
761
762       accessToken = await userLogin(server, user16)
763
764       await getMyUserInformation(server.url, accessToken, 200)
765       await blockUser(server.url, user16Id, server.accessToken)
766
767       await getMyUserInformation(server.url, accessToken, 401)
768       await userLogin(server, user16, 400)
769
770       await unblockUser(server.url, user16Id, server.accessToken)
771       accessToken = await userLogin(server, user16)
772       await getMyUserInformation(server.url, accessToken, 200)
773     })
774   })
775
776   after(async function () {
777     await cleanupTests([ server ])
778   })
779 })