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