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

Merge pull request #9956 from overleaf/em-node-fetch-web

Replace request-promise with fetch in web acceptance tests

GitOrigin-RevId: f50357cdea2d1353d7a82c5346b149018f91823f
Eric Mc Sween 3 лет назад
Родитель
Сommit
fe963ba692

+ 2 - 87
package-lock.json

@@ -25893,49 +25893,6 @@
         "throttleit": "^1.0.0"
       }
     },
-    "node_modules/request-promise-core": {
-      "version": "1.1.4",
-      "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz",
-      "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==",
-      "dependencies": {
-        "lodash": "^4.17.19"
-      },
-      "engines": {
-        "node": ">=0.10.0"
-      },
-      "peerDependencies": {
-        "request": "^2.34"
-      }
-    },
-    "node_modules/request-promise-native": {
-      "version": "1.0.9",
-      "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz",
-      "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==",
-      "deprecated": "request-promise-native has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142",
-      "dependencies": {
-        "request-promise-core": "1.1.4",
-        "stealthy-require": "^1.1.1",
-        "tough-cookie": "^2.3.3"
-      },
-      "engines": {
-        "node": ">=0.12.0"
-      },
-      "peerDependencies": {
-        "request": "^2.34"
-      }
-    },
-    "node_modules/request-promise-native/node_modules/tough-cookie": {
-      "version": "2.5.0",
-      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
-      "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
-      "dependencies": {
-        "psl": "^1.1.28",
-        "punycode": "^2.1.1"
-      },
-      "engines": {
-        "node": ">=0.8"
-      }
-    },
     "node_modules/request/node_modules/form-data": {
       "version": "2.3.3",
       "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
@@ -28212,14 +28169,6 @@
         "node": ">= 0.6"
       }
     },
-    "node_modules/stealthy-require": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
-      "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/stream-buffers": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-3.0.2.tgz",
@@ -35066,7 +35015,6 @@
         "recurly": "^4.0.0",
         "referer-parser": "0.0.3",
         "request": "^2.88.2",
-        "request-promise-native": "^1.0.8",
         "requestretry": "^6.0.0",
         "rimraf": "2.2.6",
         "rolling-rate-limiter": "^0.2.10",
@@ -35169,6 +35117,7 @@
         "terser-webpack-plugin": "^5.3.1",
         "timekeeper": "^2.2.0",
         "to-string-loader": "^1.2.0",
+        "tough-cookie": "^4.0.0",
         "typescript": "^4.5.5",
         "val-loader": "^4.0.0",
         "webpack": "^5.71.0",
@@ -42853,7 +42802,6 @@
         "recurly": "^4.0.0",
         "referer-parser": "0.0.3",
         "request": "^2.88.2",
-        "request-promise-native": "^1.0.8",
         "requestretry": "^6.0.0",
         "requirejs": "^2.3.6",
         "rimraf": "2.2.6",
@@ -42870,6 +42818,7 @@
         "terser-webpack-plugin": "^5.3.1",
         "timekeeper": "^2.2.0",
         "to-string-loader": "^1.2.0",
+        "tough-cookie": "^4.0.0",
         "tsscmp": "^1.0.6",
         "typescript": "^4.5.5",
         "underscore": "^1.13.1",
@@ -61749,35 +61698,6 @@
         "throttleit": "^1.0.0"
       }
     },
-    "request-promise-core": {
-      "version": "1.1.4",
-      "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz",
-      "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==",
-      "requires": {
-        "lodash": "^4.17.19"
-      }
-    },
-    "request-promise-native": {
-      "version": "1.0.9",
-      "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz",
-      "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==",
-      "requires": {
-        "request-promise-core": "1.1.4",
-        "stealthy-require": "^1.1.1",
-        "tough-cookie": "^2.3.3"
-      },
-      "dependencies": {
-        "tough-cookie": {
-          "version": "2.5.0",
-          "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
-          "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
-          "requires": {
-            "psl": "^1.1.28",
-            "punycode": "^2.1.1"
-          }
-        }
-      }
-    },
     "requestretry": {
       "version": "4.1.2",
       "resolved": "https://registry.npmjs.org/requestretry/-/requestretry-4.1.2.tgz",
@@ -63594,11 +63514,6 @@
       "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
       "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
     },
-    "stealthy-require": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
-      "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks="
-    },
     "stream-buffers": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-3.0.2.tgz",

+ 33 - 23
services/web/modules/launchpad/test/acceptance/src/LaunchpadTests.js

@@ -9,9 +9,10 @@ describe('Launchpad', function () {
   const user = new UserHelper()
 
   it('should show the launchpad page', async function () {
-    const response = await user.request.get('/launchpad')
-    expect(response.statusCode).to.equal(200)
-    const $ = cheerio.load(response.body)
+    const response = await user.fetch('/launchpad')
+    expect(response.status).to.equal(200)
+    const body = await response.text()
+    const $ = cheerio.load(body)
     expect($('h2').first().text()).to.equal('Create the first Admin account')
     expect($('form[name="email"]').first()).to.exist
     expect($('form[name="password"]').first()).to.exist
@@ -19,45 +20,54 @@ describe('Launchpad', function () {
 
   it('should allow for creation of the first admin user', async function () {
     // Load the launchpad page
-    const initialPageResponse = await user.request.get('/launchpad')
-    expect(initialPageResponse.statusCode).to.equal(200)
-    const $ = cheerio.load(initialPageResponse.body)
+    const initialPageResponse = await user.fetch('/launchpad')
+    expect(initialPageResponse.status).to.equal(200)
+    const initialPageBody = await initialPageResponse.text()
+    const $ = cheerio.load(initialPageBody)
     expect($('h2').first().text()).to.equal('Create the first Admin account')
     expect($('form[name="email"]').first()).to.exist
     expect($('form[name="password"]').first()).to.exist
 
     // Submit the form
     let csrfToken = await user.getCsrfToken()
-    const postResponse = await user.request.post({
-      url: '/launchpad/register_admin',
-      json: {
+    const postResponse = await user.fetch('/launchpad/register_admin', {
+      method: 'POST',
+      headers: {
+        'Content-Type': 'application/json',
+        Accept: 'application/json',
+      },
+      body: JSON.stringify({
         _csrf: csrfToken,
         email: adminEmail,
         password: adminPassword,
-      },
+      }),
     })
-    expect(postResponse.statusCode).to.equal(200)
-    expect(postResponse.body).to.deep.equal({ redir: '/launchpad' })
+    expect(postResponse.status).to.equal(200)
+    const postBody = await postResponse.json()
+    expect(postBody).to.deep.equal({ redir: '/launchpad' })
 
     // Try to load the page again
-    const secondPageResponse = await user.request.get('/launchpad', {
-      simple: false,
-    })
-    expect(secondPageResponse.statusCode).to.equal(302)
-    expect(secondPageResponse.headers.location).to.equal('/login')
+    const secondPageResponse = await user.fetch('/launchpad')
+    expect(secondPageResponse.status).to.equal(302)
+    expect(secondPageResponse.headers.get('location')).to.equal(
+      UserHelper.url('/login').toString()
+    )
 
     // Forbid submitting the form again
     csrfToken = await user.getCsrfToken()
-    const badPostResponse = await user.request.post({
-      url: '/launchpad/register_admin',
-      json: {
+    const badPostResponse = await user.fetch('/launchpad/register_admin', {
+      method: 'POST',
+      headers: {
+        'Content-Type': 'application/json',
+        Accept: 'application/json',
+      },
+      body: JSON.stringify({
         _csrf: csrfToken,
         email: adminEmail + '1',
         password: adminPassword + '1',
-      },
-      simple: false,
+      }),
     })
-    expect(badPostResponse.statusCode).to.equal(403)
+    expect(badPostResponse.status).to.equal(403)
 
     // Log in as this new admin user
     const adminUser = await UserHelper.loginUser({

+ 1 - 1
services/web/package.json

@@ -206,7 +206,6 @@
     "recurly": "^4.0.0",
     "referer-parser": "0.0.3",
     "request": "^2.88.2",
-    "request-promise-native": "^1.0.8",
     "requestretry": "^6.0.0",
     "rimraf": "2.2.6",
     "rolling-rate-limiter": "^0.2.10",
@@ -309,6 +308,7 @@
     "terser-webpack-plugin": "^5.3.1",
     "timekeeper": "^2.2.0",
     "to-string-loader": "^1.2.0",
+    "tough-cookie": "^4.0.0",
     "typescript": "^4.5.5",
     "val-loader": "^4.0.0",
     "webpack": "^5.71.0",

+ 12 - 22
services/web/test/acceptance/src/BetaProgramTests.js

@@ -13,31 +13,21 @@ describe('BetaProgram', function () {
     })
   })
   it('should opt in', async function () {
-    const response = await userHelper.request.post('/beta/opt-in', {
-      simple: false,
-    })
-    expect(response.statusCode).to.equal(302)
-    response.statusCode.should.equal(302)
-    expect(response.headers.location).to.equal('/beta/participate')
-    const user = (
-      await UserHelper.getUser({
-        email,
-      })
-    ).user
+    const response = await userHelper.fetch('/beta/opt-in', { method: 'POST' })
+    expect(response.status).to.equal(302)
+    expect(response.headers.get('location')).to.equal(
+      UserHelper.url('/beta/participate').toString()
+    )
+    const user = (await UserHelper.getUser({ email })).user
     expect(user.betaProgram).to.equal(true)
   })
   it('should opt out', async function () {
-    const response = await userHelper.request.post('/beta/opt-out', {
-      simple: false,
-    })
-    expect(response.statusCode).to.equal(302)
-    response.statusCode.should.equal(302)
-    expect(response.headers.location).to.equal('/beta/participate')
-    const user = (
-      await UserHelper.getUser({
-        email,
-      })
-    ).user
+    const response = await userHelper.fetch('/beta/opt-out', { method: 'POST' })
+    expect(response.status).to.equal(302)
+    expect(response.headers.get('location')).to.equal(
+      UserHelper.url('/beta/participate').toString()
+    )
+    const user = (await UserHelper.getUser({ email })).user
     expect(user.betaProgram).to.equal(false)
   })
 })

+ 152 - 154
services/web/test/acceptance/src/PasswordResetTests.js

@@ -13,10 +13,9 @@ describe('PasswordReset', function () {
 
     // generate the token
     await userHelper.getCsrfToken()
-    response = await userHelper.request.post('/user/password/reset', {
-      form: {
-        email,
-      },
+    response = await userHelper.fetch('/user/password/reset', {
+      method: 'POST',
+      body: new URLSearchParams({ email }),
     })
 
     token = (
@@ -32,20 +31,20 @@ describe('PasswordReset', function () {
           email,
           password: userHelper.getDefaultPassword(),
         })
-        response = await userHelper.request.get(
-          `/user/password/set?passwordResetToken=${token}&email=${email}`,
-          { simple: false }
+        response = await userHelper.fetch(
+          `/user/password/set?passwordResetToken=${token}&email=${email}`
         )
-        expect(response.statusCode).to.equal(302)
-        expect(response.headers.location).to.equal(
-          `/user/password/set${emailQuery}`
+        expect(response.status).to.equal(302)
+        expect(response.headers.get('location')).to.equal(
+          UserHelper.url(`/user/password/set${emailQuery}`).toString()
         )
         // send reset request
-        response = await userHelper.request.post('/user/password/set', {
-          form: {
+        response = await userHelper.fetch('/user/password/set', {
+          method: 'POST',
+          body: new URLSearchParams({
             passwordResetToken: token,
             password: 'a-password',
-          },
+          }),
         })
         userHelper = await UserHelper.getUser({ email })
         user = userHelper.user
@@ -75,20 +74,20 @@ describe('PasswordReset', function () {
           email: otherUserEmail,
           password: userHelper.getDefaultPassword(),
         })
-        response = await userHelper.request.get(
-          `/user/password/set?passwordResetToken=${token}&email=${email}`,
-          { simple: false }
+        response = await userHelper.fetch(
+          `/user/password/set?passwordResetToken=${token}&email=${email}`
         )
-        expect(response.statusCode).to.equal(302)
-        expect(response.headers.location).to.equal(
-          `/user/password/set${emailQuery}`
+        expect(response.status).to.equal(302)
+        expect(response.headers.get('location')).to.equal(
+          UserHelper.url(`/user/password/set${emailQuery}`).toString()
         )
         // send reset request
-        response = await userHelper.request.post('/user/password/set', {
-          form: {
+        response = await userHelper.fetch('/user/password/set', {
+          method: 'POST',
+          body: new URLSearchParams({
             passwordResetToken: token,
             password: 'a-password',
-          },
+          }),
         })
         userHelper = await UserHelper.getUser({ email })
         user = userHelper.user
@@ -110,20 +109,20 @@ describe('PasswordReset', function () {
     })
     describe('when not logged in', function () {
       beforeEach(async function () {
-        response = await userHelper.request.get(
-          `/user/password/set?passwordResetToken=${token}&email=${email}`,
-          { simple: false }
+        response = await userHelper.fetch(
+          `/user/password/set?passwordResetToken=${token}&email=${email}`
         )
-        expect(response.statusCode).to.equal(302)
-        expect(response.headers.location).to.equal(
-          `/user/password/set${emailQuery}`
+        expect(response.status).to.equal(302)
+        expect(response.headers.get('location')).to.equal(
+          UserHelper.url(`/user/password/set${emailQuery}`).toString()
         )
         // send reset request
-        response = await userHelper.request.post('/user/password/set', {
-          form: {
+        response = await userHelper.fetch('/user/password/set', {
+          method: 'POST',
+          body: new URLSearchParams({
             passwordResetToken: token,
             password: 'a-password',
-          },
+          }),
         })
         userHelper = await UserHelper.getUser({ email })
         user = userHelper.user
@@ -144,24 +143,23 @@ describe('PasswordReset', function () {
     })
     describe('password checks', function () {
       beforeEach(async function () {
-        response = await userHelper.request.get(
-          `/user/password/set?passwordResetToken=${token}&email=${email}`,
-          { simple: false }
+        response = await userHelper.fetch(
+          `/user/password/set?passwordResetToken=${token}&email=${email}`
         )
-        expect(response.statusCode).to.equal(302)
-        expect(response.headers.location).to.equal(
-          `/user/password/set${emailQuery}`
+        expect(response.status).to.equal(302)
+        expect(response.headers.get('location')).to.equal(
+          UserHelper.url(`/user/password/set${emailQuery}`).toString()
         )
       })
       it('without a password should return 400 and not log the change', async function () {
         // send reset request
-        response = await userHelper.request.post('/user/password/set', {
-          form: {
+        response = await userHelper.fetch('/user/password/set', {
+          method: 'POST',
+          body: new URLSearchParams({
             passwordResetToken: token,
-          },
-          simple: false,
+          }),
         })
-        expect(response.statusCode).to.equal(400)
+        expect(response.status).to.equal(400)
         userHelper = await UserHelper.getUser({ email })
 
         const auditLog = userHelper.getAuditLogWithoutNoise()
@@ -170,14 +168,14 @@ describe('PasswordReset', function () {
 
       it('without a valid password should return 400 and not log the change', async function () {
         // send reset request
-        response = await userHelper.request.post('/user/password/set', {
-          form: {
+        response = await userHelper.fetch('/user/password/set', {
+          method: 'POST',
+          body: new URLSearchParams({
             passwordResetToken: token,
             password: 'short',
-          },
-          simple: false,
+          }),
         })
-        expect(response.statusCode).to.equal(400)
+        expect(response.status).to.equal(400)
         userHelper = await UserHelper.getUser({ email })
 
         const auditLog = userHelper.getAuditLogWithoutNoise()
@@ -187,41 +185,45 @@ describe('PasswordReset', function () {
       it('should flag email in password', async function () {
         const localPart = email.split('@').shift()
         // send bad password
-        response = await userHelper.request.post('/user/password/set', {
-          form: {
+        response = await userHelper.fetch('/user/password/set', {
+          method: 'POST',
+          headers: {
+            'Content-Type': 'application/json',
+            Accept: 'application/json',
+          },
+          body: JSON.stringify({
             passwordResetToken: token,
             password: localPart,
             email,
-          },
-          json: true,
-          simple: false,
+          }),
         })
-        expect(response.statusCode).to.equal(400)
-        expect(response.body).to.deep.equal({
+        expect(response.status).to.equal(400)
+        const body = await response.json()
+        expect(body).to.deep.equal({
           message: { text: 'password contains part of email address' },
         })
       })
 
       it('should be able to retry after providing an invalid password', async function () {
         // send bad password
-        response = await userHelper.request.post('/user/password/set', {
-          form: {
+        response = await userHelper.fetch('/user/password/set', {
+          method: 'POST',
+          body: new URLSearchParams({
             passwordResetToken: token,
             password: 'short',
-          },
-          simple: false,
+          }),
         })
-        expect(response.statusCode).to.equal(400)
+        expect(response.status).to.equal(400)
 
         // send good password
-        response = await userHelper.request.post('/user/password/set', {
-          form: {
+        response = await userHelper.fetch('/user/password/set', {
+          method: 'POST',
+          body: new URLSearchParams({
             passwordResetToken: token,
             password: 'SomeThingVeryStrong!11',
-          },
-          simple: false,
+          }),
         })
-        expect(response.statusCode).to.equal(200)
+        expect(response.status).to.equal(200)
         userHelper = await UserHelper.getUser({ email })
 
         const auditLog = userHelper.getAuditLogWithoutNoise()
@@ -230,17 +232,16 @@ describe('PasswordReset', function () {
 
       it('when the password is the same as current, should return 400 and log the change', async function () {
         // send reset request
-        response = await userHelper.request.post('/user/password/set', {
-          form: {
+        response = await userHelper.fetch('/user/password/set', {
+          method: 'POST',
+          body: new URLSearchParams({
             passwordResetToken: token,
             password: userHelper.getDefaultPassword(),
-          },
-          simple: false,
+          }),
         })
-        expect(response.statusCode).to.equal(400)
-        expect(JSON.parse(response.body).message.key).to.equal(
-          'password-must-be-different'
-        )
+        expect(response.status).to.equal(400)
+        const body = await response.json()
+        expect(body.message.key).to.equal('password-must-be-different')
         userHelper = await UserHelper.getUser({ email })
 
         const auditLog = userHelper.getAuditLogWithoutNoise()
@@ -251,80 +252,81 @@ describe('PasswordReset', function () {
 
   describe('multiple attempts to set the password, reaching attempt limit', async function () {
     beforeEach(async function () {
-      response = await userHelper.request.get(
-        `/user/password/set?passwordResetToken=${token}&email=${email}`,
-        { simple: false }
+      response = await userHelper.fetch(
+        `/user/password/set?passwordResetToken=${token}&email=${email}`
       )
-      expect(response.statusCode).to.equal(302)
-      expect(response.headers.location).to.equal(
-        `/user/password/set${emailQuery}`
+      expect(response.status).to.equal(302)
+      expect(response.headers.get('location')).to.equal(
+        UserHelper.url(`/user/password/set${emailQuery}`).toString()
       )
     })
 
     it('should allow multiple attempts with same-password error, then deny further attempts', async function () {
       const sendSamePasswordRequest = async function () {
-        return userHelper.request.post('/user/password/set', {
-          form: {
+        return userHelper.fetch('/user/password/set', {
+          method: 'POST',
+          headers: {
+            Accept: 'application/json',
+          },
+          body: new URLSearchParams({
             passwordResetToken: token,
             password: userHelper.getDefaultPassword(),
-          },
-          simple: false,
+          }),
         })
       }
       // Three attempts at setting the password, all rejected for being the same as
       // the current password
       const response1 = await sendSamePasswordRequest()
-      expect(response1.statusCode).to.equal(400)
-      expect(JSON.parse(response1.body).message.key).to.equal(
-        'password-must-be-different'
-      )
+      expect(response1.status).to.equal(400)
+      const body1 = await response1.json()
+      expect(body1.message.key).to.equal('password-must-be-different')
       const response2 = await sendSamePasswordRequest()
-      expect(response2.statusCode).to.equal(400)
-      expect(JSON.parse(response2.body).message.key).to.equal(
-        'password-must-be-different'
-      )
+      expect(response2.status).to.equal(400)
+      const body2 = await response2.json()
+      expect(body2.message.key).to.equal('password-must-be-different')
       const response3 = await sendSamePasswordRequest()
-      expect(response3.statusCode).to.equal(400)
-      expect(JSON.parse(response3.body).message.key).to.equal(
-        'password-must-be-different'
-      )
+      expect(response3.status).to.equal(400)
+      const body3 = await response3.json()
+      expect(body3.message.key).to.equal('password-must-be-different')
       // Fourth attempt is rejected because the token has been used too many times
       const response4 = await sendSamePasswordRequest()
-      expect(response4.statusCode).to.equal(404)
-      expect(JSON.parse(response4.body).message.key).to.equal('token-expired')
+      expect(response4.status).to.equal(404)
+      const body4 = await response4.json()
+      expect(body4.message.key).to.equal('token-expired')
     })
 
     it('should allow multiple attempts with same-password error, then set the password', async function () {
       const sendSamePasswordRequest = async function () {
-        return userHelper.request.post('/user/password/set', {
-          form: {
+        return userHelper.fetch('/user/password/set', {
+          method: 'POST',
+          headers: {
+            Accept: 'application/json',
+          },
+          body: new URLSearchParams({
             passwordResetToken: token,
             password: userHelper.getDefaultPassword(),
-          },
-          simple: false,
+          }),
         })
       }
       // Two attempts at setting the password, all rejected for being the same as
       // the current password
       const response1 = await sendSamePasswordRequest()
-      expect(response1.statusCode).to.equal(400)
-      expect(JSON.parse(response1.body).message.key).to.equal(
-        'password-must-be-different'
-      )
+      expect(response1.status).to.equal(400)
+      const body1 = await response1.json()
+      expect(body1.message.key).to.equal('password-must-be-different')
       const response2 = await sendSamePasswordRequest()
-      expect(response2.statusCode).to.equal(400)
-      expect(JSON.parse(response2.body).message.key).to.equal(
-        'password-must-be-different'
-      )
+      expect(response2.status).to.equal(400)
+      const body2 = await response2.json()
+      expect(body2.message.key).to.equal('password-must-be-different')
       // Third attempt is succeeds
-      const response3 = await userHelper.request.post('/user/password/set', {
-        form: {
+      const response3 = await userHelper.fetch('/user/password/set', {
+        method: 'POST',
+        body: new URLSearchParams({
           passwordResetToken: token,
           password: 'some-new-password',
-        },
-        simple: false,
+        }),
       })
-      expect(response3.statusCode).to.equal(200)
+      expect(response3.status).to.equal(200)
       // Check the user and audit log
       userHelper = await UserHelper.getUser({ email })
       user = userHelper.user
@@ -342,83 +344,79 @@ describe('PasswordReset', function () {
 
   describe('without a valid token', function () {
     it('no token should redirect to page to re-request reset token', async function () {
-      response = await userHelper.request.get(
-        `/user/password/set?&email=${email}`,
-        { simple: false }
+      response = await userHelper.fetch(`/user/password/set?&email=${email}`)
+      expect(response.status).to.equal(302)
+      expect(response.headers.get('location')).to.equal(
+        UserHelper.url('/user/password/reset').toString()
       )
-      expect(response.statusCode).to.equal(302)
-      expect(response.headers.location).to.equal('/user/password/reset')
     })
     it('should show error for invalid tokens and return 404 if used', async function () {
       const invalidToken = 'not-real-token'
-      response = await userHelper.request.get(
-        `/user/password/set?&passwordResetToken=${invalidToken}&email=${email}`,
-        { simple: false }
+      response = await userHelper.fetch(
+        `/user/password/set?&passwordResetToken=${invalidToken}&email=${email}`
       )
-      expect(response.statusCode).to.equal(302)
-      expect(response.headers.location).to.equal(
-        `/user/password/reset?error=token_expired`
+      expect(response.status).to.equal(302)
+      expect(response.headers.get('location')).to.equal(
+        UserHelper.url('/user/password/reset?error=token_expired').toString()
       )
       // send reset request
-      response = await userHelper.request.post('/user/password/set', {
-        form: {
+      response = await userHelper.fetch('/user/password/set', {
+        method: 'POST',
+        body: new URLSearchParams({
           passwordResetToken: invalidToken,
           password: 'a-password',
-        },
-        simple: false,
+        }),
       })
-      expect(response.statusCode).to.equal(404)
+      expect(response.status).to.equal(404)
     })
   })
   describe('password reset', function () {
     it('should return 200 if email field is valid', async function () {
-      response = await userHelper.request.post(`/user/password/reset`, {
-        form: {
-          email,
-        },
+      response = await userHelper.fetch(`/user/password/reset`, {
+        method: 'POST',
+        body: new URLSearchParams({ email }),
       })
-      expect(response.statusCode).to.equal(200)
+      expect(response.status).to.equal(200)
     })
 
     it('should return 400 if email field is missing', async function () {
-      response = await userHelper.request.post(`/user/password/reset`, {
-        form: {
-          mail: email,
-        },
-        simple: false,
+      response = await userHelper.fetch(`/user/password/reset`, {
+        method: 'POST',
+        body: new URLSearchParams({ mail: email }),
       })
-      expect(response.statusCode).to.equal(400)
+      expect(response.status).to.equal(400)
     })
   })
   describe('password set', function () {
     it('should return 200 if password and passwordResetToken fields are valid', async function () {
-      response = await userHelper.request.post(`/user/password/set`, {
-        form: {
+      response = await userHelper.fetch(`/user/password/set`, {
+        method: 'POST',
+        body: new URLSearchParams({
           password: 'new-password',
           passwordResetToken: token,
-        },
+        }),
       })
-      expect(response.statusCode).to.equal(200)
+      expect(response.status).to.equal(200)
     })
 
     it('should return 400 if password field is missing', async function () {
-      response = await userHelper.request.post(`/user/password/set`, {
-        form: {
+      response = await userHelper.fetch(`/user/password/set`, {
+        method: 'POST',
+        body: new URLSearchParams({
           passwordResetToken: token,
-        },
-        simple: false,
+        }),
       })
-      expect(response.statusCode).to.equal(400)
+      expect(response.status).to.equal(400)
     })
 
     it('should return 400 if passwordResetToken field is missing', async function () {
-      response = await userHelper.request.post(`/user/password/set`, {
-        form: {
+      response = await userHelper.fetch(`/user/password/set`, {
+        method: 'POST',
+        body: new URLSearchParams({
           password: 'new-password',
-        },
-        simple: false,
+        }),
       })
-      expect(response.statusCode).to.equal(400)
+      expect(response.status).to.equal(400)
     })
   })
 })

+ 37 - 29
services/web/test/acceptance/src/PasswordUpdateTests.js

@@ -23,19 +23,19 @@ describe('PasswordUpdate', function () {
   })
   describe('success', function () {
     beforeEach(async function () {
-      response = await userHelper.request.post('/user/password/update', {
-        form: {
+      response = await userHelper.fetch('/user/password/update', {
+        method: 'POST',
+        body: new URLSearchParams({
           currentPassword: password,
           newPassword1: 'new-password',
           newPassword2: 'new-password',
-        },
-        simple: false,
+        }),
       })
       userHelper = await UserHelper.getUser({ email })
       user = userHelper.user
     })
     it('should return 200', async function () {
-      expect(response.statusCode).to.equal(200)
+      expect(response.status).to.equal(200)
     })
     it('should update the audit log', function () {
       const auditLog = userHelper.getAuditLogWithoutNoise()
@@ -50,17 +50,17 @@ describe('PasswordUpdate', function () {
   describe('errors', function () {
     describe('missing current password', function () {
       beforeEach(async function () {
-        response = await userHelper.request.post('/user/password/update', {
-          form: {
+        response = await userHelper.fetch('/user/password/update', {
+          method: 'POST',
+          body: new URLSearchParams({
             newPassword1: 'new-password',
             newPassword2: 'new-password',
-          },
-          simple: false,
+          }),
         })
         userHelper = await UserHelper.getUser({ email })
       })
       it('should return 500', async function () {
-        expect(response.statusCode).to.equal(500)
+        expect(response.status).to.equal(500)
       })
       it('should not update audit log', async function () {
         const auditLog = userHelper.getAuditLogWithoutNoise()
@@ -69,18 +69,18 @@ describe('PasswordUpdate', function () {
     })
     describe('wrong current password', function () {
       beforeEach(async function () {
-        response = await userHelper.request.post('/user/password/update', {
-          form: {
+        response = await userHelper.fetch('/user/password/update', {
+          method: 'POST',
+          body: new URLSearchParams({
             currentPassword: 'wrong-password',
             newPassword1: 'new-password',
             newPassword2: 'new-password',
-          },
-          simple: false,
+          }),
         })
         userHelper = await UserHelper.getUser({ email })
       })
       it('should return 400', async function () {
-        expect(response.statusCode).to.equal(400)
+        expect(response.status).to.equal(400)
       })
       it('should not update audit log', async function () {
         const auditLog = userHelper.getAuditLogWithoutNoise()
@@ -89,22 +89,26 @@ describe('PasswordUpdate', function () {
     })
     describe('newPassword1 does not match newPassword2', function () {
       beforeEach(async function () {
-        response = await userHelper.request.post('/user/password/update', {
-          form: {
+        response = await userHelper.fetch('/user/password/update', {
+          method: 'POST',
+          headers: {
+            'Content-Type': 'application/json',
+            Accept: 'application/json',
+          },
+          body: JSON.stringify({
             currentPassword: password,
             newPassword1: 'new-password',
             newPassword2: 'oops-password',
-          },
-          json: true,
-          simple: false,
+          }),
         })
         userHelper = await UserHelper.getUser({ email })
       })
       it('should return 400', async function () {
-        expect(response.statusCode).to.equal(400)
+        expect(response.status).to.equal(400)
       })
       it('should return error message', async function () {
-        expect(response.body.message).to.equal('Passwords do not match')
+        const body = await response.json()
+        expect(body.message).to.equal('Passwords do not match')
       })
       it('should not update audit log', async function () {
         const auditLog = userHelper.getAuditLogWithoutNoise()
@@ -113,22 +117,26 @@ describe('PasswordUpdate', function () {
     })
     describe('new password is not valid', function () {
       beforeEach(async function () {
-        response = await userHelper.request.post('/user/password/update', {
-          form: {
+        response = await userHelper.fetch('/user/password/update', {
+          method: 'POST',
+          headers: {
+            'Content-Type': 'application/json',
+            Accept: 'application/json',
+          },
+          body: JSON.stringify({
             currentPassword: password,
             newPassword1: 'short',
             newPassword2: 'short',
-          },
-          json: true,
-          simple: false,
+          }),
         })
         userHelper = await UserHelper.getUser({ email })
       })
       it('should return 400', async function () {
-        expect(response.statusCode).to.equal(400)
+        expect(response.status).to.equal(400)
       })
       it('should return error message', async function () {
-        expect(response.body.message).to.equal('password is too short')
+        const body = await response.json()
+        expect(body.message).to.equal('password is too short')
       })
       it('should not update audit log', async function () {
         const auditLog = userHelper.getAuditLogWithoutNoise()

+ 44 - 52
services/web/test/acceptance/src/PrimaryEmailCheckTests.js

@@ -43,19 +43,18 @@ describe('PrimaryEmailCheck', function () {
   describe('redirections', function () {
     describe('when the user has signed up recently', function () {
       it("shouldn't be redirected from project list to the primary email check page", async function () {
-        const response = await userHelper.request.get(
-          '/project' + SPLIT_TEST_QUERY
-        )
-        expect(response.statusCode).to.equal(200)
+        const response = await userHelper.fetch('/project' + SPLIT_TEST_QUERY)
+        expect(response.status).to.equal(200)
       })
 
       it('should be redirected from the primary email check page to the project list', async function () {
-        const response = await userHelper.request.get(
-          '/user/emails/primary-email-check' + SPLIT_TEST_QUERY,
-          { simple: false }
+        const response = await userHelper.fetch(
+          '/user/emails/primary-email-check' + SPLIT_TEST_QUERY
+        )
+        expect(response.status).to.equal(302)
+        expect(response.headers.get('location')).to.equal(
+          UserHelper.url('/project').toString()
         )
-        expect(response.statusCode).to.equal(302)
-        expect(response.headers.location).to.equal('/project')
       })
     })
 
@@ -68,19 +67,18 @@ describe('PrimaryEmailCheck', function () {
       })
 
       it("shouldn't be redirected from project list to the primary email check page", async function () {
-        const response = await userHelper.request.get(
-          '/project' + SPLIT_TEST_QUERY
-        )
-        expect(response.statusCode).to.equal(200)
+        const response = await userHelper.fetch('/project' + SPLIT_TEST_QUERY)
+        expect(response.status).to.equal(200)
       })
 
       it('should be redirected from the primary email check page to the project list', async function () {
-        const response = await userHelper.request.get(
-          '/user/emails/primary-email-check' + SPLIT_TEST_QUERY,
-          { simple: false }
+        const response = await userHelper.fetch(
+          '/user/emails/primary-email-check' + SPLIT_TEST_QUERY
+        )
+        expect(response.status).to.equal(302)
+        expect(response.headers.get('location')).to.equal(
+          UserHelper.url('/project').toString()
         )
-        expect(response.statusCode).to.equal(302)
-        expect(response.headers.location).to.equal('/project')
       })
     })
 
@@ -100,19 +98,18 @@ describe('PrimaryEmailCheck', function () {
       })
 
       it("shouldn't be redirected from project list to the primary email check page", async function () {
-        const response = await userHelper.request.get(
-          '/project' + SPLIT_TEST_QUERY
-        )
-        expect(response.statusCode).to.equal(200)
+        const response = await userHelper.fetch('/project' + SPLIT_TEST_QUERY)
+        expect(response.status).to.equal(200)
       })
 
       it('should be redirected from the primary email check page to the project list', async function () {
-        const response = await userHelper.request.get(
-          '/user/emails/primary-email-check' + SPLIT_TEST_QUERY,
-          { simple: false }
+        const response = await userHelper.fetch(
+          '/user/emails/primary-email-check' + SPLIT_TEST_QUERY
+        )
+        expect(response.status).to.equal(302)
+        expect(response.headers.get('location')).to.equal(
+          UserHelper.url('/project').toString()
         )
-        expect(response.statusCode).to.equal(302)
-        expect(response.headers.location).to.equal('/project')
       })
     })
 
@@ -125,21 +122,18 @@ describe('PrimaryEmailCheck', function () {
       })
 
       it('should be redirected from project list to the primary email check page', async function () {
-        const response = await userHelper.request.get(
-          '/project' + SPLIT_TEST_QUERY,
-          { simple: false }
-        )
-        expect(response.statusCode).to.equal(302)
-        expect(response.headers.location).to.equal(
-          '/user/emails/primary-email-check'
+        const response = await userHelper.fetch('/project' + SPLIT_TEST_QUERY)
+        expect(response.status).to.equal(302)
+        expect(response.headers.get('location')).to.equal(
+          UserHelper.url('/user/emails/primary-email-check').toString()
         )
       })
 
       it('can visit the primary email check page', async function () {
-        const response = await userHelper.request.get(
+        const response = await userHelper.fetch(
           '/user/emails/primary-email-check'
         )
-        expect(response.statusCode).to.equal(200)
+        expect(response.status).to.equal(200)
       })
     })
   })
@@ -154,34 +148,32 @@ describe('PrimaryEmailCheck', function () {
         $set: { lastPrimaryEmailCheck: new Date(time) },
       })
 
-      checkResponse = await userHelper.request.post(
+      checkResponse = await userHelper.fetch(
         '/user/emails/primary-email-check' + SPLIT_TEST_QUERY,
-        {
-          form: {},
-          simple: false,
-        }
+        { method: 'POST' }
       )
     })
 
     it('should be redirected to the project list page', function () {
-      expect(checkResponse.statusCode).to.equal(302)
-      expect(checkResponse.headers.location).to.equal('/project')
+      expect(checkResponse.status).to.equal(302)
+      expect(checkResponse.headers.get('location')).to.equal(
+        UserHelper.url('/project').toString()
+      )
     })
 
     it("shouldn't be redirected from project list to the primary email check page any longer", async function () {
-      const response = await userHelper.request.get(
-        '/project' + SPLIT_TEST_QUERY
-      )
-      expect(response.statusCode).to.equal(200)
+      const response = await userHelper.fetch('/project' + SPLIT_TEST_QUERY)
+      expect(response.status).to.equal(200)
     })
 
     it('visiting the primary email check page should redirect to the project list page', async function () {
-      const response = await userHelper.request.get(
-        '/user/emails/primary-email-check',
-        { simple: false }
+      const response = await userHelper.fetch(
+        '/user/emails/primary-email-check'
+      )
+      expect(response.status).to.equal(302)
+      expect(response.headers.get('location')).to.equal(
+        UserHelper.url('/project').toString()
       )
-      expect(response.statusCode).to.equal(302)
-      expect(response.headers.location).to.equal('/project')
     })
   })
 })

+ 7 - 8
services/web/test/acceptance/src/ProjectDuplicateNameTests.js

@@ -668,15 +668,14 @@ describe('ProjectDuplicateNames', function () {
     })
     it('should handle characters that would cause an invalid regular expression', async function () {
       const projectName = 'Example (test'
-      response = await userHelper.request.post('/project/new', {
-        simple: false,
-        form: { projectName },
+      response = await userHelper.fetch('/project/new', {
+        method: 'POST',
+        body: new URLSearchParams([['projectName', projectName]]),
       })
-      expect(response.statusCode).to.equal(200) // can create project
-      response = await userHelper.request.get(
-        `/project/${JSON.parse(response.body).project_id}`
-      )
-      expect(response.statusCode).to.equal(200) // can open project
+      const body = await response.json()
+      expect(response.status).to.equal(200) // can create project
+      response = await userHelper.fetch(`/project/${body.project_id}`)
+      expect(response.status).to.equal(200) // can open project
     })
   })
 })

+ 79 - 76
services/web/test/acceptance/src/helpers/UserHelper.js

@@ -1,4 +1,5 @@
 const { expect } = require('chai')
+const { CookieJar } = require('tough-cookie')
 const AuthenticationManager = require('../../../../app/src/Features/Authentication/AuthenticationManager')
 const Settings = require('@overleaf/settings')
 const InstitutionsAPI = require('../../../../app/src/Features/Institutions/InstitutionsAPI')
@@ -6,7 +7,7 @@ const UserCreator = require('../../../../app/src/Features/User/UserCreator')
 const UserGetter = require('../../../../app/src/Features/User/UserGetter')
 const UserUpdater = require('../../../../app/src/Features/User/UserUpdater')
 const moment = require('moment')
-const request = require('request-promise-native')
+const fetch = require('node-fetch')
 const { db } = require('../../../../app/src/infrastructure/mongodb')
 const { ObjectId } = require('mongodb')
 const {
@@ -82,43 +83,33 @@ class UserHelper {
     // used to store mongo user object once created/loaded
     this.user = null
     // cookie jar
-    this.jar = request.jar()
-    // create new request instance
-    this.request = request.defaults({})
-    // initialize request instance with default options
-    this.setRequestDefaults({
-      baseUrl: UserHelper.baseUrl(),
-      followRedirect: false,
-      jar: this.jar,
-      resolveWithFullResponse: true,
-    })
+    this.jar = new CookieJar()
   }
 
-  /* Set defaults for request object. Applied over existing defaults.
-   * @param {object} [defaults]
-   */
-  setRequestDefaults(defaults = {}) {
-    // request-promise instance for making requests
-    this.request = this.request.defaults(defaults)
-  }
+  async fetch(url, opts = {}) {
+    url = UserHelper.url(url)
+    const headers = {}
+    const cookieString = this.jar.getCookieStringSync(url)
+    if (cookieString) {
+      headers.Cookie = cookieString
+    }
+    if (this._csrfToken) {
+      headers['x-csrf-token'] = this._csrfToken
+    }
+    const response = await fetch(url, {
+      redirect: 'manual',
+      ...opts,
+      headers: { ...headers, ...opts.headers },
+    })
 
-  /**
-   * Make a request for the user and run expectations on the response.
-   * @param {object} [requestOptions] options to pass to request
-   * @param {object} [responseExpectations] expectations:
-   *   - {Int} statusCode the expected status code
-   *   - {RegEx} message a matcher for the message
-   */
-  async expectErrorOnRequest(requestOptions, responseExpectations) {
-    let error
-    try {
-      await this.request(requestOptions)
-    } catch (e) {
-      error = e
+    // From https://www.npmjs.com/package/node-fetch#extract-set-cookie-header
+    const cookies = response.headers.raw()['set-cookie']
+    if (cookies != null) {
+      for (const cookie of cookies) {
+        this.jar.setCookieSync(cookie, url)
+      }
     }
-    expect(error).to.exist
-    expect(error.statusCode).to.equal(responseExpectations.statusCode)
-    expect(error.message).to.match(responseExpectations.message)
+    return response
   }
 
   /* async http api call methods */
@@ -128,12 +119,8 @@ class UserHelper {
    */
   async getCsrfToken() {
     // get csrf token from api and store
-    const response = await this.request.get('/dev/csrf')
-    this._csrfToken = response.body
-    // use csrf token for requests
-    this.setRequestDefaults({
-      headers: { 'x-csrf-token': this._csrfToken },
-    })
+    const response = await this.fetch('/dev/csrf')
+    this._csrfToken = await response.text()
   }
 
   /**
@@ -142,13 +129,11 @@ class UserHelper {
    * @returns {object} http response
    */
   async logout(options = {}) {
-    // do not throw exception on 302
-    options.simple = false
     // post logout
-    const response = await this.request.post('/logout', options)
+    const response = await this.fetch('/logout', { method: 'POST', ...options })
     if (
-      response.statusCode !== 302 ||
-      !response.headers.location.includes('/login')
+      response.status !== 302 ||
+      !response.headers.get('location').includes('/login')
     ) {
       throw new Error('logout failed')
     }
@@ -168,6 +153,13 @@ class UserHelper {
     return `http://${process.env.HTTP_TEST_HOST || 'localhost'}:23000`
   }
 
+  /**
+   * Generates a full URL given a path
+   */
+  static url(path) {
+    return new URL(path, UserHelper.baseUrl())
+  }
+
   /* static async instantiation methods */
 
   /**
@@ -247,17 +239,30 @@ class UserHelper {
     const userHelper = new UserHelper()
     const loginPath = Settings.enableLegacyLogin ? '/login/legacy' : '/login'
     await userHelper.getCsrfToken()
-    const response = await userHelper.request.post(loginPath, {
-      json: {
+    const response = await userHelper.fetch(loginPath, {
+      method: 'POST',
+      headers: {
+        'Content-Type': 'application/json',
+        Accept: 'application/json',
+      },
+      body: JSON.stringify({
         'g-recaptcha-response': 'valid',
         ...userData,
-      },
+      }),
     })
-    if (response.statusCode !== 200 || response.body.redir !== '/project') {
+    if (!response.ok) {
+      const error = new Error('login failed')
+      error.response = response
+      throw error
+    }
+
+    const body = await response.json()
+    if (body.redir !== '/project') {
       const error = new Error('login failed')
       error.response = response
       throw error
     }
+
     userHelper.user = await UserGetter.promises.getUser({
       email: userData.email,
     })
@@ -274,10 +279,10 @@ class UserHelper {
    * @returns {Boolean}
    */
   async isLoggedIn() {
-    const response = await this.request.get('/user/sessions', {
-      followRedirect: true,
+    const response = await this.fetch('/user/sessions', {
+      redirect: 'follow',
     })
-    return response.request.path === '/user/sessions'
+    return !response.redirected
   }
 
   /**
@@ -292,8 +297,16 @@ class UserHelper {
     const userHelper = new UserHelper()
     await userHelper.getCsrfToken()
     userData = userHelper.getDefaultEmailPassword(userData)
-    options.json = userData
-    const { body } = await userHelper.request.post('/register', options)
+    const response = await userHelper.fetch('/register', {
+      method: 'POST',
+      headers: {
+        'Content-Type': 'application/json',
+        Accept: 'application/json',
+      },
+      body: JSON.stringify(userData),
+      ...options,
+    })
+    const body = await response.json()
     if (body.message && body.message.type === 'error') {
       throw new Error(`register api error: ${body.message.text}`)
     }
@@ -321,14 +334,11 @@ class UserHelper {
   }
 
   async addEmail(email) {
-    const response = await this.request.post({
-      form: {
-        email,
-      },
-      simple: false,
-      uri: '/user/emails',
+    const response = await this.fetch('/user/emails', {
+      method: 'POST',
+      body: new URLSearchParams([['email', email]]),
     })
-    expect(response.statusCode).to.equal(204)
+    expect(response.status).to.equal(204)
   }
 
   async addEmailAndConfirm(userId, email) {
@@ -393,16 +403,12 @@ class UserHelper {
   }
 
   async confirmEmail(userId, email) {
-    let response
     // UserHelper.createUser does not create a confirmation token
-    response = await this.request.post({
-      form: {
-        email,
-      },
-      simple: false,
-      uri: '/user/emails/resend_confirmation',
+    let response = await this.fetch('/user/emails/resend_confirmation', {
+      method: 'POST',
+      body: new URLSearchParams([['email', email]]),
     })
-    expect(response.statusCode).to.equal(200)
+    expect(response.status).to.equal(200)
     const tokenData = await db.tokens
       .find({
         use: 'email_confirmation',
@@ -411,14 +417,11 @@ class UserHelper {
         usedAt: { $exists: false },
       })
       .next()
-    response = await this.request.post({
-      form: {
-        token: tokenData.token,
-      },
-      simple: false,
-      uri: '/user/emails/confirm',
+    response = await this.fetch('/user/emails/confirm', {
+      method: 'POST',
+      body: new URLSearchParams([['token', tokenData.token]]),
     })
-    expect(response.statusCode).to.equal(200)
+    expect(response.status).to.equal(200)
   }
 }