Repository: parse-community/parse-server
Pull request #9692: ci: Fix flaky `sendVerificationEmail` tests
Author: login: dplewis
Labels: name: state:released
description: Released as stable version
Created: 2025-04-02T18:13:19Z | Merged: 2025-04-15T16:25:42Z

## Pull Request

- Report security issues [confidentially](https://github.com/parse-community/parse-server/security/policy).
- Any contribution is under this [license](https://github.com/parse-community/parse-server/blob/alpha/LICENSE).
- Link this pull request to an [issue](https://github.com/parse-community/parse-server/issues?q=is%3Aissue).

## Issue
<!-- Add the link to the issue that this PR closes. -->
sendVerificationEmail runs in the background so you need to wait until it's finished

Closes: https://github.com/parse-community/parse-server/issues/9274
Closes: https://github.com/parse-community/parse-server/issues/9275
Closes: https://github.com/parse-community/parse-server/issues/9276

## Approach
<!-- Describe the changes in this PR. -->
* Use deferred promises
* Remove jasmine.timeout where needed
* Refactor tests to use async / await


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Tests**
	- Improved reliability and readability of email verification tests by updating asynchronous handling to use async/await.
	- Ensured email sending in verification tests is fully completed before assertions.
	- Removed several previously marked flaky tests from the retry list, reflecting increased test stability.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/spec/EmailVerificationToken.spec.js b/spec/EmailVerificationToken.spec.js
index ec3d7b8ec0..6dd0a01966 100644
--- a/spec/EmailVerificationToken.spec.js
+++ b/spec/EmailVerificationToken.spec.js
@@ -3,295 +3,230 @@
 const Auth = require('../lib/Auth');
 const Config = require('../lib/Config');
 const request = require('../lib/request');
+const { resolvingPromise, sleep } = require('../lib/TestUtils');
 const MockEmailAdapterWithOptions = require('./support/MockEmailAdapterWithOptions');
 
-describe('Email Verification Token Expiration: ', () => {
-  it('show the invalid verification link page, if the user clicks on the verify email link after the email verify token expires', done => {
+describe('Email Verification Token Expiration:', () => {
+  it('show the invalid verification link page, if the user clicks on the verify email link after the email verify token expires', async ()  => {
     const user = new Parse.User();
     let sendEmailOptions;
+    const sendPromise = resolvingPromise();
     const emailAdapter = {
       sendVerificationEmail: options => {
         sendEmailOptions = options;
+        sendPromise.resolve();
       },
       sendPasswordResetEmail: () => Promise.resolve(),
       sendMail: () => {},
     };
-    reconfigureServer({
+    await reconfigureServer({
       appName: 'emailVerifyToken',
       verifyUserEmails: true,
       emailAdapter: emailAdapter,
       emailVerifyTokenValidityDuration: 0.5, // 0.5 second
       publicServerURL: 'http://localhost:8378/1',
-    })
-      .then(() => {
-        user.setUsername('testEmailVerifyTokenValidity');
-        user.setPassword('expiringToken');
-        user.set('email', 'user@parse.com');
-        return user.signUp();
-      })
-      .then(() => {
-        // wait for 1 second - simulate user behavior to some extent
-        setTimeout(() => {
-          expect(sendEmailOptions).not.toBeUndefined();
+    });
+    user.setUsername('testEmailVerifyTokenValidity');
+    user.setPassword('expiringToken');
+    user.set('email', 'user@parse.com');
+    await user.signUp();
+    await sendPromise;
+    // wait for 1 second - simulate user behavior to some extent
+    await sleep(1000);
 
-          request({
-            url: sendEmailOptions.link,
-            followRedirects: false,
-          }).then(response => {
-            expect(response.status).toEqual(302);
-            const url = new URL(sendEmailOptions.link);
-            const token = url.searchParams.get('token');
-            expect(response.text).toEqual(
-              `Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?appId=test&token=${token}`
-            );
-            done();
-          });
-        }, 1000);
-      })
-      .catch(err => {
-        jfail(err);
-        done();
-      });
+    expect(sendEmailOptions).not.toBeUndefined();
+
+    const response = await request({
+      url: sendEmailOptions.link,
+      followRedirects: false,
+    });
+    expect(response.status).toEqual(302);
+    const url = new URL(sendEmailOptions.link);
+    const token = url.searchParams.get('token');
+    expect(response.text).toEqual(
+      `Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?appId=test&token=${token}`
+    );
   });
 
-  it('emailVerified should set to false, if the user does not verify their email before the email verify token expires', done => {
+  it('emailVerified should set to false, if the user does not verify their email before the email verify token expires', async () => {
     const user = new Parse.User();
     let sendEmailOptions;
+    const sendPromise = resolvingPromise();
     const emailAdapter = {
       sendVerificationEmail: options => {
         sendEmailOptions = options;
+        sendPromise.resolve();
       },
       sendPasswordResetEmail: () => Promise.resolve(),
       sendMail: () => {},
     };
-    reconfigureServer({
+    await reconfigureServer({
       appName: 'emailVerifyToken',
       verifyUserEmails: true,
       emailAdapter: emailAdapter,
       emailVerifyTokenValidityDuration: 0.5, // 0.5 second
       publicServerURL: 'http://localhost:8378/1',
-    })
-      .then(() => {
-        user.setUsername('testEmailVerifyTokenValidity');
-        user.setPassword('expiringToken');
-        user.set('email', 'user@parse.com');
-        return user.signUp();
-      })
-      .then(() => {
-        // wait for 1 second - simulate user behavior to some extent
-        setTimeout(() => {
-          expect(sendEmailOptions).not.toBeUndefined();
+    });
+    user.setUsername('testEmailVerifyTokenValidity');
+    user.setPassword('expiringToken');
+    user.set('email', 'user@parse.com');
+    await user.signUp();
+    await sendPromise;
+    // wait for 1 second - simulate user behavior to some extent
+    await sleep(1000);
 
-          request({
-            url: sendEmailOptions.link,
-            followRedirects: false,
-          }).then(response => {
-            expect(response.status).toEqual(302);
-            user
-              .fetch()
-              .then(() => {
-                expect(user.get('emailVerified')).toEqual(false);
-                done();
-              })
-              .catch(error => {
-                jfail(error);
-                done();
-              });
-          });
-        }, 1000);
-      })
-      .catch(error => {
-        jfail(error);
-        done();
-      });
+    expect(sendEmailOptions).not.toBeUndefined();
+
+    const response = await request({
+      url: sendEmailOptions.link,
+      followRedirects: false,
+    });
+    expect(response.status).toEqual(302);
+    await user.fetch();
+    expect(user.get('emailVerified')).toEqual(false);
   });
 
-  it_id('f20dd3c2-87d9-4bc6-a51d-4ea2834acbcc')(it)('if user clicks on the email verify link before email verification token expiration then show the verify email success page', done => {
+  it_id('f20dd3c2-87d9-4bc6-a51d-4ea2834acbcc')(it)('if user clicks on the email verify link before email verification token expiration then show the verify email success page', async () => {
     const user = new Parse.User();
     let sendEmailOptions;
+    const sendPromise = resolvingPromise();
     const emailAdapter = {
       sendVerificationEmail: options => {
         sendEmailOptions = options;
+        sendPromise.resolve();
       },
       sendPasswordResetEmail: () => Promise.resolve(),
       sendMail: () => {},
     };
-    reconfigureServer({
+    await reconfigureServer({
       appName: 'emailVerifyToken',
       verifyUserEmails: true,
       emailAdapter: emailAdapter,
       emailVerifyTokenValidityDuration: 5, // 5 seconds
       publicServerURL: 'http://localhost:8378/1',
-    })
-      .then(() => {
-        user.setUsername('testEmailVerifyTokenValidity');
-        user.setPassword('expiringToken');
-        user.set('email', 'user@parse.com');
-        return user.signUp();
-      })
-      .then(() => jasmine.timeout())
-      .then(() => {
-        request({
-          url: sendEmailOptions.link,
-          followRedirects: false,
-        }).then(response => {
-          expect(response.status).toEqual(302);
-          expect(response.text).toEqual(
-            'Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html'
-          );
-          done();
-        });
-      })
-      .catch(error => {
-        jfail(error);
-        done();
-      });
+    });
+    user.setUsername('testEmailVerifyTokenValidity');
+    user.setPassword('expiringToken');
+    user.set('email', 'user@parse.com');
+    await user.signUp();
+    await sendPromise;
+    const response = await request({
+      url: sendEmailOptions.link,
+      followRedirects: false,
+    });
+    expect(response.status).toEqual(302);
+    expect(response.text).toEqual(
+      'Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html'
+    );
   });
 
-  it_id('94956799-c85e-4297-b879-e2d1f985394c')(it)('if user clicks on the email verify link before email verification token expiration then emailVerified should be true', done => {
+  it_id('94956799-c85e-4297-b879-e2d1f985394c')(it)('if user clicks on the email verify link before email verification token expiration then emailVerified should be true', async () => {
     const user = new Parse.User();
     let sendEmailOptions;
+    const sendPromise = resolvingPromise();
     const emailAdapter = {
       sendVerificationEmail: options => {
         sendEmailOptions = options;
+        sendPromise.resolve();
       },
       sendPasswordResetEmail: () => Promise.resolve(),
       sendMail: () => {},
     };
-    reconfigureServer({
+    await reconfigureServer({
       appName: 'emailVerifyToken',
       verifyUserEmails: true,
       emailAdapter: emailAdapter,
       emailVerifyTokenValidityDuration: 5, // 5 seconds
       publicServerURL: 'http://localhost:8378/1',
-    })
-      .then(() => {
-        user.setUsername('testEmailVerifyTokenValidity');
-        user.setPassword('expiringToken');
-        user.set('email', 'user@parse.com');
-        return user.signUp();
-      })
-      .then(() => jasmine.timeout())
-      .then(() => {
-        request({
-          url: sendEmailOptions.link,
-          followRedirects: false,
-        }).then(response => {
-          expect(response.status).toEqual(302);
-          user
-            .fetch()
-            .then(() => {
-              expect(user.get('emailVerified')).toEqual(true);
-              done();
-            })
-            .catch(error => {
-              jfail(error);
-              done();
-            });
-        });
-      })
-      .catch(error => {
-        jfail(error);
-        done();
-      });
+    });
+    user.setUsername('testEmailVerifyTokenValidity');
+    user.setPassword('expiringToken');
+    user.set('email', 'user@parse.com');
+    await user.signUp();
+    await sendPromise;
+    const response = await request({
+      url: sendEmailOptions.link,
+      followRedirects: false,
+    });
+    expect(response.status).toEqual(302);
+    await user.fetch();
+    expect(user.get('emailVerified')).toEqual(true);
   });
 
-  it_id('25f3f895-c987-431c-9841-17cb6aaf18b5')(it)('if user clicks on the email verify link before email verification token expiration then user should be able to login', done => {
+  it_id('25f3f895-c987-431c-9841-17cb6aaf18b5')(it)('if user clicks on the email verify link before email verification token expiration then user should be able to login', async () => {
     const user = new Parse.User();
     let sendEmailOptions;
+    const sendPromise = resolvingPromise();
     const emailAdapter = {
       sendVerificationEmail: options => {
         sendEmailOptions = options;
+        sendPromise.resolve();
       },
       sendPasswordResetEmail: () => Promise.resolve(),
       sendMail: () => {},
     };
-    reconfigureServer({
+    await reconfigureServer({
       appName: 'emailVerifyToken',
       verifyUserEmails: true,
       emailAdapter: emailAdapter,
       emailVerifyTokenValidityDuration: 5, // 5 seconds
       publicServerURL: 'http://localhost:8378/1',
-    })
-      .then(() => {
-        user.setUsername('testEmailVerifyTokenValidity');
-        user.setPassword('expiringToken');
-        user.set('email', 'user@parse.com');
-        return user.signUp();
-      })
-      .then(() => jasmine.timeout())
-      .then(() => {
-        request({
-          url: sendEmailOptions.link,
-          followRedirects: false,
-        }).then(response => {
-          expect(response.status).toEqual(302);
-          Parse.User.logIn('testEmailVerifyTokenValidity', 'expiringToken')
-            .then(user => {
-              expect(typeof user).toBe('object');
-              expect(user.get('emailVerified')).toBe(true);
-              done();
-            })
-            .catch(error => {
-              jfail(error);
-              done();
-            });
-        });
-      })
-      .catch(error => {
-        jfail(error);
-        done();
-      });
+    });
+    user.setUsername('testEmailVerifyTokenValidity');
+    user.setPassword('expiringToken');
+    user.set('email', 'user@parse.com');
+    await user.signUp();
+    await sendPromise;
+    const response = await request({
+      url: sendEmailOptions.link,
+      followRedirects: false,
+    });
+    expect(response.status).toEqual(302);
+    const verifiedUser = await Parse.User.logIn('testEmailVerifyTokenValidity', 'expiringToken');
+    expect(typeof verifiedUser).toBe('object');
+    expect(verifiedUser.get('emailVerified')).toBe(true);
   });
 
-  it_id('c6a3e188-9065-4f50-842d-454d1e82f289')(it)('sets the _email_verify_token_expires_at and _email_verify_token fields after user SignUp', done => {
+  it_id('c6a3e188-9065-4f50-842d-454d1e82f289')(it)('sets the _email_verify_token_expires_at and _email_verify_token fields after user SignUp', async () => {
     const user = new Parse.User();
     let sendEmailOptions;
+    const sendPromise = resolvingPromise();
     const emailAdapter = {
       sendVerificationEmail: options => {
         sendEmailOptions = options;
+        sendPromise.resolve();
       },
       sendPasswordResetEmail: () => Promise.resolve(),
       sendMail: () => {},
     };
-    reconfigureServer({
+    await reconfigureServer({
       appName: 'emailVerifyToken',
       verifyUserEmails: true,
       emailAdapter: emailAdapter,
       emailVerifyTokenValidityDuration: 5, // 5 seconds
       publicServerURL: 'http://localhost:8378/1',
-    })
-      .then(() => {
-        user.setUsername('sets_email_verify_token_expires_at');
-        user.setPassword('expiringToken');
-        user.set('email', 'user@parse.com');
-        return user.signUp();
-      })
-      .then(() => {
-        const config = Config.get('test');
-        return config.database.find(
-          '_User',
-          {
-            username: 'sets_email_verify_token_expires_at',
-          },
-          {},
-          Auth.maintenance(config)
-        );
-      })
-      .then(results => {
-        expect(results.length).toBe(1);
-        const user = results[0];
-        expect(typeof user).toBe('object');
-        expect(user.emailVerified).toEqual(false);
-        expect(typeof user._email_verify_token).toBe('string');
-        expect(typeof user._email_verify_token_expires_at).toBe('object');
-        expect(sendEmailOptions).toBeDefined();
-        done();
-      })
-      .catch(error => {
-        jfail(error);
-        done();
-      });
+    });
+    user.setUsername('sets_email_verify_token_expires_at');
+    user.setPassword('expiringToken');
+    user.set('email', 'user@parse.com');
+    await user.signUp();
+    await sendPromise;
+    const config = Config.get('test');
+    const results = await config.database.find(
+      '_User',
+      {
+        username: 'sets_email_verify_token_expires_at',
+      },
+      {},
+      Auth.maintenance(config)
+    );
+    expect(results.length).toBe(1);
+    const verifiedUser = results[0];
+    expect(typeof verifiedUser).toBe('object');
+    expect(verifiedUser.emailVerified).toEqual(false);
+    expect(typeof verifiedUser._email_verify_token).toBe('string');
+    expect(typeof verifiedUser._email_verify_token_expires_at).toBe('object');
+    expect(sendEmailOptions).toBeDefined();
   });
 
   it('can resend email using an expired token', async () => {
@@ -413,9 +348,11 @@ describe('Email Verification Token Expiration: ', () => {
 
   it_id('b3549300-bed7-4a5e-bed5-792dbfead960')(it)('can conditionally send emails and allow conditional login', async () => {
     let sendEmailOptions;
+    const sendPromise = resolvingPromise();
     const emailAdapter = {
       sendVerificationEmail: options => {
         sendEmailOptions = options;
+        sendPromise.resolve();
       },
       sendPasswordResetEmail: () => Promise.resolve(),
       sendMail: () => {},
@@ -451,7 +388,7 @@ describe('Email Verification Token Expiration: ', () => {
     user2.setPassword('expiringToken');
     user2.set('email', 'user2@example.com');
     await user2.signUp();
-    await jasmine.timeout();
+    await sendPromise;
     expect(user2.getSessionToken()).toBeUndefined();
     expect(sendEmailOptions).toBeDefined();
     expect(verifySpy).toHaveBeenCalledTimes(5);
@@ -486,7 +423,7 @@ describe('Email Verification Token Expiration: ', () => {
     newUser.set('email', 'user@example.com');
     await newUser.signUp();
     await Parse.User.requestEmailVerification('user@example.com');
-    await jasmine.timeout();
+    await sleep(100);
     expect(sendSpy).toHaveBeenCalledTimes(2);
     expect(emailSpy).toHaveBeenCalledTimes(0);
   });
@@ -529,9 +466,11 @@ describe('Email Verification Token Expiration: ', () => {
 
   it_id('a8c1f820-822f-4a37-9d08-a968cac8369d')(it)('beforeSave options do not change existing behaviour', async () => {
     let sendEmailOptions;
+    const sendPromise = resolvingPromise();
     const emailAdapter = {
       sendVerificationEmail: options => {
         sendEmailOptions = options;
+        sendPromise.resolve();
       },
       sendPasswordResetEmail: () => Promise.resolve(),
       sendMail: () => {},
@@ -549,7 +488,7 @@ describe('Email Verification Token Expiration: ', () => {
     newUser.setPassword('expiringToken');
     newUser.set('email', 'user@parse.com');
     await newUser.signUp();
-    await jasmine.timeout();
+    await sendPromise;
     const response = await request({
       url: sendEmailOptions.link,
       followRedirects: false,
@@ -569,70 +508,56 @@ describe('Email Verification Token Expiration: ', () => {
     expect(emailSpy).toHaveBeenCalled();
   });
 
-  it_id('36d277eb-ec7c-4a39-9108-435b68228741')(it)('unsets the _email_verify_token_expires_at and _email_verify_token fields in the User class if email verification is successful', done => {
+  it_id('36d277eb-ec7c-4a39-9108-435b68228741')(it)('unsets the _email_verify_token_expires_at and _email_verify_token fields in the User class if email verification is successful', async () => {
     const user = new Parse.User();
     let sendEmailOptions;
+    const sendPromise = resolvingPromise();
     const emailAdapter = {
       sendVerificationEmail: options => {
         sendEmailOptions = options;
+        sendPromise.resolve();
       },
       sendPasswordResetEmail: () => Promise.resolve(),
       sendMail: () => {},
     };
-    reconfigureServer({
+    await reconfigureServer({
       appName: 'emailVerifyToken',
       verifyUserEmails: true,
       emailAdapter: emailAdapter,
       emailVerifyTokenValidityDuration: 5, // 5 seconds
       publicServerURL: 'http://localhost:8378/1',
-    })
-      .then(() => {
-        user.setUsername('unsets_email_verify_token_expires_at');
-        user.setPassword('expiringToken');
-        user.set('email', 'user@parse.com');
-        return user.signUp();
-      })
-      .then(() => jasmine.timeout())
-      .then(() => {
-        request({
-          url: sendEmailOptions.link,
-          followRedirects: false,
-        }).then(response => {
-          expect(response.status).toEqual(302);
-          const config = Config.get('test');
-          return config.database
-            .find('_User', {
-              username: 'unsets_email_verify_token_expires_at',
-            })
-            .then(results => {
-              expect(results.length).toBe(1);
-              return results[0];
-            })
-            .then(user => {
-              expect(typeof user).toBe('object');
-              expect(user.emailVerified).toEqual(true);
-              expect(typeof user._email_verify_token).toBe('undefined');
-              expect(typeof user._email_verify_token_expires_at).toBe('undefined');
-              done();
-            })
-            .catch(error => {
-              jfail(error);
-              done();
-            });
-        });
-      })
-      .catch(error => {
-        jfail(error);
-        done();
-      });
+    });
+    user.setUsername('unsets_email_verify_token_expires_at');
+    user.setPassword('expiringToken');
+    user.set('email', 'user@parse.com');
+    await user.signUp();
+    await sendPromise;
+    const response = await request({
+      url: sendEmailOptions.link,
+      followRedirects: false,
+    });
+    expect(response.status).toEqual(302);
+    const config = Config.get('test');
+    const results = await config.database.find('_User', {
+      username: 'unsets_email_verify_token_expires_at',
+    });
+    expect(results.length).toBe(1);
+    const verifiedUser = results[0];
+
+    expect(typeof verifiedUser).toBe('object');
+    expect(verifiedUser.emailVerified).toEqual(true);
+    expect(typeof verifiedUser._email_verify_token).toBe('undefined');
+    expect(typeof verifiedUser._email_verify_token_expires_at).toBe('undefined');
   });
 
-  it_id('4f444704-ec4b-4dff-b947-614b1c6971c4')(it)('clicking on the email verify link by an email VERIFIED user that was setup before enabling the expire email verify token should show email verify email success', done => {
+  it_id('4f444704-ec4b-4dff-b947-614b1c6971c4')(it)('clicking on the email verify link by an email VERIFIED user that was setup before enabling the expire email verify token should show email verify email success', async () => {
     const user = new Parse.User();
     let sendEmailOptions;
+    const sendPromise = resolvingPromise();
     const emailAdapter = {
       sendVerificationEmail: options => {
         sendEmailOptions = options;
+        sendPromise.resolve();
       },
       sendPasswordResetEmail: () => Promise.resolve(),
       sendMail: () => {},
@@ -645,55 +570,43 @@ describe('Email Verification Token Expiration: ', () => {
     };
 
     // setup server WITHOUT enabling the expire email verify token flag
-    reconfigureServer(serverConfig)
-      .then(() => {
-        user.setUsername('testEmailVerifyTokenValidity');
-        user.setPassword('expiringToken');
-        user.set('email', 'user@parse.com');
-        return user.signUp();
-      })
-      .then(() => jasmine.timeout())
-      .then(() => {
-        return request({
-          url: sendEmailOptions.link,
-          followRedirects: false,
-        }).then(response => {
-          expect(response.status).toEqual(302);
-          return user.fetch();
-        });
-      })
-      .then(() => {
-        expect(user.get('emailVerified')).toEqual(true);
-        // RECONFIGURE the server i.e., ENABLE the expire email verify token flag
-        serverConfig.emailVerifyTokenValidityDuration = 5; // 5 seconds
-        return reconfigureServer(serverConfig);
-      })
-      .then(() => {
-        request({
-          url: sendEmailOptions.link,
-          followRedirects: false,
-        }).then(response => {
-          expect(response.status).toEqual(302);
-          const url = new URL(sendEmailOptions.link);
-          const token = url.searchParams.get('token');
-          expect(response.text).toEqual(
-            `Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?appId=test&token=${token}`
-          );
-          done();
-        });
-      })
-      .catch(error => {
-        jfail(error);
-        done();
-      });
+    await reconfigureServer(serverConfig);
+    user.setUsername('testEmailVerifyTokenValidity');
+    user.setPassword('expiringToken');
+    user.set('email', 'user@parse.com');
+    await user.signUp();
+    await sendPromise;
+    let response = await request({
+      url: sendEmailOptions.link,
+      followRedirects: false,
+    });
+    expect(response.status).toEqual(302);
+    await user.fetch();
+    expect(user.get('emailVerified')).toEqual(true);
+    // RECONFIGURE the server i.e., ENABLE the expire email verify token flag
+    serverConfig.emailVerifyTokenValidityDuration = 5; // 5 seconds
+    await reconfigureServer(serverConfig);
+
+    response = await request({
+      url: sendEmailOptions.link,
+      followRedirects: false,
+    });
+    expect(response.status).toEqual(302);
+    const url = new URL(sendEmailOptions.link);
+    const token = url.searchParams.get('token');
+    expect(response.text).toEqual(
+      `Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?appId=test&token=${token}`
+    );
   });
 
-  it('clicking on the email verify link by an email UNVERIFIED user that was setup before enabling the expire email verify token should show invalid verficiation link page', done => {
+  it('clicking on the email verify link by an email UNVERIFIED user that was setup before enabling the expire email verify token should show invalid verficiation link page', async () => {
     const user = new Parse.User();
     let sendEmailOptions;
+    const sendPromise = resolvingPromise();
     const emailAdapter = {
       sendVerificationEmail: options => {
         sendEmailOptions = options;
+        sendPromise.resolve();
       },
       sendPasswordResetEmail: () => Promise.resolve(),
       sendMail: () => {},
@@ -706,51 +619,42 @@ describe('Email Verification Token Expiration: ', () => {
     };
 
     // setup server WITHOUT enabling the expire email verify token flag
-    reconfigureServer(serverConfig)
-      .then(() => {
-        user.setUsername('testEmailVerifyTokenValidity');
-        user.setPassword('expiringToken');
-        user.set('email', 'user@parse.com');
-        return user.signUp();
-      })
-      .then(() => {
-        // just get the user again - DO NOT email verify the user
-        return user.fetch();
-      })
-      .then(() => {
-        expect(user.get('emailVerified')).toEqual(false);
-        // RECONFIGURE the server i.e., ENABLE the expire email verify token flag
-        serverConfig.emailVerifyTokenValidityDuration = 5; // 5 seconds
-        return reconfigureServer(serverConfig);
-      })
-      .then(() => {
-        request({
-          url: sendEmailOptions.link,
-          followRedirects: false,
-        }).then(response => {
-          expect(response.status).toEqual(302);
-          const url = new URL(sendEmailOptions.link);
-          const token = url.searchParams.get('token');
-          expect(response.text).toEqual(
-            `Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?appId=test&token=${token}`
-          );
-          done();
-        });
-      })
-      .catch(error => {
-        jfail(error);
-        done();
-      });
+    await reconfigureServer(serverConfig);
+    user.setUsername('testEmailVerifyTokenValidity');
+    user.setPassword('expiringToken');
+    user.set('email', 'user@parse.com');
+    await user.signUp();
+    await sendPromise;
+    // just get the user again - DO NOT email verify the user
+    await user.fetch();
+
+    expect(user.get('emailVerified')).toEqual(false);
+    // RECONFIGURE the server i.e., ENABLE the expire email verify token flag
+    serverConfig.emailVerifyTokenValidityDuration = 5; // 5 seconds
+    await reconfigureServer(serverConfig);
+
+    const response = await request({
+      url: sendEmailOptions.link,
+      followRedirects: false,
+    });
+    expect(response.status).toEqual(302);
+    const url = new URL(sendEmailOptions.link);
+    const token = url.searchParams.get('token');
+    expect(response.text).toEqual(
+      `Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?appId=test&token=${token}`
+    );
   });
 
-  it_id('b6c87f35-d887-477d-bc86-a9217a424f53')(it)('setting the email on the user should set a new email verification token and new expiration date for the token when expire email verify token flag is set', done => {
+  it_id('b6c87f35-d887-477d-bc86-a9217a424f53')(it)('setting the email on the user should set a new email verification token and new expiration date for the token when expire email verify token flag is set', async () => {
     const user = new Parse.User();
     let userBeforeEmailReset;
 
     let sendEmailOptions;
+    const sendPromise = resolvingPromise();
     const emailAdapter = {
       sendVerificationEmail: options => {
         sendEmailOptions = options;
+        sendPromise.resolve();
       },
       sendPasswordResetEmail: () => Promise.resolve(),
       sendMail: () => {},
@@ -763,147 +667,122 @@ describe('Email Verification Token Expiration: ', () => {
       publicServerURL: 'http://localhost:8378/1',
     };
 
-    reconfigureServer(serverConfig)
-      .then(() => {
-        user.setUsername('newEmailVerifyTokenOnEmailReset');
-        user.setPassword('expiringToken');
-        user.set('email', 'user@parse.com');
-        return user.signUp();
-      })
-      .then(() => {
-        const config = Config.get('test');
-        return config.database
-          .find('_User', { username: 'newEmailVerifyTokenOnEmailReset' })
-          .then(results => {
-            return results[0];
-          });
-      })
-      .then(userFromDb => {
-        expect(typeof userFromDb).toBe('object');
-        userBeforeEmailReset = userFromDb;
+    await reconfigureServer(serverConfig);
+    user.setUsername('newEmailVerifyTokenOnEmailReset');
+    user.setPassword('expiringToken');
+    user.set('email', 'user@parse.com');
+    await user.signUp();
+    await sendPromise;
+    const config = Config.get('test');
+    const userFromDb = await config.database
+      .find('_User', { username: 'newEmailVerifyTokenOnEmailReset' })
+      .then(results => {
+        return results[0];
+      });
+    expect(typeof userFromDb).toBe('object');
+    userBeforeEmailReset = userFromDb;
 
-        // trigger another token generation by setting the email
-        user.set('email', 'user@parse.com');
-        return new Promise(resolve => {
-          // wait for half a sec to get a new expiration time
-          setTimeout(() => resolve(user.save()), 500);
-        });
-      })
-      .then(() => {
-        const config = Config.get('test');
-        return config.database
-          .find(
-            '_User',
-            { username: 'newEmailVerifyTokenOnEmailReset' },
-            {},
-            Auth.maintenance(config)
-          )
-          .then(results => {
-            return results[0];
-          });
-      })
-      .then(userAfterEmailReset => {
-        expect(typeof userAfterEmailReset).toBe('object');
-        expect(userBeforeEmailReset._email_verify_token).not.toEqual(
-          userAfterEmailReset._email_verify_token
-        );
-        expect(userBeforeEmailReset._email_verify_token_expires_at).not.toEqual(
-          userAfterEmailReset._email_verify_token_expires_at
-        );
-        expect(sendEmailOptions).toBeDefined();
-        done();
-      })
-      .catch(error => {
-        jfail(error);
-        done();
+    // trigger another token generation by setting the email
+    user.set('email', 'user@parse.com');
+    await new Promise(resolve => {
+      // wait for half a sec to get a new expiration time
+      setTimeout(() => resolve(user.save()), 500);
+    });
+    const userAfterEmailReset = await config.database
+      .find(
+        '_User',
+        { username: 'newEmailVerifyTokenOnEmailReset' },
+        {},
+        Auth.maintenance(config)
+      )
+      .then(results => {
+        return results[0];
       });
+
+    expect(typeof userAfterEmailReset).toBe('object');
+    expect(userBeforeEmailReset._email_verify_token).not.toEqual(
+      userAfterEmailReset._email_verify_token
+    );
+    expect(userBeforeEmailReset._email_verify_token_expires_at).not.toEqual(
+      userAfterEmailReset._email_verify_token_expires_at
+    );
+    expect(sendEmailOptions).toBeDefined();
   });
 
-  it_id('28f2140d-48bd-44ac-a141-ba60ea8d9713')(it)('should send a new verification email when a resend is requested and the user is UNVERIFIED', done => {
+  it_id('28f2140d-48bd-44ac-a141-ba60ea8d9713')(it)('should send a new verification email when a resend is requested and the user is UNVERIFIED', async () => {
     const user = new Parse.User();
     let sendEmailOptions;
     let sendVerificationEmailCallCount = 0;
     let userBeforeRequest;
+    const promise1 = resolvingPromise();
+    const promise2 = resolvingPromise();
     const emailAdapter = {
       sendVerificationEmail: options => {
         sendEmailOptions = options;
         sendVerificationEmailCallCount++;
+        if (sendVerificationEmailCallCount === 1) {
+          promise1.resolve();
+        } else {
+          promise2.resolve();
+        }
       },
       sendPasswordResetEmail: () => Promise.resolve(),
       sendMail: () => {},
     };
-    reconfigureServer({
+    await reconfigureServer({
       appName: 'emailVerifyToken',
       verifyUserEmails: true,
       emailAdapter: emailAdapter,
       emailVerifyTokenValidityDuration: 5, // 5 seconds
       publicServerURL: 'http://localhost:8378/1',
-    })
-      .then(() => {
-        user.setUsername('resends_verification_token');
-        user.setPassword('expiringToken');
-        user.set('email', 'user@parse.com');
-        return user.signUp();
-      })
-      .then(() => {
-        const config = Config.get('test');
-        return config.database
-          .find('_User', { username: 'resends_verification_token' })
-          .then(results => {
-            return results[0];
-          });
-      })
-      .then(newUser => {
-        // store this user before we make our email request
-        userBeforeRequest = newUser;
+    });
+    user.setUsername('resends_verification_token');
+    user.setPassword('expiringToken');
+    user.set('email', 'user@parse.com');
+    await user.signUp();
+    await promise1;
+    const config = Config.get('test');
+    const newUser = await config.database
+      .find('_User', { username: 'resends_verification_token' })
+      .then(results => {
+        return results[0];
+      });
+    // store this user before we make our email request
+    userBeforeRequest = newUser;
 
-        expect(sendVerificationEmailCallCount).toBe(1);
+    expect(sendVerificationEmailCallCount).toBe(1);
 
-        return request({
-          url: 'http://localhost:8378/1/verificationEmailRequest',
-          method: 'POST',
-          body: {
-            email: 'user@parse.com',
-          },
-          headers: {
-            'X-Parse-Application-Id': Parse.applicationId,
-            'X-Parse-REST-API-Key': 'rest',
-            'Content-Type': 'application/json',
-          },
-        });
-      })
-      .then(response => {
-        expect(response.status).toBe(200);
-      })
-      .then(() => jasmine.timeout())
-      .then(() => {
-        expect(sendVerificationEmailCallCount).toBe(2);
-        expect(sendEmailOptions).toBeDefined();
+    const response = await request({
+      url: 'http://localhost:8378/1/verificationEmailRequest',
+      method: 'POST',
+      body: {
+        email: 'user@parse.com',
+      },
+      headers: {
+        'X-Parse-Application-Id': Parse.applicationId,
+        'X-Parse-REST-API-Key': 'rest',
+        'Content-Type': 'application/json',
+      },
+    });
+    expect(response.status).toBe(200);
+    await promise2;
+    expect(sendVerificationEmailCallCount).toBe(2);
+    expect(sendEmailOptions).toBeDefined();
 
-        // query for this user again
-        const config = Config.get('test');
-        return config.database
-          .find('_User', { username: 'resends_verification_token' }, {}, Auth.maintenance(config))
-          .then(results => {
-            return results[0];
-          });
-      })
-      .then(userAfterRequest => {
-        // verify that our token & expiration has been changed for this new request
-        expect(typeof userAfterRequest).toBe('object');
-        expect(userBeforeRequest._email_verify_token).not.toEqual(
-          userAfterRequest._email_verify_token
-        );
-        expect(userBeforeRequest._email_verify_token_expires_at).not.toEqual(
-          userAfterRequest._email_verify_token_expires_at
-        );
-        done();
-      })
-      .catch(error => {
-        console.log(error);
-        jfail(error);
-        done();
+    // query for this user again
+    const userAfterRequest = await config.database
+      .find('_User', { username: 'resends_verification_token' }, {}, Auth.maintenance(config))
+      .then(results => {
+        return results[0];
       });
+    // verify that our token & expiration has been changed for this new request
+    expect(typeof userAfterRequest).toBe('object');
+    expect(userBeforeRequest._email_verify_token).not.toEqual(
+      userAfterRequest._email_verify_token
+    );
+    expect(userBeforeRequest._email_verify_token_expires_at).not.toEqual(
+      userAfterRequest._email_verify_token_expires_at
+    );
   });
 
   it('provides function arguments in verifyUserEmails on verificationEmailRequest', async () => {
@@ -941,7 +820,7 @@ describe('Email Verification Token Expiration: ', () => {
     expect(verifyUserEmailsSpy).toHaveBeenCalledTimes(1);
   });
 
-  it('should throw with invalid emailVerifyTokenReuseIfValid', async done => {
+  it('should throw with invalid emailVerifyTokenReuseIfValid', async () => {
     const sendEmailOptions = [];
     const emailAdapter = {
       sendVerificationEmail: () => Promise.resolve(),
@@ -977,16 +856,22 @@ describe('Email Verification Token Expiration: ', () => {
         'You cannot use emailVerifyTokenReuseIfValid without emailVerifyTokenValidityDuration'
       );
     }
-    done();
   });
 
-  it_id('0e66b7f6-2c07-4117-a8b9-605aa31a1e29')(it)('should match codes with emailVerifyTokenReuseIfValid', async done => {
+  it_id('0e66b7f6-2c07-4117-a8b9-605aa31a1e29')(it)('should match codes with emailVerifyTokenReuseIfValid', async () => {
     let sendEmailOptions;
     let sendVerificationEmailCallCount = 0;
+    const promise1 = resolvingPromise();
+    const promise2 = resolvingPromise();
     const emailAdapter = {
       sendVerificationEmail: options => {
         sendEmailOptions = options;
         sendVerificationEmailCallCount++;
+        if (sendVerificationEmailCallCount === 1) {
+          promise1.resolve();
+        } else {
+          promise2.resolve();
+        }
       },
       sendPasswordResetEmail: () => Promise.resolve(),
       sendMail: () => {},
@@ -1004,7 +889,7 @@ describe('Email Verification Token Expiration: ', () => {
     user.setPassword('expiringToken');
     user.set('email', 'user@example.com');
     await user.signUp();
-
+    await promise1;
     const config = Config.get('test');
     const [userBeforeRequest] = await config.database.find('_User', {
       username: 'resends_verification_token',
@@ -1028,7 +913,7 @@ describe('Email Verification Token Expiration: ', () => {
         'Content-Type': 'application/json',
       },
     });
-    await jasmine.timeout();
+    await promise2;
     expect(response.status).toBe(200);
     expect(sendVerificationEmailCallCount).toBe(2);
     expect(sendEmailOptions).toBeDefined();
@@ -1043,72 +928,58 @@ describe('Email Verification Token Expiration: ', () => {
     expect(userBeforeRequest._email_verify_token).toEqual(userAfterRequest._email_verify_token);
     expect(userBeforeRequest._email_verify_token_expires_at).toBeDefined();
     expect(userBeforeRequest._email_verify_token_expires_at).toEqual(userAfterRequest._email_verify_token_expires_at);
-    done();
   });
 
-  it_id('1ed9a6c2-bebc-4813-af30-4f4a212544c2')(it)('should not send a new verification email when a resend is requested and the user is VERIFIED', done => {
+  it_id('1ed9a6c2-bebc-4813-af30-4f4a212544c2')(it)('should not send a new verification email when a resend is requested and the user is VERIFIED', async () => {
     const user = new Parse.User();
     let sendEmailOptions;
     let sendVerificationEmailCallCount = 0;
+    const sendPromise = resolvingPromise();
     const emailAdapter = {
       sendVerificationEmail: options => {
         sendEmailOptions = options;
         sendVerificationEmailCallCount++;
+        sendPromise.resolve();
       },
       sendPasswordResetEmail: () => Promise.resolve(),
       sendMail: () => {},
     };
-    reconfigureServer({
+    await reconfigureServer({
       appName: 'emailVerifyToken',
       verifyUserEmails: true,
       emailAdapter: emailAdapter,
       emailVerifyTokenValidityDuration: 5, // 5 seconds
       publicServerURL: 'http://localhost:8378/1',
-    })
-      .then(() => {
-        user.setUsername('no_new_verification_token_once_verified');
-        user.setPassword('expiringToken');
-        user.set('email', 'user@parse.com');
-        return user.signUp();
-      })
-      .then(() => jasmine.timeout())
-      .then(() => {
-        return request({
-          url: sendEmailOptions.link,
-          followRedirects: false,
-        }).then(response => {
-          expect(response.status).toEqual(302);
-        });
-      })
-      .then(() => {
-        expect(sendVerificationEmailCallCount).toBe(1);
+    });
+    user.setUsername('no_new_verification_token_once_verified');
+    user.setPassword('expiringToken');
+    user.set('email', 'user@parse.com');
+    await user.signUp();
+    await sendPromise;
+    let response = await request({
+      url: sendEmailOptions.link,
+      followRedirects: false,
+    });
+    expect(response.status).toEqual(302);
+    expect(sendVerificationEmailCallCount).toBe(1);
 
-        return request({
-          url: 'http://localhost:8378/1/verificationEmailRequest',
-          method: 'POST',
-          body: {
-            email: 'user@parse.com',
-          },
-          headers: {
-            'X-Parse-Application-Id': Parse.applicationId,
-            'X-Parse-REST-API-Key': 'rest',
-            'Content-Type': 'application/json',
-          },
-        })
-          .then(fail, res => res)
-          .then(response => {
-            expect(response.status).toBe(400);
-            expect(sendVerificationEmailCallCount).toBe(1);
-            done();
-          });
-      })
-      .catch(error => {
-        jfail(error);
-        done();
-      });
+    response = await request({
+      url: 'http://localhost:8378/1/verificationEmailRequest',
+      method: 'POST',
+      body: {
+        email: 'user@parse.com',
+      },
+      headers: {
+        'X-Parse-Application-Id': Parse.applicationId,
+        'X-Parse-REST-API-Key': 'rest',
+        'Content-Type': 'application/json',
+      },
+    }).then(fail, res => res);
+    expect(response.status).toBe(400);
+    expect(sendVerificationEmailCallCount).toBe(1);
   });
 
-  it('should not send a new verification email if this user does not exist', done => {
+  it('should not send a new verification email if this user does not exist', async () => {
     let sendEmailOptions;
     let sendVerificationEmailCallCount = 0;
     const emailAdapter = {
@@ -1119,42 +990,34 @@ describe('Email Verification Token Expiration: ', () => {
       sendPasswordResetEmail: () => Promise.resolve(),
       sendMail: () => {},
     };
-    reconfigureServer({
+    await reconfigureServer({
       appName: 'emailVerifyToken',
       verifyUserEmails: true,
       emailAdapter: emailAdapter,
       emailVerifyTokenValidityDuration: 5, // 5 seconds
       publicServerURL: 'http://localhost:8378/1',
+    });
+    const response = await request({
+      url: 'http://localhost:8378/1/verificationEmailRequest',
+      method: 'POST',
+      body: {
+        email: 'user@parse.com',
+      },
+      headers: {
+        'X-Parse-Application-Id': Parse.applicationId,
+        'X-Parse-REST-API-Key': 'rest',
+        'Content-Type': 'application/json',
+      },
     })
-      .then(() => {
-        return request({
-          url: 'http://localhost:8378/1/verificationEmailRequest',
-          method: 'POST',
-          body: {
-            email: 'user@parse.com',
-          },
-          headers: {
-            'X-Parse-Application-Id': Parse.applicationId,
-            'X-Parse-REST-API-Key': 'rest',
-            'Content-Type': 'application/json',
-          },
-        })
-          .then(fail)
-          .catch(response => response)
-          .then(response => {
-            expect(response.status).toBe(400);
-            expect(sendVerificationEmailCallCount).toBe(0);
-            expect(sendEmailOptions).not.toBeDefined();
-            done();
-          });
-      })
-      .catch(error => {
-        jfail(error);
-        done();
-      });
+      .then(fail)
+      .catch(response => response);
+      
+    expect(response.status).toBe(400);
+    expect(sendVerificationEmailCallCount).toBe(0);
+    expect(sendEmailOptions).not.toBeDefined();
   });
 
-  it('should fail if no email is supplied', done => {
+  it('should fail if no email is supplied', async () => {
     let sendEmailOptions;
     let sendVerificationEmailCallCount = 0;
     const emailAdapter = {
@@ -1165,41 +1028,31 @@ describe('Email Verification Token Expiration: ', () => {
       sendPasswordResetEmail: () => Promise.resolve(),
       sendMail: () => {},
     };
-    reconfigureServer({
+    await reconfigureServer({
       appName: 'emailVerifyToken',
       verifyUserEmails: true,
       emailAdapter: emailAdapter,
       emailVerifyTokenValidityDuration: 5, // 5 seconds
       publicServerURL: 'http://localhost:8378/1',
-    })
-      .then(() => {
-        request({
-          url: 'http://localhost:8378/1/verificationEmailRequest',
-          method: 'POST',
-          body: {},
-          headers: {
-            'X-Parse-Application-Id': Parse.applicationId,
-            'X-Parse-REST-API-Key': 'rest',
-            'Content-Type': 'application/json',
-          },
-        })
-          .then(fail, response => response)
-          .then(response => {
-            expect(response.status).toBe(400);
-            expect(response.data.code).toBe(Parse.Error.EMAIL_MISSING);
-            expect(response.data.error).toBe('you must provide an email');
-            expect(sendVerificationEmailCallCount).toBe(0);
-            expect(sendEmailOptions).not.toBeDefined();
-            done();
-          });
-      })
-      .catch(error => {
-        jfail(error);
-        done();
-      });
+    });
+    const response = await request({
+      url: 'http://localhost:8378/1/verificationEmailRequest',
+      method: 'POST',
+      body: {},
+      headers: {
+        'X-Parse-Application-Id': Parse.applicationId,
+        'X-Parse-REST-API-Key': 'rest',
+        'Content-Type': 'application/json',
+      },
+    }).then(fail, response => response);
+    expect(response.status).toBe(400);
+    expect(response.data.code).toBe(Parse.Error.EMAIL_MISSING);
+    expect(response.data.error).toBe('you must provide an email');
+    expect(sendVerificationEmailCallCount).toBe(0);
+    expect(sendEmailOptions).not.toBeDefined();
   });
 
-  it('should fail if email is not a string', done => {
+  it('should fail if email is not a string', async () => {
     let sendEmailOptions;
     let sendVerificationEmailCallCount = 0;
     const emailAdapter = {
@@ -1210,144 +1063,104 @@ describe('Email Verification Token Expiration: ', () => {
       sendPasswordResetEmail: () => Promise.resolve(),
       sendMail: () => {},
     };
-    reconfigureServer({
+    await reconfigureServer({
       appName: 'emailVerifyToken',
       verifyUserEmails: true,
       emailAdapter: emailAdapter,
       emailVerifyTokenValidityDuration: 5, // 5 seconds
       publicServerURL: 'http://localhost:8378/1',
-    })
-      .then(() => {
-        request({
-          url: 'http://localhost:8378/1/verificationEmailRequest',
-          method: 'POST',
-          body: { email: 3 },
-          headers: {
-            'X-Parse-Application-Id': Parse.applicationId,
-            'X-Parse-REST-API-Key': 'rest',
-            'Content-Type': 'application/json',
-          },
-        })
-          .then(fail, res => res)
-          .then(response => {
-            expect(response.status).toBe(400);
-            expect(response.data.code).toBe(Parse.Error.INVALID_EMAIL_ADDRESS);
-            expect(response.data.error).toBe('you must provide a valid email string');
-            expect(sendVerificationEmailCallCount).toBe(0);
-            expect(sendEmailOptions).not.toBeDefined();
-            done();
-          });
-      })
-      .catch(error => {
-        jfail(error);
-        done();
-      });
+    });
+    const response = await request({
+      url: 'http://localhost:8378/1/verificationEmailRequest',
+      method: 'POST',
+      body: { email: 3 },
+      headers: {
+        'X-Parse-Application-Id': Parse.applicationId,
+        'X-Parse-REST-API-Key': 'rest',
+        'Content-Type': 'application/json',
+      },
+    }).then(fail, res => res);
+    expect(response.status).toBe(400);
+    expect(response.data.code).toBe(Parse.Error.INVALID_EMAIL_ADDRESS);
+    expect(response.data.error).toBe('you must provide a valid email string');
+    expect(sendVerificationEmailCallCount).toBe(0);
+    expect(sendEmailOptions).not.toBeDefined();
   });
 
-  it('client should not see the _email_verify_token_expires_at field', done => {
+  it('client should not see the _email_verify_token_expires_at field', async () => {
     const user = new Parse.User();
     let sendEmailOptions;
+    const sendPromise = resolvingPromise();
     const emailAdapter = {
       sendVerificationEmail: options => {
         sendEmailOptions = options;
+        sendPromise.resolve();
       },
       sendPasswordResetEmail: () => Promise.resolve(),
       sendMail: () => {},
     };
-    reconfigureServer({
+    await reconfigureServer({
       appName: 'emailVerifyToken',
       verifyUserEmails: true,
       emailAdapter: emailAdapter,
       emailVerifyTokenValidityDuration: 5, // 5 seconds
       publicServerURL: 'http://localhost:8378/1',
-    })
-      .then(() => {
-        user.setUsername('testEmailVerifyTokenValidity');
-        user.setPassword('expiringToken');
-        user.set('email', 'user@parse.com');
-        return user.signUp();
-      })
-      .then(() => {
-        user
-          .fetch()
-          .then(() => {
-            expect(user.get('emailVerified')).toEqual(false);
-            expect(typeof user.get('_email_verify_token_expires_at')).toBe('undefined');
-            expect(sendEmailOptions).toBeDefined();
-            done();
-          })
-          .catch(error => {
-            jfail(error);
-            done();
-          });
-      })
-      .catch(error => {
-        jfail(error);
-        done();
-      });
+    });
+    user.setUsername('testEmailVerifyTokenValidity');
+    user.setPassword('expiringToken');
+    user.set('email', 'user@parse.com');
+    await user.signUp();
+    await sendPromise;
+    await user.fetch();
+    expect(user.get('emailVerified')).toEqual(false);
+    expect(typeof user.get('_email_verify_token_expires_at')).toBe('undefined');
+    expect(sendEmailOptions).toBeDefined();
   });
 
-  it_id('b082d387-4974-4d45-a0d9-0c85ca2d7cbf')(it)('emailVerified should be set to false after changing from an already verified email', done => {
-    const user = new Parse.User();
+  it_id('b082d387-4974-4d45-a0d9-0c85ca2d7cbf')(it)('emailVerified should be set to false after changing from an already verified email', async () => {
+    let user = new Parse.User();
     let sendEmailOptions;
+    const sendPromise = resolvingPromise();
     const emailAdapter = {
       sendVerificationEmail: options => {
         sendEmailOptions = options;
+        sendPromise.resolve();
       },
       sendPasswordResetEmail: () => Promise.resolve(),
       sendMail: () => {},
     };
-    reconfigureServer({
+    await reconfigureServer({
       appName: 'emailVerifyToken',
       verifyUserEmails: true,
       emailAdapter: emailAdapter,
       emailVerifyTokenValidityDuration: 5, // 5 seconds
       publicServerURL: 'http://localhost:8378/1',
-    })
-      .then(() => {
-        user.setUsername('testEmailVerifyTokenValidity');
-        user.setPassword('expiringToken');
-        user.set('email', 'user@parse.com');
-        return user.signUp();
-      })
-      .then(() => jasmine.timeout())
-      .then(() => {
-        request({
-          url: sendEmailOptions.link,
-          followRedirects: false,
-        }).then(response => {
-          expect(response.status).toEqual(302);
-          Parse.User.logIn('testEmailVerifyTokenValidity', 'expiringToken')
-            .then(user => {
-              expect(typeof user).toBe('object');
-              expect(user.get('emailVerified')).toBe(true);
+    });
+    user.setUsername('testEmailVerifyTokenValidity');
+    user.setPassword('expiringToken');
+    user.set('email', 'user@parse.com');
+    await user.signUp();
+    await sendPromise;
+    let response = await request({
+      url: sendEmailOptions.link,
+      followRedirects: false,
+    });
+    expect(response.status).toEqual(302);
+    user = await Parse.User.logIn('testEmailVerifyTokenValidity', 'expiringToken');
+    expect(typeof user).toBe('object');
+    expect(user.get('emailVerified')).toBe(true);
 
-              user.set('email', 'newEmail@parse.com');
-              return user.save();
-            })
-            .then(() => user.fetch())
-            .then(user => {
-              expect(typeof user).toBe('object');
-              expect(user.get('email')).toBe('newEmail@parse.com');
-              expect(user.get('emailVerified')).toBe(false);
+    user.set('email', 'newEmail@parse.com');
+    await user.save();
+    await user.fetch();
+    expect(typeof user).toBe('object');
+    expect(user.get('email')).toBe('newEmail@parse.com');
+    expect(user.get('emailVerified')).toBe(false);
 
-              request({
-                url: sendEmailOptions.link,
-                followRedirects: false,
-              }).then(response => {
-                expect(response.status).toEqual(302);
-                done();
-              });
-            })
-            .catch(error => {
-              jfail(error);
-              done();
-            });
-        });
-      })
-      .catch(error => {
-        jfail(error);
-        done();
-      });
+    response = await request({
+      url: sendEmailOptions.link,
+      followRedirects: false,
+    });
+    expect(response.status).toEqual(302);
   });
 });
diff --git a/spec/UserController.spec.js b/spec/UserController.spec.js
index 31d5051960..1993dde079 100644
--- a/spec/UserController.spec.js
+++ b/spec/UserController.spec.js
@@ -1,6 +1,7 @@
 const emailAdapter = require('./support/MockEmailAdapter');
 const Config = require('../lib/Config');
 const Auth = require('../lib/Auth');
+const { resolvingPromise } = require('../lib/TestUtils');
 
 describe('UserController', () => {
   describe('sendVerificationEmail', () => {
@@ -17,8 +18,10 @@ describe('UserController', () => {
         });
 
         let emailOptions;
+        const sendPromise = resolvingPromise();
         emailAdapter.sendVerificationEmail = options => {
           emailOptions = options;
+          sendPromise.resolve();
         };
 
         const username = 'verificationUser';
@@ -27,6 +30,7 @@ describe('UserController', () => {
         user.setPassword('pass');
         user.setEmail('verification@example.com');
         await user.signUp();
+        await sendPromise;
 
         const config = Config.get('test');
         const rawUser = await config.database.find('_User', { username }, {}, Auth.maintenance(config));
@@ -52,8 +56,10 @@ describe('UserController', () => {
         });
 
         let emailOptions;
+        const sendPromise = resolvingPromise();
         emailAdapter.sendVerificationEmail = options => {
           emailOptions = options;
+          sendPromise.resolve();
         };
 
         const username = 'verificationUser';
@@ -62,6 +68,7 @@ describe('UserController', () => {
         user.setPassword('pass');
         user.setEmail('verification@example.com');
         await user.signUp();
+        await sendPromise;
 
         const config = Config.get('test');
         const rawUser = await config.database.find('_User', { username }, {}, Auth.maintenance(config));
diff --git a/spec/support/CurrentSpecReporter.js b/spec/support/CurrentSpecReporter.js
index 27e4942095..4f4968fdcb 100755
--- a/spec/support/CurrentSpecReporter.js
+++ b/spec/support/CurrentSpecReporter.js
@@ -14,16 +14,6 @@ const flakyTests = [
   "ParseLiveQuery handle invalid websocket payload length",
   // Unhandled promise rejection: TypeError: message.split is not a function
   "rest query query internal field",
-  // TypeError: Cannot read properties of undefined (reading 'link')
-  "UserController sendVerificationEmail parseFrameURL not provided uses publicServerURL",
-  // TypeError: Cannot read properties of undefined (reading 'link')
-  "UserController sendVerificationEmail parseFrameURL provided uses parseFrameURL and includes the destination in the link parameter",
-  // Expected undefined to be defined
-  "Email Verification Token Expiration:  sets the _email_verify_token_expires_at and _email_verify_token fields after user SignUp",
-  // Expected 0 to be 1.
-  "Email Verification Token Expiration:  should send a new verification email when a resend is requested and the user is UNVERIFIED",
-  // Expected 0 to be 1.
-  "Email Verification Token Expiration:  should match codes with emailVerifyTokenReuseIfValid",
 ];
 
 /** The minimum execution time in seconds for a test to be considered slow. */


Reviews:
user: login: mtrezza
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-07T01:59:59Z
user: login: dplewis
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-14T04:41:48Z
user: login: mtrezza
body: Is this ready for merge? Re-running CI to see whether still flaky...
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-14T17:41:21Z

Review comments:
diff_hunk: @@ -3,295 +3,230 @@
 const Auth = require('../lib/Auth');
 const Config = require('../lib/Config');
 const request = require('../lib/request');
+const { resolvingPromise, sleep } = require('../lib/TestUtils');
 const MockEmailAdapterWithOptions = require('./support/MockEmailAdapterWithOptions');
 
-describe('Email Verification Token Expiration: ', () => {
-  it('show the invalid verification link page, if the user clicks on the verify email link after the email verify token expires', done => {
+describe('Email Verification Token Expiration:', () => {
+  it('show the invalid verification link page, if the user clicks on the verify email link after the email verify token expires', async ()  => {
     const user = new Parse.User();
     let sendEmailOptions;
+    const sendPromise = resolvingPromise();
     const emailAdapter = {
       sendVerificationEmail: options => {
         sendEmailOptions = options;
+        sendPromise.resolve();
       },
       sendPasswordResetEmail: () => Promise.resolve(),
       sendMail: () => {},
     };
-    reconfigureServer({
+    await reconfigureServer({
       appName: 'emailVerifyToken',
       verifyUserEmails: true,
       emailAdapter: emailAdapter,
       emailVerifyTokenValidityDuration: 0.5, // 0.5 second
       publicServerURL: 'http://localhost:8378/1',
-    })
-      .then(() => {
-        user.setUsername('testEmailVerifyTokenValidity');
-        user.setPassword('expiringToken');
-        user.set('email', 'user@parse.com');
-        return user.signUp();
-      })
-      .then(() => {
-        // wait for 1 second - simulate user behavior to some extent
-        setTimeout(() => {
-          expect(sendEmailOptions).not.toBeUndefined();
+    });
+    user.setUsername('testEmailVerifyTokenValidity');
+    user.setPassword('expiringToken');
+    user.set('email', 'user@parse.com');
+    await user.signUp();
+    await sendPromise;
+    // wait for 1 second - simulate user behavior to some extent
+    await sleep(1000);
path: spec/EmailVerificationToken.spec.js
user: login: mtrezza
body: Can all these sleeps be reduced to maybe 100ms or less? I think it makes sense to introduce a global var or sleep method for this specific purpose like `const emailSendSleepDuration = 100;`, so we can manage them easier.
created_at: 2025-04-07T01:56:23Z
author_association: MEMBER
diff_hunk: @@ -3,295 +3,230 @@
 const Auth = require('../lib/Auth');
 const Config = require('../lib/Config');
 const request = require('../lib/request');
+const { resolvingPromise, sleep } = require('../lib/TestUtils');
 const MockEmailAdapterWithOptions = require('./support/MockEmailAdapterWithOptions');
 
-describe('Email Verification Token Expiration: ', () => {
-  it('show the invalid verification link page, if the user clicks on the verify email link after the email verify token expires', done => {
+describe('Email Verification Token Expiration:', () => {
+  it('show the invalid verification link page, if the user clicks on the verify email link after the email verify token expires', async ()  => {
     const user = new Parse.User();
     let sendEmailOptions;
+    const sendPromise = resolvingPromise();
     const emailAdapter = {
       sendVerificationEmail: options => {
         sendEmailOptions = options;
+        sendPromise.resolve();
       },
       sendPasswordResetEmail: () => Promise.resolve(),
       sendMail: () => {},
     };
-    reconfigureServer({
+    await reconfigureServer({
       appName: 'emailVerifyToken',
       verifyUserEmails: true,
       emailAdapter: emailAdapter,
       emailVerifyTokenValidityDuration: 0.5, // 0.5 second
       publicServerURL: 'http://localhost:8378/1',
-    })
-      .then(() => {
-        user.setUsername('testEmailVerifyTokenValidity');
-        user.setPassword('expiringToken');
-        user.set('email', 'user@parse.com');
-        return user.signUp();
-      })
-      .then(() => {
-        // wait for 1 second - simulate user behavior to some extent
-        setTimeout(() => {
-          expect(sendEmailOptions).not.toBeUndefined();
+    });
+    user.setUsername('testEmailVerifyTokenValidity');
+    user.setPassword('expiringToken');
+    user.set('email', 'user@parse.com');
+    await user.signUp();
+    await sendPromise;
+    // wait for 1 second - simulate user behavior to some extent
+    await sleep(1000);
path: spec/EmailVerificationToken.spec.js
user: login: dplewis
body: There are only 2 sleep calls and these require to be 1 second per the original comment. 100ms causes them to fail
created_at: 2025-04-14T04:41:48Z
author_association: MEMBER

PR comments:
user: login: parse-github-assistant[bot]
created_at: 2025-04-02T18:13:22Z
body: 
🚀 Thanks for opening this pull request! 

<!-- parse-issue-bot-meta-tag-id {"suggestPr":false,"thanksFeature":false,"thanksPr":false} -->


author_association: NONE
user: login: codecov[bot]
created_at: 2025-04-02T18:20:57Z
body: ## [Codecov](https://app.codecov.io/gh/parse-community/parse-server/pull/9692?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=parse-community) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 93.01%. Comparing base [(`e86718f`)](https://app.codecov.io/gh/parse-community/parse-server/commit/e86718fc59c7c8e6f3c6abd0feb7d1a68ca76c23?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=parse-community) to head [(`57a9867`)](https://app.codecov.io/gh/parse-community/parse-server/commit/57a986726a802bbd8951e4e15a092f1b68100e14?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=parse-community).
> Report is 10 commits behind head on alpha.

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##            alpha    #9692   +/-   ##
=======================================
  Coverage   93.01%   93.01%           
=======================================
  Files         187      187           
  Lines       15081    15081           
  Branches      174      174           
=======================================
  Hits        14028    14028           
  Misses       1041     1041           
  Partials       12       12           
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/parse-community/parse-server/pull/9692?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=parse-community).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=parse-community).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: dplewis
created_at: 2025-04-14T17:46:57Z
body: This is ready to merge
author_association: MEMBER
user: login: parseplatformorg
created_at: 2025-05-01T10:11:02Z
body: 🎉 This change has been released in version [8.2.0](https://github.com/parse-community/parse-server/releases/tag/8.2.0)
author_association: CONTRIBUTOR
user: login: coderabbitai[bot]
created_at: 2025-05-01T10:11:29Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: failure by coderabbit.ai -->

> [!CAUTION]
> ## Review failed
> 
> The pull request is closed.

<!-- end of auto-generated comment: failure by coderabbit.ai -->
<!-- walkthrough_start -->

<details>
<summary>📝 Walkthrough</summary>

## Walkthrough

The changes refactor asynchronous test cases in `EmailVerificationToken.spec.js` and `UserController.spec.js` to use async/await syntax, replacing promise chains and done callbacks. Utilities like `resolvingPromise` and `sleep` are introduced for improved asynchronous flow control and timing. Additionally, several test names are removed from the `flakyTests` array in `CurrentSpecReporter.js`, specifically those related to email verification token expiration and verification email sending. No test logic or assertions are changed; only the asynchronous handling and test flakiness management are updated.

## Changes

| File(s)                                              | Change Summary                                                                                                    |
|------------------------------------------------------|------------------------------------------------------------------------------------------------------------------|
| spec/EmailVerificationToken.spec.js                  | Refactored all asynchronous test cases to use async/await, introduced `resolvingPromise` and `sleep` utilities.  |
| spec/UserController.spec.js                          | Updated email verification tests to await asynchronous email sending using `resolvingPromise`.                    |
| spec/support/CurrentSpecReporter.js                  | Removed six test entries related to email verification from the `flakyTests` array.                               |

## Assessment against linked issues

| Objective                                                                                                                      | Addressed | Explanation                                                                                 |
|--------------------------------------------------------------------------------------------------------------------------------|-----------|---------------------------------------------------------------------------------------------|
| Fix flaky test `UserController sendVerificationEmail parseFrameURL not provided uses publicServerURL` (#9274)                  | ✅        |                                                                                             |
| Fix flaky test `UserController sendVerificationEmail parseFrameURL provided uses parseFrameURL and includes the destination...` (#9275) | ✅        |                                                                                             |
| Fix flaky test `Email Verification Token Expiration: sets the _email_verify_token_expires_at and _email_verify_token fields...` (#9276) | ✅        |                                                                                             |

</details>

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- DwQgtGAEAqAWCWBnSTIEMB26CuAXA9mAOYCmGJATmriQCaQDG+Ats2bgFyQAOFk+AIwBWJBrngA3EsgEBPRvlqU0AgfFwA6NPEgQAfACgjoCEYDEZyAAUASpETZWaCrKNwSPbABsvkCiQBHbGlcSHFcLzpIACIGeC4AMXgAD0gAMy80AGt5AANEMloANUp4NPgGanh8DABRZm0vXLCQxGjIAHc0ZAcBZnUaejlIbAK+Wm5IjtRu6zsMRwFKSABOADYVgCZ+LFxYDytnAsgAZUopPn9ufER1fBcNGH3IBvgsJAcPNFpaf0QC+ioPbUMLPfKFEoUMoVKo1eqNZppbAYMTVLAUZHIN6gjwCNAMLJECj4ZG0AA0jDQozeRBaiFwyAIkCW6UyOWZoipxz2JHktHwkAw+FCXXU6XuOJ4xIY0kZAqYzEmJBojzgqFsjC8N2koP8Hn8mUGKH+wUQXDMWwA7AAWCkWzaWgCsFMw9HtlrWGjczzQ3F4+HxsDC2TIKBRXmwSmQ1IwtKUaUo/no/v6BTlUvw3EoXnksFdkXQiFkKNgxKFow5eYk1QoFP8zHw1djIww5Bl/2ceSE3X65A04jYJNwzUqPkQLow9H8aXxBChzZ5dIZYQFoy+RZRAHo0KLQhvcGhUmkJUtcDQ+AxMlDcPJXX4SF54Cp4I+b6r9scGHnYzrtMwV/YB5qI+ABeHiLjQ9IyPIZAOPOtI8vIu7inwi4kK8vgFJONIZu26blBgSBBksx7+IWYziM2Q4KtIXoGBYkAAMIsGwGDLg4TguN6Bx2P4MqSFEvbwA0vgKqxDLvl8eCwPcFITFMSAUm8tAwkawKhIuX6YKQyDOPqJDfPITJsBQpCPCc2ACAUQTsDmFKLtwRwkJM1CkQ2pktGg/6YOWKJROpkrlKkeYyCQoZvJekZRNii6HBQxxnBQFz3pE3QeBctw1JAAAcGibBoAAMklkR0lAeEK6TYBQPLjEgDCjJlGDIBK/g2VBKGMN+OmCsK0W7M8YnsIgdH6MY4BQIU/BpDgBDEGQyhGoNbFcLw/DCKI4hSNBChKFQqjqFoOijSYUBqliulYFSs2kOQVCLSx7BcFQHT2I4DQuMy8hMLtKhqJo2i6GAhhjaYBiIFmDCbvCL6QtClTiDU0D4FkZAaODogaEIZoGNEuP0ZYACCACSc23dQUQce98j4NNWk/og3FLq96geKRkDoY0kAXHDsK7MjoYkMk3DwHdaKdLM06zvc/mrsc3TFpDO7aHuxYHqkbz0gZ9A0xmqYeFpGudOoQb8uQlI+HiBLDZAtSBkzQovUr6i6RuX5liSzVZqLNQ9NgX6FvY5zLHxNTlEQVW8xSa58LcRAYGA2DcBSHMvoH2GxhO9AABLQNAVj3m1DJKYqxJNrS/jfM+r63pOrL4C9TBscSXiSZAuR/PgXhl1YxJ680eAvuo8ioG8uDErQfsy+gyGaSwSoI1gOvyyW7sVinmGFDho5eJbWShaRHj+jKdA0q3+SRM5/fiNX94ue2bcFLg0DCSQQ6IhKSiZLIPTCd4VQLvzLAgthbe0XnwaO4sxQDlokYAmPglxYgwBITuZd2YYS5qUco8MxYEBRkAoWItI4rjwfecgXRfBZXXp1bSJA6zSEmtzLBvM0Gc1aqaIu6Ba4SDQI+WgzCdZULeNwPAPUG4+yQDQNiOYRhy1dtuXcrdILLkYTBIWG0og5zzveFSfFQh/GuE1aQUcxgsJhpg+AFMDy4FGJnYhoZygPloIgyUfCgJpUeLUCgxI+DfloI+ZsqBbiKkfA4oY8h6yNhwsAx8cRQjw39gILUVtOH0FNvrHhXhxwpVkDhLKy8GBMxnC+KqMCDAADl5RdR1KVMiDQlAAQgiESAWoiAVH4HwboFE0SIAANw7GkbPJunc66O1rvk0sNQPaQF8f42kNSPANl2oRMCtBFGtAUGxN4wQAJMGSogGUGBnDVEQZFFSzZgEixlngrJ3DeF2KahSRuKkF48NMRvdORB7KAPvGuWxl4LFsS5kgeAwEh5TXuezAhoD0gWK8E4uiDE4Hnl5umRcShLzOBReC4B9wjQSmEYktp7B1AWIZgYKAFTqH0wAji6qUR8WWRiezTZN5IDoqvFi+ZLxvgeBiggZAKk0hpC9LjaIRgIBgCMOjSGABVMYzEhk+EoGjCGmNsaivxpAYmpMFoUzep2cFdMdKMyUZSNMHVwSTlhkwhe0MmhhjbnKygCqx6d0iBQFVGMsbNC5Ys6EU9YIlMLArCZ5ZkBUKwmc2kqB/Re2kU7QYjwCZtw7l3GkPcWBIBIM0VADAK5GhItLfgFwoRRrbuvAmfDuDnjRhCcx2C4QYVyI8ImTV4D1MXI2RM7alBYDYHsRQ9lngpizcae8iAUHRVYCfcm8a0jnkpNWqqOE0LoMzAva2BN53LEXBA2OTURhJ0lKah209laMn5brUdTJw6YrYmFSUEbN7NhaW0hU88dSFrIkfMKZbph7HIpQDd75c1VPFrpP0j4oiN1uJrKRt4803F0vAg0JBuGAtNZUNMjxKXCn2HwV9BSJSdKA90zoZV0BeHPHQOi5hCZUYWmRpkaLRAco3dioWuL6V8AJUy4l4hpBGEpUa6pFG6ngQFCxjFoDmrTVpXinjjKiUstJSKvG5LQbSs3A4P0uLNyMSqv4NiJwIY2Gclxj1WMOA4zxoikmN1dX0EpganWImyXuCpaQe8DYtr2BSKybIRkmnsChDqNIvdJS5AyIF6ArRmjOCoMPLA+QIbacTtcaq+nDPsBM6IMzGWa3eskscCJUh6AhdJQoLx0gDH0GY+sg05M6sCmiE6igLrm7urTsUetvM7XtDvNEO1kBrWqTFkjEhtQoW83aAFXgaHqijGkXmegS1lzKTG82aVcN2ZePuM1PgRTIjlbUWIFFOGBR4eWN9DwEpCNjp4dR2gfSagDOeI+ek4LovsiUemFk/gx4WPoCFDkoZEAyTpeQMJ3nIkLg/B4bbWD7ZeV/LXHkItyL4DiE1hQ062LDSMHRrVDGZMNOeOyzF7GBGcbpdrRThKCn8dJUJmoJA1Nio08DE6zLtbTSuoQBzd1oMPWWn4Hcr1OLyGGDdvaf1DqAz0EAA= -->

<!-- internal state end -->

---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 788532bd39bb4230c1cc5ddfb599575d42ff0f6e and 57a986726a802bbd8951e4e15a092f1b68100e14.

</details>

<details>
<summary>📒 Files selected for processing (3)</summary>

* `spec/EmailVerificationToken.spec.js` (18 hunks)
* `spec/UserController.spec.js` (5 hunks)
* `spec/support/CurrentSpecReporter.js` (0 hunks)

</details>

</details>
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=parse-community/parse-server&utm_content=9692):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE