add quarantine videos feature (#1637)
[oweals/peertube.git] / shared / utils / users / user-notifications.ts
1 /* tslint:disable:no-unused-expression */
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 function markAsReadAllNotifications (url: string, token: string, statusCodeExpected = 204) {
58   const path = '/api/v1/users/me/notifications/read-all'
59
60   return makePostBodyRequest({
61     url,
62     path,
63     token,
64     statusCodeExpected
65   })
66 }
67
68 async function getLastNotification (serverUrl: string, accessToken: string) {
69   const res = await getUserNotifications(serverUrl, accessToken, 0, 1, undefined, '-createdAt')
70
71   if (res.body.total === 0) return undefined
72
73   return res.body.data[0] as UserNotification
74 }
75
76 type CheckerBaseParams = {
77   server: ServerInfo
78   emails: object[]
79   socketNotifications: UserNotification[]
80   token: string,
81   check?: { web: boolean, mail: boolean }
82 }
83
84 type CheckerType = 'presence' | 'absence'
85
86 async function checkNotification (
87   base: CheckerBaseParams,
88   notificationChecker: (notification: UserNotification, type: CheckerType) => void,
89   emailNotificationFinder: (email: object) => boolean,
90   checkType: CheckerType
91 ) {
92   const check = base.check || { web: true, mail: true }
93
94   if (check.web) {
95     const notification = await getLastNotification(base.server.url, base.token)
96
97     if (notification || checkType !== 'absence') {
98       notificationChecker(notification, checkType)
99     }
100
101     const socketNotification = base.socketNotifications.find(n => {
102       try {
103         notificationChecker(n, 'presence')
104         return true
105       } catch {
106         return false
107       }
108     })
109
110     if (checkType === 'presence') {
111       const obj = inspect(base.socketNotifications, { depth: 5 })
112       expect(socketNotification, 'The socket notification is absent. ' + obj).to.not.be.undefined
113     } else {
114       const obj = inspect(socketNotification, { depth: 5 })
115       expect(socketNotification, 'The socket notification is present. ' + obj).to.be.undefined
116     }
117   }
118
119   if (check.mail) {
120     // Last email
121     const email = base.emails
122                       .slice()
123                       .reverse()
124                       .find(e => emailNotificationFinder(e))
125
126     if (checkType === 'presence') {
127       expect(email, 'The email is absent. ' + inspect(base.emails)).to.not.be.undefined
128     } else {
129       expect(email, 'The email is present. ' + inspect(email)).to.be.undefined
130     }
131   }
132 }
133
134 function checkVideo (video: any, videoName?: string, videoUUID?: string) {
135   expect(video.name).to.be.a('string')
136   expect(video.name).to.not.be.empty
137   if (videoName) expect(video.name).to.equal(videoName)
138
139   expect(video.uuid).to.be.a('string')
140   expect(video.uuid).to.not.be.empty
141   if (videoUUID) expect(video.uuid).to.equal(videoUUID)
142
143   expect(video.id).to.be.a('number')
144 }
145
146 function checkActor (actor: any) {
147   expect(actor.displayName).to.be.a('string')
148   expect(actor.displayName).to.not.be.empty
149   expect(actor.host).to.not.be.undefined
150 }
151
152 function checkComment (comment: any, commentId: number, threadId: number) {
153   expect(comment.id).to.equal(commentId)
154   expect(comment.threadId).to.equal(threadId)
155 }
156
157 async function checkNewVideoFromSubscription (base: CheckerBaseParams, videoName: string, videoUUID: string, type: CheckerType) {
158   const notificationType = UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION
159
160   function notificationChecker (notification: UserNotification, type: CheckerType) {
161     if (type === 'presence') {
162       expect(notification).to.not.be.undefined
163       expect(notification.type).to.equal(notificationType)
164
165       checkVideo(notification.video, videoName, videoUUID)
166       checkActor(notification.video.channel)
167     } else {
168       expect(notification).to.satisfy((n: UserNotification) => {
169         return n === undefined || n.type !== UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION || n.video.name !== videoName
170       })
171     }
172   }
173
174   function emailFinder (email: object) {
175     const text = email[ 'text' ]
176     return text.indexOf(videoUUID) !== -1 && text.indexOf('Your subscription') !== -1
177   }
178
179   await checkNotification(base, notificationChecker, emailFinder, type)
180 }
181
182 async function checkVideoIsPublished (base: CheckerBaseParams, videoName: string, videoUUID: string, type: CheckerType) {
183   const notificationType = UserNotificationType.MY_VIDEO_PUBLISHED
184
185   function notificationChecker (notification: UserNotification, type: CheckerType) {
186     if (type === 'presence') {
187       expect(notification).to.not.be.undefined
188       expect(notification.type).to.equal(notificationType)
189
190       checkVideo(notification.video, videoName, videoUUID)
191       checkActor(notification.video.channel)
192     } else {
193       expect(notification.video).to.satisfy(v => v === undefined || v.name !== videoName)
194     }
195   }
196
197   function emailFinder (email: object) {
198     const text: string = email[ 'text' ]
199     return text.includes(videoUUID) && text.includes('Your video')
200   }
201
202   await checkNotification(base, notificationChecker, emailFinder, type)
203 }
204
205 async function checkMyVideoImportIsFinished (
206   base: CheckerBaseParams,
207   videoName: string,
208   videoUUID: string,
209   url: string,
210   success: boolean,
211   type: CheckerType
212 ) {
213   const notificationType = success ? UserNotificationType.MY_VIDEO_IMPORT_SUCCESS : UserNotificationType.MY_VIDEO_IMPORT_ERROR
214
215   function notificationChecker (notification: UserNotification, type: CheckerType) {
216     if (type === 'presence') {
217       expect(notification).to.not.be.undefined
218       expect(notification.type).to.equal(notificationType)
219
220       expect(notification.videoImport.targetUrl).to.equal(url)
221
222       if (success) checkVideo(notification.videoImport.video, videoName, videoUUID)
223     } else {
224       expect(notification.videoImport).to.satisfy(i => i === undefined || i.targetUrl !== url)
225     }
226   }
227
228   function emailFinder (email: object) {
229     const text: string = email[ 'text' ]
230     const toFind = success ? ' finished' : ' error'
231
232     return text.includes(url) && text.includes(toFind)
233   }
234
235   await checkNotification(base, notificationChecker, emailFinder, type)
236 }
237
238 async function checkUserRegistered (base: CheckerBaseParams, username: string, type: CheckerType) {
239   const notificationType = UserNotificationType.NEW_USER_REGISTRATION
240
241   function notificationChecker (notification: UserNotification, type: CheckerType) {
242     if (type === 'presence') {
243       expect(notification).to.not.be.undefined
244       expect(notification.type).to.equal(notificationType)
245
246       checkActor(notification.account)
247       expect(notification.account.name).to.equal(username)
248     } else {
249       expect(notification).to.satisfy(n => n.type !== notificationType || n.account.name !== username)
250     }
251   }
252
253   function emailFinder (email: object) {
254     const text: string = email[ 'text' ]
255
256     return text.includes(' registered ') && text.includes(username)
257   }
258
259   await checkNotification(base, notificationChecker, emailFinder, type)
260 }
261
262 async function checkNewActorFollow (
263   base: CheckerBaseParams,
264   followType: 'channel' | 'account',
265   followerName: string,
266   followerDisplayName: string,
267   followingDisplayName: string,
268   type: CheckerType
269 ) {
270   const notificationType = UserNotificationType.NEW_FOLLOW
271
272   function notificationChecker (notification: UserNotification, type: CheckerType) {
273     if (type === 'presence') {
274       expect(notification).to.not.be.undefined
275       expect(notification.type).to.equal(notificationType)
276
277       checkActor(notification.actorFollow.follower)
278       expect(notification.actorFollow.follower.displayName).to.equal(followerDisplayName)
279       expect(notification.actorFollow.follower.name).to.equal(followerName)
280       expect(notification.actorFollow.follower.host).to.not.be.undefined
281
282       expect(notification.actorFollow.following.displayName).to.equal(followingDisplayName)
283       expect(notification.actorFollow.following.type).to.equal(followType)
284     } else {
285       expect(notification).to.satisfy(n => {
286         return n.type !== notificationType ||
287           (n.actorFollow.follower.name !== followerName && n.actorFollow.following !== followingDisplayName)
288       })
289     }
290   }
291
292   function emailFinder (email: object) {
293     const text: string = email[ 'text' ]
294
295     return text.includes('Your ' + followType) && text.includes(followingDisplayName) && text.includes(followerDisplayName)
296   }
297
298   await checkNotification(base, notificationChecker, emailFinder, type)
299 }
300
301 async function checkCommentMention (
302   base: CheckerBaseParams,
303   uuid: string,
304   commentId: number,
305   threadId: number,
306   byAccountDisplayName: string,
307   type: CheckerType
308 ) {
309   const notificationType = UserNotificationType.COMMENT_MENTION
310
311   function notificationChecker (notification: UserNotification, type: CheckerType) {
312     if (type === 'presence') {
313       expect(notification).to.not.be.undefined
314       expect(notification.type).to.equal(notificationType)
315
316       checkComment(notification.comment, commentId, threadId)
317       checkActor(notification.comment.account)
318       expect(notification.comment.account.displayName).to.equal(byAccountDisplayName)
319
320       checkVideo(notification.comment.video, undefined, uuid)
321     } else {
322       expect(notification).to.satisfy(n => n.type !== notificationType || n.comment.id !== commentId)
323     }
324   }
325
326   function emailFinder (email: object) {
327     const text: string = email[ 'text' ]
328
329     return text.includes(' mentioned ') && text.includes(uuid) && text.includes(byAccountDisplayName)
330   }
331
332   await checkNotification(base, notificationChecker, emailFinder, type)
333 }
334
335 let lastEmailCount = 0
336 async function checkNewCommentOnMyVideo (base: CheckerBaseParams, uuid: string, commentId: number, threadId: number, type: CheckerType) {
337   const notificationType = UserNotificationType.NEW_COMMENT_ON_MY_VIDEO
338
339   function notificationChecker (notification: UserNotification, type: CheckerType) {
340     if (type === 'presence') {
341       expect(notification).to.not.be.undefined
342       expect(notification.type).to.equal(notificationType)
343
344       checkComment(notification.comment, commentId, threadId)
345       checkActor(notification.comment.account)
346       checkVideo(notification.comment.video, undefined, uuid)
347     } else {
348       expect(notification).to.satisfy((n: UserNotification) => {
349         return n === undefined || n.comment === undefined || n.comment.id !== commentId
350       })
351     }
352   }
353
354   const commentUrl = `http://localhost:9001/videos/watch/${uuid};threadId=${threadId}`
355   function emailFinder (email: object) {
356     return email[ 'text' ].indexOf(commentUrl) !== -1
357   }
358
359   await checkNotification(base, notificationChecker, emailFinder, type)
360
361   if (type === 'presence') {
362     // We cannot detect email duplicates, so check we received another email
363     expect(base.emails).to.have.length.above(lastEmailCount)
364     lastEmailCount = base.emails.length
365   }
366 }
367
368 async function checkNewVideoAbuseForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) {
369   const notificationType = UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS
370
371   function notificationChecker (notification: UserNotification, type: CheckerType) {
372     if (type === 'presence') {
373       expect(notification).to.not.be.undefined
374       expect(notification.type).to.equal(notificationType)
375
376       expect(notification.videoAbuse.id).to.be.a('number')
377       checkVideo(notification.videoAbuse.video, videoName, videoUUID)
378     } else {
379       expect(notification).to.satisfy((n: UserNotification) => {
380         return n === undefined || n.videoAbuse === undefined || n.videoAbuse.video.uuid !== videoUUID
381       })
382     }
383   }
384
385   function emailFinder (email: object) {
386     const text = email[ 'text' ]
387     return text.indexOf(videoUUID) !== -1 && text.indexOf('abuse') !== -1
388   }
389
390   await checkNotification(base, notificationChecker, emailFinder, type)
391 }
392
393 async function checkVideoAutoBlacklistForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) {
394   const notificationType = UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS
395
396   function notificationChecker (notification: UserNotification, type: CheckerType) {
397     if (type === 'presence') {
398       expect(notification).to.not.be.undefined
399       expect(notification.type).to.equal(notificationType)
400
401       expect(notification.video.id).to.be.a('number')
402       checkVideo(notification.video, videoName, videoUUID)
403     } else {
404       expect(notification).to.satisfy((n: UserNotification) => {
405         return n === undefined || n.video === undefined || n.video.uuid !== videoUUID
406       })
407     }
408   }
409
410   function emailFinder (email: object) {
411     const text = email[ 'text' ]
412     return text.indexOf(videoUUID) !== -1 && email[ 'text' ].indexOf('video-auto-blacklist/list') !== -1
413   }
414
415   await checkNotification(base, notificationChecker, emailFinder, type)
416 }
417
418 async function checkNewBlacklistOnMyVideo (
419   base: CheckerBaseParams,
420   videoUUID: string,
421   videoName: string,
422   blacklistType: 'blacklist' | 'unblacklist'
423 ) {
424   const notificationType = blacklistType === 'blacklist'
425     ? UserNotificationType.BLACKLIST_ON_MY_VIDEO
426     : UserNotificationType.UNBLACKLIST_ON_MY_VIDEO
427
428   function notificationChecker (notification: UserNotification) {
429     expect(notification).to.not.be.undefined
430     expect(notification.type).to.equal(notificationType)
431
432     const video = blacklistType === 'blacklist' ? notification.videoBlacklist.video : notification.video
433
434     checkVideo(video, videoName, videoUUID)
435   }
436
437   function emailFinder (email: object) {
438     const text = email[ 'text' ]
439     return text.indexOf(videoUUID) !== -1 && text.indexOf(' ' + blacklistType) !== -1
440   }
441
442   await checkNotification(base, notificationChecker, emailFinder, 'presence')
443 }
444
445 // ---------------------------------------------------------------------------
446
447 export {
448   CheckerBaseParams,
449   CheckerType,
450   checkNotification,
451   markAsReadAllNotifications,
452   checkMyVideoImportIsFinished,
453   checkUserRegistered,
454   checkVideoIsPublished,
455   checkNewVideoFromSubscription,
456   checkNewActorFollow,
457   checkNewCommentOnMyVideo,
458   checkNewBlacklistOnMyVideo,
459   checkCommentMention,
460   updateMyNotificationSettings,
461   checkNewVideoAbuseForModerators,
462   checkVideoAutoBlacklistForModerators,
463   getUserNotifications,
464   markAsReadNotifications,
465   getLastNotification
466 }