rename blacklist to block/blocklist, merge block and auto-block views
[oweals/peertube.git] / shared / extra-utils / users / user-notifications.ts
1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3 import { makeGetRequest, makePostBodyRequest, makePutBodyRequest } from '../requests/requests'
4 import { UserNotification, UserNotificationSetting, UserNotificationType } from '../../models/users'
5 import { ServerInfo } from '..'
6 import { expect } from 'chai'
7 import { inspect } from 'util'
8
9 function updateMyNotificationSettings (url: string, token: string, settings: UserNotificationSetting, statusCodeExpected = 204) {
10   const path = '/api/v1/users/me/notification-settings'
11
12   return makePutBodyRequest({
13     url,
14     path,
15     token,
16     fields: settings,
17     statusCodeExpected
18   })
19 }
20
21 async function getUserNotifications (
22   url: string,
23   token: string,
24   start: number,
25   count: number,
26   unread?: boolean,
27   sort = '-createdAt',
28   statusCodeExpected = 200
29 ) {
30   const path = '/api/v1/users/me/notifications'
31
32   return makeGetRequest({
33     url,
34     path,
35     token,
36     query: {
37       start,
38       count,
39       sort,
40       unread
41     },
42     statusCodeExpected
43   })
44 }
45
46 function markAsReadNotifications (url: string, token: string, ids: number[], statusCodeExpected = 204) {
47   const path = '/api/v1/users/me/notifications/read'
48
49   return makePostBodyRequest({
50     url,
51     path,
52     token,
53     fields: { ids },
54     statusCodeExpected
55   })
56 }
57
58 function markAsReadAllNotifications (url: string, token: string, statusCodeExpected = 204) {
59   const path = '/api/v1/users/me/notifications/read-all'
60
61   return makePostBodyRequest({
62     url,
63     path,
64     token,
65     statusCodeExpected
66   })
67 }
68
69 async function getLastNotification (serverUrl: string, accessToken: string) {
70   const res = await getUserNotifications(serverUrl, accessToken, 0, 1, undefined, '-createdAt')
71
72   if (res.body.total === 0) return undefined
73
74   return res.body.data[0] as UserNotification
75 }
76
77 type CheckerBaseParams = {
78   server: ServerInfo
79   emails: object[]
80   socketNotifications: UserNotification[]
81   token: string
82   check?: { web: boolean, mail: boolean }
83 }
84
85 type CheckerType = 'presence' | 'absence'
86
87 async function checkNotification (
88   base: CheckerBaseParams,
89   notificationChecker: (notification: UserNotification, type: CheckerType) => void,
90   emailNotificationFinder: (email: object) => boolean,
91   checkType: CheckerType
92 ) {
93   const check = base.check || { web: true, mail: true }
94
95   if (check.web) {
96     const notification = await getLastNotification(base.server.url, base.token)
97
98     if (notification || checkType !== 'absence') {
99       notificationChecker(notification, checkType)
100     }
101
102     const socketNotification = base.socketNotifications.find(n => {
103       try {
104         notificationChecker(n, 'presence')
105         return true
106       } catch {
107         return false
108       }
109     })
110
111     if (checkType === 'presence') {
112       const obj = inspect(base.socketNotifications, { depth: 5 })
113       expect(socketNotification, 'The socket notification is absent when is should be present. ' + obj).to.not.be.undefined
114     } else {
115       const obj = inspect(socketNotification, { depth: 5 })
116       expect(socketNotification, 'The socket notification is present when is should not be present. ' + obj).to.be.undefined
117     }
118   }
119
120   if (check.mail) {
121     // Last email
122     const email = base.emails
123                       .slice()
124                       .reverse()
125                       .find(e => emailNotificationFinder(e))
126
127     if (checkType === 'presence') {
128       expect(email, 'The email is absent when is should be present. ' + inspect(base.emails)).to.not.be.undefined
129     } else {
130       expect(email, 'The email is present when is should not be present. ' + inspect(email)).to.be.undefined
131     }
132   }
133 }
134
135 function checkVideo (video: any, videoName?: string, videoUUID?: string) {
136   expect(video.name).to.be.a('string')
137   expect(video.name).to.not.be.empty
138   if (videoName) expect(video.name).to.equal(videoName)
139
140   expect(video.uuid).to.be.a('string')
141   expect(video.uuid).to.not.be.empty
142   if (videoUUID) expect(video.uuid).to.equal(videoUUID)
143
144   expect(video.id).to.be.a('number')
145 }
146
147 function checkActor (actor: any) {
148   expect(actor.displayName).to.be.a('string')
149   expect(actor.displayName).to.not.be.empty
150   expect(actor.host).to.not.be.undefined
151 }
152
153 function checkComment (comment: any, commentId: number, threadId: number) {
154   expect(comment.id).to.equal(commentId)
155   expect(comment.threadId).to.equal(threadId)
156 }
157
158 async function checkNewVideoFromSubscription (base: CheckerBaseParams, videoName: string, videoUUID: string, type: CheckerType) {
159   const notificationType = UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION
160
161   function notificationChecker (notification: UserNotification, type: CheckerType) {
162     if (type === 'presence') {
163       expect(notification).to.not.be.undefined
164       expect(notification.type).to.equal(notificationType)
165
166       checkVideo(notification.video, videoName, videoUUID)
167       checkActor(notification.video.channel)
168     } else {
169       expect(notification).to.satisfy((n: UserNotification) => {
170         return n === undefined || n.type !== UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION || n.video.name !== videoName
171       })
172     }
173   }
174
175   function emailNotificationFinder (email: object) {
176     const text = email['text']
177     return text.indexOf(videoUUID) !== -1 && text.indexOf('Your subscription') !== -1
178   }
179
180   await checkNotification(base, notificationChecker, emailNotificationFinder, type)
181 }
182
183 async function checkVideoIsPublished (base: CheckerBaseParams, videoName: string, videoUUID: string, type: CheckerType) {
184   const notificationType = UserNotificationType.MY_VIDEO_PUBLISHED
185
186   function notificationChecker (notification: UserNotification, type: CheckerType) {
187     if (type === 'presence') {
188       expect(notification).to.not.be.undefined
189       expect(notification.type).to.equal(notificationType)
190
191       checkVideo(notification.video, videoName, videoUUID)
192       checkActor(notification.video.channel)
193     } else {
194       expect(notification.video).to.satisfy(v => v === undefined || v.name !== videoName)
195     }
196   }
197
198   function emailNotificationFinder (email: object) {
199     const text: string = email['text']
200     return text.includes(videoUUID) && text.includes('Your video')
201   }
202
203   await checkNotification(base, notificationChecker, emailNotificationFinder, type)
204 }
205
206 async function checkMyVideoImportIsFinished (
207   base: CheckerBaseParams,
208   videoName: string,
209   videoUUID: string,
210   url: string,
211   success: boolean,
212   type: CheckerType
213 ) {
214   const notificationType = success ? UserNotificationType.MY_VIDEO_IMPORT_SUCCESS : UserNotificationType.MY_VIDEO_IMPORT_ERROR
215
216   function notificationChecker (notification: UserNotification, type: CheckerType) {
217     if (type === 'presence') {
218       expect(notification).to.not.be.undefined
219       expect(notification.type).to.equal(notificationType)
220
221       expect(notification.videoImport.targetUrl).to.equal(url)
222
223       if (success) checkVideo(notification.videoImport.video, videoName, videoUUID)
224     } else {
225       expect(notification.videoImport).to.satisfy(i => i === undefined || i.targetUrl !== url)
226     }
227   }
228
229   function emailNotificationFinder (email: object) {
230     const text: string = email['text']
231     const toFind = success ? ' finished' : ' error'
232
233     return text.includes(url) && text.includes(toFind)
234   }
235
236   await checkNotification(base, notificationChecker, emailNotificationFinder, type)
237 }
238
239 async function checkUserRegistered (base: CheckerBaseParams, username: string, type: CheckerType) {
240   const notificationType = UserNotificationType.NEW_USER_REGISTRATION
241
242   function notificationChecker (notification: UserNotification, type: CheckerType) {
243     if (type === 'presence') {
244       expect(notification).to.not.be.undefined
245       expect(notification.type).to.equal(notificationType)
246
247       checkActor(notification.account)
248       expect(notification.account.name).to.equal(username)
249     } else {
250       expect(notification).to.satisfy(n => n.type !== notificationType || n.account.name !== username)
251     }
252   }
253
254   function emailNotificationFinder (email: object) {
255     const text: string = email['text']
256
257     return text.includes(' registered.') && text.includes(username)
258   }
259
260   await checkNotification(base, notificationChecker, emailNotificationFinder, type)
261 }
262
263 async function checkNewActorFollow (
264   base: CheckerBaseParams,
265   followType: 'channel' | 'account',
266   followerName: string,
267   followerDisplayName: string,
268   followingDisplayName: string,
269   type: CheckerType
270 ) {
271   const notificationType = UserNotificationType.NEW_FOLLOW
272
273   function notificationChecker (notification: UserNotification, type: CheckerType) {
274     if (type === 'presence') {
275       expect(notification).to.not.be.undefined
276       expect(notification.type).to.equal(notificationType)
277
278       checkActor(notification.actorFollow.follower)
279       expect(notification.actorFollow.follower.displayName).to.equal(followerDisplayName)
280       expect(notification.actorFollow.follower.name).to.equal(followerName)
281       expect(notification.actorFollow.follower.host).to.not.be.undefined
282
283       const following = notification.actorFollow.following
284       expect(following.displayName).to.equal(followingDisplayName)
285       expect(following.type).to.equal(followType)
286     } else {
287       expect(notification).to.satisfy(n => {
288         return n.type !== notificationType ||
289           (n.actorFollow.follower.name !== followerName && n.actorFollow.following !== followingDisplayName)
290       })
291     }
292   }
293
294   function emailNotificationFinder (email: object) {
295     const text: string = email['text']
296
297     return text.includes(followType) && text.includes(followingDisplayName) && text.includes(followerDisplayName)
298   }
299
300   await checkNotification(base, notificationChecker, emailNotificationFinder, type)
301 }
302
303 async function checkNewInstanceFollower (base: CheckerBaseParams, followerHost: string, type: CheckerType) {
304   const notificationType = UserNotificationType.NEW_INSTANCE_FOLLOWER
305
306   function notificationChecker (notification: UserNotification, type: CheckerType) {
307     if (type === 'presence') {
308       expect(notification).to.not.be.undefined
309       expect(notification.type).to.equal(notificationType)
310
311       checkActor(notification.actorFollow.follower)
312       expect(notification.actorFollow.follower.name).to.equal('peertube')
313       expect(notification.actorFollow.follower.host).to.equal(followerHost)
314
315       expect(notification.actorFollow.following.name).to.equal('peertube')
316     } else {
317       expect(notification).to.satisfy(n => {
318         return n.type !== notificationType || n.actorFollow.follower.host !== followerHost
319       })
320     }
321   }
322
323   function emailNotificationFinder (email: object) {
324     const text: string = email['text']
325
326     return text.includes('instance has a new follower') && text.includes(followerHost)
327   }
328
329   await checkNotification(base, notificationChecker, emailNotificationFinder, type)
330 }
331
332 async function checkAutoInstanceFollowing (base: CheckerBaseParams, followerHost: string, followingHost: string, type: CheckerType) {
333   const notificationType = UserNotificationType.AUTO_INSTANCE_FOLLOWING
334
335   function notificationChecker (notification: UserNotification, type: CheckerType) {
336     if (type === 'presence') {
337       expect(notification).to.not.be.undefined
338       expect(notification.type).to.equal(notificationType)
339
340       const following = notification.actorFollow.following
341       checkActor(following)
342       expect(following.name).to.equal('peertube')
343       expect(following.host).to.equal(followingHost)
344
345       expect(notification.actorFollow.follower.name).to.equal('peertube')
346       expect(notification.actorFollow.follower.host).to.equal(followerHost)
347     } else {
348       expect(notification).to.satisfy(n => {
349         return n.type !== notificationType || n.actorFollow.following.host !== followingHost
350       })
351     }
352   }
353
354   function emailNotificationFinder (email: object) {
355     const text: string = email['text']
356
357     return text.includes(' automatically followed a new instance') && text.includes(followingHost)
358   }
359
360   await checkNotification(base, notificationChecker, emailNotificationFinder, type)
361 }
362
363 async function checkCommentMention (
364   base: CheckerBaseParams,
365   uuid: string,
366   commentId: number,
367   threadId: number,
368   byAccountDisplayName: string,
369   type: CheckerType
370 ) {
371   const notificationType = UserNotificationType.COMMENT_MENTION
372
373   function notificationChecker (notification: UserNotification, type: CheckerType) {
374     if (type === 'presence') {
375       expect(notification).to.not.be.undefined
376       expect(notification.type).to.equal(notificationType)
377
378       checkComment(notification.comment, commentId, threadId)
379       checkActor(notification.comment.account)
380       expect(notification.comment.account.displayName).to.equal(byAccountDisplayName)
381
382       checkVideo(notification.comment.video, undefined, uuid)
383     } else {
384       expect(notification).to.satisfy(n => n.type !== notificationType || n.comment.id !== commentId)
385     }
386   }
387
388   function emailNotificationFinder (email: object) {
389     const text: string = email['text']
390
391     return text.includes(' mentioned ') && text.includes(uuid) && text.includes(byAccountDisplayName)
392   }
393
394   await checkNotification(base, notificationChecker, emailNotificationFinder, type)
395 }
396
397 let lastEmailCount = 0
398
399 async function checkNewCommentOnMyVideo (base: CheckerBaseParams, uuid: string, commentId: number, threadId: number, type: CheckerType) {
400   const notificationType = UserNotificationType.NEW_COMMENT_ON_MY_VIDEO
401
402   function notificationChecker (notification: UserNotification, type: CheckerType) {
403     if (type === 'presence') {
404       expect(notification).to.not.be.undefined
405       expect(notification.type).to.equal(notificationType)
406
407       checkComment(notification.comment, commentId, threadId)
408       checkActor(notification.comment.account)
409       checkVideo(notification.comment.video, undefined, uuid)
410     } else {
411       expect(notification).to.satisfy((n: UserNotification) => {
412         return n === undefined || n.comment === undefined || n.comment.id !== commentId
413       })
414     }
415   }
416
417   const commentUrl = `http://localhost:${base.server.port}/videos/watch/${uuid};threadId=${threadId}`
418
419   function emailNotificationFinder (email: object) {
420     return email['text'].indexOf(commentUrl) !== -1
421   }
422
423   await checkNotification(base, notificationChecker, emailNotificationFinder, type)
424
425   if (type === 'presence') {
426     // We cannot detect email duplicates, so check we received another email
427     expect(base.emails).to.have.length.above(lastEmailCount)
428     lastEmailCount = base.emails.length
429   }
430 }
431
432 async function checkNewVideoAbuseForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) {
433   const notificationType = UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS
434
435   function notificationChecker (notification: UserNotification, type: CheckerType) {
436     if (type === 'presence') {
437       expect(notification).to.not.be.undefined
438       expect(notification.type).to.equal(notificationType)
439
440       expect(notification.videoAbuse.id).to.be.a('number')
441       checkVideo(notification.videoAbuse.video, videoName, videoUUID)
442     } else {
443       expect(notification).to.satisfy((n: UserNotification) => {
444         return n === undefined || n.videoAbuse === undefined || n.videoAbuse.video.uuid !== videoUUID
445       })
446     }
447   }
448
449   function emailNotificationFinder (email: object) {
450     const text = email['text']
451     return text.indexOf(videoUUID) !== -1 && text.indexOf('abuse') !== -1
452   }
453
454   await checkNotification(base, notificationChecker, emailNotificationFinder, type)
455 }
456
457 async function checkVideoAutoBlacklistForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) {
458   const notificationType = UserNotificationType.VIDEO_AUTO_BLOCK_FOR_MODERATORS
459
460   function notificationChecker (notification: UserNotification, type: CheckerType) {
461     if (type === 'presence') {
462       expect(notification).to.not.be.undefined
463       expect(notification.type).to.equal(notificationType)
464
465       expect(notification.videoBlacklist.video.id).to.be.a('number')
466       checkVideo(notification.videoBlacklist.video, videoName, videoUUID)
467     } else {
468       expect(notification).to.satisfy((n: UserNotification) => {
469         return n === undefined || n.video === undefined || n.video.uuid !== videoUUID
470       })
471     }
472   }
473
474   function emailNotificationFinder (email: object) {
475     const text = email['text']
476     return text.indexOf(videoUUID) !== -1 && email['text'].indexOf('video-auto-blacklist/list') !== -1
477   }
478
479   await checkNotification(base, notificationChecker, emailNotificationFinder, type)
480 }
481
482 async function checkNewBlacklistOnMyVideo (
483   base: CheckerBaseParams,
484   videoUUID: string,
485   videoName: string,
486   blacklistType: 'blacklist' | 'unblacklist'
487 ) {
488   const notificationType = blacklistType === 'blacklist'
489     ? UserNotificationType.BLOCK_ON_MY_VIDEO
490     : UserNotificationType.UNBLOCK_ON_MY_VIDEO
491
492   function notificationChecker (notification: UserNotification) {
493     expect(notification).to.not.be.undefined
494     expect(notification.type).to.equal(notificationType)
495
496     const video = blacklistType === 'blacklist' ? notification.videoBlacklist.video : notification.video
497
498     checkVideo(video, videoName, videoUUID)
499   }
500
501   function emailNotificationFinder (email: object) {
502     const text = email['text']
503     return text.indexOf(videoUUID) !== -1 && text.indexOf(' ' + blacklistType) !== -1
504   }
505
506   await checkNotification(base, notificationChecker, emailNotificationFinder, 'presence')
507 }
508
509 // ---------------------------------------------------------------------------
510
511 export {
512   CheckerBaseParams,
513   CheckerType,
514   checkNotification,
515   markAsReadAllNotifications,
516   checkMyVideoImportIsFinished,
517   checkUserRegistered,
518   checkAutoInstanceFollowing,
519   checkVideoIsPublished,
520   checkNewVideoFromSubscription,
521   checkNewActorFollow,
522   checkNewCommentOnMyVideo,
523   checkNewBlacklistOnMyVideo,
524   checkCommentMention,
525   updateMyNotificationSettings,
526   checkNewVideoAbuseForModerators,
527   checkVideoAutoBlacklistForModerators,
528   getUserNotifications,
529   markAsReadNotifications,
530   getLastNotification,
531   checkNewInstanceFollower
532 }