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'
14 import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
16 function updateMyNotificationSettings (
19 settings: UserNotificationSetting,
20 statusCodeExpected = HttpStatusCode.NO_CONTENT_204
22 const path = '/api/v1/users/me/notification-settings'
24 return makePutBodyRequest({
33 async function getUserNotifications (
40 statusCodeExpected = HttpStatusCode.OK_200
42 const path = '/api/v1/users/me/notifications'
44 return makeGetRequest({
58 function markAsReadNotifications (url: string, token: string, ids: number[], statusCodeExpected = HttpStatusCode.NO_CONTENT_204) {
59 const path = '/api/v1/users/me/notifications/read'
61 return makePostBodyRequest({
70 function markAsReadAllNotifications (url: string, token: string, statusCodeExpected = HttpStatusCode.NO_CONTENT_204) {
71 const path = '/api/v1/users/me/notifications/read-all'
73 return makePostBodyRequest({
81 async function getLastNotification (serverUrl: string, accessToken: string) {
82 const res = await getUserNotifications(serverUrl, accessToken, 0, 1, undefined, '-createdAt')
84 if (res.body.total === 0) return undefined
86 return res.body.data[0] as UserNotification
89 type CheckerBaseParams = {
92 socketNotifications: UserNotification[]
94 check?: { web: boolean, mail: boolean }
97 type CheckerType = 'presence' | 'absence'
99 async function checkNotification (
100 base: CheckerBaseParams,
101 notificationChecker: (notification: UserNotification, type: CheckerType) => void,
102 emailNotificationFinder: (email: object) => boolean,
103 checkType: CheckerType
105 const check = base.check || { web: true, mail: true }
108 const notification = await getLastNotification(base.server.url, base.token)
110 if (notification || checkType !== 'absence') {
111 notificationChecker(notification, checkType)
114 const socketNotification = base.socketNotifications.find(n => {
116 notificationChecker(n, 'presence')
123 if (checkType === 'presence') {
124 const obj = inspect(base.socketNotifications, { depth: 5 })
125 expect(socketNotification, 'The socket notification is absent when it should be present. ' + obj).to.not.be.undefined
127 const obj = inspect(socketNotification, { depth: 5 })
128 expect(socketNotification, 'The socket notification is present when it should not be present. ' + obj).to.be.undefined
134 const email = base.emails
137 .find(e => emailNotificationFinder(e))
139 if (checkType === 'presence') {
140 const emails = base.emails.map(e => e.text)
141 expect(email, 'The email is absent when is should be present. ' + inspect(emails)).to.not.be.undefined
143 expect(email, 'The email is present when is should not be present. ' + inspect(email)).to.be.undefined
148 function checkVideo (video: any, videoName?: string, videoUUID?: string) {
150 expect(video.name).to.be.a('string')
151 expect(video.name).to.not.be.empty
152 expect(video.name).to.equal(videoName)
156 expect(video.uuid).to.be.a('string')
157 expect(video.uuid).to.not.be.empty
158 expect(video.uuid).to.equal(videoUUID)
161 expect(video.id).to.be.a('number')
164 function checkActor (actor: any) {
165 expect(actor.displayName).to.be.a('string')
166 expect(actor.displayName).to.not.be.empty
167 expect(actor.host).to.not.be.undefined
170 function checkComment (comment: any, commentId: number, threadId: number) {
171 expect(comment.id).to.equal(commentId)
172 expect(comment.threadId).to.equal(threadId)
175 async function checkNewVideoFromSubscription (base: CheckerBaseParams, videoName: string, videoUUID: string, type: CheckerType) {
176 const notificationType = UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION
178 function notificationChecker (notification: UserNotification, type: CheckerType) {
179 if (type === 'presence') {
180 expect(notification).to.not.be.undefined
181 expect(notification.type).to.equal(notificationType)
183 checkVideo(notification.video, videoName, videoUUID)
184 checkActor(notification.video.channel)
186 expect(notification).to.satisfy((n: UserNotification) => {
187 return n === undefined || n.type !== UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION || n.video.name !== videoName
192 function emailNotificationFinder (email: object) {
193 const text = email['text']
194 return text.indexOf(videoUUID) !== -1 && text.indexOf('Your subscription') !== -1
197 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
200 async function checkVideoIsPublished (base: CheckerBaseParams, videoName: string, videoUUID: string, type: CheckerType) {
201 const notificationType = UserNotificationType.MY_VIDEO_PUBLISHED
203 function notificationChecker (notification: UserNotification, type: CheckerType) {
204 if (type === 'presence') {
205 expect(notification).to.not.be.undefined
206 expect(notification.type).to.equal(notificationType)
208 checkVideo(notification.video, videoName, videoUUID)
209 checkActor(notification.video.channel)
211 expect(notification.video).to.satisfy(v => v === undefined || v.name !== videoName)
215 function emailNotificationFinder (email: object) {
216 const text: string = email['text']
217 return text.includes(videoUUID) && text.includes('Your video')
220 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
223 async function checkMyVideoImportIsFinished (
224 base: CheckerBaseParams,
231 const notificationType = success ? UserNotificationType.MY_VIDEO_IMPORT_SUCCESS : UserNotificationType.MY_VIDEO_IMPORT_ERROR
233 function notificationChecker (notification: UserNotification, type: CheckerType) {
234 if (type === 'presence') {
235 expect(notification).to.not.be.undefined
236 expect(notification.type).to.equal(notificationType)
238 expect(notification.videoImport.targetUrl).to.equal(url)
240 if (success) checkVideo(notification.videoImport.video, videoName, videoUUID)
242 expect(notification.videoImport).to.satisfy(i => i === undefined || i.targetUrl !== url)
246 function emailNotificationFinder (email: object) {
247 const text: string = email['text']
248 const toFind = success ? ' finished' : ' error'
250 return text.includes(url) && text.includes(toFind)
253 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
256 async function checkUserRegistered (base: CheckerBaseParams, username: string, type: CheckerType) {
257 const notificationType = UserNotificationType.NEW_USER_REGISTRATION
259 function notificationChecker (notification: UserNotification, type: CheckerType) {
260 if (type === 'presence') {
261 expect(notification).to.not.be.undefined
262 expect(notification.type).to.equal(notificationType)
264 checkActor(notification.account)
265 expect(notification.account.name).to.equal(username)
267 expect(notification).to.satisfy(n => n.type !== notificationType || n.account.name !== username)
271 function emailNotificationFinder (email: object) {
272 const text: string = email['text']
274 return text.includes(' registered.') && text.includes(username)
277 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
280 async function checkNewActorFollow (
281 base: CheckerBaseParams,
282 followType: 'channel' | 'account',
283 followerName: string,
284 followerDisplayName: string,
285 followingDisplayName: string,
288 const notificationType = UserNotificationType.NEW_FOLLOW
290 function notificationChecker (notification: UserNotification, type: CheckerType) {
291 if (type === 'presence') {
292 expect(notification).to.not.be.undefined
293 expect(notification.type).to.equal(notificationType)
295 checkActor(notification.actorFollow.follower)
296 expect(notification.actorFollow.follower.displayName).to.equal(followerDisplayName)
297 expect(notification.actorFollow.follower.name).to.equal(followerName)
298 expect(notification.actorFollow.follower.host).to.not.be.undefined
300 const following = notification.actorFollow.following
301 expect(following.displayName).to.equal(followingDisplayName)
302 expect(following.type).to.equal(followType)
304 expect(notification).to.satisfy(n => {
305 return n.type !== notificationType ||
306 (n.actorFollow.follower.name !== followerName && n.actorFollow.following !== followingDisplayName)
311 function emailNotificationFinder (email: object) {
312 const text: string = email['text']
314 return text.includes(followType) && text.includes(followingDisplayName) && text.includes(followerDisplayName)
317 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
320 async function checkNewInstanceFollower (base: CheckerBaseParams, followerHost: string, type: CheckerType) {
321 const notificationType = UserNotificationType.NEW_INSTANCE_FOLLOWER
323 function notificationChecker (notification: UserNotification, type: CheckerType) {
324 if (type === 'presence') {
325 expect(notification).to.not.be.undefined
326 expect(notification.type).to.equal(notificationType)
328 checkActor(notification.actorFollow.follower)
329 expect(notification.actorFollow.follower.name).to.equal('peertube')
330 expect(notification.actorFollow.follower.host).to.equal(followerHost)
332 expect(notification.actorFollow.following.name).to.equal('peertube')
334 expect(notification).to.satisfy(n => {
335 return n.type !== notificationType || n.actorFollow.follower.host !== followerHost
340 function emailNotificationFinder (email: object) {
341 const text: string = email['text']
343 return text.includes('instance has a new follower') && text.includes(followerHost)
346 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
349 async function checkAutoInstanceFollowing (base: CheckerBaseParams, followerHost: string, followingHost: string, type: CheckerType) {
350 const notificationType = UserNotificationType.AUTO_INSTANCE_FOLLOWING
352 function notificationChecker (notification: UserNotification, type: CheckerType) {
353 if (type === 'presence') {
354 expect(notification).to.not.be.undefined
355 expect(notification.type).to.equal(notificationType)
357 const following = notification.actorFollow.following
358 checkActor(following)
359 expect(following.name).to.equal('peertube')
360 expect(following.host).to.equal(followingHost)
362 expect(notification.actorFollow.follower.name).to.equal('peertube')
363 expect(notification.actorFollow.follower.host).to.equal(followerHost)
365 expect(notification).to.satisfy(n => {
366 return n.type !== notificationType || n.actorFollow.following.host !== followingHost
371 function emailNotificationFinder (email: object) {
372 const text: string = email['text']
374 return text.includes(' automatically followed a new instance') && text.includes(followingHost)
377 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
380 async function checkCommentMention (
381 base: CheckerBaseParams,
385 byAccountDisplayName: string,
388 const notificationType = UserNotificationType.COMMENT_MENTION
390 function notificationChecker (notification: UserNotification, type: CheckerType) {
391 if (type === 'presence') {
392 expect(notification).to.not.be.undefined
393 expect(notification.type).to.equal(notificationType)
395 checkComment(notification.comment, commentId, threadId)
396 checkActor(notification.comment.account)
397 expect(notification.comment.account.displayName).to.equal(byAccountDisplayName)
399 checkVideo(notification.comment.video, undefined, uuid)
401 expect(notification).to.satisfy(n => n.type !== notificationType || n.comment.id !== commentId)
405 function emailNotificationFinder (email: object) {
406 const text: string = email['text']
408 return text.includes(' mentioned ') && text.includes(uuid) && text.includes(byAccountDisplayName)
411 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
414 let lastEmailCount = 0
416 async function checkNewCommentOnMyVideo (base: CheckerBaseParams, uuid: string, commentId: number, threadId: number, type: CheckerType) {
417 const notificationType = UserNotificationType.NEW_COMMENT_ON_MY_VIDEO
419 function notificationChecker (notification: UserNotification, type: CheckerType) {
420 if (type === 'presence') {
421 expect(notification).to.not.be.undefined
422 expect(notification.type).to.equal(notificationType)
424 checkComment(notification.comment, commentId, threadId)
425 checkActor(notification.comment.account)
426 checkVideo(notification.comment.video, undefined, uuid)
428 expect(notification).to.satisfy((n: UserNotification) => {
429 return n === undefined || n.comment === undefined || n.comment.id !== commentId
434 const commentUrl = `http://localhost:${base.server.port}/videos/watch/${uuid};threadId=${threadId}`
436 function emailNotificationFinder (email: object) {
437 return email['text'].indexOf(commentUrl) !== -1
440 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
442 if (type === 'presence') {
443 // We cannot detect email duplicates, so check we received another email
444 expect(base.emails).to.have.length.above(lastEmailCount)
445 lastEmailCount = base.emails.length
449 async function checkNewVideoAbuseForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) {
450 const notificationType = UserNotificationType.NEW_ABUSE_FOR_MODERATORS
452 function notificationChecker (notification: UserNotification, type: CheckerType) {
453 if (type === 'presence') {
454 expect(notification).to.not.be.undefined
455 expect(notification.type).to.equal(notificationType)
457 expect(notification.abuse.id).to.be.a('number')
458 checkVideo(notification.abuse.video, videoName, videoUUID)
460 expect(notification).to.satisfy((n: UserNotification) => {
461 return n === undefined || n.abuse === undefined || n.abuse.video.uuid !== videoUUID
466 function emailNotificationFinder (email: object) {
467 const text = email['text']
468 return text.indexOf(videoUUID) !== -1 && text.indexOf('abuse') !== -1
471 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
474 async function checkNewAbuseMessage (base: CheckerBaseParams, abuseId: number, message: string, toEmail: string, type: CheckerType) {
475 const notificationType = UserNotificationType.ABUSE_NEW_MESSAGE
477 function notificationChecker (notification: UserNotification, type: CheckerType) {
478 if (type === 'presence') {
479 expect(notification).to.not.be.undefined
480 expect(notification.type).to.equal(notificationType)
482 expect(notification.abuse.id).to.equal(abuseId)
484 expect(notification).to.satisfy((n: UserNotification) => {
485 return n === undefined || n.type !== notificationType || n.abuse === undefined || n.abuse.id !== abuseId
490 function emailNotificationFinder (email: object) {
491 const text = email['text']
492 const to = email['to'].filter(t => t.address === toEmail)
494 return text.indexOf(message) !== -1 && to.length !== 0
497 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
500 async function checkAbuseStateChange (base: CheckerBaseParams, abuseId: number, state: AbuseState, type: CheckerType) {
501 const notificationType = UserNotificationType.ABUSE_STATE_CHANGE
503 function notificationChecker (notification: UserNotification, type: CheckerType) {
504 if (type === 'presence') {
505 expect(notification).to.not.be.undefined
506 expect(notification.type).to.equal(notificationType)
508 expect(notification.abuse.id).to.equal(abuseId)
509 expect(notification.abuse.state).to.equal(state)
511 expect(notification).to.satisfy((n: UserNotification) => {
512 return n === undefined || n.abuse === undefined || n.abuse.id !== abuseId
517 function emailNotificationFinder (email: object) {
518 const text = email['text']
520 const contains = state === AbuseState.ACCEPTED
524 return text.indexOf(contains) !== -1
527 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
530 async function checkNewCommentAbuseForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) {
531 const notificationType = UserNotificationType.NEW_ABUSE_FOR_MODERATORS
533 function notificationChecker (notification: UserNotification, type: CheckerType) {
534 if (type === 'presence') {
535 expect(notification).to.not.be.undefined
536 expect(notification.type).to.equal(notificationType)
538 expect(notification.abuse.id).to.be.a('number')
539 checkVideo(notification.abuse.comment.video, videoName, videoUUID)
541 expect(notification).to.satisfy((n: UserNotification) => {
542 return n === undefined || n.abuse === undefined || n.abuse.comment.video.uuid !== videoUUID
547 function emailNotificationFinder (email: object) {
548 const text = email['text']
549 return text.indexOf(videoUUID) !== -1 && text.indexOf('abuse') !== -1
552 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
555 async function checkNewAccountAbuseForModerators (base: CheckerBaseParams, displayName: string, type: CheckerType) {
556 const notificationType = UserNotificationType.NEW_ABUSE_FOR_MODERATORS
558 function notificationChecker (notification: UserNotification, type: CheckerType) {
559 if (type === 'presence') {
560 expect(notification).to.not.be.undefined
561 expect(notification.type).to.equal(notificationType)
563 expect(notification.abuse.id).to.be.a('number')
564 expect(notification.abuse.account.displayName).to.equal(displayName)
566 expect(notification).to.satisfy((n: UserNotification) => {
567 return n === undefined || n.abuse === undefined || n.abuse.account.displayName !== displayName
572 function emailNotificationFinder (email: object) {
573 const text = email['text']
574 return text.indexOf(displayName) !== -1 && text.indexOf('abuse') !== -1
577 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
580 async function checkVideoAutoBlacklistForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) {
581 const notificationType = UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS
583 function notificationChecker (notification: UserNotification, type: CheckerType) {
584 if (type === 'presence') {
585 expect(notification).to.not.be.undefined
586 expect(notification.type).to.equal(notificationType)
588 expect(notification.videoBlacklist.video.id).to.be.a('number')
589 checkVideo(notification.videoBlacklist.video, videoName, videoUUID)
591 expect(notification).to.satisfy((n: UserNotification) => {
592 return n === undefined || n.video === undefined || n.video.uuid !== videoUUID
597 function emailNotificationFinder (email: object) {
598 const text = email['text']
599 return text.indexOf(videoUUID) !== -1 && email['text'].indexOf('video-auto-blacklist/list') !== -1
602 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
605 async function checkNewBlacklistOnMyVideo (
606 base: CheckerBaseParams,
609 blacklistType: 'blacklist' | 'unblacklist'
611 const notificationType = blacklistType === 'blacklist'
612 ? UserNotificationType.BLACKLIST_ON_MY_VIDEO
613 : UserNotificationType.UNBLACKLIST_ON_MY_VIDEO
615 function notificationChecker (notification: UserNotification) {
616 expect(notification).to.not.be.undefined
617 expect(notification.type).to.equal(notificationType)
619 const video = blacklistType === 'blacklist' ? notification.videoBlacklist.video : notification.video
621 checkVideo(video, videoName, videoUUID)
624 function emailNotificationFinder (email: object) {
625 const text = email['text']
626 return text.indexOf(videoUUID) !== -1 && text.indexOf(' ' + blacklistType) !== -1
629 await checkNotification(base, notificationChecker, emailNotificationFinder, 'presence')
632 function getAllNotificationsSettings () {
634 newVideoFromSubscription: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
635 newCommentOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
636 abuseAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
637 videoAutoBlacklistAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
638 blacklistOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
639 myVideoImportFinished: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
640 myVideoPublished: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
641 commentMention: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
642 newFollow: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
643 newUserRegistration: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
644 newInstanceFollower: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
645 abuseNewMessage: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
646 abuseStateChange: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
647 autoInstanceFollowing: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL
648 } as UserNotificationSetting
651 async function prepareNotificationsTest (serversCount = 3) {
652 const userNotifications: UserNotification[] = []
653 const adminNotifications: UserNotification[] = []
654 const adminNotificationsServer2: UserNotification[] = []
655 const emails: object[] = []
657 const port = await MockSmtpServer.Instance.collectEmails(emails)
659 const overrideConfig = {
661 hostname: 'localhost',
668 const servers = await flushAndRunMultipleServers(serversCount, overrideConfig)
670 await setAccessTokensToServers(servers)
672 if (serversCount > 1) {
673 await doubleFollow(servers[0], servers[1])
678 password: 'super password'
682 accessToken: servers[0].accessToken,
683 username: user.username,
684 password: user.password,
685 videoQuota: 10 * 1000 * 1000
687 const userAccessToken = await userLogin(servers[0], user)
689 await updateMyNotificationSettings(servers[0].url, userAccessToken, getAllNotificationsSettings())
690 await updateMyNotificationSettings(servers[0].url, servers[0].accessToken, getAllNotificationsSettings())
692 if (serversCount > 1) {
693 await updateMyNotificationSettings(servers[1].url, servers[1].accessToken, getAllNotificationsSettings())
697 const socket = getUserNotificationSocket(servers[0].url, userAccessToken)
698 socket.on('new-notification', n => userNotifications.push(n))
701 const socket = getUserNotificationSocket(servers[0].url, servers[0].accessToken)
702 socket.on('new-notification', n => adminNotifications.push(n))
705 if (serversCount > 1) {
706 const socket = getUserNotificationSocket(servers[1].url, servers[1].accessToken)
707 socket.on('new-notification', n => adminNotificationsServer2.push(n))
710 const resChannel = await getMyUserInformation(servers[0].url, servers[0].accessToken)
711 const channelId = resChannel.body.videoChannels[0].id
716 adminNotificationsServer2,
724 // ---------------------------------------------------------------------------
729 getAllNotificationsSettings,
731 markAsReadAllNotifications,
732 checkMyVideoImportIsFinished,
734 checkAutoInstanceFollowing,
735 checkVideoIsPublished,
736 checkNewVideoFromSubscription,
738 checkNewCommentOnMyVideo,
739 checkNewBlacklistOnMyVideo,
741 updateMyNotificationSettings,
742 checkNewVideoAbuseForModerators,
743 checkVideoAutoBlacklistForModerators,
744 checkNewAbuseMessage,
745 checkAbuseStateChange,
746 getUserNotifications,
747 markAsReadNotifications,
749 checkNewInstanceFollower,
750 prepareNotificationsTest,
751 checkNewCommentAbuseForModerators,
752 checkNewAccountAbuseForModerators