1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
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'
14 function updateMyNotificationSettings (url: string, token: string, settings: UserNotificationSetting, statusCodeExpected = 204) {
15 const path = '/api/v1/users/me/notification-settings'
17 return makePutBodyRequest({
26 async function getUserNotifications (
33 statusCodeExpected = 200
35 const path = '/api/v1/users/me/notifications'
37 return makeGetRequest({
51 function markAsReadNotifications (url: string, token: string, ids: number[], statusCodeExpected = 204) {
52 const path = '/api/v1/users/me/notifications/read'
54 return makePostBodyRequest({
63 function markAsReadAllNotifications (url: string, token: string, statusCodeExpected = 204) {
64 const path = '/api/v1/users/me/notifications/read-all'
66 return makePostBodyRequest({
74 async function getLastNotification (serverUrl: string, accessToken: string) {
75 const res = await getUserNotifications(serverUrl, accessToken, 0, 1, undefined, '-createdAt')
77 if (res.body.total === 0) return undefined
79 return res.body.data[0] as UserNotification
82 type CheckerBaseParams = {
85 socketNotifications: UserNotification[]
87 check?: { web: boolean, mail: boolean }
90 type CheckerType = 'presence' | 'absence'
92 async function checkNotification (
93 base: CheckerBaseParams,
94 notificationChecker: (notification: UserNotification, type: CheckerType) => void,
95 emailNotificationFinder: (email: object) => boolean,
96 checkType: CheckerType
98 const check = base.check || { web: true, mail: true }
101 const notification = await getLastNotification(base.server.url, base.token)
103 if (notification || checkType !== 'absence') {
104 notificationChecker(notification, checkType)
107 const socketNotification = base.socketNotifications.find(n => {
109 notificationChecker(n, 'presence')
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
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
127 const email = base.emails
130 .find(e => emailNotificationFinder(e))
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
136 expect(email, 'The email is present when is should not be present. ' + inspect(email)).to.be.undefined
141 function checkVideo (video: any, videoName?: string, videoUUID?: string) {
143 expect(video.name).to.be.a('string')
144 expect(video.name).to.not.be.empty
145 expect(video.name).to.equal(videoName)
149 expect(video.uuid).to.be.a('string')
150 expect(video.uuid).to.not.be.empty
151 expect(video.uuid).to.equal(videoUUID)
154 expect(video.id).to.be.a('number')
157 function checkActor (actor: any) {
158 expect(actor.displayName).to.be.a('string')
159 expect(actor.displayName).to.not.be.empty
160 expect(actor.host).to.not.be.undefined
163 function checkComment (comment: any, commentId: number, threadId: number) {
164 expect(comment.id).to.equal(commentId)
165 expect(comment.threadId).to.equal(threadId)
168 async function checkNewVideoFromSubscription (base: CheckerBaseParams, videoName: string, videoUUID: string, type: CheckerType) {
169 const notificationType = UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION
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)
176 checkVideo(notification.video, videoName, videoUUID)
177 checkActor(notification.video.channel)
179 expect(notification).to.satisfy((n: UserNotification) => {
180 return n === undefined || n.type !== UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION || n.video.name !== videoName
185 function emailNotificationFinder (email: object) {
186 const text = email['text']
187 return text.indexOf(videoUUID) !== -1 && text.indexOf('Your subscription') !== -1
190 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
193 async function checkVideoIsPublished (base: CheckerBaseParams, videoName: string, videoUUID: string, type: CheckerType) {
194 const notificationType = UserNotificationType.MY_VIDEO_PUBLISHED
196 function notificationChecker (notification: UserNotification, type: CheckerType) {
197 if (type === 'presence') {
198 expect(notification).to.not.be.undefined
199 expect(notification.type).to.equal(notificationType)
201 checkVideo(notification.video, videoName, videoUUID)
202 checkActor(notification.video.channel)
204 expect(notification.video).to.satisfy(v => v === undefined || v.name !== videoName)
208 function emailNotificationFinder (email: object) {
209 const text: string = email['text']
210 return text.includes(videoUUID) && text.includes('Your video')
213 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
216 async function checkMyVideoImportIsFinished (
217 base: CheckerBaseParams,
224 const notificationType = success ? UserNotificationType.MY_VIDEO_IMPORT_SUCCESS : UserNotificationType.MY_VIDEO_IMPORT_ERROR
226 function notificationChecker (notification: UserNotification, type: CheckerType) {
227 if (type === 'presence') {
228 expect(notification).to.not.be.undefined
229 expect(notification.type).to.equal(notificationType)
231 expect(notification.videoImport.targetUrl).to.equal(url)
233 if (success) checkVideo(notification.videoImport.video, videoName, videoUUID)
235 expect(notification.videoImport).to.satisfy(i => i === undefined || i.targetUrl !== url)
239 function emailNotificationFinder (email: object) {
240 const text: string = email['text']
241 const toFind = success ? ' finished' : ' error'
243 return text.includes(url) && text.includes(toFind)
246 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
249 async function checkUserRegistered (base: CheckerBaseParams, username: string, type: CheckerType) {
250 const notificationType = UserNotificationType.NEW_USER_REGISTRATION
252 function notificationChecker (notification: UserNotification, type: CheckerType) {
253 if (type === 'presence') {
254 expect(notification).to.not.be.undefined
255 expect(notification.type).to.equal(notificationType)
257 checkActor(notification.account)
258 expect(notification.account.name).to.equal(username)
260 expect(notification).to.satisfy(n => n.type !== notificationType || n.account.name !== username)
264 function emailNotificationFinder (email: object) {
265 const text: string = email['text']
267 return text.includes(' registered.') && text.includes(username)
270 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
273 async function checkNewActorFollow (
274 base: CheckerBaseParams,
275 followType: 'channel' | 'account',
276 followerName: string,
277 followerDisplayName: string,
278 followingDisplayName: string,
281 const notificationType = UserNotificationType.NEW_FOLLOW
283 function notificationChecker (notification: UserNotification, type: CheckerType) {
284 if (type === 'presence') {
285 expect(notification).to.not.be.undefined
286 expect(notification.type).to.equal(notificationType)
288 checkActor(notification.actorFollow.follower)
289 expect(notification.actorFollow.follower.displayName).to.equal(followerDisplayName)
290 expect(notification.actorFollow.follower.name).to.equal(followerName)
291 expect(notification.actorFollow.follower.host).to.not.be.undefined
293 const following = notification.actorFollow.following
294 expect(following.displayName).to.equal(followingDisplayName)
295 expect(following.type).to.equal(followType)
297 expect(notification).to.satisfy(n => {
298 return n.type !== notificationType ||
299 (n.actorFollow.follower.name !== followerName && n.actorFollow.following !== followingDisplayName)
304 function emailNotificationFinder (email: object) {
305 const text: string = email['text']
307 return text.includes(followType) && text.includes(followingDisplayName) && text.includes(followerDisplayName)
310 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
313 async function checkNewInstanceFollower (base: CheckerBaseParams, followerHost: string, type: CheckerType) {
314 const notificationType = UserNotificationType.NEW_INSTANCE_FOLLOWER
316 function notificationChecker (notification: UserNotification, type: CheckerType) {
317 if (type === 'presence') {
318 expect(notification).to.not.be.undefined
319 expect(notification.type).to.equal(notificationType)
321 checkActor(notification.actorFollow.follower)
322 expect(notification.actorFollow.follower.name).to.equal('peertube')
323 expect(notification.actorFollow.follower.host).to.equal(followerHost)
325 expect(notification.actorFollow.following.name).to.equal('peertube')
327 expect(notification).to.satisfy(n => {
328 return n.type !== notificationType || n.actorFollow.follower.host !== followerHost
333 function emailNotificationFinder (email: object) {
334 const text: string = email['text']
336 return text.includes('instance has a new follower') && text.includes(followerHost)
339 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
342 async function checkAutoInstanceFollowing (base: CheckerBaseParams, followerHost: string, followingHost: string, type: CheckerType) {
343 const notificationType = UserNotificationType.AUTO_INSTANCE_FOLLOWING
345 function notificationChecker (notification: UserNotification, type: CheckerType) {
346 if (type === 'presence') {
347 expect(notification).to.not.be.undefined
348 expect(notification.type).to.equal(notificationType)
350 const following = notification.actorFollow.following
351 checkActor(following)
352 expect(following.name).to.equal('peertube')
353 expect(following.host).to.equal(followingHost)
355 expect(notification.actorFollow.follower.name).to.equal('peertube')
356 expect(notification.actorFollow.follower.host).to.equal(followerHost)
358 expect(notification).to.satisfy(n => {
359 return n.type !== notificationType || n.actorFollow.following.host !== followingHost
364 function emailNotificationFinder (email: object) {
365 const text: string = email['text']
367 return text.includes(' automatically followed a new instance') && text.includes(followingHost)
370 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
373 async function checkCommentMention (
374 base: CheckerBaseParams,
378 byAccountDisplayName: string,
381 const notificationType = UserNotificationType.COMMENT_MENTION
383 function notificationChecker (notification: UserNotification, type: CheckerType) {
384 if (type === 'presence') {
385 expect(notification).to.not.be.undefined
386 expect(notification.type).to.equal(notificationType)
388 checkComment(notification.comment, commentId, threadId)
389 checkActor(notification.comment.account)
390 expect(notification.comment.account.displayName).to.equal(byAccountDisplayName)
392 checkVideo(notification.comment.video, undefined, uuid)
394 expect(notification).to.satisfy(n => n.type !== notificationType || n.comment.id !== commentId)
398 function emailNotificationFinder (email: object) {
399 const text: string = email['text']
401 return text.includes(' mentioned ') && text.includes(uuid) && text.includes(byAccountDisplayName)
404 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
407 let lastEmailCount = 0
409 async function checkNewCommentOnMyVideo (base: CheckerBaseParams, uuid: string, commentId: number, threadId: number, type: CheckerType) {
410 const notificationType = UserNotificationType.NEW_COMMENT_ON_MY_VIDEO
412 function notificationChecker (notification: UserNotification, type: CheckerType) {
413 if (type === 'presence') {
414 expect(notification).to.not.be.undefined
415 expect(notification.type).to.equal(notificationType)
417 checkComment(notification.comment, commentId, threadId)
418 checkActor(notification.comment.account)
419 checkVideo(notification.comment.video, undefined, uuid)
421 expect(notification).to.satisfy((n: UserNotification) => {
422 return n === undefined || n.comment === undefined || n.comment.id !== commentId
427 const commentUrl = `http://localhost:${base.server.port}/videos/watch/${uuid};threadId=${threadId}`
429 function emailNotificationFinder (email: object) {
430 return email['text'].indexOf(commentUrl) !== -1
433 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
435 if (type === 'presence') {
436 // We cannot detect email duplicates, so check we received another email
437 expect(base.emails).to.have.length.above(lastEmailCount)
438 lastEmailCount = base.emails.length
442 async function checkNewVideoAbuseForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) {
443 const notificationType = UserNotificationType.NEW_ABUSE_FOR_MODERATORS
445 function notificationChecker (notification: UserNotification, type: CheckerType) {
446 if (type === 'presence') {
447 expect(notification).to.not.be.undefined
448 expect(notification.type).to.equal(notificationType)
450 expect(notification.abuse.id).to.be.a('number')
451 checkVideo(notification.abuse.video, videoName, videoUUID)
453 expect(notification).to.satisfy((n: UserNotification) => {
454 return n === undefined || n.abuse === undefined || n.abuse.video.uuid !== videoUUID
459 function emailNotificationFinder (email: object) {
460 const text = email['text']
461 return text.indexOf(videoUUID) !== -1 && text.indexOf('abuse') !== -1
464 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
467 async function checkNewCommentAbuseForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) {
468 const notificationType = UserNotificationType.NEW_ABUSE_FOR_MODERATORS
470 function notificationChecker (notification: UserNotification, type: CheckerType) {
471 if (type === 'presence') {
472 expect(notification).to.not.be.undefined
473 expect(notification.type).to.equal(notificationType)
475 expect(notification.abuse.id).to.be.a('number')
476 checkVideo(notification.abuse.comment.video, videoName, videoUUID)
478 expect(notification).to.satisfy((n: UserNotification) => {
479 return n === undefined || n.abuse === undefined || n.abuse.comment.video.uuid !== videoUUID
484 function emailNotificationFinder (email: object) {
485 const text = email['text']
486 return text.indexOf(videoUUID) !== -1 && text.indexOf('abuse') !== -1
489 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
492 async function checkNewAccountAbuseForModerators (base: CheckerBaseParams, displayName: string, type: CheckerType) {
493 const notificationType = UserNotificationType.NEW_ABUSE_FOR_MODERATORS
495 function notificationChecker (notification: UserNotification, type: CheckerType) {
496 if (type === 'presence') {
497 expect(notification).to.not.be.undefined
498 expect(notification.type).to.equal(notificationType)
500 expect(notification.abuse.id).to.be.a('number')
501 expect(notification.abuse.account.displayName).to.equal(displayName)
503 expect(notification).to.satisfy((n: UserNotification) => {
504 return n === undefined || n.abuse === undefined || n.abuse.account.displayName !== displayName
509 function emailNotificationFinder (email: object) {
510 const text = email['text']
511 return text.indexOf(displayName) !== -1 && text.indexOf('abuse') !== -1
514 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
517 async function checkVideoAutoBlacklistForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) {
518 const notificationType = UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS
520 function notificationChecker (notification: UserNotification, type: CheckerType) {
521 if (type === 'presence') {
522 expect(notification).to.not.be.undefined
523 expect(notification.type).to.equal(notificationType)
525 expect(notification.videoBlacklist.video.id).to.be.a('number')
526 checkVideo(notification.videoBlacklist.video, videoName, videoUUID)
528 expect(notification).to.satisfy((n: UserNotification) => {
529 return n === undefined || n.video === undefined || n.video.uuid !== videoUUID
534 function emailNotificationFinder (email: object) {
535 const text = email['text']
536 return text.indexOf(videoUUID) !== -1 && email['text'].indexOf('video-auto-blacklist/list') !== -1
539 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
542 async function checkNewBlacklistOnMyVideo (
543 base: CheckerBaseParams,
546 blacklistType: 'blacklist' | 'unblacklist'
548 const notificationType = blacklistType === 'blacklist'
549 ? UserNotificationType.BLACKLIST_ON_MY_VIDEO
550 : UserNotificationType.UNBLACKLIST_ON_MY_VIDEO
552 function notificationChecker (notification: UserNotification) {
553 expect(notification).to.not.be.undefined
554 expect(notification.type).to.equal(notificationType)
556 const video = blacklistType === 'blacklist' ? notification.videoBlacklist.video : notification.video
558 checkVideo(video, videoName, videoUUID)
561 function emailNotificationFinder (email: object) {
562 const text = email['text']
563 return text.indexOf(videoUUID) !== -1 && text.indexOf(' ' + blacklistType) !== -1
566 await checkNotification(base, notificationChecker, emailNotificationFinder, 'presence')
569 function getAllNotificationsSettings () {
571 newVideoFromSubscription: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
572 newCommentOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
573 abuseAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
574 videoAutoBlacklistAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
575 blacklistOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
576 myVideoImportFinished: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
577 myVideoPublished: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
578 commentMention: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
579 newFollow: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
580 newUserRegistration: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
581 newInstanceFollower: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
582 autoInstanceFollowing: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL
583 } as UserNotificationSetting
586 async function prepareNotificationsTest (serversCount = 3) {
587 const userNotifications: UserNotification[] = []
588 const adminNotifications: UserNotification[] = []
589 const adminNotificationsServer2: UserNotification[] = []
590 const emails: object[] = []
592 const port = await MockSmtpServer.Instance.collectEmails(emails)
594 const overrideConfig = {
596 hostname: 'localhost',
603 const servers = await flushAndRunMultipleServers(serversCount, overrideConfig)
605 await setAccessTokensToServers(servers)
607 if (serversCount > 1) {
608 await doubleFollow(servers[0], servers[1])
613 password: 'super password'
617 accessToken: servers[0].accessToken,
618 username: user.username,
619 password: user.password,
620 videoQuota: 10 * 1000 * 1000
622 const userAccessToken = await userLogin(servers[0], user)
624 await updateMyNotificationSettings(servers[0].url, userAccessToken, getAllNotificationsSettings())
625 await updateMyNotificationSettings(servers[0].url, servers[0].accessToken, getAllNotificationsSettings())
627 if (serversCount > 1) {
628 await updateMyNotificationSettings(servers[1].url, servers[1].accessToken, getAllNotificationsSettings())
632 const socket = getUserNotificationSocket(servers[0].url, userAccessToken)
633 socket.on('new-notification', n => userNotifications.push(n))
636 const socket = getUserNotificationSocket(servers[0].url, servers[0].accessToken)
637 socket.on('new-notification', n => adminNotifications.push(n))
640 if (serversCount > 1) {
641 const socket = getUserNotificationSocket(servers[1].url, servers[1].accessToken)
642 socket.on('new-notification', n => adminNotificationsServer2.push(n))
645 const resChannel = await getMyUserInformation(servers[0].url, servers[0].accessToken)
646 const channelId = resChannel.body.videoChannels[0].id
651 adminNotificationsServer2,
659 // ---------------------------------------------------------------------------
664 getAllNotificationsSettings,
666 markAsReadAllNotifications,
667 checkMyVideoImportIsFinished,
669 checkAutoInstanceFollowing,
670 checkVideoIsPublished,
671 checkNewVideoFromSubscription,
673 checkNewCommentOnMyVideo,
674 checkNewBlacklistOnMyVideo,
676 updateMyNotificationSettings,
677 checkNewVideoAbuseForModerators,
678 checkVideoAutoBlacklistForModerators,
679 getUserNotifications,
680 markAsReadNotifications,
682 checkNewInstanceFollower,
683 prepareNotificationsTest,
684 checkNewCommentAbuseForModerators,
685 checkNewAccountAbuseForModerators