Просмотр исходного кода

[web] Promisify RegistrationTests and SessionTests (#30646)

* Promisify RegistrationTests.mjs

* Re-add `await this.user.request.get('/login')` and remove comment

* Promisify SessionTests.mjs

* Remove callback versions of RedisHelper

* Re-add `await this.user.request.get('/login')`

GitOrigin-RevId: 9d0e21336f348c6aff99cb262738163e611f5712
Antoine Clausse 8 месяцев назад
Родитель
Сommit
379788a8a6

+ 113 - 189
services/web/test/acceptance/src/RegistrationTests.mjs

@@ -1,5 +1,4 @@
 import { expect } from 'chai'
-import async from 'async'
 import metrics from './helpers/metrics.mjs'
 import User from './helpers/User.mjs'
 import redis from './helpers/redis.mjs'
@@ -8,43 +7,28 @@ import Features from '../../../app/src/infrastructure/Features.mjs'
 const UserPromises = User.promises
 
 // Expectations
-const expectProjectAccess = function (user, projectId, callback) {
+async function expectProjectAccess(user, projectId) {
   // should have access to project
-  user.openProject(projectId, err => {
-    expect(err).to.be.oneOf([null, undefined])
-    return callback()
-  })
+  await user.openProject(projectId)
 }
 
-const expectNoProjectAccess = function (user, projectId, callback) {
+async function expectNoProjectAccess(user, projectId) {
   // should not have access to project page
-  user.openProject(projectId, err => {
-    expect(err).to.be.instanceof(Error)
-    return callback()
-  })
+  let error = null
+  try {
+    await user.openProject(projectId)
+  } catch (err) {
+    error = err
+  }
+  expect(error).to.be.instanceof(Error)
 }
 
 // Actions
-const tryLoginThroughRegistrationForm = function (
-  user,
-  email,
-  password,
-  callback
-) {
-  user.getCsrfToken(err => {
-    if (err != null) {
-      return callback(err)
-    }
-    user.request.post(
-      {
-        url: '/register',
-        json: {
-          email,
-          password,
-        },
-      },
-      callback
-    )
+async function tryLoginThroughRegistrationForm(user, email, password) {
+  await user.getCsrfToken()
+  return await user.doRequest('POST', {
+    url: '/register',
+    json: { email, password },
   })
 }
 
@@ -204,95 +188,63 @@ describe('Registration', function () {
     })
 
     beforeEach(function () {
-      this.user = new User()
+      this.user = new UserPromises()
       this.email = `test+${Math.random()}@example.com`
       this.password = 'password11'
     })
 
-    afterEach(function (done) {
-      this.user.fullDeleteUser(this.email, done)
+    afterEach(async function () {
+      await this.user.fullDeleteUser(this.email)
     })
 
-    it('should register with the csrf token', function (done) {
-      this.user.request.get('/login', (err, res, body) => {
-        expect(err).to.not.exist
-        this.user.getCsrfToken(error => {
-          expect(error).to.not.exist
-          this.user.request.post(
-            {
-              url: '/register',
-              json: {
-                email: this.email,
-                password: this.password,
-              },
-              headers: {
-                'x-csrf-token': this.user.csrfToken,
-              },
-            },
-            (error, response, body) => {
-              expect(error).to.not.exist
-              expect(response.statusCode).to.equal(200)
-              return done()
-            }
-          )
-        })
+    it('should register with the csrf token', async function () {
+      await this.user.request.get('/login')
+      await this.user.getCsrfToken()
+      const { response } = await this.user.doRequest('POST', {
+        url: '/register',
+        json: {
+          email: this.email,
+          password: this.password,
+        },
+        headers: {
+          'x-csrf-token': this.user.csrfToken,
+        },
       })
+      expect(response.statusCode).to.equal(200)
     })
 
-    it('should fail with no csrf token', function (done) {
-      this.user.request.get('/login', (err, res, body) => {
-        expect(err).to.not.exist
-        this.user.getCsrfToken(error => {
-          expect(error).to.not.exist
-          this.user.request.post(
-            {
-              url: '/register',
-              json: {
-                email: this.email,
-                password: this.password,
-              },
-              headers: {
-                'x-csrf-token': '',
-              },
-            },
-            (error, response, body) => {
-              expect(error).to.not.exist
-              expect(response.statusCode).to.equal(403)
-              return done()
-            }
-          )
-        })
+    it('should fail with no csrf token', async function () {
+      await this.user.request.get('/login')
+      await this.user.getCsrfToken()
+      const { response } = await this.user.doRequest('POST', {
+        url: '/register',
+        json: {
+          email: this.email,
+          password: this.password,
+        },
+        headers: {
+          'x-csrf-token': '',
+        },
       })
+      expect(response.statusCode).to.equal(403)
     })
 
-    it('should fail with a stale csrf token', function (done) {
-      this.user.request.get('/login', (err, res, body) => {
-        expect(err).to.not.exist
-        this.user.getCsrfToken(error => {
-          expect(error).to.not.exist
-          const oldCsrfToken = this.user.csrfToken
-          this.user.logout(err => {
-            expect(err).to.not.exist
-            this.user.request.post(
-              {
-                url: '/register',
-                json: {
-                  email: this.email,
-                  password: this.password,
-                },
-                headers: {
-                  'x-csrf-token': oldCsrfToken,
-                },
-              },
-              (error, response, body) => {
-                expect(error).to.not.exist
-                expect(response.statusCode).to.equal(403)
-                return done()
-              }
-            )
-          })
-        })
+    it('should fail with a stale csrf token', async function () {
+      await this.user.request.get('/login')
+      await this.user.getCsrfToken()
+      const oldCsrfToken = this.user.csrfToken
+      await this.user.logout()
+      const { response } = await this.user.doRequest('POST', {
+        url: '/register',
+        json: {
+          email: this.email,
+          password: this.password,
+        },
+        headers: {
+          'x-csrf-token': oldCsrfToken,
+        },
       })
+      expect(response.statusCode).to.equal(403)
     })
   })
 
@@ -304,38 +256,32 @@ describe('Registration', function () {
     })
 
     beforeEach(function () {
-      this.user = new User()
+      this.user = new UserPromises()
     })
 
-    it('Set emails attribute', function (done) {
-      this.user.register((error, user) => {
-        expect(error).to.not.exist
-        user.email.should.equal(this.user.email)
-        user.emails.should.exist
-        user.emails.should.be.a('array')
-        user.emails.length.should.equal(1)
-        user.emails[0].email.should.equal(this.user.email)
-        return done()
-      })
+    it('Set emails attribute', async function () {
+      const user = await this.user.register()
+      expect(user.email).to.equal(this.user.email)
+      expect(user.emails).to.exist
+      expect(user.emails).to.be.a('array')
+      expect(user.emails.length).to.equal(1)
+      expect(user.emails[0].email).to.equal(this.user.email)
     })
   })
 
   describe('LoginViaRegistration', function () {
-    beforeEach(function (done) {
+    beforeEach(async function () {
       this.timeout(60000)
-      this.user1 = new User()
-      this.user2 = new User()
-      async.series(
-        [
-          cb => this.user1.login(cb),
-          cb => this.user1.logout(cb),
-          cb => redis.clearUserSessions(this.user1, cb),
-          cb => this.user2.login(cb),
-          cb => this.user2.logout(cb),
-          cb => redis.clearUserSessions(this.user2, cb),
-        ],
-        done
-      )
+      this.user1 = new UserPromises()
+      this.user2 = new UserPromises()
+
+      await this.user1.login()
+      await this.user1.logout()
+      await redis.clearUserSessions(this.user1)
+      await this.user2.login()
+      await this.user2.logout()
+      await redis.clearUserSessions(this.user2)
+
       this.project_id = null
     })
 
@@ -346,68 +292,46 @@ describe('Registration', function () {
         }
       })
 
-      it('should not allow sign in with secondary email', function (done) {
+      it('should not allow sign in with secondary email', async function () {
         const secondaryEmail = 'acceptance-test-secondary@example.com'
-        this.user1.addEmail(secondaryEmail, err => {
-          expect(err).to.not.exist
-          this.user1.loginWith(secondaryEmail, err => {
-            expect(err).to.match(/login failed: status=401/)
-            expect(err.info.body).to.deep.equal({
-              message: {
-                type: 'error',
-                key: 'invalid-password-retry-or-reset',
-              },
-            })
-            this.user1.isLoggedIn((err, isLoggedIn) => {
-              expect(err).to.not.exist
-              expect(isLoggedIn).to.equal(false)
-              return done()
-            })
-          })
+        await this.user1.addEmail(secondaryEmail)
+        let error = null
+        try {
+          await this.user1.loginWith(secondaryEmail)
+        } catch (err) {
+          error = err
+        }
+        expect(error).to.be.instanceOf(Error)
+        expect(error.message).to.match(/login failed: status=401/)
+        expect(error.info.body).to.deep.equal({
+          message: {
+            type: 'error',
+            key: 'invalid-password-retry-or-reset',
+          },
         })
+
+        const isLoggedIn = await this.user1.isLoggedIn()
+        expect(isLoggedIn).to.equal(false)
       })
 
-      it('should have user1 login and create a project, which user2 cannot access', function (done) {
-        let projectId
-        async.series(
-          [
-            // user1 logs in and creates a project which only they can access
-            cb => {
-              this.user1.login(err => {
-                expect(err).not.to.exist
-                cb()
-              })
-            },
-            cb => {
-              this.user1.createProject('Private Project', (err, id) => {
-                expect(err).not.to.exist
-                projectId = id
-                cb()
-              })
-            },
-            cb => expectProjectAccess(this.user1, projectId, cb),
-            cb => expectNoProjectAccess(this.user2, projectId, cb),
-            // should prevent user2 from login/register with user1 email address
-            cb => {
-              tryLoginThroughRegistrationForm(
-                this.user2,
-                this.user1.email,
-                'totally_not_the_right_password',
-                (err, response, body) => {
-                  expect(err).to.not.exist
-                  expect(body.redir != null).to.equal(false)
-                  expect(body.message != null).to.equal(true)
-                  expect(body.message).to.have.all.keys('type', 'text')
-                  expect(body.message.type).to.equal('error')
-                  cb()
-                }
-              )
-            },
-            // check user still can't access the project
-            cb => expectNoProjectAccess(this.user2, projectId, done),
-          ],
-          done
+      it('should have user1 login and create a project, which user2 cannot access', async function () {
+        // user1 logs in and creates a project which only they can access
+        await this.user1.login()
+        const projectId = await this.user1.createProject('Private Project')
+        await expectProjectAccess(this.user1, projectId)
+        await expectNoProjectAccess(this.user2, projectId)
+        // should prevent user2 from login/register with user1 email address
+        const { body } = await tryLoginThroughRegistrationForm(
+          this.user2,
+          this.user1.email,
+          'totally_not_the_right_password'
         )
+        expect(body.redir != null).to.equal(false)
+        expect(body.message != null).to.equal(true)
+        expect(body.message).to.have.all.keys('type', 'text')
+        expect(body.message.type).to.equal('error')
+        // check user still can't access the project
+        await expectNoProjectAccess(this.user2, projectId)
       })
     })
   })

+ 229 - 501
services/web/test/acceptance/src/SessionTests.mjs

@@ -1,558 +1,286 @@
 import { expect } from 'chai'
-import async from 'async'
+import { setTimeout } from 'node:timers/promises'
 import UserHelper from './helpers/User.mjs'
 import redis from './helpers/redis.mjs'
 import UserSessionsRedis from '../../../app/src/Features/User/UserSessionsRedis.mjs'
+
 const rclient = UserSessionsRedis.client()
 
+const UserPromises = UserHelper.promises
+
 describe('Sessions', function () {
-  beforeEach(function (done) {
+  beforeEach(async function () {
     this.timeout(20000)
-    this.user1 = new UserHelper()
-    this.site_admin = new UserHelper({ email: 'admin@example.com' })
-    async.series(
-      [cb => this.user1.login(cb), cb => this.user1.logout(cb)],
-      done
-    )
+    this.user1 = new UserPromises()
+    this.site_admin = new UserPromises({ email: 'admin@example.com' })
+    await this.user1.login()
+    await this.user1.logout()
   })
 
   describe('one session', function () {
-    it('should have one session in UserSessions set', function (done) {
-      async.series(
-        [
-          next => {
-            redis.clearUserSessions(this.user1, next)
-          },
-
-          // login, should add session to set
-          next => {
-            this.user1.login(err => next(err))
-          },
-
-          next => {
-            redis.getUserSessions(this.user1, (err, sessions) => {
-              expect(err).to.not.exist
-              expect(sessions.length).to.equal(1)
-              expect(sessions[0].slice(0, 5)).to.equal('sess:')
-              next()
-            })
-          },
-
-          // should be able to access project list page
-          next => {
-            this.user1.getProjectListPage((err, statusCode) => {
-              expect(err).to.equal(null)
-              expect(statusCode).to.equal(200)
-              next()
-            })
-          },
-
-          // logout, should remove session from set
-          next => {
-            this.user1.logout(err => next(err))
-          },
-
-          next => {
-            redis.getUserSessions(this.user1, (err, sessions) => {
-              expect(err).to.not.exist
-              expect(sessions.length).to.equal(0)
-              next()
-            })
-          },
-        ],
-        (err, result) => {
-          if (err) {
-            throw err
-          }
-          done()
-        }
-      )
+    it('should have one session in UserSessions set', async function () {
+      await redis.clearUserSessions(this.user1)
+
+      // login, should add session to set
+      await this.user1.login()
+
+      const sessions = await redis.getUserSessions(this.user1)
+      expect(sessions.length).to.equal(1)
+      expect(sessions[0].slice(0, 5)).to.equal('sess:')
+
+      // should be able to access project list page
+      const statusCode = await this.user1.getProjectListPage()
+      expect(statusCode).to.equal(200)
+
+      // logout, should remove session from set
+      await this.user1.logout()
+      const sessionsAfterLogout = await redis.getUserSessions(this.user1)
+      expect(sessionsAfterLogout.length).to.equal(0)
     })
 
-    it('should update audit log on logout', function (done) {
-      async.series(
-        [
-          next => {
-            redis.clearUserSessions(this.user1, next)
-          },
-
-          // login
-          next => {
-            this.user1.login(err => next(err))
-          },
-
-          // logout, should add logout audit log entry (happens in background)
-          next => {
-            this.user1.logout(err => next(err))
-          },
-
-          // poll for audit log entry since it's written in the background
-          next => {
-            let attempts = 0
-            const checkAuditLog = () => {
-              this.user1.getAuditLogWithoutNoise((error, auditLog) => {
-                if (error) return next(error)
-
-                const logoutEntries = auditLog.filter(
-                  entry => entry.operation === 'logout'
-                )
-
-                // If we found the logout entry, we're done
-                if (logoutEntries.length > 0) {
-                  expect(logoutEntries.length).to.be.greaterThan(0)
-                  const lastLogout = logoutEntries[logoutEntries.length - 1]
-                  expect(lastLogout.operation).to.equal('logout')
-                  expect(lastLogout.ipAddress).to.exist
-                  expect(lastLogout.initiatorId).to.exist
-                  expect(lastLogout.timestamp).to.exist
-                  return next()
-                }
-
-                // Otherwise retry up to 10 times
-                attempts++
-                if (attempts >= 10) {
-                  return next(
-                    new Error(
-                      'Logout audit log entry not found after 10 attempts'
-                    )
-                  )
-                }
-
-                setTimeout(checkAuditLog, 25)
-              })
-            }
-            checkAuditLog()
-          },
-        ],
-        (err, result) => {
-          if (err) {
-            throw err
+    it('should update audit log on logout', async function () {
+      await redis.clearUserSessions(this.user1)
+
+      // login
+      await this.user1.login()
+
+      // logout, should add logout audit log entry (happens in background)
+      await this.user1.logout()
+
+      // poll for audit log entry since it's written in the background
+      const findAuditLogEntry = async () => {
+        for (let attempts = 0; attempts < 10; attempts++) {
+          const auditLog = await this.user1.getAuditLogWithoutNoise()
+
+          const logoutEntries = auditLog.filter(
+            entry => entry.operation === 'logout'
+          )
+
+          // If we found the logout entry, we're done
+          if (logoutEntries.length > 0) {
+            return logoutEntries
           }
-          done()
+
+          await setTimeout(25)
         }
-      )
+
+        throw new Error('Logout audit log entry not found after 10 attempts')
+      }
+
+      const logoutEntries = await findAuditLogEntry()
+      expect(logoutEntries.length).to.be.greaterThan(0)
+      const lastLogout = logoutEntries[logoutEntries.length - 1]
+      expect(lastLogout.operation).to.equal('logout')
+      expect(lastLogout.ipAddress).to.exist
+      expect(lastLogout.initiatorId).to.exist
+      expect(lastLogout.timestamp).to.exist
     })
   })
 
   describe('two sessions', function () {
     beforeEach(function () {
       // set up second session for this user
-      this.user2 = new UserHelper()
+      this.user2 = new UserPromises()
       this.user2.email = this.user1.email
       this.user2.emails = this.user1.emails
       this.user2.password = this.user1.password
     })
 
-    it('should have two sessions in UserSessions set', function (done) {
-      async.series(
-        [
-          next => {
-            redis.clearUserSessions(this.user1, next)
-          },
-
-          // login, should add session to set
-          next => {
-            this.user1.login(err => next(err))
-          },
-
-          next => {
-            redis.getUserSessions(this.user1, (err, sessions) => {
-              expect(err).to.not.exist
-              expect(sessions.length).to.equal(1)
-              expect(sessions[0].slice(0, 5)).to.equal('sess:')
-              next()
-            })
-          },
-
-          // login again, should add the second session to set
-          next => {
-            this.user2.login(err => next(err))
-          },
-
-          next => {
-            redis.getUserSessions(this.user1, (err, sessions) => {
-              expect(err).to.not.exist
-              expect(sessions.length).to.equal(2)
-              expect(sessions[0].slice(0, 5)).to.equal('sess:')
-              expect(sessions[1].slice(0, 5)).to.equal('sess:')
-              next()
-            })
-          },
-
-          // both should be able to access project list page
-          next => {
-            this.user1.getProjectListPage((err, statusCode) => {
-              expect(err).to.equal(null)
-              expect(statusCode).to.equal(200)
-              next()
-            })
-          },
-
-          next => {
-            this.user2.getProjectListPage((err, statusCode) => {
-              expect(err).to.equal(null)
-              expect(statusCode).to.equal(200)
-              next()
-            })
-          },
-
-          // logout first session, should remove session from set
-          next => {
-            this.user1.logout(err => next(err))
-          },
-
-          next => {
-            redis.getUserSessions(this.user1, (err, sessions) => {
-              expect(err).to.not.exist
-              expect(sessions.length).to.equal(1)
-              next()
-            })
-          },
-
-          // first session should not have access to project list page
-          next => {
-            this.user1.getProjectListPage((err, statusCode) => {
-              expect(err).to.equal(null)
-              expect(statusCode).to.equal(302)
-              next()
-            })
-          },
-
-          // second session should still have access to settings
-          next => {
-            this.user2.getProjectListPage((err, statusCode) => {
-              expect(err).to.equal(null)
-              expect(statusCode).to.equal(200)
-              next()
-            })
-          },
-
-          // logout second session, should remove last session from set
-          next => {
-            this.user2.logout(err => next(err))
-          },
-
-          next => {
-            redis.getUserSessions(this.user1, (err, sessions) => {
-              expect(err).to.not.exist
-              expect(sessions.length).to.equal(0)
-              next()
-            })
-          },
-
-          // second session should not have access to project list page
-          next => {
-            this.user2.getProjectListPage((err, statusCode) => {
-              expect(err).to.equal(null)
-              expect(statusCode).to.equal(302)
-              next()
-            })
-          },
-        ],
-        (err, result) => {
-          if (err) {
-            throw err
-          }
-          done()
-        }
-      )
+    it('should have two sessions in UserSessions set', async function () {
+      await redis.clearUserSessions(this.user1)
+
+      // login, should add session to set
+      await this.user1.login()
+
+      const sessions1 = await redis.getUserSessions(this.user1)
+      expect(sessions1.length).to.equal(1)
+      expect(sessions1[0].slice(0, 5)).to.equal('sess:')
+
+      // login again, should add the second session to set
+      await this.user2.login()
+
+      const sessions2 = await redis.getUserSessions(this.user1)
+      expect(sessions2.length).to.equal(2)
+      expect(sessions2[0].slice(0, 5)).to.equal('sess:')
+      expect(sessions2[1].slice(0, 5)).to.equal('sess:')
+
+      // both should be able to access project list page
+      const statusCode1 = await this.user1.getProjectListPage()
+      expect(statusCode1).to.equal(200)
+      const statusCode2 = await this.user2.getProjectListPage()
+      expect(statusCode2).to.equal(200)
+
+      // logout first session, should remove session from set
+      await this.user1.logout()
+
+      const sessions3 = await redis.getUserSessions(this.user1)
+      expect(sessions3.length).to.equal(1)
+
+      // first session should not have access to project list page
+      const statusCode3 = await this.user1.getProjectListPage()
+      expect(statusCode3).to.equal(302)
+
+      // second session should still have access to settings
+      const statusCode4 = await this.user2.getProjectListPage()
+      expect(statusCode4).to.equal(200)
+
+      // logout second session, should remove last session from set
+      await this.user2.logout()
+
+      const sessions4 = await redis.getUserSessions(this.user1)
+      expect(sessions4.length).to.equal(0)
+
+      // second session should not have access to project list page
+      const statusCode5 = await this.user2.getProjectListPage()
+      expect(statusCode5).to.equal(302)
     })
   })
 
   describe('three sessions, password reset', function () {
     beforeEach(function () {
       // set up second session for this user
-      this.user2 = new UserHelper()
+      this.user2 = new UserPromises()
       this.user2.email = this.user1.email
       this.user2.emails = this.user1.emails
       this.user2.password = this.user1.password
-      this.user3 = new UserHelper()
+      this.user3 = new UserPromises()
       this.user3.email = this.user1.email
       this.user3.emails = this.user1.emails
       this.user3.password = this.user1.password
     })
 
-    it('should erase both sessions when password is reset', function (done) {
-      async.series(
-        [
-          next => {
-            redis.clearUserSessions(this.user1, next)
-          },
-
-          // login, should add session to set
-          next => {
-            this.user1.login(err => next(err))
-          },
-
-          next => {
-            redis.getUserSessions(this.user1, (err, sessions) => {
-              expect(err).to.not.exist
-              expect(sessions.length).to.equal(1)
-              expect(sessions[0].slice(0, 5)).to.equal('sess:')
-              next()
-            })
-          },
-
-          // login again, should add the second session to set
-          next => {
-            this.user2.login(err => next(err))
-          },
-
-          next => {
-            redis.getUserSessions(this.user1, (err, sessions) => {
-              expect(err).to.not.exist
-              expect(sessions.length).to.equal(2)
-              expect(sessions[0].slice(0, 5)).to.equal('sess:')
-              expect(sessions[1].slice(0, 5)).to.equal('sess:')
-              next()
-            })
-          },
-
-          // login third session, should add the second session to set
-          next => {
-            this.user3.login(err => next(err))
-          },
-
-          next => {
-            redis.getUserSessions(this.user1, (err, sessions) => {
-              expect(err).to.not.exist
-              expect(sessions.length).to.equal(3)
-              expect(sessions[0].slice(0, 5)).to.equal('sess:')
-              expect(sessions[1].slice(0, 5)).to.equal('sess:')
-              next()
-            })
-          },
-
-          // password reset from second session, should erase two of the three sessions
-          next => {
-            this.user2.changePassword(`password${Date.now()}`, err => next(err))
-          },
-
-          next => {
-            redis.getUserSessions(this.user2, (err, sessions) => {
-              expect(err).to.not.exist
-              expect(sessions.length).to.equal(1)
-              next()
-            })
-          },
-
-          // users one and three should not be able to access project list page
-          next => {
-            this.user1.getProjectListPage((err, statusCode) => {
-              expect(err).to.equal(null)
-              expect(statusCode).to.equal(302)
-              next()
-            })
-          },
-
-          next => {
-            this.user3.getProjectListPage((err, statusCode) => {
-              expect(err).to.equal(null)
-              expect(statusCode).to.equal(302)
-              next()
-            })
-          },
-
-          // user two should still be logged in, and able to access project list page
-          next => {
-            this.user2.getProjectListPage((err, statusCode) => {
-              expect(err).to.equal(null)
-              expect(statusCode).to.equal(200)
-              next()
-            })
-          },
-
-          // logout second session, should remove last session from set
-          next => {
-            this.user2.logout(err => next(err))
-          },
-
-          next => {
-            redis.getUserSessions(this.user1, (err, sessions) => {
-              expect(err).to.not.exist
-              expect(sessions.length).to.equal(0)
-              next()
-            })
-          },
-        ],
-        (err, result) => {
-          if (err) {
-            throw err
-          }
-          done()
-        }
-      )
+    it('should erase both sessions when password is reset', async function () {
+      await redis.clearUserSessions(this.user1)
+
+      // login, should add session to set
+      await this.user1.login()
+
+      const sessions1 = await redis.getUserSessions(this.user1)
+      expect(sessions1.length).to.equal(1)
+      expect(sessions1[0].slice(0, 5)).to.equal('sess:')
+
+      // login again, should add the second session to set
+      await this.user2.login()
+
+      const sessions2 = await redis.getUserSessions(this.user1)
+      expect(sessions2.length).to.equal(2)
+      expect(sessions2[0].slice(0, 5)).to.equal('sess:')
+      expect(sessions2[1].slice(0, 5)).to.equal('sess:')
+
+      // login third session, should add the second session to set
+      await this.user3.login()
+
+      const sessions3 = await redis.getUserSessions(this.user1)
+      expect(sessions3.length).to.equal(3)
+      expect(sessions3[0].slice(0, 5)).to.equal('sess:')
+      expect(sessions3[1].slice(0, 5)).to.equal('sess:')
+
+      // password reset from second session, should erase two of the three sessions
+      await this.user2.changePassword(`password${Date.now()}`)
+
+      const sessions4 = await redis.getUserSessions(this.user2)
+      expect(sessions4.length).to.equal(1)
+
+      // users one and three should not be able to access project list page
+      const statusCode1 = await this.user1.getProjectListPage()
+      expect(statusCode1).to.equal(302)
+      const statusCode3 = await this.user3.getProjectListPage()
+      expect(statusCode3).to.equal(302)
+
+      // user two should still be logged in, and able to access project list page
+      const statusCode2 = await this.user2.getProjectListPage()
+      expect(statusCode2).to.equal(200)
+
+      // logout second session, should remove last session from set
+      await this.user2.logout()
+
+      const sessions5 = await redis.getUserSessions(this.user1)
+      expect(sessions5.length).to.equal(0)
     })
   })
 
   describe('three sessions, sessions page', function () {
-    beforeEach(function (done) {
+    beforeEach(async function () {
       // set up second session for this user
-      this.user2 = new UserHelper()
+      this.user2 = new UserPromises()
       this.user2.email = this.user1.email
       this.user2.emails = this.user1.emails
       this.user2.password = this.user1.password
-      this.user3 = new UserHelper()
+      this.user3 = new UserPromises()
       this.user3.email = this.user1.email
       this.user3.emails = this.user1.emails
       this.user3.password = this.user1.password
-      async.series([this.user2.login.bind(this.user2)], done)
+      await this.user2.login()
     })
 
-    it('should allow the user to erase the other two sessions', function (done) {
-      async.series(
-        [
-          next => {
-            redis.clearUserSessions(this.user1, next)
-          },
-
-          // login, should add session to set
-          next => {
-            this.user1.login(err => next(err))
-          },
-
-          next => {
-            redis.getUserSessions(this.user1, (err, sessions) => {
-              expect(err).to.not.exist
-              expect(sessions.length).to.equal(1)
-              expect(sessions[0].slice(0, 5)).to.equal('sess:')
-              next()
-            })
-          },
-
-          // login again, should add the second session to set
-          next => {
-            this.user2.login(err => next(err))
-          },
-
-          next => {
-            redis.getUserSessions(this.user1, (err, sessions) => {
-              expect(err).to.not.exist
-              expect(sessions.length).to.equal(2)
-              expect(sessions[0].slice(0, 5)).to.equal('sess:')
-              expect(sessions[1].slice(0, 5)).to.equal('sess:')
-              next()
-            })
-          },
-
-          // login third session, should add the second session to set
-          next => {
-            this.user3.login(err => next(err))
-          },
-
-          next => {
-            redis.getUserSessions(this.user1, (err, sessions) => {
-              expect(err).to.not.exist
-              expect(sessions.length).to.equal(3)
-              expect(sessions[0].slice(0, 5)).to.equal('sess:')
-              expect(sessions[1].slice(0, 5)).to.equal('sess:')
-              next()
-            })
-          },
-
-          // check the sessions page
-          next => {
-            this.user2.request.get(
-              {
-                uri: '/user/sessions',
-              },
-              (err, response, body) => {
-                expect(err).to.be.oneOf([null, undefined])
-                expect(response.statusCode).to.equal(200)
-                next()
-              }
-            )
-          },
-
-          // clear sessions from second session, should erase two of the three sessions
-          next => {
-            this.user2.getCsrfToken(err => {
-              expect(err).to.be.oneOf([null, undefined])
-              this.user2.request.post(
-                {
-                  uri: '/user/sessions/clear',
-                },
-                err => next(err)
-              )
-            })
-          },
-
-          next => {
-            redis.getUserSessions(this.user2, (err, sessions) => {
-              expect(err).to.not.exist
-              expect(sessions.length).to.equal(1)
-              next()
-            })
-          },
-
-          // users one and three should not be able to access project list page
-          next => {
-            this.user1.getProjectListPage((err, statusCode) => {
-              expect(err).to.equal(null)
-              expect(statusCode).to.equal(302)
-              next()
-            })
-          },
-
-          next => {
-            this.user3.getProjectListPage((err, statusCode) => {
-              expect(err).to.equal(null)
-              expect(statusCode).to.equal(302)
-              next()
-            })
-          },
-
-          // user two should still be logged in, and able to access project list page
-          next => {
-            this.user2.getProjectListPage((err, statusCode) => {
-              expect(err).to.equal(null)
-              expect(statusCode).to.equal(200)
-              next()
-            })
-          },
-
-          // logout second session, should remove last session from set
-          next => {
-            this.user2.logout(err => next(err))
-          },
-
-          next => {
-            redis.getUserSessions(this.user1, (err, sessions) => {
-              expect(err).to.not.exist
-              expect(sessions.length).to.equal(0)
-              next()
-            })
-          },
-
-          // the user audit log should have been updated
-          next => {
-            this.user1.getAuditLogWithoutNoise((error, auditLog) => {
-              expect(error).not.to.exist
-              expect(auditLog).to.exist
-
-              // find the clear-sessions entry
-              const clearSessionsEntries = auditLog.filter(
-                entry => entry.operation === 'clear-sessions'
-              )
-              expect(clearSessionsEntries.length).to.equal(1)
-              expect(clearSessionsEntries[0].operation).to.equal(
-                'clear-sessions'
-              )
-              expect(clearSessionsEntries[0].ipAddress).to.exist
-              expect(clearSessionsEntries[0].initiatorId).to.exist
-              expect(clearSessionsEntries[0].timestamp).to.exist
-              expect(clearSessionsEntries[0].info.sessions.length).to.equal(2)
-              next()
-            })
-          },
-        ],
-        (err, result) => {
-          if (err) {
-            throw err
-          }
-          done()
-        }
+    it('should allow the user to erase the other two sessions', async function () {
+      await redis.clearUserSessions(this.user1)
+
+      // login, should add session to set
+      await this.user1.login()
+
+      const sessions1 = await redis.getUserSessions(this.user1)
+      expect(sessions1.length).to.equal(1)
+      expect(sessions1[0].slice(0, 5)).to.equal('sess:')
+
+      // login again, should add the second session to set
+      await this.user2.login()
+
+      const sessions2 = await redis.getUserSessions(this.user1)
+      expect(sessions2.length).to.equal(2)
+      expect(sessions2[0].slice(0, 5)).to.equal('sess:')
+      expect(sessions2[1].slice(0, 5)).to.equal('sess:')
+
+      // login third session, should add the second session to set
+      await this.user3.login()
+
+      const sessions3 = await redis.getUserSessions(this.user1)
+      expect(sessions3.length).to.equal(3)
+      expect(sessions3[0].slice(0, 5)).to.equal('sess:')
+      expect(sessions3[1].slice(0, 5)).to.equal('sess:')
+
+      // check the sessions page
+      const { response: sessionsPageResponse } = await this.user2.doRequest(
+        'GET',
+        '/user/sessions'
+      )
+      expect(sessionsPageResponse.statusCode).to.equal(200)
+
+      // clear sessions from second session, should erase two of the three sessions
+      await this.user2.getCsrfToken()
+      await this.user2.doRequest('POST', '/user/sessions/clear')
+      const sessions4 = await redis.getUserSessions(this.user2)
+      expect(sessions4.length).to.equal(1)
+
+      // users one and three should not be able to access project list page
+      const statusCode1 = await this.user1.getProjectListPage()
+      expect(statusCode1).to.equal(302)
+      const statusCode3 = await this.user3.getProjectListPage()
+      expect(statusCode3).to.equal(302)
+
+      // user two should still be logged in, and able to access project list page
+      const statusCode2 = await this.user2.getProjectListPage()
+      expect(statusCode2).to.equal(200)
+
+      // logout second session, should remove last session from set
+      await this.user2.logout()
+      const sessions5 = await redis.getUserSessions(this.user1)
+      expect(sessions5.length).to.equal(0)
+
+      // the user audit log should have been updated
+      const auditLog = await this.user1.getAuditLogWithoutNoise()
+      expect(auditLog).to.exist
+
+      // find the clear-sessions entry
+      const clearSessionsEntries = auditLog.filter(
+        entry => entry.operation === 'clear-sessions'
       )
+      expect(clearSessionsEntries.length).to.equal(1)
+      expect(clearSessionsEntries[0].operation).to.equal('clear-sessions')
+      expect(clearSessionsEntries[0].ipAddress).to.exist
+      expect(clearSessionsEntries[0].initiatorId).to.exist
+      expect(clearSessionsEntries[0].timestamp).to.exist
+      expect(clearSessionsEntries[0].info.sessions.length).to.equal(2)
     })
   })
 

+ 20 - 43
services/web/test/acceptance/src/helpers/redis.mjs

@@ -1,49 +1,26 @@
-// TODO: This file was created by bulk-decaffeinate.
-// Fix any style issues and re-enable lint.
-/*
- * decaffeinate suggestions:
- * DS102: Remove unnecessary code created because of implicit returns
- * DS207: Consider shorter variations of null checks
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
-import Async from 'async'
 import UserSessionsRedis from '../../../../app/src/Features/User/UserSessionsRedis.mjs'
 
-// rclient = redis.createClient(Settings.redis.web)
 const rclient = UserSessionsRedis.client()
 
-export default {
-  getUserSessions(user, callback) {
-    if (callback == null) {
-      callback = function () {}
-    }
-    return rclient.smembers(
-      UserSessionsRedis.sessionSetKey(user),
-      (err, result) => callback(err, result)
-    )
-  },
+async function getUserSessions(user) {
+  return await rclient.smembers(UserSessionsRedis.sessionSetKey(user))
+}
+
+async function clearUserSessions(user) {
+  const sessionSetKey = UserSessionsRedis.sessionSetKey(user)
+  const sessionKeys = await rclient.smembers(sessionSetKey)
+  if (sessionKeys.length === 0) {
+    return
+  }
+  for (const k of sessionKeys) {
+    await rclient.del(k)
+  }
+  await rclient.srem(sessionSetKey, sessionKeys)
+}
 
-  clearUserSessions(user, callback) {
-    if (callback == null) {
-      callback = function () {}
-    }
-    const sessionSetKey = UserSessionsRedis.sessionSetKey(user)
-    return rclient.smembers(sessionSetKey, (err, sessionKeys) => {
-      if (err) {
-        return callback(err)
-      }
-      if (sessionKeys.length === 0) {
-        return callback(null)
-      }
-      const actions = sessionKeys.map(k => cb => rclient.del(k, err => cb(err)))
-      return Async.series(actions, () =>
-        rclient.srem(sessionSetKey, sessionKeys, err => {
-          if (err) {
-            return callback(err)
-          }
-          return callback(null)
-        })
-      )
-    })
-  },
+const RedisHelper = {
+  getUserSessions,
+  clearUserSessions,
 }
+
+export default RedisHelper