1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
3 import { expect } from 'chai'
4 import { inspect } from 'util'
5 import { AbuseState } from '@shared/models'
6 import { UserNotification, UserNotificationSetting, UserNotificationSettingValue, UserNotificationType } from '../../models/users'
7 import { MockSmtpServer } from '../miscs/email'
8 import { makeGetRequest, makePostBodyRequest, makePutBodyRequest } from '../requests/requests'
9 import { doubleFollow } from '../server/follows'
10 import { flushAndRunMultipleServers, ServerInfo } from '../server/servers'
11 import { getUserNotificationSocket } from '../socket/socket-io'
12 import { setAccessTokensToServers, userLogin } from './login'
13 import { createUser, getMyUserInformation } from './users'
15 function updateMyNotificationSettings (url: string, token: string, settings: UserNotificationSetting, statusCodeExpected = 204) {
16 const path = '/api/v1/users/me/notification-settings'
18 return makePutBodyRequest({
27 async function getUserNotifications (
34 statusCodeExpected = 200
36 const path = '/api/v1/users/me/notifications'
38 return makeGetRequest({
52 function markAsReadNotifications (url: string, token: string, ids: number[], statusCodeExpected = 204) {
53 const path = '/api/v1/users/me/notifications/read'
55 return makePostBodyRequest({
64 function markAsReadAllNotifications (url: string, token: string, statusCodeExpected = 204) {
65 const path = '/api/v1/users/me/notifications/read-all'
67 return makePostBodyRequest({
75 async function getLastNotification (serverUrl: string, accessToken: string) {
76 const res = await getUserNotifications(serverUrl, accessToken, 0, 1, undefined, '-createdAt')
78 if (res.body.total === 0) return undefined
80 return res.body.data[0] as UserNotification
83 type CheckerBaseParams = {
86 socketNotifications: UserNotification[]
88 check?: { web: boolean, mail: boolean }
91 type CheckerType = 'presence' | 'absence'
93 async function checkNotification (
94 base: CheckerBaseParams,
95 notificationChecker: (notification: UserNotification, type: CheckerType) => void,
96 emailNotificationFinder: (email: object) => boolean,
97 checkType: CheckerType
99 const check = base.check || { web: true, mail: true }
102 const notification = await getLastNotification(base.server.url, base.token)
104 if (notification || checkType !== 'absence') {
105 notificationChecker(notification, checkType)
108 const socketNotification = base.socketNotifications.find(n => {
110 notificationChecker(n, 'presence')
117 if (checkType === 'presence') {
118 const obj = inspect(base.socketNotifications, { depth: 5 })
119 expect(socketNotification, 'The socket notification is absent when it should be present. ' + obj).to.not.be.undefined
121 const obj = inspect(socketNotification, { depth: 5 })
122 expect(socketNotification, 'The socket notification is present when it should not be present. ' + obj).to.be.undefined
128 const email = base.emails
131 .find(e => emailNotificationFinder(e))
133 if (checkType === 'presence') {
134 const emails = base.emails.map(e => e.text)
135 expect(email, 'The email is absent when is should be present. ' + inspect(emails)).to.not.be.undefined
137 expect(email, 'The email is present when is should not be present. ' + inspect(email)).to.be.undefined
142 function checkVideo (video: any, videoName?: string, videoUUID?: string) {
144 expect(video.name).to.be.a('string')
145 expect(video.name).to.not.be.empty
146 expect(video.name).to.equal(videoName)
150 expect(video.uuid).to.be.a('string')
151 expect(video.uuid).to.not.be.empty
152 expect(video.uuid).to.equal(videoUUID)
155 expect(video.id).to.be.a('number')
158 function checkActor (actor: any) {
159 expect(actor.displayName).to.be.a('string')
160 expect(actor.displayName).to.not.be.empty
161 expect(actor.host).to.not.be.undefined
164 function checkComment (comment: any, commentId: number, threadId: number) {
165 expect(comment.id).to.equal(commentId)
166 expect(comment.threadId).to.equal(threadId)
169 async function checkNewVideoFromSubscription (base: CheckerBaseParams, videoName: string, videoUUID: string, type: CheckerType) {
170 const notificationType = UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION
172 function notificationChecker (notification: UserNotification, type: CheckerType) {
173 if (type === 'presence') {
174 expect(notification).to.not.be.undefined
175 expect(notification.type).to.equal(notificationType)
177 checkVideo(notification.video, videoName, videoUUID)
178 checkActor(notification.video.channel)
180 expect(notification).to.satisfy((n: UserNotification) => {
181 return n === undefined || n.type !== UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION || n.video.name !== videoName
186 function emailNotificationFinder (email: object) {
187 const text = email['text']
188 return text.indexOf(videoUUID) !== -1 && text.indexOf('Your subscription') !== -1
191 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
194 async function checkVideoIsPublished (base: CheckerBaseParams, videoName: string, videoUUID: string, type: CheckerType) {
195 const notificationType = UserNotificationType.MY_VIDEO_PUBLISHED
197 function notificationChecker (notification: UserNotification, type: CheckerType) {
198 if (type === 'presence') {
199 expect(notification).to.not.be.undefined
200 expect(notification.type).to.equal(notificationType)
202 checkVideo(notification.video, videoName, videoUUID)
203 checkActor(notification.video.channel)
205 expect(notification.video).to.satisfy(v => v === undefined || v.name !== videoName)
209 function emailNotificationFinder (email: object) {
210 const text: string = email['text']
211 return text.includes(videoUUID) && text.includes('Your video')
214 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
217 async function checkMyVideoImportIsFinished (
218 base: CheckerBaseParams,
225 const notificationType = success ? UserNotificationType.MY_VIDEO_IMPORT_SUCCESS : UserNotificationType.MY_VIDEO_IMPORT_ERROR
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)
232 expect(notification.videoImport.targetUrl).to.equal(url)
234 if (success) checkVideo(notification.videoImport.video, videoName, videoUUID)
236 expect(notification.videoImport).to.satisfy(i => i === undefined || i.targetUrl !== url)
240 function emailNotificationFinder (email: object) {
241 const text: string = email['text']
242 const toFind = success ? ' finished' : ' error'
244 return text.includes(url) && text.includes(toFind)
247 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
250 async function checkUserRegistered (base: CheckerBaseParams, username: string, type: CheckerType) {
251 const notificationType = UserNotificationType.NEW_USER_REGISTRATION
253 function notificationChecker (notification: UserNotification, type: CheckerType) {
254 if (type === 'presence') {
255 expect(notification).to.not.be.undefined
256 expect(notification.type).to.equal(notificationType)
258 checkActor(notification.account)
259 expect(notification.account.name).to.equal(username)
261 expect(notification).to.satisfy(n => n.type !== notificationType || n.account.name !== username)
265 function emailNotificationFinder (email: object) {
266 const text: string = email['text']
268 return text.includes(' registered.') && text.includes(username)
271 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
274 async function checkNewActorFollow (
275 base: CheckerBaseParams,
276 followType: 'channel' | 'account',
277 followerName: string,
278 followerDisplayName: string,
279 followingDisplayName: string,
282 const notificationType = UserNotificationType.NEW_FOLLOW
284 function notificationChecker (notification: UserNotification, type: CheckerType) {
285 if (type === 'presence') {
286 expect(notification).to.not.be.undefined
287 expect(notification.type).to.equal(notificationType)
289 checkActor(notification.actorFollow.follower)
290 expect(notification.actorFollow.follower.displayName).to.equal(followerDisplayName)
291 expect(notification.actorFollow.follower.name).to.equal(followerName)
292 expect(notification.actorFollow.follower.host).to.not.be.undefined
294 const following = notification.actorFollow.following
295 expect(following.displayName).to.equal(followingDisplayName)
296 expect(following.type).to.equal(followType)
298 expect(notification).to.satisfy(n => {
299 return n.type !== notificationType ||
300 (n.actorFollow.follower.name !== followerName && n.actorFollow.following !== followingDisplayName)
305 function emailNotificationFinder (email: object) {
306 const text: string = email['text']
308 return text.includes(followType) && text.includes(followingDisplayName) && text.includes(followerDisplayName)
311 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
314 async function checkNewInstanceFollower (base: CheckerBaseParams, followerHost: string, type: CheckerType) {
315 const notificationType = UserNotificationType.NEW_INSTANCE_FOLLOWER
317 function notificationChecker (notification: UserNotification, type: CheckerType) {
318 if (type === 'presence') {
319 expect(notification).to.not.be.undefined
320 expect(notification.type).to.equal(notificationType)
322 checkActor(notification.actorFollow.follower)
323 expect(notification.actorFollow.follower.name).to.equal('peertube')
324 expect(notification.actorFollow.follower.host).to.equal(followerHost)
326 expect(notification.actorFollow.following.name).to.equal('peertube')
328 expect(notification).to.satisfy(n => {
329 return n.type !== notificationType || n.actorFollow.follower.host !== followerHost
334 function emailNotificationFinder (email: object) {
335 const text: string = email['text']
337 return text.includes('instance has a new follower') && text.includes(followerHost)
340 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
343 async function checkAutoInstanceFollowing (base: CheckerBaseParams, followerHost: string, followingHost: string, type: CheckerType) {
344 const notificationType = UserNotificationType.AUTO_INSTANCE_FOLLOWING
346 function notificationChecker (notification: UserNotification, type: CheckerType) {
347 if (type === 'presence') {
348 expect(notification).to.not.be.undefined
349 expect(notification.type).to.equal(notificationType)
351 const following = notification.actorFollow.following
352 checkActor(following)
353 expect(following.name).to.equal('peertube')
354 expect(following.host).to.equal(followingHost)
356 expect(notification.actorFollow.follower.name).to.equal('peertube')
357 expect(notification.actorFollow.follower.host).to.equal(followerHost)
359 expect(notification).to.satisfy(n => {
360 return n.type !== notificationType || n.actorFollow.following.host !== followingHost
365 function emailNotificationFinder (email: object) {
366 const text: string = email['text']
368 return text.includes(' automatically followed a new instance') && text.includes(followingHost)
371 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
374 async function checkCommentMention (
375 base: CheckerBaseParams,
379 byAccountDisplayName: string,
382 const notificationType = UserNotificationType.COMMENT_MENTION
384 function notificationChecker (notification: UserNotification, type: CheckerType) {
385 if (type === 'presence') {
386 expect(notification).to.not.be.undefined
387 expect(notification.type).to.equal(notificationType)
389 checkComment(notification.comment, commentId, threadId)
390 checkActor(notification.comment.account)
391 expect(notification.comment.account.displayName).to.equal(byAccountDisplayName)
393 checkVideo(notification.comment.video, undefined, uuid)
395 expect(notification).to.satisfy(n => n.type !== notificationType || n.comment.id !== commentId)
399 function emailNotificationFinder (email: object) {
400 const text: string = email['text']
402 return text.includes(' mentioned ') && text.includes(uuid) && text.includes(byAccountDisplayName)
405 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
408 let lastEmailCount = 0
410 async function checkNewCommentOnMyVideo (base: CheckerBaseParams, uuid: string, commentId: number, threadId: number, type: CheckerType) {
411 const notificationType = UserNotificationType.NEW_COMMENT_ON_MY_VIDEO
413 function notificationChecker (notification: UserNotification, type: CheckerType) {
414 if (type === 'presence') {
415 expect(notification).to.not.be.undefined
416 expect(notification.type).to.equal(notificationType)
418 checkComment(notification.comment, commentId, threadId)
419 checkActor(notification.comment.account)
420 checkVideo(notification.comment.video, undefined, uuid)
422 expect(notification).to.satisfy((n: UserNotification) => {
423 return n === undefined || n.comment === undefined || n.comment.id !== commentId
428 const commentUrl = `http://localhost:${base.server.port}/videos/watch/${uuid};threadId=${threadId}`
430 function emailNotificationFinder (email: object) {
431 return email['text'].indexOf(commentUrl) !== -1
434 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
436 if (type === 'presence') {
437 // We cannot detect email duplicates, so check we received another email
438 expect(base.emails).to.have.length.above(lastEmailCount)
439 lastEmailCount = base.emails.length
443 async function checkNewVideoAbuseForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) {
444 const notificationType = UserNotificationType.NEW_ABUSE_FOR_MODERATORS
446 function notificationChecker (notification: UserNotification, type: CheckerType) {
447 if (type === 'presence') {
448 expect(notification).to.not.be.undefined
449 expect(notification.type).to.equal(notificationType)
451 expect(notification.abuse.id).to.be.a('number')
452 checkVideo(notification.abuse.video, videoName, videoUUID)
454 expect(notification).to.satisfy((n: UserNotification) => {
455 return n === undefined || n.abuse === undefined || n.abuse.video.uuid !== videoUUID
460 function emailNotificationFinder (email: object) {
461 const text = email['text']
462 return text.indexOf(videoUUID) !== -1 && text.indexOf('abuse') !== -1
465 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
468 async function checkNewAbuseMessage (base: CheckerBaseParams, abuseId: number, message: string, toEmail: string, type: CheckerType) {
469 const notificationType = UserNotificationType.ABUSE_NEW_MESSAGE
471 function notificationChecker (notification: UserNotification, type: CheckerType) {
472 if (type === 'presence') {
473 expect(notification).to.not.be.undefined
474 expect(notification.type).to.equal(notificationType)
476 expect(notification.abuse.id).to.equal(abuseId)
478 expect(notification).to.satisfy((n: UserNotification) => {
479 return n === undefined || n.type !== notificationType || n.abuse === undefined || n.abuse.id !== abuseId
484 function emailNotificationFinder (email: object) {
485 const text = email['text']
486 const to = email['to'].filter(t => t.address === toEmail)
488 return text.indexOf(message) !== -1 && to.length !== 0
491 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
494 async function checkAbuseStateChange (base: CheckerBaseParams, abuseId: number, state: AbuseState, type: CheckerType) {
495 const notificationType = UserNotificationType.ABUSE_STATE_CHANGE
497 function notificationChecker (notification: UserNotification, type: CheckerType) {
498 if (type === 'presence') {
499 expect(notification).to.not.be.undefined
500 expect(notification.type).to.equal(notificationType)
502 expect(notification.abuse.id).to.equal(abuseId)
503 expect(notification.abuse.state).to.equal(state)
505 expect(notification).to.satisfy((n: UserNotification) => {
506 return n === undefined || n.abuse === undefined || n.abuse.id !== abuseId
511 function emailNotificationFinder (email: object) {
512 const text = email['text']
514 const contains = state === AbuseState.ACCEPTED
518 return text.indexOf(contains) !== -1
521 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
524 async function checkNewCommentAbuseForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) {
525 const notificationType = UserNotificationType.NEW_ABUSE_FOR_MODERATORS
527 function notificationChecker (notification: UserNotification, type: CheckerType) {
528 if (type === 'presence') {
529 expect(notification).to.not.be.undefined
530 expect(notification.type).to.equal(notificationType)
532 expect(notification.abuse.id).to.be.a('number')
533 checkVideo(notification.abuse.comment.video, videoName, videoUUID)
535 expect(notification).to.satisfy((n: UserNotification) => {
536 return n === undefined || n.abuse === undefined || n.abuse.comment.video.uuid !== videoUUID
541 function emailNotificationFinder (email: object) {
542 const text = email['text']
543 return text.indexOf(videoUUID) !== -1 && text.indexOf('abuse') !== -1
546 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
549 async function checkNewAccountAbuseForModerators (base: CheckerBaseParams, displayName: string, type: CheckerType) {
550 const notificationType = UserNotificationType.NEW_ABUSE_FOR_MODERATORS
552 function notificationChecker (notification: UserNotification, type: CheckerType) {
553 if (type === 'presence') {
554 expect(notification).to.not.be.undefined
555 expect(notification.type).to.equal(notificationType)
557 expect(notification.abuse.id).to.be.a('number')
558 expect(notification.abuse.account.displayName).to.equal(displayName)
560 expect(notification).to.satisfy((n: UserNotification) => {
561 return n === undefined || n.abuse === undefined || n.abuse.account.displayName !== displayName
566 function emailNotificationFinder (email: object) {
567 const text = email['text']
568 return text.indexOf(displayName) !== -1 && text.indexOf('abuse') !== -1
571 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
574 async function checkVideoAutoBlacklistForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) {
575 const notificationType = UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS
577 function notificationChecker (notification: UserNotification, type: CheckerType) {
578 if (type === 'presence') {
579 expect(notification).to.not.be.undefined
580 expect(notification.type).to.equal(notificationType)
582 expect(notification.videoBlacklist.video.id).to.be.a('number')
583 checkVideo(notification.videoBlacklist.video, videoName, videoUUID)
585 expect(notification).to.satisfy((n: UserNotification) => {
586 return n === undefined || n.video === undefined || n.video.uuid !== videoUUID
591 function emailNotificationFinder (email: object) {
592 const text = email['text']
593 return text.indexOf(videoUUID) !== -1 && email['text'].indexOf('video-auto-blacklist/list') !== -1
596 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
599 async function checkNewBlacklistOnMyVideo (
600 base: CheckerBaseParams,
603 blacklistType: 'blacklist' | 'unblacklist'
605 const notificationType = blacklistType === 'blacklist'
606 ? UserNotificationType.BLACKLIST_ON_MY_VIDEO
607 : UserNotificationType.UNBLACKLIST_ON_MY_VIDEO
609 function notificationChecker (notification: UserNotification) {
610 expect(notification).to.not.be.undefined
611 expect(notification.type).to.equal(notificationType)
613 const video = blacklistType === 'blacklist' ? notification.videoBlacklist.video : notification.video
615 checkVideo(video, videoName, videoUUID)
618 function emailNotificationFinder (email: object) {
619 const text = email['text']
620 return text.indexOf(videoUUID) !== -1 && text.indexOf(' ' + blacklistType) !== -1
623 await checkNotification(base, notificationChecker, emailNotificationFinder, 'presence')
626 function getAllNotificationsSettings () {
628 newVideoFromSubscription: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
629 newCommentOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
630 abuseAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
631 videoAutoBlacklistAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
632 blacklistOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
633 myVideoImportFinished: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
634 myVideoPublished: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
635 commentMention: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
636 newFollow: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
637 newUserRegistration: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
638 newInstanceFollower: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
639 abuseNewMessage: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
640 abuseStateChange: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
641 autoInstanceFollowing: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL
642 } as UserNotificationSetting
645 async function prepareNotificationsTest (serversCount = 3) {
646 const userNotifications: UserNotification[] = []
647 const adminNotifications: UserNotification[] = []
648 const adminNotificationsServer2: UserNotification[] = []
649 const emails: object[] = []
651 const port = await MockSmtpServer.Instance.collectEmails(emails)
653 const overrideConfig = {
655 hostname: 'localhost',
662 const servers = await flushAndRunMultipleServers(serversCount, overrideConfig)
664 await setAccessTokensToServers(servers)
666 if (serversCount > 1) {
667 await doubleFollow(servers[0], servers[1])
672 password: 'super password'
676 accessToken: servers[0].accessToken,
677 username: user.username,
678 password: user.password,
679 videoQuota: 10 * 1000 * 1000
681 const userAccessToken = await userLogin(servers[0], user)
683 await updateMyNotificationSettings(servers[0].url, userAccessToken, getAllNotificationsSettings())
684 await updateMyNotificationSettings(servers[0].url, servers[0].accessToken, getAllNotificationsSettings())
686 if (serversCount > 1) {
687 await updateMyNotificationSettings(servers[1].url, servers[1].accessToken, getAllNotificationsSettings())
691 const socket = getUserNotificationSocket(servers[0].url, userAccessToken)
692 socket.on('new-notification', n => userNotifications.push(n))
695 const socket = getUserNotificationSocket(servers[0].url, servers[0].accessToken)
696 socket.on('new-notification', n => adminNotifications.push(n))
699 if (serversCount > 1) {
700 const socket = getUserNotificationSocket(servers[1].url, servers[1].accessToken)
701 socket.on('new-notification', n => adminNotificationsServer2.push(n))
704 const resChannel = await getMyUserInformation(servers[0].url, servers[0].accessToken)
705 const channelId = resChannel.body.videoChannels[0].id
710 adminNotificationsServer2,
718 // ---------------------------------------------------------------------------
723 getAllNotificationsSettings,
725 markAsReadAllNotifications,
726 checkMyVideoImportIsFinished,
728 checkAutoInstanceFollowing,
729 checkVideoIsPublished,
730 checkNewVideoFromSubscription,
732 checkNewCommentOnMyVideo,
733 checkNewBlacklistOnMyVideo,
735 updateMyNotificationSettings,
736 checkNewVideoAbuseForModerators,
737 checkVideoAutoBlacklistForModerators,
738 checkNewAbuseMessage,
739 checkAbuseStateChange,
740 getUserNotifications,
741 markAsReadNotifications,
743 checkNewInstanceFollower,
744 prepareNotificationsTest,
745 checkNewCommentAbuseForModerators,
746 checkNewAccountAbuseForModerators