user-notifications.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. /* tslint:disable:no-unused-expression */
  2. import { makeGetRequest, makePostBodyRequest, makePutBodyRequest } from '../requests/requests'
  3. import { UserNotification, UserNotificationSetting, UserNotificationType } from '../../models/users'
  4. import { ServerInfo } from '..'
  5. import { expect } from 'chai'
  6. import { inspect } from 'util'
  7. function updateMyNotificationSettings (url: string, token: string, settings: UserNotificationSetting, statusCodeExpected = 204) {
  8. const path = '/api/v1/users/me/notification-settings'
  9. return makePutBodyRequest({
  10. url,
  11. path,
  12. token,
  13. fields: settings,
  14. statusCodeExpected
  15. })
  16. }
  17. async function getUserNotifications (
  18. url: string,
  19. token: string,
  20. start: number,
  21. count: number,
  22. unread?: boolean,
  23. sort = '-createdAt',
  24. statusCodeExpected = 200
  25. ) {
  26. const path = '/api/v1/users/me/notifications'
  27. return makeGetRequest({
  28. url,
  29. path,
  30. token,
  31. query: {
  32. start,
  33. count,
  34. sort,
  35. unread
  36. },
  37. statusCodeExpected
  38. })
  39. }
  40. function markAsReadNotifications (url: string, token: string, ids: number[], statusCodeExpected = 204) {
  41. const path = '/api/v1/users/me/notifications/read'
  42. return makePostBodyRequest({
  43. url,
  44. path,
  45. token,
  46. fields: { ids },
  47. statusCodeExpected
  48. })
  49. }
  50. function markAsReadAllNotifications (url: string, token: string, statusCodeExpected = 204) {
  51. const path = '/api/v1/users/me/notifications/read-all'
  52. return makePostBodyRequest({
  53. url,
  54. path,
  55. token,
  56. statusCodeExpected
  57. })
  58. }
  59. async function getLastNotification (serverUrl: string, accessToken: string) {
  60. const res = await getUserNotifications(serverUrl, accessToken, 0, 1, undefined, '-createdAt')
  61. if (res.body.total === 0) return undefined
  62. return res.body.data[0] as UserNotification
  63. }
  64. type CheckerBaseParams = {
  65. server: ServerInfo
  66. emails: object[]
  67. socketNotifications: UserNotification[]
  68. token: string,
  69. check?: { web: boolean, mail: boolean }
  70. }
  71. type CheckerType = 'presence' | 'absence'
  72. async function checkNotification (
  73. base: CheckerBaseParams,
  74. notificationChecker: (notification: UserNotification, type: CheckerType) => void,
  75. emailNotificationFinder: (email: object) => boolean,
  76. checkType: CheckerType
  77. ) {
  78. const check = base.check || { web: true, mail: true }
  79. if (check.web) {
  80. const notification = await getLastNotification(base.server.url, base.token)
  81. if (notification || checkType !== 'absence') {
  82. notificationChecker(notification, checkType)
  83. }
  84. const socketNotification = base.socketNotifications.find(n => {
  85. try {
  86. notificationChecker(n, 'presence')
  87. return true
  88. } catch {
  89. return false
  90. }
  91. })
  92. if (checkType === 'presence') {
  93. const obj = inspect(base.socketNotifications, { depth: 5 })
  94. expect(socketNotification, 'The socket notification is absent. ' + obj).to.not.be.undefined
  95. } else {
  96. const obj = inspect(socketNotification, { depth: 5 })
  97. expect(socketNotification, 'The socket notification is present. ' + obj).to.be.undefined
  98. }
  99. }
  100. if (check.mail) {
  101. // Last email
  102. const email = base.emails
  103. .slice()
  104. .reverse()
  105. .find(e => emailNotificationFinder(e))
  106. if (checkType === 'presence') {
  107. expect(email, 'The email is absent. ' + inspect(base.emails)).to.not.be.undefined
  108. } else {
  109. expect(email, 'The email is present. ' + inspect(email)).to.be.undefined
  110. }
  111. }
  112. }
  113. function checkVideo (video: any, videoName?: string, videoUUID?: string) {
  114. expect(video.name).to.be.a('string')
  115. expect(video.name).to.not.be.empty
  116. if (videoName) expect(video.name).to.equal(videoName)
  117. expect(video.uuid).to.be.a('string')
  118. expect(video.uuid).to.not.be.empty
  119. if (videoUUID) expect(video.uuid).to.equal(videoUUID)
  120. expect(video.id).to.be.a('number')
  121. }
  122. function checkActor (actor: any) {
  123. expect(actor.displayName).to.be.a('string')
  124. expect(actor.displayName).to.not.be.empty
  125. expect(actor.host).to.not.be.undefined
  126. }
  127. function checkComment (comment: any, commentId: number, threadId: number) {
  128. expect(comment.id).to.equal(commentId)
  129. expect(comment.threadId).to.equal(threadId)
  130. }
  131. async function checkNewVideoFromSubscription (base: CheckerBaseParams, videoName: string, videoUUID: string, type: CheckerType) {
  132. const notificationType = UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION
  133. function notificationChecker (notification: UserNotification, type: CheckerType) {
  134. if (type === 'presence') {
  135. expect(notification).to.not.be.undefined
  136. expect(notification.type).to.equal(notificationType)
  137. checkVideo(notification.video, videoName, videoUUID)
  138. checkActor(notification.video.channel)
  139. } else {
  140. expect(notification).to.satisfy((n: UserNotification) => {
  141. return n === undefined || n.type !== UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION || n.video.name !== videoName
  142. })
  143. }
  144. }
  145. function emailFinder (email: object) {
  146. const text = email[ 'text' ]
  147. return text.indexOf(videoUUID) !== -1 && text.indexOf('Your subscription') !== -1
  148. }
  149. await checkNotification(base, notificationChecker, emailFinder, type)
  150. }
  151. async function checkVideoIsPublished (base: CheckerBaseParams, videoName: string, videoUUID: string, type: CheckerType) {
  152. const notificationType = UserNotificationType.MY_VIDEO_PUBLISHED
  153. function notificationChecker (notification: UserNotification, type: CheckerType) {
  154. if (type === 'presence') {
  155. expect(notification).to.not.be.undefined
  156. expect(notification.type).to.equal(notificationType)
  157. checkVideo(notification.video, videoName, videoUUID)
  158. checkActor(notification.video.channel)
  159. } else {
  160. expect(notification.video).to.satisfy(v => v === undefined || v.name !== videoName)
  161. }
  162. }
  163. function emailFinder (email: object) {
  164. const text: string = email[ 'text' ]
  165. return text.includes(videoUUID) && text.includes('Your video')
  166. }
  167. await checkNotification(base, notificationChecker, emailFinder, type)
  168. }
  169. async function checkMyVideoImportIsFinished (
  170. base: CheckerBaseParams,
  171. videoName: string,
  172. videoUUID: string,
  173. url: string,
  174. success: boolean,
  175. type: CheckerType
  176. ) {
  177. const notificationType = success ? UserNotificationType.MY_VIDEO_IMPORT_SUCCESS : UserNotificationType.MY_VIDEO_IMPORT_ERROR
  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)
  182. expect(notification.videoImport.targetUrl).to.equal(url)
  183. if (success) checkVideo(notification.videoImport.video, videoName, videoUUID)
  184. } else {
  185. expect(notification.videoImport).to.satisfy(i => i === undefined || i.targetUrl !== url)
  186. }
  187. }
  188. function emailFinder (email: object) {
  189. const text: string = email[ 'text' ]
  190. const toFind = success ? ' finished' : ' error'
  191. return text.includes(url) && text.includes(toFind)
  192. }
  193. await checkNotification(base, notificationChecker, emailFinder, type)
  194. }
  195. async function checkUserRegistered (base: CheckerBaseParams, username: string, type: CheckerType) {
  196. const notificationType = UserNotificationType.NEW_USER_REGISTRATION
  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)
  201. checkActor(notification.account)
  202. expect(notification.account.name).to.equal(username)
  203. } else {
  204. expect(notification).to.satisfy(n => n.type !== notificationType || n.account.name !== username)
  205. }
  206. }
  207. function emailFinder (email: object) {
  208. const text: string = email[ 'text' ]
  209. return text.includes(' registered ') && text.includes(username)
  210. }
  211. await checkNotification(base, notificationChecker, emailFinder, type)
  212. }
  213. async function checkNewActorFollow (
  214. base: CheckerBaseParams,
  215. followType: 'channel' | 'account',
  216. followerName: string,
  217. followerDisplayName: string,
  218. followingDisplayName: string,
  219. type: CheckerType
  220. ) {
  221. const notificationType = UserNotificationType.NEW_FOLLOW
  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. checkActor(notification.actorFollow.follower)
  227. expect(notification.actorFollow.follower.displayName).to.equal(followerDisplayName)
  228. expect(notification.actorFollow.follower.name).to.equal(followerName)
  229. expect(notification.actorFollow.follower.host).to.not.be.undefined
  230. expect(notification.actorFollow.following.displayName).to.equal(followingDisplayName)
  231. expect(notification.actorFollow.following.type).to.equal(followType)
  232. } else {
  233. expect(notification).to.satisfy(n => {
  234. return n.type !== notificationType ||
  235. (n.actorFollow.follower.name !== followerName && n.actorFollow.following !== followingDisplayName)
  236. })
  237. }
  238. }
  239. function emailFinder (email: object) {
  240. const text: string = email[ 'text' ]
  241. return text.includes('Your ' + followType) && text.includes(followingDisplayName) && text.includes(followerDisplayName)
  242. }
  243. await checkNotification(base, notificationChecker, emailFinder, type)
  244. }
  245. async function checkNewInstanceFollower (base: CheckerBaseParams, followerHost: string, type: CheckerType) {
  246. const notificationType = UserNotificationType.NEW_INSTANCE_FOLLOWER
  247. function notificationChecker (notification: UserNotification, type: CheckerType) {
  248. if (type === 'presence') {
  249. expect(notification).to.not.be.undefined
  250. expect(notification.type).to.equal(notificationType)
  251. checkActor(notification.actorFollow.follower)
  252. expect(notification.actorFollow.follower.name).to.equal('peertube')
  253. expect(notification.actorFollow.follower.host).to.equal(followerHost)
  254. expect(notification.actorFollow.following.name).to.equal('peertube')
  255. } else {
  256. expect(notification).to.satisfy(n => {
  257. return n.type !== notificationType || n.actorFollow.follower.host !== followerHost
  258. })
  259. }
  260. }
  261. function emailFinder (email: object) {
  262. const text: string = email[ 'text' ]
  263. return text.includes('instance has a new follower') && text.includes(followerHost)
  264. }
  265. await checkNotification(base, notificationChecker, emailFinder, type)
  266. }
  267. async function checkCommentMention (
  268. base: CheckerBaseParams,
  269. uuid: string,
  270. commentId: number,
  271. threadId: number,
  272. byAccountDisplayName: string,
  273. type: CheckerType
  274. ) {
  275. const notificationType = UserNotificationType.COMMENT_MENTION
  276. function notificationChecker (notification: UserNotification, type: CheckerType) {
  277. if (type === 'presence') {
  278. expect(notification).to.not.be.undefined
  279. expect(notification.type).to.equal(notificationType)
  280. checkComment(notification.comment, commentId, threadId)
  281. checkActor(notification.comment.account)
  282. expect(notification.comment.account.displayName).to.equal(byAccountDisplayName)
  283. checkVideo(notification.comment.video, undefined, uuid)
  284. } else {
  285. expect(notification).to.satisfy(n => n.type !== notificationType || n.comment.id !== commentId)
  286. }
  287. }
  288. function emailFinder (email: object) {
  289. const text: string = email[ 'text' ]
  290. return text.includes(' mentioned ') && text.includes(uuid) && text.includes(byAccountDisplayName)
  291. }
  292. await checkNotification(base, notificationChecker, emailFinder, type)
  293. }
  294. let lastEmailCount = 0
  295. async function checkNewCommentOnMyVideo (base: CheckerBaseParams, uuid: string, commentId: number, threadId: number, type: CheckerType) {
  296. const notificationType = UserNotificationType.NEW_COMMENT_ON_MY_VIDEO
  297. function notificationChecker (notification: UserNotification, type: CheckerType) {
  298. if (type === 'presence') {
  299. expect(notification).to.not.be.undefined
  300. expect(notification.type).to.equal(notificationType)
  301. checkComment(notification.comment, commentId, threadId)
  302. checkActor(notification.comment.account)
  303. checkVideo(notification.comment.video, undefined, uuid)
  304. } else {
  305. expect(notification).to.satisfy((n: UserNotification) => {
  306. return n === undefined || n.comment === undefined || n.comment.id !== commentId
  307. })
  308. }
  309. }
  310. const commentUrl = `http://localhost:${base.server.port}/videos/watch/${uuid};threadId=${threadId}`
  311. function emailFinder (email: object) {
  312. return email[ 'text' ].indexOf(commentUrl) !== -1
  313. }
  314. await checkNotification(base, notificationChecker, emailFinder, type)
  315. if (type === 'presence') {
  316. // We cannot detect email duplicates, so check we received another email
  317. expect(base.emails).to.have.length.above(lastEmailCount)
  318. lastEmailCount = base.emails.length
  319. }
  320. }
  321. async function checkNewVideoAbuseForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) {
  322. const notificationType = UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS
  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)
  327. expect(notification.videoAbuse.id).to.be.a('number')
  328. checkVideo(notification.videoAbuse.video, videoName, videoUUID)
  329. } else {
  330. expect(notification).to.satisfy((n: UserNotification) => {
  331. return n === undefined || n.videoAbuse === undefined || n.videoAbuse.video.uuid !== videoUUID
  332. })
  333. }
  334. }
  335. function emailFinder (email: object) {
  336. const text = email[ 'text' ]
  337. return text.indexOf(videoUUID) !== -1 && text.indexOf('abuse') !== -1
  338. }
  339. await checkNotification(base, notificationChecker, emailFinder, type)
  340. }
  341. async function checkVideoAutoBlacklistForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) {
  342. const notificationType = UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS
  343. function notificationChecker (notification: UserNotification, type: CheckerType) {
  344. if (type === 'presence') {
  345. expect(notification).to.not.be.undefined
  346. expect(notification.type).to.equal(notificationType)
  347. expect(notification.video.id).to.be.a('number')
  348. checkVideo(notification.video, videoName, videoUUID)
  349. } else {
  350. expect(notification).to.satisfy((n: UserNotification) => {
  351. return n === undefined || n.video === undefined || n.video.uuid !== videoUUID
  352. })
  353. }
  354. }
  355. function emailFinder (email: object) {
  356. const text = email[ 'text' ]
  357. return text.indexOf(videoUUID) !== -1 && email[ 'text' ].indexOf('video-auto-blacklist/list') !== -1
  358. }
  359. await checkNotification(base, notificationChecker, emailFinder, type)
  360. }
  361. async function checkNewBlacklistOnMyVideo (
  362. base: CheckerBaseParams,
  363. videoUUID: string,
  364. videoName: string,
  365. blacklistType: 'blacklist' | 'unblacklist'
  366. ) {
  367. const notificationType = blacklistType === 'blacklist'
  368. ? UserNotificationType.BLACKLIST_ON_MY_VIDEO
  369. : UserNotificationType.UNBLACKLIST_ON_MY_VIDEO
  370. function notificationChecker (notification: UserNotification) {
  371. expect(notification).to.not.be.undefined
  372. expect(notification.type).to.equal(notificationType)
  373. const video = blacklistType === 'blacklist' ? notification.videoBlacklist.video : notification.video
  374. checkVideo(video, videoName, videoUUID)
  375. }
  376. function emailFinder (email: object) {
  377. const text = email[ 'text' ]
  378. return text.indexOf(videoUUID) !== -1 && text.indexOf(' ' + blacklistType) !== -1
  379. }
  380. await checkNotification(base, notificationChecker, emailFinder, 'presence')
  381. }
  382. // ---------------------------------------------------------------------------
  383. export {
  384. CheckerBaseParams,
  385. CheckerType,
  386. checkNotification,
  387. markAsReadAllNotifications,
  388. checkMyVideoImportIsFinished,
  389. checkUserRegistered,
  390. checkVideoIsPublished,
  391. checkNewVideoFromSubscription,
  392. checkNewActorFollow,
  393. checkNewCommentOnMyVideo,
  394. checkNewBlacklistOnMyVideo,
  395. checkCommentMention,
  396. updateMyNotificationSettings,
  397. checkNewVideoAbuseForModerators,
  398. checkVideoAutoBlacklistForModerators,
  399. getUserNotifications,
  400. markAsReadNotifications,
  401. getLastNotification,
  402. checkNewInstanceFollower
  403. }