]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - shared/extra-utils/users/user-notifications.ts
Merge branch 'release/2.1.0' into develop
[github/Chocobozzz/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. ' + obj).to.not.be.undefined
114 } else {
115 const obj = inspect(socketNotification, { depth: 5 })
116 expect(socketNotification, 'The socket notification is 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. ' + inspect(base.emails)).to.not.be.undefined
129 } else {
130 expect(email, 'The email is 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 emailFinder (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, emailFinder, 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 emailFinder (email: object) {
199 const text: string = email['text']
200 return text.includes(videoUUID) && text.includes('Your video')
201 }
202
203 await checkNotification(base, notificationChecker, emailFinder, 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 emailFinder (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, emailFinder, 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 emailFinder (email: object) {
255 const text: string = email['text']
256
257 return text.includes(' registered ') && text.includes(username)
258 }
259
260 await checkNotification(base, notificationChecker, emailFinder, 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 emailFinder (email: object) {
295 const text: string = email['text']
296
297 return text.includes('Your ' + followType) && text.includes(followingDisplayName) && text.includes(followerDisplayName)
298 }
299
300 await checkNotification(base, notificationChecker, emailFinder, 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 emailFinder (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, emailFinder, 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 emailFinder (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, emailFinder, 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 emailFinder (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, emailFinder, 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 emailFinder (email: object) {
420 return email['text'].indexOf(commentUrl) !== -1
421 }
422
423 await checkNotification(base, notificationChecker, emailFinder, 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 emailFinder (email: object) {
450 const text = email['text']
451 return text.indexOf(videoUUID) !== -1 && text.indexOf('abuse') !== -1
452 }
453
454 await checkNotification(base, notificationChecker, emailFinder, type)
455 }
456
457 async function checkVideoAutoBlacklistForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) {
458 const notificationType = UserNotificationType.VIDEO_AUTO_BLACKLIST_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 emailFinder (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, emailFinder, 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.BLACKLIST_ON_MY_VIDEO
490 : UserNotificationType.UNBLACKLIST_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 emailFinder (email: object) {
502 const text = email['text']
503 return text.indexOf(videoUUID) !== -1 && text.indexOf(' ' + blacklistType) !== -1
504 }
505
506 await checkNotification(base, notificationChecker, emailFinder, '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 }