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