From e83761904cf0dea91f411bb4ca4815660cf25c6b Mon Sep 17 00:00:00 2001 From: Mahesh Makani Date: Fri, 14 Jul 2023 09:09:43 +0100 Subject: [PATCH] chore: use `@guardian/prettier` config and rewrite files --- .editorconfig | 2 - .eslintrc.json | 106 +- .prettierrc | 7 +- .ratelimit.example.json | 26 +- .storybook/clientStateDecorator.tsx | 20 +- .storybook/main.js | 126 +- .storybook/preview-head.html | 6 +- .storybook/preview.js | 122 +- .swcrc.config.js | 62 +- .vscode/extensions.json | 14 +- .vscode/settings.json | 12 +- artifact.json | 28 +- babel.config.js | 30 +- cypress.config.ts | 46 +- .../error/authentication-failed.json | 16 +- .../okta-responses/error/no-permission.json | 16 +- .../okta-responses/error/user-exists.json | 24 +- .../okta-responses/error/user-not-found.json | 16 +- .../success/auth-reset-password.json | 38 +- .../success/forgot-password-email-factor.json | 14 +- .../success/reset-password.json | 8 +- .../okta-responses/success/social-user.json | 26 +- .../okta-responses/success/token.json | 10 +- .../fixtures/okta-responses/success/user.json | 26 +- .../success/valid-user-groups.json | 20 +- .../okta-responses/success/valid-user.json | 38 +- .../success/verify-recovery-token.json | 38 +- cypress/fixtures/users.json | 12 +- .../integration/ete-okta/jobs_terms.4.cy.ts | 434 +-- .../ete-okta/onboarding_flow.5.cy.ts | 324 +- .../ete-okta/post_sign_in_prompt.5.cy.ts | 198 +- .../ete-okta/reauthenticate.4.cy.ts | 188 +- .../integration/ete-okta/registration.2.cy.ts | 2664 ++++++++--------- .../ete-okta/reset_password.3.cy.ts | 1038 +++---- cypress/integration/ete-okta/sign_in.1.cy.ts | 820 ++--- cypress/integration/ete-okta/sign_out.5.cy.ts | 106 +- .../ete/change_email/change_email.2.cy.ts | 58 +- .../ete/consent_token/consent_token.2.cy.ts | 86 +- .../ete/registration/register.2.cy.ts | 614 ++-- .../registration/register_email_sent.3.cy.ts | 396 +-- .../ete/reset_password/reset_password.4.cy.ts | 406 +-- .../integration/ete/sign_in/sign_in.1.cy.ts | 100 +- .../integration/ete/sign_out/sign_out.3.cy.ts | 128 +- .../integration/ete/terms/jobs_terms.4.cy.ts | 480 +-- .../ete/unsubscribe/unsubscribe.3.cy.ts | 402 +-- .../integration/mocked-okta/rateLimit.1.cy.ts | 302 +- .../mocked-okta/registerController.1.cy.ts | 400 +-- .../mocked-okta/resendEmailController.3.cy.ts | 978 +++--- .../resetPasswordController.4.cy.ts | 1302 ++++---- .../mocked-okta/signInController.2.cy.ts | 142 +- .../integration/mocked/change_email.5.cy.ts | 88 +- .../mocked/change_password.3.cy.ts | 648 ++-- .../integration/mocked/consent_token.3.cy.ts | 74 +- .../integration/mocked/email_input.2.cy.ts | 62 +- .../mocked/email_templates.5.cy.ts | 40 +- .../mocked/okta_change_password.2.cy.ts | 590 ++-- .../integration/mocked/okta_register.3.cy.ts | 316 +- .../mocked/okta_send_reset_password.4.cy.ts | 526 ++-- .../integration/mocked/okta_sign_in.6.cy.ts | 472 +-- .../mocked/onboarding_flow.1.cy.ts | 2034 ++++++------- .../mocked/post_sign_in_prompt.3.cy.ts | 260 +- cypress/integration/mocked/register.6.cy.ts | 374 +-- .../integration/mocked/reset_password.5.cy.ts | 152 +- .../integration/mocked/set_password.5.cy.ts | 496 +-- cypress/integration/mocked/sign_in.2.cy.ts | 528 ++-- cypress/integration/mocked/sign_out.2.cy.ts | 114 +- .../integration/mocked/unsubscribe.5.cy.ts | 104 +- .../integration/mocked/verify_email.4.cy.ts | 548 ++-- cypress/integration/mocked/welcome.4.cy.ts | 636 ++-- cypress/integration/shared/sign_in.shared.ts | 838 +++--- cypress/support/commands.ts | 52 +- cypress/support/commands/getEmailDetails.ts | 94 +- cypress/support/commands/lastPayloadIs.ts | 22 +- cypress/support/commands/lastPayloadsAre.ts | 28 +- cypress/support/commands/manageCmp.ts | 56 +- cypress/support/commands/mockAll.ts | 46 +- cypress/support/commands/mockNext.ts | 32 +- cypress/support/commands/mockPattern.ts | 46 +- cypress/support/commands/mockPurge.ts | 14 +- cypress/support/commands/network.ts | 38 +- cypress/support/commands/setAdFreeCookie.ts | 20 +- .../commands/setEncryptedStateCookie.ts | 26 +- cypress/support/commands/setMvtId.ts | 18 +- cypress/support/commands/testUser.ts | 916 +++--- cypress/support/cypress-axe.ts | 54 +- cypress/support/e2e.ts | 20 +- cypress/support/geolocation.ts | 10 +- cypress/support/idapi/auth.ts | 6 +- cypress/support/idapi/consent.ts | 190 +- cypress/support/idapi/cookie.ts | 42 +- cypress/support/idapi/guest.ts | 36 +- cypress/support/idapi/newsletter.ts | 182 +- cypress/support/idapi/user.ts | 18 +- cypress/support/idapi/verify_email.ts | 44 +- cypress/support/okta/userStatuses.ts | 12 +- .../pages/onboarding/communications_page.ts | 18 +- .../pages/onboarding/newsletters_page.ts | 44 +- .../pages/onboarding/onboarding_page.ts | 80 +- .../support/pages/onboarding/review_page.ts | 36 +- .../pages/onboarding/your_data_page.ts | 50 +- cypress/support/pages/reset_password_page.ts | 50 +- cypress/support/pages/verify_email.ts | 46 +- cypress/support/types/ResponseFixture.ts | 4 +- cypress/tsconfig.json | 34 +- docs/development.md | 92 +- docs/rate-limit/token-bucket.md | 24 +- docs/setup.md | 16 +- jest.config.js | 56 +- package.json | 333 ++- scripts/banner.js | 2 +- scripts/check-node.js | 56 +- scripts/check-yarn.js | 38 +- scripts/ensure.js | 42 +- scripts/generate-rate-limit-schema.ts | 8 +- scripts/log.js | 34 +- scripts/okta/__tests__/getClientId.test.ts | 24 +- scripts/okta/__tests__/getRedirectUrl.test.ts | 362 +-- scripts/okta/__tests__/getRelayState.test.ts | 18 +- .../__tests__/getThirdPartyClientId.test.ts | 28 +- .../__tests__/getThirdPartyReturnUrl.test.ts | 44 +- scripts/okta/lib/helper.ts | 256 +- scripts/okta/login-default.html | 72 +- scripts/okta/okta-login.html | 30 +- scripts/okta/okta-login.ts | 8 +- scripts/okta/tsconfig.json | 30 +- scripts/okta/window.d.ts | 6 +- src/client/.well-known/assetlinks.json | 66 +- src/client/__tests__/EmailInput.test.tsx | 100 +- src/client/__tests__/MainForm.test.tsx | 528 ++-- src/client/__tests__/Registration.test.tsx | 164 +- src/client/__tests__/SignIn.test.tsx | 212 +- src/client/app.tsx | 98 +- src/client/components/ABTestDemo.tsx | 152 +- src/client/components/BackToTop.stories.tsx | 18 +- src/client/components/BackToTop.tsx | 100 +- src/client/components/ClientState.tsx | 34 +- .../components/CmpConsentStateHiddenInput.tsx | 22 +- src/client/components/ConsentCard.stories.tsx | 28 +- src/client/components/ConsentCard.tsx | 386 +-- .../ConsentCardOnboardingEmails.stories.tsx | 6 +- .../ConsentCardOnboardingEmails.tsx | 102 +- src/client/components/ConsentsForm.tsx | 54 +- src/client/components/ConsentsHeader.tsx | 16 +- src/client/components/ConsentsNavigation.tsx | 94 +- .../components/ConsentsSubHeader.stories.tsx | 70 +- src/client/components/ConsentsSubHeader.tsx | 340 +-- src/client/components/Container.stories.tsx | 136 +- src/client/components/Container.tsx | 84 +- src/client/components/CsrfFormField.tsx | 40 +- .../DetailedRecaptchaError.stories.tsx | 4 +- .../components/DetailedRecaptchaError.tsx | 32 +- src/client/components/EmailInput.stories.tsx | 16 +- src/client/components/EmailInput.tsx | 70 +- .../components/EnvelopeImage.stories.tsx | 4 +- src/client/components/EnvelopeImage.tsx | 120 +- src/client/components/ExternalLink.tsx | 12 +- src/client/components/Footer.stories.tsx | 72 +- src/client/components/Footer.tsx | 362 +-- src/client/components/GlobalError.stories.tsx | 34 +- src/client/components/GlobalError.tsx | 114 +- .../components/GlobalSuccess.stories.tsx | 6 +- src/client/components/GlobalSuccess.tsx | 82 +- src/client/components/Header.stories.tsx | 24 +- src/client/components/Header.tsx | 174 +- src/client/components/JobsLogo.stories.tsx | 24 +- src/client/components/JobsLogo.tsx | 60 +- .../components/MainBodyText.stories.tsx | 22 +- src/client/components/MainBodyText.tsx | 34 +- src/client/components/MainForm.stories.tsx | 100 +- src/client/components/MainForm.tsx | 510 ++-- .../components/NameInputField.stories.tsx | 8 +- src/client/components/NameInputField.tsx | 174 +- src/client/components/Nav.stories.tsx | 230 +- src/client/components/Nav.tsx | 392 +-- .../components/NoFacebookSupport.stories.tsx | 6 +- src/client/components/NoFacebookSupport.tsx | 54 +- .../components/PasswordForm.stories.tsx | 38 +- src/client/components/PasswordForm.tsx | 848 +++--- .../PasswordFormMainLayout.stories.tsx | 38 +- .../components/PasswordInput.stories.tsx | 30 +- src/client/components/PasswordInput.tsx | 288 +- .../components/RefTrackingFormFields.tsx | 14 +- .../components/SocialButtons.stories.tsx | 48 +- src/client/components/SocialButtons.tsx | 220 +- src/client/components/SubHeader.stories.tsx | 6 +- src/client/components/SubHeader.tsx | 54 +- src/client/components/Terms.stories.tsx | 20 +- src/client/components/Terms.tsx | 130 +- .../components/ToggleSwitchInput.stories.tsx | 12 +- src/client/components/ToggleSwitchInput.tsx | 212 +- src/client/layouts/ConsentsLayout.tsx | 86 +- src/client/layouts/Main.stories.tsx | 112 +- src/client/layouts/Main.tsx | 338 +-- src/client/lib/ErrorLink.ts | 10 +- src/client/lib/clientSideLogger.ts | 110 +- src/client/lib/consentsTracking.ts | 144 +- src/client/lib/fonts.ts | 188 +- .../lib/hooks/__tests__/useRecaptcha.test.tsx | 790 ++--- .../useRemoveEncryptedEmailParam.test.ts | 10 +- .../__tests__/utils/useRecaptchaTestUtils.ts | 60 +- src/client/lib/hooks/useAdFreeCookie.ts | 12 +- src/client/lib/hooks/useCmpConsent.ts | 18 +- src/client/lib/hooks/useInputValidityState.ts | 136 +- .../lib/hooks/useNameFieldInputError.ts | 44 +- .../lib/hooks/usePageLoadOphanInteraction.ts | 16 +- src/client/lib/hooks/useRecaptcha.tsx | 334 +-- src/client/lib/hooks/useRefTracking.ts | 14 +- .../hooks/useRemoveEncryptedEmailParam.tsx | 20 +- src/client/lib/ophan.ts | 42 +- src/client/models/ConsentImages.ts | 2 +- src/client/models/ConsentsPages.ts | 18 +- src/client/models/Font.ts | 42 +- src/client/models/Newsletter.ts | 72 +- src/client/models/Style.ts | 20 +- .../pages/ChangeEmailComplete.stories.tsx | 10 +- src/client/pages/ChangeEmailComplete.tsx | 64 +- src/client/pages/ChangeEmailCompletePage.tsx | 18 +- src/client/pages/ChangeEmailError.stories.tsx | 8 +- src/client/pages/ChangeEmailError.tsx | 40 +- src/client/pages/ChangeEmailErrorPage.tsx | 8 +- src/client/pages/ChangePassword.stories.tsx | 64 +- src/client/pages/ChangePassword.tsx | 58 +- .../pages/ChangePasswordComplete.stories.tsx | 48 +- src/client/pages/ChangePasswordComplete.tsx | 54 +- .../pages/ChangePasswordCompletePage.tsx | 22 +- src/client/pages/ChangePasswordPage.tsx | 100 +- .../pages/ConsentsCommunication.stories.tsx | 36 +- src/client/pages/ConsentsCommunication.tsx | 58 +- .../pages/ConsentsCommunicationPage.tsx | 8 +- .../pages/ConsentsConfirmation.stories.tsx | 182 +- src/client/pages/ConsentsConfirmation.tsx | 376 +-- src/client/pages/ConsentsConfirmationPage.tsx | 96 +- src/client/pages/ConsentsData.stories.tsx | 32 +- src/client/pages/ConsentsData.tsx | 358 +-- src/client/pages/ConsentsDataPage.tsx | 40 +- .../pages/ConsentsNewsletters.stories.tsx | 122 +- src/client/pages/ConsentsNewsletters.tsx | 166 +- .../pages/ConsentsNewslettersAB.stories.tsx | 122 +- src/client/pages/ConsentsNewslettersAB.tsx | 162 +- src/client/pages/ConsentsNewslettersPage.tsx | 72 +- src/client/pages/EmailSent.stories.tsx | 66 +- src/client/pages/EmailSent.tsx | 232 +- src/client/pages/EmailSentPage.tsx | 70 +- src/client/pages/JobsTermsAccept.stories.tsx | 30 +- src/client/pages/JobsTermsAccept.tsx | 278 +- src/client/pages/JobsTermsAcceptPage.tsx | 28 +- src/client/pages/MagicLink.stories.tsx | 14 +- src/client/pages/MagicLink.tsx | 70 +- src/client/pages/MagicLinkPage.tsx | 6 +- src/client/pages/MaintenancePage.stories.tsx | 6 +- src/client/pages/MaintenancePage.tsx | 12 +- src/client/pages/NotFoundPage.stories.tsx | 6 +- src/client/pages/NotFoundPage.tsx | 14 +- src/client/pages/Registration.stories.tsx | 48 +- src/client/pages/Registration.tsx | 94 +- .../pages/RegistrationEmailSentPage.tsx | 52 +- src/client/pages/RegistrationPage.tsx | 24 +- .../pages/ResendConsentEmail.stories.tsx | 14 +- src/client/pages/ResendConsentEmail.tsx | 48 +- src/client/pages/ResendConsentEmailPage.tsx | 6 +- .../pages/ResendEmailVerification.stories.tsx | 24 +- src/client/pages/ResendEmailVerification.tsx | 158 +- .../pages/ResendEmailVerificationPage.tsx | 30 +- src/client/pages/ResendPasswordPage.tsx | 76 +- src/client/pages/ResetPassword.stories.tsx | 142 +- src/client/pages/ResetPassword.tsx | 156 +- src/client/pages/ResetPasswordPage.tsx | 48 +- .../pages/ResetPasswordSessionExpiredPage.tsx | 64 +- src/client/pages/SetPasswordCompletePage.tsx | 22 +- src/client/pages/SetPasswordPage.tsx | 94 +- src/client/pages/SetPasswordResendPage.tsx | 52 +- .../pages/SetPasswordSessionExpiredPage.tsx | 66 +- src/client/pages/SignIn.stories.tsx | 98 +- src/client/pages/SignIn.tsx | 224 +- src/client/pages/SignInPage.tsx | 46 +- src/client/pages/SignInSuccess.tsx | 90 +- src/client/pages/SignInSuccessPage.tsx | 8 +- src/client/pages/SignedInAs.stories.tsx | 32 +- src/client/pages/SignedInAs.tsx | 66 +- src/client/pages/SignedInAsPage.tsx | 36 +- src/client/pages/SigninSuccess.stories.tsx | 18 +- .../pages/SubscriptionError.stories.tsx | 14 +- src/client/pages/SubscriptionError.tsx | 54 +- src/client/pages/SubscriptionErrorPage.tsx | 20 +- .../pages/SubscriptionSuccess.stories.tsx | 30 +- src/client/pages/SubscriptionSuccess.tsx | 48 +- src/client/pages/SubscriptionSuccessPage.tsx | 44 +- .../pages/UnexpectedErrorPage.stories.tsx | 6 +- src/client/pages/UnexpectedErrorPage.tsx | 12 +- .../UnvalidatedEmailEmailSent.stories.tsx | 40 +- .../pages/UnvalidatedEmailEmailSentPage.tsx | 62 +- src/client/pages/Welcome.stories.tsx | 46 +- src/client/pages/Welcome.tsx | 154 +- src/client/pages/WelcomePage.tsx | 94 +- .../pages/WelcomePasswordAlreadySetPage.tsx | 24 +- src/client/pages/WelcomeResendPage.tsx | 52 +- .../pages/WelcomeSessionExpiredPage.tsx | 66 +- src/client/routes.tsx | 370 +-- src/client/static/analytics/ophan.ts | 48 +- src/client/static/hydration.tsx | 96 +- src/client/static/index.tsx | 24 +- src/client/styles/Consents.ts | 72 +- src/client/styles/Grid.stories.tsx | 126 +- src/client/styles/Grid.ts | 382 +-- src/client/styles/Shared.ts | 56 +- src/client/window.guardian.d.ts | 48 +- src/email/components/Button.stories.tsx | 6 +- src/email/components/Button.tsx | 42 +- src/email/components/Footer.stories.tsx | 28 +- src/email/components/Footer.tsx | 74 +- src/email/components/Header.stories.tsx | 6 +- src/email/components/Header.tsx | 28 +- src/email/components/Link.stories.tsx | 14 +- src/email/components/Link.tsx | 18 +- src/email/components/Page.stories.tsx | 46 +- src/email/components/Page.tsx | 30 +- src/email/components/SubHeader.stories.tsx | 6 +- src/email/components/SubHeader.tsx | 64 +- src/email/components/Text.stories.tsx | 14 +- src/email/components/Text.tsx | 48 +- src/email/lib/generateUrl.ts | 32 +- src/email/lib/send.ts | 94 +- .../AccidentalEmail.stories.tsx | 8 +- .../AccidentalEmail/AccidentalEmail.tsx | 66 +- .../sendAccidentalEmailEmail.ts | 20 +- .../AccountExists/AccountExists.stories.tsx | 8 +- .../templates/AccountExists/AccountExists.tsx | 46 +- .../AccountExists/sendAccountExistsEmail.ts | 50 +- .../AccountWithoutPasswordExists.stories.tsx | 8 +- .../AccountWithoutPasswordExists.tsx | 46 +- .../sendAccountWithoutPasswordExists.ts | 44 +- .../CompleteRegistration.stories.tsx | 8 +- .../CompleteRegistration.tsx | 36 +- .../sendCompleteRegistration.ts | 44 +- .../CreatePassword/CreatePassword.stories.tsx | 8 +- .../CreatePassword/CreatePassword.tsx | 36 +- .../CreatePassword/sendCreatePasswordEmail.ts | 44 +- .../templates/NoAccount/NoAccount.stories.tsx | 8 +- src/email/templates/NoAccount/NoAccount.tsx | 50 +- .../templates/NoAccount/sendNoAccountEmail.ts | 20 +- .../ResetPassword/ResetPassword.stories.tsx | 8 +- .../templates/ResetPassword/ResetPassword.tsx | 42 +- .../ResetPassword/sendResetPasswordEmail.ts | 44 +- .../UnvalidatedEmailResetPassword.stories.tsx | 8 +- .../UnvalidatedEmailResetPassword.tsx | 44 +- .../sendUnvalidatedEmailResetPasswordEmail.ts | 44 +- src/email/templates/renderedTemplates.ts | 36 +- src/email/testUtils.tsx | 8 +- src/server/controllers/changePassword.ts | 560 ++-- src/server/controllers/checkPasswordToken.ts | 380 +-- .../controllers/sendChangePasswordEmail.ts | 522 ++-- src/server/index.ts | 8 +- src/server/lib/IDAPIFetch.ts | 214 +- .../__tests__/breachedPasswordCheck.test.ts | 60 +- src/server/lib/__tests__/crypto.test.ts | 160 +- .../deeplink/oktaRecoveryToken.test.ts | 258 +- .../lib/__tests__/getABForcedVariants.test.ts | 88 +- .../getBrowserNameFromUserAgent.test.ts | 38 +- .../lib/__tests__/getConfiguration.test.ts | 266 +- .../lib/__tests__/getCsrfPageUrl.test.ts | 70 +- .../__tests__/getGeolocationRegion.test.ts | 34 +- src/server/lib/__tests__/getMvtId.test.ts | 150 +- .../__tests__/getRegistrationLocation.test.ts | 62 +- .../lib/__tests__/handleAsyncErrors.test.ts | 48 +- src/server/lib/__tests__/headers.test.ts | 122 +- .../idapi/invertOptOutConsents.test.ts | 160 +- .../members-data-api/user-attributes.test.ts | 304 +- .../lib/__tests__/okta/api/sessions.test.ts | 86 +- .../lib/__tests__/okta/api/users.test.ts | 866 +++--- .../lib/__tests__/okta/register.test.ts | 602 ++-- src/server/lib/__tests__/ophan.test.ts | 418 +-- src/server/lib/__tests__/queryParams.test.ts | 290 +- .../__tests__/rate-limit/rateLimit.test.ts | 884 +++--- .../rate-limit/rateLimitMiddleware.test.ts | 902 +++--- src/server/lib/__tests__/sharedConfig.ts | 102 +- .../updateRegistrationLocation.test.ts | 210 +- .../lib/__tests__/validateClientId.test.ts | 26 +- .../__tests__/validatePasswordField.test.ts | 46 +- src/server/lib/__tests__/validateUrl.test.ts | 94 +- src/server/lib/awsConfig.ts | 20 +- src/server/lib/breachedPasswordCheck.ts | 88 +- src/server/lib/crypto.ts | 98 +- src/server/lib/deeplink/oktaRecoveryToken.ts | 74 +- src/server/lib/emailCookie.ts | 2 +- src/server/lib/encryptedStateCookie.ts | 126 +- src/server/lib/experiments.ts | 62 +- src/server/lib/expressWrappers.ts | 16 +- src/server/lib/getABForcedVariants.ts | 24 +- src/server/lib/getABTesting.ts | 40 +- src/server/lib/getAssets.ts | 72 +- src/server/lib/getBrowserName.ts | 12 +- src/server/lib/getConfiguration.ts | 424 +-- src/server/lib/getCsrfPageUrl.ts | 12 +- src/server/lib/getGeolocationRegion.ts | 22 +- src/server/lib/getMvtId.ts | 16 +- src/server/lib/getProfileUrl.ts | 26 +- src/server/lib/getRegistrationLocation.ts | 504 ++-- src/server/lib/idapi/IDAPICookies.ts | 126 +- src/server/lib/idapi/auth.ts | 224 +- src/server/lib/idapi/changePassword.ts | 164 +- src/server/lib/idapi/consentToken.ts | 112 +- src/server/lib/idapi/consents.ts | 268 +- src/server/lib/idapi/decryptToken.ts | 46 +- src/server/lib/idapi/guest.ts | 122 +- src/server/lib/idapi/invertOptOutConsents.ts | 30 +- src/server/lib/idapi/newsletters.ts | 196 +- src/server/lib/idapi/resetPassword.ts | 58 +- src/server/lib/idapi/subscriptions.ts | 98 +- src/server/lib/idapi/unauth.ts | 50 +- src/server/lib/idapi/user.ts | 678 ++--- src/server/lib/idapi/verifyEmail.ts | 124 +- src/server/lib/isStringBoolean.ts | 14 +- src/server/lib/jobs.ts | 98 +- .../lib/members-data-api/user-attributes.ts | 124 +- src/server/lib/middleware/404.ts | 16 +- src/server/lib/middleware/cache.ts | 6 +- src/server/lib/middleware/csrf.ts | 14 +- src/server/lib/middleware/errorHandler.ts | 92 +- src/server/lib/middleware/helmet.ts | 98 +- src/server/lib/middleware/index.ts | 34 +- src/server/lib/middleware/logger.ts | 14 +- src/server/lib/middleware/login.ts | 312 +- src/server/lib/middleware/oktaDev.ts | 26 +- src/server/lib/middleware/rateLimit.ts | 194 +- .../lib/middleware/redirectIfLoggedIn.ts | 214 +- src/server/lib/middleware/requestId.ts | 12 +- src/server/lib/middleware/requestState.ts | 174 +- src/server/lib/okta/api/apps.ts | 40 +- src/server/lib/okta/api/authentication.ts | 124 +- src/server/lib/okta/api/errors.ts | 46 +- src/server/lib/okta/api/headers.ts | 12 +- src/server/lib/okta/api/responses.ts | 10 +- src/server/lib/okta/api/sessions.ts | 50 +- src/server/lib/okta/api/users.ts | 340 +-- .../okta/dangerouslySetPlaceholderPassword.ts | 52 +- src/server/lib/okta/oauth.ts | 160 +- src/server/lib/okta/openid-connect.ts | 216 +- src/server/lib/okta/register.ts | 510 ++-- src/server/lib/okta/tokens.ts | 140 +- src/server/lib/okta/validateEmail.ts | 20 +- src/server/lib/ophan.ts | 292 +- src/server/lib/postSignInController.ts | 116 +- src/server/lib/queryParams.ts | 98 +- src/server/lib/rate-limit/bucket.ts | 308 +- .../lib/rate-limit/configurationValidator.ts | 74 +- src/server/lib/rate-limit/keys.ts | 72 +- src/server/lib/rate-limit/logger.ts | 56 +- src/server/lib/rate-limit/rateLimit.ts | 166 +- src/server/lib/rate-limit/types.ts | 80 +- src/server/lib/rateLimiterConfiguration.ts | 56 +- src/server/lib/recaptcha.ts | 114 +- src/server/lib/redis/redisClient.ts | 12 +- src/server/lib/renderer.tsx | 176 +- src/server/lib/requestState.ts | 6 +- src/server/lib/serverSideLogger.ts | 224 +- src/server/lib/timeoutSignal.ts | 6 +- src/server/lib/trackMetric.ts | 126 +- src/server/lib/typedRoutes.ts | 18 +- src/server/lib/unvalidatedEmail.ts | 54 +- src/server/lib/updateRegistrationLocation.ts | 120 +- src/server/lib/user-features.ts | 52 +- src/server/lib/validateClientId.ts | 2 +- src/server/lib/validatePasswordField.ts | 154 +- src/server/lib/validateUrl.ts | 86 +- src/server/models/Configuration.ts | 106 +- src/server/models/Error.ts | 88 +- src/server/models/Express.ts | 54 +- src/server/models/Metrics.ts | 118 +- src/server/models/okta/App.ts | 14 +- src/server/models/okta/Authentication.ts | 154 +- src/server/models/okta/Error.ts | 74 +- src/server/models/okta/Group.ts | 10 +- src/server/models/okta/Session.ts | 20 +- src/server/models/okta/User.ts | 138 +- src/server/routes/agree.ts | 422 +-- src/server/routes/changeEmail.ts | 80 +- src/server/routes/consentToken.ts | 118 +- src/server/routes/consents.ts | 890 +++--- src/server/routes/core.ts | 32 +- src/server/routes/emailTemplates.ts | 36 +- src/server/routes/index.ts | 2 +- src/server/routes/magicLink.ts | 84 +- src/server/routes/maintenance.ts | 10 +- src/server/routes/oauth.ts | 706 ++--- src/server/routes/register.ts | 738 ++--- src/server/routes/resetPassword.ts | 124 +- src/server/routes/setPassword.ts | 226 +- src/server/routes/signIn.ts | 1138 +++---- src/server/routes/signOut.ts | 188 +- src/server/routes/subscriptions.ts | 108 +- src/server/routes/verifyEmail.ts | 352 +-- src/server/routes/welcome.ts | 256 +- src/server/server.ts | 14 +- src/shared/__tests__/newsletter.test.ts | 180 +- src/shared/__tests__/queryParams.test.ts | 266 +- src/shared/__tests__/regexparam.test.ts | 482 +-- src/shared/__tests__/routeUtils.test.ts | 36 +- src/shared/lib/baseLogger.ts | 34 +- src/shared/lib/clientId.ts | 10 +- src/shared/lib/featureSwitches.ts | 44 +- src/shared/lib/locations.ts | 16 +- src/shared/lib/newsletter.ts | 30 +- .../lib/newsletterConsentsPageLocalisation.ts | 168 +- src/shared/lib/queryParams.ts | 144 +- src/shared/lib/regexparam.ts | 44 +- src/shared/lib/routeUtils.ts | 54 +- src/shared/lib/subscriptions.ts | 2 +- src/shared/model/ClientState.ts | 124 +- src/shared/model/Consent.ts | 30 +- src/shared/model/EmailType.ts | 8 +- src/shared/model/EncryptedState.ts | 14 +- src/shared/model/Errors.ts | 88 +- src/shared/model/IDAPIAuth.ts | 26 +- src/shared/model/IdapiQueryParams.ts | 2 +- src/shared/model/Logger.ts | 22 +- src/shared/model/Newsletter.ts | 44 +- src/shared/model/OktaQueryParams.ts | 2 +- src/shared/model/PageTitle.ts | 64 +- src/shared/model/QueryParams.ts | 82 +- src/shared/model/Routes.ts | 216 +- src/shared/model/Success.ts | 4 +- src/shared/model/User.ts | 28 +- src/shared/model/experiments/abSwitches.ts | 4 +- src/shared/model/experiments/abTests.ts | 40 +- .../tests/abDefaultWeeklyNewsletterTest.ts | 48 +- .../model/experiments/tests/example-test.ts | 54 +- src/shared/model/ophan.ts | 16 +- tsconfig.json | 40 +- util/mock-server.js | 116 +- webpack.config.js | 461 ++- webpack.development.js | 124 +- yarn.lock | 5 + 532 files changed, 35451 insertions(+), 35453 deletions(-) diff --git a/.editorconfig b/.editorconfig index 21cb9608a0..e90faf856b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,8 +8,6 @@ root = true end_of_line = lf insert_final_newline = true charset = utf-8 -indent_style = space -indent_size = 2 # makefile [makefile] diff --git a/.eslintrc.json b/.eslintrc.json index 21ffb13a09..20c968413a 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,55 +1,55 @@ { - "parser": "@typescript-eslint/parser", - "extends": [ - "plugin:prettier/recommended", - "plugin:@typescript-eslint/recommended", - "plugin:react/recommended", - "prettier", - "plugin:jsx-a11y/recommended", - "plugin:functional/no-mutations", - "plugin:react-hooks/recommended", - "plugin:@guardian/source-foundations/recommended", - "plugin:@guardian/source-react-components/recommended", - "plugin:storybook/recommended" - ], - "parserOptions": { - "ecmaFeatures": { - "jsx": true - } - }, - "plugins": ["functional"], - "rules": { - "react/prop-types": 0, - "@typescript-eslint/explicit-function-return-type": 0, - "@typescript-eslint/explicit-module-boundary-types": 0, - "functional/prefer-readonly-type": 0, - "functional/no-method-signature": 0, - "functional/immutable-data": [ - "error", - { - "ignoreImmediateMutation": true - } - ], - "functional/prefer-immutable-types": 0, - "functional/type-declaration-immutability": 0, - "no-var": "error", - "no-param-reassign": "error", - "prefer-const": "error", - "no-sequences": "error", - "no-console": "error", - "react/no-unknown-property": ["error", { "ignore": ["css"] }] - }, - "overrides": [ - { - "files": ["*.stories.tsx"], - "rules": { - "functional/immutable-data": 0 - } - } - ], - "settings": { - "react": { - "version": "detect" - } - } + "parser": "@typescript-eslint/parser", + "extends": [ + "plugin:prettier/recommended", + "plugin:@typescript-eslint/recommended", + "plugin:react/recommended", + "prettier", + "plugin:jsx-a11y/recommended", + "plugin:functional/no-mutations", + "plugin:react-hooks/recommended", + "plugin:@guardian/source-foundations/recommended", + "plugin:@guardian/source-react-components/recommended", + "plugin:storybook/recommended" + ], + "parserOptions": { + "ecmaFeatures": { + "jsx": true + } + }, + "plugins": ["functional"], + "rules": { + "react/prop-types": 0, + "@typescript-eslint/explicit-function-return-type": 0, + "@typescript-eslint/explicit-module-boundary-types": 0, + "functional/prefer-readonly-type": 0, + "functional/no-method-signature": 0, + "functional/immutable-data": [ + "error", + { + "ignoreImmediateMutation": true + } + ], + "functional/prefer-immutable-types": 0, + "functional/type-declaration-immutability": 0, + "no-var": "error", + "no-param-reassign": "error", + "prefer-const": "error", + "no-sequences": "error", + "no-console": "error", + "react/no-unknown-property": ["error", { "ignore": ["css"] }] + }, + "overrides": [ + { + "files": ["*.stories.tsx"], + "rules": { + "functional/immutable-data": 0 + } + } + ], + "settings": { + "react": { + "version": "detect" + } + } } diff --git a/.prettierrc b/.prettierrc index 51bdee92e5..1ddbdff6f0 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,6 +1 @@ -{ - "tabWidth": 2, - "trailingComma": "all", - "singleQuote": true, - "bracketSpacing": true -} +"@guardian/prettier" diff --git a/.ratelimit.example.json b/.ratelimit.example.json index 36fd4da0af..71d0ef88ba 100644 --- a/.ratelimit.example.json +++ b/.ratelimit.example.json @@ -1,15 +1,15 @@ { - "enabled": false, - "settings": { - "logOnly": false, - "trackBucketCapacity": false - }, - "defaultBuckets": { - "globalBucket": { "capacity": 500, "addTokenMs": 50 }, - "ipBucket": { "capacity": 100, "addTokenMs": 50 }, - "emailBucket": { "capacity": 100, "addTokenMs": 50 }, - "oktaIdentifierBucket": { "capacity": 100, "addTokenMs": 50 }, - "accessTokenBucket": { "capacity": 100, "addTokenMs": 50 } - }, - "routeBuckets": {} + "enabled": false, + "settings": { + "logOnly": false, + "trackBucketCapacity": false + }, + "defaultBuckets": { + "globalBucket": { "capacity": 500, "addTokenMs": 50 }, + "ipBucket": { "capacity": 100, "addTokenMs": 50 }, + "emailBucket": { "capacity": 100, "addTokenMs": 50 }, + "oktaIdentifierBucket": { "capacity": 100, "addTokenMs": 50 }, + "accessTokenBucket": { "capacity": 100, "addTokenMs": 50 } + }, + "routeBuckets": {} } diff --git a/.storybook/clientStateDecorator.tsx b/.storybook/clientStateDecorator.tsx index a653ed6a04..4d206c8184 100644 --- a/.storybook/clientStateDecorator.tsx +++ b/.storybook/clientStateDecorator.tsx @@ -2,19 +2,19 @@ import React from 'react'; import { DecoratorFn } from '@storybook/react'; import { - ClientStateProvider, - defaultClientState, + ClientStateProvider, + defaultClientState, } from '../src/client/components/ClientState'; const clientStateDecorator: DecoratorFn = (StoryToDecorate, context) => ( - - - + + + ); export default clientStateDecorator; diff --git a/.storybook/main.js b/.storybook/main.js index 3cab22db5c..82e558b314 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -3,17 +3,17 @@ const { neutral } = require('@guardian/source-foundations'); const deepmerge = require('deepmerge'); const sharedLoader = require('../.swcrc.config'); const config = { - stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], - addons: [ - '@storybook/addon-links', - '@storybook/addon-essentials', - '@storybook/addon-styling', - ], - babel: async (options) => { - options.presets.push('@emotion/babel-preset-css-prop'); - return options; - }, - previewHead: (head) => ` + stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], + addons: [ + '@storybook/addon-links', + '@storybook/addon-essentials', + '@storybook/addon-styling', + ], + babel: async (options) => { + options.presets.push('@emotion/babel-preset-css-prop'); + return options; + }, + previewHead: (head) => ` ${head} `, - webpackFinal: async (config) => { - // Add the @client alias to prevent imports using it from failing - // Nb. __dirname is the current working directory, so .storybook in this case - config.resolve.alias = { - ...config.resolve.alias, - '@': path.join(__dirname, '../src'), - react: 'preact/compat', - 'react-dom/test-utils': 'preact/test-utils', - 'react-dom': 'preact/compat', - // Must be below test-utils - 'react/jsx-runtime': 'preact/jsx-runtime', - mjml: 'mjml-browser', - // We stub these libraries required by mjml because Storybook cannot run these on the client side. - 'uglify-js': false, - 'clean-css': false, - }; + webpackFinal: async (config) => { + // Add the @client alias to prevent imports using it from failing + // Nb. __dirname is the current working directory, so .storybook in this case + config.resolve.alias = { + ...config.resolve.alias, + '@': path.join(__dirname, '../src'), + react: 'preact/compat', + 'react-dom/test-utils': 'preact/test-utils', + 'react-dom': 'preact/compat', + // Must be below test-utils + 'react/jsx-runtime': 'preact/jsx-runtime', + mjml: 'mjml-browser', + // We stub these libraries required by mjml because Storybook cannot run these on the client side. + 'uglify-js': false, + 'clean-css': false, + }; - // transpile certain modules so we can get them to work with ie11 storybook - const transpileModules = { - include: [/node_modules[\\\/]@guardian/], - test: /\.(m?)(j|t)s(x?)/, - use: [ - deepmerge(sharedLoader, { - options: { - env: { - targets: { - chrome: '100', - }, - }, - }, - }), - ], - }; + // transpile certain modules so we can get them to work with ie11 storybook + const transpileModules = { + include: [/node_modules[\\\/]@guardian/], + test: /\.(m?)(j|t)s(x?)/, + use: [ + deepmerge(sharedLoader, { + options: { + env: { + targets: { + chrome: '100', + }, + }, + }, + }), + ], + }; - // Return the altered config - return { - ...config, - module: { - ...config.module, - rules: [...config.module.rules, transpileModules], - }, - target: ['web'], - }; - }, - typescript: { - reactDocgen: 'react-docgen-typescript-plugin', - }, - framework: { - name: '@storybook/react-webpack5', - options: {}, - }, - docs: { - autodocs: false, - }, + // Return the altered config + return { + ...config, + module: { + ...config.module, + rules: [...config.module.rules, transpileModules], + }, + target: ['web'], + }; + }, + typescript: { + reactDocgen: 'react-docgen-typescript-plugin', + }, + framework: { + name: '@storybook/react-webpack5', + options: {}, + }, + docs: { + autodocs: false, + }, }; export default config; diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html index 618d9372b8..9824d89961 100644 --- a/.storybook/preview-head.html +++ b/.storybook/preview-head.html @@ -1,5 +1,5 @@ diff --git a/.storybook/preview.js b/.storybook/preview.js index 6c6704dc4f..a4f8ceb535 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -9,87 +9,87 @@ import clientStateDecorator from './clientStateDecorator'; import { neutral } from '@guardian/source-foundations'; const GlobalStyles = () => ( - + ); /* Source provides a global utility that manages the appearance of focus styles. When enabled, * focus styles will be hidden while the user interacts using the mouse. * They will appear when the tab key is pressed to begin keyboard navigation. */ const FocusManagerDecorator = (storyFn) => { - React.useEffect(() => { - FocusStyleManager.onlyShowFocusOnTabs(); - }, []); + React.useEffect(() => { + FocusStyleManager.onlyShowFocusOnTabs(); + }, []); - return <>{storyFn()}; + return <>{storyFn()}; }; const customViewports = {}; for (let breakpoint in Breakpoints) { - if (isNaN(Number(breakpoint))) { - // Breakpoints is an enum, not an object, so it also loops the values which we want to avoid here - customViewports[breakpoint] = { - name: `${breakpoint} (${Breakpoints[breakpoint]})`, - styles: { - width: `${Breakpoints[breakpoint]}px`, - height: '100vh', - }, - }; - } + if (isNaN(Number(breakpoint))) { + // Breakpoints is an enum, not an object, so it also loops the values which we want to avoid here + customViewports[breakpoint] = { + name: `${breakpoint} (${Breakpoints[breakpoint]})`, + styles: { + width: `${Breakpoints[breakpoint]}px`, + height: '100vh', + }, + }; + } } const decorators = [ - withThemeFromJSXProvider({ - GlobalStyles, - }), - FocusManagerDecorator, - clientStateDecorator, + withThemeFromJSXProvider({ + GlobalStyles, + }), + FocusManagerDecorator, + clientStateDecorator, ]; const parameters = { - actions: { argTypesRegex: '^on[A-Z].*' }, - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/, - }, - }, - viewport: { - viewports: { - ...customViewports, - ...INITIAL_VIEWPORTS, - }, - defaultViewport: 'MOBILE', - }, + actions: { argTypesRegex: '^on[A-Z].*' }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, + }, + viewport: { + viewports: { + ...customViewports, + ...INITIAL_VIEWPORTS, + }, + defaultViewport: 'MOBILE', + }, }; const preview = { - decorators, - parameters, + decorators, + parameters, }; export default preview; diff --git a/.swcrc.config.js b/.swcrc.config.js index 16305f872b..16c6c6183f 100644 --- a/.swcrc.config.js +++ b/.swcrc.config.js @@ -1,35 +1,35 @@ const mode = - process.env.ENVIRONMENT === 'production' ? 'production' : 'development'; + process.env.ENVIRONMENT === 'production' ? 'production' : 'development'; module.exports = { - loader: 'swc-loader', - options: { - jsc: { - parser: { - syntax: 'typescript', - tsx: true, - decorators: false, - dynamicImport: true, - }, - transform: { - react: { - runtime: 'automatic', - throwIfNamespace: true, - importSource: '@emotion/react', - }, - }, - experimental: { - plugins: [ - [ - '@swc/plugin-emotion', - { - // only in development, as this increases bundle size in production - sourceMap: mode === 'development', - }, - ], - ], - }, - }, - sourceMaps: true, - }, + loader: 'swc-loader', + options: { + jsc: { + parser: { + syntax: 'typescript', + tsx: true, + decorators: false, + dynamicImport: true, + }, + transform: { + react: { + runtime: 'automatic', + throwIfNamespace: true, + importSource: '@emotion/react', + }, + }, + experimental: { + plugins: [ + [ + '@swc/plugin-emotion', + { + // only in development, as this increases bundle size in production + sourceMap: mode === 'development', + }, + ], + ], + }, + }, + sourceMaps: true, + }, }; diff --git a/.vscode/extensions.json b/.vscode/extensions.json index df4da5f863..e63b735ae4 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,9 +1,9 @@ { - "recommendations": [ - "editorconfig.editorconfig", - "dbaeumer.vscode-eslint", - "esbenp.prettier-vscode", - "visualstudioexptteam.vscodeintellicode", - "ms-vsliveshare.vsliveshare" - ] + "recommendations": [ + "editorconfig.editorconfig", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "visualstudioexptteam.vscodeintellicode", + "ms-vsliveshare.vsliveshare" + ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 7989999885..c694989589 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,8 +1,8 @@ { - "json.schemas": [ - { - "fileMatch": [".ratelimit.json", ".ratelimit.example.json"], - "url": "./.ratelimit.schema.json" - } - ] + "json.schemas": [ + { + "fileMatch": [".ratelimit.json", ".ratelimit.example.json"], + "url": "./.ratelimit.schema.json" + } + ] } diff --git a/artifact.json b/artifact.json index 007b4c0770..9d0f73a650 100644 --- a/artifact.json +++ b/artifact.json @@ -1,16 +1,16 @@ { - "projectName": "identity:identity-gateway", - "vcsURL": "https://github.com/guardian/gateway", - "actions": [ - { - "action": "identity-gateway", - "path": "build", - "compress": "zip" - }, - { - "action": "gateway-cloudformation", - "path": "cloudformation.yaml", - "compress": false - } - ] + "projectName": "identity:identity-gateway", + "vcsURL": "https://github.com/guardian/gateway", + "actions": [ + { + "action": "identity-gateway", + "path": "build", + "compress": "zip" + }, + { + "action": "gateway-cloudformation", + "path": "cloudformation.yaml", + "compress": false + } + ] } diff --git a/babel.config.js b/babel.config.js index 407556570d..41d146631d 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,19 +1,19 @@ /* eslint-disable functional/immutable-data */ // only used by storybook module.exports = { - presets: [ - [ - '@babel/preset-env', - { - shippedProposals: true, - useBuiltIns: 'usage', - corejs: '3', - modules: false, - targets: { chrome: '100' }, - }, - ], - '@babel/preset-typescript', - '@babel/preset-react', - ], - plugins: [], + presets: [ + [ + '@babel/preset-env', + { + shippedProposals: true, + useBuiltIns: 'usage', + corejs: '3', + modules: false, + targets: { chrome: '100' }, + }, + ], + '@babel/preset-typescript', + '@babel/preset-react', + ], + plugins: [], }; diff --git a/cypress.config.ts b/cypress.config.ts index 7fa637e4ee..55a4df6f6c 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -1,28 +1,28 @@ import { defineConfig } from 'cypress'; export default defineConfig({ - video: false, - chromeWebSecurity: false, - defaultCommandTimeout: 12000, - responseTimeout: 12000, - requestTimeout: 12000, - env: { - mockingEndpoint: 'localhost:9000/mock', - }, - e2e: { - baseUrl: 'https://profile.thegulocal.com', - specPattern: 'cypress/integration/**/*.cy.ts', - excludeSpecPattern: '*.shared.ts', - // eslint-disable-next-line @typescript-eslint/no-unused-vars - setupNodeEvents(on, config) { - on('task', { - log(message) { - // eslint-disable-next-line no-console - console.log(`CYPRESS: ${message}`); + video: false, + chromeWebSecurity: false, + defaultCommandTimeout: 12000, + responseTimeout: 12000, + requestTimeout: 12000, + env: { + mockingEndpoint: 'localhost:9000/mock', + }, + e2e: { + baseUrl: 'https://profile.thegulocal.com', + specPattern: 'cypress/integration/**/*.cy.ts', + excludeSpecPattern: '*.shared.ts', + // eslint-disable-next-line @typescript-eslint/no-unused-vars + setupNodeEvents(on, config) { + on('task', { + log(message) { + // eslint-disable-next-line no-console + console.log(`CYPRESS: ${message}`); - return null; - }, - }); - }, - }, + return null; + }, + }); + }, + }, }); diff --git a/cypress/fixtures/okta-responses/error/authentication-failed.json b/cypress/fixtures/okta-responses/error/authentication-failed.json index 249c65f1ce..21dc9ba010 100644 --- a/cypress/fixtures/okta-responses/error/authentication-failed.json +++ b/cypress/fixtures/okta-responses/error/authentication-failed.json @@ -1,10 +1,10 @@ { - "code": 401, - "response": { - "errorCode": "E0000004", - "errorSummary": "Authentication failed", - "errorLink": "E0000004", - "errorId": "mocked-error-id", - "errorCauses": [] - } + "code": 401, + "response": { + "errorCode": "E0000004", + "errorSummary": "Authentication failed", + "errorLink": "E0000004", + "errorId": "mocked-error-id", + "errorCauses": [] + } } diff --git a/cypress/fixtures/okta-responses/error/no-permission.json b/cypress/fixtures/okta-responses/error/no-permission.json index 2afdcc20ed..d75d0c997d 100644 --- a/cypress/fixtures/okta-responses/error/no-permission.json +++ b/cypress/fixtures/okta-responses/error/no-permission.json @@ -1,10 +1,10 @@ { - "code": 403, - "response": { - "errorCode": "E0000006", - "errorSummary": "You do not have permission to perform the requested action", - "errorLink": "E0000006", - "errorId": "mocked-error-id", - "errorCauses": [] - } + "code": 403, + "response": { + "errorCode": "E0000006", + "errorSummary": "You do not have permission to perform the requested action", + "errorLink": "E0000006", + "errorId": "mocked-error-id", + "errorCauses": [] + } } diff --git a/cypress/fixtures/okta-responses/error/user-exists.json b/cypress/fixtures/okta-responses/error/user-exists.json index ded5454d26..dcc2dedb6c 100644 --- a/cypress/fixtures/okta-responses/error/user-exists.json +++ b/cypress/fixtures/okta-responses/error/user-exists.json @@ -1,14 +1,14 @@ { - "code": 401, - "response": { - "errorCode": "E0000001", - "errorSummary": "Api validation failed: login", - "errorLink": "E0000001", - "errorId": "mocked-error-id", - "errorCauses": [ - { - "errorSummary": "login: An object with this field already exists in the current organization" - } - ] - } + "code": 401, + "response": { + "errorCode": "E0000001", + "errorSummary": "Api validation failed: login", + "errorLink": "E0000001", + "errorId": "mocked-error-id", + "errorCauses": [ + { + "errorSummary": "login: An object with this field already exists in the current organization" + } + ] + } } diff --git a/cypress/fixtures/okta-responses/error/user-not-found.json b/cypress/fixtures/okta-responses/error/user-not-found.json index ec7de80c92..cd5a25e127 100644 --- a/cypress/fixtures/okta-responses/error/user-not-found.json +++ b/cypress/fixtures/okta-responses/error/user-not-found.json @@ -1,10 +1,10 @@ { - "code": 404, - "response": { - "errorCode": "E0000007", - "errorSummary": "Not found: Resource not found: example@example.com (User)", - "errorLink": "E0000007", - "errorId": "mocked-error-id", - "errorCauses": [] - } + "code": 404, + "response": { + "errorCode": "E0000007", + "errorSummary": "Not found: Resource not found: example@example.com (User)", + "errorLink": "E0000007", + "errorId": "mocked-error-id", + "errorCauses": [] + } } diff --git a/cypress/fixtures/okta-responses/success/auth-reset-password.json b/cypress/fixtures/okta-responses/success/auth-reset-password.json index 81a31ffefd..49f11bf074 100644 --- a/cypress/fixtures/okta-responses/success/auth-reset-password.json +++ b/cypress/fixtures/okta-responses/success/auth-reset-password.json @@ -1,21 +1,21 @@ { - "code": 200, - "response": { - "expiresAt": "2015-11-03T10:15:57.000Z", - "status": "SUCCESS", - "sessionToken": "mocked_session_token", - "_embedded": { - "user": { - "id": "mocked_user_id", - "passwordChanged": "2015-11-08T20:14:45.000Z", - "profile": { - "login": "test.testerson@example.com", - "firstName": "Test", - "lastName": "Testerson", - "locale": "en_US", - "timeZone": "America/Los_Angeles" - } - } - } - } + "code": 200, + "response": { + "expiresAt": "2015-11-03T10:15:57.000Z", + "status": "SUCCESS", + "sessionToken": "mocked_session_token", + "_embedded": { + "user": { + "id": "mocked_user_id", + "passwordChanged": "2015-11-08T20:14:45.000Z", + "profile": { + "login": "test.testerson@example.com", + "firstName": "Test", + "lastName": "Testerson", + "locale": "en_US", + "timeZone": "America/Los_Angeles" + } + } + } + } } diff --git a/cypress/fixtures/okta-responses/success/forgot-password-email-factor.json b/cypress/fixtures/okta-responses/success/forgot-password-email-factor.json index 6764f21039..c390ecf6f0 100644 --- a/cypress/fixtures/okta-responses/success/forgot-password-email-factor.json +++ b/cypress/fixtures/okta-responses/success/forgot-password-email-factor.json @@ -1,9 +1,9 @@ { - "code": 200, - "response": { - "status": "RECOVERY_CHALLENGE", - "factorResult": "WAITING", - "factorType": "EMAIL", - "recoveryType": "PASSWORD" - } + "code": 200, + "response": { + "status": "RECOVERY_CHALLENGE", + "factorResult": "WAITING", + "factorType": "EMAIL", + "recoveryType": "PASSWORD" + } } diff --git a/cypress/fixtures/okta-responses/success/reset-password.json b/cypress/fixtures/okta-responses/success/reset-password.json index ff5f12332a..140fe769a9 100644 --- a/cypress/fixtures/okta-responses/success/reset-password.json +++ b/cypress/fixtures/okta-responses/success/reset-password.json @@ -1,6 +1,6 @@ { - "code": 200, - "response": { - "resetPasswordUrl": "https://$OKTA_ORG_URL/reset_password/XE6wE17zmphl3KqAPFxO" - } + "code": 200, + "response": { + "resetPasswordUrl": "https://$OKTA_ORG_URL/reset_password/XE6wE17zmphl3KqAPFxO" + } } diff --git a/cypress/fixtures/okta-responses/success/social-user.json b/cypress/fixtures/okta-responses/success/social-user.json index 2aeff70cd9..cce3a43060 100644 --- a/cypress/fixtures/okta-responses/success/social-user.json +++ b/cypress/fixtures/okta-responses/success/social-user.json @@ -1,15 +1,15 @@ { - "code": 200, - "response": { - "id": "mocked-id", - "status": "ACTIVE", - "profile": { - "firstName": null, - "lastName": null, - "email": "example@example.com", - "login": "example@example.com", - "isGuardianUser": true - }, - "credentials": { "provider": { "type": "SOCIAL", "name": "SOCIAL" } } - } + "code": 200, + "response": { + "id": "mocked-id", + "status": "ACTIVE", + "profile": { + "firstName": null, + "lastName": null, + "email": "example@example.com", + "login": "example@example.com", + "isGuardianUser": true + }, + "credentials": { "provider": { "type": "SOCIAL", "name": "SOCIAL" } } + } } diff --git a/cypress/fixtures/okta-responses/success/token.json b/cypress/fixtures/okta-responses/success/token.json index 35d7066b5e..b383bde747 100644 --- a/cypress/fixtures/okta-responses/success/token.json +++ b/cypress/fixtures/okta-responses/success/token.json @@ -1,7 +1,7 @@ { - "code": 200, - "response": { - "activationUrl": "https://$OKTA_ORG_URL/welcome/XE6wE17zmphl3KqAPFxO", - "activationToken": "XE6wE17zmphl3KqAPFxO" - } + "code": 200, + "response": { + "activationUrl": "https://$OKTA_ORG_URL/welcome/XE6wE17zmphl3KqAPFxO", + "activationToken": "XE6wE17zmphl3KqAPFxO" + } } diff --git a/cypress/fixtures/okta-responses/success/user.json b/cypress/fixtures/okta-responses/success/user.json index b9334a5fd0..fd3defbae4 100644 --- a/cypress/fixtures/okta-responses/success/user.json +++ b/cypress/fixtures/okta-responses/success/user.json @@ -1,15 +1,15 @@ { - "code": 200, - "response": { - "id": "mocked-id", - "status": "$STATUS", - "profile": { - "firstName": null, - "lastName": null, - "email": "example@example.com", - "login": "example@example.com", - "isGuardianUser": true - }, - "credentials": { "provider": { "type": "OKTA", "name": "OKTA" } } - } + "code": 200, + "response": { + "id": "mocked-id", + "status": "$STATUS", + "profile": { + "firstName": null, + "lastName": null, + "email": "example@example.com", + "login": "example@example.com", + "isGuardianUser": true + }, + "credentials": { "provider": { "type": "OKTA", "name": "OKTA" } } + } } diff --git a/cypress/fixtures/okta-responses/success/valid-user-groups.json b/cypress/fixtures/okta-responses/success/valid-user-groups.json index 83d8649abf..1599c8f4bb 100644 --- a/cypress/fixtures/okta-responses/success/valid-user-groups.json +++ b/cypress/fixtures/okta-responses/success/valid-user-groups.json @@ -1,12 +1,12 @@ { - "code": 200, - "response": [ - { - "id": "123", - "profile": { - "name": "GuardianUser-EmailValidated", - "description": "User has validated their email" - } - } - ] + "code": 200, + "response": [ + { + "id": "123", + "profile": { + "name": "GuardianUser-EmailValidated", + "description": "User has validated their email" + } + } + ] } diff --git a/cypress/fixtures/okta-responses/success/valid-user.json b/cypress/fixtures/okta-responses/success/valid-user.json index 85ef3bd370..0635f3c09d 100644 --- a/cypress/fixtures/okta-responses/success/valid-user.json +++ b/cypress/fixtures/okta-responses/success/valid-user.json @@ -1,21 +1,21 @@ { - "code": 200, - "response": { - "expiresAt": "2022-08-11T11:54:13.000Z", - "status": "SUCCESS", - "sessionToken": "session-token", - "_embedded": { - "user": { - "id": "user-id", - "passwordChanged": "2022-08-11T11:24:42.000Z", - "profile": { - "login": "example@example.com", - "firstName": null, - "lastName": null, - "locale": "en_US", - "timeZone": "America/Los_Angeles" - } - } - } - } + "code": 200, + "response": { + "expiresAt": "2022-08-11T11:54:13.000Z", + "status": "SUCCESS", + "sessionToken": "session-token", + "_embedded": { + "user": { + "id": "user-id", + "passwordChanged": "2022-08-11T11:24:42.000Z", + "profile": { + "login": "example@example.com", + "firstName": null, + "lastName": null, + "locale": "en_US", + "timeZone": "America/Los_Angeles" + } + } + } + } } diff --git a/cypress/fixtures/okta-responses/success/verify-recovery-token.json b/cypress/fixtures/okta-responses/success/verify-recovery-token.json index c43aada839..f27dcf817b 100644 --- a/cypress/fixtures/okta-responses/success/verify-recovery-token.json +++ b/cypress/fixtures/okta-responses/success/verify-recovery-token.json @@ -1,21 +1,21 @@ { - "code": 200, - "response": { - "stateToken": "some_mocked_state_token", - "expiresAt": "2015-11-03T10:15:57.000Z", - "status": "RECOVERY", - "_embedded": { - "user": { - "id": "some_user_id", - "passwordChanged": "2015-09-08T20:14:45.000Z", - "profile": { - "login": "test.testerson@example.com", - "firstName": "Test", - "lastName": "Testerson", - "locale": "en_US", - "timeZone": "America/Los_Angeles" - } - } - } - } + "code": 200, + "response": { + "stateToken": "some_mocked_state_token", + "expiresAt": "2015-11-03T10:15:57.000Z", + "status": "RECOVERY", + "_embedded": { + "user": { + "id": "some_user_id", + "passwordChanged": "2015-09-08T20:14:45.000Z", + "profile": { + "login": "test.testerson@example.com", + "firstName": "Test", + "lastName": "Testerson", + "locale": "en_US", + "timeZone": "America/Los_Angeles" + } + } + } + } } diff --git a/cypress/fixtures/users.json b/cypress/fixtures/users.json index 8efff910bb..12399acbf0 100644 --- a/cypress/fixtures/users.json +++ b/cypress/fixtures/users.json @@ -1,8 +1,8 @@ { - "validEmail": { - "email": "mrtest@theguardian.com" - }, - "emailNotRegistered": { - "email": "mxunregistered@theguardian.com" - } + "validEmail": { + "email": "mrtest@theguardian.com" + }, + "emailNotRegistered": { + "email": "mxunregistered@theguardian.com" + } } diff --git a/cypress/integration/ete-okta/jobs_terms.4.cy.ts b/cypress/integration/ete-okta/jobs_terms.4.cy.ts index 1881e47366..6f03f2d686 100644 --- a/cypress/integration/ete-okta/jobs_terms.4.cy.ts +++ b/cypress/integration/ete-okta/jobs_terms.4.cy.ts @@ -1,219 +1,219 @@ describe('Jobs terms and conditions flow in Okta', () => { - context('Shows the terms and conditions page on Sign In', () => { - it('visits /agree/GRS after sign in if clientId=jobs parameter is set', () => { - cy.intercept('GET', 'https://jobs.theguardian.com/', (req) => { - req.reply(200); - }); - - cy - .createTestUser({ - isUserEmailValidated: true, - }) - ?.then(({ emailAddress, finalPassword }) => { - const visitUrl = - '/signin?clientId=jobs&returnUrl=https%3A%2F%2Fjobs.theguardian.com%2F'; - cy.visit(visitUrl); - cy.get('input[name=email]').type(emailAddress); - cy.get('input[name=password]').type(finalPassword); - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.url().should('include', '/agree/GRS'); - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.url().should('include', 'https://jobs.theguardian.com/'); - }); - }); - }); - - context('Accepts Jobs terms and conditions and sets their name', () => { - it('should redirect users with an invalid session cookie to reauthenticate', () => { - // load the consents page as its on the same domain - const termsAcceptPageUrl = `https://${Cypress.env( - 'BASE_URI', - )}/agree/GRS?returnUrl=https://profile.thegulocal.com/signin?returnUrl=https%3A%2F%2Fm.code.dev-theguardian.com%2F`; - cy.setCookie('sid', 'invalid-cookie'); - cy.visit(termsAcceptPageUrl); - cy.url().should( - 'include', - 'https://profile.thegulocal.com/reauthenticate', - ); - }); - - it('should redirect users with no session cookie to the signin page', () => { - // load the consents page as its on the same domain - const termsAcceptPageUrl = `https://${Cypress.env( - 'BASE_URI', - )}/agree/GRS?returnUrl=https://profile.thegulocal.com/healthcheck`; - cy.visit(termsAcceptPageUrl); - cy.url().should( - 'include', - 'https://profile.thegulocal.com/signin?fromURI=', - ); - }); - - it('should show the jobs terms page for users who do not have first/last name set, but are jobs users', () => { - cy - .createTestUser({ - isUserEmailValidated: true, - }) - ?.then(({ emailAddress, finalPassword }) => { - cy.visit('/signin'); - cy.get('input[name=email]').type(emailAddress); - cy.get('input[name=password]').type(finalPassword); - - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.url().should('include', '/signin/success'); - - const termsAcceptPageUrl = `https://${Cypress.env( - 'BASE_URI', - )}/agree/GRS?returnUrl=https://profile.thegulocal.com/healthcheck`; - - // Create a test user without a first/last name who has `isJobsUser` set to true. - cy.updateOktaTestUserProfile(emailAddress, { - firstName: '', - lastName: '', - isJobsUser: true, - }).then(() => { - // clear oauth token cookies to simulate the user profile update - cy.clearCookie('GU_ID_TOKEN', { domain: Cypress.env('BASE_URI') }); - cy.clearCookie('GU_ACCESS_TOKEN', { - domain: Cypress.env('BASE_URI'), - }); - - cy.visit(termsAcceptPageUrl); - cy.contains('Please complete your details for'); - cy.contains(emailAddress); - cy.contains('We will use these details on your job applications'); - - cy.get('input[name=firstName]').should('be.empty'); - cy.get('input[name=secondName]').should('be.empty'); - - cy.get('input[name=firstName]').type('First Name'); - cy.get('input[name=secondName]').type('Second Name'); - - cy.findByText('Save and continue').click(); - - // User should have `isJobsUser` set to true and First/Last name set to our custom values. - cy.getTestOktaUser(emailAddress).then(({ profile, status }) => { - const { firstName, lastName, isJobsUser } = profile; - expect(status).to.eq('ACTIVE'); - expect(firstName).to.eq('First Name'); - expect(lastName).to.eq('Second Name'); - expect(isJobsUser).to.eq(true); - }); - }); - }); - }); - - it('should redirect users who have already accepted the terms away', () => { - cy - .createTestUser({ - isUserEmailValidated: true, - }) - ?.then(({ emailAddress, finalPassword }) => { - // load the consents page as its on the same domain - const termsAcceptPageUrl = `https://${Cypress.env( - 'BASE_URI', - )}/agree/GRS?returnUrl=https://profile.thegulocal.com/healthcheck`; - - cy.visit('/signin'); - cy.get('input[name=email]').type(emailAddress); - cy.get('input[name=password]').type(finalPassword); - - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.url().should('include', '/signin/success'); - - cy.updateOktaTestUserProfile(emailAddress, { - firstName: 'Test', - lastName: 'User', - isJobsUser: true, - }).then(() => { - cy.visit(termsAcceptPageUrl); - - cy.url().should( - 'include', - 'https://profile.thegulocal.com/healthcheck', - ); - - const finalTermsAcceptPageUrl = `https://${Cypress.env( - 'BASE_URI', - )}/agree/GRS?returnUrl=https://profile.thegulocal.com/maintenance`; - - cy.visit(finalTermsAcceptPageUrl, { failOnStatusCode: false }); - - // Make sure the returnURL is respected. - cy.url().should( - 'include', - 'https://profile.thegulocal.com/maintenance', - ); - }); - }); - }); - - it('should allow a non-jobs user to enter their first/last name and accept the terms', () => { - cy - .createTestUser({ - isUserEmailValidated: true, - }) - ?.then(({ emailAddress, finalPassword }) => { - // load the consents page as its on the same domain - const termsAcceptPageUrl = `https://${Cypress.env( - 'BASE_URI', - )}/agree/GRS?returnUrl=https://jobs.theguardian.com/`; - - cy.visit('/signin'); - cy.get('input[name=email]').type(emailAddress); - cy.get('input[name=password]').type(finalPassword); - - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.url().should('include', '/signin/success'); - - cy.visit(termsAcceptPageUrl); - - // check sign in has worked first - cy.url().should('include', `/agree/GRS`); - // check session cookie is set - cy.getCookie('sid').should('exist'); - // check idapi cookies are set - cy.getCookie('SC_GU_U').should('exist'); - cy.getCookie('SC_GU_LA').should('exist'); - cy.getCookie('GU_U').should('exist'); - - cy.contains('Welcome to Guardian Jobs'); - - // User should not be a jobs user yet and have their original first/last name. - cy.getTestOktaUser(emailAddress).then(({ profile, status }) => { - const { firstName, lastName, isJobsUser } = profile; - expect(status).to.eq('ACTIVE'); - cy.get('input[name=firstName]').should('contain.value', firstName); - cy.get('input[name=secondName]').should('contain.value', lastName); - expect(isJobsUser).to.eq(false); - }); - - cy.get('input[name=firstName]').clear().type('First Name'); - cy.get('input[name=secondName]').clear().type('Second Name'); - - // Intercept the external redirect page. - // We just want to check that the redirect happens, not that the page loads. - cy.intercept('GET', 'https://jobs.theguardian.com/', (req) => { - req.reply(200); - }); - - cy.findByText('Continue').click(); - - // Make sure the returnURL is respected. - cy.url().should('include', 'https://jobs.theguardian.com/'); - - // User should have `isJobsUser` set to true and their First/Last name set. - cy.getTestOktaUser(emailAddress).then(({ profile, status }) => { - const { firstName, lastName, isJobsUser } = profile; - expect(status).to.eq('ACTIVE'); - expect(firstName).to.eq('First Name'); - expect(lastName).to.eq('Second Name'); - expect(isJobsUser).to.eq(true); - }); - }); - }); - }); + context('Shows the terms and conditions page on Sign In', () => { + it('visits /agree/GRS after sign in if clientId=jobs parameter is set', () => { + cy.intercept('GET', 'https://jobs.theguardian.com/', (req) => { + req.reply(200); + }); + + cy + .createTestUser({ + isUserEmailValidated: true, + }) + ?.then(({ emailAddress, finalPassword }) => { + const visitUrl = + '/signin?clientId=jobs&returnUrl=https%3A%2F%2Fjobs.theguardian.com%2F'; + cy.visit(visitUrl); + cy.get('input[name=email]').type(emailAddress); + cy.get('input[name=password]').type(finalPassword); + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.url().should('include', '/agree/GRS'); + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.url().should('include', 'https://jobs.theguardian.com/'); + }); + }); + }); + + context('Accepts Jobs terms and conditions and sets their name', () => { + it('should redirect users with an invalid session cookie to reauthenticate', () => { + // load the consents page as its on the same domain + const termsAcceptPageUrl = `https://${Cypress.env( + 'BASE_URI', + )}/agree/GRS?returnUrl=https://profile.thegulocal.com/signin?returnUrl=https%3A%2F%2Fm.code.dev-theguardian.com%2F`; + cy.setCookie('sid', 'invalid-cookie'); + cy.visit(termsAcceptPageUrl); + cy.url().should( + 'include', + 'https://profile.thegulocal.com/reauthenticate', + ); + }); + + it('should redirect users with no session cookie to the signin page', () => { + // load the consents page as its on the same domain + const termsAcceptPageUrl = `https://${Cypress.env( + 'BASE_URI', + )}/agree/GRS?returnUrl=https://profile.thegulocal.com/healthcheck`; + cy.visit(termsAcceptPageUrl); + cy.url().should( + 'include', + 'https://profile.thegulocal.com/signin?fromURI=', + ); + }); + + it('should show the jobs terms page for users who do not have first/last name set, but are jobs users', () => { + cy + .createTestUser({ + isUserEmailValidated: true, + }) + ?.then(({ emailAddress, finalPassword }) => { + cy.visit('/signin'); + cy.get('input[name=email]').type(emailAddress); + cy.get('input[name=password]').type(finalPassword); + + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.url().should('include', '/signin/success'); + + const termsAcceptPageUrl = `https://${Cypress.env( + 'BASE_URI', + )}/agree/GRS?returnUrl=https://profile.thegulocal.com/healthcheck`; + + // Create a test user without a first/last name who has `isJobsUser` set to true. + cy.updateOktaTestUserProfile(emailAddress, { + firstName: '', + lastName: '', + isJobsUser: true, + }).then(() => { + // clear oauth token cookies to simulate the user profile update + cy.clearCookie('GU_ID_TOKEN', { domain: Cypress.env('BASE_URI') }); + cy.clearCookie('GU_ACCESS_TOKEN', { + domain: Cypress.env('BASE_URI'), + }); + + cy.visit(termsAcceptPageUrl); + cy.contains('Please complete your details for'); + cy.contains(emailAddress); + cy.contains('We will use these details on your job applications'); + + cy.get('input[name=firstName]').should('be.empty'); + cy.get('input[name=secondName]').should('be.empty'); + + cy.get('input[name=firstName]').type('First Name'); + cy.get('input[name=secondName]').type('Second Name'); + + cy.findByText('Save and continue').click(); + + // User should have `isJobsUser` set to true and First/Last name set to our custom values. + cy.getTestOktaUser(emailAddress).then(({ profile, status }) => { + const { firstName, lastName, isJobsUser } = profile; + expect(status).to.eq('ACTIVE'); + expect(firstName).to.eq('First Name'); + expect(lastName).to.eq('Second Name'); + expect(isJobsUser).to.eq(true); + }); + }); + }); + }); + + it('should redirect users who have already accepted the terms away', () => { + cy + .createTestUser({ + isUserEmailValidated: true, + }) + ?.then(({ emailAddress, finalPassword }) => { + // load the consents page as its on the same domain + const termsAcceptPageUrl = `https://${Cypress.env( + 'BASE_URI', + )}/agree/GRS?returnUrl=https://profile.thegulocal.com/healthcheck`; + + cy.visit('/signin'); + cy.get('input[name=email]').type(emailAddress); + cy.get('input[name=password]').type(finalPassword); + + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.url().should('include', '/signin/success'); + + cy.updateOktaTestUserProfile(emailAddress, { + firstName: 'Test', + lastName: 'User', + isJobsUser: true, + }).then(() => { + cy.visit(termsAcceptPageUrl); + + cy.url().should( + 'include', + 'https://profile.thegulocal.com/healthcheck', + ); + + const finalTermsAcceptPageUrl = `https://${Cypress.env( + 'BASE_URI', + )}/agree/GRS?returnUrl=https://profile.thegulocal.com/maintenance`; + + cy.visit(finalTermsAcceptPageUrl, { failOnStatusCode: false }); + + // Make sure the returnURL is respected. + cy.url().should( + 'include', + 'https://profile.thegulocal.com/maintenance', + ); + }); + }); + }); + + it('should allow a non-jobs user to enter their first/last name and accept the terms', () => { + cy + .createTestUser({ + isUserEmailValidated: true, + }) + ?.then(({ emailAddress, finalPassword }) => { + // load the consents page as its on the same domain + const termsAcceptPageUrl = `https://${Cypress.env( + 'BASE_URI', + )}/agree/GRS?returnUrl=https://jobs.theguardian.com/`; + + cy.visit('/signin'); + cy.get('input[name=email]').type(emailAddress); + cy.get('input[name=password]').type(finalPassword); + + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.url().should('include', '/signin/success'); + + cy.visit(termsAcceptPageUrl); + + // check sign in has worked first + cy.url().should('include', `/agree/GRS`); + // check session cookie is set + cy.getCookie('sid').should('exist'); + // check idapi cookies are set + cy.getCookie('SC_GU_U').should('exist'); + cy.getCookie('SC_GU_LA').should('exist'); + cy.getCookie('GU_U').should('exist'); + + cy.contains('Welcome to Guardian Jobs'); + + // User should not be a jobs user yet and have their original first/last name. + cy.getTestOktaUser(emailAddress).then(({ profile, status }) => { + const { firstName, lastName, isJobsUser } = profile; + expect(status).to.eq('ACTIVE'); + cy.get('input[name=firstName]').should('contain.value', firstName); + cy.get('input[name=secondName]').should('contain.value', lastName); + expect(isJobsUser).to.eq(false); + }); + + cy.get('input[name=firstName]').clear().type('First Name'); + cy.get('input[name=secondName]').clear().type('Second Name'); + + // Intercept the external redirect page. + // We just want to check that the redirect happens, not that the page loads. + cy.intercept('GET', 'https://jobs.theguardian.com/', (req) => { + req.reply(200); + }); + + cy.findByText('Continue').click(); + + // Make sure the returnURL is respected. + cy.url().should('include', 'https://jobs.theguardian.com/'); + + // User should have `isJobsUser` set to true and their First/Last name set. + cy.getTestOktaUser(emailAddress).then(({ profile, status }) => { + const { firstName, lastName, isJobsUser } = profile; + expect(status).to.eq('ACTIVE'); + expect(firstName).to.eq('First Name'); + expect(lastName).to.eq('Second Name'); + expect(isJobsUser).to.eq(true); + }); + }); + }); + }); }); diff --git a/cypress/integration/ete-okta/onboarding_flow.5.cy.ts b/cypress/integration/ete-okta/onboarding_flow.5.cy.ts index 0dbf5c5f12..c8b46a28b4 100644 --- a/cypress/integration/ete-okta/onboarding_flow.5.cy.ts +++ b/cypress/integration/ete-okta/onboarding_flow.5.cy.ts @@ -1,7 +1,7 @@ import { setMvtId } from '../../support/commands/setMvtId'; import { - randomMailosaurEmail, - randomPassword, + randomMailosaurEmail, + randomPassword, } from '../../support/commands/testUser'; import CommunicationsPage from '../../support/pages/onboarding/communications_page'; import NewslettersPage from '../../support/pages/onboarding/newsletters_page'; @@ -9,223 +9,223 @@ import ReviewPage from '../../support/pages/onboarding/review_page'; import YourDataPage from '../../support/pages/onboarding/your_data_page'; describe('Onboarding flow', () => { - beforeEach(() => { - // Intercept the external redirect page. - // We just want to check that the redirect happens, not that the page loads. - cy.intercept('GET', 'https://m.code.dev-theguardian.com/', (req) => { - req.reply(200); - }); - setMvtId('0'); - }); + beforeEach(() => { + // Intercept the external redirect page. + // We just want to check that the redirect happens, not that the page loads. + cy.intercept('GET', 'https://m.code.dev-theguardian.com/', (req) => { + req.reply(200); + }); + setMvtId('0'); + }); - context('Full flow', () => { - it('goes through the full flow, opt in all consents/marketing, preserve returnUrl', () => { - const returnUrl = - 'https%3A%2F%2Fm.code.dev-theguardian.com%2Ftravel%2F2019%2Fdec%2F18%2Ffood-culture-tour-bethlehem-palestine-east-jerusalem-photo-essay'; - const unregisteredEmail = randomMailosaurEmail(); + context('Full flow', () => { + it('goes through the full flow, opt in all consents/marketing, preserve returnUrl', () => { + const returnUrl = + 'https%3A%2F%2Fm.code.dev-theguardian.com%2Ftravel%2F2019%2Fdec%2F18%2Ffood-culture-tour-bethlehem-palestine-east-jerusalem-photo-essay'; + const unregisteredEmail = randomMailosaurEmail(); - cy.visit(`/register?returnUrl=${returnUrl}`); + cy.visit(`/register?returnUrl=${returnUrl}`); - const timeRequestWasMade = new Date(); - cy.get('input[name=email]').type(unregisteredEmail); - cy.get('[data-cy="main-form-submit-button"]').click(); + const timeRequestWasMade = new Date(); + cy.get('input[name=email]').type(unregisteredEmail); + cy.get('[data-cy="main-form-submit-button"]').click(); - cy.contains('Check your email inbox'); - cy.contains(unregisteredEmail); - cy.contains('Resend email'); - cy.contains('Change email address'); + cy.contains('Check your email inbox'); + cy.contains(unregisteredEmail); + cy.contains('Resend email'); + cy.contains('Change email address'); - cy.checkForEmailAndGetDetails( - unregisteredEmail, - timeRequestWasMade, - /welcome\/([^"]*)/, - ).then(({ body, token }) => { - expect(body).to.have.string('Complete registration'); - cy.enableCMP(); - cy.setCookie('GU_geo_country', 'FR'); + cy.checkForEmailAndGetDetails( + unregisteredEmail, + timeRequestWasMade, + /welcome\/([^"]*)/, + ).then(({ body, token }) => { + expect(body).to.have.string('Complete registration'); + cy.enableCMP(); + cy.setCookie('GU_geo_country', 'FR'); - cy.visit(`/welcome/${token}`); - cy.acceptCMP(); + cy.visit(`/welcome/${token}`); + cy.acceptCMP(); - cy.contains('Save and continue'); + cy.contains('Save and continue'); - cy.get('input[name=password]').type(randomPassword()); + cy.get('input[name=password]').type(randomPassword()); - cy.get('[data-cy="change-password-button"]').click(); + cy.get('[data-cy="change-password-button"]').click(); - cy.url().should('include', CommunicationsPage.URL); - cy.url().should('include', `returnUrl=${returnUrl}`); + cy.url().should('include', CommunicationsPage.URL); + cy.url().should('include', `returnUrl=${returnUrl}`); - CommunicationsPage.backButton().should('not.exist'); - CommunicationsPage.allCheckboxes().should('not.be.checked'); - CommunicationsPage.allCheckboxes().click({ multiple: true }); + CommunicationsPage.backButton().should('not.exist'); + CommunicationsPage.allCheckboxes().should('not.be.checked'); + CommunicationsPage.allCheckboxes().click({ multiple: true }); - CommunicationsPage.saveAndContinueButton().click(); + CommunicationsPage.saveAndContinueButton().click(); - cy.url().should('include', NewslettersPage.URL); - cy.url().should('include', `returnUrl=${returnUrl}`); + cy.url().should('include', NewslettersPage.URL); + cy.url().should('include', `returnUrl=${returnUrl}`); - NewslettersPage.backButton() - .should('have.attr', 'href') - .and('include', CommunicationsPage.URL); + NewslettersPage.backButton() + .should('have.attr', 'href') + .and('include', CommunicationsPage.URL); - NewslettersPage.allCheckboxes() - .should('not.be.checked') - .click({ multiple: true }); + NewslettersPage.allCheckboxes() + .should('not.be.checked') + .click({ multiple: true }); - NewslettersPage.saveAndContinueButton().click(); + NewslettersPage.saveAndContinueButton().click(); - cy.url().should('include', YourDataPage.URL); - cy.url().should('include', `returnUrl=${returnUrl}`); + cy.url().should('include', YourDataPage.URL); + cy.url().should('include', `returnUrl=${returnUrl}`); - YourDataPage.backButton() - .should('have.attr', 'href') - .and('include', NewslettersPage.URL); + YourDataPage.backButton() + .should('have.attr', 'href') + .and('include', NewslettersPage.URL); - YourDataPage.personalisedAdvertisingOptInInput().should( - 'not.be.checked', - ); + YourDataPage.personalisedAdvertisingOptInInput().should( + 'not.be.checked', + ); - YourDataPage.personalisedAdvertisingOptInSwitch().click(); - YourDataPage.marketingOptInSwitch().should('be.checked'); - YourDataPage.personalisedAdvertisingOptInInput().should('be.checked'); + YourDataPage.personalisedAdvertisingOptInSwitch().click(); + YourDataPage.marketingOptInSwitch().should('be.checked'); + YourDataPage.personalisedAdvertisingOptInInput().should('be.checked'); - YourDataPage.saveAndContinueButton().click(); + YourDataPage.saveAndContinueButton().click(); - cy.url().should('include', ReviewPage.URL); - cy.url().should('include', `returnUrl=${returnUrl}`); + cy.url().should('include', ReviewPage.URL); + cy.url().should('include', `returnUrl=${returnUrl}`); - ReviewPage.backButton().should('not.exist'); - ReviewPage.saveAndContinueButton().should('not.exist'); + ReviewPage.backButton().should('not.exist'); + ReviewPage.saveAndContinueButton().should('not.exist'); - // contains opted in newsletters - cy.contains('Down to Earth'); - cy.contains('The Long Read'); - cy.contains('First Edition'); + // contains opted in newsletters + cy.contains('Down to Earth'); + cy.contains('The Long Read'); + cy.contains('First Edition'); - // contains consents - cy.contains(ReviewPage.CONTENT.SUPPORTER_CONSENT); - cy.contains(ReviewPage.CONTENT.PROFILING_CONSENT); - cy.contains(ReviewPage.CONTENT.PERSONALISED_ADVERTISING_CONSENT); + // contains consents + cy.contains(ReviewPage.CONTENT.SUPPORTER_CONSENT); + cy.contains(ReviewPage.CONTENT.PROFILING_CONSENT); + cy.contains(ReviewPage.CONTENT.PERSONALISED_ADVERTISING_CONSENT); - // does not contain messaging encouraging user to consider other newsletters - cy.contains(ReviewPage.CONTENT.NO_NEWSLETTERS_TITLE).should( - 'not.exist', - ); + // does not contain messaging encouraging user to consider other newsletters + cy.contains(ReviewPage.CONTENT.NO_NEWSLETTERS_TITLE).should( + 'not.exist', + ); - ReviewPage.returnButton() - .should('have.attr', 'href') - .and('include', decodeURIComponent(returnUrl)); + ReviewPage.returnButton() + .should('have.attr', 'href') + .and('include', decodeURIComponent(returnUrl)); - ReviewPage.returnButton().click(); + ReviewPage.returnButton().click(); - cy.url().should('include', decodeURIComponent(returnUrl)); - }); - }); + cy.url().should('include', decodeURIComponent(returnUrl)); + }); + }); - it('goes through full fow, opt out of all consents/newsletters, preserve returnUrl', () => { - const returnUrl = - 'https%3A%2F%2Fm.code.dev-theguardian.com%2Ftravel%2F2019%2Fdec%2F18%2Ffood-culture-tour-bethlehem-palestine-east-jerusalem-photo-essay'; - const unregisteredEmail = randomMailosaurEmail(); + it('goes through full fow, opt out of all consents/newsletters, preserve returnUrl', () => { + const returnUrl = + 'https%3A%2F%2Fm.code.dev-theguardian.com%2Ftravel%2F2019%2Fdec%2F18%2Ffood-culture-tour-bethlehem-palestine-east-jerusalem-photo-essay'; + const unregisteredEmail = randomMailosaurEmail(); - cy.visit(`/register?returnUrl=${returnUrl}`); + cy.visit(`/register?returnUrl=${returnUrl}`); - const timeRequestWasMade = new Date(); - cy.get('input[name=email]').type(unregisteredEmail); - cy.get('[data-cy="main-form-submit-button"]').click(); + const timeRequestWasMade = new Date(); + cy.get('input[name=email]').type(unregisteredEmail); + cy.get('[data-cy="main-form-submit-button"]').click(); - cy.contains('Check your email inbox'); - cy.contains(unregisteredEmail); - cy.contains('Resend email'); - cy.contains('Change email address'); + cy.contains('Check your email inbox'); + cy.contains(unregisteredEmail); + cy.contains('Resend email'); + cy.contains('Change email address'); - cy.checkForEmailAndGetDetails( - unregisteredEmail, - timeRequestWasMade, - /welcome\/([^"]*)/, - ).then(({ body, token }) => { - expect(body).to.have.string('Complete registration'); - cy.enableCMP(); - cy.setCookie('GU_geo_country', 'FR'); + cy.checkForEmailAndGetDetails( + unregisteredEmail, + timeRequestWasMade, + /welcome\/([^"]*)/, + ).then(({ body, token }) => { + expect(body).to.have.string('Complete registration'); + cy.enableCMP(); + cy.setCookie('GU_geo_country', 'FR'); - cy.visit(`/welcome/${token}`); - cy.acceptCMP(); + cy.visit(`/welcome/${token}`); + cy.acceptCMP(); - cy.contains('Save and continue'); + cy.contains('Save and continue'); - cy.get('input[name=password]').type(randomPassword()); + cy.get('input[name=password]').type(randomPassword()); - cy.get('[data-cy="change-password-button"]').click(); + cy.get('[data-cy="change-password-button"]').click(); - cy.url().should('include', CommunicationsPage.URL); - cy.url().should('include', `returnUrl=${returnUrl}`); + cy.url().should('include', CommunicationsPage.URL); + cy.url().should('include', `returnUrl=${returnUrl}`); - CommunicationsPage.backButton().should('not.exist'); - CommunicationsPage.allCheckboxes().should('not.be.checked'); + CommunicationsPage.backButton().should('not.exist'); + CommunicationsPage.allCheckboxes().should('not.be.checked'); - CommunicationsPage.saveAndContinueButton().click(); + CommunicationsPage.saveAndContinueButton().click(); - cy.url().should('include', NewslettersPage.URL); - cy.url().should('include', `returnUrl=${returnUrl}`); + cy.url().should('include', NewslettersPage.URL); + cy.url().should('include', `returnUrl=${returnUrl}`); - NewslettersPage.backButton() - .should('have.attr', 'href') - .and('include', CommunicationsPage.URL); + NewslettersPage.backButton() + .should('have.attr', 'href') + .and('include', CommunicationsPage.URL); - NewslettersPage.allCheckboxes().should('not.be.checked'); + NewslettersPage.allCheckboxes().should('not.be.checked'); - NewslettersPage.saveAndContinueButton().click(); + NewslettersPage.saveAndContinueButton().click(); - cy.url().should('include', YourDataPage.URL); - cy.url().should('include', `returnUrl=${returnUrl}`); + cy.url().should('include', YourDataPage.URL); + cy.url().should('include', `returnUrl=${returnUrl}`); - YourDataPage.backButton() - .should('have.attr', 'href') - .and('include', NewslettersPage.URL); + YourDataPage.backButton() + .should('have.attr', 'href') + .and('include', NewslettersPage.URL); - YourDataPage.personalisedAdvertisingOptInInput().should( - 'not.be.checked', - ); - YourDataPage.marketingOptInSwitch().should('be.checked'); + YourDataPage.personalisedAdvertisingOptInInput().should( + 'not.be.checked', + ); + YourDataPage.marketingOptInSwitch().should('be.checked'); - YourDataPage.marketingOptInSwitch().click({ force: true }); + YourDataPage.marketingOptInSwitch().click({ force: true }); - YourDataPage.marketingOptInSwitch().should('not.be.checked'); - YourDataPage.personalisedAdvertisingOptInInput().should( - 'not.be.checked', - ); + YourDataPage.marketingOptInSwitch().should('not.be.checked'); + YourDataPage.personalisedAdvertisingOptInInput().should( + 'not.be.checked', + ); - YourDataPage.saveAndContinueButton().click(); + YourDataPage.saveAndContinueButton().click(); - cy.url().should('include', ReviewPage.URL); - cy.url().should('include', `returnUrl=${returnUrl}`); + cy.url().should('include', ReviewPage.URL); + cy.url().should('include', `returnUrl=${returnUrl}`); - ReviewPage.backButton().should('not.exist'); - ReviewPage.saveAndContinueButton().should('not.exist'); + ReviewPage.backButton().should('not.exist'); + ReviewPage.saveAndContinueButton().should('not.exist'); - // contains opted in newsletters - cy.contains('Down to Earth').should('not.exist'); - cy.contains('The Long Read').should('not.exist'); - cy.contains('First Edition').should('not.exist'); + // contains opted in newsletters + cy.contains('Down to Earth').should('not.exist'); + cy.contains('The Long Read').should('not.exist'); + cy.contains('First Edition').should('not.exist'); - // contains consents - cy.contains(ReviewPage.CONTENT.SUPPORTER_CONSENT).should('not.exist'); - cy.contains(ReviewPage.CONTENT.PROFILING_CONSENT).should('not.exist'); - cy.contains(ReviewPage.CONTENT.PERSONALISED_ADVERTISING_CONSENT).should( - 'not.exist', - ); + // contains consents + cy.contains(ReviewPage.CONTENT.SUPPORTER_CONSENT).should('not.exist'); + cy.contains(ReviewPage.CONTENT.PROFILING_CONSENT).should('not.exist'); + cy.contains(ReviewPage.CONTENT.PERSONALISED_ADVERTISING_CONSENT).should( + 'not.exist', + ); - // contains messaging encouraging user to consider other newsletters - cy.contains(ReviewPage.CONTENT.NO_NEWSLETTERS_TITLE).should('exist'); + // contains messaging encouraging user to consider other newsletters + cy.contains(ReviewPage.CONTENT.NO_NEWSLETTERS_TITLE).should('exist'); - ReviewPage.returnButton() - .should('have.attr', 'href') - .and('include', decodeURIComponent(returnUrl)); + ReviewPage.returnButton() + .should('have.attr', 'href') + .and('include', decodeURIComponent(returnUrl)); - ReviewPage.returnButton().click(); + ReviewPage.returnButton().click(); - cy.url().should('include', decodeURIComponent(returnUrl)); - }); - }); - }); + cy.url().should('include', decodeURIComponent(returnUrl)); + }); + }); + }); }); diff --git a/cypress/integration/ete-okta/post_sign_in_prompt.5.cy.ts b/cypress/integration/ete-okta/post_sign_in_prompt.5.cy.ts index d01ef50cf1..ab48e6517b 100644 --- a/cypress/integration/ete-okta/post_sign_in_prompt.5.cy.ts +++ b/cypress/integration/ete-okta/post_sign_in_prompt.5.cy.ts @@ -1,105 +1,105 @@ describe('Post sign-in prompt', () => { - beforeEach(() => { - // Intercept the external redirect page. - // We just want to check that the redirect happens, not that the page loads. - cy.intercept('GET', 'https://m.code.dev-theguardian.com/', (req) => { - req.reply(200); - }); - cy.clearCookie('GU_ran_experiments'); - }); + beforeEach(() => { + // Intercept the external redirect page. + // We just want to check that the redirect happens, not that the page loads. + cy.intercept('GET', 'https://m.code.dev-theguardian.com/', (req) => { + req.reply(200); + }); + cy.clearCookie('GU_ran_experiments'); + }); - it('allows user to opt in and continue', () => { - cy - .createTestUser({ - isUserEmailValidated: true, - }) - ?.then(({ emailAddress, finalPassword }) => { - const visitUrl = '/signin'; - cy.visit(visitUrl); - cy.get('input[name=email]').type(emailAddress); - cy.get('input[name=password]').type(finalPassword); - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.url().should('include', `/signin/success`); - cy.url().should( - 'include', - `returnUrl=${encodeURIComponent( - 'https://m.code.dev-theguardian.com/', - )}`, - ); - const checkbox = cy.findByLabelText('Yes, sign me up'); - checkbox.should('not.be.checked'); - checkbox.click(); - checkbox.should('be.checked'); - cy.findByText('Continue to the Guardian').click(); - cy.url().should('include', 'https://m.code.dev-theguardian.com/'); - }); - }); + it('allows user to opt in and continue', () => { + cy + .createTestUser({ + isUserEmailValidated: true, + }) + ?.then(({ emailAddress, finalPassword }) => { + const visitUrl = '/signin'; + cy.visit(visitUrl); + cy.get('input[name=email]').type(emailAddress); + cy.get('input[name=password]').type(finalPassword); + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.url().should('include', `/signin/success`); + cy.url().should( + 'include', + `returnUrl=${encodeURIComponent( + 'https://m.code.dev-theguardian.com/', + )}`, + ); + const checkbox = cy.findByLabelText('Yes, sign me up'); + checkbox.should('not.be.checked'); + checkbox.click(); + checkbox.should('be.checked'); + cy.findByText('Continue to the Guardian').click(); + cy.url().should('include', 'https://m.code.dev-theguardian.com/'); + }); + }); - it('allows user to opt out and continue', () => { - cy - .createTestUser({ - isUserEmailValidated: true, - }) - ?.then(({ emailAddress, finalPassword }) => { - const visitUrl = '/signin'; - cy.visit(visitUrl); - cy.get('input[name=email]').type(emailAddress); - cy.get('input[name=password]').type(finalPassword); - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.url().should('include', `/signin/success`); - cy.url().should( - 'include', - `returnUrl=${encodeURIComponent( - 'https://m.code.dev-theguardian.com/', - )}`, - ); - const checkbox = cy.findByLabelText('Yes, sign me up'); - checkbox.should('not.be.checked'); + it('allows user to opt out and continue', () => { + cy + .createTestUser({ + isUserEmailValidated: true, + }) + ?.then(({ emailAddress, finalPassword }) => { + const visitUrl = '/signin'; + cy.visit(visitUrl); + cy.get('input[name=email]').type(emailAddress); + cy.get('input[name=password]').type(finalPassword); + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.url().should('include', `/signin/success`); + cy.url().should( + 'include', + `returnUrl=${encodeURIComponent( + 'https://m.code.dev-theguardian.com/', + )}`, + ); + const checkbox = cy.findByLabelText('Yes, sign me up'); + checkbox.should('not.be.checked'); - cy.findByText('Continue to the Guardian').click(); - cy.url().should('include', 'https://m.code.dev-theguardian.com/'); - }); - }); + cy.findByText('Continue to the Guardian').click(); + cy.url().should('include', 'https://m.code.dev-theguardian.com/'); + }); + }); - it("doesn't show the prompt if the user has already seen it", () => { - cy - .createTestUser({ - isUserEmailValidated: true, - }) - ?.then(({ emailAddress, finalPassword }) => { - const returnUrl = `https://${Cypress.env( - 'BASE_URI', - )}/signout?returnUrl=${encodeURIComponent( - `https://${Cypress.env('BASE_URI')}/signin`, - )}`; - const visitUrl = `/signin?returnUrl=${encodeURIComponent(returnUrl)}`; - cy.visit(visitUrl); - cy.get('input[name=email]').type(emailAddress); - cy.get('input[name=password]').type(finalPassword); - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.url().should('include', `/signin/success`); - cy.url().should( - 'include', - `returnUrl=${encodeURIComponent( - 'https://m.code.dev-theguardian.com/', - )}`, - ); - const checkbox = cy.findByLabelText('Yes, sign me up'); - checkbox.should('not.be.checked'); - checkbox.click(); - checkbox.should('be.checked'); - cy.findByText('Continue to the Guardian').click(); - cy.url().should('include', 'https://m.code.dev-theguardian.com/'); - cy.visit( - `/signout?returnUrl=${encodeURIComponent( - `https://${Cypress.env('BASE_URI')}/signin`, - )}`, - ); - cy.get('input[name=email]').type(emailAddress); - cy.get('input[name=password]').type(finalPassword); - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.url().should('not.include', `/signin/success`); - cy.url().should('include', 'https://m.code.dev-theguardian.com/'); - }); - }); + it("doesn't show the prompt if the user has already seen it", () => { + cy + .createTestUser({ + isUserEmailValidated: true, + }) + ?.then(({ emailAddress, finalPassword }) => { + const returnUrl = `https://${Cypress.env( + 'BASE_URI', + )}/signout?returnUrl=${encodeURIComponent( + `https://${Cypress.env('BASE_URI')}/signin`, + )}`; + const visitUrl = `/signin?returnUrl=${encodeURIComponent(returnUrl)}`; + cy.visit(visitUrl); + cy.get('input[name=email]').type(emailAddress); + cy.get('input[name=password]').type(finalPassword); + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.url().should('include', `/signin/success`); + cy.url().should( + 'include', + `returnUrl=${encodeURIComponent( + 'https://m.code.dev-theguardian.com/', + )}`, + ); + const checkbox = cy.findByLabelText('Yes, sign me up'); + checkbox.should('not.be.checked'); + checkbox.click(); + checkbox.should('be.checked'); + cy.findByText('Continue to the Guardian').click(); + cy.url().should('include', 'https://m.code.dev-theguardian.com/'); + cy.visit( + `/signout?returnUrl=${encodeURIComponent( + `https://${Cypress.env('BASE_URI')}/signin`, + )}`, + ); + cy.get('input[name=email]').type(emailAddress); + cy.get('input[name=password]').type(finalPassword); + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.url().should('not.include', `/signin/success`); + cy.url().should('include', 'https://m.code.dev-theguardian.com/'); + }); + }); }); diff --git a/cypress/integration/ete-okta/reauthenticate.4.cy.ts b/cypress/integration/ete-okta/reauthenticate.4.cy.ts index 505d455c4a..d1d216f716 100644 --- a/cypress/integration/ete-okta/reauthenticate.4.cy.ts +++ b/cypress/integration/ete-okta/reauthenticate.4.cy.ts @@ -1,100 +1,100 @@ describe('Reauthenticate flow, Okta enabled', () => { - beforeEach(() => { - // Disable redirect to /signin/success by default - cy.setCookie( - 'GU_ran_experiments', - new URLSearchParams({ - OptInPromptPostSignIn: Date.now().toString(), - }).toString(), - ); - }); - it('keeps User A signed in when User A attempts to reauthenticate', () => { - cy - .createTestUser({ isUserEmailValidated: true }) - ?.then(({ emailAddress, finalPassword }) => { - // First, sign in - cy.visit( - `/signin?returnUrl=${encodeURIComponent( - `https://${Cypress.env('BASE_URI')}/consents/data`, - )}`, - ); - cy.get('input[name=email]').type(emailAddress); - cy.get('input[name=password]').type(finalPassword); - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.url().should('include', '/consents/data'); + beforeEach(() => { + // Disable redirect to /signin/success by default + cy.setCookie( + 'GU_ran_experiments', + new URLSearchParams({ + OptInPromptPostSignIn: Date.now().toString(), + }).toString(), + ); + }); + it('keeps User A signed in when User A attempts to reauthenticate', () => { + cy + .createTestUser({ isUserEmailValidated: true }) + ?.then(({ emailAddress, finalPassword }) => { + // First, sign in + cy.visit( + `/signin?returnUrl=${encodeURIComponent( + `https://${Cypress.env('BASE_URI')}/consents/data`, + )}`, + ); + cy.get('input[name=email]').type(emailAddress); + cy.get('input[name=password]').type(finalPassword); + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.url().should('include', '/consents/data'); - // Then, try to reauthenticate - cy.visit( - `/reauthenticate?returnUrl=${encodeURIComponent( - `https://${Cypress.env('BASE_URI')}/consents/data`, - )}`, - ); - cy.get('input[name=email]').type(emailAddress); - cy.get('input[name=password]').type(finalPassword); - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.url().should('include', '/consents/data'); + // Then, try to reauthenticate + cy.visit( + `/reauthenticate?returnUrl=${encodeURIComponent( + `https://${Cypress.env('BASE_URI')}/consents/data`, + )}`, + ); + cy.get('input[name=email]').type(emailAddress); + cy.get('input[name=password]').type(finalPassword); + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.url().should('include', '/consents/data'); - // Get the current session data - cy.getCookie('sid').then((sidCookie) => { - const sid = sidCookie?.value; - expect(sid).to.exist; - if (sid) { - cy.getCurrentOktaSession(sid).then((session) => { - expect(session.login).to.equal(emailAddress); - }); - } - }); - }); - }); - it('signs in User B when User B attempts to reauthenticate while User A is logged in', () => { - // Create User A - cy - .createTestUser({ isUserEmailValidated: true }) - ?.then( - ({ emailAddress: emailAddressA, finalPassword: finalPasswordA }) => { - // First, sign in as User A - cy.visit( - `/signin?returnUrl=${encodeURIComponent( - `https://${Cypress.env('BASE_URI')}/consents/data`, - )}`, - ); - cy.get('input[name=email]').type(emailAddressA); - cy.get('input[name=password]').type(finalPasswordA); - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.url().should('include', '/consents/data'); + // Get the current session data + cy.getCookie('sid').then((sidCookie) => { + const sid = sidCookie?.value; + expect(sid).to.exist; + if (sid) { + cy.getCurrentOktaSession(sid).then((session) => { + expect(session.login).to.equal(emailAddress); + }); + } + }); + }); + }); + it('signs in User B when User B attempts to reauthenticate while User A is logged in', () => { + // Create User A + cy + .createTestUser({ isUserEmailValidated: true }) + ?.then( + ({ emailAddress: emailAddressA, finalPassword: finalPasswordA }) => { + // First, sign in as User A + cy.visit( + `/signin?returnUrl=${encodeURIComponent( + `https://${Cypress.env('BASE_URI')}/consents/data`, + )}`, + ); + cy.get('input[name=email]').type(emailAddressA); + cy.get('input[name=password]').type(finalPasswordA); + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.url().should('include', '/consents/data'); - // Create User B - cy - .createTestUser({ isUserEmailValidated: true }) - ?.then( - ({ - emailAddress: emailAddressB, - finalPassword: finalPasswordB, - }) => { - // Then, try to reauthenticate as User B - cy.visit( - `/reauthenticate?returnUrl=${encodeURIComponent( - `https://${Cypress.env('BASE_URI')}/consents/data`, - )}`, - ); - cy.get('input[name=email]').type(emailAddressB); - cy.get('input[name=password]').type(finalPasswordB); - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.url().should('include', '/consents/data'); + // Create User B + cy + .createTestUser({ isUserEmailValidated: true }) + ?.then( + ({ + emailAddress: emailAddressB, + finalPassword: finalPasswordB, + }) => { + // Then, try to reauthenticate as User B + cy.visit( + `/reauthenticate?returnUrl=${encodeURIComponent( + `https://${Cypress.env('BASE_URI')}/consents/data`, + )}`, + ); + cy.get('input[name=email]').type(emailAddressB); + cy.get('input[name=password]').type(finalPasswordB); + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.url().should('include', '/consents/data'); - // Get the current session data - cy.getCookie('sid').then((sidCookie) => { - const sid = sidCookie?.value; - expect(sid).to.exist; - if (sid) { - cy.getCurrentOktaSession(sid).then((session) => { - expect(session.login).to.equal(emailAddressB); - }); - } - }); - }, - ); - }, - ); - }); + // Get the current session data + cy.getCookie('sid').then((sidCookie) => { + const sid = sidCookie?.value; + expect(sid).to.exist; + if (sid) { + cy.getCurrentOktaSession(sid).then((session) => { + expect(session.login).to.equal(emailAddressB); + }); + } + }); + }, + ); + }, + ); + }); }); diff --git a/cypress/integration/ete-okta/registration.2.cy.ts b/cypress/integration/ete-okta/registration.2.cy.ts index 0ef33fa912..08e3987a3e 100644 --- a/cypress/integration/ete-okta/registration.2.cy.ts +++ b/cypress/integration/ete-okta/registration.2.cy.ts @@ -1,1338 +1,1338 @@ import { - randomMailosaurEmail, - randomPassword, + randomMailosaurEmail, + randomPassword, } from '../../support/commands/testUser'; import { Status } from '../../../src/server/models/okta/User'; describe('Registration flow', () => { - context('Registering with Okta', () => { - it('successfully registers using an email with no existing account', () => { - const encodedReturnUrl = - 'https%3A%2F%2Fm.code.dev-theguardian.com%2Ftravel%2F2019%2Fdec%2F18%2Ffood-culture-tour-bethlehem-palestine-east-jerusalem-photo-essay'; - const unregisteredEmail = randomMailosaurEmail(); - const encodedRef = 'https%3A%2F%2Fm.theguardian.com'; - const refViewId = 'testRefViewId'; - const clientId = 'jobs'; - - // these params should *not* persist between initial registration and welcome page - // despite the fact that they PersistableQueryParams, as these are set by the Okta SDK sign in method - // and subsequent interception, and not by gateway - const appClientId = 'appClientId1'; - const fromURI = 'fromURI1'; - - cy.visit( - `/register?returnUrl=${encodedReturnUrl}&ref=${encodedRef}&refViewId=${refViewId}&clientId=${clientId}&appClientId=${appClientId}&fromURI=${fromURI}`, - ); - - const timeRequestWasMade = new Date(); - cy.get('input[name=email]').type(unregisteredEmail); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains('Check your email inbox'); - cy.contains(unregisteredEmail); - cy.contains('Resend email'); - cy.contains('Change email address'); - - cy.checkForEmailAndGetDetails( - unregisteredEmail, - timeRequestWasMade, - /welcome\/([^"]*)/, - ).then(({ body, token }) => { - expect(body).to.have.string('Complete registration'); - cy.visit(`/welcome/${token}`); - cy.contains('Save and continue'); - - cy.get('form') - .should('have.attr', 'action') - .and('match', new RegExp(encodedReturnUrl)) - .and('match', new RegExp(refViewId)) - .and('match', new RegExp(encodedRef)) - .and('match', new RegExp(clientId)) - .and('not.match', new RegExp(appClientId)) - .and('not.match', new RegExp(fromURI)); - - //we are reloading here to make sure the params are persisted even on page refresh - cy.reload(); - - cy.get('input[name="firstName"]').type('First Name'); - cy.get('input[name="secondName"]').type('Last Name'); - cy.get('input[name="password"]').type(randomPassword()); - cy.get('button[type="submit"]').click(); - cy.url().should('contain', encodedReturnUrl); - cy.url().should('contain', refViewId); - cy.url().should('contain', encodedRef); - cy.url().should('contain', clientId); - cy.url().should('not.contain', appClientId); - cy.url().should('not.contain', fromURI); - }); - }); - - it('successfully registers using an email with no existing account, and has a prefixed activation token when using a native app', () => { - const encodedReturnUrl = - 'https%3A%2F%2Fm.code.dev-theguardian.com%2Ftravel%2F2019%2Fdec%2F18%2Ffood-culture-tour-bethlehem-palestine-east-jerusalem-photo-essay'; - const unregisteredEmail = randomMailosaurEmail(); - const encodedRef = 'https%3A%2F%2Fm.theguardian.com'; - const refViewId = 'testRefViewId'; - const clientId = 'jobs'; - - // these params should *not* persist between initial registration and welcome page - // despite the fact that they PersistableQueryParams, as these are set by the Okta SDK sign in method - // and subsequent interception, and not by gateway - const appClientId = Cypress.env('OKTA_ANDROID_CLIENT_ID'); - const fromURI = 'fromURI1'; - - cy.visit( - `/register?returnUrl=${encodedReturnUrl}&ref=${encodedRef}&refViewId=${refViewId}&clientId=${clientId}&appClientId=${appClientId}&fromURI=${fromURI}`, - ); - - const timeRequestWasMade = new Date(); - cy.get('input[name=email]').type(unregisteredEmail); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains('Check your email inbox'); - cy.contains(unregisteredEmail); - cy.contains('Resend email'); - cy.contains('Change email address'); - - cy.checkForEmailAndGetDetails( - unregisteredEmail, - timeRequestWasMade, - /welcome\/([^"]*)/, - ).then(({ body, token }) => { - expect(body).to.have.string('Complete registration'); - expect(token).to.have.string('al_'); - cy.visit(`/welcome/${token}`); - cy.contains('Save and continue'); - - cy.get('form') - .should('have.attr', 'action') - .and('match', new RegExp(encodedReturnUrl)) - .and('match', new RegExp(refViewId)) - .and('match', new RegExp(encodedRef)) - .and('match', new RegExp(clientId)) - .and('not.match', new RegExp(appClientId)) - .and('not.match', new RegExp(fromURI)); - - //we are reloading here to make sure the params are persisted even on page refresh - cy.reload(); - - cy.get('input[name="firstName"]').type('First Name'); - cy.get('input[name="secondName"]').type('Last Name'); - cy.get('input[name="password"]').type(randomPassword()); - cy.get('button[type="submit"]').click(); - cy.url().should('contain', encodedReturnUrl); - cy.url().should('contain', refViewId); - cy.url().should('contain', encodedRef); - cy.url().should('contain', clientId); - cy.url().should('not.contain', appClientId); - cy.url().should('not.contain', fromURI); - }); - }); - - it('does not register registrationLocation for email with no existing account if cmp is not consented', () => { - const unregisteredEmail = randomMailosaurEmail(); - cy.enableCMP(); - cy.visit(`/register`); - cy.setCookie('GU_geo_country', 'FR'); - cy.declineCMP(); - - cy.get('input[name=email]').type(unregisteredEmail); - cy.get('input[name=_cmpConsentedState]').should('have.value', 'false'); - - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.intercept('POST', '/register**', (req) => { - expect(req.body).to.include('_cmpConsentedState=false'); - expect(req.headers.cookie).to.include('GU_geo_country=FR'); - }); - }); - - it('successfully registers registrationLocation for email with no existing account if cmp consented', () => { - const unregisteredEmail = randomMailosaurEmail(); - cy.enableCMP(); - cy.visit(`/register`); - cy.setCookie('GU_geo_country', 'FR'); - cy.acceptCMP(); - - cy.get('input[name=email]').type(unregisteredEmail); - cy.get('input[name=_cmpConsentedState]').should('have.value', 'true'); - - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.intercept('POST', '/register**', (req) => { - expect(req.body).to.include('_cmpConsentedState=true'); - expect(req.headers.cookie).to.include('GU_geo_country=FR'); - }); - }); - - it('successfully blocks the password set page /welcome if a password has already been set', () => { - const unregisteredEmail = randomMailosaurEmail(); - cy.visit(`/register`); - - const timeRequestWasMade = new Date(); - cy.get('input[name=email]').type(unregisteredEmail); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains('Check your email inbox'); - cy.contains(unregisteredEmail); - cy.contains('Resend email'); - cy.contains('Change email address'); - - cy.checkForEmailAndGetDetails( - unregisteredEmail, - timeRequestWasMade, - /welcome\/([^"]*)/, - ).then(({ body, token }) => { - expect(body).to.have.string('Complete registration'); - cy.visit(`/welcome/${token}`); - cy.contains('Save and continue'); - - cy.get('input[name="password"]').type(randomPassword()); - cy.get('button[type="submit"]').click(); - cy.url().should('contain', '/consents/'); - cy.go('back'); - cy.url().should('contain', '/welcome/'); - cy.contains('Password already set for'); - }); - }); - - it('completes registration and overrides returnUrl from encryptedStateCookie if one set on welcome page url', () => { - const encodedReturnUrl = - 'https%3A%2F%2Fm.code.dev-theguardian.com%2Ftravel%2F2019%2Fdec%2F18%2Ffood-culture-tour-bethlehem-palestine-east-jerusalem-photo-essay'; - const unregisteredEmail = randomMailosaurEmail(); - - cy.visit(`/register?returnUrl=${encodedReturnUrl}`); - - const timeRequestWasMade = new Date(); - cy.get('input[name=email]').type(unregisteredEmail); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains('Check your email inbox'); - cy.contains(unregisteredEmail); - cy.contains('Resend email'); - cy.contains('Change email address'); - - cy.checkForEmailAndGetDetails( - unregisteredEmail, - timeRequestWasMade, - /welcome\/([^"]*)/, - ).then(({ body, token }) => { - expect(body).to.have.string('Complete registration'); - const newReturnUrl = encodeURIComponent( - 'https://www.theguardian.com/technology/2017/may/04/nier-automata-sci-fi-game-sleeper-hit-designer-yoko-taro', - ); - cy.visit(`/welcome/${token}?returnUrl=${newReturnUrl}`); - cy.contains('Save and continue'); - cy.url() - .should('contain', newReturnUrl) - .and('not.contain', encodedReturnUrl); - - cy.get('form') - .should('have.attr', 'action') - .and('match', new RegExp(newReturnUrl)) - .and('not.match', new RegExp(encodedReturnUrl)); - - //we are reloading here to make sure the params are persisted even on page refresh - cy.reload(); - - cy.get('input[name="password"]').type(randomPassword()); - cy.get('button[type="submit"]').click(); - cy.url() - .should('contain', newReturnUrl) - .and('not.contain', encodedReturnUrl); - }); - }); - - it('overrides appClientId and fromURI if set on activation link', () => { - const encodedReturnUrl = - 'https%3A%2F%2Fm.code.dev-theguardian.com%2Ftravel%2F2019%2Fdec%2F18%2Ffood-culture-tour-bethlehem-palestine-east-jerusalem-photo-essay'; - const unregisteredEmail = randomMailosaurEmail(); - - // these params should *not* persist between initial registration and welcome page - // despite the fact that they PersistableQueryParams, as these are set by the Okta SDK sign in method - // and subsequent interception, and not by gateway - const appClientId1 = 'appClientId1'; - const fromURI1 = 'fromURI1'; - - cy.visit( - `/register?returnUrl=${encodedReturnUrl}&appClientId=${appClientId1}&fromURI=${fromURI1}`, - ); - - const timeRequestWasMade = new Date(); - cy.get('input[name=email]').type(unregisteredEmail); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains('Check your email inbox'); - cy.contains(unregisteredEmail); - cy.contains('Resend email'); - cy.contains('Change email address'); - - cy.checkForEmailAndGetDetails( - unregisteredEmail, - timeRequestWasMade, - /welcome\/([^"]*)/, - ).then(({ body, token }) => { - expect(body).to.have.string('Complete registration'); - // should contain these params instead - const appClientId2 = 'appClientId2'; - const fromURI2 = 'fromURI2'; - cy.visit( - `/welcome/${token}?appClientId=${appClientId2}&fromURI=${fromURI2}`, - ); - cy.contains('Save and continue'); - cy.url() - .should('contain', appClientId2) - .and('contain', fromURI2) - .and('not.contain', appClientId1) - .and('not.contain', fromURI1); - - cy.get('form') - .should('have.attr', 'action') - .and('match', new RegExp(appClientId2)) - .and('match', new RegExp(fromURI2)) - .and('not.match', new RegExp(appClientId1)) - .and('not.match', new RegExp(fromURI1)); - }); - }); - }); - context('Existing users attempting to register with Okta', () => { - it('should send a STAGED user a set password email with an Okta activation token', () => { - // Test users created via IDAPI-with-Okta do not have the activation - // lifecycle run at creation, so they don't transition immediately from - // STAGED to PROVISIONED (c.f. - // https://developer.okta.com/docs/reference/api/users/#create-user) . - // This is useful for us as we can test STAGED users first, then test - // PROVISIONED users in the next test by activating a STAGED user. Users - // created through Gateway-with-Okta do have this lifecycle run, so if we - // rebuild these tests to not use IDAPI at all, we need to figure out a - // way to test STAGED and PROVISIONED users (probably by just passing an - // optional `activate` prop to a createUser function). - cy - .createTestUser({ - isGuestUser: true, - isUserEmailValidated: true, - }) - ?.then(({ emailAddress }) => { - cy.getTestOktaUser(emailAddress).then((oktaUser) => { - expect(oktaUser.status).to.eq(Status.STAGED); - - cy.visit('/register'); - const timeRequestWasMade = new Date(); - - cy.get('input[name=email]').type(emailAddress); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains('Check your email inbox'); - cy.contains(emailAddress); - cy.contains('Resend email'); - cy.contains('Change email address'); - - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /\/set-password\/([^"]*)/, - ).then(({ links, body }) => { - expect(body).to.have.string('This account already exists'); - - expect(body).to.have.string('Create password'); - expect(links.length).to.eq(2); - const setPasswordLink = links.find( - (s) => s.text?.includes('Create password'), - ); - expect(setPasswordLink?.href).not.to.have.string('useOkta=true'); - cy.visit(setPasswordLink?.href as string); - cy.contains('Create password'); - cy.contains(emailAddress); - }); - }); - }); - }); - - it('should send a STAGED user a set password email with an Okta activation token, and has a prefixed activation token when using a native app', () => { - // Test users created via IDAPI-with-Okta do not have the activation - // lifecycle run at creation, so they don't transition immediately from - // STAGED to PROVISIONED (c.f. - // https://developer.okta.com/docs/reference/api/users/#create-user) . - // This is useful for us as we can test STAGED users first, then test - // PROVISIONED users in the next test by activating a STAGED user. Users - // created through Gateway-with-Okta do have this lifecycle run, so if we - // rebuild these tests to not use IDAPI at all, we need to figure out a - // way to test STAGED and PROVISIONED users (probably by just passing an - // optional `activate` prop to a createUser function). - cy - .createTestUser({ - isGuestUser: true, - isUserEmailValidated: true, - }) - ?.then(({ emailAddress }) => { - cy.getTestOktaUser(emailAddress).then((oktaUser) => { - expect(oktaUser.status).to.eq(Status.STAGED); - - const appClientId = Cypress.env('OKTA_ANDROID_CLIENT_ID'); - const fromURI = 'fromURI1'; - - cy.visit(`/register?appClientId=${appClientId}&fromURI=${fromURI}`); - const timeRequestWasMade = new Date(); - - cy.get('input[name=email]').type(emailAddress); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains('Check your email inbox'); - cy.contains(emailAddress); - cy.contains('Resend email'); - cy.contains('Change email address'); - - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /\/set-password\/([^"]*)/, - ).then(({ links, body }) => { - expect(body).to.have.string('This account already exists'); - - expect(body).to.have.string('Create password'); - expect(links.length).to.eq(2); - const setPasswordLink = links.find( - (s) => s.text?.includes('Create password'), - ); - expect(setPasswordLink?.href ?? '') - .to.have.string('al_') - .and.not.to.have.string('useOkta=true'); - cy.visit(setPasswordLink?.href as string); - cy.contains('Create password'); - cy.contains(emailAddress); - }); - }); - }); - }); - - it('should send a PROVISIONED user a set password email with an Okta activation token', () => { - cy - .createTestUser({ - isGuestUser: true, - isUserEmailValidated: true, - }) - ?.then(({ emailAddress }) => { - cy.activateTestOktaUser(emailAddress).then(() => { - cy.getTestOktaUser(emailAddress).then((oktaUser) => { - expect(oktaUser.status).to.eq(Status.PROVISIONED); - - cy.visit('/register'); - const timeRequestWasMade = new Date(); - - cy.get('input[name=email]').type(emailAddress); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains('Check your email inbox'); - cy.contains(emailAddress); - cy.contains('Resend email'); - cy.contains('Change email address'); - - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /\/set-password\/([^"]*)/, - ).then(({ links, body }) => { - expect(body).to.have.string('This account already exists'); - expect(body).to.have.string('Create password'); - expect(links.length).to.eq(2); - const setPasswordLink = links.find( - (s) => s.text?.includes('Create password'), - ); - expect(setPasswordLink?.href).not.to.have.string( - 'useOkta=true', - ); - cy.visit(setPasswordLink?.href as string); - cy.contains('Create password'); - cy.contains(emailAddress); - }); - }); - }); - }); - }); - it('should send an ACTIVE UNvalidated user with a password a security email with activation token', () => { - cy - .createTestUser({ - isGuestUser: false, - isUserEmailValidated: false, - }) - ?.then(({ emailAddress }) => { - cy.getTestOktaUser(emailAddress).then((oktaUser) => { - expect(oktaUser.status).to.eq(Status.ACTIVE); - - cy.visit('/register'); - const timeRequestWasMade = new Date(); - - cy.get('input[name=email]').type(emailAddress); - cy.get('[data-cy="main-form-submit-button"]').click(); - - // Make sure that we don't get sent to the 'security reasons' page - cy.url().should('include', '/register/email-sent'); - cy.contains( - 'For security reasons we need you to change your password.', - ).should('not.exist'); - cy.contains(emailAddress); - cy.contains('Resend email'); - cy.contains('Change email address'); - - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /reset-password\/([^"]*)/, - ).then(({ links, body }) => { - expect(body).to.have.string( - 'Because your security is extremely important to us, we have changed our password policy.', - ); - expect(body).to.have.string('Reset password'); - expect(links.length).to.eq(2); - const resetPasswordLink = links.find( - (s) => s.text?.includes('Reset password'), - ); - cy.visit(resetPasswordLink?.href as string); - cy.contains(emailAddress); - cy.contains('Reset password'); - }); - }); - }); - }); - it('should send an ACTIVE validated user WITH a password a reset password email with an activation token', () => { - cy - .createTestUser({ - isGuestUser: false, - isUserEmailValidated: true, - }) - ?.then(({ emailAddress }) => { - cy.getTestOktaUser(emailAddress).then((oktaUser) => { - expect(oktaUser.status).to.eq(Status.ACTIVE); - - cy.visit('/register'); - const timeRequestWasMade = new Date(); - - cy.get('input[name=email]').type(emailAddress); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains('Check your email inbox'); - cy.contains(emailAddress); - cy.contains('Resend email'); - cy.contains('Change email address'); - - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /reset-password\/([^"]*)/, - ).then(({ links, body }) => { - expect(body).to.have.string('This account already exists'); - expect(body).to.have.string('Sign in'); - expect(body).to.have.string('Reset password'); - expect(links.length).to.eq(3); - const resetPasswordLink = links.find( - (s) => s.text?.includes('Reset password'), - ); - expect(resetPasswordLink?.href ?? '').not.to.have.string( - 'useOkta=true', - ); - cy.visit(resetPasswordLink?.href as string); - cy.contains(emailAddress); - cy.contains('Reset password'); - }); - }); - }); - }); - it('should send an ACTIVE validated user WITH a password a reset password email with an activation token, and prefixed activation token if using native app', () => { - cy - .createTestUser({ - isGuestUser: false, - isUserEmailValidated: true, - }) - ?.then(({ emailAddress }) => { - cy.getTestOktaUser(emailAddress).then((oktaUser) => { - expect(oktaUser.status).to.eq(Status.ACTIVE); - - const appClientId = Cypress.env('OKTA_ANDROID_CLIENT_ID'); - const fromURI = 'fromURI1'; - - cy.visit(`/register?appClientId=${appClientId}&fromURI=${fromURI}`); - const timeRequestWasMade = new Date(); - - cy.get('input[name=email]').type(emailAddress); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains('Check your email inbox'); - cy.contains(emailAddress); - cy.contains('Resend email'); - cy.contains('Change email address'); - - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /reset-password\/([^"]*)/, - ).then(({ links, body }) => { - expect(body).to.have.string('This account already exists'); - expect(body).to.have.string('Sign in'); - expect(body).to.have.string('Reset password'); - expect(links.length).to.eq(3); - const resetPasswordLink = links.find( - (s) => s.text?.includes('Reset password'), - ); - expect(resetPasswordLink?.href ?? '') - .to.have.string('al_') - .and.not.to.have.string('useOkta=true'); - cy.visit(resetPasswordLink?.href as string); - cy.contains(emailAddress); - cy.contains('Reset password'); - }); - }); - }); - }); - it('should send a RECOVERY user a reset password email with an Okta activation token', () => { - cy - .createTestUser({ - isGuestUser: false, - isUserEmailValidated: true, - }) - ?.then(({ emailAddress }) => { - cy.resetOktaUserPassword(emailAddress).then(() => { - cy.getTestOktaUser(emailAddress).then((oktaUser) => { - expect(oktaUser.status).to.eq(Status.RECOVERY); - - cy.visit('/register'); - const timeRequestWasMade = new Date(); - - cy.get('input[name=email]').type(emailAddress); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains('Check your email inbox'); - cy.contains(emailAddress); - cy.contains('Resend email'); - cy.contains('Change email address'); - - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /reset-password\/([^"]*)/, - ).then(({ links, body }) => { - expect(body).to.have.string('Password reset'); - expect(body).to.have.string('Reset password'); - expect(links.length).to.eq(2); - const resetPasswordLink = links.find( - (s) => s.text?.includes('Reset password'), - ); - expect(resetPasswordLink?.href ?? '').not.to.have.string( - 'useOkta=true', - ); - cy.visit(resetPasswordLink?.href as string); - cy.contains('Reset password'); - cy.contains(emailAddress); - }); - }); - }); - }); - }); - it('should send a PASSWORD_EXPIRED user a reset password email with an Okta activation token', () => { - cy - .createTestUser({ - isGuestUser: false, - isUserEmailValidated: true, - }) - ?.then(({ emailAddress }) => { - cy.expireOktaUserPassword(emailAddress).then(() => { - cy.getTestOktaUser(emailAddress).then((oktaUser) => { - expect(oktaUser.status).to.eq(Status.PASSWORD_EXPIRED); - - cy.visit('/register'); - const timeRequestWasMade = new Date(); - - cy.get('input[name=email]').type(emailAddress); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains('Check your email inbox'); - cy.contains(emailAddress); - cy.contains('Resend email'); - cy.contains('Change email address'); - - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /reset-password\/([^"]*)/, - ).then(({ links, body }) => { - expect(body).to.have.string('Password reset'); - expect(body).to.have.string('Reset password'); - expect(links.length).to.eq(2); - const resetPasswordLink = links.find( - (s) => s.text?.includes('Reset password'), - ); - expect(resetPasswordLink?.href ?? '').not.to.have.string( - 'useOkta=true', - ); - cy.visit(resetPasswordLink?.href as string); - cy.contains('Reset password'); - cy.contains(emailAddress); - }); - }); - }); - }); - }); - it('should display an error if a SUSPENDED user attempts to register', () => { - cy - .createTestUser({ - isGuestUser: false, - isUserEmailValidated: true, - }) - ?.then(({ emailAddress }) => { - cy.suspendOktaUser(emailAddress).then(() => { - cy.getTestOktaUser(emailAddress).then((oktaUser) => { - expect(oktaUser.status).to.eq(Status.SUSPENDED); - - cy.visit('/register'); - - cy.get('input[name=email]').type(emailAddress); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains('There was a problem registering, please try again.'); - }); - }); - }); - }); - }); - - context( - 'Existing users asking for an email to be resent after attempting to register with Okta', - () => { - it('should resend a STAGED user a set password email with an Okta activation token', () => { - cy - .createTestUser({ - isGuestUser: true, - isUserEmailValidated: true, - }) - ?.then(({ emailAddress }) => { - cy.getTestOktaUser(emailAddress).then((oktaUser) => { - expect(oktaUser.status).to.eq(Status.STAGED); - - cy.visit('/register'); - - const timeRequestWasMade = new Date(); - - cy.get('input[name=email]').type(emailAddress); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains('Check your email inbox'); - cy.contains(emailAddress); - cy.contains('Resend email'); - cy.contains('Change email address'); - - // Wait for the first email to arrive... - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /\/set-password\/([^"]*)/, - ).then(() => { - const timeRequestWasMade = new Date(); - - cy.get('[data-cy="main-form-submit-button"]').click(); - - // ...before waiting for the second email to arrive - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /\/set-password\/([^"]*)/, - ).then(({ links, body }) => { - expect(body).to.have.string('This account already exists'); - expect(body).to.have.string('Create password'); - expect(links.length).to.eq(2); - const setPasswordLink = links.find( - (s) => s.text?.includes('Create password'), - ); - expect(setPasswordLink?.href ?? '').not.to.have.string( - 'useOkta=true', - ); - cy.visit(setPasswordLink?.href as string); - cy.contains('Create password'); - cy.contains(emailAddress); - }); - }); - }); - }); - }); - - it('should resend a PROVISIONED user a set password email with an Okta activation token', () => { - cy - .createTestUser({ - isGuestUser: true, - isUserEmailValidated: true, - }) - ?.then(({ emailAddress }) => { - cy.activateTestOktaUser(emailAddress).then(() => { - cy.getTestOktaUser(emailAddress).then((oktaUser) => { - expect(oktaUser.status).to.eq(Status.PROVISIONED); - - cy.visit('/register'); - - const timeRequestWasMade = new Date(); - - cy.get('input[name=email]').type(emailAddress); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains('Check your email inbox'); - cy.contains(emailAddress); - cy.contains('Resend email'); - cy.contains('Change email address'); - - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /\/set-password\/([^"]*)/, - ).then(() => { - const timeRequestWasMade = new Date(); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /\/set-password\/([^"]*)/, - ).then(({ links, body }) => { - expect(body).to.have.string('This account already exists'); - expect(body).to.have.string('Create password'); - expect(links.length).to.eq(2); - const setPasswordLink = links.find( - (s) => s.text?.includes('Create password'), - ); - expect(setPasswordLink?.href ?? '').not.to.have.string( - 'useOkta=true', - ); - cy.visit(setPasswordLink?.href as string); - cy.contains('Create password'); - cy.contains(emailAddress); - }); - }); - }); - }); - }); - }); - it('should send an ACTIVE user a reset password email with an Okta activation token', () => { - cy - .createTestUser({ - isGuestUser: false, - isUserEmailValidated: true, - }) - ?.then(({ emailAddress }) => { - cy.getTestOktaUser(emailAddress).then((oktaUser) => { - expect(oktaUser.status).to.eq(Status.ACTIVE); - - cy.visit('/register'); - const timeRequestWasMade = new Date(); - - cy.get('input[name=email]').type(emailAddress); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains('Check your email inbox'); - cy.contains(emailAddress); - cy.contains('Resend email'); - cy.contains('Change email address'); - - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - ).then(() => { - const timeRequestWasMade = new Date(); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /reset-password\/([^"]*)/, - ).then(({ links, body }) => { - expect(body).to.have.string('This account already exists'); - expect(body).to.have.string('Sign in'); - expect(body).to.have.string('Reset password'); - expect(links.length).to.eq(3); - const resetPasswordLink = links.find( - (s) => s.text?.includes('Reset password'), - ); - expect(resetPasswordLink?.href ?? '').not.to.have.string( - 'useOkta=true', - ); - cy.visit(resetPasswordLink?.href as string); - cy.contains('Reset password'); - cy.contains(emailAddress); - }); - }); - }); - }); - }); - it('should send a RECOVERY user a reset password email with an Okta activation token', () => { - cy - .createTestUser({ - isGuestUser: false, - isUserEmailValidated: true, - }) - ?.then(({ emailAddress }) => { - cy.resetOktaUserPassword(emailAddress).then(() => { - cy.getTestOktaUser(emailAddress).then((oktaUser) => { - expect(oktaUser.status).to.eq(Status.RECOVERY); - - cy.visit('/register'); - const timeRequestWasMade = new Date(); - - cy.get('input[name=email]').type(emailAddress); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains('Check your email inbox'); - cy.contains(emailAddress); - cy.contains('Resend email'); - cy.contains('Change email address'); - - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /reset-password\/([^"]*)/, - ).then(() => { - const timeRequestWasMade = new Date(); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /reset-password\/([^"]*)/, - ).then(({ links, body }) => { - expect(body).to.have.string('Password reset'); - expect(body).to.have.string('Reset password'); - expect(links.length).to.eq(2); - const resetPasswordLink = links.find( - (s) => s.text?.includes('Reset password'), - ); - expect(resetPasswordLink?.href ?? '').not.to.have.string( - 'useOkta=true', - ); - cy.visit(resetPasswordLink?.href as string); - cy.contains('Reset password'); - cy.contains(emailAddress); - }); - }); - }); - }); - }); - }); - it('should send a PASSWORD_EXPIRED user a reset password email with an Okta activation token', () => { - cy - .createTestUser({ - isGuestUser: false, - isUserEmailValidated: true, - }) - ?.then(({ emailAddress }) => { - cy.expireOktaUserPassword(emailAddress).then(() => { - cy.getTestOktaUser(emailAddress).then((oktaUser) => { - expect(oktaUser.status).to.eq(Status.PASSWORD_EXPIRED); - - cy.visit('/register'); - const timeRequestWasMade = new Date(); - - cy.get('input[name=email]').type(emailAddress); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains('Check your email inbox'); - cy.contains(emailAddress); - cy.contains('Resend email'); - cy.contains('Change email address'); - - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /reset-password\/([^"]*)/, - ).then(() => { - const timeRequestWasMade = new Date(); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /reset-password\/([^"]*)/, - ).then(({ links, body }) => { - expect(body).to.have.string('Password reset'); - expect(body).to.have.string('Reset password'); - expect(links.length).to.eq(2); - const resetPasswordLink = links.find( - (s) => s.text?.includes('Reset password'), - ); - expect(resetPasswordLink?.href ?? '').not.to.have.string( - 'useOkta=true', - ); - cy.visit(resetPasswordLink?.href as string); - cy.contains('Reset password'); - cy.contains(emailAddress); - }); - }); - }); - }); - }); - }); - }, - ); - - context('Welcome Page - Resend (Link expired)', () => { - it('send an email for user with no existing account', () => { - const encodedReturnUrl = - 'https%3A%2F%2Fm.code.dev-theguardian.com%2Ftravel%2F2019%2Fdec%2F18%2Ffood-culture-tour-bethlehem-palestine-east-jerusalem-photo-essay'; - const unregisteredEmail = randomMailosaurEmail(); - - cy.visit(`/welcome/resend?returnUrl=${encodedReturnUrl}`); - - const timeRequestWasMade = new Date(); - cy.get('input[name=email]').type(unregisteredEmail); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains('Check your email inbox'); - cy.contains(unregisteredEmail); - cy.contains('Resend email'); - cy.contains('Change email address'); - - cy.checkForEmailAndGetDetails( - unregisteredEmail, - timeRequestWasMade, - /welcome\/([^"]*)/, - ).then(({ body, token }) => { - expect(body).to.have.string('Complete registration'); - cy.visit(`/welcome/${token}`); - cy.contains('Save and continue'); - - cy.get('form') - .should('have.attr', 'action') - .and('match', new RegExp(encodedReturnUrl)); - - //we are reloading here to make sure the params are persisted even on page refresh - cy.reload(); - - cy.get('input[name="password"]').type(randomPassword()); - cy.get('button[type="submit"]').click(); - cy.url().should('contain', encodedReturnUrl); - }); - }); - - it('should resend a STAGED user a set password email with an Okta activation token', () => { - cy - .createTestUser({ - isGuestUser: true, - isUserEmailValidated: true, - }) - ?.then(({ emailAddress }) => { - cy.getTestOktaUser(emailAddress).then((oktaUser) => { - expect(oktaUser.status).to.eq(Status.STAGED); - - cy.visit('/welcome/resend'); - - const timeRequestWasMade = new Date(); - - cy.get('input[name=email]').type(emailAddress); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains('Check your email inbox'); - cy.contains(emailAddress); - cy.contains('Resend email'); - cy.contains('Change email address'); - - // Wait for the first email to arrive... - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /\/set-password\/([^"]*)/, - ).then(() => { - const timeRequestWasMade = new Date(); - - cy.get('[data-cy="main-form-submit-button"]').click(); - - // ...before waiting for the second email to arrive - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /\/set-password\/([^"]*)/, - ).then(({ links, body }) => { - expect(body).to.have.string('This account already exists'); - expect(body).to.have.string('Create password'); - expect(links.length).to.eq(2); - const setPasswordLink = links.find( - (s) => s.text?.includes('Create password'), - ); - expect(setPasswordLink?.href ?? '').not.to.have.string( - 'useOkta=true', - ); - cy.visit(setPasswordLink?.href as string); - cy.contains('Create password'); - cy.contains(emailAddress); - }); - }); - }); - }); - }); - it('should resend a PROVISIONED user a set password email with an Okta activation token', () => { - cy - .createTestUser({ - isGuestUser: true, - isUserEmailValidated: true, - }) - ?.then(({ emailAddress }) => { - cy.activateTestOktaUser(emailAddress).then(() => { - cy.getTestOktaUser(emailAddress).then((oktaUser) => { - expect(oktaUser.status).to.eq(Status.PROVISIONED); - - cy.visit('/welcome/resend'); - - const timeRequestWasMade = new Date(); - - cy.get('input[name=email]').type(emailAddress); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains('Check your email inbox'); - cy.contains(emailAddress); - cy.contains('Resend email'); - cy.contains('Change email address'); - - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /\/set-password\/([^"]*)/, - ).then(() => { - const timeRequestWasMade = new Date(); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /\/set-password\/([^"]*)/, - ).then(({ links, body }) => { - expect(body).to.have.string('This account already exists'); - expect(body).to.have.string('Create password'); - expect(links.length).to.eq(2); - const setPasswordLink = links.find( - (s) => s.text?.includes('Create password'), - ); - expect(setPasswordLink?.href ?? '').not.to.have.string( - 'useOkta=true', - ); - cy.visit(setPasswordLink?.href as string); - cy.contains('Create password'); - cy.contains(emailAddress); - }); - }); - }); - }); - }); - }); - it('should send an ACTIVE user a reset password email with an activation token', () => { - cy - .createTestUser({ - isGuestUser: false, - isUserEmailValidated: true, - }) - ?.then(({ emailAddress }) => { - cy.getTestOktaUser(emailAddress).then((oktaUser) => { - expect(oktaUser.status).to.eq(Status.ACTIVE); - - cy.visit('/welcome/resend'); - const timeRequestWasMade = new Date(); - - cy.get('input[name=email]').type(emailAddress); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains('Check your email inbox'); - cy.contains(emailAddress); - cy.contains('Resend email'); - cy.contains('Change email address'); - - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - ).then(() => { - const timeRequestWasMade = new Date(); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /reset-password\/([^"]*)/, - ).then(({ links, body }) => { - expect(body).to.have.string('This account already exists'); - expect(body).to.have.string('Sign in'); - expect(body).to.have.string('Reset password'); - expect(links.length).to.eq(3); - const resetPasswordLink = links.find( - (s) => s.text?.includes('Reset password'), - ); - expect(resetPasswordLink?.href ?? '').not.to.have.string( - 'useOkta=true', - ); - cy.visit(resetPasswordLink?.href as string); - cy.contains('Reset password'); - cy.contains(emailAddress); - }); - }); - }); - }); - }); - it('should send a RECOVERY user a reset password email with an Okta activation token', () => { - cy - .createTestUser({ - isGuestUser: false, - isUserEmailValidated: true, - }) - ?.then(({ emailAddress }) => { - cy.resetOktaUserPassword(emailAddress).then(() => { - cy.getTestOktaUser(emailAddress).then((oktaUser) => { - expect(oktaUser.status).to.eq(Status.RECOVERY); - - cy.visit('/welcome/resend'); - const timeRequestWasMade = new Date(); - - cy.get('input[name=email]').type(emailAddress); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains('Check your email inbox'); - cy.contains(emailAddress); - cy.contains('Resend email'); - cy.contains('Change email address'); - - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /reset-password\/([^"]*)/, - ).then(() => { - const timeRequestWasMade = new Date(); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /reset-password\/([^"]*)/, - ).then(({ links, body }) => { - expect(body).to.have.string('Password reset'); - expect(body).to.have.string('Reset password'); - expect(links.length).to.eq(2); - const resetPasswordLink = links.find( - (s) => s.text?.includes('Reset password'), - ); - expect(resetPasswordLink?.href ?? '').not.to.have.string( - 'useOkta=true', - ); - cy.visit(resetPasswordLink?.href as string); - cy.contains('Reset password'); - cy.contains(emailAddress); - }); - }); - }); - }); - }); - }); - it('should send a PASSWORD_EXPIRED user a reset password email with an Okta activation token', () => { - cy - .createTestUser({ - isGuestUser: false, - isUserEmailValidated: true, - }) - ?.then(({ emailAddress }) => { - cy.expireOktaUserPassword(emailAddress).then(() => { - cy.getTestOktaUser(emailAddress).then((oktaUser) => { - expect(oktaUser.status).to.eq(Status.PASSWORD_EXPIRED); - - cy.visit('/welcome/resend'); - const timeRequestWasMade = new Date(); - - cy.get('input[name=email]').type(emailAddress); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains('Check your email inbox'); - cy.contains(emailAddress); - cy.contains('Resend email'); - cy.contains('Change email address'); - - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /reset-password\/([^"]*)/, - ).then(() => { - const timeRequestWasMade = new Date(); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /reset-password\/([^"]*)/, - ).then(({ links, body }) => { - expect(body).to.have.string('Password reset'); - expect(body).to.have.string('Reset password'); - expect(links.length).to.eq(2); - const resetPasswordLink = links.find( - (s) => s.text?.includes('Reset password'), - ); - expect(resetPasswordLink?.href ?? '').not.to.have.string( - 'useOkta=true', - ); - cy.visit(resetPasswordLink?.href as string); - cy.contains('Reset password'); - cy.contains(emailAddress); - }); - }); - }); - }); - }); - }); - }); - - context('Okta session exists on /signin', () => { - beforeEach(() => { - // Disable redirect to /signin/success by default - cy.setCookie( - 'GU_ran_experiments', - new URLSearchParams({ - OptInPromptPostSignIn: Date.now().toString(), - }).toString(), - ); - // Intercept the external redirect page. - // We just want to check that the redirect happens, not that the page loads. - cy.intercept('GET', 'https://m.code.dev-theguardian.com/', (req) => { - req.reply(200); - }); - }); - - it('shows the signed in as page', () => { - // Create a validated test user - cy.createTestUser({ isUserEmailValidated: true }).then( - ({ emailAddress, finalPassword }) => { - // Sign our new user in - cy.visit( - `/signin?returnUrl=${encodeURIComponent( - `https://${Cypress.env('BASE_URI')}/consents`, - )}`, - ); - cy.get('input[name=email]').type(emailAddress); - cy.get('input[name=password]').type(finalPassword); - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.url().should('include', '/consents'); - - // Get the current session data - cy.getCookie('sid').then((originalSidCookie) => { - expect(originalSidCookie).to.exist; - - // Visit register again - cy.visit( - `/register?returnUrl=${encodeURIComponent( - `https://${Cypress.env('BASE_URI')}/consents`, - )}`, - ); - cy.url().should('include', '/register'); - - cy.contains('Sign in to the Guardian'); - cy.contains('You are signed in with'); - cy.contains(emailAddress); - cy.contains('Continue') - .should('have.attr', 'href') - .and( - 'include', - `https://${Cypress.env( - 'BASE_URI', - )}/signin/refresh?returnUrl=https%3A%2F%2Fprofile.thegulocal.com%2Fconsents`, - ); - cy.contains('a', 'Sign in') - .should('have.attr', 'href') - .and('include', '/signout?returnUrl='); - cy.contains('Sign in with a different email'); - }); - }, - ); - }); - }); + context('Registering with Okta', () => { + it('successfully registers using an email with no existing account', () => { + const encodedReturnUrl = + 'https%3A%2F%2Fm.code.dev-theguardian.com%2Ftravel%2F2019%2Fdec%2F18%2Ffood-culture-tour-bethlehem-palestine-east-jerusalem-photo-essay'; + const unregisteredEmail = randomMailosaurEmail(); + const encodedRef = 'https%3A%2F%2Fm.theguardian.com'; + const refViewId = 'testRefViewId'; + const clientId = 'jobs'; + + // these params should *not* persist between initial registration and welcome page + // despite the fact that they PersistableQueryParams, as these are set by the Okta SDK sign in method + // and subsequent interception, and not by gateway + const appClientId = 'appClientId1'; + const fromURI = 'fromURI1'; + + cy.visit( + `/register?returnUrl=${encodedReturnUrl}&ref=${encodedRef}&refViewId=${refViewId}&clientId=${clientId}&appClientId=${appClientId}&fromURI=${fromURI}`, + ); + + const timeRequestWasMade = new Date(); + cy.get('input[name=email]').type(unregisteredEmail); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains('Check your email inbox'); + cy.contains(unregisteredEmail); + cy.contains('Resend email'); + cy.contains('Change email address'); + + cy.checkForEmailAndGetDetails( + unregisteredEmail, + timeRequestWasMade, + /welcome\/([^"]*)/, + ).then(({ body, token }) => { + expect(body).to.have.string('Complete registration'); + cy.visit(`/welcome/${token}`); + cy.contains('Save and continue'); + + cy.get('form') + .should('have.attr', 'action') + .and('match', new RegExp(encodedReturnUrl)) + .and('match', new RegExp(refViewId)) + .and('match', new RegExp(encodedRef)) + .and('match', new RegExp(clientId)) + .and('not.match', new RegExp(appClientId)) + .and('not.match', new RegExp(fromURI)); + + //we are reloading here to make sure the params are persisted even on page refresh + cy.reload(); + + cy.get('input[name="firstName"]').type('First Name'); + cy.get('input[name="secondName"]').type('Last Name'); + cy.get('input[name="password"]').type(randomPassword()); + cy.get('button[type="submit"]').click(); + cy.url().should('contain', encodedReturnUrl); + cy.url().should('contain', refViewId); + cy.url().should('contain', encodedRef); + cy.url().should('contain', clientId); + cy.url().should('not.contain', appClientId); + cy.url().should('not.contain', fromURI); + }); + }); + + it('successfully registers using an email with no existing account, and has a prefixed activation token when using a native app', () => { + const encodedReturnUrl = + 'https%3A%2F%2Fm.code.dev-theguardian.com%2Ftravel%2F2019%2Fdec%2F18%2Ffood-culture-tour-bethlehem-palestine-east-jerusalem-photo-essay'; + const unregisteredEmail = randomMailosaurEmail(); + const encodedRef = 'https%3A%2F%2Fm.theguardian.com'; + const refViewId = 'testRefViewId'; + const clientId = 'jobs'; + + // these params should *not* persist between initial registration and welcome page + // despite the fact that they PersistableQueryParams, as these are set by the Okta SDK sign in method + // and subsequent interception, and not by gateway + const appClientId = Cypress.env('OKTA_ANDROID_CLIENT_ID'); + const fromURI = 'fromURI1'; + + cy.visit( + `/register?returnUrl=${encodedReturnUrl}&ref=${encodedRef}&refViewId=${refViewId}&clientId=${clientId}&appClientId=${appClientId}&fromURI=${fromURI}`, + ); + + const timeRequestWasMade = new Date(); + cy.get('input[name=email]').type(unregisteredEmail); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains('Check your email inbox'); + cy.contains(unregisteredEmail); + cy.contains('Resend email'); + cy.contains('Change email address'); + + cy.checkForEmailAndGetDetails( + unregisteredEmail, + timeRequestWasMade, + /welcome\/([^"]*)/, + ).then(({ body, token }) => { + expect(body).to.have.string('Complete registration'); + expect(token).to.have.string('al_'); + cy.visit(`/welcome/${token}`); + cy.contains('Save and continue'); + + cy.get('form') + .should('have.attr', 'action') + .and('match', new RegExp(encodedReturnUrl)) + .and('match', new RegExp(refViewId)) + .and('match', new RegExp(encodedRef)) + .and('match', new RegExp(clientId)) + .and('not.match', new RegExp(appClientId)) + .and('not.match', new RegExp(fromURI)); + + //we are reloading here to make sure the params are persisted even on page refresh + cy.reload(); + + cy.get('input[name="firstName"]').type('First Name'); + cy.get('input[name="secondName"]').type('Last Name'); + cy.get('input[name="password"]').type(randomPassword()); + cy.get('button[type="submit"]').click(); + cy.url().should('contain', encodedReturnUrl); + cy.url().should('contain', refViewId); + cy.url().should('contain', encodedRef); + cy.url().should('contain', clientId); + cy.url().should('not.contain', appClientId); + cy.url().should('not.contain', fromURI); + }); + }); + + it('does not register registrationLocation for email with no existing account if cmp is not consented', () => { + const unregisteredEmail = randomMailosaurEmail(); + cy.enableCMP(); + cy.visit(`/register`); + cy.setCookie('GU_geo_country', 'FR'); + cy.declineCMP(); + + cy.get('input[name=email]').type(unregisteredEmail); + cy.get('input[name=_cmpConsentedState]').should('have.value', 'false'); + + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.intercept('POST', '/register**', (req) => { + expect(req.body).to.include('_cmpConsentedState=false'); + expect(req.headers.cookie).to.include('GU_geo_country=FR'); + }); + }); + + it('successfully registers registrationLocation for email with no existing account if cmp consented', () => { + const unregisteredEmail = randomMailosaurEmail(); + cy.enableCMP(); + cy.visit(`/register`); + cy.setCookie('GU_geo_country', 'FR'); + cy.acceptCMP(); + + cy.get('input[name=email]').type(unregisteredEmail); + cy.get('input[name=_cmpConsentedState]').should('have.value', 'true'); + + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.intercept('POST', '/register**', (req) => { + expect(req.body).to.include('_cmpConsentedState=true'); + expect(req.headers.cookie).to.include('GU_geo_country=FR'); + }); + }); + + it('successfully blocks the password set page /welcome if a password has already been set', () => { + const unregisteredEmail = randomMailosaurEmail(); + cy.visit(`/register`); + + const timeRequestWasMade = new Date(); + cy.get('input[name=email]').type(unregisteredEmail); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains('Check your email inbox'); + cy.contains(unregisteredEmail); + cy.contains('Resend email'); + cy.contains('Change email address'); + + cy.checkForEmailAndGetDetails( + unregisteredEmail, + timeRequestWasMade, + /welcome\/([^"]*)/, + ).then(({ body, token }) => { + expect(body).to.have.string('Complete registration'); + cy.visit(`/welcome/${token}`); + cy.contains('Save and continue'); + + cy.get('input[name="password"]').type(randomPassword()); + cy.get('button[type="submit"]').click(); + cy.url().should('contain', '/consents/'); + cy.go('back'); + cy.url().should('contain', '/welcome/'); + cy.contains('Password already set for'); + }); + }); + + it('completes registration and overrides returnUrl from encryptedStateCookie if one set on welcome page url', () => { + const encodedReturnUrl = + 'https%3A%2F%2Fm.code.dev-theguardian.com%2Ftravel%2F2019%2Fdec%2F18%2Ffood-culture-tour-bethlehem-palestine-east-jerusalem-photo-essay'; + const unregisteredEmail = randomMailosaurEmail(); + + cy.visit(`/register?returnUrl=${encodedReturnUrl}`); + + const timeRequestWasMade = new Date(); + cy.get('input[name=email]').type(unregisteredEmail); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains('Check your email inbox'); + cy.contains(unregisteredEmail); + cy.contains('Resend email'); + cy.contains('Change email address'); + + cy.checkForEmailAndGetDetails( + unregisteredEmail, + timeRequestWasMade, + /welcome\/([^"]*)/, + ).then(({ body, token }) => { + expect(body).to.have.string('Complete registration'); + const newReturnUrl = encodeURIComponent( + 'https://www.theguardian.com/technology/2017/may/04/nier-automata-sci-fi-game-sleeper-hit-designer-yoko-taro', + ); + cy.visit(`/welcome/${token}?returnUrl=${newReturnUrl}`); + cy.contains('Save and continue'); + cy.url() + .should('contain', newReturnUrl) + .and('not.contain', encodedReturnUrl); + + cy.get('form') + .should('have.attr', 'action') + .and('match', new RegExp(newReturnUrl)) + .and('not.match', new RegExp(encodedReturnUrl)); + + //we are reloading here to make sure the params are persisted even on page refresh + cy.reload(); + + cy.get('input[name="password"]').type(randomPassword()); + cy.get('button[type="submit"]').click(); + cy.url() + .should('contain', newReturnUrl) + .and('not.contain', encodedReturnUrl); + }); + }); + + it('overrides appClientId and fromURI if set on activation link', () => { + const encodedReturnUrl = + 'https%3A%2F%2Fm.code.dev-theguardian.com%2Ftravel%2F2019%2Fdec%2F18%2Ffood-culture-tour-bethlehem-palestine-east-jerusalem-photo-essay'; + const unregisteredEmail = randomMailosaurEmail(); + + // these params should *not* persist between initial registration and welcome page + // despite the fact that they PersistableQueryParams, as these are set by the Okta SDK sign in method + // and subsequent interception, and not by gateway + const appClientId1 = 'appClientId1'; + const fromURI1 = 'fromURI1'; + + cy.visit( + `/register?returnUrl=${encodedReturnUrl}&appClientId=${appClientId1}&fromURI=${fromURI1}`, + ); + + const timeRequestWasMade = new Date(); + cy.get('input[name=email]').type(unregisteredEmail); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains('Check your email inbox'); + cy.contains(unregisteredEmail); + cy.contains('Resend email'); + cy.contains('Change email address'); + + cy.checkForEmailAndGetDetails( + unregisteredEmail, + timeRequestWasMade, + /welcome\/([^"]*)/, + ).then(({ body, token }) => { + expect(body).to.have.string('Complete registration'); + // should contain these params instead + const appClientId2 = 'appClientId2'; + const fromURI2 = 'fromURI2'; + cy.visit( + `/welcome/${token}?appClientId=${appClientId2}&fromURI=${fromURI2}`, + ); + cy.contains('Save and continue'); + cy.url() + .should('contain', appClientId2) + .and('contain', fromURI2) + .and('not.contain', appClientId1) + .and('not.contain', fromURI1); + + cy.get('form') + .should('have.attr', 'action') + .and('match', new RegExp(appClientId2)) + .and('match', new RegExp(fromURI2)) + .and('not.match', new RegExp(appClientId1)) + .and('not.match', new RegExp(fromURI1)); + }); + }); + }); + context('Existing users attempting to register with Okta', () => { + it('should send a STAGED user a set password email with an Okta activation token', () => { + // Test users created via IDAPI-with-Okta do not have the activation + // lifecycle run at creation, so they don't transition immediately from + // STAGED to PROVISIONED (c.f. + // https://developer.okta.com/docs/reference/api/users/#create-user) . + // This is useful for us as we can test STAGED users first, then test + // PROVISIONED users in the next test by activating a STAGED user. Users + // created through Gateway-with-Okta do have this lifecycle run, so if we + // rebuild these tests to not use IDAPI at all, we need to figure out a + // way to test STAGED and PROVISIONED users (probably by just passing an + // optional `activate` prop to a createUser function). + cy + .createTestUser({ + isGuestUser: true, + isUserEmailValidated: true, + }) + ?.then(({ emailAddress }) => { + cy.getTestOktaUser(emailAddress).then((oktaUser) => { + expect(oktaUser.status).to.eq(Status.STAGED); + + cy.visit('/register'); + const timeRequestWasMade = new Date(); + + cy.get('input[name=email]').type(emailAddress); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains('Check your email inbox'); + cy.contains(emailAddress); + cy.contains('Resend email'); + cy.contains('Change email address'); + + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /\/set-password\/([^"]*)/, + ).then(({ links, body }) => { + expect(body).to.have.string('This account already exists'); + + expect(body).to.have.string('Create password'); + expect(links.length).to.eq(2); + const setPasswordLink = links.find( + (s) => s.text?.includes('Create password'), + ); + expect(setPasswordLink?.href).not.to.have.string('useOkta=true'); + cy.visit(setPasswordLink?.href as string); + cy.contains('Create password'); + cy.contains(emailAddress); + }); + }); + }); + }); + + it('should send a STAGED user a set password email with an Okta activation token, and has a prefixed activation token when using a native app', () => { + // Test users created via IDAPI-with-Okta do not have the activation + // lifecycle run at creation, so they don't transition immediately from + // STAGED to PROVISIONED (c.f. + // https://developer.okta.com/docs/reference/api/users/#create-user) . + // This is useful for us as we can test STAGED users first, then test + // PROVISIONED users in the next test by activating a STAGED user. Users + // created through Gateway-with-Okta do have this lifecycle run, so if we + // rebuild these tests to not use IDAPI at all, we need to figure out a + // way to test STAGED and PROVISIONED users (probably by just passing an + // optional `activate` prop to a createUser function). + cy + .createTestUser({ + isGuestUser: true, + isUserEmailValidated: true, + }) + ?.then(({ emailAddress }) => { + cy.getTestOktaUser(emailAddress).then((oktaUser) => { + expect(oktaUser.status).to.eq(Status.STAGED); + + const appClientId = Cypress.env('OKTA_ANDROID_CLIENT_ID'); + const fromURI = 'fromURI1'; + + cy.visit(`/register?appClientId=${appClientId}&fromURI=${fromURI}`); + const timeRequestWasMade = new Date(); + + cy.get('input[name=email]').type(emailAddress); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains('Check your email inbox'); + cy.contains(emailAddress); + cy.contains('Resend email'); + cy.contains('Change email address'); + + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /\/set-password\/([^"]*)/, + ).then(({ links, body }) => { + expect(body).to.have.string('This account already exists'); + + expect(body).to.have.string('Create password'); + expect(links.length).to.eq(2); + const setPasswordLink = links.find( + (s) => s.text?.includes('Create password'), + ); + expect(setPasswordLink?.href ?? '') + .to.have.string('al_') + .and.not.to.have.string('useOkta=true'); + cy.visit(setPasswordLink?.href as string); + cy.contains('Create password'); + cy.contains(emailAddress); + }); + }); + }); + }); + + it('should send a PROVISIONED user a set password email with an Okta activation token', () => { + cy + .createTestUser({ + isGuestUser: true, + isUserEmailValidated: true, + }) + ?.then(({ emailAddress }) => { + cy.activateTestOktaUser(emailAddress).then(() => { + cy.getTestOktaUser(emailAddress).then((oktaUser) => { + expect(oktaUser.status).to.eq(Status.PROVISIONED); + + cy.visit('/register'); + const timeRequestWasMade = new Date(); + + cy.get('input[name=email]').type(emailAddress); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains('Check your email inbox'); + cy.contains(emailAddress); + cy.contains('Resend email'); + cy.contains('Change email address'); + + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /\/set-password\/([^"]*)/, + ).then(({ links, body }) => { + expect(body).to.have.string('This account already exists'); + expect(body).to.have.string('Create password'); + expect(links.length).to.eq(2); + const setPasswordLink = links.find( + (s) => s.text?.includes('Create password'), + ); + expect(setPasswordLink?.href).not.to.have.string( + 'useOkta=true', + ); + cy.visit(setPasswordLink?.href as string); + cy.contains('Create password'); + cy.contains(emailAddress); + }); + }); + }); + }); + }); + it('should send an ACTIVE UNvalidated user with a password a security email with activation token', () => { + cy + .createTestUser({ + isGuestUser: false, + isUserEmailValidated: false, + }) + ?.then(({ emailAddress }) => { + cy.getTestOktaUser(emailAddress).then((oktaUser) => { + expect(oktaUser.status).to.eq(Status.ACTIVE); + + cy.visit('/register'); + const timeRequestWasMade = new Date(); + + cy.get('input[name=email]').type(emailAddress); + cy.get('[data-cy="main-form-submit-button"]').click(); + + // Make sure that we don't get sent to the 'security reasons' page + cy.url().should('include', '/register/email-sent'); + cy.contains( + 'For security reasons we need you to change your password.', + ).should('not.exist'); + cy.contains(emailAddress); + cy.contains('Resend email'); + cy.contains('Change email address'); + + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /reset-password\/([^"]*)/, + ).then(({ links, body }) => { + expect(body).to.have.string( + 'Because your security is extremely important to us, we have changed our password policy.', + ); + expect(body).to.have.string('Reset password'); + expect(links.length).to.eq(2); + const resetPasswordLink = links.find( + (s) => s.text?.includes('Reset password'), + ); + cy.visit(resetPasswordLink?.href as string); + cy.contains(emailAddress); + cy.contains('Reset password'); + }); + }); + }); + }); + it('should send an ACTIVE validated user WITH a password a reset password email with an activation token', () => { + cy + .createTestUser({ + isGuestUser: false, + isUserEmailValidated: true, + }) + ?.then(({ emailAddress }) => { + cy.getTestOktaUser(emailAddress).then((oktaUser) => { + expect(oktaUser.status).to.eq(Status.ACTIVE); + + cy.visit('/register'); + const timeRequestWasMade = new Date(); + + cy.get('input[name=email]').type(emailAddress); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains('Check your email inbox'); + cy.contains(emailAddress); + cy.contains('Resend email'); + cy.contains('Change email address'); + + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /reset-password\/([^"]*)/, + ).then(({ links, body }) => { + expect(body).to.have.string('This account already exists'); + expect(body).to.have.string('Sign in'); + expect(body).to.have.string('Reset password'); + expect(links.length).to.eq(3); + const resetPasswordLink = links.find( + (s) => s.text?.includes('Reset password'), + ); + expect(resetPasswordLink?.href ?? '').not.to.have.string( + 'useOkta=true', + ); + cy.visit(resetPasswordLink?.href as string); + cy.contains(emailAddress); + cy.contains('Reset password'); + }); + }); + }); + }); + it('should send an ACTIVE validated user WITH a password a reset password email with an activation token, and prefixed activation token if using native app', () => { + cy + .createTestUser({ + isGuestUser: false, + isUserEmailValidated: true, + }) + ?.then(({ emailAddress }) => { + cy.getTestOktaUser(emailAddress).then((oktaUser) => { + expect(oktaUser.status).to.eq(Status.ACTIVE); + + const appClientId = Cypress.env('OKTA_ANDROID_CLIENT_ID'); + const fromURI = 'fromURI1'; + + cy.visit(`/register?appClientId=${appClientId}&fromURI=${fromURI}`); + const timeRequestWasMade = new Date(); + + cy.get('input[name=email]').type(emailAddress); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains('Check your email inbox'); + cy.contains(emailAddress); + cy.contains('Resend email'); + cy.contains('Change email address'); + + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /reset-password\/([^"]*)/, + ).then(({ links, body }) => { + expect(body).to.have.string('This account already exists'); + expect(body).to.have.string('Sign in'); + expect(body).to.have.string('Reset password'); + expect(links.length).to.eq(3); + const resetPasswordLink = links.find( + (s) => s.text?.includes('Reset password'), + ); + expect(resetPasswordLink?.href ?? '') + .to.have.string('al_') + .and.not.to.have.string('useOkta=true'); + cy.visit(resetPasswordLink?.href as string); + cy.contains(emailAddress); + cy.contains('Reset password'); + }); + }); + }); + }); + it('should send a RECOVERY user a reset password email with an Okta activation token', () => { + cy + .createTestUser({ + isGuestUser: false, + isUserEmailValidated: true, + }) + ?.then(({ emailAddress }) => { + cy.resetOktaUserPassword(emailAddress).then(() => { + cy.getTestOktaUser(emailAddress).then((oktaUser) => { + expect(oktaUser.status).to.eq(Status.RECOVERY); + + cy.visit('/register'); + const timeRequestWasMade = new Date(); + + cy.get('input[name=email]').type(emailAddress); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains('Check your email inbox'); + cy.contains(emailAddress); + cy.contains('Resend email'); + cy.contains('Change email address'); + + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /reset-password\/([^"]*)/, + ).then(({ links, body }) => { + expect(body).to.have.string('Password reset'); + expect(body).to.have.string('Reset password'); + expect(links.length).to.eq(2); + const resetPasswordLink = links.find( + (s) => s.text?.includes('Reset password'), + ); + expect(resetPasswordLink?.href ?? '').not.to.have.string( + 'useOkta=true', + ); + cy.visit(resetPasswordLink?.href as string); + cy.contains('Reset password'); + cy.contains(emailAddress); + }); + }); + }); + }); + }); + it('should send a PASSWORD_EXPIRED user a reset password email with an Okta activation token', () => { + cy + .createTestUser({ + isGuestUser: false, + isUserEmailValidated: true, + }) + ?.then(({ emailAddress }) => { + cy.expireOktaUserPassword(emailAddress).then(() => { + cy.getTestOktaUser(emailAddress).then((oktaUser) => { + expect(oktaUser.status).to.eq(Status.PASSWORD_EXPIRED); + + cy.visit('/register'); + const timeRequestWasMade = new Date(); + + cy.get('input[name=email]').type(emailAddress); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains('Check your email inbox'); + cy.contains(emailAddress); + cy.contains('Resend email'); + cy.contains('Change email address'); + + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /reset-password\/([^"]*)/, + ).then(({ links, body }) => { + expect(body).to.have.string('Password reset'); + expect(body).to.have.string('Reset password'); + expect(links.length).to.eq(2); + const resetPasswordLink = links.find( + (s) => s.text?.includes('Reset password'), + ); + expect(resetPasswordLink?.href ?? '').not.to.have.string( + 'useOkta=true', + ); + cy.visit(resetPasswordLink?.href as string); + cy.contains('Reset password'); + cy.contains(emailAddress); + }); + }); + }); + }); + }); + it('should display an error if a SUSPENDED user attempts to register', () => { + cy + .createTestUser({ + isGuestUser: false, + isUserEmailValidated: true, + }) + ?.then(({ emailAddress }) => { + cy.suspendOktaUser(emailAddress).then(() => { + cy.getTestOktaUser(emailAddress).then((oktaUser) => { + expect(oktaUser.status).to.eq(Status.SUSPENDED); + + cy.visit('/register'); + + cy.get('input[name=email]').type(emailAddress); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains('There was a problem registering, please try again.'); + }); + }); + }); + }); + }); + + context( + 'Existing users asking for an email to be resent after attempting to register with Okta', + () => { + it('should resend a STAGED user a set password email with an Okta activation token', () => { + cy + .createTestUser({ + isGuestUser: true, + isUserEmailValidated: true, + }) + ?.then(({ emailAddress }) => { + cy.getTestOktaUser(emailAddress).then((oktaUser) => { + expect(oktaUser.status).to.eq(Status.STAGED); + + cy.visit('/register'); + + const timeRequestWasMade = new Date(); + + cy.get('input[name=email]').type(emailAddress); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains('Check your email inbox'); + cy.contains(emailAddress); + cy.contains('Resend email'); + cy.contains('Change email address'); + + // Wait for the first email to arrive... + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /\/set-password\/([^"]*)/, + ).then(() => { + const timeRequestWasMade = new Date(); + + cy.get('[data-cy="main-form-submit-button"]').click(); + + // ...before waiting for the second email to arrive + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /\/set-password\/([^"]*)/, + ).then(({ links, body }) => { + expect(body).to.have.string('This account already exists'); + expect(body).to.have.string('Create password'); + expect(links.length).to.eq(2); + const setPasswordLink = links.find( + (s) => s.text?.includes('Create password'), + ); + expect(setPasswordLink?.href ?? '').not.to.have.string( + 'useOkta=true', + ); + cy.visit(setPasswordLink?.href as string); + cy.contains('Create password'); + cy.contains(emailAddress); + }); + }); + }); + }); + }); + + it('should resend a PROVISIONED user a set password email with an Okta activation token', () => { + cy + .createTestUser({ + isGuestUser: true, + isUserEmailValidated: true, + }) + ?.then(({ emailAddress }) => { + cy.activateTestOktaUser(emailAddress).then(() => { + cy.getTestOktaUser(emailAddress).then((oktaUser) => { + expect(oktaUser.status).to.eq(Status.PROVISIONED); + + cy.visit('/register'); + + const timeRequestWasMade = new Date(); + + cy.get('input[name=email]').type(emailAddress); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains('Check your email inbox'); + cy.contains(emailAddress); + cy.contains('Resend email'); + cy.contains('Change email address'); + + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /\/set-password\/([^"]*)/, + ).then(() => { + const timeRequestWasMade = new Date(); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /\/set-password\/([^"]*)/, + ).then(({ links, body }) => { + expect(body).to.have.string('This account already exists'); + expect(body).to.have.string('Create password'); + expect(links.length).to.eq(2); + const setPasswordLink = links.find( + (s) => s.text?.includes('Create password'), + ); + expect(setPasswordLink?.href ?? '').not.to.have.string( + 'useOkta=true', + ); + cy.visit(setPasswordLink?.href as string); + cy.contains('Create password'); + cy.contains(emailAddress); + }); + }); + }); + }); + }); + }); + it('should send an ACTIVE user a reset password email with an Okta activation token', () => { + cy + .createTestUser({ + isGuestUser: false, + isUserEmailValidated: true, + }) + ?.then(({ emailAddress }) => { + cy.getTestOktaUser(emailAddress).then((oktaUser) => { + expect(oktaUser.status).to.eq(Status.ACTIVE); + + cy.visit('/register'); + const timeRequestWasMade = new Date(); + + cy.get('input[name=email]').type(emailAddress); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains('Check your email inbox'); + cy.contains(emailAddress); + cy.contains('Resend email'); + cy.contains('Change email address'); + + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + ).then(() => { + const timeRequestWasMade = new Date(); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /reset-password\/([^"]*)/, + ).then(({ links, body }) => { + expect(body).to.have.string('This account already exists'); + expect(body).to.have.string('Sign in'); + expect(body).to.have.string('Reset password'); + expect(links.length).to.eq(3); + const resetPasswordLink = links.find( + (s) => s.text?.includes('Reset password'), + ); + expect(resetPasswordLink?.href ?? '').not.to.have.string( + 'useOkta=true', + ); + cy.visit(resetPasswordLink?.href as string); + cy.contains('Reset password'); + cy.contains(emailAddress); + }); + }); + }); + }); + }); + it('should send a RECOVERY user a reset password email with an Okta activation token', () => { + cy + .createTestUser({ + isGuestUser: false, + isUserEmailValidated: true, + }) + ?.then(({ emailAddress }) => { + cy.resetOktaUserPassword(emailAddress).then(() => { + cy.getTestOktaUser(emailAddress).then((oktaUser) => { + expect(oktaUser.status).to.eq(Status.RECOVERY); + + cy.visit('/register'); + const timeRequestWasMade = new Date(); + + cy.get('input[name=email]').type(emailAddress); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains('Check your email inbox'); + cy.contains(emailAddress); + cy.contains('Resend email'); + cy.contains('Change email address'); + + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /reset-password\/([^"]*)/, + ).then(() => { + const timeRequestWasMade = new Date(); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /reset-password\/([^"]*)/, + ).then(({ links, body }) => { + expect(body).to.have.string('Password reset'); + expect(body).to.have.string('Reset password'); + expect(links.length).to.eq(2); + const resetPasswordLink = links.find( + (s) => s.text?.includes('Reset password'), + ); + expect(resetPasswordLink?.href ?? '').not.to.have.string( + 'useOkta=true', + ); + cy.visit(resetPasswordLink?.href as string); + cy.contains('Reset password'); + cy.contains(emailAddress); + }); + }); + }); + }); + }); + }); + it('should send a PASSWORD_EXPIRED user a reset password email with an Okta activation token', () => { + cy + .createTestUser({ + isGuestUser: false, + isUserEmailValidated: true, + }) + ?.then(({ emailAddress }) => { + cy.expireOktaUserPassword(emailAddress).then(() => { + cy.getTestOktaUser(emailAddress).then((oktaUser) => { + expect(oktaUser.status).to.eq(Status.PASSWORD_EXPIRED); + + cy.visit('/register'); + const timeRequestWasMade = new Date(); + + cy.get('input[name=email]').type(emailAddress); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains('Check your email inbox'); + cy.contains(emailAddress); + cy.contains('Resend email'); + cy.contains('Change email address'); + + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /reset-password\/([^"]*)/, + ).then(() => { + const timeRequestWasMade = new Date(); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /reset-password\/([^"]*)/, + ).then(({ links, body }) => { + expect(body).to.have.string('Password reset'); + expect(body).to.have.string('Reset password'); + expect(links.length).to.eq(2); + const resetPasswordLink = links.find( + (s) => s.text?.includes('Reset password'), + ); + expect(resetPasswordLink?.href ?? '').not.to.have.string( + 'useOkta=true', + ); + cy.visit(resetPasswordLink?.href as string); + cy.contains('Reset password'); + cy.contains(emailAddress); + }); + }); + }); + }); + }); + }); + }, + ); + + context('Welcome Page - Resend (Link expired)', () => { + it('send an email for user with no existing account', () => { + const encodedReturnUrl = + 'https%3A%2F%2Fm.code.dev-theguardian.com%2Ftravel%2F2019%2Fdec%2F18%2Ffood-culture-tour-bethlehem-palestine-east-jerusalem-photo-essay'; + const unregisteredEmail = randomMailosaurEmail(); + + cy.visit(`/welcome/resend?returnUrl=${encodedReturnUrl}`); + + const timeRequestWasMade = new Date(); + cy.get('input[name=email]').type(unregisteredEmail); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains('Check your email inbox'); + cy.contains(unregisteredEmail); + cy.contains('Resend email'); + cy.contains('Change email address'); + + cy.checkForEmailAndGetDetails( + unregisteredEmail, + timeRequestWasMade, + /welcome\/([^"]*)/, + ).then(({ body, token }) => { + expect(body).to.have.string('Complete registration'); + cy.visit(`/welcome/${token}`); + cy.contains('Save and continue'); + + cy.get('form') + .should('have.attr', 'action') + .and('match', new RegExp(encodedReturnUrl)); + + //we are reloading here to make sure the params are persisted even on page refresh + cy.reload(); + + cy.get('input[name="password"]').type(randomPassword()); + cy.get('button[type="submit"]').click(); + cy.url().should('contain', encodedReturnUrl); + }); + }); + + it('should resend a STAGED user a set password email with an Okta activation token', () => { + cy + .createTestUser({ + isGuestUser: true, + isUserEmailValidated: true, + }) + ?.then(({ emailAddress }) => { + cy.getTestOktaUser(emailAddress).then((oktaUser) => { + expect(oktaUser.status).to.eq(Status.STAGED); + + cy.visit('/welcome/resend'); + + const timeRequestWasMade = new Date(); + + cy.get('input[name=email]').type(emailAddress); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains('Check your email inbox'); + cy.contains(emailAddress); + cy.contains('Resend email'); + cy.contains('Change email address'); + + // Wait for the first email to arrive... + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /\/set-password\/([^"]*)/, + ).then(() => { + const timeRequestWasMade = new Date(); + + cy.get('[data-cy="main-form-submit-button"]').click(); + + // ...before waiting for the second email to arrive + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /\/set-password\/([^"]*)/, + ).then(({ links, body }) => { + expect(body).to.have.string('This account already exists'); + expect(body).to.have.string('Create password'); + expect(links.length).to.eq(2); + const setPasswordLink = links.find( + (s) => s.text?.includes('Create password'), + ); + expect(setPasswordLink?.href ?? '').not.to.have.string( + 'useOkta=true', + ); + cy.visit(setPasswordLink?.href as string); + cy.contains('Create password'); + cy.contains(emailAddress); + }); + }); + }); + }); + }); + it('should resend a PROVISIONED user a set password email with an Okta activation token', () => { + cy + .createTestUser({ + isGuestUser: true, + isUserEmailValidated: true, + }) + ?.then(({ emailAddress }) => { + cy.activateTestOktaUser(emailAddress).then(() => { + cy.getTestOktaUser(emailAddress).then((oktaUser) => { + expect(oktaUser.status).to.eq(Status.PROVISIONED); + + cy.visit('/welcome/resend'); + + const timeRequestWasMade = new Date(); + + cy.get('input[name=email]').type(emailAddress); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains('Check your email inbox'); + cy.contains(emailAddress); + cy.contains('Resend email'); + cy.contains('Change email address'); + + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /\/set-password\/([^"]*)/, + ).then(() => { + const timeRequestWasMade = new Date(); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /\/set-password\/([^"]*)/, + ).then(({ links, body }) => { + expect(body).to.have.string('This account already exists'); + expect(body).to.have.string('Create password'); + expect(links.length).to.eq(2); + const setPasswordLink = links.find( + (s) => s.text?.includes('Create password'), + ); + expect(setPasswordLink?.href ?? '').not.to.have.string( + 'useOkta=true', + ); + cy.visit(setPasswordLink?.href as string); + cy.contains('Create password'); + cy.contains(emailAddress); + }); + }); + }); + }); + }); + }); + it('should send an ACTIVE user a reset password email with an activation token', () => { + cy + .createTestUser({ + isGuestUser: false, + isUserEmailValidated: true, + }) + ?.then(({ emailAddress }) => { + cy.getTestOktaUser(emailAddress).then((oktaUser) => { + expect(oktaUser.status).to.eq(Status.ACTIVE); + + cy.visit('/welcome/resend'); + const timeRequestWasMade = new Date(); + + cy.get('input[name=email]').type(emailAddress); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains('Check your email inbox'); + cy.contains(emailAddress); + cy.contains('Resend email'); + cy.contains('Change email address'); + + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + ).then(() => { + const timeRequestWasMade = new Date(); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /reset-password\/([^"]*)/, + ).then(({ links, body }) => { + expect(body).to.have.string('This account already exists'); + expect(body).to.have.string('Sign in'); + expect(body).to.have.string('Reset password'); + expect(links.length).to.eq(3); + const resetPasswordLink = links.find( + (s) => s.text?.includes('Reset password'), + ); + expect(resetPasswordLink?.href ?? '').not.to.have.string( + 'useOkta=true', + ); + cy.visit(resetPasswordLink?.href as string); + cy.contains('Reset password'); + cy.contains(emailAddress); + }); + }); + }); + }); + }); + it('should send a RECOVERY user a reset password email with an Okta activation token', () => { + cy + .createTestUser({ + isGuestUser: false, + isUserEmailValidated: true, + }) + ?.then(({ emailAddress }) => { + cy.resetOktaUserPassword(emailAddress).then(() => { + cy.getTestOktaUser(emailAddress).then((oktaUser) => { + expect(oktaUser.status).to.eq(Status.RECOVERY); + + cy.visit('/welcome/resend'); + const timeRequestWasMade = new Date(); + + cy.get('input[name=email]').type(emailAddress); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains('Check your email inbox'); + cy.contains(emailAddress); + cy.contains('Resend email'); + cy.contains('Change email address'); + + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /reset-password\/([^"]*)/, + ).then(() => { + const timeRequestWasMade = new Date(); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /reset-password\/([^"]*)/, + ).then(({ links, body }) => { + expect(body).to.have.string('Password reset'); + expect(body).to.have.string('Reset password'); + expect(links.length).to.eq(2); + const resetPasswordLink = links.find( + (s) => s.text?.includes('Reset password'), + ); + expect(resetPasswordLink?.href ?? '').not.to.have.string( + 'useOkta=true', + ); + cy.visit(resetPasswordLink?.href as string); + cy.contains('Reset password'); + cy.contains(emailAddress); + }); + }); + }); + }); + }); + }); + it('should send a PASSWORD_EXPIRED user a reset password email with an Okta activation token', () => { + cy + .createTestUser({ + isGuestUser: false, + isUserEmailValidated: true, + }) + ?.then(({ emailAddress }) => { + cy.expireOktaUserPassword(emailAddress).then(() => { + cy.getTestOktaUser(emailAddress).then((oktaUser) => { + expect(oktaUser.status).to.eq(Status.PASSWORD_EXPIRED); + + cy.visit('/welcome/resend'); + const timeRequestWasMade = new Date(); + + cy.get('input[name=email]').type(emailAddress); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains('Check your email inbox'); + cy.contains(emailAddress); + cy.contains('Resend email'); + cy.contains('Change email address'); + + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /reset-password\/([^"]*)/, + ).then(() => { + const timeRequestWasMade = new Date(); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /reset-password\/([^"]*)/, + ).then(({ links, body }) => { + expect(body).to.have.string('Password reset'); + expect(body).to.have.string('Reset password'); + expect(links.length).to.eq(2); + const resetPasswordLink = links.find( + (s) => s.text?.includes('Reset password'), + ); + expect(resetPasswordLink?.href ?? '').not.to.have.string( + 'useOkta=true', + ); + cy.visit(resetPasswordLink?.href as string); + cy.contains('Reset password'); + cy.contains(emailAddress); + }); + }); + }); + }); + }); + }); + }); + + context('Okta session exists on /signin', () => { + beforeEach(() => { + // Disable redirect to /signin/success by default + cy.setCookie( + 'GU_ran_experiments', + new URLSearchParams({ + OptInPromptPostSignIn: Date.now().toString(), + }).toString(), + ); + // Intercept the external redirect page. + // We just want to check that the redirect happens, not that the page loads. + cy.intercept('GET', 'https://m.code.dev-theguardian.com/', (req) => { + req.reply(200); + }); + }); + + it('shows the signed in as page', () => { + // Create a validated test user + cy.createTestUser({ isUserEmailValidated: true }).then( + ({ emailAddress, finalPassword }) => { + // Sign our new user in + cy.visit( + `/signin?returnUrl=${encodeURIComponent( + `https://${Cypress.env('BASE_URI')}/consents`, + )}`, + ); + cy.get('input[name=email]').type(emailAddress); + cy.get('input[name=password]').type(finalPassword); + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.url().should('include', '/consents'); + + // Get the current session data + cy.getCookie('sid').then((originalSidCookie) => { + expect(originalSidCookie).to.exist; + + // Visit register again + cy.visit( + `/register?returnUrl=${encodeURIComponent( + `https://${Cypress.env('BASE_URI')}/consents`, + )}`, + ); + cy.url().should('include', '/register'); + + cy.contains('Sign in to the Guardian'); + cy.contains('You are signed in with'); + cy.contains(emailAddress); + cy.contains('Continue') + .should('have.attr', 'href') + .and( + 'include', + `https://${Cypress.env( + 'BASE_URI', + )}/signin/refresh?returnUrl=https%3A%2F%2Fprofile.thegulocal.com%2Fconsents`, + ); + cy.contains('a', 'Sign in') + .should('have.attr', 'href') + .and('include', '/signout?returnUrl='); + cy.contains('Sign in with a different email'); + }); + }, + ); + }); + }); }); diff --git a/cypress/integration/ete-okta/reset_password.3.cy.ts b/cypress/integration/ete-okta/reset_password.3.cy.ts index 242858b784..6dacd3d16f 100644 --- a/cypress/integration/ete-okta/reset_password.3.cy.ts +++ b/cypress/integration/ete-okta/reset_password.3.cy.ts @@ -2,526 +2,526 @@ import { Status } from '../../../src/server/models/okta/User'; import { randomPassword } from '../../support/commands/testUser'; const breachCheck = () => { - cy.intercept({ - method: 'GET', - url: 'https://api.pwnedpasswords.com/range/*', - }).as('breachCheck'); + cy.intercept({ + method: 'GET', + url: 'https://api.pwnedpasswords.com/range/*', + }).as('breachCheck'); }; describe('Password reset flow in Okta', () => { - context('Account exists', () => { - it("changes the reader's password", () => { - const encodedReturnUrl = - 'https%3A%2F%2Fm.code.dev-theguardian.com%2Ftravel%2F2019%2Fdec%2F18%2Ffood-culture-tour-bethlehem-palestine-east-jerusalem-photo-essay'; - const encodedRef = 'https%3A%2F%2Fm.theguardian.com'; - const refViewId = 'testRefViewId'; - const clientId = 'jobs'; - - // these params should *not* persist between initial registration and welcome page - // despite the fact that they PersistableQueryParams, as these are set by the Okta SDK sign in method - // and subsequent interception, and not by gateway - const appClientId = 'appClientId1'; - const fromURI = 'fromURI1'; - - breachCheck(); - cy - .createTestUser({ - isUserEmailValidated: true, - }) - ?.then(({ emailAddress }) => { - cy.visit( - `/reset-password?returnUrl=${encodedReturnUrl}&ref=${encodedRef}&refViewId=${refViewId}&clientId=${clientId}&appClientId=${appClientId}&fromURI=${fromURI}`, - ); - const timeRequestWasMade = new Date(); - - cy.contains('Forgot password'); - cy.get('input[name=email]').type(emailAddress); - - // Continue checking the password reset flow after reCAPTCHA assertions above. - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.contains('Check your email inbox'); - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /reset-password\/([^"]*)/, - ).then(({ token }) => { - cy.visit(`/reset-password/${token}`); - - cy.get('form') - .should('have.attr', 'action') - .and('match', new RegExp(encodedReturnUrl)) - .and('match', new RegExp(refViewId)) - .and('match', new RegExp(encodedRef)) - .and('match', new RegExp(clientId)) - .and('not.match', new RegExp(appClientId)) - .and('not.match', new RegExp(fromURI)); - - //we are reloading here to make sure the params are persisted even on page refresh - cy.reload(); - - cy.get('input[name=password]').type(randomPassword()); - - cy.wait('@breachCheck'); - cy.get('[data-cy="main-form-submit-button"]') - .click() - .should('be.disabled'); - cy.contains('Password updated'); - cy.contains(emailAddress.toLowerCase()); - - cy.url().should('contain', encodedReturnUrl); - cy.url().should('contain', refViewId); - cy.url().should('contain', encodedRef); - cy.url().should('contain', clientId); - cy.url().should('not.contain', 'useOkta=true'); - cy.url().should('not.contain', appClientId); - cy.url().should('not.contain', fromURI); - }); - }); - }); - - it("changes the reader's password, and has a prefixed recovery token when using a native app", () => { - const encodedReturnUrl = - 'https%3A%2F%2Fm.code.dev-theguardian.com%2Ftravel%2F2019%2Fdec%2F18%2Ffood-culture-tour-bethlehem-palestine-east-jerusalem-photo-essay'; - const encodedRef = 'https%3A%2F%2Fm.theguardian.com'; - const refViewId = 'testRefViewId'; - const clientId = 'jobs'; - - // these params should *not* persist between initial registration and welcome page - // despite the fact that they PersistableQueryParams, as these are set by the Okta SDK sign in method - // and subsequent interception, and not by gateway - const appClientId = Cypress.env('OKTA_ANDROID_CLIENT_ID'); - const fromURI = 'fromURI1'; - - breachCheck(); - cy - .createTestUser({ - isUserEmailValidated: true, - }) - ?.then(({ emailAddress }) => { - cy.visit( - `/reset-password?returnUrl=${encodedReturnUrl}&ref=${encodedRef}&refViewId=${refViewId}&clientId=${clientId}&appClientId=${appClientId}&fromURI=${fromURI}`, - ); - const timeRequestWasMade = new Date(); - - cy.contains('Forgot password'); - cy.get('input[name=email]').type(emailAddress); - - // Continue checking the password reset flow after reCAPTCHA assertions above. - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.contains('Check your email inbox'); - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /reset-password\/([^"]*)/, - ).then(({ token }) => { - expect(token).to.have.string('al_'); - cy.visit(`/reset-password/${token}`); - - cy.get('form') - .should('have.attr', 'action') - .and('match', new RegExp(encodedReturnUrl)) - .and('match', new RegExp(refViewId)) - .and('match', new RegExp(encodedRef)) - .and('match', new RegExp(clientId)) - .and('not.match', new RegExp(appClientId)) - .and('not.match', new RegExp(fromURI)); - - //we are reloading here to make sure the params are persisted even on page refresh - cy.reload(); - - cy.get('input[name=password]').type(randomPassword()); - - cy.wait('@breachCheck'); - cy.get('[data-cy="main-form-submit-button"]') - .click() - .should('be.disabled'); - cy.contains('Password updated'); - cy.contains(emailAddress.toLowerCase()); - - cy.url().should('contain', encodedReturnUrl); - cy.url().should('contain', refViewId); - cy.url().should('contain', encodedRef); - cy.url().should('contain', clientId); - cy.url().should('not.contain', 'useOkta=true'); - cy.url().should('not.contain', appClientId); - cy.url().should('not.contain', fromURI); - }); - }); - }); - - it("changes the reader's password and overrides returnUrl from encryptedStateCookie if one set on reset password page url", () => { - const encodedReturnUrl = - 'https%3A%2F%2Fm.code.dev-theguardian.com%2Ftravel%2F2019%2Fdec%2F18%2Ffood-culture-tour-bethlehem-palestine-east-jerusalem-photo-essay'; - - breachCheck(); - cy - .createTestUser({ - isUserEmailValidated: true, - }) - ?.then(({ emailAddress }) => { - cy.visit(`/reset-password?returnUrl=${encodedReturnUrl}`); - const timeRequestWasMade = new Date(); - - cy.contains('Forgot password'); - cy.get('input[name=email]').type(emailAddress); - - // Continue checking the password reset flow after reCAPTCHA assertions above. - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.contains('Check your email inbox'); - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /reset-password\/([^"]*)/, - ).then(({ token }) => { - const newReturnUrl = encodeURIComponent( - 'https://www.theguardian.com/technology/2017/may/04/nier-automata-sci-fi-game-sleeper-hit-designer-yoko-taro', - ); - cy.visit(`/reset-password/${token}?returnUrl=${newReturnUrl}`); - - cy.url() - .should('contain', newReturnUrl) - .and('not.contain', encodedReturnUrl); - - cy.get('form') - .should('have.attr', 'action') - .and('match', new RegExp(newReturnUrl)) - .and('not.match', new RegExp(encodedReturnUrl)); - - //we are reloading here to make sure the params are persisted even on page refresh - cy.reload(); - - cy.get('input[name=password]').type(randomPassword()); - - cy.wait('@breachCheck'); - cy.get('[data-cy="main-form-submit-button"]') - .click() - .should('be.disabled'); - cy.contains('Password updated'); - cy.contains(emailAddress.toLowerCase()); - - cy.url() - .should('contain', newReturnUrl) - .and('not.contain', encodedReturnUrl) - .and('not.contain', 'useOkta=true'); - }); - }); - }); - - it('overrides appClientId and fromURI if set on reset password page url', () => { - const encodedReturnUrl = - 'https%3A%2F%2Fm.code.dev-theguardian.com%2Ftravel%2F2019%2Fdec%2F18%2Ffood-culture-tour-bethlehem-palestine-east-jerusalem-photo-essay'; - - // these params should *not* persist between initial registration and welcome page - // despite the fact that they PersistableQueryParams, as these are set by the Okta SDK sign in method - // and subsequent interception, and not by gateway - const appClientId1 = 'appClientId1'; - const fromURI1 = 'fromURI1'; - - breachCheck(); - cy - .createTestUser({ - isUserEmailValidated: true, - }) - ?.then(({ emailAddress }) => { - cy.visit( - `/reset-password?returnUrl=${encodedReturnUrl}&appClientId=${appClientId1}&fromURI=${fromURI1}`, - ); - const timeRequestWasMade = new Date(); - - cy.contains('Forgot password'); - cy.get('input[name=email]').type(emailAddress); - - // Continue checking the password reset flow after reCAPTCHA assertions above. - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.contains('Check your email inbox'); - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /reset-password\/([^"]*)/, - ).then(({ token }) => { - // should contain these params instead - const appClientId2 = 'appClientId2'; - const fromURI2 = 'fromURI2'; - cy.visit( - `/reset-password/${token}?appClientId=${appClientId2}&fromURI=${fromURI2}`, - ); - - cy.url() - .should('contain', appClientId2) - .and('contain', fromURI2) - .and('not.contain', appClientId1) - .and('not.contain', fromURI1); - - cy.get('form') - .should('have.attr', 'action') - .and('match', new RegExp(appClientId2)) - .and('match', new RegExp(fromURI2)) - .and('not.match', new RegExp(appClientId1)) - .and('not.match', new RegExp(fromURI1)); - }); - }); - }); - }); - - context('STAGED user', () => { - it("changes the reader's password", () => { - breachCheck(); - cy.createTestUser({ isGuestUser: true })?.then(({ emailAddress }) => { - cy.getTestOktaUser(emailAddress).then((oktaUser) => { - expect(oktaUser.status).to.eq(Status.STAGED); - - cy.visit('/reset-password'); - const timeRequestWasMade = new Date(); - - cy.get('input[name=email]').type(emailAddress); - cy.get('button[type="submit"]').click(); - - cy.contains('Check your email inbox'); - cy.contains(emailAddress); - cy.contains('Resend email'); - cy.contains('Change email address'); - - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /\/set-password\/([^"]*)/, - ).then(({ links, body }) => { - expect(body).to.have.string('Welcome back'); - - expect(body).to.have.string('Create password'); - expect(links.length).to.eq(2); - const setPasswordLink = links.find( - (s) => s.text?.includes('Create password'), - ); - expect(setPasswordLink?.href ?? '').not.to.have.string( - 'useOkta=true', - ); - cy.visit(setPasswordLink?.href as string); - cy.contains('Create password'); - cy.contains(emailAddress); - - cy.get('input[name=password]').type(randomPassword()); - - cy.wait('@breachCheck'); - cy.get('[data-cy="main-form-submit-button"]') - .click() - .should('be.disabled'); - cy.contains('Password created'); - cy.contains(emailAddress.toLowerCase()); - }); - }); - }); - }); - - it("changes the reader's password, and has a prefixed recovery token when using a native app", () => { - breachCheck(); - cy.createTestUser({ isGuestUser: true })?.then(({ emailAddress }) => { - cy.getTestOktaUser(emailAddress).then((oktaUser) => { - expect(oktaUser.status).to.eq(Status.STAGED); - - const appClientId = Cypress.env('OKTA_ANDROID_CLIENT_ID'); - const fromURI = 'fromURI1'; - - cy.visit( - `/reset-password?appClientId=${appClientId}&fromURI=${fromURI}`, - ); - const timeRequestWasMade = new Date(); - - cy.get('input[name=email]').type(emailAddress); - cy.get('button[type="submit"]').click(); - - cy.contains('Check your email inbox'); - cy.contains(emailAddress); - cy.contains('Resend email'); - cy.contains('Change email address'); - - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /\/set-password\/([^"]*)/, - ).then(({ links, body }) => { - expect(body).to.have.string('Welcome back'); - - expect(body).to.have.string('Create password'); - expect(links.length).to.eq(2); - const setPasswordLink = links.find( - (s) => s.text?.includes('Create password'), - ); - expect(setPasswordLink?.href ?? '') - .to.have.string('al_') - .and.not.to.have.string('useOkta=true'); - cy.visit(setPasswordLink?.href as string); - cy.contains('Create password'); - cy.contains(emailAddress); - - cy.get('input[name=password]').type(randomPassword()); - - cy.wait('@breachCheck'); - cy.get('[data-cy="main-form-submit-button"]') - .click() - .should('be.disabled'); - cy.contains('Password created'); - cy.contains(emailAddress.toLowerCase()); - }); - }); - }); - }); - }); - - context('PROVISIONED user', () => { - it("changes the reader's password", () => { - breachCheck(); - cy.createTestUser({ isGuestUser: true })?.then(({ emailAddress }) => { - cy.activateTestOktaUser(emailAddress).then(() => { - cy.getTestOktaUser(emailAddress).then((oktaUser) => { - expect(oktaUser.status).to.eq(Status.PROVISIONED); - - cy.visit('/reset-password'); - const timeRequestWasMade = new Date(); - - cy.get('input[name=email]').type(emailAddress); - cy.get('button[type="submit"]').click(); - - cy.contains('Check your email inbox'); - cy.contains(emailAddress); - cy.contains('Resend email'); - cy.contains('Change email address'); - - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /\/set-password\/([^"]*)/, - ).then(({ links, body }) => { - expect(body).to.have.string('Welcome back'); - - expect(body).to.have.string('Create password'); - expect(links.length).to.eq(2); - const setPasswordLink = links.find( - (s) => s.text?.includes('Create password'), - ); - expect(setPasswordLink?.href ?? '').not.to.have.string( - 'useOkta=true', - ); - cy.visit(setPasswordLink?.href as string); - cy.contains('Create password'); - cy.contains(emailAddress); - - cy.get('input[name=password]').type(randomPassword()); - - cy.wait('@breachCheck'); - cy.get('[data-cy="main-form-submit-button"]') - .click() - .should('be.disabled'); - cy.contains('Password created'); - cy.contains(emailAddress.toLowerCase()); - }); - }); - }); - }); - }); - }); - - context('RECOVERY user', () => { - it("changes the reader's password", () => { - breachCheck(); - cy.createTestUser({ isGuestUser: false })?.then(({ emailAddress }) => { - cy.resetOktaUserPassword(emailAddress).then(() => { - cy.getTestOktaUser(emailAddress).then((oktaUser) => { - expect(oktaUser.status).to.eq(Status.RECOVERY); - - cy.visit('/reset-password'); - const timeRequestWasMade = new Date(); - - cy.get('input[name=email]').type(emailAddress); - cy.get('button[type="submit"]').click(); - - cy.contains('Check your email inbox'); - cy.contains(emailAddress); - cy.contains('Resend email'); - cy.contains('Change email address'); - - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /reset-password\/([^"]*)/, - ).then(({ links, body }) => { - expect(body).to.have.string('Password reset'); - expect(body).to.have.string('Reset password'); - expect(links.length).to.eq(2); - const resetPasswordLink = links.find( - (s) => s.text?.includes('Reset password'), - ); - expect(resetPasswordLink?.href ?? '').not.to.have.string( - 'useOkta=true', - ); - cy.visit(resetPasswordLink?.href as string); - cy.contains('Reset password'); - cy.contains(emailAddress); - - cy.get('input[name=password]').type(randomPassword()); - - cy.wait('@breachCheck'); - cy.get('[data-cy="main-form-submit-button"]') - .click() - .should('be.disabled'); - cy.contains('Password updated'); - cy.contains(emailAddress.toLowerCase()); - }); - }); - }); - }); - }); - }); - - context('PASSWORD_EXPIRED user', () => { - it("changes the reader's password", () => { - breachCheck(); - cy.createTestUser({ isGuestUser: false })?.then(({ emailAddress }) => { - cy.expireOktaUserPassword(emailAddress).then(() => { - cy.getTestOktaUser(emailAddress).then((oktaUser) => { - expect(oktaUser.status).to.eq(Status.PASSWORD_EXPIRED); - - cy.visit('/reset-password'); - const timeRequestWasMade = new Date(); - - cy.get('input[name=email]').type(emailAddress); - cy.get('button[type="submit"]').click(); - - cy.contains('Check your email inbox'); - cy.contains(emailAddress); - cy.contains('Resend email'); - cy.contains('Change email address'); - - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /reset-password\/([^"]*)/, - ).then(({ links, body }) => { - expect(body).to.have.string('Password reset'); - expect(body).to.have.string('Reset password'); - expect(links.length).to.eq(2); - const resetPasswordLink = links.find( - (s) => s.text?.includes('Reset password'), - ); - expect(resetPasswordLink?.href ?? '').not.to.have.string( - 'useOkta=true', - ); - cy.visit(resetPasswordLink?.href as string); - cy.contains('Reset password'); - cy.contains(emailAddress); - - cy.get('input[name=password]').type(randomPassword()); - - cy.wait('@breachCheck'); - cy.get('[data-cy="main-form-submit-button"]') - .click() - .should('be.disabled'); - cy.contains('Password updated'); - cy.contains(emailAddress.toLowerCase()); - }); - }); - }); - }); - }); - }); + context('Account exists', () => { + it("changes the reader's password", () => { + const encodedReturnUrl = + 'https%3A%2F%2Fm.code.dev-theguardian.com%2Ftravel%2F2019%2Fdec%2F18%2Ffood-culture-tour-bethlehem-palestine-east-jerusalem-photo-essay'; + const encodedRef = 'https%3A%2F%2Fm.theguardian.com'; + const refViewId = 'testRefViewId'; + const clientId = 'jobs'; + + // these params should *not* persist between initial registration and welcome page + // despite the fact that they PersistableQueryParams, as these are set by the Okta SDK sign in method + // and subsequent interception, and not by gateway + const appClientId = 'appClientId1'; + const fromURI = 'fromURI1'; + + breachCheck(); + cy + .createTestUser({ + isUserEmailValidated: true, + }) + ?.then(({ emailAddress }) => { + cy.visit( + `/reset-password?returnUrl=${encodedReturnUrl}&ref=${encodedRef}&refViewId=${refViewId}&clientId=${clientId}&appClientId=${appClientId}&fromURI=${fromURI}`, + ); + const timeRequestWasMade = new Date(); + + cy.contains('Forgot password'); + cy.get('input[name=email]').type(emailAddress); + + // Continue checking the password reset flow after reCAPTCHA assertions above. + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.contains('Check your email inbox'); + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /reset-password\/([^"]*)/, + ).then(({ token }) => { + cy.visit(`/reset-password/${token}`); + + cy.get('form') + .should('have.attr', 'action') + .and('match', new RegExp(encodedReturnUrl)) + .and('match', new RegExp(refViewId)) + .and('match', new RegExp(encodedRef)) + .and('match', new RegExp(clientId)) + .and('not.match', new RegExp(appClientId)) + .and('not.match', new RegExp(fromURI)); + + //we are reloading here to make sure the params are persisted even on page refresh + cy.reload(); + + cy.get('input[name=password]').type(randomPassword()); + + cy.wait('@breachCheck'); + cy.get('[data-cy="main-form-submit-button"]') + .click() + .should('be.disabled'); + cy.contains('Password updated'); + cy.contains(emailAddress.toLowerCase()); + + cy.url().should('contain', encodedReturnUrl); + cy.url().should('contain', refViewId); + cy.url().should('contain', encodedRef); + cy.url().should('contain', clientId); + cy.url().should('not.contain', 'useOkta=true'); + cy.url().should('not.contain', appClientId); + cy.url().should('not.contain', fromURI); + }); + }); + }); + + it("changes the reader's password, and has a prefixed recovery token when using a native app", () => { + const encodedReturnUrl = + 'https%3A%2F%2Fm.code.dev-theguardian.com%2Ftravel%2F2019%2Fdec%2F18%2Ffood-culture-tour-bethlehem-palestine-east-jerusalem-photo-essay'; + const encodedRef = 'https%3A%2F%2Fm.theguardian.com'; + const refViewId = 'testRefViewId'; + const clientId = 'jobs'; + + // these params should *not* persist between initial registration and welcome page + // despite the fact that they PersistableQueryParams, as these are set by the Okta SDK sign in method + // and subsequent interception, and not by gateway + const appClientId = Cypress.env('OKTA_ANDROID_CLIENT_ID'); + const fromURI = 'fromURI1'; + + breachCheck(); + cy + .createTestUser({ + isUserEmailValidated: true, + }) + ?.then(({ emailAddress }) => { + cy.visit( + `/reset-password?returnUrl=${encodedReturnUrl}&ref=${encodedRef}&refViewId=${refViewId}&clientId=${clientId}&appClientId=${appClientId}&fromURI=${fromURI}`, + ); + const timeRequestWasMade = new Date(); + + cy.contains('Forgot password'); + cy.get('input[name=email]').type(emailAddress); + + // Continue checking the password reset flow after reCAPTCHA assertions above. + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.contains('Check your email inbox'); + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /reset-password\/([^"]*)/, + ).then(({ token }) => { + expect(token).to.have.string('al_'); + cy.visit(`/reset-password/${token}`); + + cy.get('form') + .should('have.attr', 'action') + .and('match', new RegExp(encodedReturnUrl)) + .and('match', new RegExp(refViewId)) + .and('match', new RegExp(encodedRef)) + .and('match', new RegExp(clientId)) + .and('not.match', new RegExp(appClientId)) + .and('not.match', new RegExp(fromURI)); + + //we are reloading here to make sure the params are persisted even on page refresh + cy.reload(); + + cy.get('input[name=password]').type(randomPassword()); + + cy.wait('@breachCheck'); + cy.get('[data-cy="main-form-submit-button"]') + .click() + .should('be.disabled'); + cy.contains('Password updated'); + cy.contains(emailAddress.toLowerCase()); + + cy.url().should('contain', encodedReturnUrl); + cy.url().should('contain', refViewId); + cy.url().should('contain', encodedRef); + cy.url().should('contain', clientId); + cy.url().should('not.contain', 'useOkta=true'); + cy.url().should('not.contain', appClientId); + cy.url().should('not.contain', fromURI); + }); + }); + }); + + it("changes the reader's password and overrides returnUrl from encryptedStateCookie if one set on reset password page url", () => { + const encodedReturnUrl = + 'https%3A%2F%2Fm.code.dev-theguardian.com%2Ftravel%2F2019%2Fdec%2F18%2Ffood-culture-tour-bethlehem-palestine-east-jerusalem-photo-essay'; + + breachCheck(); + cy + .createTestUser({ + isUserEmailValidated: true, + }) + ?.then(({ emailAddress }) => { + cy.visit(`/reset-password?returnUrl=${encodedReturnUrl}`); + const timeRequestWasMade = new Date(); + + cy.contains('Forgot password'); + cy.get('input[name=email]').type(emailAddress); + + // Continue checking the password reset flow after reCAPTCHA assertions above. + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.contains('Check your email inbox'); + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /reset-password\/([^"]*)/, + ).then(({ token }) => { + const newReturnUrl = encodeURIComponent( + 'https://www.theguardian.com/technology/2017/may/04/nier-automata-sci-fi-game-sleeper-hit-designer-yoko-taro', + ); + cy.visit(`/reset-password/${token}?returnUrl=${newReturnUrl}`); + + cy.url() + .should('contain', newReturnUrl) + .and('not.contain', encodedReturnUrl); + + cy.get('form') + .should('have.attr', 'action') + .and('match', new RegExp(newReturnUrl)) + .and('not.match', new RegExp(encodedReturnUrl)); + + //we are reloading here to make sure the params are persisted even on page refresh + cy.reload(); + + cy.get('input[name=password]').type(randomPassword()); + + cy.wait('@breachCheck'); + cy.get('[data-cy="main-form-submit-button"]') + .click() + .should('be.disabled'); + cy.contains('Password updated'); + cy.contains(emailAddress.toLowerCase()); + + cy.url() + .should('contain', newReturnUrl) + .and('not.contain', encodedReturnUrl) + .and('not.contain', 'useOkta=true'); + }); + }); + }); + + it('overrides appClientId and fromURI if set on reset password page url', () => { + const encodedReturnUrl = + 'https%3A%2F%2Fm.code.dev-theguardian.com%2Ftravel%2F2019%2Fdec%2F18%2Ffood-culture-tour-bethlehem-palestine-east-jerusalem-photo-essay'; + + // these params should *not* persist between initial registration and welcome page + // despite the fact that they PersistableQueryParams, as these are set by the Okta SDK sign in method + // and subsequent interception, and not by gateway + const appClientId1 = 'appClientId1'; + const fromURI1 = 'fromURI1'; + + breachCheck(); + cy + .createTestUser({ + isUserEmailValidated: true, + }) + ?.then(({ emailAddress }) => { + cy.visit( + `/reset-password?returnUrl=${encodedReturnUrl}&appClientId=${appClientId1}&fromURI=${fromURI1}`, + ); + const timeRequestWasMade = new Date(); + + cy.contains('Forgot password'); + cy.get('input[name=email]').type(emailAddress); + + // Continue checking the password reset flow after reCAPTCHA assertions above. + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.contains('Check your email inbox'); + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /reset-password\/([^"]*)/, + ).then(({ token }) => { + // should contain these params instead + const appClientId2 = 'appClientId2'; + const fromURI2 = 'fromURI2'; + cy.visit( + `/reset-password/${token}?appClientId=${appClientId2}&fromURI=${fromURI2}`, + ); + + cy.url() + .should('contain', appClientId2) + .and('contain', fromURI2) + .and('not.contain', appClientId1) + .and('not.contain', fromURI1); + + cy.get('form') + .should('have.attr', 'action') + .and('match', new RegExp(appClientId2)) + .and('match', new RegExp(fromURI2)) + .and('not.match', new RegExp(appClientId1)) + .and('not.match', new RegExp(fromURI1)); + }); + }); + }); + }); + + context('STAGED user', () => { + it("changes the reader's password", () => { + breachCheck(); + cy.createTestUser({ isGuestUser: true })?.then(({ emailAddress }) => { + cy.getTestOktaUser(emailAddress).then((oktaUser) => { + expect(oktaUser.status).to.eq(Status.STAGED); + + cy.visit('/reset-password'); + const timeRequestWasMade = new Date(); + + cy.get('input[name=email]').type(emailAddress); + cy.get('button[type="submit"]').click(); + + cy.contains('Check your email inbox'); + cy.contains(emailAddress); + cy.contains('Resend email'); + cy.contains('Change email address'); + + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /\/set-password\/([^"]*)/, + ).then(({ links, body }) => { + expect(body).to.have.string('Welcome back'); + + expect(body).to.have.string('Create password'); + expect(links.length).to.eq(2); + const setPasswordLink = links.find( + (s) => s.text?.includes('Create password'), + ); + expect(setPasswordLink?.href ?? '').not.to.have.string( + 'useOkta=true', + ); + cy.visit(setPasswordLink?.href as string); + cy.contains('Create password'); + cy.contains(emailAddress); + + cy.get('input[name=password]').type(randomPassword()); + + cy.wait('@breachCheck'); + cy.get('[data-cy="main-form-submit-button"]') + .click() + .should('be.disabled'); + cy.contains('Password created'); + cy.contains(emailAddress.toLowerCase()); + }); + }); + }); + }); + + it("changes the reader's password, and has a prefixed recovery token when using a native app", () => { + breachCheck(); + cy.createTestUser({ isGuestUser: true })?.then(({ emailAddress }) => { + cy.getTestOktaUser(emailAddress).then((oktaUser) => { + expect(oktaUser.status).to.eq(Status.STAGED); + + const appClientId = Cypress.env('OKTA_ANDROID_CLIENT_ID'); + const fromURI = 'fromURI1'; + + cy.visit( + `/reset-password?appClientId=${appClientId}&fromURI=${fromURI}`, + ); + const timeRequestWasMade = new Date(); + + cy.get('input[name=email]').type(emailAddress); + cy.get('button[type="submit"]').click(); + + cy.contains('Check your email inbox'); + cy.contains(emailAddress); + cy.contains('Resend email'); + cy.contains('Change email address'); + + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /\/set-password\/([^"]*)/, + ).then(({ links, body }) => { + expect(body).to.have.string('Welcome back'); + + expect(body).to.have.string('Create password'); + expect(links.length).to.eq(2); + const setPasswordLink = links.find( + (s) => s.text?.includes('Create password'), + ); + expect(setPasswordLink?.href ?? '') + .to.have.string('al_') + .and.not.to.have.string('useOkta=true'); + cy.visit(setPasswordLink?.href as string); + cy.contains('Create password'); + cy.contains(emailAddress); + + cy.get('input[name=password]').type(randomPassword()); + + cy.wait('@breachCheck'); + cy.get('[data-cy="main-form-submit-button"]') + .click() + .should('be.disabled'); + cy.contains('Password created'); + cy.contains(emailAddress.toLowerCase()); + }); + }); + }); + }); + }); + + context('PROVISIONED user', () => { + it("changes the reader's password", () => { + breachCheck(); + cy.createTestUser({ isGuestUser: true })?.then(({ emailAddress }) => { + cy.activateTestOktaUser(emailAddress).then(() => { + cy.getTestOktaUser(emailAddress).then((oktaUser) => { + expect(oktaUser.status).to.eq(Status.PROVISIONED); + + cy.visit('/reset-password'); + const timeRequestWasMade = new Date(); + + cy.get('input[name=email]').type(emailAddress); + cy.get('button[type="submit"]').click(); + + cy.contains('Check your email inbox'); + cy.contains(emailAddress); + cy.contains('Resend email'); + cy.contains('Change email address'); + + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /\/set-password\/([^"]*)/, + ).then(({ links, body }) => { + expect(body).to.have.string('Welcome back'); + + expect(body).to.have.string('Create password'); + expect(links.length).to.eq(2); + const setPasswordLink = links.find( + (s) => s.text?.includes('Create password'), + ); + expect(setPasswordLink?.href ?? '').not.to.have.string( + 'useOkta=true', + ); + cy.visit(setPasswordLink?.href as string); + cy.contains('Create password'); + cy.contains(emailAddress); + + cy.get('input[name=password]').type(randomPassword()); + + cy.wait('@breachCheck'); + cy.get('[data-cy="main-form-submit-button"]') + .click() + .should('be.disabled'); + cy.contains('Password created'); + cy.contains(emailAddress.toLowerCase()); + }); + }); + }); + }); + }); + }); + + context('RECOVERY user', () => { + it("changes the reader's password", () => { + breachCheck(); + cy.createTestUser({ isGuestUser: false })?.then(({ emailAddress }) => { + cy.resetOktaUserPassword(emailAddress).then(() => { + cy.getTestOktaUser(emailAddress).then((oktaUser) => { + expect(oktaUser.status).to.eq(Status.RECOVERY); + + cy.visit('/reset-password'); + const timeRequestWasMade = new Date(); + + cy.get('input[name=email]').type(emailAddress); + cy.get('button[type="submit"]').click(); + + cy.contains('Check your email inbox'); + cy.contains(emailAddress); + cy.contains('Resend email'); + cy.contains('Change email address'); + + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /reset-password\/([^"]*)/, + ).then(({ links, body }) => { + expect(body).to.have.string('Password reset'); + expect(body).to.have.string('Reset password'); + expect(links.length).to.eq(2); + const resetPasswordLink = links.find( + (s) => s.text?.includes('Reset password'), + ); + expect(resetPasswordLink?.href ?? '').not.to.have.string( + 'useOkta=true', + ); + cy.visit(resetPasswordLink?.href as string); + cy.contains('Reset password'); + cy.contains(emailAddress); + + cy.get('input[name=password]').type(randomPassword()); + + cy.wait('@breachCheck'); + cy.get('[data-cy="main-form-submit-button"]') + .click() + .should('be.disabled'); + cy.contains('Password updated'); + cy.contains(emailAddress.toLowerCase()); + }); + }); + }); + }); + }); + }); + + context('PASSWORD_EXPIRED user', () => { + it("changes the reader's password", () => { + breachCheck(); + cy.createTestUser({ isGuestUser: false })?.then(({ emailAddress }) => { + cy.expireOktaUserPassword(emailAddress).then(() => { + cy.getTestOktaUser(emailAddress).then((oktaUser) => { + expect(oktaUser.status).to.eq(Status.PASSWORD_EXPIRED); + + cy.visit('/reset-password'); + const timeRequestWasMade = new Date(); + + cy.get('input[name=email]').type(emailAddress); + cy.get('button[type="submit"]').click(); + + cy.contains('Check your email inbox'); + cy.contains(emailAddress); + cy.contains('Resend email'); + cy.contains('Change email address'); + + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /reset-password\/([^"]*)/, + ).then(({ links, body }) => { + expect(body).to.have.string('Password reset'); + expect(body).to.have.string('Reset password'); + expect(links.length).to.eq(2); + const resetPasswordLink = links.find( + (s) => s.text?.includes('Reset password'), + ); + expect(resetPasswordLink?.href ?? '').not.to.have.string( + 'useOkta=true', + ); + cy.visit(resetPasswordLink?.href as string); + cy.contains('Reset password'); + cy.contains(emailAddress); + + cy.get('input[name=password]').type(randomPassword()); + + cy.wait('@breachCheck'); + cy.get('[data-cy="main-form-submit-button"]') + .click() + .should('be.disabled'); + cy.contains('Password updated'); + cy.contains(emailAddress.toLowerCase()); + }); + }); + }); + }); + }); + }); }); diff --git a/cypress/integration/ete-okta/sign_in.1.cy.ts b/cypress/integration/ete-okta/sign_in.1.cy.ts index 2349335c79..b6a8e9a922 100644 --- a/cypress/integration/ete-okta/sign_in.1.cy.ts +++ b/cypress/integration/ete-okta/sign_in.1.cy.ts @@ -1,414 +1,414 @@ import * as SignIn from '../shared/sign_in.shared'; describe('Sign in flow, Okta enabled', () => { - beforeEach(() => { - SignIn.beforeEach(); - }); - - context('Terms and Conditions links', () => { - it(...SignIn.linksToTheGoogleTermsOfServicePage()); - it(...SignIn.linksToTheGooglePrivacyPolicyPage()); - it(...SignIn.linksToTheGuardianTermsAndConditionsPage()); - it(...SignIn.linksToTheGuardianPrivacyPolicyPage()); - it( - ...SignIn.linksToTheGuardianJobsTermsAndConditionsPageWhenJobsClientIdSet(), - ); - it(...SignIn.linksToTheGuardianJobsPrivacyPolicyPageWhenJobsClientIdSet()); - }); - - it(...SignIn.persistsTheClientIdWhenNavigatingAway()); - it(...SignIn.appliesFormValidationToEmailAndPasswordInputFields()); - it(...SignIn.showsAMessageWhenCredentialsAreInvalid()); - it(...SignIn.correctlySignsInAnExistingUser()); - it(...SignIn.navigatesToResetPassword()); - it(...SignIn.navigatesToRegistration()); - it(...SignIn.respectsTheReturnUrlQueryParam()); - it(...SignIn.removesEncryptedEmailParameterFromQueryString()); - it( - ...SignIn.removesEncryptedEmailParameterAndPreservesAllOtherValidParameters(), - ); - it( - ...SignIn.showsRecaptchaErrorsWhenTheUserTriesToSignInOfflineAndAllowsSignInWhenBackOnline(), - ); - it(...SignIn.redirectsToOptInPrompt()); - it(...SignIn.hitsAccessTokenRateLimitAndRecoversTokenAfterTimeout()); - - it(...SignIn.redirectsCorrectlyForSocialSignIn()); - it( - ...SignIn.showsAnErrorMessageAndInformationParagraphWhenAccountLinkingRequiredErrorParameterIsPresent(), - ); - it( - ...SignIn.doesNotDisplaySocialButtonsWhenAccountLinkingRequiredErrorParameterIsPresent(), - ); - - it('sets emailValidated flag on oauth callback', () => { - // this is a specific test case for new user registrations in Okta - // In Okta new social registered users are added to the GuardianUser-EmailValidated group - // by default, but the custom emailValidated field is not defined/set to false - // this causes problems in legacy code, where the emailValidated flag is not set but the group is - // so we need to set the flag to true when the user is added to the group - // we do this on the oauth callback route /oauth/authorization-code/callback - // where we update the user profile with the emailValidated flag if the user is in the GuardianUser-EmailValidated group but the emailValidated is falsy - - // This test checks this behaviour by first getting a user into this state - // i.e user.profile.emailValidated = false, and user groups has GuardianUser-EmailValidated - - // first we have to get the id of the GuardianUser-EmailValidated group - cy.findEmailValidatedOktaGroupId().then((groupId) => { - // next we create a test user - cy.createTestUser({}).then(({ emailAddress, finalPassword }) => { - // we get the user profile object from Okta - cy.getTestOktaUser(emailAddress).then((user) => { - const { id, profile } = user; - // check the user profile has the emailValidated flag set to false - expect(profile.emailValidated).to.be.false; - // next check the user groups - cy.getOktaUserGroups(id).then((groups) => { - // make sure the user is not in the GuardianUser-EmailValidated group - const group = groups.find((g) => g.id === groupId); - expect(group).not.to.exist; - - // and add them to the group if this is the case - cy.addOktaUserToGroup(id, groupId); - - // at this point the user is in the correct state - // so we attempt to sign them in - cy.visit( - `/signin?returnUrl=${encodeURIComponent( - `https://${Cypress.env('BASE_URI')}/consents`, - )}`, - ); - cy.get('input[name=email]').type(emailAddress); - cy.get('input[name=password]').type(finalPassword); - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.url().should('include', '/consents'); - - // at this point the oauth callback route will have run, so we can recheck the user profile to see if the emailValidated flag has been set - cy.getTestOktaUser(id).then((user) => { - const { profile } = user; - expect(profile.emailValidated).to.be.true; - }); - - // and the user should also be in the group - cy.getOktaUserGroups(id).then((groups) => { - const group = groups.find((g) => g.id === groupId); - expect(group).to.exist; - }); - }); - }); - }); - }); - }); - context('Okta session refresh', () => { - it('refreshes a valid Okta session', () => { - // Create a validated test user - cy.createTestUser({ isUserEmailValidated: true }).then( - ({ emailAddress, finalPassword }) => { - // Sign our new user in - cy.visit( - `/signin?returnUrl=${encodeURIComponent( - `https://${Cypress.env('BASE_URI')}/consents`, - )}`, - ); - cy.get('input[name=email]').type(emailAddress); - cy.get('input[name=password]').type(finalPassword); - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.url().should('include', '/consents'); - - // Get the current session data - cy.getCookie('sid').then((originalSidCookie) => { - expect(originalSidCookie).to.exist; - - // Refresh our user session - cy.visit( - `/signin/refresh?returnUrl=${encodeURIComponent( - `https://${Cypress.env('BASE_URI')}/consents`, - )}`, - ); - cy.url().should('include', '/consents'); - - // Get the refreshed session data - cy.getCookie('sid').then((newSidCookie) => { - expect(newSidCookie).to.exist; - expect(newSidCookie?.value).to.equal(originalSidCookie?.value); - if (newSidCookie?.expiry && originalSidCookie?.expiry) { - expect(newSidCookie?.expiry).to.be.greaterThan( - originalSidCookie?.expiry, - ); - } - }); - }); - }, - ); - }); - it('sends a client with the Okta cookie and an invalid Okta session to the redirectUrl', () => { - // Create a validated test user - cy.createTestUser({ isUserEmailValidated: true }).then( - ({ emailAddress, finalPassword }) => { - // Sign our new user in - cy.visit( - `/signin?returnUrl=${encodeURIComponent( - `https://${Cypress.env('BASE_URI')}/consents`, - )}`, - ); - cy.get('input[name=email]').type(emailAddress); - cy.get('input[name=password]').type(finalPassword); - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.url().should('include', '/consents'); - - // Get the current session data - cy.getCookie('sid').then((sidCookie) => { - // Close the user's current session in Okta - cy.closeCurrentOktaSession(sidCookie?.value).then(() => { - // Refresh our user session - cy.visit( - `/signin/refresh?returnUrl=${encodeURIComponent( - `https://${Cypress.env('BASE_URI')}/consents`, - )}`, - ); - cy.url().should('include', '/consents'); - }); - }); - }, - ); - }); - it('sends a client without the Okta cookie to the redirectUrl', () => { - // Create a validated test user - cy.createTestUser({ isUserEmailValidated: true }).then( - ({ emailAddress, finalPassword }) => { - // Sign our new user in - cy.visit( - `/signin?returnUrl=${encodeURIComponent( - `https://${Cypress.env('BASE_URI')}/consents`, - )}`, - ); - cy.get('input[name=email]').type(emailAddress); - cy.get('input[name=password]').type(finalPassword); - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.url().should('include', '/consents'); - - // Delete the Okta sid cookie - // strange behaviour form Cypress 12 - // where we need to delete cookie from both domains - // to get the test to pass - // cypress 12 seems to have issues with hostOnly cookies not being removed or persisting after clear - // https://github.com/cypress-io/cypress/issues/25174 - cy.clearCookie('sid', { - domain: Cypress.env('BASE_URI'), - }); - cy.clearCookie('sid', { - domain: `.${Cypress.env('BASE_URI')}`, - }); - - // Visit the refresh endpoint - cy.visit( - `/signin/refresh?returnUrl=${encodeURIComponent( - `https://${Cypress.env('BASE_URI')}/consents`, - )}`, - ); - cy.url().should('include', '/consents'); - - cy.getCookie('sid').should('not.exist'); - }, - ); - }); - it('sends a client with neither Okta nor Identity cookies to /signin', () => { - // Create a validated test user - cy.createTestUser({ isUserEmailValidated: true }).then( - ({ emailAddress, finalPassword }) => { - // Sign our new user in - cy.visit( - `/signin?returnUrl=${encodeURIComponent( - `https://${Cypress.env('BASE_URI')}/consents`, - )}`, - ); - cy.get('input[name=email]').type(emailAddress); - cy.get('input[name=password]').type(finalPassword); - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.url().should('include', '/consents'); - - // Delete all cookies (Okta and IDAPI) - cy.clearCookies(); - - // Visit the refresh endpoint - cy.visit( - `/signin/refresh?returnUrl=${encodeURIComponent( - `https://${Cypress.env('BASE_URI')}/consents`, - )}`, - ); - cy.url().should('include', '/signin'); - - cy.getCookie('sid').should('not.exist'); - cy.getCookie('sc_gu_u').should('not.exist'); - cy.getCookie('sc_gu_la').should('not.exist'); - }, - ); - }); - it('leaves the last access cookie unchanged when refreshing a valid Okta session', () => { - // Create a validated test user - cy.createTestUser({ isUserEmailValidated: true }).then( - ({ emailAddress, finalPassword }) => { - // Sign our new user in - cy.visit( - `/signin?returnUrl=${encodeURIComponent( - `https://${Cypress.env('BASE_URI')}/consents`, - )}`, - ); - cy.get('input[name=email]').type(emailAddress); - cy.get('input[name=password]').type(finalPassword); - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.url().should('include', '/consents'); - - // Get the current session data - cy.getCookie('SC_GU_LA').then((originalLastAccessCookie) => { - cy.getCookie('SC_GU_U').then((originalSecureIdapiCookie) => { - expect(originalLastAccessCookie).to.exist; - expect(originalSecureIdapiCookie).to.exist; - - // Refresh our user session - cy.visit( - `/signin/refresh?returnUrl=${encodeURIComponent( - `https://${Cypress.env('BASE_URI')}/consents`, - )}`, - ); - cy.url().should('include', '/consents'); - - // Expect the last access cookie to be unchanged - cy.getCookie('SC_GU_LA').then((lastAccessCookie) => { - expect(lastAccessCookie).to.exist; - expect(lastAccessCookie?.value).to.equal( - originalLastAccessCookie?.value, - ); - expect(lastAccessCookie?.expiry).to.equal( - originalLastAccessCookie?.expiry, - ); - }); - - // Expect other Idapi cookies to have changed - cy.getCookie('SC_GU_U').then((secureIdapiCookie) => { - expect(secureIdapiCookie).to.exist; - expect(secureIdapiCookie?.value).not.to.equal( - originalSecureIdapiCookie?.value, - ); - if ( - secureIdapiCookie?.expiry && - originalSecureIdapiCookie?.expiry - ) { - expect(secureIdapiCookie?.expiry).to.be.greaterThan( - originalSecureIdapiCookie?.expiry, - ); - } - }); - }); - }); - }, - ); - }); - }); - - context('Okta unvalidated email flow', () => { - it('Sends a user with an unvalidated email a reset password email on sign in', () => { - cy - .createTestUser({ - isUserEmailValidated: false, - }) - ?.then(({ emailAddress, finalPassword }) => { - const timeRequestWasMade = new Date(); - cy.visit('/signin'); - cy.get('input[name=email]').type(emailAddress); - cy.get('input[name=password]').type(finalPassword); - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.url().should('include', '/signin/email-sent'); - cy.contains( - 'For security reasons we need you to change your password.', - ); - cy.contains(emailAddress); - cy.contains('Resend email'); - cy.contains('Change email address'); - - // Ensure the user's authentication cookies are not set - cy.getCookie('sid').then((sidCookie) => { - expect(sidCookie).to.not.exist; - - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /reset-password\/([^"]*)/, - ).then(({ links, body, token }) => { - expect(body).to.have.string( - 'Because your security is extremely important to us, we have changed our password policy.', - ); - expect(body).to.have.string('Reset password'); - expect(links.length).to.eq(2); - const resetPasswordLink = links.find( - (s) => s.text?.includes('Reset password'), - ); - expect(resetPasswordLink?.href ?? '').to.have.string( - 'reset-password', - ); - cy.visit(`/reset-password/${token}`); - cy.contains(emailAddress); - cy.contains('Reset password'); - }); - }); - }); - }); - }); - - context('Okta session exists on /signin', () => { - beforeEach(() => { - // Intercept the external redirect page. - // We just want to check that the redirect happens, not that the page loads. - cy.intercept('GET', 'https://m.code.dev-theguardian.com/', (req) => { - req.reply(200); - }); - }); - - it('shows the signed in as page', () => { - // Create a validated test user - cy.createTestUser({ isUserEmailValidated: true }).then( - ({ emailAddress, finalPassword }) => { - // Sign our new user in - cy.visit( - `/signin?returnUrl=${encodeURIComponent( - `https://${Cypress.env('BASE_URI')}/consents`, - )}`, - ); - cy.get('input[name=email]').type(emailAddress); - cy.get('input[name=password]').type(finalPassword); - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.url().should('include', '/consents'); - - // Get the current session data - cy.getCookie('sid').then((originalSidCookie) => { - expect(originalSidCookie).to.exist; - - // Visit sign in again - cy.visit( - `/signin?returnUrl=${encodeURIComponent( - `https://${Cypress.env('BASE_URI')}/consents`, - )}`, - ); - cy.url().should('include', '/signin'); - - cy.contains('Sign in to the Guardian'); - cy.contains('You are signed in with'); - cy.contains(emailAddress); - cy.contains('Continue') - .should('have.attr', 'href') - .and( - 'include', - `https://${Cypress.env( - 'BASE_URI', - )}/signin/refresh?returnUrl=https%3A%2F%2Fprofile.thegulocal.com%2Fconsents`, - ); - cy.contains('a', 'Sign in') - .should('have.attr', 'href') - .and('include', '/signout?returnUrl='); - cy.contains('Sign in with a different email'); - }); - }, - ); - }); - }); + beforeEach(() => { + SignIn.beforeEach(); + }); + + context('Terms and Conditions links', () => { + it(...SignIn.linksToTheGoogleTermsOfServicePage()); + it(...SignIn.linksToTheGooglePrivacyPolicyPage()); + it(...SignIn.linksToTheGuardianTermsAndConditionsPage()); + it(...SignIn.linksToTheGuardianPrivacyPolicyPage()); + it( + ...SignIn.linksToTheGuardianJobsTermsAndConditionsPageWhenJobsClientIdSet(), + ); + it(...SignIn.linksToTheGuardianJobsPrivacyPolicyPageWhenJobsClientIdSet()); + }); + + it(...SignIn.persistsTheClientIdWhenNavigatingAway()); + it(...SignIn.appliesFormValidationToEmailAndPasswordInputFields()); + it(...SignIn.showsAMessageWhenCredentialsAreInvalid()); + it(...SignIn.correctlySignsInAnExistingUser()); + it(...SignIn.navigatesToResetPassword()); + it(...SignIn.navigatesToRegistration()); + it(...SignIn.respectsTheReturnUrlQueryParam()); + it(...SignIn.removesEncryptedEmailParameterFromQueryString()); + it( + ...SignIn.removesEncryptedEmailParameterAndPreservesAllOtherValidParameters(), + ); + it( + ...SignIn.showsRecaptchaErrorsWhenTheUserTriesToSignInOfflineAndAllowsSignInWhenBackOnline(), + ); + it(...SignIn.redirectsToOptInPrompt()); + it(...SignIn.hitsAccessTokenRateLimitAndRecoversTokenAfterTimeout()); + + it(...SignIn.redirectsCorrectlyForSocialSignIn()); + it( + ...SignIn.showsAnErrorMessageAndInformationParagraphWhenAccountLinkingRequiredErrorParameterIsPresent(), + ); + it( + ...SignIn.doesNotDisplaySocialButtonsWhenAccountLinkingRequiredErrorParameterIsPresent(), + ); + + it('sets emailValidated flag on oauth callback', () => { + // this is a specific test case for new user registrations in Okta + // In Okta new social registered users are added to the GuardianUser-EmailValidated group + // by default, but the custom emailValidated field is not defined/set to false + // this causes problems in legacy code, where the emailValidated flag is not set but the group is + // so we need to set the flag to true when the user is added to the group + // we do this on the oauth callback route /oauth/authorization-code/callback + // where we update the user profile with the emailValidated flag if the user is in the GuardianUser-EmailValidated group but the emailValidated is falsy + + // This test checks this behaviour by first getting a user into this state + // i.e user.profile.emailValidated = false, and user groups has GuardianUser-EmailValidated + + // first we have to get the id of the GuardianUser-EmailValidated group + cy.findEmailValidatedOktaGroupId().then((groupId) => { + // next we create a test user + cy.createTestUser({}).then(({ emailAddress, finalPassword }) => { + // we get the user profile object from Okta + cy.getTestOktaUser(emailAddress).then((user) => { + const { id, profile } = user; + // check the user profile has the emailValidated flag set to false + expect(profile.emailValidated).to.be.false; + // next check the user groups + cy.getOktaUserGroups(id).then((groups) => { + // make sure the user is not in the GuardianUser-EmailValidated group + const group = groups.find((g) => g.id === groupId); + expect(group).not.to.exist; + + // and add them to the group if this is the case + cy.addOktaUserToGroup(id, groupId); + + // at this point the user is in the correct state + // so we attempt to sign them in + cy.visit( + `/signin?returnUrl=${encodeURIComponent( + `https://${Cypress.env('BASE_URI')}/consents`, + )}`, + ); + cy.get('input[name=email]').type(emailAddress); + cy.get('input[name=password]').type(finalPassword); + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.url().should('include', '/consents'); + + // at this point the oauth callback route will have run, so we can recheck the user profile to see if the emailValidated flag has been set + cy.getTestOktaUser(id).then((user) => { + const { profile } = user; + expect(profile.emailValidated).to.be.true; + }); + + // and the user should also be in the group + cy.getOktaUserGroups(id).then((groups) => { + const group = groups.find((g) => g.id === groupId); + expect(group).to.exist; + }); + }); + }); + }); + }); + }); + context('Okta session refresh', () => { + it('refreshes a valid Okta session', () => { + // Create a validated test user + cy.createTestUser({ isUserEmailValidated: true }).then( + ({ emailAddress, finalPassword }) => { + // Sign our new user in + cy.visit( + `/signin?returnUrl=${encodeURIComponent( + `https://${Cypress.env('BASE_URI')}/consents`, + )}`, + ); + cy.get('input[name=email]').type(emailAddress); + cy.get('input[name=password]').type(finalPassword); + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.url().should('include', '/consents'); + + // Get the current session data + cy.getCookie('sid').then((originalSidCookie) => { + expect(originalSidCookie).to.exist; + + // Refresh our user session + cy.visit( + `/signin/refresh?returnUrl=${encodeURIComponent( + `https://${Cypress.env('BASE_URI')}/consents`, + )}`, + ); + cy.url().should('include', '/consents'); + + // Get the refreshed session data + cy.getCookie('sid').then((newSidCookie) => { + expect(newSidCookie).to.exist; + expect(newSidCookie?.value).to.equal(originalSidCookie?.value); + if (newSidCookie?.expiry && originalSidCookie?.expiry) { + expect(newSidCookie?.expiry).to.be.greaterThan( + originalSidCookie?.expiry, + ); + } + }); + }); + }, + ); + }); + it('sends a client with the Okta cookie and an invalid Okta session to the redirectUrl', () => { + // Create a validated test user + cy.createTestUser({ isUserEmailValidated: true }).then( + ({ emailAddress, finalPassword }) => { + // Sign our new user in + cy.visit( + `/signin?returnUrl=${encodeURIComponent( + `https://${Cypress.env('BASE_URI')}/consents`, + )}`, + ); + cy.get('input[name=email]').type(emailAddress); + cy.get('input[name=password]').type(finalPassword); + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.url().should('include', '/consents'); + + // Get the current session data + cy.getCookie('sid').then((sidCookie) => { + // Close the user's current session in Okta + cy.closeCurrentOktaSession(sidCookie?.value).then(() => { + // Refresh our user session + cy.visit( + `/signin/refresh?returnUrl=${encodeURIComponent( + `https://${Cypress.env('BASE_URI')}/consents`, + )}`, + ); + cy.url().should('include', '/consents'); + }); + }); + }, + ); + }); + it('sends a client without the Okta cookie to the redirectUrl', () => { + // Create a validated test user + cy.createTestUser({ isUserEmailValidated: true }).then( + ({ emailAddress, finalPassword }) => { + // Sign our new user in + cy.visit( + `/signin?returnUrl=${encodeURIComponent( + `https://${Cypress.env('BASE_URI')}/consents`, + )}`, + ); + cy.get('input[name=email]').type(emailAddress); + cy.get('input[name=password]').type(finalPassword); + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.url().should('include', '/consents'); + + // Delete the Okta sid cookie + // strange behaviour form Cypress 12 + // where we need to delete cookie from both domains + // to get the test to pass + // cypress 12 seems to have issues with hostOnly cookies not being removed or persisting after clear + // https://github.com/cypress-io/cypress/issues/25174 + cy.clearCookie('sid', { + domain: Cypress.env('BASE_URI'), + }); + cy.clearCookie('sid', { + domain: `.${Cypress.env('BASE_URI')}`, + }); + + // Visit the refresh endpoint + cy.visit( + `/signin/refresh?returnUrl=${encodeURIComponent( + `https://${Cypress.env('BASE_URI')}/consents`, + )}`, + ); + cy.url().should('include', '/consents'); + + cy.getCookie('sid').should('not.exist'); + }, + ); + }); + it('sends a client with neither Okta nor Identity cookies to /signin', () => { + // Create a validated test user + cy.createTestUser({ isUserEmailValidated: true }).then( + ({ emailAddress, finalPassword }) => { + // Sign our new user in + cy.visit( + `/signin?returnUrl=${encodeURIComponent( + `https://${Cypress.env('BASE_URI')}/consents`, + )}`, + ); + cy.get('input[name=email]').type(emailAddress); + cy.get('input[name=password]').type(finalPassword); + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.url().should('include', '/consents'); + + // Delete all cookies (Okta and IDAPI) + cy.clearCookies(); + + // Visit the refresh endpoint + cy.visit( + `/signin/refresh?returnUrl=${encodeURIComponent( + `https://${Cypress.env('BASE_URI')}/consents`, + )}`, + ); + cy.url().should('include', '/signin'); + + cy.getCookie('sid').should('not.exist'); + cy.getCookie('sc_gu_u').should('not.exist'); + cy.getCookie('sc_gu_la').should('not.exist'); + }, + ); + }); + it('leaves the last access cookie unchanged when refreshing a valid Okta session', () => { + // Create a validated test user + cy.createTestUser({ isUserEmailValidated: true }).then( + ({ emailAddress, finalPassword }) => { + // Sign our new user in + cy.visit( + `/signin?returnUrl=${encodeURIComponent( + `https://${Cypress.env('BASE_URI')}/consents`, + )}`, + ); + cy.get('input[name=email]').type(emailAddress); + cy.get('input[name=password]').type(finalPassword); + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.url().should('include', '/consents'); + + // Get the current session data + cy.getCookie('SC_GU_LA').then((originalLastAccessCookie) => { + cy.getCookie('SC_GU_U').then((originalSecureIdapiCookie) => { + expect(originalLastAccessCookie).to.exist; + expect(originalSecureIdapiCookie).to.exist; + + // Refresh our user session + cy.visit( + `/signin/refresh?returnUrl=${encodeURIComponent( + `https://${Cypress.env('BASE_URI')}/consents`, + )}`, + ); + cy.url().should('include', '/consents'); + + // Expect the last access cookie to be unchanged + cy.getCookie('SC_GU_LA').then((lastAccessCookie) => { + expect(lastAccessCookie).to.exist; + expect(lastAccessCookie?.value).to.equal( + originalLastAccessCookie?.value, + ); + expect(lastAccessCookie?.expiry).to.equal( + originalLastAccessCookie?.expiry, + ); + }); + + // Expect other Idapi cookies to have changed + cy.getCookie('SC_GU_U').then((secureIdapiCookie) => { + expect(secureIdapiCookie).to.exist; + expect(secureIdapiCookie?.value).not.to.equal( + originalSecureIdapiCookie?.value, + ); + if ( + secureIdapiCookie?.expiry && + originalSecureIdapiCookie?.expiry + ) { + expect(secureIdapiCookie?.expiry).to.be.greaterThan( + originalSecureIdapiCookie?.expiry, + ); + } + }); + }); + }); + }, + ); + }); + }); + + context('Okta unvalidated email flow', () => { + it('Sends a user with an unvalidated email a reset password email on sign in', () => { + cy + .createTestUser({ + isUserEmailValidated: false, + }) + ?.then(({ emailAddress, finalPassword }) => { + const timeRequestWasMade = new Date(); + cy.visit('/signin'); + cy.get('input[name=email]').type(emailAddress); + cy.get('input[name=password]').type(finalPassword); + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.url().should('include', '/signin/email-sent'); + cy.contains( + 'For security reasons we need you to change your password.', + ); + cy.contains(emailAddress); + cy.contains('Resend email'); + cy.contains('Change email address'); + + // Ensure the user's authentication cookies are not set + cy.getCookie('sid').then((sidCookie) => { + expect(sidCookie).to.not.exist; + + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /reset-password\/([^"]*)/, + ).then(({ links, body, token }) => { + expect(body).to.have.string( + 'Because your security is extremely important to us, we have changed our password policy.', + ); + expect(body).to.have.string('Reset password'); + expect(links.length).to.eq(2); + const resetPasswordLink = links.find( + (s) => s.text?.includes('Reset password'), + ); + expect(resetPasswordLink?.href ?? '').to.have.string( + 'reset-password', + ); + cy.visit(`/reset-password/${token}`); + cy.contains(emailAddress); + cy.contains('Reset password'); + }); + }); + }); + }); + }); + + context('Okta session exists on /signin', () => { + beforeEach(() => { + // Intercept the external redirect page. + // We just want to check that the redirect happens, not that the page loads. + cy.intercept('GET', 'https://m.code.dev-theguardian.com/', (req) => { + req.reply(200); + }); + }); + + it('shows the signed in as page', () => { + // Create a validated test user + cy.createTestUser({ isUserEmailValidated: true }).then( + ({ emailAddress, finalPassword }) => { + // Sign our new user in + cy.visit( + `/signin?returnUrl=${encodeURIComponent( + `https://${Cypress.env('BASE_URI')}/consents`, + )}`, + ); + cy.get('input[name=email]').type(emailAddress); + cy.get('input[name=password]').type(finalPassword); + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.url().should('include', '/consents'); + + // Get the current session data + cy.getCookie('sid').then((originalSidCookie) => { + expect(originalSidCookie).to.exist; + + // Visit sign in again + cy.visit( + `/signin?returnUrl=${encodeURIComponent( + `https://${Cypress.env('BASE_URI')}/consents`, + )}`, + ); + cy.url().should('include', '/signin'); + + cy.contains('Sign in to the Guardian'); + cy.contains('You are signed in with'); + cy.contains(emailAddress); + cy.contains('Continue') + .should('have.attr', 'href') + .and( + 'include', + `https://${Cypress.env( + 'BASE_URI', + )}/signin/refresh?returnUrl=https%3A%2F%2Fprofile.thegulocal.com%2Fconsents`, + ); + cy.contains('a', 'Sign in') + .should('have.attr', 'href') + .and('include', '/signout?returnUrl='); + cy.contains('Sign in with a different email'); + }); + }, + ); + }); + }); }); diff --git a/cypress/integration/ete-okta/sign_out.5.cy.ts b/cypress/integration/ete-okta/sign_out.5.cy.ts index ec23d6dbac..601884ec9a 100644 --- a/cypress/integration/ete-okta/sign_out.5.cy.ts +++ b/cypress/integration/ete-okta/sign_out.5.cy.ts @@ -1,57 +1,57 @@ describe('Sign out flow', () => { - context('Signs a user out', () => { - it('Removes Okta cookies and dotcom cookies when signing out', () => { - cy - .createTestUser({ - isUserEmailValidated: true, - }) - ?.then(({ emailAddress, finalPassword }) => { - // Disable redirect to /signin/success by default - cy.setCookie( - 'GU_ran_experiments', - new URLSearchParams({ - OptInPromptPostSignIn: Date.now().toString(), - }).toString(), - ); + context('Signs a user out', () => { + it('Removes Okta cookies and dotcom cookies when signing out', () => { + cy + .createTestUser({ + isUserEmailValidated: true, + }) + ?.then(({ emailAddress, finalPassword }) => { + // Disable redirect to /signin/success by default + cy.setCookie( + 'GU_ran_experiments', + new URLSearchParams({ + OptInPromptPostSignIn: Date.now().toString(), + }).toString(), + ); - // load the consents page as its on the same domain - const postSignInReturnUrl = `https://${Cypress.env( - 'BASE_URI', - )}/consents/data`; - const visitUrl = `/signin?returnUrl=${encodeURIComponent( - postSignInReturnUrl, - )}`; - cy.visit(visitUrl); - cy.get('input[name=email]').type(emailAddress); - cy.get('input[name=password]').type(finalPassword); - cy.get('[data-cy="main-form-submit-button"]').click(); - // check sign in has worked first - cy.url().should('include', `/consents/data`); - // check session cookie is set - cy.getCookie('sid').should('exist'); - // check idapi cookies are set - cy.getCookie('SC_GU_U').should('exist'); - cy.getCookie('SC_GU_LA').should('exist'); - cy.getCookie('GU_U').should('exist'); + // load the consents page as its on the same domain + const postSignInReturnUrl = `https://${Cypress.env( + 'BASE_URI', + )}/consents/data`; + const visitUrl = `/signin?returnUrl=${encodeURIComponent( + postSignInReturnUrl, + )}`; + cy.visit(visitUrl); + cy.get('input[name=email]').type(emailAddress); + cy.get('input[name=password]').type(finalPassword); + cy.get('[data-cy="main-form-submit-button"]').click(); + // check sign in has worked first + cy.url().should('include', `/consents/data`); + // check session cookie is set + cy.getCookie('sid').should('exist'); + // check idapi cookies are set + cy.getCookie('SC_GU_U').should('exist'); + cy.getCookie('SC_GU_LA').should('exist'); + cy.getCookie('GU_U').should('exist'); - // attempt to sign out and redirect to sign in to make sure the cookies are removed - // and the signed in as page isn't visible - const postSignOutReturnUrl = `https://${Cypress.env( - 'BASE_URI', - )}/signin`; - cy.visit( - `/signout?returnUrl=${encodeURIComponent(postSignOutReturnUrl)}`, - ); - // cypress 12 seems to have issues with hostOnly cookies not being removed or persisting after clear - // https://github.com/cypress-io/cypress/issues/25174 - // so for now I've changed this check to make sure the "Signed in as" page isn't visible - // cy.getCookie('sid').should('not.exist'); - cy.contains('You are signed in with').should('not.exist'); - // check cookies are removed - cy.getCookie('SC_GU_U').should('not.exist'); - cy.getCookie('SC_GU_LA').should('not.exist'); - cy.getCookie('GU_U').should('not.exist'); - }); - }); - }); + // attempt to sign out and redirect to sign in to make sure the cookies are removed + // and the signed in as page isn't visible + const postSignOutReturnUrl = `https://${Cypress.env( + 'BASE_URI', + )}/signin`; + cy.visit( + `/signout?returnUrl=${encodeURIComponent(postSignOutReturnUrl)}`, + ); + // cypress 12 seems to have issues with hostOnly cookies not being removed or persisting after clear + // https://github.com/cypress-io/cypress/issues/25174 + // so for now I've changed this check to make sure the "Signed in as" page isn't visible + // cy.getCookie('sid').should('not.exist'); + cy.contains('You are signed in with').should('not.exist'); + // check cookies are removed + cy.getCookie('SC_GU_U').should('not.exist'); + cy.getCookie('SC_GU_LA').should('not.exist'); + cy.getCookie('GU_U').should('not.exist'); + }); + }); + }); }); diff --git a/cypress/integration/ete/change_email/change_email.2.cy.ts b/cypress/integration/ete/change_email/change_email.2.cy.ts index eaf6e51e61..0f297d4a64 100644 --- a/cypress/integration/ete/change_email/change_email.2.cy.ts +++ b/cypress/integration/ete/change_email/change_email.2.cy.ts @@ -1,38 +1,38 @@ import { randomMailosaurEmail } from '../../../support/commands/testUser'; describe('Change email', () => { - it('change email flow successful', () => { - cy.createTestUser({ - isUserEmailValidated: true, - }).then(({ cookies }) => { - // SC_GU_U is required for the cy.updateTestUser - const scGuU = cookies.find((cookie) => cookie.key === 'SC_GU_U'); - if (!scGuU) throw new Error('SC_GU_U cookie not found'); - cy.setCookie('SC_GU_U', scGuU?.value); + it('change email flow successful', () => { + cy.createTestUser({ + isUserEmailValidated: true, + }).then(({ cookies }) => { + // SC_GU_U is required for the cy.updateTestUser + const scGuU = cookies.find((cookie) => cookie.key === 'SC_GU_U'); + if (!scGuU) throw new Error('SC_GU_U cookie not found'); + cy.setCookie('SC_GU_U', scGuU?.value); - const timeRequestWasMade = new Date(); + const timeRequestWasMade = new Date(); - const newEmail = randomMailosaurEmail(); + const newEmail = randomMailosaurEmail(); - cy.updateTestUser({ - primaryEmailAddress: newEmail, - }); + cy.updateTestUser({ + primaryEmailAddress: newEmail, + }); - cy.checkForEmailAndGetDetails( - newEmail, - timeRequestWasMade, - /change-email\/([^"]*)/, - ).then(({ token }) => { - cy.visit(`/change-email/${token}`); - cy.contains('Success! Your email address has been updated.'); - }); - }); - }); + cy.checkForEmailAndGetDetails( + newEmail, + timeRequestWasMade, + /change-email\/([^"]*)/, + ).then(({ token }) => { + cy.visit(`/change-email/${token}`); + cy.contains('Success! Your email address has been updated.'); + }); + }); + }); - it('change email flow unsuccessful', () => { - cy.visit(`/change-email/bad_token`); - cy.contains( - 'The email change link you followed has expired or was invalid.', - ); - }); + it('change email flow unsuccessful', () => { + cy.visit(`/change-email/bad_token`); + cy.contains( + 'The email change link you followed has expired or was invalid.', + ); + }); }); diff --git a/cypress/integration/ete/consent_token/consent_token.2.cy.ts b/cypress/integration/ete/consent_token/consent_token.2.cy.ts index fe9903a90c..54eece99a3 100644 --- a/cypress/integration/ete/consent_token/consent_token.2.cy.ts +++ b/cypress/integration/ete/consent_token/consent_token.2.cy.ts @@ -1,46 +1,46 @@ describe('Consent token flow', () => { - it('redirects to the success route when supplied a valid token by a logged in user', () => { - cy.createTestUser({ - isUserEmailValidated: true, - }).then(({ emailAddress, cookies }) => { - // SC_GU_U is required for the cy.getTestUserDetails - const scGuU = cookies.find((cookie) => cookie.key === 'SC_GU_U'); - if (!scGuU) throw new Error('SC_GU_U cookie not found'); - cy.setCookie('SC_GU_U', scGuU?.value); - // SC_GU_LA is required for the cy.getTestUserDetails - const scGuLa = cookies.find((cookie) => cookie.key === 'SC_GU_LA'); - if (!scGuLa) throw new Error('SC_GU_LA cookie not found'); - cy.setCookie('SC_GU_LA', scGuLa?.value); - cy.sendConsentEmail({ - emailAddress, - consents: ['jobs'], - }).then(() => { - const timeRequestWasMade = new Date(); - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /consent-token\/([^"]*)/, - ).then(({ token }) => { - cy.visit(`/consent-token/${token}/accept`, { - failOnStatusCode: false, - }); - // /consents/thank-you isn't hosted by Gateway so all we need to check - // is that the cy.visit() redirected successfully, so intercept the request - // and return a 200 - cy.intercept('GET', '/consents/thank-you', (req) => { - req.reply(200); - }); - }); - }); - }); - }); + it('redirects to the success route when supplied a valid token by a logged in user', () => { + cy.createTestUser({ + isUserEmailValidated: true, + }).then(({ emailAddress, cookies }) => { + // SC_GU_U is required for the cy.getTestUserDetails + const scGuU = cookies.find((cookie) => cookie.key === 'SC_GU_U'); + if (!scGuU) throw new Error('SC_GU_U cookie not found'); + cy.setCookie('SC_GU_U', scGuU?.value); + // SC_GU_LA is required for the cy.getTestUserDetails + const scGuLa = cookies.find((cookie) => cookie.key === 'SC_GU_LA'); + if (!scGuLa) throw new Error('SC_GU_LA cookie not found'); + cy.setCookie('SC_GU_LA', scGuLa?.value); + cy.sendConsentEmail({ + emailAddress, + consents: ['jobs'], + }).then(() => { + const timeRequestWasMade = new Date(); + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /consent-token\/([^"]*)/, + ).then(({ token }) => { + cy.visit(`/consent-token/${token}/accept`, { + failOnStatusCode: false, + }); + // /consents/thank-you isn't hosted by Gateway so all we need to check + // is that the cy.visit() redirected successfully, so intercept the request + // and return a 200 + cy.intercept('GET', '/consents/thank-you', (req) => { + req.reply(200); + }); + }); + }); + }); + }); - it('shows the email sent page when supplied an invalid token', () => { - const invalidToken = 'invalid-consent-token'; - cy.visit(`/consent-token/${invalidToken}/accept`); - cy.contains('This link has expired.'); - cy.get('button[type=submit]').click(); - cy.url().should('include', '/consent-token/email-sent'); - cy.contains('Check your email inbox'); - }); + it('shows the email sent page when supplied an invalid token', () => { + const invalidToken = 'invalid-consent-token'; + cy.visit(`/consent-token/${invalidToken}/accept`); + cy.contains('This link has expired.'); + cy.get('button[type=submit]').click(); + cy.url().should('include', '/consent-token/email-sent'); + cy.contains('Check your email inbox'); + }); }); diff --git a/cypress/integration/ete/registration/register.2.cy.ts b/cypress/integration/ete/registration/register.2.cy.ts index 28c8e10e18..40bd8b767b 100644 --- a/cypress/integration/ete/registration/register.2.cy.ts +++ b/cypress/integration/ete/registration/register.2.cy.ts @@ -1,311 +1,311 @@ import { randomMailosaurEmail } from '../../../support/commands/testUser'; describe('Registration flow', () => { - context('Terms and Conditions links', () => { - it('links to the Google terms of service page', () => { - const googleTermsUrl = 'https://policies.google.com/terms'; - // Intercept the external redirect page. - // We just want to check that the redirect happens, not that the page loads. - cy.intercept('GET', googleTermsUrl, (req) => { - req.reply(200); - }); - cy.visit('/signin?useIdapi=true'); - cy.contains('terms of service').click(); - cy.url().should('eq', googleTermsUrl); - }); - - it('links to the Google privacy policy page', () => { - const googlePrivacyPolicyUrl = 'https://policies.google.com/privacy'; - // Intercept the external redirect page. - // We just want to check that the redirect happens, not that the page loads. - cy.intercept('GET', googlePrivacyPolicyUrl, (req) => { - req.reply(200); - }); - cy.visit('/signin?useIdapi=true'); - cy.contains('This service is protected by reCAPTCHA and the Google') - .contains('privacy policy') - .click(); - cy.url().should('eq', googlePrivacyPolicyUrl); - }); - - it('links to the Guardian terms and conditions page', () => { - const guardianTermsOfServiceUrl = - 'https://www.theguardian.com/help/terms-of-service'; - // Intercept the external redirect page. - // We just want to check that the redirect happens, not that the page loads. - cy.intercept('GET', guardianTermsOfServiceUrl, (req) => { - req.reply(200); - }); - cy.visit('/signin?useIdapi=true'); - cy.contains('terms & conditions').click(); - cy.url().should('eq', guardianTermsOfServiceUrl); - }); - - it('links to the Guardian privacy policy page', () => { - const guardianPrivacyPolicyUrl = - 'https://www.theguardian.com/help/privacy-policy'; - // Intercept the external redirect page. - // We just want to check that the redirect happens, not that the page loads. - cy.intercept('GET', guardianPrivacyPolicyUrl, (req) => { - req.reply(200); - }); - cy.visit('/signin?useIdapi=true'); - cy.contains('For information about how we use your data') - .contains('privacy policy') - .click(); - cy.url().should('eq', guardianPrivacyPolicyUrl); - }); - - it('links to the Guardian jobs terms and conditions page when jobs clientId set', () => { - const guardianJobsTermsOfServiceUrl = - 'https://jobs.theguardian.com/terms-and-conditions/'; - // Intercept the external redirect page. - // We just want to check that the redirect happens, not that the page loads. - cy.intercept('GET', guardianJobsTermsOfServiceUrl, (req) => { - req.reply(200); - }); - cy.visit('/signin?clientId=jobs&useIdapi=true'); - cy.contains('Guardian Jobs terms & conditions').click(); - cy.url().should('eq', guardianJobsTermsOfServiceUrl); - }); - - it('links to the Guardian jobs privacy policy page when jobs clientId set', () => { - const guardianJobsPrivacyPolicyUrl = - 'https://jobs.theguardian.com/privacy-policy/'; - // Intercept the external redirect page. - // We just want to check that the redirect happens, not that the page loads. - cy.intercept('GET', guardianJobsPrivacyPolicyUrl, (req) => { - req.reply(200); - }); - cy.visit('/signin?clientId=jobs&useIdapi=true'); - cy.contains('For information about how we use your data') - .contains('Guardian Jobs privacy policy') - .click(); - cy.url().should('eq', guardianJobsPrivacyPolicyUrl); - }); - }); - it('persists the clientId when navigating away', () => { - cy.visit('/register?clientId=jobs&useIdapi=true'); - cy.contains('Sign in').click(); - cy.url().should('contain', 'clientId=jobs'); - }); - it('does not proceed when no email provided', () => { - cy.visit('/register'); - cy.get('[data-cy="main-form-submit-button"]').click(); - // check that form isn't submitted - cy.url().should('not.contain', 'returnUrl'); - cy.contains('Please enter your email.'); - }); - - it('does not proceed when invalid email provided', () => { - cy.visit('/register?useIdapi=true'); - const invalidEmail = 'invalid.email.com'; - cy.get('input[name=email]').type(invalidEmail); - cy.get('[data-cy="main-form-submit-button"]').click(); - // check that form isn't submitted - cy.url().should('not.contain', 'returnUrl'); - cy.contains('Please enter a valid email format.'); - }); - - it('successfully registers using an email with no existing account', () => { - const encodedReturnUrl = - 'https%3A%2F%2Fm.code.dev-theguardian.com%2Ftravel%2F2019%2Fdec%2F18%2Ffood-culture-tour-bethlehem-palestine-east-jerusalem-photo-essay'; - const encodedRef = 'https%3A%2F%2Fm.theguardian.com'; - const refViewId = 'testRefViewId'; - const clientId = 'jobs'; - const unregisteredEmail = randomMailosaurEmail(); - - cy.visit( - `/register?returnUrl=${encodedReturnUrl}&ref=${encodedRef}&refViewId=${refViewId}&clientId=${clientId}&useIdapi=true`, - ); - const timeRequestWasMade = new Date(); - cy.get('input[name=email]').type(unregisteredEmail); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains('Check your email inbox'); - cy.contains(unregisteredEmail); - cy.contains('Resend email'); - cy.contains('Change email address'); - - cy.checkForEmailAndGetDetails( - unregisteredEmail, - timeRequestWasMade, - /welcome\/([^"]*)/, - ).then(({ body, token }) => { - expect(body).to.have.string('Complete registration'); - expect(body).to.have.string('returnUrl=' + encodedReturnUrl); - expect(body).to.have.string('ref=' + encodedRef); - expect(body).to.have.string('refViewId=' + refViewId); - expect(body).to.have.string('clientId=' + clientId); - cy.visit(`/welcome/${token}`); - cy.contains('Save and continue'); - }); - }); - - it('sends user an account exists email for user with existing account with password trying to register, clicks sign in, taken to /signin', () => { - cy - .createTestUser({ - isUserEmailValidated: true, - }) - ?.then(({ emailAddress }) => { - cy.visit('/register?useIdapi=true'); - const timeRequestWasMade = new Date(); - - cy.get('input[name=email]').type(emailAddress); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains('Check your email inbox'); - cy.contains(emailAddress); - cy.contains('Resend email'); - cy.contains('Change email address'); - - cy.checkForEmailAndGetDetails(emailAddress, timeRequestWasMade).then( - ({ links, body }) => { - expect(body).to.have.string('This account already exists'); - expect(body).to.have.string('Sign in'); - expect(body).to.have.string('Reset password'); - - expect(links.length).to.eq(3); - - const signInLink = links.find((link) => link.text === 'Sign in'); - - expect(signInLink).not.to.be.undefined; - expect(signInLink?.href ?? '').to.include('/signin'); - - const signInUrl = new URL(signInLink?.href ?? ''); - - cy.visit(signInUrl.pathname); - cy.url().should('include', '/signin'); - }, - ); - }); - }); - - it('sends user an account exists email for user with existing account with password trying to register, clicks reset password on email', () => { - cy - .createTestUser({ - isUserEmailValidated: true, - }) - ?.then(({ emailAddress }) => { - cy.visit('/register?useIdapi=true'); - const timeRequestWasMade = new Date(); - - cy.get('input[name=email]').type(emailAddress); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains('Check your email inbox'); - cy.contains(emailAddress); - cy.contains('Resend email'); - cy.contains('Change email address'); - - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /\/reset-password\/([^"]*)/, - ).then(({ links, body, token }) => { - expect(body).to.have.string('This account already exists'); - expect(body).to.have.string('Sign in'); - expect(body).to.have.string('Reset password'); - expect(body).to.have.string('This link is valid for 60 minutes.'); - - expect(links.length).to.eq(3); - - const passwordResetLink = links.find( - (link) => link.text === 'Reset password', - ); - - expect(passwordResetLink).not.to.be.undefined; - - cy.visit(`/reset-password/${token}`); - cy.contains('Reset password'); - }); - }); - }); - - it('sends user an account exists without password email for user with existing account without password trying to register, clicks create password on email', () => { - cy - .createTestUser({ - isUserEmailValidated: false, - isGuestUser: true, - }) - ?.then(({ emailAddress }) => { - cy.visit('/register?useIdapi=true'); - const timeRequestWasMade = new Date(); - - cy.get('input[name=email]').type(emailAddress); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains('Check your email inbox'); - cy.contains(emailAddress); - cy.contains('Resend email'); - cy.contains('Change email address'); - - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /\/set-password\/([^"]*)/, - ).then(({ links, body, token }) => { - expect(body).to.have.string('This account already exists'); - expect(body).to.have.string( - 'To continue to your account please click below to create a password.', - ); - expect(body).to.have.string('This link is valid for 60 minutes.'); - expect(body).to.have.string('Create password'); - - expect(links.length).to.eq(2); - - const createPasswordLink = links.find( - (link) => link.text === 'Create password', - ); - - expect(createPasswordLink).not.to.be.undefined; - - cy.visit(`/set-password/${token}`); - cy.contains('Create password'); - }); - }); - }); - - it('shows reCAPTCHA errors when the user tries to register offline and allows registration when back online', () => { - const unregisteredEmail = randomMailosaurEmail(); - - cy.visit('/register?useIdapi=true'); - - // Simulate going offline by failing to reCAPTCHA POST request. - cy.intercept({ - method: 'POST', - url: 'https://www.google.com/recaptcha/api2/**', - times: 1, - }); - - cy.get('input[name=email').type(unregisteredEmail); - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.contains('Google reCAPTCHA verification failed. Please try again.'); - - // On second click, an expanded error is shown. - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains('Google reCAPTCHA verification failed.'); - cy.contains('Report this error').should( - 'have.attr', - 'href', - 'https://manage.theguardian.com/help-centre/contact-us', - ); - cy.contains('If the problem persists please try the following:'); - - const timeRequestWasMade = new Date(); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains( - 'Google reCAPTCHA verification failed. Please try again.', - ).should('not.exist'); - - cy.contains('Check your email inbox'); - cy.contains(unregisteredEmail); - cy.checkForEmailAndGetDetails(unregisteredEmail, timeRequestWasMade).then( - ({ body }) => { - expect(body).to.have.string('Complete registration'); - }, - ); - }); + context('Terms and Conditions links', () => { + it('links to the Google terms of service page', () => { + const googleTermsUrl = 'https://policies.google.com/terms'; + // Intercept the external redirect page. + // We just want to check that the redirect happens, not that the page loads. + cy.intercept('GET', googleTermsUrl, (req) => { + req.reply(200); + }); + cy.visit('/signin?useIdapi=true'); + cy.contains('terms of service').click(); + cy.url().should('eq', googleTermsUrl); + }); + + it('links to the Google privacy policy page', () => { + const googlePrivacyPolicyUrl = 'https://policies.google.com/privacy'; + // Intercept the external redirect page. + // We just want to check that the redirect happens, not that the page loads. + cy.intercept('GET', googlePrivacyPolicyUrl, (req) => { + req.reply(200); + }); + cy.visit('/signin?useIdapi=true'); + cy.contains('This service is protected by reCAPTCHA and the Google') + .contains('privacy policy') + .click(); + cy.url().should('eq', googlePrivacyPolicyUrl); + }); + + it('links to the Guardian terms and conditions page', () => { + const guardianTermsOfServiceUrl = + 'https://www.theguardian.com/help/terms-of-service'; + // Intercept the external redirect page. + // We just want to check that the redirect happens, not that the page loads. + cy.intercept('GET', guardianTermsOfServiceUrl, (req) => { + req.reply(200); + }); + cy.visit('/signin?useIdapi=true'); + cy.contains('terms & conditions').click(); + cy.url().should('eq', guardianTermsOfServiceUrl); + }); + + it('links to the Guardian privacy policy page', () => { + const guardianPrivacyPolicyUrl = + 'https://www.theguardian.com/help/privacy-policy'; + // Intercept the external redirect page. + // We just want to check that the redirect happens, not that the page loads. + cy.intercept('GET', guardianPrivacyPolicyUrl, (req) => { + req.reply(200); + }); + cy.visit('/signin?useIdapi=true'); + cy.contains('For information about how we use your data') + .contains('privacy policy') + .click(); + cy.url().should('eq', guardianPrivacyPolicyUrl); + }); + + it('links to the Guardian jobs terms and conditions page when jobs clientId set', () => { + const guardianJobsTermsOfServiceUrl = + 'https://jobs.theguardian.com/terms-and-conditions/'; + // Intercept the external redirect page. + // We just want to check that the redirect happens, not that the page loads. + cy.intercept('GET', guardianJobsTermsOfServiceUrl, (req) => { + req.reply(200); + }); + cy.visit('/signin?clientId=jobs&useIdapi=true'); + cy.contains('Guardian Jobs terms & conditions').click(); + cy.url().should('eq', guardianJobsTermsOfServiceUrl); + }); + + it('links to the Guardian jobs privacy policy page when jobs clientId set', () => { + const guardianJobsPrivacyPolicyUrl = + 'https://jobs.theguardian.com/privacy-policy/'; + // Intercept the external redirect page. + // We just want to check that the redirect happens, not that the page loads. + cy.intercept('GET', guardianJobsPrivacyPolicyUrl, (req) => { + req.reply(200); + }); + cy.visit('/signin?clientId=jobs&useIdapi=true'); + cy.contains('For information about how we use your data') + .contains('Guardian Jobs privacy policy') + .click(); + cy.url().should('eq', guardianJobsPrivacyPolicyUrl); + }); + }); + it('persists the clientId when navigating away', () => { + cy.visit('/register?clientId=jobs&useIdapi=true'); + cy.contains('Sign in').click(); + cy.url().should('contain', 'clientId=jobs'); + }); + it('does not proceed when no email provided', () => { + cy.visit('/register'); + cy.get('[data-cy="main-form-submit-button"]').click(); + // check that form isn't submitted + cy.url().should('not.contain', 'returnUrl'); + cy.contains('Please enter your email.'); + }); + + it('does not proceed when invalid email provided', () => { + cy.visit('/register?useIdapi=true'); + const invalidEmail = 'invalid.email.com'; + cy.get('input[name=email]').type(invalidEmail); + cy.get('[data-cy="main-form-submit-button"]').click(); + // check that form isn't submitted + cy.url().should('not.contain', 'returnUrl'); + cy.contains('Please enter a valid email format.'); + }); + + it('successfully registers using an email with no existing account', () => { + const encodedReturnUrl = + 'https%3A%2F%2Fm.code.dev-theguardian.com%2Ftravel%2F2019%2Fdec%2F18%2Ffood-culture-tour-bethlehem-palestine-east-jerusalem-photo-essay'; + const encodedRef = 'https%3A%2F%2Fm.theguardian.com'; + const refViewId = 'testRefViewId'; + const clientId = 'jobs'; + const unregisteredEmail = randomMailosaurEmail(); + + cy.visit( + `/register?returnUrl=${encodedReturnUrl}&ref=${encodedRef}&refViewId=${refViewId}&clientId=${clientId}&useIdapi=true`, + ); + const timeRequestWasMade = new Date(); + cy.get('input[name=email]').type(unregisteredEmail); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains('Check your email inbox'); + cy.contains(unregisteredEmail); + cy.contains('Resend email'); + cy.contains('Change email address'); + + cy.checkForEmailAndGetDetails( + unregisteredEmail, + timeRequestWasMade, + /welcome\/([^"]*)/, + ).then(({ body, token }) => { + expect(body).to.have.string('Complete registration'); + expect(body).to.have.string('returnUrl=' + encodedReturnUrl); + expect(body).to.have.string('ref=' + encodedRef); + expect(body).to.have.string('refViewId=' + refViewId); + expect(body).to.have.string('clientId=' + clientId); + cy.visit(`/welcome/${token}`); + cy.contains('Save and continue'); + }); + }); + + it('sends user an account exists email for user with existing account with password trying to register, clicks sign in, taken to /signin', () => { + cy + .createTestUser({ + isUserEmailValidated: true, + }) + ?.then(({ emailAddress }) => { + cy.visit('/register?useIdapi=true'); + const timeRequestWasMade = new Date(); + + cy.get('input[name=email]').type(emailAddress); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains('Check your email inbox'); + cy.contains(emailAddress); + cy.contains('Resend email'); + cy.contains('Change email address'); + + cy.checkForEmailAndGetDetails(emailAddress, timeRequestWasMade).then( + ({ links, body }) => { + expect(body).to.have.string('This account already exists'); + expect(body).to.have.string('Sign in'); + expect(body).to.have.string('Reset password'); + + expect(links.length).to.eq(3); + + const signInLink = links.find((link) => link.text === 'Sign in'); + + expect(signInLink).not.to.be.undefined; + expect(signInLink?.href ?? '').to.include('/signin'); + + const signInUrl = new URL(signInLink?.href ?? ''); + + cy.visit(signInUrl.pathname); + cy.url().should('include', '/signin'); + }, + ); + }); + }); + + it('sends user an account exists email for user with existing account with password trying to register, clicks reset password on email', () => { + cy + .createTestUser({ + isUserEmailValidated: true, + }) + ?.then(({ emailAddress }) => { + cy.visit('/register?useIdapi=true'); + const timeRequestWasMade = new Date(); + + cy.get('input[name=email]').type(emailAddress); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains('Check your email inbox'); + cy.contains(emailAddress); + cy.contains('Resend email'); + cy.contains('Change email address'); + + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /\/reset-password\/([^"]*)/, + ).then(({ links, body, token }) => { + expect(body).to.have.string('This account already exists'); + expect(body).to.have.string('Sign in'); + expect(body).to.have.string('Reset password'); + expect(body).to.have.string('This link is valid for 60 minutes.'); + + expect(links.length).to.eq(3); + + const passwordResetLink = links.find( + (link) => link.text === 'Reset password', + ); + + expect(passwordResetLink).not.to.be.undefined; + + cy.visit(`/reset-password/${token}`); + cy.contains('Reset password'); + }); + }); + }); + + it('sends user an account exists without password email for user with existing account without password trying to register, clicks create password on email', () => { + cy + .createTestUser({ + isUserEmailValidated: false, + isGuestUser: true, + }) + ?.then(({ emailAddress }) => { + cy.visit('/register?useIdapi=true'); + const timeRequestWasMade = new Date(); + + cy.get('input[name=email]').type(emailAddress); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains('Check your email inbox'); + cy.contains(emailAddress); + cy.contains('Resend email'); + cy.contains('Change email address'); + + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /\/set-password\/([^"]*)/, + ).then(({ links, body, token }) => { + expect(body).to.have.string('This account already exists'); + expect(body).to.have.string( + 'To continue to your account please click below to create a password.', + ); + expect(body).to.have.string('This link is valid for 60 minutes.'); + expect(body).to.have.string('Create password'); + + expect(links.length).to.eq(2); + + const createPasswordLink = links.find( + (link) => link.text === 'Create password', + ); + + expect(createPasswordLink).not.to.be.undefined; + + cy.visit(`/set-password/${token}`); + cy.contains('Create password'); + }); + }); + }); + + it('shows reCAPTCHA errors when the user tries to register offline and allows registration when back online', () => { + const unregisteredEmail = randomMailosaurEmail(); + + cy.visit('/register?useIdapi=true'); + + // Simulate going offline by failing to reCAPTCHA POST request. + cy.intercept({ + method: 'POST', + url: 'https://www.google.com/recaptcha/api2/**', + times: 1, + }); + + cy.get('input[name=email').type(unregisteredEmail); + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.contains('Google reCAPTCHA verification failed. Please try again.'); + + // On second click, an expanded error is shown. + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains('Google reCAPTCHA verification failed.'); + cy.contains('Report this error').should( + 'have.attr', + 'href', + 'https://manage.theguardian.com/help-centre/contact-us', + ); + cy.contains('If the problem persists please try the following:'); + + const timeRequestWasMade = new Date(); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains( + 'Google reCAPTCHA verification failed. Please try again.', + ).should('not.exist'); + + cy.contains('Check your email inbox'); + cy.contains(unregisteredEmail); + cy.checkForEmailAndGetDetails(unregisteredEmail, timeRequestWasMade).then( + ({ body }) => { + expect(body).to.have.string('Complete registration'); + }, + ); + }); }); diff --git a/cypress/integration/ete/registration/register_email_sent.3.cy.ts b/cypress/integration/ete/registration/register_email_sent.3.cy.ts index 7eb639f00a..b53dafcd16 100644 --- a/cypress/integration/ete/registration/register_email_sent.3.cy.ts +++ b/cypress/integration/ete/registration/register_email_sent.3.cy.ts @@ -2,202 +2,202 @@ import { injectAndCheckAxe } from '../../../support/cypress-axe'; import { randomMailosaurEmail } from '../../../support/commands/testUser'; describe('Registration email sent page', () => { - context('A11y checks', () => { - it('has no detectable a11y violations on the registration email sent page', () => { - cy.visit('/register/email-sent?useIdapi=true'); - injectAndCheckAxe(); - }); - }); - - it('should resend "Complete Registration" email when a new user registers which is same as initial email sent', () => { - const unregisteredEmail = randomMailosaurEmail(); - - const clientId = 'jobs'; - cy.visit(`/register?clientId=${clientId}&useIdapi=true`); - cy.get('input[name=email]').type(unregisteredEmail); - const timeRequestWasMadeInitialEmail = new Date(); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains('Check your email inbox'); - cy.contains(unregisteredEmail); - cy.contains('Resend email'); - cy.contains('Change email address'); - - // test and delete initial email - cy.checkForEmailAndGetDetails( - unregisteredEmail, - timeRequestWasMadeInitialEmail, - ).then(({ body }) => { - expect(body).to.have.string('Complete registration'); - }); - - const timeRequestWasMade = new Date(); - cy.contains('Resend email').click(); - cy.contains('Check your email inbox'); - cy.contains(unregisteredEmail); - - // test and delete resent email - cy.checkForEmailAndGetDetails(unregisteredEmail, timeRequestWasMade).then( - ({ body }) => { - expect(body).to.have.string('Complete registration'); - expect(body).to.have.string('clientId=' + clientId); - }, - ); - }); - - it('should resend account exists without password email when an existing user without password registers which is same as initial email sent', () => { - cy - .createTestUser({ - isUserEmailValidated: false, - isGuestUser: true, - }) - ?.then(({ emailAddress }) => { - cy.visit('/register?useIdapi=true'); - cy.get('input[name=email]').type(emailAddress); - const timeRequestWasMadeInitialEmail = new Date(); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains('Check your email inbox'); - cy.contains(emailAddress); - cy.contains('Resend email'); - cy.contains('Change email address'); - - // test and delete initial email - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMadeInitialEmail, - ).then(({ body }) => { - expect(body).to.have.string('This account already exists'); - expect(body).to.have.string( - 'To continue to your account please click below to create a password.', - ); - expect(body).to.have.string('This link is valid for 60 minutes.'); - expect(body).to.have.string('Create password'); - }); - - const timeRequestWasMade = new Date(); - cy.contains('Resend email').click(); - cy.contains('Check your email inbox'); - cy.contains(emailAddress); - - // test and delete resent email - cy.checkForEmailAndGetDetails(emailAddress, timeRequestWasMade).then( - ({ body }) => { - expect(body).to.have.string('This account already exists'); - expect(body).to.have.string( - 'To continue to your account please click below to create a password.', - ); - expect(body).to.have.string('This link is valid for 60 minutes.'); - expect(body).to.have.string('Create password'); - }, - ); - }); - }); - - it('should resend "Account Exists" email when an existing user with password registers which is same as initial email sent', () => { - cy - .createTestUser({ - isUserEmailValidated: false, - }) - ?.then(({ emailAddress }) => { - cy.visit('/register?useIdapi=true'); - cy.get('input[name=email]').type(emailAddress); - const timeRequestWasMadeInitialEmail = new Date(); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains('Check your email inbox'); - cy.contains(emailAddress); - cy.contains('Resend email'); - cy.contains('Change email address'); - - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMadeInitialEmail, - ).then(({ body }) => { - expect(body).to.have.string( - 'You are already registered with the Guardian.', - ); - }); - - const timeRequestWasMade = new Date(); - cy.contains('Resend email').click(); - cy.contains('Check your email inbox'); - cy.contains(emailAddress); - - // test and delete resent email - cy.checkForEmailAndGetDetails(emailAddress, timeRequestWasMade).then( - ({ body }) => { - expect(body).to.have.string( - 'You are already registered with the Guardian.', - ); - }, - ); - }); - }); - - it('should navigate back to the correct page when change email is clicked', () => { - cy.visit('/register/email-sent?useIdapi=true'); - cy.contains('Change email address').click(); - cy.contains('Register'); - cy.title().should('eq', 'Register | The Guardian'); - }); - - it('should render properly if the encrypted email cookie is not set', () => { - cy.visit('/register/email-sent?useIdapi=true'); - cy.contains('Change email address'); - cy.contains('Check your email inbox'); - }); - - it('shows reCAPTCHA errors when the request fails', () => { - cy - .createTestUser({ - isUserEmailValidated: false, - }) - ?.then(({ emailAddress }) => { - cy.visit('/register?useIdapi=true'); - cy.get('input[name=email]').type(emailAddress); - - const timeRequestWasMadeInitialEmail = new Date(); - - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains('Check your email inbox'); - cy.contains(emailAddress); - cy.contains('Resend email'); - cy.contains('Change email address'); - - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMadeInitialEmail, - ); - - // Simulate going offline by failing the reCAPTCHA POST request. - cy.intercept({ - method: 'POST', - url: 'https://www.google.com/recaptcha/api2/**', - times: 1, - }); - cy.contains('Resend email').click(); - cy.contains('Google reCAPTCHA verification failed. Please try again.'); - - // On second click, an expanded error is shown. - cy.contains('Resend email').click(); - - cy.contains('Google reCAPTCHA verification failed.'); - cy.contains('If the problem persists please try the following:'); - cy.contains('userhelp@'); - - const timeRequestWasMade = new Date(); - cy.contains('Resend email').click(); - - cy.contains( - 'Google reCAPTCHA verification failed. Please try again.', - ).should('not.exist'); - - cy.contains('Check your email inbox'); - cy.contains(emailAddress); - - cy.checkForEmailAndGetDetails(emailAddress, timeRequestWasMade); - }); - }); + context('A11y checks', () => { + it('has no detectable a11y violations on the registration email sent page', () => { + cy.visit('/register/email-sent?useIdapi=true'); + injectAndCheckAxe(); + }); + }); + + it('should resend "Complete Registration" email when a new user registers which is same as initial email sent', () => { + const unregisteredEmail = randomMailosaurEmail(); + + const clientId = 'jobs'; + cy.visit(`/register?clientId=${clientId}&useIdapi=true`); + cy.get('input[name=email]').type(unregisteredEmail); + const timeRequestWasMadeInitialEmail = new Date(); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains('Check your email inbox'); + cy.contains(unregisteredEmail); + cy.contains('Resend email'); + cy.contains('Change email address'); + + // test and delete initial email + cy.checkForEmailAndGetDetails( + unregisteredEmail, + timeRequestWasMadeInitialEmail, + ).then(({ body }) => { + expect(body).to.have.string('Complete registration'); + }); + + const timeRequestWasMade = new Date(); + cy.contains('Resend email').click(); + cy.contains('Check your email inbox'); + cy.contains(unregisteredEmail); + + // test and delete resent email + cy.checkForEmailAndGetDetails(unregisteredEmail, timeRequestWasMade).then( + ({ body }) => { + expect(body).to.have.string('Complete registration'); + expect(body).to.have.string('clientId=' + clientId); + }, + ); + }); + + it('should resend account exists without password email when an existing user without password registers which is same as initial email sent', () => { + cy + .createTestUser({ + isUserEmailValidated: false, + isGuestUser: true, + }) + ?.then(({ emailAddress }) => { + cy.visit('/register?useIdapi=true'); + cy.get('input[name=email]').type(emailAddress); + const timeRequestWasMadeInitialEmail = new Date(); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains('Check your email inbox'); + cy.contains(emailAddress); + cy.contains('Resend email'); + cy.contains('Change email address'); + + // test and delete initial email + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMadeInitialEmail, + ).then(({ body }) => { + expect(body).to.have.string('This account already exists'); + expect(body).to.have.string( + 'To continue to your account please click below to create a password.', + ); + expect(body).to.have.string('This link is valid for 60 minutes.'); + expect(body).to.have.string('Create password'); + }); + + const timeRequestWasMade = new Date(); + cy.contains('Resend email').click(); + cy.contains('Check your email inbox'); + cy.contains(emailAddress); + + // test and delete resent email + cy.checkForEmailAndGetDetails(emailAddress, timeRequestWasMade).then( + ({ body }) => { + expect(body).to.have.string('This account already exists'); + expect(body).to.have.string( + 'To continue to your account please click below to create a password.', + ); + expect(body).to.have.string('This link is valid for 60 minutes.'); + expect(body).to.have.string('Create password'); + }, + ); + }); + }); + + it('should resend "Account Exists" email when an existing user with password registers which is same as initial email sent', () => { + cy + .createTestUser({ + isUserEmailValidated: false, + }) + ?.then(({ emailAddress }) => { + cy.visit('/register?useIdapi=true'); + cy.get('input[name=email]').type(emailAddress); + const timeRequestWasMadeInitialEmail = new Date(); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains('Check your email inbox'); + cy.contains(emailAddress); + cy.contains('Resend email'); + cy.contains('Change email address'); + + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMadeInitialEmail, + ).then(({ body }) => { + expect(body).to.have.string( + 'You are already registered with the Guardian.', + ); + }); + + const timeRequestWasMade = new Date(); + cy.contains('Resend email').click(); + cy.contains('Check your email inbox'); + cy.contains(emailAddress); + + // test and delete resent email + cy.checkForEmailAndGetDetails(emailAddress, timeRequestWasMade).then( + ({ body }) => { + expect(body).to.have.string( + 'You are already registered with the Guardian.', + ); + }, + ); + }); + }); + + it('should navigate back to the correct page when change email is clicked', () => { + cy.visit('/register/email-sent?useIdapi=true'); + cy.contains('Change email address').click(); + cy.contains('Register'); + cy.title().should('eq', 'Register | The Guardian'); + }); + + it('should render properly if the encrypted email cookie is not set', () => { + cy.visit('/register/email-sent?useIdapi=true'); + cy.contains('Change email address'); + cy.contains('Check your email inbox'); + }); + + it('shows reCAPTCHA errors when the request fails', () => { + cy + .createTestUser({ + isUserEmailValidated: false, + }) + ?.then(({ emailAddress }) => { + cy.visit('/register?useIdapi=true'); + cy.get('input[name=email]').type(emailAddress); + + const timeRequestWasMadeInitialEmail = new Date(); + + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains('Check your email inbox'); + cy.contains(emailAddress); + cy.contains('Resend email'); + cy.contains('Change email address'); + + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMadeInitialEmail, + ); + + // Simulate going offline by failing the reCAPTCHA POST request. + cy.intercept({ + method: 'POST', + url: 'https://www.google.com/recaptcha/api2/**', + times: 1, + }); + cy.contains('Resend email').click(); + cy.contains('Google reCAPTCHA verification failed. Please try again.'); + + // On second click, an expanded error is shown. + cy.contains('Resend email').click(); + + cy.contains('Google reCAPTCHA verification failed.'); + cy.contains('If the problem persists please try the following:'); + cy.contains('userhelp@'); + + const timeRequestWasMade = new Date(); + cy.contains('Resend email').click(); + + cy.contains( + 'Google reCAPTCHA verification failed. Please try again.', + ).should('not.exist'); + + cy.contains('Check your email inbox'); + cy.contains(emailAddress); + + cy.checkForEmailAndGetDetails(emailAddress, timeRequestWasMade); + }); + }); }); diff --git a/cypress/integration/ete/reset_password/reset_password.4.cy.ts b/cypress/integration/ete/reset_password/reset_password.4.cy.ts index bc33ca7bb2..fc9045c65b 100644 --- a/cypress/integration/ete/reset_password/reset_password.4.cy.ts +++ b/cypress/integration/ete/reset_password/reset_password.4.cy.ts @@ -1,210 +1,210 @@ import { randomPassword } from '../../../support/commands/testUser'; describe('Password reset flow', () => { - context('Account exists', () => { - it("changes the reader's password", () => { - cy.intercept({ - method: 'GET', - url: 'https://api.pwnedpasswords.com/range/*', - }).as('breachCheck'); - - cy - .createTestUser({ - isUserEmailValidated: true, - }) - ?.then(({ emailAddress }) => { - cy.visit('/signin?useIdapi=true'); - const timeRequestWasMade = new Date(); - cy.contains('Reset password').click(); - - // Simulate going offline by failing the reCAPTCHA POST request. - cy.intercept({ - method: 'POST', - url: 'https://www.google.com/recaptcha/api2/**', - times: 1, - }); - - cy.contains('Forgot password'); - cy.get('input[name=email]').type(emailAddress); - - // Check that both reCAPTCHA errors are shown. - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.contains( - 'Google reCAPTCHA verification failed. Please try again.', - ); - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.contains('Google reCAPTCHA verification failed.'); - cy.contains('If the problem persists please try the following:'); - cy.contains('userhelp@'); - - // Continue checking the password reset flow after reCAPTCHA assertions above. - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.contains('Check your email inbox'); - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /reset-password\/([^"]*)/, - ).then(({ token }) => { - cy.visit(`/reset-password/${token}`); - cy.get('input[name=password]').type(randomPassword()); - cy.wait('@breachCheck'); - cy.get('[data-cy="main-form-submit-button"]') - .click() - .should('be.disabled'); - cy.contains('Password updated'); - cy.contains(emailAddress.toLowerCase()); - }); - }); - }); - }); - - context('Account without password exists', () => { - it('changes the users password', () => { - cy.intercept({ - method: 'GET', - url: 'https://api.pwnedpasswords.com/range/*', - }).as('breachCheck'); - - cy - .createTestUser({ - isUserEmailValidated: true, - }) - ?.then(({ emailAddress }) => { - cy.visit('/signin?useIdapi=true'); - const timeRequestWasMade = new Date(); - cy.contains('Reset password').click(); - - // Simulate going offline by failing the reCAPTCHA POST request. - cy.intercept({ - method: 'POST', - url: 'https://www.google.com/recaptcha/api2/**', - times: 1, - }); - - cy.contains('Forgot password'); - cy.get('input[name=email]').type(emailAddress); - - // Check that both reCAPTCHA errors are shown. - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.contains( - 'Google reCAPTCHA verification failed. Please try again.', - ); - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.contains('Google reCAPTCHA verification failed.'); - cy.contains('If the problem persists please try the following:'); - cy.contains('userhelp@'); - - // Continue checking the password reset flow after reCAPTCHA assertions above. - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.contains('Check your email inbox'); - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMade, - /reset-password\/([^"]*)/, - ).then(({ token }) => { - cy.visit(`/reset-password/${token}`); - cy.get('input[name=password]').type(randomPassword()); - cy.wait('@breachCheck'); - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.contains('Password updated'); - cy.contains(emailAddress.toLowerCase()); - }); - }); - }); - }); - - context('No Account', () => { - it('shows the email sent page with link to register when attempting to reset password', () => { - cy.visit('/reset-password?useIdapi=true'); - cy.contains('Forgot password'); - cy.get('input[name=email]').type('invalid@doesnotexist.com'); - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.contains('Check your email inbox'); - cy.contains('Register for free'); - }); - }); + context('Account exists', () => { + it("changes the reader's password", () => { + cy.intercept({ + method: 'GET', + url: 'https://api.pwnedpasswords.com/range/*', + }).as('breachCheck'); + + cy + .createTestUser({ + isUserEmailValidated: true, + }) + ?.then(({ emailAddress }) => { + cy.visit('/signin?useIdapi=true'); + const timeRequestWasMade = new Date(); + cy.contains('Reset password').click(); + + // Simulate going offline by failing the reCAPTCHA POST request. + cy.intercept({ + method: 'POST', + url: 'https://www.google.com/recaptcha/api2/**', + times: 1, + }); + + cy.contains('Forgot password'); + cy.get('input[name=email]').type(emailAddress); + + // Check that both reCAPTCHA errors are shown. + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.contains( + 'Google reCAPTCHA verification failed. Please try again.', + ); + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.contains('Google reCAPTCHA verification failed.'); + cy.contains('If the problem persists please try the following:'); + cy.contains('userhelp@'); + + // Continue checking the password reset flow after reCAPTCHA assertions above. + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.contains('Check your email inbox'); + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /reset-password\/([^"]*)/, + ).then(({ token }) => { + cy.visit(`/reset-password/${token}`); + cy.get('input[name=password]').type(randomPassword()); + cy.wait('@breachCheck'); + cy.get('[data-cy="main-form-submit-button"]') + .click() + .should('be.disabled'); + cy.contains('Password updated'); + cy.contains(emailAddress.toLowerCase()); + }); + }); + }); + }); + + context('Account without password exists', () => { + it('changes the users password', () => { + cy.intercept({ + method: 'GET', + url: 'https://api.pwnedpasswords.com/range/*', + }).as('breachCheck'); + + cy + .createTestUser({ + isUserEmailValidated: true, + }) + ?.then(({ emailAddress }) => { + cy.visit('/signin?useIdapi=true'); + const timeRequestWasMade = new Date(); + cy.contains('Reset password').click(); + + // Simulate going offline by failing the reCAPTCHA POST request. + cy.intercept({ + method: 'POST', + url: 'https://www.google.com/recaptcha/api2/**', + times: 1, + }); + + cy.contains('Forgot password'); + cy.get('input[name=email]').type(emailAddress); + + // Check that both reCAPTCHA errors are shown. + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.contains( + 'Google reCAPTCHA verification failed. Please try again.', + ); + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.contains('Google reCAPTCHA verification failed.'); + cy.contains('If the problem persists please try the following:'); + cy.contains('userhelp@'); + + // Continue checking the password reset flow after reCAPTCHA assertions above. + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.contains('Check your email inbox'); + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + /reset-password\/([^"]*)/, + ).then(({ token }) => { + cy.visit(`/reset-password/${token}`); + cy.get('input[name=password]').type(randomPassword()); + cy.wait('@breachCheck'); + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.contains('Password updated'); + cy.contains(emailAddress.toLowerCase()); + }); + }); + }); + }); + + context('No Account', () => { + it('shows the email sent page with link to register when attempting to reset password', () => { + cy.visit('/reset-password?useIdapi=true'); + cy.contains('Forgot password'); + cy.get('input[name=email]').type('invalid@doesnotexist.com'); + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.contains('Check your email inbox'); + cy.contains('Register for free'); + }); + }); }); describe('Password set flow', () => { - context('Account without password exists', () => { - it('from the set passsword link expired page, successfully send and reset the create password email, and get taken to the set password page from the email', () => { - cy - .createTestUser({ - isUserEmailValidated: false, - isGuestUser: true, - }) - ?.then(({ emailAddress }) => { - cy.visit('/set-password/expired?useIdapi=true'); - - // Simulate going offline by failing the reCAPTCHA POST request. - cy.intercept({ - method: 'POST', - url: 'https://www.google.com/recaptcha/api2/**', - times: 1, - }); - - // link expired - const timeRequestWasMadeLinkExpired = new Date(); - cy.get('input[name=email]').type(emailAddress); - - // Check that both reCAPTCHA errors are shown. - cy.contains('Send me a link').click(); - cy.contains( - 'Google reCAPTCHA verification failed. Please try again.', - ); - - cy.contains('Send me a link').click(); - cy.contains('Google reCAPTCHA verification failed.'); - cy.contains('If the problem persists please try the following:'); - cy.contains('userhelp@'); - - // Continue checking the password reset flow after reCAPTCHA assertions above. - cy.contains('Send me a link').click(); - cy.contains('Check your email inbox'); - cy.contains(emailAddress); - cy.contains('Resend email'); - cy.contains('Change email address'); - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMadeLinkExpired, - ).then(({ body }) => { - expect(body).to.have.string('Welcome back'); - expect(body).to.have.string( - 'Please click below to create a password for your account.', - ); - expect(body).to.have.string('This link is valid for 60 minutes.'); - expect(body).to.have.string('Create password'); - }); - - // resend email - const timeRequestWasMadeResend = new Date(); - cy.contains('Resend email').click(); - cy.contains('Check your email inbox'); - cy.contains(emailAddress); - cy.checkForEmailAndGetDetails( - emailAddress, - timeRequestWasMadeResend, - /\/set-password\/([^"]*)/, - ).then(({ body, links, token }) => { - expect(body).to.have.string('Welcome back'); - expect(body).to.have.string( - 'Please click below to create a password for your account.', - ); - expect(body).to.have.string('This link is valid for 60 minutes.'); - expect(body).to.have.string('Create password'); - - expect(links.length).to.eq(2); - - const createPasswordLink = links.find( - (link) => link.text === 'Create password', - ); - - expect(createPasswordLink).not.to.be.undefined; - - cy.visit(`/set-password/${token}`); - cy.contains('Create password'); - }); - }); - }); - }); + context('Account without password exists', () => { + it('from the set passsword link expired page, successfully send and reset the create password email, and get taken to the set password page from the email', () => { + cy + .createTestUser({ + isUserEmailValidated: false, + isGuestUser: true, + }) + ?.then(({ emailAddress }) => { + cy.visit('/set-password/expired?useIdapi=true'); + + // Simulate going offline by failing the reCAPTCHA POST request. + cy.intercept({ + method: 'POST', + url: 'https://www.google.com/recaptcha/api2/**', + times: 1, + }); + + // link expired + const timeRequestWasMadeLinkExpired = new Date(); + cy.get('input[name=email]').type(emailAddress); + + // Check that both reCAPTCHA errors are shown. + cy.contains('Send me a link').click(); + cy.contains( + 'Google reCAPTCHA verification failed. Please try again.', + ); + + cy.contains('Send me a link').click(); + cy.contains('Google reCAPTCHA verification failed.'); + cy.contains('If the problem persists please try the following:'); + cy.contains('userhelp@'); + + // Continue checking the password reset flow after reCAPTCHA assertions above. + cy.contains('Send me a link').click(); + cy.contains('Check your email inbox'); + cy.contains(emailAddress); + cy.contains('Resend email'); + cy.contains('Change email address'); + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMadeLinkExpired, + ).then(({ body }) => { + expect(body).to.have.string('Welcome back'); + expect(body).to.have.string( + 'Please click below to create a password for your account.', + ); + expect(body).to.have.string('This link is valid for 60 minutes.'); + expect(body).to.have.string('Create password'); + }); + + // resend email + const timeRequestWasMadeResend = new Date(); + cy.contains('Resend email').click(); + cy.contains('Check your email inbox'); + cy.contains(emailAddress); + cy.checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMadeResend, + /\/set-password\/([^"]*)/, + ).then(({ body, links, token }) => { + expect(body).to.have.string('Welcome back'); + expect(body).to.have.string( + 'Please click below to create a password for your account.', + ); + expect(body).to.have.string('This link is valid for 60 minutes.'); + expect(body).to.have.string('Create password'); + + expect(links.length).to.eq(2); + + const createPasswordLink = links.find( + (link) => link.text === 'Create password', + ); + + expect(createPasswordLink).not.to.be.undefined; + + cy.visit(`/set-password/${token}`); + cy.contains('Create password'); + }); + }); + }); + }); }); diff --git a/cypress/integration/ete/sign_in/sign_in.1.cy.ts b/cypress/integration/ete/sign_in/sign_in.1.cy.ts index 6f049451fb..102e8176a9 100644 --- a/cypress/integration/ete/sign_in/sign_in.1.cy.ts +++ b/cypress/integration/ete/sign_in/sign_in.1.cy.ts @@ -1,55 +1,55 @@ import * as SignIn from '../../shared/sign_in.shared'; describe('Sign in flow, Okta disabled', () => { - beforeEach(() => { - SignIn.beforeEach(); - }); + beforeEach(() => { + SignIn.beforeEach(); + }); - context('Terms and Conditions links', () => { - it(...SignIn.linksToTheGoogleTermsOfServicePage(true)); - it(...SignIn.linksToTheGooglePrivacyPolicyPage(true)); - it(...SignIn.linksToTheGuardianTermsAndConditionsPage(true)); - it(...SignIn.linksToTheGuardianPrivacyPolicyPage(true)); - it( - ...SignIn.linksToTheGuardianJobsTermsAndConditionsPageWhenJobsClientIdSet( - true, - ), - ); - it( - ...SignIn.linksToTheGuardianJobsPrivacyPolicyPageWhenJobsClientIdSet( - true, - ), - ); - }); - it(...SignIn.persistsTheClientIdWhenNavigatingAway(true)); - it(...SignIn.appliesFormValidationToEmailAndPasswordInputFields(true)); - it(...SignIn.showsAMessageWhenCredentialsAreInvalid(true)); - it(...SignIn.correctlySignsInAnExistingUser(true)); - it(...SignIn.navigatesToResetPassword(true)); - it(...SignIn.navigatesToRegistration(true)); - it(...SignIn.respectsTheReturnUrlQueryParam(true)); - it(...SignIn.redirectsCorrectlyForSocialSignIn(true)); - it(...SignIn.removesEncryptedEmailParameterFromQueryString(true)); - it( - ...SignIn.removesEncryptedEmailParameterAndPreservesAllOtherValidParameters( - true, - ), - ); - it( - ...SignIn.showsAnErrorMessageAndInformationParagraphWhenAccountLinkingRequiredErrorParameterIsPresent( - true, - ), - ); - it( - ...SignIn.doesNotDisplaySocialButtonsWhenAccountLinkingRequiredErrorParameterIsPresent( - true, - ), - ); - it( - ...SignIn.showsRecaptchaErrorsWhenTheUserTriesToSignInOfflineAndAllowsSignInWhenBackOnline( - true, - ), - ); - it(...SignIn.redirectsToOptInPrompt(true)); - it(...SignIn.hitsAccessTokenRateLimitAndRecoversTokenAfterTimeout(true)); + context('Terms and Conditions links', () => { + it(...SignIn.linksToTheGoogleTermsOfServicePage(true)); + it(...SignIn.linksToTheGooglePrivacyPolicyPage(true)); + it(...SignIn.linksToTheGuardianTermsAndConditionsPage(true)); + it(...SignIn.linksToTheGuardianPrivacyPolicyPage(true)); + it( + ...SignIn.linksToTheGuardianJobsTermsAndConditionsPageWhenJobsClientIdSet( + true, + ), + ); + it( + ...SignIn.linksToTheGuardianJobsPrivacyPolicyPageWhenJobsClientIdSet( + true, + ), + ); + }); + it(...SignIn.persistsTheClientIdWhenNavigatingAway(true)); + it(...SignIn.appliesFormValidationToEmailAndPasswordInputFields(true)); + it(...SignIn.showsAMessageWhenCredentialsAreInvalid(true)); + it(...SignIn.correctlySignsInAnExistingUser(true)); + it(...SignIn.navigatesToResetPassword(true)); + it(...SignIn.navigatesToRegistration(true)); + it(...SignIn.respectsTheReturnUrlQueryParam(true)); + it(...SignIn.redirectsCorrectlyForSocialSignIn(true)); + it(...SignIn.removesEncryptedEmailParameterFromQueryString(true)); + it( + ...SignIn.removesEncryptedEmailParameterAndPreservesAllOtherValidParameters( + true, + ), + ); + it( + ...SignIn.showsAnErrorMessageAndInformationParagraphWhenAccountLinkingRequiredErrorParameterIsPresent( + true, + ), + ); + it( + ...SignIn.doesNotDisplaySocialButtonsWhenAccountLinkingRequiredErrorParameterIsPresent( + true, + ), + ); + it( + ...SignIn.showsRecaptchaErrorsWhenTheUserTriesToSignInOfflineAndAllowsSignInWhenBackOnline( + true, + ), + ); + it(...SignIn.redirectsToOptInPrompt(true)); + it(...SignIn.hitsAccessTokenRateLimitAndRecoversTokenAfterTimeout(true)); }); diff --git a/cypress/integration/ete/sign_out/sign_out.3.cy.ts b/cypress/integration/ete/sign_out/sign_out.3.cy.ts index 890b1dfd98..f6cee6e401 100644 --- a/cypress/integration/ete/sign_out/sign_out.3.cy.ts +++ b/cypress/integration/ete/sign_out/sign_out.3.cy.ts @@ -1,71 +1,71 @@ describe('Sign out flow', () => { - const DotComCookies = [ - 'gu_user_features_expiry', - 'gu_paying_member', - 'gu_recurring_contributor', - 'gu_digital_subscriber', - ]; + const DotComCookies = [ + 'gu_user_features_expiry', + 'gu_paying_member', + 'gu_recurring_contributor', + 'gu_digital_subscriber', + ]; - context('Signs a user out', () => { - it('Removes IDAPI log in cookies and dotcom cookies when signing out', () => { - cy - .createTestUser({ - isUserEmailValidated: true, - }) - ?.then(({ emailAddress, finalPassword }) => { - // Disable redirect to /signin/success by default - cy.setCookie( - 'GU_ran_experiments', - new URLSearchParams({ - OptInPromptPostSignIn: Date.now().toString(), - }).toString(), - ); - // load the consents page as its on the same domain - const postSignInReturnUrl = `https://${Cypress.env( - 'BASE_URI', - )}/consents/data?useIdapi=true`; - const visitUrl = `/signin?returnUrl=${encodeURIComponent( - postSignInReturnUrl, - )}&useIdapi=true`; - cy.visit(visitUrl); - cy.get('input[name=email]').type(emailAddress); - cy.get('input[name=password]').type(finalPassword); + context('Signs a user out', () => { + it('Removes IDAPI log in cookies and dotcom cookies when signing out', () => { + cy + .createTestUser({ + isUserEmailValidated: true, + }) + ?.then(({ emailAddress, finalPassword }) => { + // Disable redirect to /signin/success by default + cy.setCookie( + 'GU_ran_experiments', + new URLSearchParams({ + OptInPromptPostSignIn: Date.now().toString(), + }).toString(), + ); + // load the consents page as its on the same domain + const postSignInReturnUrl = `https://${Cypress.env( + 'BASE_URI', + )}/consents/data?useIdapi=true`; + const visitUrl = `/signin?returnUrl=${encodeURIComponent( + postSignInReturnUrl, + )}&useIdapi=true`; + cy.visit(visitUrl); + cy.get('input[name=email]').type(emailAddress); + cy.get('input[name=password]').type(finalPassword); - DotComCookies.forEach((cookie) => { - cy.setCookie(cookie, `the_${cookie}_cookie`, { - domain: Cypress.env('BASE_URI')?.replace('profile', ''), - }); - }); + DotComCookies.forEach((cookie) => { + cy.setCookie(cookie, `the_${cookie}_cookie`, { + domain: Cypress.env('BASE_URI')?.replace('profile', ''), + }); + }); - cy.get('[data-cy="main-form-submit-button"]').click(); + cy.get('[data-cy="main-form-submit-button"]').click(); - // check sign in has worked first - cy.url().should('include', `/consents/data`); - // check cookies are set - cy.getCookie('SC_GU_U').should('exist'); - cy.getCookie('SC_GU_LA').should('exist'); - cy.getCookie('GU_U').should('exist'); - DotComCookies.forEach((cookie) => { - cy.getCookie(cookie).should('exist'); - }); + // check sign in has worked first + cy.url().should('include', `/consents/data`); + // check cookies are set + cy.getCookie('SC_GU_U').should('exist'); + cy.getCookie('SC_GU_LA').should('exist'); + cy.getCookie('GU_U').should('exist'); + DotComCookies.forEach((cookie) => { + cy.getCookie(cookie).should('exist'); + }); - // attempt to sign out and redirect to reset password as it's on same domain - const postSignOutReturnUrl = `https://${Cypress.env( - 'BASE_URI', - )}/reset-password`; - cy.visit( - `/signout?returnUrl=${encodeURIComponent( - postSignOutReturnUrl, - )}&useIdapi=true`, - ); - // check cookies are removed - cy.getCookie('SC_GU_U').should('not.exist'); - cy.getCookie('SC_GU_LA').should('not.exist'); - cy.getCookie('GU_U').should('not.exist'); - DotComCookies.forEach((cookie) => { - cy.getCookie(cookie).should('not.exist'); - }); - }); - }); - }); + // attempt to sign out and redirect to reset password as it's on same domain + const postSignOutReturnUrl = `https://${Cypress.env( + 'BASE_URI', + )}/reset-password`; + cy.visit( + `/signout?returnUrl=${encodeURIComponent( + postSignOutReturnUrl, + )}&useIdapi=true`, + ); + // check cookies are removed + cy.getCookie('SC_GU_U').should('not.exist'); + cy.getCookie('SC_GU_LA').should('not.exist'); + cy.getCookie('GU_U').should('not.exist'); + DotComCookies.forEach((cookie) => { + cy.getCookie(cookie).should('not.exist'); + }); + }); + }); + }); }); diff --git a/cypress/integration/ete/terms/jobs_terms.4.cy.ts b/cypress/integration/ete/terms/jobs_terms.4.cy.ts index 8f70d0404a..d7bbd272b5 100644 --- a/cypress/integration/ete/terms/jobs_terms.4.cy.ts +++ b/cypress/integration/ete/terms/jobs_terms.4.cy.ts @@ -1,242 +1,242 @@ describe('Jobs terms and conditions flow', () => { - context('Accepts Jobs terms and conditions and sets their name', () => { - it('should redirect users with an invalid SC_GU_U cookie to the signin page', () => { - // load the consents page as its on the same domain - const termsAcceptPageUrl = `https://${Cypress.env( - 'BASE_URI', - )}/agree/GRS?returnUrl=https://profile.thegulocal.com/signin?returnUrl=https%3A%2F%2Fm.code.dev-theguardian.com%2F&useIdapi=true`; - - cy.setCookie('SC_GU_U', 'invalid-cookie'); - - cy.visit(termsAcceptPageUrl); - cy.url().should('include', 'https://profile.thegulocal.com/signin'); - }); - - it('should redirect users with no SC_GU_U cookie to the signin page', () => { - // load the consents page as its on the same domain - const termsAcceptPageUrl = `https://${Cypress.env( - 'BASE_URI', - )}/agree/GRS?useIdapi=true`; - cy.visit(termsAcceptPageUrl); - cy.url().should( - 'include', - 'https://profile.thegulocal.com/signin?returnUrl=https%3A%2F%2Fprofile.thegulocal.com%2Fagree%2FGRS&useIdapi=true', - ); - }); - - it('should show the jobs terms page for users who do not have first/last name set, but are part of GRS', () => { - cy - .createTestUser({ - isUserEmailValidated: true, - }) - ?.then(({ emailAddress, finalPassword }) => { - cy.visit('/signin?useIdapi=true'); - cy.get('input[name=email]').type(emailAddress); - cy.get('input[name=password]').type(finalPassword); - - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.url().should('include', '/signin/success'); - - const termsAcceptPageUrl = `https://${Cypress.env( - 'BASE_URI', - )}/agree/GRS?returnUrl=https://profile.thegulocal.com/healthcheck&useIdapi=true`; - - // Create a test user without a first/last name who is in the GRS group. - cy.updateTestUser({ - privateFields: { - firstName: '', - secondName: '', - }, - }) - .then(cy.addToGRS) - .then(() => { - cy.visit(termsAcceptPageUrl); - cy.contains('Please complete your details for'); - cy.contains(emailAddress); - cy.contains('We will use these details on your job applications'); - - cy.get('input[name=firstName]').should('be.empty'); - cy.get('input[name=secondName]').should('be.empty'); - - cy.get('input[name=firstName]').type('First Name'); - cy.get('input[name=secondName]').type('Second Name'); - - cy.findByText('Save and continue').click(); - - // User should be in the GRS group and have First/Last name set to our custom values. - cy.getTestUserDetails().then(({ user, status }) => { - expect(status).to.eq('ok'); - expect(user.privateFields.firstName).to.eq('First Name'); - expect(user.privateFields.secondName).to.eq('Second Name'); - const joinedGroups = user.userGroups.map( - (group) => group.packageCode, - ); - expect(joinedGroups).to.include('GRS'); - }); - }); - }); - }); - - it('should redirect users who have already accepted the terms away', () => { - cy - .createTestUser({ - isUserEmailValidated: true, - }) - ?.then(({ emailAddress, finalPassword }) => { - // load the consents page as its on the same domain - const termsAcceptPageUrl = `https://${Cypress.env( - 'BASE_URI', - )}/agree/GRS?returnUrl=https://profile.thegulocal.com/healthcheck&useIdapi=true`; - - cy.visit('/signin?useIdapi=true'); - cy.get('input[name=email]').type(emailAddress); - cy.get('input[name=password]').type(finalPassword); - - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.url().should('include', '/signin/success'); - - cy.visit(termsAcceptPageUrl); - - // check sign in has worked first - cy.url().should('include', `/agree/GRS`); - // check cookies are set - cy.getCookie('SC_GU_U').should('exist'); - cy.getCookie('SC_GU_LA').should('exist'); - cy.getCookie('GU_U').should('exist'); - - cy.contains('Welcome to Guardian Jobs'); - - // User should not be in GRS and have the original first/last name. - cy.getTestUserDetails().then(({ user, status }) => { - expect(status).to.eq('ok'); - cy.get('input[name=firstName]').should( - 'contain.value', - user.privateFields.firstName, - ); - cy.get('input[name=secondName]').should( - 'contain.value', - user.privateFields.secondName, - ); - - const joinedGroups = user.userGroups.map( - (group) => group.packageCode, - ); - expect(joinedGroups).not.to.include('GRS'); - }); - - cy.get('input[name=firstName]').clear().type('First Name'); - cy.get('input[name=secondName]').clear().type('Second Name'); - - cy.findByText('Continue').click(); - - // User should be in the GRS group and have First/Last name set to our custom values. - cy.getTestUserDetails().then(({ user, status }) => { - expect(status).to.eq('ok'); - expect(user.privateFields.firstName).to.eq('First Name'); - expect(user.privateFields.secondName).to.eq('Second Name'); - const joinedGroups = user.userGroups.map( - (group) => group.packageCode, - ); - expect(joinedGroups).to.include('GRS'); - }); - - cy.url().should( - 'include', - 'https://profile.thegulocal.com/healthcheck', - ); - - const finalTermsAcceptPageUrl = `https://${Cypress.env( - 'BASE_URI', - )}/agree/GRS?returnUrl=https://profile.thegulocal.com/maintenance&useIdapi=true`; - - cy.visit(finalTermsAcceptPageUrl, { failOnStatusCode: false }); - - // Make sure the returnURL is respected. - cy.url().should( - 'include', - 'https://profile.thegulocal.com/maintenance', - ); - }); - }); - - // Skipping this test for now, because of some flakiness where - // users' first and last name aren't updated by the time we make the request - // to /user/me to check their info. - it('should allow a non-jobs user to enter their first/last name and accept the terms', () => { - cy - .createTestUser({ - isUserEmailValidated: true, - }) - ?.then(({ emailAddress, finalPassword }) => { - // load the consents page as its on the same domain - const termsAcceptPageUrl = `https://${Cypress.env( - 'BASE_URI', - )}/agree/GRS?returnUrl=https://jobs.thegulocal.com/&useIdapi=true`; - - cy.visit('/signin?useIdapi=true'); - cy.get('input[name=email]').type(emailAddress); - cy.get('input[name=password]').type(finalPassword); - - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.url().should('include', '/signin/success'); - - cy.visit(termsAcceptPageUrl); - - // check sign in has worked first - cy.url().should('include', `/agree/GRS`); - // check cookies are set - cy.getCookie('SC_GU_U').should('exist'); - cy.getCookie('SC_GU_LA').should('exist'); - cy.getCookie('GU_U').should('exist'); - - cy.contains('Welcome to Guardian Jobs'); - - // User should not be in GRS and have the original first/last name. - cy.getTestUserDetails().then(({ user, status }) => { - expect(status).to.eq('ok'); - cy.get('input[name=firstName]').should( - 'contain.value', - user.privateFields.firstName, - ); - cy.get('input[name=secondName]').should( - 'contain.value', - user.privateFields.secondName, - ); - - const joinedGroups = user.userGroups.map( - (group) => group.packageCode, - ); - expect(joinedGroups).not.to.include('GRS'); - }); - - cy.get('input[name=firstName]').clear().type('First Name'); - cy.get('input[name=secondName]').clear().type('Second Name'); - - // Intercept the external redirect page. - // We just want to check that the redirect happens, not that the page loads. - cy.intercept('GET', 'https://jobs.thegulocal.com/', (req) => { - req.reply(200); - }); - - cy.findByText('Continue').click(); - - // Make sure the returnURL is respected. - cy.url().should('include', 'https://jobs.thegulocal.com/'); - - // User should be in the GRS group and have First/Last name set. - cy.getTestUserDetails().then(({ user, status }) => { - expect(status).to.eq('ok'); - expect(user.privateFields.firstName).to.eq('First Name'); - expect(user.privateFields.secondName).to.eq('Second Name'); - const joinedGroups = user.userGroups.map( - (group) => group.packageCode, - ); - expect(joinedGroups).to.include('GRS'); - }); - }); - }); - }); + context('Accepts Jobs terms and conditions and sets their name', () => { + it('should redirect users with an invalid SC_GU_U cookie to the signin page', () => { + // load the consents page as its on the same domain + const termsAcceptPageUrl = `https://${Cypress.env( + 'BASE_URI', + )}/agree/GRS?returnUrl=https://profile.thegulocal.com/signin?returnUrl=https%3A%2F%2Fm.code.dev-theguardian.com%2F&useIdapi=true`; + + cy.setCookie('SC_GU_U', 'invalid-cookie'); + + cy.visit(termsAcceptPageUrl); + cy.url().should('include', 'https://profile.thegulocal.com/signin'); + }); + + it('should redirect users with no SC_GU_U cookie to the signin page', () => { + // load the consents page as its on the same domain + const termsAcceptPageUrl = `https://${Cypress.env( + 'BASE_URI', + )}/agree/GRS?useIdapi=true`; + cy.visit(termsAcceptPageUrl); + cy.url().should( + 'include', + 'https://profile.thegulocal.com/signin?returnUrl=https%3A%2F%2Fprofile.thegulocal.com%2Fagree%2FGRS&useIdapi=true', + ); + }); + + it('should show the jobs terms page for users who do not have first/last name set, but are part of GRS', () => { + cy + .createTestUser({ + isUserEmailValidated: true, + }) + ?.then(({ emailAddress, finalPassword }) => { + cy.visit('/signin?useIdapi=true'); + cy.get('input[name=email]').type(emailAddress); + cy.get('input[name=password]').type(finalPassword); + + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.url().should('include', '/signin/success'); + + const termsAcceptPageUrl = `https://${Cypress.env( + 'BASE_URI', + )}/agree/GRS?returnUrl=https://profile.thegulocal.com/healthcheck&useIdapi=true`; + + // Create a test user without a first/last name who is in the GRS group. + cy.updateTestUser({ + privateFields: { + firstName: '', + secondName: '', + }, + }) + .then(cy.addToGRS) + .then(() => { + cy.visit(termsAcceptPageUrl); + cy.contains('Please complete your details for'); + cy.contains(emailAddress); + cy.contains('We will use these details on your job applications'); + + cy.get('input[name=firstName]').should('be.empty'); + cy.get('input[name=secondName]').should('be.empty'); + + cy.get('input[name=firstName]').type('First Name'); + cy.get('input[name=secondName]').type('Second Name'); + + cy.findByText('Save and continue').click(); + + // User should be in the GRS group and have First/Last name set to our custom values. + cy.getTestUserDetails().then(({ user, status }) => { + expect(status).to.eq('ok'); + expect(user.privateFields.firstName).to.eq('First Name'); + expect(user.privateFields.secondName).to.eq('Second Name'); + const joinedGroups = user.userGroups.map( + (group) => group.packageCode, + ); + expect(joinedGroups).to.include('GRS'); + }); + }); + }); + }); + + it('should redirect users who have already accepted the terms away', () => { + cy + .createTestUser({ + isUserEmailValidated: true, + }) + ?.then(({ emailAddress, finalPassword }) => { + // load the consents page as its on the same domain + const termsAcceptPageUrl = `https://${Cypress.env( + 'BASE_URI', + )}/agree/GRS?returnUrl=https://profile.thegulocal.com/healthcheck&useIdapi=true`; + + cy.visit('/signin?useIdapi=true'); + cy.get('input[name=email]').type(emailAddress); + cy.get('input[name=password]').type(finalPassword); + + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.url().should('include', '/signin/success'); + + cy.visit(termsAcceptPageUrl); + + // check sign in has worked first + cy.url().should('include', `/agree/GRS`); + // check cookies are set + cy.getCookie('SC_GU_U').should('exist'); + cy.getCookie('SC_GU_LA').should('exist'); + cy.getCookie('GU_U').should('exist'); + + cy.contains('Welcome to Guardian Jobs'); + + // User should not be in GRS and have the original first/last name. + cy.getTestUserDetails().then(({ user, status }) => { + expect(status).to.eq('ok'); + cy.get('input[name=firstName]').should( + 'contain.value', + user.privateFields.firstName, + ); + cy.get('input[name=secondName]').should( + 'contain.value', + user.privateFields.secondName, + ); + + const joinedGroups = user.userGroups.map( + (group) => group.packageCode, + ); + expect(joinedGroups).not.to.include('GRS'); + }); + + cy.get('input[name=firstName]').clear().type('First Name'); + cy.get('input[name=secondName]').clear().type('Second Name'); + + cy.findByText('Continue').click(); + + // User should be in the GRS group and have First/Last name set to our custom values. + cy.getTestUserDetails().then(({ user, status }) => { + expect(status).to.eq('ok'); + expect(user.privateFields.firstName).to.eq('First Name'); + expect(user.privateFields.secondName).to.eq('Second Name'); + const joinedGroups = user.userGroups.map( + (group) => group.packageCode, + ); + expect(joinedGroups).to.include('GRS'); + }); + + cy.url().should( + 'include', + 'https://profile.thegulocal.com/healthcheck', + ); + + const finalTermsAcceptPageUrl = `https://${Cypress.env( + 'BASE_URI', + )}/agree/GRS?returnUrl=https://profile.thegulocal.com/maintenance&useIdapi=true`; + + cy.visit(finalTermsAcceptPageUrl, { failOnStatusCode: false }); + + // Make sure the returnURL is respected. + cy.url().should( + 'include', + 'https://profile.thegulocal.com/maintenance', + ); + }); + }); + + // Skipping this test for now, because of some flakiness where + // users' first and last name aren't updated by the time we make the request + // to /user/me to check their info. + it('should allow a non-jobs user to enter their first/last name and accept the terms', () => { + cy + .createTestUser({ + isUserEmailValidated: true, + }) + ?.then(({ emailAddress, finalPassword }) => { + // load the consents page as its on the same domain + const termsAcceptPageUrl = `https://${Cypress.env( + 'BASE_URI', + )}/agree/GRS?returnUrl=https://jobs.thegulocal.com/&useIdapi=true`; + + cy.visit('/signin?useIdapi=true'); + cy.get('input[name=email]').type(emailAddress); + cy.get('input[name=password]').type(finalPassword); + + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.url().should('include', '/signin/success'); + + cy.visit(termsAcceptPageUrl); + + // check sign in has worked first + cy.url().should('include', `/agree/GRS`); + // check cookies are set + cy.getCookie('SC_GU_U').should('exist'); + cy.getCookie('SC_GU_LA').should('exist'); + cy.getCookie('GU_U').should('exist'); + + cy.contains('Welcome to Guardian Jobs'); + + // User should not be in GRS and have the original first/last name. + cy.getTestUserDetails().then(({ user, status }) => { + expect(status).to.eq('ok'); + cy.get('input[name=firstName]').should( + 'contain.value', + user.privateFields.firstName, + ); + cy.get('input[name=secondName]').should( + 'contain.value', + user.privateFields.secondName, + ); + + const joinedGroups = user.userGroups.map( + (group) => group.packageCode, + ); + expect(joinedGroups).not.to.include('GRS'); + }); + + cy.get('input[name=firstName]').clear().type('First Name'); + cy.get('input[name=secondName]').clear().type('Second Name'); + + // Intercept the external redirect page. + // We just want to check that the redirect happens, not that the page loads. + cy.intercept('GET', 'https://jobs.thegulocal.com/', (req) => { + req.reply(200); + }); + + cy.findByText('Continue').click(); + + // Make sure the returnURL is respected. + cy.url().should('include', 'https://jobs.thegulocal.com/'); + + // User should be in the GRS group and have First/Last name set. + cy.getTestUserDetails().then(({ user, status }) => { + expect(status).to.eq('ok'); + expect(user.privateFields.firstName).to.eq('First Name'); + expect(user.privateFields.secondName).to.eq('Second Name'); + const joinedGroups = user.userGroups.map( + (group) => group.packageCode, + ); + expect(joinedGroups).to.include('GRS'); + }); + }); + }); + }); }); diff --git a/cypress/integration/ete/unsubscribe/unsubscribe.3.cy.ts b/cypress/integration/ete/unsubscribe/unsubscribe.3.cy.ts index 86243a2906..eaf00732db 100644 --- a/cypress/integration/ete/unsubscribe/unsubscribe.3.cy.ts +++ b/cypress/integration/ete/unsubscribe/unsubscribe.3.cy.ts @@ -1,205 +1,205 @@ import crypto from 'crypto'; describe('Unsubscribe newsletter/marketing email', () => { - const newsletterListId = '4151'; - const newsletterId = 'today-uk'; - const marketingId = 'supporter'; - - const createData = (newsletterId: string, userId: string) => - `${newsletterId}:${userId}:${Math.floor(Date.now() / 1000)}`; - - const createToken = (data: string) => - crypto - .createHmac('sha1', Cypress.env('BRAZE_HMAC_KEY')) - .update(data) - .digest('hex'); - - it('should be able to unsubscribe from a newsletter', () => { - cy.createTestUser({ - isUserEmailValidated: true, - }).then(({ cookies }) => { - // SC_GU_U is required for the cy.getTestUserDetails - const scGuU = cookies.find((cookie) => cookie.key === 'SC_GU_U'); - if (!scGuU) throw new Error('SC_GU_U cookie not found'); - cy.setCookie('SC_GU_U', scGuU?.value); - - cy.getTestUserDetails().then(({ user }) => { - const id = user.id; - const data = createData(newsletterId, id); - cy.subscribeToNewsletter(newsletterListId).then(() => { - cy.visit( - `/unsubscribe/newsletter/${encodeURIComponent(data)}/${createToken( - data, - )}`, - ); - cy.contains('You have been unsubscribed.'); - }); - }); - }); - }); - - it('should be able to unsubscribe from a marketing email', () => { - cy.createTestUser({ - isUserEmailValidated: true, - }).then(({ cookies }) => { - // SC_GU_U is required for the cy.getTestUserDetails - const scGuU = cookies.find((cookie) => cookie.key === 'SC_GU_U'); - if (!scGuU) throw new Error('SC_GU_U cookie not found'); - cy.setCookie('SC_GU_U', scGuU?.value); - - cy.getTestUserDetails().then(({ user }) => { - const id = user.id; - const data = createData(marketingId, id); - cy.subscribeToMarketingConsent(marketingId).then(() => { - cy.visit( - `/unsubscribe/marketing/${encodeURIComponent(data)}/${createToken( - data, - )}`, - ); - cy.contains('You have been unsubscribed.'); - }); - }); - }); - }); - - it('should be able to handle a unsubscribe error if emailType is not newsletter/marketing', () => { - cy.createTestUser({ - isUserEmailValidated: true, - }).then(({ cookies }) => { - // SC_GU_U is required for the cy.getTestUserDetails - const scGuU = cookies.find((cookie) => cookie.key === 'SC_GU_U'); - if (!scGuU) throw new Error('SC_GU_U cookie not found'); - cy.setCookie('SC_GU_U', scGuU?.value); - - cy.getTestUserDetails().then(({ user }) => { - const id = user.id; - const data = createData(marketingId, id); - cy.subscribeToMarketingConsent(marketingId).then(() => { - cy.visit( - `/unsubscribe/fake/${encodeURIComponent(data)}/${createToken( - data, - )}`, - ); - cy.contains('Unable to unsubscribe.'); - }); - }); - }); - }); - - it('should be able to handle a unsubscribe error if token is invalid', () => { - cy.createTestUser({ - isUserEmailValidated: true, - }).then(({ cookies }) => { - // SC_GU_U is required for the cy.getTestUserDetails - const scGuU = cookies.find((cookie) => cookie.key === 'SC_GU_U'); - if (!scGuU) throw new Error('SC_GU_U cookie not found'); - cy.setCookie('SC_GU_U', scGuU?.value); - - cy.getTestUserDetails().then(({ user }) => { - const id = user.id; - const data = createData(newsletterId, id); - cy.subscribeToNewsletter(newsletterListId).then(() => { - cy.visit( - `/unsubscribe/newsletter/${encodeURIComponent(data)}/fake_token`, - ); - cy.contains('Unable to unsubscribe.'); - }); - }); - }); - }); - - it('should be able to handle a unsubscribe error if newsletterId is invalid', () => { - cy.createTestUser({ - isUserEmailValidated: true, - }).then(({ cookies }) => { - // SC_GU_U is required for the cy.getTestUserDetails - const scGuU = cookies.find((cookie) => cookie.key === 'SC_GU_U'); - if (!scGuU) throw new Error('SC_GU_U cookie not found'); - cy.setCookie('SC_GU_U', scGuU?.value); - - cy.getTestUserDetails().then(({ user }) => { - const id = user.id; - const data = createData(`${newsletterId}-fake`, id); - cy.subscribeToNewsletter(newsletterListId).then(() => { - cy.visit( - `/unsubscribe/newsletter/${encodeURIComponent(data)}/${createToken( - data, - )}`, - ); - cy.contains('Unable to unsubscribe.'); - }); - }); - }); - }); - - it('should be able to handle a unsubscribe error if marketingId is invalid', () => { - cy.createTestUser({ - isUserEmailValidated: true, - }).then(({ cookies }) => { - // SC_GU_U is required for the cy.getTestUserDetails - const scGuU = cookies.find((cookie) => cookie.key === 'SC_GU_U'); - if (!scGuU) throw new Error('SC_GU_U cookie not found'); - cy.setCookie('SC_GU_U', scGuU?.value); - - cy.getTestUserDetails().then(({ user }) => { - const id = user.id; - const data = createData(`${marketingId}-fake`, id); - cy.subscribeToMarketingConsent(marketingId).then(() => { - cy.visit( - `/unsubscribe/marketing/${encodeURIComponent(data)}/${createToken( - data, - )}`, - ); - cy.contains('Unable to unsubscribe.'); - }); - }); - }); - }); - - it('should be able to handle a unsubscribe error if userId is invalid', () => { - cy.createTestUser({ - isUserEmailValidated: true, - }).then(({ cookies }) => { - // SC_GU_U is required for the cy.getTestUserDetails - const scGuU = cookies.find((cookie) => cookie.key === 'SC_GU_U'); - if (!scGuU) throw new Error('SC_GU_U cookie not found'); - cy.setCookie('SC_GU_U', scGuU?.value); - - cy.getTestUserDetails().then(({ user }) => { - const id = user.id; - const data = createData(marketingId, `${id}0`); - cy.subscribeToMarketingConsent(marketingId).then(() => { - cy.visit( - `/unsubscribe/marketing/${encodeURIComponent(data)}/${createToken( - data, - )}`, - ); - cy.contains('Unable to unsubscribe.'); - }); - }); - }); - }); - - it('should be able to handle a unsubscribe error if data is invalid', () => { - cy.createTestUser({ - isUserEmailValidated: true, - }).then(({ cookies }) => { - // SC_GU_U is required for the cy.getTestUserDetails - const scGuU = cookies.find((cookie) => cookie.key === 'SC_GU_U'); - if (!scGuU) throw new Error('SC_GU_U cookie not found'); - cy.setCookie('SC_GU_U', scGuU?.value); - - cy.getTestUserDetails().then(() => { - const data = 'data-is-invalid:yes-123'; - cy.subscribeToMarketingConsent(marketingId).then(() => { - cy.visit( - `/unsubscribe/marketing/${encodeURIComponent(data)}/${createToken( - data, - )}`, - ); - cy.contains('Unable to unsubscribe.'); - }); - }); - }); - }); + const newsletterListId = '4151'; + const newsletterId = 'today-uk'; + const marketingId = 'supporter'; + + const createData = (newsletterId: string, userId: string) => + `${newsletterId}:${userId}:${Math.floor(Date.now() / 1000)}`; + + const createToken = (data: string) => + crypto + .createHmac('sha1', Cypress.env('BRAZE_HMAC_KEY')) + .update(data) + .digest('hex'); + + it('should be able to unsubscribe from a newsletter', () => { + cy.createTestUser({ + isUserEmailValidated: true, + }).then(({ cookies }) => { + // SC_GU_U is required for the cy.getTestUserDetails + const scGuU = cookies.find((cookie) => cookie.key === 'SC_GU_U'); + if (!scGuU) throw new Error('SC_GU_U cookie not found'); + cy.setCookie('SC_GU_U', scGuU?.value); + + cy.getTestUserDetails().then(({ user }) => { + const id = user.id; + const data = createData(newsletterId, id); + cy.subscribeToNewsletter(newsletterListId).then(() => { + cy.visit( + `/unsubscribe/newsletter/${encodeURIComponent(data)}/${createToken( + data, + )}`, + ); + cy.contains('You have been unsubscribed.'); + }); + }); + }); + }); + + it('should be able to unsubscribe from a marketing email', () => { + cy.createTestUser({ + isUserEmailValidated: true, + }).then(({ cookies }) => { + // SC_GU_U is required for the cy.getTestUserDetails + const scGuU = cookies.find((cookie) => cookie.key === 'SC_GU_U'); + if (!scGuU) throw new Error('SC_GU_U cookie not found'); + cy.setCookie('SC_GU_U', scGuU?.value); + + cy.getTestUserDetails().then(({ user }) => { + const id = user.id; + const data = createData(marketingId, id); + cy.subscribeToMarketingConsent(marketingId).then(() => { + cy.visit( + `/unsubscribe/marketing/${encodeURIComponent(data)}/${createToken( + data, + )}`, + ); + cy.contains('You have been unsubscribed.'); + }); + }); + }); + }); + + it('should be able to handle a unsubscribe error if emailType is not newsletter/marketing', () => { + cy.createTestUser({ + isUserEmailValidated: true, + }).then(({ cookies }) => { + // SC_GU_U is required for the cy.getTestUserDetails + const scGuU = cookies.find((cookie) => cookie.key === 'SC_GU_U'); + if (!scGuU) throw new Error('SC_GU_U cookie not found'); + cy.setCookie('SC_GU_U', scGuU?.value); + + cy.getTestUserDetails().then(({ user }) => { + const id = user.id; + const data = createData(marketingId, id); + cy.subscribeToMarketingConsent(marketingId).then(() => { + cy.visit( + `/unsubscribe/fake/${encodeURIComponent(data)}/${createToken( + data, + )}`, + ); + cy.contains('Unable to unsubscribe.'); + }); + }); + }); + }); + + it('should be able to handle a unsubscribe error if token is invalid', () => { + cy.createTestUser({ + isUserEmailValidated: true, + }).then(({ cookies }) => { + // SC_GU_U is required for the cy.getTestUserDetails + const scGuU = cookies.find((cookie) => cookie.key === 'SC_GU_U'); + if (!scGuU) throw new Error('SC_GU_U cookie not found'); + cy.setCookie('SC_GU_U', scGuU?.value); + + cy.getTestUserDetails().then(({ user }) => { + const id = user.id; + const data = createData(newsletterId, id); + cy.subscribeToNewsletter(newsletterListId).then(() => { + cy.visit( + `/unsubscribe/newsletter/${encodeURIComponent(data)}/fake_token`, + ); + cy.contains('Unable to unsubscribe.'); + }); + }); + }); + }); + + it('should be able to handle a unsubscribe error if newsletterId is invalid', () => { + cy.createTestUser({ + isUserEmailValidated: true, + }).then(({ cookies }) => { + // SC_GU_U is required for the cy.getTestUserDetails + const scGuU = cookies.find((cookie) => cookie.key === 'SC_GU_U'); + if (!scGuU) throw new Error('SC_GU_U cookie not found'); + cy.setCookie('SC_GU_U', scGuU?.value); + + cy.getTestUserDetails().then(({ user }) => { + const id = user.id; + const data = createData(`${newsletterId}-fake`, id); + cy.subscribeToNewsletter(newsletterListId).then(() => { + cy.visit( + `/unsubscribe/newsletter/${encodeURIComponent(data)}/${createToken( + data, + )}`, + ); + cy.contains('Unable to unsubscribe.'); + }); + }); + }); + }); + + it('should be able to handle a unsubscribe error if marketingId is invalid', () => { + cy.createTestUser({ + isUserEmailValidated: true, + }).then(({ cookies }) => { + // SC_GU_U is required for the cy.getTestUserDetails + const scGuU = cookies.find((cookie) => cookie.key === 'SC_GU_U'); + if (!scGuU) throw new Error('SC_GU_U cookie not found'); + cy.setCookie('SC_GU_U', scGuU?.value); + + cy.getTestUserDetails().then(({ user }) => { + const id = user.id; + const data = createData(`${marketingId}-fake`, id); + cy.subscribeToMarketingConsent(marketingId).then(() => { + cy.visit( + `/unsubscribe/marketing/${encodeURIComponent(data)}/${createToken( + data, + )}`, + ); + cy.contains('Unable to unsubscribe.'); + }); + }); + }); + }); + + it('should be able to handle a unsubscribe error if userId is invalid', () => { + cy.createTestUser({ + isUserEmailValidated: true, + }).then(({ cookies }) => { + // SC_GU_U is required for the cy.getTestUserDetails + const scGuU = cookies.find((cookie) => cookie.key === 'SC_GU_U'); + if (!scGuU) throw new Error('SC_GU_U cookie not found'); + cy.setCookie('SC_GU_U', scGuU?.value); + + cy.getTestUserDetails().then(({ user }) => { + const id = user.id; + const data = createData(marketingId, `${id}0`); + cy.subscribeToMarketingConsent(marketingId).then(() => { + cy.visit( + `/unsubscribe/marketing/${encodeURIComponent(data)}/${createToken( + data, + )}`, + ); + cy.contains('Unable to unsubscribe.'); + }); + }); + }); + }); + + it('should be able to handle a unsubscribe error if data is invalid', () => { + cy.createTestUser({ + isUserEmailValidated: true, + }).then(({ cookies }) => { + // SC_GU_U is required for the cy.getTestUserDetails + const scGuU = cookies.find((cookie) => cookie.key === 'SC_GU_U'); + if (!scGuU) throw new Error('SC_GU_U cookie not found'); + cy.setCookie('SC_GU_U', scGuU?.value); + + cy.getTestUserDetails().then(() => { + const data = 'data-is-invalid:yes-123'; + cy.subscribeToMarketingConsent(marketingId).then(() => { + cy.visit( + `/unsubscribe/marketing/${encodeURIComponent(data)}/${createToken( + data, + )}`, + ); + cy.contains('Unable to unsubscribe.'); + }); + }); + }); + }); }); diff --git a/cypress/integration/mocked-okta/rateLimit.1.cy.ts b/cypress/integration/mocked-okta/rateLimit.1.cy.ts index 7609f58dc3..6bc462932e 100644 --- a/cypress/integration/mocked-okta/rateLimit.1.cy.ts +++ b/cypress/integration/mocked-okta/rateLimit.1.cy.ts @@ -1,153 +1,153 @@ describe('POST requests return a user-facing error message when encountering a rate limit from Okta', () => { - specify('Submit /signin', () => { - cy.visit('/signin'); - cy.get('input[name="email"]').type('example@example.com'); - cy.get('input[name="password"]').type('password'); - - cy.mockNext(429, { - errorCode: 'E0000047', - errorSummary: 'API call exceeded rate limit due to too many requests.', - errorLink: 'E0000047', - errorId: 'sampleXAy5B35EOzELmZL1zMy', - errorCauses: [], - }); - cy.get('button[type=submit]').click(); - cy.contains('There was a problem signing in, please try again.'); - }); - - specify('Submit /reauthenticate', () => { - cy.visit('/reauthenticate'); - cy.get('input[name="email"]').type('example@example.com'); - cy.get('input[name="password"]').type('password'); - - cy.mockNext(429, { - errorCode: 'E0000047', - errorSummary: 'API call exceeded rate limit due to too many requests.', - errorLink: 'E0000047', - errorId: 'sampleXAy5B35EOzELmZL1zMy', - errorCauses: [], - }); - cy.get('button[type=submit]').click(); - cy.contains('There was a problem signing in, please try again.'); - }); - - specify('Submit /register', () => { - cy.visit('/register'); - cy.get('input[name="email"]').type('example@example.com'); - - cy.mockNext(429, { - errorCode: 'E0000047', - errorSummary: 'API call exceeded rate limit due to too many requests.', - errorLink: 'E0000047', - errorId: 'sampleXAy5B35EOzELmZL1zMy', - errorCauses: [], - }); - cy.get('button[type=submit]').click(); - cy.contains('There was a problem registering, please try again.'); - }); - - specify('Submit /reset-password', () => { - cy.visit('/reset-password'); - cy.get('input[name="email"]').type('example@example.com'); - - cy.mockNext(429, { - errorCode: 'E0000047', - errorSummary: 'API call exceeded rate limit due to too many requests.', - errorLink: 'E0000047', - errorId: 'sampleXAy5B35EOzELmZL1zMy', - errorCauses: [], - }); - cy.get('button[type=submit]').click(); - cy.contains('Sorry, something went wrong. Please try again.'); - }); - - specify('Submit /welcome/resend', () => { - cy.visit(`/welcome/resend`); - cy.get('input[name="email"]').type('example@example.com'); - - cy.mockNext(429, { - errorCode: 'E0000047', - errorSummary: 'API call exceeded rate limit due to too many requests.', - errorLink: 'E0000047', - errorId: 'sampleXAy5B35EOzELmZL1zMy', - errorCauses: [], - }); - cy.get('button[type=submit]').click(); - cy.contains('Sorry, something went wrong. Please try again.'); - }); - - specify('Submit /welcome/expired', () => { - cy.visit(`/welcome/expired`); - cy.get('input[name="email"]').type('example@example.com'); - - cy.mockNext(429, { - errorCode: 'E0000047', - errorSummary: 'API call exceeded rate limit due to too many requests.', - errorLink: 'E0000047', - errorId: 'sampleXAy5B35EOzELmZL1zMy', - errorCauses: [], - }); - cy.get('button[type=submit]').click(); - cy.contains('Sorry, something went wrong. Please try again.'); - }); - - specify('Submit /reset-password/resend', () => { - cy.visit(`/reset-password/resend`); - cy.get('input[name="email"]').type('example@example.com'); - - cy.mockNext(429, { - errorCode: 'E0000047', - errorSummary: 'API call exceeded rate limit due to too many requests.', - errorLink: 'E0000047', - errorId: 'sampleXAy5B35EOzELmZL1zMy', - errorCauses: [], - }); - cy.get('button[type=submit]').click(); - cy.contains('Sorry, something went wrong. Please try again.'); - }); - - specify('Submit /reset-password/expired', () => { - cy.visit(`/reset-password/expired`); - cy.get('input[name="email"]').type('example@example.com'); - - cy.mockNext(429, { - errorCode: 'E0000047', - errorSummary: 'API call exceeded rate limit due to too many requests.', - errorLink: 'E0000047', - errorId: 'sampleXAy5B35EOzELmZL1zMy', - errorCauses: [], - }); - cy.get('button[type=submit]').click(); - cy.contains('Sorry, something went wrong. Please try again.'); - }); - - specify('Submit /set-password/resend', () => { - cy.visit(`/set-password/resend`); - cy.get('input[name="email"]').type('example@example.com'); - - cy.mockNext(429, { - errorCode: 'E0000047', - errorSummary: 'API call exceeded rate limit due to too many requests.', - errorLink: 'E0000047', - errorId: 'sampleXAy5B35EOzELmZL1zMy', - errorCauses: [], - }); - cy.get('button[type=submit]').click(); - cy.contains('Sorry, something went wrong. Please try again.'); - }); - - specify('Submit /set-password/expired', () => { - cy.visit(`/set-password/expired`); - cy.get('input[name="email"]').type('example@example.com'); - - cy.mockNext(429, { - errorCode: 'E0000047', - errorSummary: 'API call exceeded rate limit due to too many requests.', - errorLink: 'E0000047', - errorId: 'sampleXAy5B35EOzELmZL1zMy', - errorCauses: [], - }); - cy.get('button[type=submit]').click(); - cy.contains('Sorry, something went wrong. Please try again.'); - }); + specify('Submit /signin', () => { + cy.visit('/signin'); + cy.get('input[name="email"]').type('example@example.com'); + cy.get('input[name="password"]').type('password'); + + cy.mockNext(429, { + errorCode: 'E0000047', + errorSummary: 'API call exceeded rate limit due to too many requests.', + errorLink: 'E0000047', + errorId: 'sampleXAy5B35EOzELmZL1zMy', + errorCauses: [], + }); + cy.get('button[type=submit]').click(); + cy.contains('There was a problem signing in, please try again.'); + }); + + specify('Submit /reauthenticate', () => { + cy.visit('/reauthenticate'); + cy.get('input[name="email"]').type('example@example.com'); + cy.get('input[name="password"]').type('password'); + + cy.mockNext(429, { + errorCode: 'E0000047', + errorSummary: 'API call exceeded rate limit due to too many requests.', + errorLink: 'E0000047', + errorId: 'sampleXAy5B35EOzELmZL1zMy', + errorCauses: [], + }); + cy.get('button[type=submit]').click(); + cy.contains('There was a problem signing in, please try again.'); + }); + + specify('Submit /register', () => { + cy.visit('/register'); + cy.get('input[name="email"]').type('example@example.com'); + + cy.mockNext(429, { + errorCode: 'E0000047', + errorSummary: 'API call exceeded rate limit due to too many requests.', + errorLink: 'E0000047', + errorId: 'sampleXAy5B35EOzELmZL1zMy', + errorCauses: [], + }); + cy.get('button[type=submit]').click(); + cy.contains('There was a problem registering, please try again.'); + }); + + specify('Submit /reset-password', () => { + cy.visit('/reset-password'); + cy.get('input[name="email"]').type('example@example.com'); + + cy.mockNext(429, { + errorCode: 'E0000047', + errorSummary: 'API call exceeded rate limit due to too many requests.', + errorLink: 'E0000047', + errorId: 'sampleXAy5B35EOzELmZL1zMy', + errorCauses: [], + }); + cy.get('button[type=submit]').click(); + cy.contains('Sorry, something went wrong. Please try again.'); + }); + + specify('Submit /welcome/resend', () => { + cy.visit(`/welcome/resend`); + cy.get('input[name="email"]').type('example@example.com'); + + cy.mockNext(429, { + errorCode: 'E0000047', + errorSummary: 'API call exceeded rate limit due to too many requests.', + errorLink: 'E0000047', + errorId: 'sampleXAy5B35EOzELmZL1zMy', + errorCauses: [], + }); + cy.get('button[type=submit]').click(); + cy.contains('Sorry, something went wrong. Please try again.'); + }); + + specify('Submit /welcome/expired', () => { + cy.visit(`/welcome/expired`); + cy.get('input[name="email"]').type('example@example.com'); + + cy.mockNext(429, { + errorCode: 'E0000047', + errorSummary: 'API call exceeded rate limit due to too many requests.', + errorLink: 'E0000047', + errorId: 'sampleXAy5B35EOzELmZL1zMy', + errorCauses: [], + }); + cy.get('button[type=submit]').click(); + cy.contains('Sorry, something went wrong. Please try again.'); + }); + + specify('Submit /reset-password/resend', () => { + cy.visit(`/reset-password/resend`); + cy.get('input[name="email"]').type('example@example.com'); + + cy.mockNext(429, { + errorCode: 'E0000047', + errorSummary: 'API call exceeded rate limit due to too many requests.', + errorLink: 'E0000047', + errorId: 'sampleXAy5B35EOzELmZL1zMy', + errorCauses: [], + }); + cy.get('button[type=submit]').click(); + cy.contains('Sorry, something went wrong. Please try again.'); + }); + + specify('Submit /reset-password/expired', () => { + cy.visit(`/reset-password/expired`); + cy.get('input[name="email"]').type('example@example.com'); + + cy.mockNext(429, { + errorCode: 'E0000047', + errorSummary: 'API call exceeded rate limit due to too many requests.', + errorLink: 'E0000047', + errorId: 'sampleXAy5B35EOzELmZL1zMy', + errorCauses: [], + }); + cy.get('button[type=submit]').click(); + cy.contains('Sorry, something went wrong. Please try again.'); + }); + + specify('Submit /set-password/resend', () => { + cy.visit(`/set-password/resend`); + cy.get('input[name="email"]').type('example@example.com'); + + cy.mockNext(429, { + errorCode: 'E0000047', + errorSummary: 'API call exceeded rate limit due to too many requests.', + errorLink: 'E0000047', + errorId: 'sampleXAy5B35EOzELmZL1zMy', + errorCauses: [], + }); + cy.get('button[type=submit]').click(); + cy.contains('Sorry, something went wrong. Please try again.'); + }); + + specify('Submit /set-password/expired', () => { + cy.visit(`/set-password/expired`); + cy.get('input[name="email"]').type('example@example.com'); + + cy.mockNext(429, { + errorCode: 'E0000047', + errorSummary: 'API call exceeded rate limit due to too many requests.', + errorLink: 'E0000047', + errorId: 'sampleXAy5B35EOzELmZL1zMy', + errorCauses: [], + }); + cy.get('button[type=submit]').click(); + cy.contains('Sorry, something went wrong. Please try again.'); + }); }); diff --git a/cypress/integration/mocked-okta/registerController.1.cy.ts b/cypress/integration/mocked-okta/registerController.1.cy.ts index 6993027418..22d6423c75 100644 --- a/cypress/integration/mocked-okta/registerController.1.cy.ts +++ b/cypress/integration/mocked-okta/registerController.1.cy.ts @@ -7,209 +7,209 @@ import successTokenResponse from '../../fixtures/okta-responses/success/token.js import resetPasswordResponse from '../../fixtures/okta-responses/success/reset-password.json'; beforeEach(() => { - cy.mockPurge(); + cy.mockPurge(); }); const verifyInRegularEmailSentPage = () => { - cy.contains('Check your email inbox'); - cy.contains('Resend email'); + cy.contains('Check your email inbox'); + cy.contains('Resend email'); }; userStatuses.forEach((status) => { - context(`Given I am a ${status || 'nonexistent'} user`, () => { - context('When I submit the form on /register', () => { - beforeEach(() => { - cy.visit(`/register`); - cy.get('input[name="email"]').type('example@example.com'); - }); - switch (status) { - case false: - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status: 'STAGED' }; - cy.mockNext(userResponse.code, response); - cy.mockNext( - successTokenResponse.code, - successTokenResponse.response, - ); - cy.get('button[type=submit]').click(); - verifyInRegularEmailSentPage(); - }, - ); - break; - case 'ACTIVE': - specify( - "Then I should be shown the 'Check your email inbox' page if I have a validated email", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - const responseWithPassword = { - ...response, - credentials: { - ...response.credentials, - password: {}, - }, - }; - cy.mockNext(userExistsError.code, userExistsError.response); - cy.mockNext(userResponse.code, responseWithPassword); - cy.mockNext(userGroupsResponse.code, userGroupsResponse.response); - cy.get('button[type=submit]').click(); - verifyInRegularEmailSentPage(); - }, - ); - specify( - "Then I should be shown the 'Check your email inbox' page if I have a validated email but no password", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userExistsError.code, userExistsError.response); - cy.mockNext(userResponse.code, response); - cy.mockNext(userGroupsResponse.code, userGroupsResponse.response); - // dangerouslyResetPassword() - cy.mockNext(200, { - resetPasswordUrl: - 'https://example.com/reset_password/XE6wE17zmphl3KqAPFxO', - }); - // validateRecoveryToken() - cy.mockNext(200, { - stateToken: 'sometoken', - }); - // authenticationResetPassword() - cy.mockNext(200, {}); - // from sendEmailToUnvalidatedUser() --> forgotPassword() - cy.mockNext(200, { - resetPasswordUrl: - 'https://example.com/signin/reset-password/XE6wE17zmphl3KqAPFxO', - }); - cy.get('button[type=submit]').click(); - verifyInRegularEmailSentPage(); - }, - ); - specify( - "Then I should be shown the 'Check your email inbox' page for social user", - () => { - cy.mockNext(userExistsError.code, userExistsError.response); - cy.mockNext(socialUserResponse.code, socialUserResponse.response); - cy.mockNext(userGroupsResponse.code, userGroupsResponse.response); - // dangerouslyResetPassword() - cy.mockNext(200, { - resetPasswordUrl: - 'https://example.com/reset_password/XE6wE17zmphl3KqAPFxO', - }); - // validateRecoveryToken() - cy.mockNext(200, { - stateToken: 'sometoken', - }); - // authenticationResetPassword() - cy.mockNext(200, {}); - // from sendEmailToUnvalidatedUser() --> forgotPassword() - cy.mockNext(200, { - resetPasswordUrl: - 'https://example.com/signin/reset-password/XE6wE17zmphl3KqAPFxO', - }); - cy.get('button[type=submit]').click(); - verifyInRegularEmailSentPage(); - }, - ); - specify( - "Then I should be shown the 'Check your email inbox' page if I don't have a validated email and don't have a password set", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userExistsError.code, userExistsError.response); - // This user response doesn't have a password credential - cy.mockNext(userResponse.code, response); - const userGroupsResponseWithoutEmailValidated = - userGroupsResponse.response.filter( - (group) => - group.profile.name !== 'GuardianUser-EmailValidated', - ); - cy.mockNext( - userGroupsResponse.code, - userGroupsResponseWithoutEmailValidated, - ); - // dangerouslyResetPassword() - cy.mockNext(200, { - resetPasswordUrl: - 'https://example.com/reset_password/XE6wE17zmphl3KqAPFxO', - }); - // validateRecoveryToken() - cy.mockNext(200, { - stateToken: 'sometoken', - }); - // authenticationResetPassword() - cy.mockNext(200, {}); - // from sendEmailToUnvalidatedUser() --> forgotPassword() - cy.mockNext(200, { - resetPasswordUrl: - 'https://example.com/signin/reset-password/XE6wE17zmphl3KqAPFxO', - }); - cy.get('button[type=submit]').click(); - verifyInRegularEmailSentPage(); - }, - ); - specify( - "Then I should be shown the 'Check your email inbox' page if I don't have a validated email and do have a password set", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - const responseWithPassword = { - ...response, - credentials: { - ...response.credentials, - password: {}, - }, - }; - cy.mockNext(userExistsError.code, userExistsError.response); - cy.mockNext(userResponse.code, responseWithPassword); - cy.mockNext(userGroupsResponse.code, userGroupsResponse.response); - cy.get('button[type=submit]').click(); - verifyInRegularEmailSentPage(); - }, - ); - //not sure if we need to do anything for the social case here. the only mocked response I can change is the user response - //but it would be changing fields that no one looks at - break; - case 'PROVISIONED': - case 'STAGED': - // Then Gateway should generate an activation token - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userExistsError.code, userExistsError.response); - cy.mockNext(userResponse.code, response); - cy.mockNext( - successTokenResponse.code, - successTokenResponse.response, - ); - cy.get('button[type=submit]').click(); - verifyInRegularEmailSentPage(); - }, - ); - break; - case 'RECOVERY': - case 'PASSWORD_EXPIRED': - // Then Gateway should generate a reset password token - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userExistsError.code, userExistsError.response); - cy.mockNext(userResponse.code, response); - cy.mockNext( - resetPasswordResponse.code, - resetPasswordResponse.response, - ); - cy.get('button[type=submit]').click(); - verifyInRegularEmailSentPage(); - }, - ); - break; - } - }); - }); + context(`Given I am a ${status || 'nonexistent'} user`, () => { + context('When I submit the form on /register', () => { + beforeEach(() => { + cy.visit(`/register`); + cy.get('input[name="email"]').type('example@example.com'); + }); + switch (status) { + case false: + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status: 'STAGED' }; + cy.mockNext(userResponse.code, response); + cy.mockNext( + successTokenResponse.code, + successTokenResponse.response, + ); + cy.get('button[type=submit]').click(); + verifyInRegularEmailSentPage(); + }, + ); + break; + case 'ACTIVE': + specify( + "Then I should be shown the 'Check your email inbox' page if I have a validated email", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + const responseWithPassword = { + ...response, + credentials: { + ...response.credentials, + password: {}, + }, + }; + cy.mockNext(userExistsError.code, userExistsError.response); + cy.mockNext(userResponse.code, responseWithPassword); + cy.mockNext(userGroupsResponse.code, userGroupsResponse.response); + cy.get('button[type=submit]').click(); + verifyInRegularEmailSentPage(); + }, + ); + specify( + "Then I should be shown the 'Check your email inbox' page if I have a validated email but no password", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userExistsError.code, userExistsError.response); + cy.mockNext(userResponse.code, response); + cy.mockNext(userGroupsResponse.code, userGroupsResponse.response); + // dangerouslyResetPassword() + cy.mockNext(200, { + resetPasswordUrl: + 'https://example.com/reset_password/XE6wE17zmphl3KqAPFxO', + }); + // validateRecoveryToken() + cy.mockNext(200, { + stateToken: 'sometoken', + }); + // authenticationResetPassword() + cy.mockNext(200, {}); + // from sendEmailToUnvalidatedUser() --> forgotPassword() + cy.mockNext(200, { + resetPasswordUrl: + 'https://example.com/signin/reset-password/XE6wE17zmphl3KqAPFxO', + }); + cy.get('button[type=submit]').click(); + verifyInRegularEmailSentPage(); + }, + ); + specify( + "Then I should be shown the 'Check your email inbox' page for social user", + () => { + cy.mockNext(userExistsError.code, userExistsError.response); + cy.mockNext(socialUserResponse.code, socialUserResponse.response); + cy.mockNext(userGroupsResponse.code, userGroupsResponse.response); + // dangerouslyResetPassword() + cy.mockNext(200, { + resetPasswordUrl: + 'https://example.com/reset_password/XE6wE17zmphl3KqAPFxO', + }); + // validateRecoveryToken() + cy.mockNext(200, { + stateToken: 'sometoken', + }); + // authenticationResetPassword() + cy.mockNext(200, {}); + // from sendEmailToUnvalidatedUser() --> forgotPassword() + cy.mockNext(200, { + resetPasswordUrl: + 'https://example.com/signin/reset-password/XE6wE17zmphl3KqAPFxO', + }); + cy.get('button[type=submit]').click(); + verifyInRegularEmailSentPage(); + }, + ); + specify( + "Then I should be shown the 'Check your email inbox' page if I don't have a validated email and don't have a password set", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userExistsError.code, userExistsError.response); + // This user response doesn't have a password credential + cy.mockNext(userResponse.code, response); + const userGroupsResponseWithoutEmailValidated = + userGroupsResponse.response.filter( + (group) => + group.profile.name !== 'GuardianUser-EmailValidated', + ); + cy.mockNext( + userGroupsResponse.code, + userGroupsResponseWithoutEmailValidated, + ); + // dangerouslyResetPassword() + cy.mockNext(200, { + resetPasswordUrl: + 'https://example.com/reset_password/XE6wE17zmphl3KqAPFxO', + }); + // validateRecoveryToken() + cy.mockNext(200, { + stateToken: 'sometoken', + }); + // authenticationResetPassword() + cy.mockNext(200, {}); + // from sendEmailToUnvalidatedUser() --> forgotPassword() + cy.mockNext(200, { + resetPasswordUrl: + 'https://example.com/signin/reset-password/XE6wE17zmphl3KqAPFxO', + }); + cy.get('button[type=submit]').click(); + verifyInRegularEmailSentPage(); + }, + ); + specify( + "Then I should be shown the 'Check your email inbox' page if I don't have a validated email and do have a password set", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + const responseWithPassword = { + ...response, + credentials: { + ...response.credentials, + password: {}, + }, + }; + cy.mockNext(userExistsError.code, userExistsError.response); + cy.mockNext(userResponse.code, responseWithPassword); + cy.mockNext(userGroupsResponse.code, userGroupsResponse.response); + cy.get('button[type=submit]').click(); + verifyInRegularEmailSentPage(); + }, + ); + //not sure if we need to do anything for the social case here. the only mocked response I can change is the user response + //but it would be changing fields that no one looks at + break; + case 'PROVISIONED': + case 'STAGED': + // Then Gateway should generate an activation token + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userExistsError.code, userExistsError.response); + cy.mockNext(userResponse.code, response); + cy.mockNext( + successTokenResponse.code, + successTokenResponse.response, + ); + cy.get('button[type=submit]').click(); + verifyInRegularEmailSentPage(); + }, + ); + break; + case 'RECOVERY': + case 'PASSWORD_EXPIRED': + // Then Gateway should generate a reset password token + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userExistsError.code, userExistsError.response); + cy.mockNext(userResponse.code, response); + cy.mockNext( + resetPasswordResponse.code, + resetPasswordResponse.response, + ); + cy.get('button[type=submit]').click(); + verifyInRegularEmailSentPage(); + }, + ); + break; + } + }); + }); }); diff --git a/cypress/integration/mocked-okta/resendEmailController.3.cy.ts b/cypress/integration/mocked-okta/resendEmailController.3.cy.ts index d23f5273d0..01d1f18528 100644 --- a/cypress/integration/mocked-okta/resendEmailController.3.cy.ts +++ b/cypress/integration/mocked-okta/resendEmailController.3.cy.ts @@ -7,497 +7,497 @@ import successTokenResponse from '../../fixtures/okta-responses/success/token.js import resetPasswordResponse from '../../fixtures/okta-responses/success/reset-password.json'; beforeEach(() => { - cy.mockPurge(); + cy.mockPurge(); }); const verifyInRegularEmailSentPage = () => { - cy.contains('Check your email inbox'); - cy.contains('Resend email'); - cy.contains('Email sent'); - cy.contains('within 2 minutes').should('not.exist'); + cy.contains('Check your email inbox'); + cy.contains('Resend email'); + cy.contains('Email sent'); + cy.contains('within 2 minutes').should('not.exist'); }; userStatuses.forEach((status) => { - context(`Given I am a ${status || 'nonexistent'} user`, () => { - context('When I submit the form on /register/email-sent', () => { - beforeEach(() => { - // We mock the encrypted state cookie here to trick the endpoint - // into thinking we've just gone through the preceeding flow. - // For readEncryptedStateCookie to succeed, it relies on a testing - // env variable to be set, otherwise it won't be able to read the cookie. - cy.setEncryptedStateCookie({ - email: 'example@example.com', - status: String(status), - }); - cy.visit(`/register/email-sent`); - }); - switch (status) { - case false: - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status: 'STAGED' }; - cy.mockNext(userResponse.code, response); - cy.get('[data-cy="main-form-submit-button"]').click(); - verifyInRegularEmailSentPage(); - }, - ); - break; - case 'ACTIVE': - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userExistsError.code, userExistsError.response); - cy.mockNext(userResponse.code, response); - cy.mockNext(userGroupsResponse.code, userGroupsResponse.response); - cy.get('[data-cy="main-form-submit-button"]').click(); - verifyInRegularEmailSentPage(); - }, - ); - specify( - "Then I should be shown the 'Check your email inbox' page for social users ", - () => { - cy.mockNext(userExistsError.code, userExistsError.response); - cy.mockNext(socialUserResponse.code, socialUserResponse.response); - cy.mockNext(userGroupsResponse.code, userGroupsResponse.response); - cy.get('[data-cy="main-form-submit-button"]').click(); - verifyInRegularEmailSentPage(); - }, - ); - specify( - "Then I should be shown the 'Check your email inbox' page if I don't have a validated email and don't have a password set", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userExistsError.code, userExistsError.response); - // This user response doesn't have a password credential - cy.mockNext(userResponse.code, response); - const userGroupsResponseWithoutEmailValidated = - userGroupsResponse.response.filter( - (group) => - group.profile.name !== 'GuardianUser-EmailValidated', - ); - cy.mockNext( - userGroupsResponse.code, - userGroupsResponseWithoutEmailValidated, - ); - // dangerouslyResetPassword() - cy.mockNext(200, { - resetPasswordUrl: - 'https://example.com/reset_password/XE6wE17zmphl3KqAPFxO', - }); - // validateRecoveryToken() - cy.mockNext(200, { - stateToken: 'sometoken', - }); - // authenticationResetPassword() - cy.mockNext(200, {}); - // from sendEmailToUnvalidatedUser() --> forgotPassword() - cy.mockNext(200, { - resetPasswordUrl: - 'https://example.com/signin/reset-password/XE6wE17zmphl3KqAPFxO', - }); - cy.get('button[type=submit]').click(); - verifyInRegularEmailSentPage(); - }, - ); - specify( - "Then I should be shown the 'Check your email inbox' page if I don't have a validated email and do have a password set", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userExistsError.code, userExistsError.response); - cy.mockNext(userResponse.code, response); - cy.mockNext(userGroupsResponse.code, userGroupsResponse.response); - cy.get('button[type=submit]').click(); - verifyInRegularEmailSentPage(); - }, - ); - //i Think this is the same as register.. I don't know if I should add the modked social user here - // when the execution doesn't check the credentials part of the response - break; - case 'PROVISIONED': - case 'STAGED': - // Then Gateway should generate an activation token - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userExistsError.code, userExistsError.response); - cy.mockNext(userResponse.code, response); - cy.mockNext( - successTokenResponse.code, - successTokenResponse.response, - ); - cy.get('[data-cy="main-form-submit-button"]').click(); - verifyInRegularEmailSentPage(); - }, - ); - break; - case 'RECOVERY': - case 'PASSWORD_EXPIRED': - // Then Gateway should generate a reset password token - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userExistsError.code, userExistsError.response); - cy.mockNext(userResponse.code, response); - cy.mockNext( - resetPasswordResponse.code, - resetPasswordResponse.response, - ); - cy.get('[data-cy="main-form-submit-button"]').click(); - verifyInRegularEmailSentPage(); - }, - ); - break; - } - }); - context('When I submit the form on /welcome/email-sent', () => { - beforeEach(() => { - // We mock the encrypted state cookie here to trick the endpoint - // into thinking we've just gone through the preceeding flow. - // For readEncryptedStateCookie to succeed, it relies on a testing - // env variable to be set, otherwise it won't be able to read the cookie. - cy.setEncryptedStateCookie({ - email: 'example@example.com', - status: String(status), - }); - cy.visit(`/welcome/email-sent`); - }); - switch (status) { - case false: - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status: 'STAGED' }; - cy.mockNext(userResponse.code, response); - cy.mockNext( - successTokenResponse.code, - successTokenResponse.response, - ); - cy.get('[data-cy="main-form-submit-button"]').click(); - verifyInRegularEmailSentPage(); - }, - ); - break; - case 'ACTIVE': - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - const responseWithPassword = { - ...response, - credentials: { - ...response.credentials, - password: {}, - }, - }; - cy.mockNext(userExistsError.code, userExistsError.response); - cy.mockNext(userResponse.code, responseWithPassword); - cy.mockNext(userGroupsResponse.code, userGroupsResponse.response); - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.contains('Check your email inbox'); - cy.contains('Resend email'); - cy.contains('Email sent'); - }, - ); - specify( - "Then I should be shown the 'Check your email inbox' page for social user", - () => { - cy.mockNext(userExistsError.code, userExistsError.response); - // This user response doesn't have a password, which is what we want - // for a social user. - cy.mockNext(socialUserResponse.code, socialUserResponse.response); - cy.mockNext(userGroupsResponse.code, userGroupsResponse.response); - // dangerouslyResetPassword() - cy.mockNext(200, { - resetPasswordUrl: - 'https://example.com/reset_password/XE6wE17zmphl3KqAPFxO', - }); - // validateRecoveryToken() - cy.mockNext(200, { - stateToken: 'sometoken', - }); - // authenticationResetPassword() - cy.mockNext(200, {}); - // from sendEmailToUnvalidatedUser() --> forgotPassword() - cy.mockNext(200, { - resetPasswordUrl: - 'https://example.com/signin/reset-password/XE6wE17zmphl3KqAPFxO', - }); - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.contains('Check your email inbox'); - cy.contains('Resend email'); - cy.contains('Email sent'); - }, - ); - break; - case 'PROVISIONED': - case 'STAGED': - // Then Gateway should generate an activation token - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userExistsError.code, userExistsError.response); - cy.mockNext(userResponse.code, response); - cy.mockNext( - successTokenResponse.code, - successTokenResponse.response, - ); - cy.get('[data-cy="main-form-submit-button"]').click(); - verifyInRegularEmailSentPage(); - }, - ); - break; - case 'RECOVERY': - case 'PASSWORD_EXPIRED': - // Then Gateway should generate a reset password token - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userExistsError.code, userExistsError.response); - cy.mockNext(userResponse.code, response); - cy.mockNext( - resetPasswordResponse.code, - resetPasswordResponse.response, - ); - cy.get('[data-cy="main-form-submit-button"]').click(); - verifyInRegularEmailSentPage(); - }, - ); - break; - } - }); - context('When I submit the form on /welcome/resend', () => { - beforeEach(() => { - cy.visit(`/welcome/resend`); - cy.get('input[name="email"]').type('example@example.com'); - }); - switch (status) { - case false: - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status: 'STAGED' }; - cy.mockNext(userResponse.code, response); - cy.mockNext( - successTokenResponse.code, - successTokenResponse.response, - ); - cy.get('[data-cy="main-form-submit-button"]').click(); - verifyInRegularEmailSentPage(); - }, - ); - break; - case 'ACTIVE': - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - const responseWithPassword = { - ...response, - credentials: { - ...response.credentials, - password: {}, - }, - }; - cy.mockNext(userExistsError.code, userExistsError.response); - cy.mockNext(userResponse.code, responseWithPassword); - cy.mockNext(userGroupsResponse.code, userGroupsResponse.response); - cy.get('[data-cy="main-form-submit-button"]').click(); - verifyInRegularEmailSentPage(); - }, - ); - specify( - "Then I should be shown the 'Check your email inbox' page for social user", - () => { - cy.mockNext(userExistsError.code, userExistsError.response); - cy.mockNext(socialUserResponse.code, socialUserResponse.response); - cy.mockNext(userGroupsResponse.code, userGroupsResponse.response); - // dangerouslyResetPassword() - cy.mockNext(200, { - resetPasswordUrl: - 'https://example.com/reset_password/XE6wE17zmphl3KqAPFxO', - }); - // validateRecoveryToken() - cy.mockNext(200, { - stateToken: 'sometoken', - }); - // authenticationResetPassword() - cy.mockNext(200, {}); - // from sendEmailToUnvalidatedUser() --> forgotPassword() - cy.mockNext(200, { - resetPasswordUrl: - 'https://example.com/signin/reset-password/XE6wE17zmphl3KqAPFxO', - }); - cy.get('[data-cy="main-form-submit-button"]').click(); - verifyInRegularEmailSentPage(); - }, - ); - break; - case 'PROVISIONED': - case 'STAGED': - // Then Gateway should generate an activation token - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userExistsError.code, userExistsError.response); - cy.mockNext(userResponse.code, response); - cy.mockNext( - successTokenResponse.code, - successTokenResponse.response, - ); - cy.get('[data-cy="main-form-submit-button"]').click(); - verifyInRegularEmailSentPage(); - }, - ); - break; - case 'RECOVERY': - case 'PASSWORD_EXPIRED': - // Then Gateway should generate a reset password token - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userExistsError.code, userExistsError.response); - cy.mockNext(userResponse.code, response); - cy.mockNext( - resetPasswordResponse.code, - resetPasswordResponse.response, - ); - cy.get('[data-cy="main-form-submit-button"]').click(); - verifyInRegularEmailSentPage(); - }, - ); - break; - } - }); - context('When I submit the form on /welcome/expired', () => { - beforeEach(() => { - cy.visit(`/welcome/expired`); - cy.get('input[name="email"]').type('example@example.com'); - }); - switch (status) { - case false: - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status: 'STAGED' }; - cy.mockNext(userResponse.code, response); - cy.mockNext( - successTokenResponse.code, - successTokenResponse.response, - ); - cy.get('[data-cy="main-form-submit-button"]').click(); - verifyInRegularEmailSentPage(); - }, - ); - break; - case 'ACTIVE': - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - const responseWithPassword = { - ...response, - credentials: { - ...response.credentials, - password: {}, - }, - }; - cy.mockNext(userExistsError.code, userExistsError.response); - cy.mockNext(userResponse.code, responseWithPassword); - cy.mockNext(userGroupsResponse.code, userGroupsResponse.response); - cy.get('[data-cy="main-form-submit-button"]').click(); - verifyInRegularEmailSentPage(); - }, - ); - specify( - "Then I should be shown the 'Check your email inbox' page for social users", - () => { - cy.mockNext(userExistsError.code, userExistsError.response); - cy.mockNext(socialUserResponse.code, socialUserResponse.response); - cy.mockNext(userGroupsResponse.code, userGroupsResponse.response); - // dangerouslyResetPassword() - cy.mockNext(200, { - resetPasswordUrl: - 'https://example.com/reset_password/XE6wE17zmphl3KqAPFxO', - }); - // validateRecoveryToken() - cy.mockNext(200, { - stateToken: 'sometoken', - }); - // authenticationResetPassword() - cy.mockNext(200, {}); - // from sendEmailToUnvalidatedUser() --> forgotPassword() - cy.mockNext(200, { - resetPasswordUrl: - 'https://example.com/signin/reset-password/XE6wE17zmphl3KqAPFxO', - }); - cy.get('[data-cy="main-form-submit-button"]').click(); - verifyInRegularEmailSentPage(); - }, - ); - break; - case 'PROVISIONED': - case 'STAGED': - // Then Gateway should generate an activation token - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userExistsError.code, userExistsError.response); - cy.mockNext(userResponse.code, response); - cy.mockNext( - successTokenResponse.code, - successTokenResponse.response, - ); - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.contains('Check your email inbox'); - cy.contains('Resend email'); - cy.contains('Email sent'); - }, - ); - break; - case 'RECOVERY': - case 'PASSWORD_EXPIRED': - // Then Gateway should generate a reset password token - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userExistsError.code, userExistsError.response); - cy.mockNext(userResponse.code, response); - cy.mockNext( - resetPasswordResponse.code, - resetPasswordResponse.response, - ); - cy.get('[data-cy="main-form-submit-button"]').click(); - verifyInRegularEmailSentPage(); - }, - ); - break; - } - }); - }); + context(`Given I am a ${status || 'nonexistent'} user`, () => { + context('When I submit the form on /register/email-sent', () => { + beforeEach(() => { + // We mock the encrypted state cookie here to trick the endpoint + // into thinking we've just gone through the preceeding flow. + // For readEncryptedStateCookie to succeed, it relies on a testing + // env variable to be set, otherwise it won't be able to read the cookie. + cy.setEncryptedStateCookie({ + email: 'example@example.com', + status: String(status), + }); + cy.visit(`/register/email-sent`); + }); + switch (status) { + case false: + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status: 'STAGED' }; + cy.mockNext(userResponse.code, response); + cy.get('[data-cy="main-form-submit-button"]').click(); + verifyInRegularEmailSentPage(); + }, + ); + break; + case 'ACTIVE': + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userExistsError.code, userExistsError.response); + cy.mockNext(userResponse.code, response); + cy.mockNext(userGroupsResponse.code, userGroupsResponse.response); + cy.get('[data-cy="main-form-submit-button"]').click(); + verifyInRegularEmailSentPage(); + }, + ); + specify( + "Then I should be shown the 'Check your email inbox' page for social users ", + () => { + cy.mockNext(userExistsError.code, userExistsError.response); + cy.mockNext(socialUserResponse.code, socialUserResponse.response); + cy.mockNext(userGroupsResponse.code, userGroupsResponse.response); + cy.get('[data-cy="main-form-submit-button"]').click(); + verifyInRegularEmailSentPage(); + }, + ); + specify( + "Then I should be shown the 'Check your email inbox' page if I don't have a validated email and don't have a password set", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userExistsError.code, userExistsError.response); + // This user response doesn't have a password credential + cy.mockNext(userResponse.code, response); + const userGroupsResponseWithoutEmailValidated = + userGroupsResponse.response.filter( + (group) => + group.profile.name !== 'GuardianUser-EmailValidated', + ); + cy.mockNext( + userGroupsResponse.code, + userGroupsResponseWithoutEmailValidated, + ); + // dangerouslyResetPassword() + cy.mockNext(200, { + resetPasswordUrl: + 'https://example.com/reset_password/XE6wE17zmphl3KqAPFxO', + }); + // validateRecoveryToken() + cy.mockNext(200, { + stateToken: 'sometoken', + }); + // authenticationResetPassword() + cy.mockNext(200, {}); + // from sendEmailToUnvalidatedUser() --> forgotPassword() + cy.mockNext(200, { + resetPasswordUrl: + 'https://example.com/signin/reset-password/XE6wE17zmphl3KqAPFxO', + }); + cy.get('button[type=submit]').click(); + verifyInRegularEmailSentPage(); + }, + ); + specify( + "Then I should be shown the 'Check your email inbox' page if I don't have a validated email and do have a password set", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userExistsError.code, userExistsError.response); + cy.mockNext(userResponse.code, response); + cy.mockNext(userGroupsResponse.code, userGroupsResponse.response); + cy.get('button[type=submit]').click(); + verifyInRegularEmailSentPage(); + }, + ); + //i Think this is the same as register.. I don't know if I should add the modked social user here + // when the execution doesn't check the credentials part of the response + break; + case 'PROVISIONED': + case 'STAGED': + // Then Gateway should generate an activation token + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userExistsError.code, userExistsError.response); + cy.mockNext(userResponse.code, response); + cy.mockNext( + successTokenResponse.code, + successTokenResponse.response, + ); + cy.get('[data-cy="main-form-submit-button"]').click(); + verifyInRegularEmailSentPage(); + }, + ); + break; + case 'RECOVERY': + case 'PASSWORD_EXPIRED': + // Then Gateway should generate a reset password token + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userExistsError.code, userExistsError.response); + cy.mockNext(userResponse.code, response); + cy.mockNext( + resetPasswordResponse.code, + resetPasswordResponse.response, + ); + cy.get('[data-cy="main-form-submit-button"]').click(); + verifyInRegularEmailSentPage(); + }, + ); + break; + } + }); + context('When I submit the form on /welcome/email-sent', () => { + beforeEach(() => { + // We mock the encrypted state cookie here to trick the endpoint + // into thinking we've just gone through the preceeding flow. + // For readEncryptedStateCookie to succeed, it relies on a testing + // env variable to be set, otherwise it won't be able to read the cookie. + cy.setEncryptedStateCookie({ + email: 'example@example.com', + status: String(status), + }); + cy.visit(`/welcome/email-sent`); + }); + switch (status) { + case false: + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status: 'STAGED' }; + cy.mockNext(userResponse.code, response); + cy.mockNext( + successTokenResponse.code, + successTokenResponse.response, + ); + cy.get('[data-cy="main-form-submit-button"]').click(); + verifyInRegularEmailSentPage(); + }, + ); + break; + case 'ACTIVE': + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + const responseWithPassword = { + ...response, + credentials: { + ...response.credentials, + password: {}, + }, + }; + cy.mockNext(userExistsError.code, userExistsError.response); + cy.mockNext(userResponse.code, responseWithPassword); + cy.mockNext(userGroupsResponse.code, userGroupsResponse.response); + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.contains('Check your email inbox'); + cy.contains('Resend email'); + cy.contains('Email sent'); + }, + ); + specify( + "Then I should be shown the 'Check your email inbox' page for social user", + () => { + cy.mockNext(userExistsError.code, userExistsError.response); + // This user response doesn't have a password, which is what we want + // for a social user. + cy.mockNext(socialUserResponse.code, socialUserResponse.response); + cy.mockNext(userGroupsResponse.code, userGroupsResponse.response); + // dangerouslyResetPassword() + cy.mockNext(200, { + resetPasswordUrl: + 'https://example.com/reset_password/XE6wE17zmphl3KqAPFxO', + }); + // validateRecoveryToken() + cy.mockNext(200, { + stateToken: 'sometoken', + }); + // authenticationResetPassword() + cy.mockNext(200, {}); + // from sendEmailToUnvalidatedUser() --> forgotPassword() + cy.mockNext(200, { + resetPasswordUrl: + 'https://example.com/signin/reset-password/XE6wE17zmphl3KqAPFxO', + }); + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.contains('Check your email inbox'); + cy.contains('Resend email'); + cy.contains('Email sent'); + }, + ); + break; + case 'PROVISIONED': + case 'STAGED': + // Then Gateway should generate an activation token + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userExistsError.code, userExistsError.response); + cy.mockNext(userResponse.code, response); + cy.mockNext( + successTokenResponse.code, + successTokenResponse.response, + ); + cy.get('[data-cy="main-form-submit-button"]').click(); + verifyInRegularEmailSentPage(); + }, + ); + break; + case 'RECOVERY': + case 'PASSWORD_EXPIRED': + // Then Gateway should generate a reset password token + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userExistsError.code, userExistsError.response); + cy.mockNext(userResponse.code, response); + cy.mockNext( + resetPasswordResponse.code, + resetPasswordResponse.response, + ); + cy.get('[data-cy="main-form-submit-button"]').click(); + verifyInRegularEmailSentPage(); + }, + ); + break; + } + }); + context('When I submit the form on /welcome/resend', () => { + beforeEach(() => { + cy.visit(`/welcome/resend`); + cy.get('input[name="email"]').type('example@example.com'); + }); + switch (status) { + case false: + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status: 'STAGED' }; + cy.mockNext(userResponse.code, response); + cy.mockNext( + successTokenResponse.code, + successTokenResponse.response, + ); + cy.get('[data-cy="main-form-submit-button"]').click(); + verifyInRegularEmailSentPage(); + }, + ); + break; + case 'ACTIVE': + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + const responseWithPassword = { + ...response, + credentials: { + ...response.credentials, + password: {}, + }, + }; + cy.mockNext(userExistsError.code, userExistsError.response); + cy.mockNext(userResponse.code, responseWithPassword); + cy.mockNext(userGroupsResponse.code, userGroupsResponse.response); + cy.get('[data-cy="main-form-submit-button"]').click(); + verifyInRegularEmailSentPage(); + }, + ); + specify( + "Then I should be shown the 'Check your email inbox' page for social user", + () => { + cy.mockNext(userExistsError.code, userExistsError.response); + cy.mockNext(socialUserResponse.code, socialUserResponse.response); + cy.mockNext(userGroupsResponse.code, userGroupsResponse.response); + // dangerouslyResetPassword() + cy.mockNext(200, { + resetPasswordUrl: + 'https://example.com/reset_password/XE6wE17zmphl3KqAPFxO', + }); + // validateRecoveryToken() + cy.mockNext(200, { + stateToken: 'sometoken', + }); + // authenticationResetPassword() + cy.mockNext(200, {}); + // from sendEmailToUnvalidatedUser() --> forgotPassword() + cy.mockNext(200, { + resetPasswordUrl: + 'https://example.com/signin/reset-password/XE6wE17zmphl3KqAPFxO', + }); + cy.get('[data-cy="main-form-submit-button"]').click(); + verifyInRegularEmailSentPage(); + }, + ); + break; + case 'PROVISIONED': + case 'STAGED': + // Then Gateway should generate an activation token + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userExistsError.code, userExistsError.response); + cy.mockNext(userResponse.code, response); + cy.mockNext( + successTokenResponse.code, + successTokenResponse.response, + ); + cy.get('[data-cy="main-form-submit-button"]').click(); + verifyInRegularEmailSentPage(); + }, + ); + break; + case 'RECOVERY': + case 'PASSWORD_EXPIRED': + // Then Gateway should generate a reset password token + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userExistsError.code, userExistsError.response); + cy.mockNext(userResponse.code, response); + cy.mockNext( + resetPasswordResponse.code, + resetPasswordResponse.response, + ); + cy.get('[data-cy="main-form-submit-button"]').click(); + verifyInRegularEmailSentPage(); + }, + ); + break; + } + }); + context('When I submit the form on /welcome/expired', () => { + beforeEach(() => { + cy.visit(`/welcome/expired`); + cy.get('input[name="email"]').type('example@example.com'); + }); + switch (status) { + case false: + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status: 'STAGED' }; + cy.mockNext(userResponse.code, response); + cy.mockNext( + successTokenResponse.code, + successTokenResponse.response, + ); + cy.get('[data-cy="main-form-submit-button"]').click(); + verifyInRegularEmailSentPage(); + }, + ); + break; + case 'ACTIVE': + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + const responseWithPassword = { + ...response, + credentials: { + ...response.credentials, + password: {}, + }, + }; + cy.mockNext(userExistsError.code, userExistsError.response); + cy.mockNext(userResponse.code, responseWithPassword); + cy.mockNext(userGroupsResponse.code, userGroupsResponse.response); + cy.get('[data-cy="main-form-submit-button"]').click(); + verifyInRegularEmailSentPage(); + }, + ); + specify( + "Then I should be shown the 'Check your email inbox' page for social users", + () => { + cy.mockNext(userExistsError.code, userExistsError.response); + cy.mockNext(socialUserResponse.code, socialUserResponse.response); + cy.mockNext(userGroupsResponse.code, userGroupsResponse.response); + // dangerouslyResetPassword() + cy.mockNext(200, { + resetPasswordUrl: + 'https://example.com/reset_password/XE6wE17zmphl3KqAPFxO', + }); + // validateRecoveryToken() + cy.mockNext(200, { + stateToken: 'sometoken', + }); + // authenticationResetPassword() + cy.mockNext(200, {}); + // from sendEmailToUnvalidatedUser() --> forgotPassword() + cy.mockNext(200, { + resetPasswordUrl: + 'https://example.com/signin/reset-password/XE6wE17zmphl3KqAPFxO', + }); + cy.get('[data-cy="main-form-submit-button"]').click(); + verifyInRegularEmailSentPage(); + }, + ); + break; + case 'PROVISIONED': + case 'STAGED': + // Then Gateway should generate an activation token + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userExistsError.code, userExistsError.response); + cy.mockNext(userResponse.code, response); + cy.mockNext( + successTokenResponse.code, + successTokenResponse.response, + ); + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.contains('Check your email inbox'); + cy.contains('Resend email'); + cy.contains('Email sent'); + }, + ); + break; + case 'RECOVERY': + case 'PASSWORD_EXPIRED': + // Then Gateway should generate a reset password token + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userExistsError.code, userExistsError.response); + cy.mockNext(userResponse.code, response); + cy.mockNext( + resetPasswordResponse.code, + resetPasswordResponse.response, + ); + cy.get('[data-cy="main-form-submit-button"]').click(); + verifyInRegularEmailSentPage(); + }, + ); + break; + } + }); + }); }); diff --git a/cypress/integration/mocked-okta/resetPasswordController.4.cy.ts b/cypress/integration/mocked-okta/resetPasswordController.4.cy.ts index 50c21bac94..e51b75b0ec 100644 --- a/cypress/integration/mocked-okta/resetPasswordController.4.cy.ts +++ b/cypress/integration/mocked-okta/resetPasswordController.4.cy.ts @@ -8,668 +8,668 @@ import resetPasswordResponse from '../../fixtures/okta-responses/success/reset-p import verifyRecoveryTokenReponse from '../../fixtures/okta-responses/success/verify-recovery-token.json'; import authResetPasswordResponse from '../../fixtures/okta-responses/success/auth-reset-password.json'; beforeEach(() => { - cy.mockPurge(); + cy.mockPurge(); }); const verifyIn2MinutesEmailSentPage = () => { - cy.contains('Check your email inbox'); - cy.contains('Resend email'); - cy.contains('Email sent'); - cy.contains('within 2 minutes'); + cy.contains('Check your email inbox'); + cy.contains('Resend email'); + cy.contains('Email sent'); + cy.contains('within 2 minutes'); }; const verifyInRegularEmailSentPage = () => { - cy.contains('Check your email inbox'); - cy.contains('Resend email'); - cy.contains('Email sent'); - cy.contains('within 2 minutes').should('not.exist'); + cy.contains('Check your email inbox'); + cy.contains('Resend email'); + cy.contains('Email sent'); + cy.contains('within 2 minutes').should('not.exist'); }; const setupMocksForSocialUserPasswordReset = () => { - // Response from getUser() - cy.mockNext(socialUserResponse.code, socialUserResponse.response); - // Response from sendForgotPasswordEmail() - an Okta error - cy.mockNext(oktaPermissionsError.code, oktaPermissionsError.response); - // Response from dangerouslyResetPassword() - a reset password URL - cy.mockNext(resetPasswordResponse.code, resetPasswordResponse.response); - // Response from validateRecoveryToken() - a state token - cy.mockNext( - verifyRecoveryTokenReponse.code, - verifyRecoveryTokenReponse.response, - ); - // Response from resetPassword() - cy.mockNext( - authResetPasswordResponse.code, - authResetPasswordResponse.response, - ); + // Response from getUser() + cy.mockNext(socialUserResponse.code, socialUserResponse.response); + // Response from sendForgotPasswordEmail() - an Okta error + cy.mockNext(oktaPermissionsError.code, oktaPermissionsError.response); + // Response from dangerouslyResetPassword() - a reset password URL + cy.mockNext(resetPasswordResponse.code, resetPasswordResponse.response); + // Response from validateRecoveryToken() - a state token + cy.mockNext( + verifyRecoveryTokenReponse.code, + verifyRecoveryTokenReponse.response, + ); + // Response from resetPassword() + cy.mockNext( + authResetPasswordResponse.code, + authResetPasswordResponse.response, + ); - // retry sending the email now that the user is "fixed" - // so we need to mock the same as for the non social user - cy.mockNext(socialUserResponse.code, socialUserResponse.response); - // forgotPassword() - cy.mockNext(200, { - resetPasswordUrl: - 'https://example.com/signin/reset-password/XE6wE17zmphl3KqAPFxO', - }); + // retry sending the email now that the user is "fixed" + // so we need to mock the same as for the non social user + cy.mockNext(socialUserResponse.code, socialUserResponse.response); + // forgotPassword() + cy.mockNext(200, { + resetPasswordUrl: + 'https://example.com/signin/reset-password/XE6wE17zmphl3KqAPFxO', + }); }; userStatuses.forEach((status) => { - context(`Given I am a ${status || 'nonexistent'} user`, () => { - context('When I submit the form on /reset-password', () => { - beforeEach(() => { - cy.visit(`/reset-password`); - cy.get('input[name="email"]').type('example@example.com'); - }); - switch (status) { - case false: - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - cy.mockNext(userNotFoundError.code, userNotFoundError.response); - cy.get('button[type=submit]').click(); - verifyIn2MinutesEmailSentPage(); - }, - ); - break; - case 'ACTIVE': - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userResponse.code, response); - // forgotPassword() - cy.mockNext(200, { - resetPasswordUrl: - 'https://example.com/signin/reset-password/XE6wE17zmphl3KqAPFxO', - }); - cy.get('button[type=submit]').click(); - verifyIn2MinutesEmailSentPage(); - }, - ); - specify( - "Then I should be shown the 'Check your email inbox' page for social user", - () => { - setupMocksForSocialUserPasswordReset(); - cy.get('button[type=submit]').click(); - verifyIn2MinutesEmailSentPage(); - }, - ); - break; - case 'PROVISIONED': - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userResponse.code, response); - cy.mockNext(tokenResponse.code, tokenResponse.response); - cy.get('button[type=submit]').click(); - verifyIn2MinutesEmailSentPage(); - }, - ); - break; - case 'STAGED': - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userResponse.code, response); - cy.mockNext(tokenResponse.code, tokenResponse.response); - cy.get('button[type=submit]').click(); - verifyIn2MinutesEmailSentPage(); - }, - ); - break; - case 'RECOVERY': - case 'PASSWORD_EXPIRED': - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userResponse.code, response); - cy.mockNext( - resetPasswordResponse.code, - resetPasswordResponse.response, - ); - cy.get('button[type=submit]').click(); - verifyIn2MinutesEmailSentPage(); - }, - ); - break; - } - }); - context('When I submit the form on /reset-password/email-sent', () => { - beforeEach(() => { - // We mock the encrypted state cookie here to trick the endpoint - // into thinking we've just gone through the preceeding flow. - // For readEncryptedStateCookie to succeed, it relies on a testing - // env variable to be set, otherwise it won't be able to read the cookie. - cy.setEncryptedStateCookie({ - email: 'example@example.com', - status: String(status), - }); - cy.visit(`/reset-password/email-sent`); - }); - switch (status) { - case false: - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - cy.mockNext(userNotFoundError.code, userNotFoundError.response); - cy.get('button[type=submit]').click(); - verifyIn2MinutesEmailSentPage(); - }, - ); - break; - case 'ACTIVE': - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userResponse.code, response); - // forgotPassword() - cy.mockNext(200, { - resetPasswordUrl: - 'https://example.com/signin/reset-password/XE6wE17zmphl3KqAPFxO', - }); - cy.get('button[type=submit]').click(); - verifyIn2MinutesEmailSentPage(); - }, - ); - specify( - "Then I should be shown the 'Check your email inbox' page for social user", - () => { - setupMocksForSocialUserPasswordReset(); - cy.get('button[type=submit]').click(); - verifyIn2MinutesEmailSentPage(); - }, - ); - break; - case 'PROVISIONED': - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userResponse.code, response); - cy.mockNext(tokenResponse.code, tokenResponse.response); - cy.get('button[type=submit]').click(); - verifyIn2MinutesEmailSentPage(); - }, - ); - break; - case 'STAGED': - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userResponse.code, response); - cy.mockNext(tokenResponse.code, tokenResponse.response); - cy.get('button[type=submit]').click(); - verifyIn2MinutesEmailSentPage(); - }, - ); - break; - case 'RECOVERY': - case 'PASSWORD_EXPIRED': - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userResponse.code, response); - cy.mockNext( - resetPasswordResponse.code, - resetPasswordResponse.response, - ); - cy.get('button[type=submit]').click(); - verifyIn2MinutesEmailSentPage(); - }, - ); - break; - } - }); - context('When I submit the form on /reset-password/resend', () => { - beforeEach(() => { - cy.visit(`/reset-password/resend`); - cy.get('input[name="email"]').type('example@example.com'); - }); - switch (status) { - case false: - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - cy.mockNext(userNotFoundError.code, userNotFoundError.response); - cy.get('button[type=submit]').click(); - verifyIn2MinutesEmailSentPage(); - }, - ); - break; - case 'ACTIVE': - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userResponse.code, response); - // forgotPassword() - cy.mockNext(200, { - resetPasswordUrl: - 'https://example.com/signin/reset-password/XE6wE17zmphl3KqAPFxO', - }); - cy.get('button[type=submit]').click(); - verifyIn2MinutesEmailSentPage(); - }, - ); - specify( - "Then I should be shown the 'Check your email inbox' page for social user", - () => { - setupMocksForSocialUserPasswordReset(); - cy.get('button[type=submit]').click(); - verifyIn2MinutesEmailSentPage(); - }, - ); - break; - case 'PROVISIONED': - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userResponse.code, response); - cy.mockNext(tokenResponse.code, tokenResponse.response); - cy.get('button[type=submit]').click(); - verifyIn2MinutesEmailSentPage(); - }, - ); - break; - case 'STAGED': - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userResponse.code, response); - cy.mockNext(tokenResponse.code, tokenResponse.response); - cy.get('button[type=submit]').click(); - verifyIn2MinutesEmailSentPage(); - }, - ); - break; - case 'RECOVERY': - case 'PASSWORD_EXPIRED': - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userResponse.code, response); - cy.mockNext( - resetPasswordResponse.code, - resetPasswordResponse.response, - ); - cy.get('button[type=submit]').click(); - verifyIn2MinutesEmailSentPage(); - }, - ); - break; - } - }); - context('When I submit the form on /reset-password/expired', () => { - beforeEach(() => { - cy.visit(`/reset-password/expired`); - cy.get('input[name="email"]').type('example@example.com'); - }); - switch (status) { - case false: - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - cy.mockNext(userNotFoundError.code, userNotFoundError.response); - cy.get('button[type=submit]').click(); - verifyIn2MinutesEmailSentPage(); - }, - ); - break; - case 'ACTIVE': - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userResponse.code, response); - // forgotPassword() - cy.mockNext(200, { - resetPasswordUrl: - 'https://example.com/signin/reset-password/XE6wE17zmphl3KqAPFxO', - }); - cy.get('button[type=submit]').click(); - verifyIn2MinutesEmailSentPage(); - }, - ); - specify( - "Then I should be shown the 'Check your email inbox' page for social user", - () => { - setupMocksForSocialUserPasswordReset(); - cy.get('button[type=submit]').click(); - verifyIn2MinutesEmailSentPage(); - }, - ); - break; - case 'PROVISIONED': - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userResponse.code, response); - cy.mockNext(tokenResponse.code, tokenResponse.response); - cy.get('button[type=submit]').click(); - verifyIn2MinutesEmailSentPage(); - }, - ); - break; - case 'STAGED': - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userResponse.code, response); - cy.mockNext(tokenResponse.code, tokenResponse.response); - cy.get('button[type=submit]').click(); - verifyIn2MinutesEmailSentPage(); - }, - ); - break; - case 'RECOVERY': - case 'PASSWORD_EXPIRED': - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userResponse.code, response); - cy.mockNext( - resetPasswordResponse.code, - resetPasswordResponse.response, - ); - cy.get('button[type=submit]').click(); - verifyIn2MinutesEmailSentPage(); - }, - ); - break; - } - }); - context('When I submit the form on /set-password/email-sent', () => { - beforeEach(() => { - // We mock the encrypted state cookie here to trick the endpoint - // into thinking we've just gone through the preceeding flow. - // For readEncryptedStateCookie to succeed, it relies on a testing - // env variable to be set, otherwise it won't be able to read the cookie. - cy.setEncryptedStateCookie({ - email: 'example@example.com', - status: String(status), - }); - cy.visit(`/set-password/email-sent`); - }); - switch (status) { - case false: - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - cy.mockNext(userNotFoundError.code, userNotFoundError.response); - cy.get('button[type=submit]').click(); - verifyInRegularEmailSentPage(); - }, - ); - break; - case 'ACTIVE': - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userResponse.code, response); - // forgotPassword() - cy.mockNext(200, { - resetPasswordUrl: - 'https://example.com/signin/reset-password/XE6wE17zmphl3KqAPFxO', - }); - cy.get('button[type=submit]').click(); - verifyInRegularEmailSentPage(); - }, - ); - specify( - "Then I should be shown the 'Check your email inbox' page for social user", - () => { - setupMocksForSocialUserPasswordReset(); - cy.get('button[type=submit]').click(); - verifyInRegularEmailSentPage(); - }, - ); - break; - case 'PROVISIONED': - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userResponse.code, response); - cy.mockNext(tokenResponse.code, tokenResponse.response); - cy.get('button[type=submit]').click(); - verifyInRegularEmailSentPage(); - }, - ); - break; - case 'STAGED': - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userResponse.code, response); - cy.mockNext(tokenResponse.code, tokenResponse.response); - cy.get('button[type=submit]').click(); - verifyInRegularEmailSentPage(); - }, - ); - break; - case 'RECOVERY': - case 'PASSWORD_EXPIRED': - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userResponse.code, response); - cy.mockNext( - resetPasswordResponse.code, - resetPasswordResponse.response, - ); - cy.get('button[type=submit]').click(); - verifyInRegularEmailSentPage(); - }, - ); - break; - } - }); - context('When I submit the form on /set-password/resend', () => { - beforeEach(() => { - cy.visit(`/set-password/resend`); - cy.get('input[name="email"]').type('example@example.com'); - }); - switch (status) { - case false: - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - cy.mockNext(userNotFoundError.code, userNotFoundError.response); - cy.get('button[type=submit]').click(); - verifyInRegularEmailSentPage(); - }, - ); - break; - case 'ACTIVE': - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userResponse.code, response); - // forgotPassword() - cy.mockNext(200, { - resetPasswordUrl: - 'https://example.com/signin/reset-password/XE6wE17zmphl3KqAPFxO', - }); - cy.get('button[type=submit]').click(); - verifyInRegularEmailSentPage(); - }, - ); - specify( - "Then I should be shown the 'Check your email inbox' page for social user", - () => { - setupMocksForSocialUserPasswordReset(); - cy.get('button[type=submit]').click(); - verifyInRegularEmailSentPage(); - }, - ); - break; - case 'PROVISIONED': - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userResponse.code, response); - cy.mockNext(tokenResponse.code, tokenResponse.response); - cy.get('button[type=submit]').click(); - verifyInRegularEmailSentPage(); - }, - ); - break; - case 'STAGED': - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userResponse.code, response); - cy.mockNext(tokenResponse.code, tokenResponse.response); - cy.get('button[type=submit]').click(); - verifyInRegularEmailSentPage(); - }, - ); - break; - case 'RECOVERY': - case 'PASSWORD_EXPIRED': - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userResponse.code, response); - cy.mockNext( - resetPasswordResponse.code, - resetPasswordResponse.response, - ); - cy.get('button[type=submit]').click(); - verifyInRegularEmailSentPage(); - }, - ); - break; - } - }); + context(`Given I am a ${status || 'nonexistent'} user`, () => { + context('When I submit the form on /reset-password', () => { + beforeEach(() => { + cy.visit(`/reset-password`); + cy.get('input[name="email"]').type('example@example.com'); + }); + switch (status) { + case false: + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + cy.mockNext(userNotFoundError.code, userNotFoundError.response); + cy.get('button[type=submit]').click(); + verifyIn2MinutesEmailSentPage(); + }, + ); + break; + case 'ACTIVE': + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userResponse.code, response); + // forgotPassword() + cy.mockNext(200, { + resetPasswordUrl: + 'https://example.com/signin/reset-password/XE6wE17zmphl3KqAPFxO', + }); + cy.get('button[type=submit]').click(); + verifyIn2MinutesEmailSentPage(); + }, + ); + specify( + "Then I should be shown the 'Check your email inbox' page for social user", + () => { + setupMocksForSocialUserPasswordReset(); + cy.get('button[type=submit]').click(); + verifyIn2MinutesEmailSentPage(); + }, + ); + break; + case 'PROVISIONED': + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userResponse.code, response); + cy.mockNext(tokenResponse.code, tokenResponse.response); + cy.get('button[type=submit]').click(); + verifyIn2MinutesEmailSentPage(); + }, + ); + break; + case 'STAGED': + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userResponse.code, response); + cy.mockNext(tokenResponse.code, tokenResponse.response); + cy.get('button[type=submit]').click(); + verifyIn2MinutesEmailSentPage(); + }, + ); + break; + case 'RECOVERY': + case 'PASSWORD_EXPIRED': + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userResponse.code, response); + cy.mockNext( + resetPasswordResponse.code, + resetPasswordResponse.response, + ); + cy.get('button[type=submit]').click(); + verifyIn2MinutesEmailSentPage(); + }, + ); + break; + } + }); + context('When I submit the form on /reset-password/email-sent', () => { + beforeEach(() => { + // We mock the encrypted state cookie here to trick the endpoint + // into thinking we've just gone through the preceeding flow. + // For readEncryptedStateCookie to succeed, it relies on a testing + // env variable to be set, otherwise it won't be able to read the cookie. + cy.setEncryptedStateCookie({ + email: 'example@example.com', + status: String(status), + }); + cy.visit(`/reset-password/email-sent`); + }); + switch (status) { + case false: + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + cy.mockNext(userNotFoundError.code, userNotFoundError.response); + cy.get('button[type=submit]').click(); + verifyIn2MinutesEmailSentPage(); + }, + ); + break; + case 'ACTIVE': + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userResponse.code, response); + // forgotPassword() + cy.mockNext(200, { + resetPasswordUrl: + 'https://example.com/signin/reset-password/XE6wE17zmphl3KqAPFxO', + }); + cy.get('button[type=submit]').click(); + verifyIn2MinutesEmailSentPage(); + }, + ); + specify( + "Then I should be shown the 'Check your email inbox' page for social user", + () => { + setupMocksForSocialUserPasswordReset(); + cy.get('button[type=submit]').click(); + verifyIn2MinutesEmailSentPage(); + }, + ); + break; + case 'PROVISIONED': + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userResponse.code, response); + cy.mockNext(tokenResponse.code, tokenResponse.response); + cy.get('button[type=submit]').click(); + verifyIn2MinutesEmailSentPage(); + }, + ); + break; + case 'STAGED': + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userResponse.code, response); + cy.mockNext(tokenResponse.code, tokenResponse.response); + cy.get('button[type=submit]').click(); + verifyIn2MinutesEmailSentPage(); + }, + ); + break; + case 'RECOVERY': + case 'PASSWORD_EXPIRED': + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userResponse.code, response); + cy.mockNext( + resetPasswordResponse.code, + resetPasswordResponse.response, + ); + cy.get('button[type=submit]').click(); + verifyIn2MinutesEmailSentPage(); + }, + ); + break; + } + }); + context('When I submit the form on /reset-password/resend', () => { + beforeEach(() => { + cy.visit(`/reset-password/resend`); + cy.get('input[name="email"]').type('example@example.com'); + }); + switch (status) { + case false: + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + cy.mockNext(userNotFoundError.code, userNotFoundError.response); + cy.get('button[type=submit]').click(); + verifyIn2MinutesEmailSentPage(); + }, + ); + break; + case 'ACTIVE': + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userResponse.code, response); + // forgotPassword() + cy.mockNext(200, { + resetPasswordUrl: + 'https://example.com/signin/reset-password/XE6wE17zmphl3KqAPFxO', + }); + cy.get('button[type=submit]').click(); + verifyIn2MinutesEmailSentPage(); + }, + ); + specify( + "Then I should be shown the 'Check your email inbox' page for social user", + () => { + setupMocksForSocialUserPasswordReset(); + cy.get('button[type=submit]').click(); + verifyIn2MinutesEmailSentPage(); + }, + ); + break; + case 'PROVISIONED': + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userResponse.code, response); + cy.mockNext(tokenResponse.code, tokenResponse.response); + cy.get('button[type=submit]').click(); + verifyIn2MinutesEmailSentPage(); + }, + ); + break; + case 'STAGED': + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userResponse.code, response); + cy.mockNext(tokenResponse.code, tokenResponse.response); + cy.get('button[type=submit]').click(); + verifyIn2MinutesEmailSentPage(); + }, + ); + break; + case 'RECOVERY': + case 'PASSWORD_EXPIRED': + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userResponse.code, response); + cy.mockNext( + resetPasswordResponse.code, + resetPasswordResponse.response, + ); + cy.get('button[type=submit]').click(); + verifyIn2MinutesEmailSentPage(); + }, + ); + break; + } + }); + context('When I submit the form on /reset-password/expired', () => { + beforeEach(() => { + cy.visit(`/reset-password/expired`); + cy.get('input[name="email"]').type('example@example.com'); + }); + switch (status) { + case false: + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + cy.mockNext(userNotFoundError.code, userNotFoundError.response); + cy.get('button[type=submit]').click(); + verifyIn2MinutesEmailSentPage(); + }, + ); + break; + case 'ACTIVE': + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userResponse.code, response); + // forgotPassword() + cy.mockNext(200, { + resetPasswordUrl: + 'https://example.com/signin/reset-password/XE6wE17zmphl3KqAPFxO', + }); + cy.get('button[type=submit]').click(); + verifyIn2MinutesEmailSentPage(); + }, + ); + specify( + "Then I should be shown the 'Check your email inbox' page for social user", + () => { + setupMocksForSocialUserPasswordReset(); + cy.get('button[type=submit]').click(); + verifyIn2MinutesEmailSentPage(); + }, + ); + break; + case 'PROVISIONED': + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userResponse.code, response); + cy.mockNext(tokenResponse.code, tokenResponse.response); + cy.get('button[type=submit]').click(); + verifyIn2MinutesEmailSentPage(); + }, + ); + break; + case 'STAGED': + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userResponse.code, response); + cy.mockNext(tokenResponse.code, tokenResponse.response); + cy.get('button[type=submit]').click(); + verifyIn2MinutesEmailSentPage(); + }, + ); + break; + case 'RECOVERY': + case 'PASSWORD_EXPIRED': + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userResponse.code, response); + cy.mockNext( + resetPasswordResponse.code, + resetPasswordResponse.response, + ); + cy.get('button[type=submit]').click(); + verifyIn2MinutesEmailSentPage(); + }, + ); + break; + } + }); + context('When I submit the form on /set-password/email-sent', () => { + beforeEach(() => { + // We mock the encrypted state cookie here to trick the endpoint + // into thinking we've just gone through the preceeding flow. + // For readEncryptedStateCookie to succeed, it relies on a testing + // env variable to be set, otherwise it won't be able to read the cookie. + cy.setEncryptedStateCookie({ + email: 'example@example.com', + status: String(status), + }); + cy.visit(`/set-password/email-sent`); + }); + switch (status) { + case false: + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + cy.mockNext(userNotFoundError.code, userNotFoundError.response); + cy.get('button[type=submit]').click(); + verifyInRegularEmailSentPage(); + }, + ); + break; + case 'ACTIVE': + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userResponse.code, response); + // forgotPassword() + cy.mockNext(200, { + resetPasswordUrl: + 'https://example.com/signin/reset-password/XE6wE17zmphl3KqAPFxO', + }); + cy.get('button[type=submit]').click(); + verifyInRegularEmailSentPage(); + }, + ); + specify( + "Then I should be shown the 'Check your email inbox' page for social user", + () => { + setupMocksForSocialUserPasswordReset(); + cy.get('button[type=submit]').click(); + verifyInRegularEmailSentPage(); + }, + ); + break; + case 'PROVISIONED': + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userResponse.code, response); + cy.mockNext(tokenResponse.code, tokenResponse.response); + cy.get('button[type=submit]').click(); + verifyInRegularEmailSentPage(); + }, + ); + break; + case 'STAGED': + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userResponse.code, response); + cy.mockNext(tokenResponse.code, tokenResponse.response); + cy.get('button[type=submit]').click(); + verifyInRegularEmailSentPage(); + }, + ); + break; + case 'RECOVERY': + case 'PASSWORD_EXPIRED': + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userResponse.code, response); + cy.mockNext( + resetPasswordResponse.code, + resetPasswordResponse.response, + ); + cy.get('button[type=submit]').click(); + verifyInRegularEmailSentPage(); + }, + ); + break; + } + }); + context('When I submit the form on /set-password/resend', () => { + beforeEach(() => { + cy.visit(`/set-password/resend`); + cy.get('input[name="email"]').type('example@example.com'); + }); + switch (status) { + case false: + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + cy.mockNext(userNotFoundError.code, userNotFoundError.response); + cy.get('button[type=submit]').click(); + verifyInRegularEmailSentPage(); + }, + ); + break; + case 'ACTIVE': + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userResponse.code, response); + // forgotPassword() + cy.mockNext(200, { + resetPasswordUrl: + 'https://example.com/signin/reset-password/XE6wE17zmphl3KqAPFxO', + }); + cy.get('button[type=submit]').click(); + verifyInRegularEmailSentPage(); + }, + ); + specify( + "Then I should be shown the 'Check your email inbox' page for social user", + () => { + setupMocksForSocialUserPasswordReset(); + cy.get('button[type=submit]').click(); + verifyInRegularEmailSentPage(); + }, + ); + break; + case 'PROVISIONED': + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userResponse.code, response); + cy.mockNext(tokenResponse.code, tokenResponse.response); + cy.get('button[type=submit]').click(); + verifyInRegularEmailSentPage(); + }, + ); + break; + case 'STAGED': + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userResponse.code, response); + cy.mockNext(tokenResponse.code, tokenResponse.response); + cy.get('button[type=submit]').click(); + verifyInRegularEmailSentPage(); + }, + ); + break; + case 'RECOVERY': + case 'PASSWORD_EXPIRED': + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userResponse.code, response); + cy.mockNext( + resetPasswordResponse.code, + resetPasswordResponse.response, + ); + cy.get('button[type=submit]').click(); + verifyInRegularEmailSentPage(); + }, + ); + break; + } + }); - context('When I submit the form on /set-password/expired', () => { - beforeEach(() => { - cy.visit(`/set-password/expired`); - cy.get('input[name="email"]').type('example@example.com'); - }); - switch (status) { - case false: - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - cy.mockNext(userNotFoundError.code, userNotFoundError.response); - cy.get('button[type=submit]').click(); - verifyInRegularEmailSentPage(); - }, - ); - break; - case 'ACTIVE': - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userResponse.code, response); - // forgotPassword() - cy.mockNext(200, { - resetPasswordUrl: - 'https://example.com/signin/reset-password/XE6wE17zmphl3KqAPFxO', - }); - cy.get('button[type=submit]').click(); - verifyInRegularEmailSentPage(); - }, - ); - specify( - "Then I should be shown the 'Check your email inbox' page for social user", - () => { - setupMocksForSocialUserPasswordReset(); - cy.get('button[type=submit]').click(); - verifyInRegularEmailSentPage(); - }, - ); - break; - case 'PROVISIONED': - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userResponse.code, response); - cy.mockNext(tokenResponse.code, tokenResponse.response); - cy.get('button[type=submit]').click(); - verifyInRegularEmailSentPage(); - }, - ); - break; - case 'STAGED': - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userResponse.code, response); - cy.mockNext(tokenResponse.code, tokenResponse.response); - cy.get('button[type=submit]').click(); - verifyInRegularEmailSentPage(); - }, - ); - break; - case 'RECOVERY': - case 'PASSWORD_EXPIRED': - specify( - "Then I should be shown the 'Check your email inbox' page", - () => { - // Set the correct user status on the response - const response = { ...userResponse.response, status }; - cy.mockNext(userResponse.code, response); - cy.mockNext( - resetPasswordResponse.code, - resetPasswordResponse.response, - ); - cy.get('button[type=submit]').click(); - verifyInRegularEmailSentPage(); - }, - ); - break; - } - }); - }); + context('When I submit the form on /set-password/expired', () => { + beforeEach(() => { + cy.visit(`/set-password/expired`); + cy.get('input[name="email"]').type('example@example.com'); + }); + switch (status) { + case false: + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + cy.mockNext(userNotFoundError.code, userNotFoundError.response); + cy.get('button[type=submit]').click(); + verifyInRegularEmailSentPage(); + }, + ); + break; + case 'ACTIVE': + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userResponse.code, response); + // forgotPassword() + cy.mockNext(200, { + resetPasswordUrl: + 'https://example.com/signin/reset-password/XE6wE17zmphl3KqAPFxO', + }); + cy.get('button[type=submit]').click(); + verifyInRegularEmailSentPage(); + }, + ); + specify( + "Then I should be shown the 'Check your email inbox' page for social user", + () => { + setupMocksForSocialUserPasswordReset(); + cy.get('button[type=submit]').click(); + verifyInRegularEmailSentPage(); + }, + ); + break; + case 'PROVISIONED': + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userResponse.code, response); + cy.mockNext(tokenResponse.code, tokenResponse.response); + cy.get('button[type=submit]').click(); + verifyInRegularEmailSentPage(); + }, + ); + break; + case 'STAGED': + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userResponse.code, response); + cy.mockNext(tokenResponse.code, tokenResponse.response); + cy.get('button[type=submit]').click(); + verifyInRegularEmailSentPage(); + }, + ); + break; + case 'RECOVERY': + case 'PASSWORD_EXPIRED': + specify( + "Then I should be shown the 'Check your email inbox' page", + () => { + // Set the correct user status on the response + const response = { ...userResponse.response, status }; + cy.mockNext(userResponse.code, response); + cy.mockNext( + resetPasswordResponse.code, + resetPasswordResponse.response, + ); + cy.get('button[type=submit]').click(); + verifyInRegularEmailSentPage(); + }, + ); + break; + } + }); + }); }); diff --git a/cypress/integration/mocked-okta/signInController.2.cy.ts b/cypress/integration/mocked-okta/signInController.2.cy.ts index 4212dcf780..4ddd9ca0d7 100644 --- a/cypress/integration/mocked-okta/signInController.2.cy.ts +++ b/cypress/integration/mocked-okta/signInController.2.cy.ts @@ -3,83 +3,83 @@ import validUserResponse from '../../fixtures/okta-responses/success/valid-user. import validUserGroupsResponse from '../../fixtures/okta-responses/success/valid-user-groups.json'; beforeEach(() => { - cy.mockPurge(); + cy.mockPurge(); }); context('When I submit the form on /signin', () => { - beforeEach(() => { - cy.visit(`/signin`); - cy.get('input[name="email"]').type('example@example.com'); - cy.get('input[name="password"]').type('password'); - }); - //This is expected for non existent, non active users or when the email and password are incorrect - specify( - 'if okta authentication fails then I should be shown an "Email or password incorrect" error. ', - () => { - cy.mockNext( - authenticationFailedError.code, - authenticationFailedError.response, - ); - cy.get('button[type=submit]').click(); - cy.contains("Email and password don't match"); - }, - ); - specify('if okta authentication succeeds then I should be signed in.', () => { - cy.mockNext(validUserResponse.code, validUserResponse.response); - cy.mockNext(validUserGroupsResponse.code, validUserGroupsResponse.response); + beforeEach(() => { + cy.visit(`/signin`); + cy.get('input[name="email"]').type('example@example.com'); + cy.get('input[name="password"]').type('password'); + }); + //This is expected for non existent, non active users or when the email and password are incorrect + specify( + 'if okta authentication fails then I should be shown an "Email or password incorrect" error. ', + () => { + cy.mockNext( + authenticationFailedError.code, + authenticationFailedError.response, + ); + cy.get('button[type=submit]').click(); + cy.contains("Email and password don't match"); + }, + ); + specify('if okta authentication succeeds then I should be signed in.', () => { + cy.mockNext(validUserResponse.code, validUserResponse.response); + cy.mockNext(validUserGroupsResponse.code, validUserGroupsResponse.response); - cy.get('button[type=submit]').click(); - // we can't actually check the authorization code flow - // so simply intercept the request and verify that it contains - // the mocked response - cy.intercept( - `http://localhost:9000/oauth2/${Cypress.env( - 'OKTA_CUSTOM_OAUTH_SERVER', - )}/v1/authorize*`, - 'OAuth', - ); - cy.contains('OAuth'); - }); + cy.get('button[type=submit]').click(); + // we can't actually check the authorization code flow + // so simply intercept the request and verify that it contains + // the mocked response + cy.intercept( + `http://localhost:9000/oauth2/${Cypress.env( + 'OKTA_CUSTOM_OAUTH_SERVER', + )}/v1/authorize*`, + 'OAuth', + ); + cy.contains('OAuth'); + }); }); context('When I submit the form on /reauthenticate', () => { - beforeEach(() => { - cy.visit(`/reauthenticate`); - cy.get('input[name="email"]').type('example@example.com'); - cy.get('input[name="password"]').type('password'); - }); - //This is expected for non existent, non active users or when the email and password are incorrect - specify( - 'If okta authentication fails then I should be shown an "Email or password incorrect" error.', - () => { - cy.mockNext( - authenticationFailedError.code, - authenticationFailedError.response, - ); - cy.get('button[type=submit]').click(); - cy.contains("Email and password don't match"); - }, - ); + beforeEach(() => { + cy.visit(`/reauthenticate`); + cy.get('input[name="email"]').type('example@example.com'); + cy.get('input[name="password"]').type('password'); + }); + //This is expected for non existent, non active users or when the email and password are incorrect + specify( + 'If okta authentication fails then I should be shown an "Email or password incorrect" error.', + () => { + cy.mockNext( + authenticationFailedError.code, + authenticationFailedError.response, + ); + cy.get('button[type=submit]').click(); + cy.contains("Email and password don't match"); + }, + ); - specify( - 'If okta authentication succeeeds then I should be signed in.', - () => { - cy.mockNext(validUserResponse.code, validUserResponse.response); - cy.mockNext( - validUserGroupsResponse.code, - validUserGroupsResponse.response, - ); + specify( + 'If okta authentication succeeeds then I should be signed in.', + () => { + cy.mockNext(validUserResponse.code, validUserResponse.response); + cy.mockNext( + validUserGroupsResponse.code, + validUserGroupsResponse.response, + ); - cy.get('button[type=submit]').click(); - // we can't actually check the authorization code flow - // so simply intercept the request and verify that it contains - // the mocked response - cy.intercept( - `http://localhost:9000/oauth2/${Cypress.env( - 'OKTA_CUSTOM_OAUTH_SERVER', - )}/v1/authorize*`, - 'OAuth', - ); - cy.contains('OAuth'); - }, - ); + cy.get('button[type=submit]').click(); + // we can't actually check the authorization code flow + // so simply intercept the request and verify that it contains + // the mocked response + cy.intercept( + `http://localhost:9000/oauth2/${Cypress.env( + 'OKTA_CUSTOM_OAUTH_SERVER', + )}/v1/authorize*`, + 'OAuth', + ); + cy.contains('OAuth'); + }, + ); }); diff --git a/cypress/integration/mocked/change_email.5.cy.ts b/cypress/integration/mocked/change_email.5.cy.ts index 093f0686b3..1d9c87ae42 100644 --- a/cypress/integration/mocked/change_email.5.cy.ts +++ b/cypress/integration/mocked/change_email.5.cy.ts @@ -1,52 +1,52 @@ import { injectAndCheckAxe } from '../../support/cypress-axe'; describe('Change email', () => { - beforeEach(() => { - cy.mockPurge(); - }); + beforeEach(() => { + cy.mockPurge(); + }); - context('a11y checks', () => { - it('Has no detectable a11y violations on change email complete page', () => { - cy.visit('/change-email/complete'); - injectAndCheckAxe(); - }); + context('a11y checks', () => { + it('Has no detectable a11y violations on change email complete page', () => { + cy.visit('/change-email/complete'); + injectAndCheckAxe(); + }); - it('Has no detectable a11y violations on change email error page', () => { - cy.mockNext(500, { - status: 'error', - errors: [ - { - message: 'Invalid token', - }, - ], - }); - cy.visit('/change-email/token'); - injectAndCheckAxe(); - }); - }); + it('Has no detectable a11y violations on change email error page', () => { + cy.mockNext(500, { + status: 'error', + errors: [ + { + message: 'Invalid token', + }, + ], + }); + cy.visit('/change-email/token'); + injectAndCheckAxe(); + }); + }); - context('change email flow', () => { - it('should be able to change email', () => { - cy.mockNext(200, { - status: 'ok', - }); - cy.visit('/change-email/token'); - cy.contains('Success! Your email address has been updated.'); - }); + context('change email flow', () => { + it('should be able to change email', () => { + cy.mockNext(200, { + status: 'ok', + }); + cy.visit('/change-email/token'); + cy.contains('Success! Your email address has been updated.'); + }); - it('should be able to handle a change email error', () => { - cy.mockNext(500, { - status: 'error', - errors: [ - { - message: 'Invalid token', - }, - ], - }); - cy.visit('/change-email/token'); - cy.contains( - 'The email change link you followed has expired or was invalid.', - ); - }); - }); + it('should be able to handle a change email error', () => { + cy.mockNext(500, { + status: 'error', + errors: [ + { + message: 'Invalid token', + }, + ], + }); + cy.visit('/change-email/token'); + cy.contains( + 'The email change link you followed has expired or was invalid.', + ); + }); + }); }); diff --git a/cypress/integration/mocked/change_password.3.cy.ts b/cypress/integration/mocked/change_password.3.cy.ts index 7085ffc572..2253f5d577 100644 --- a/cypress/integration/mocked/change_password.3.cy.ts +++ b/cypress/integration/mocked/change_password.3.cy.ts @@ -1,352 +1,352 @@ import { injectAndCheckAxe } from '../../support/cypress-axe'; describe('Password change flow', () => { - const fakeValidationResponse = ( - timeUntilExpiry: number | undefined = undefined, - ) => ({ - user: { - primaryEmailAddress: 'name@example.com', - }, - timeUntilExpiry, - }); + const fakeValidationResponse = ( + timeUntilExpiry: number | undefined = undefined, + ) => ({ + user: { + primaryEmailAddress: 'name@example.com', + }, + timeUntilExpiry, + }); - const fakeSuccessResponse = { - cookies: { - values: [ - { - key: 'GU_U', - value: 'FAKE_VALUE_0', - }, - { - key: 'SC_GU_LA', - value: 'FAKE_VALUE_1', - sessionCookie: true, - }, - { - key: 'SC_GU_U', - value: 'FAKE_VALUE_2', - }, - ], - expiresAt: 1, - }, - }; + const fakeSuccessResponse = { + cookies: { + values: [ + { + key: 'GU_U', + value: 'FAKE_VALUE_0', + }, + { + key: 'SC_GU_LA', + value: 'FAKE_VALUE_1', + sessionCookie: true, + }, + { + key: 'SC_GU_U', + value: 'FAKE_VALUE_2', + }, + ], + expiresAt: 1, + }, + }; - beforeEach(() => { - cy.mockPurge(); - }); + beforeEach(() => { + cy.mockPurge(); + }); - context('A11y checks', () => { - it('Has no detectable a11y violations on resend password page', () => { - cy.mockNext(500, { - status: 'error', - errors: [ - { - message: 'Invalid token', - }, - ], - }); - cy.visit(`/reset-password/fake_token?useIdapi=true`); - injectAndCheckAxe(); - }); + context('A11y checks', () => { + it('Has no detectable a11y violations on resend password page', () => { + cy.mockNext(500, { + status: 'error', + errors: [ + { + message: 'Invalid token', + }, + ], + }); + cy.visit(`/reset-password/fake_token?useIdapi=true`); + injectAndCheckAxe(); + }); - it('Has no detectable a11y violations on change password page', () => { - cy.mockNext(200); - cy.mockNext(200, fakeSuccessResponse); - cy.visit(`/reset-password/fake_token?useIdapi=true`); - injectAndCheckAxe(); - }); + it('Has no detectable a11y violations on change password page', () => { + cy.mockNext(200); + cy.mockNext(200, fakeSuccessResponse); + cy.visit(`/reset-password/fake_token?useIdapi=true`); + injectAndCheckAxe(); + }); - it('Has no detectable a11y violations on change password page with error', () => { - cy.mockNext(200); - cy.visit(`/reset-password/fake_token?useIdapi=true`); - cy.get('input[name="password"]').type('short'); - cy.get('button[type="submit"]').click(); - injectAndCheckAxe(); - }); + it('Has no detectable a11y violations on change password page with error', () => { + cy.mockNext(200); + cy.visit(`/reset-password/fake_token?useIdapi=true`); + cy.get('input[name="password"]').type('short'); + cy.get('button[type="submit"]').click(); + injectAndCheckAxe(); + }); - it('Has no detectable a11y violations on change password complete page', () => { - cy.mockNext(200); - cy.mockNext(200, fakeSuccessResponse); - cy.intercept({ - method: 'GET', - url: 'https://api.pwnedpasswords.com/range/*', - }).as('breachCheck'); - cy.visit(`/reset-password/fake_token?useIdapi=true`); - cy.get('input[name="password"]').type('thisisalongandunbreachedpassword'); - cy.wait('@breachCheck'); - cy.get('button[type="submit"]').click(); - injectAndCheckAxe(); - }); - }); + it('Has no detectable a11y violations on change password complete page', () => { + cy.mockNext(200); + cy.mockNext(200, fakeSuccessResponse); + cy.intercept({ + method: 'GET', + url: 'https://api.pwnedpasswords.com/range/*', + }).as('breachCheck'); + cy.visit(`/reset-password/fake_token?useIdapi=true`); + cy.get('input[name="password"]').type('thisisalongandunbreachedpassword'); + cy.wait('@breachCheck'); + cy.get('button[type="submit"]').click(); + injectAndCheckAxe(); + }); + }); - context('show / hide password eye button', () => { - it('clicking on the password eye shows the password and clicking it again hides it', () => { - cy.mockNext(200); - cy.visit(`/reset-password/fake_token?useIdapi=true`); - cy.get('input[name="password"]').should('have.attr', 'type', 'password'); - cy.get('input[name="password"]').type('some_password'); - cy.get('[data-cy=password-input-eye-button]').click(); - cy.get('input[name="password"]').should('have.attr', 'type', 'text'); - cy.get('[data-cy=password-input-eye-button]').click(); - cy.get('input[name="password"]').should('have.attr', 'type', 'password'); - }); - }); + context('show / hide password eye button', () => { + it('clicking on the password eye shows the password and clicking it again hides it', () => { + cy.mockNext(200); + cy.visit(`/reset-password/fake_token?useIdapi=true`); + cy.get('input[name="password"]').should('have.attr', 'type', 'password'); + cy.get('input[name="password"]').type('some_password'); + cy.get('[data-cy=password-input-eye-button]').click(); + cy.get('input[name="password"]').should('have.attr', 'type', 'text'); + cy.get('[data-cy=password-input-eye-button]').click(); + cy.get('input[name="password"]').should('have.attr', 'type', 'password'); + }); + }); - context('An expired/invalid token is used', () => { - it('shows a resend password page', () => { - cy.mockNext(500, { - status: 'error', - errors: [ - { - message: 'Invalid token', - }, - ], - }); - cy.visit(`/reset-password/fake_token?useIdapi=true`); - cy.contains('Link expired'); - }); + context('An expired/invalid token is used', () => { + it('shows a resend password page', () => { + cy.mockNext(500, { + status: 'error', + errors: [ + { + message: 'Invalid token', + }, + ], + }); + cy.visit(`/reset-password/fake_token?useIdapi=true`); + cy.contains('Link expired'); + }); - it('shows the session time out page if the token expires while on the set password page', () => { - cy.mockNext(200, fakeValidationResponse(1000)); - cy.visit(`/reset-password/fake_token?useIdapi=true`); - cy.contains('Session timed out'); - }); - }); + it('shows the session time out page if the token expires while on the set password page', () => { + cy.mockNext(200, fakeValidationResponse(1000)); + cy.visit(`/reset-password/fake_token?useIdapi=true`); + cy.contains('Session timed out'); + }); + }); - context('Password exists in breach dataset', () => { - it('displays a breached error', () => { - cy.mockNext(200); - cy.mockNext(200, fakeSuccessResponse); - cy.intercept({ - method: 'GET', - url: 'https://api.pwnedpasswords.com/range/*', - }).as('breachCheck'); - cy.visit(`/reset-password/fake_token?useIdapi=true`); - cy.get('input[name="password"]').type('password'); - cy.wait('@breachCheck'); - cy.contains('use a password that is hard to guess'); - cy.get('button[type="submit"]').click().should('not.be.disabled'); - cy.contains('Please use a password that is hard to guess.'); - }); - }); + context('Password exists in breach dataset', () => { + it('displays a breached error', () => { + cy.mockNext(200); + cy.mockNext(200, fakeSuccessResponse); + cy.intercept({ + method: 'GET', + url: 'https://api.pwnedpasswords.com/range/*', + }).as('breachCheck'); + cy.visit(`/reset-password/fake_token?useIdapi=true`); + cy.get('input[name="password"]').type('password'); + cy.wait('@breachCheck'); + cy.contains('use a password that is hard to guess'); + cy.get('button[type="submit"]').click().should('not.be.disabled'); + cy.contains('Please use a password that is hard to guess.'); + }); + }); - context('CSRF token error on submission', () => { - it('should fail on submission due to CSRF token failure if CSRF token cookie is not sent', () => { - cy.mockNext(200); - cy.intercept({ - method: 'GET', - url: 'https://api.pwnedpasswords.com/range/*', - }).as('breachCheck'); - cy.visit(`/reset-password/fake_token?useIdapi=true`); - cy.clearCookie('_csrf'); - cy.get('input[name="password"]').type('thisisalongandunbreachedpassword'); - cy.wait('@breachCheck'); - cy.get('button[type="submit"]').click(); - cy.contains('Please try again.'); - }); - }); + context('CSRF token error on submission', () => { + it('should fail on submission due to CSRF token failure if CSRF token cookie is not sent', () => { + cy.mockNext(200); + cy.intercept({ + method: 'GET', + url: 'https://api.pwnedpasswords.com/range/*', + }).as('breachCheck'); + cy.visit(`/reset-password/fake_token?useIdapi=true`); + cy.clearCookie('_csrf'); + cy.get('input[name="password"]').type('thisisalongandunbreachedpassword'); + cy.wait('@breachCheck'); + cy.get('button[type="submit"]').click(); + cy.contains('Please try again.'); + }); + }); - context('Password field is left blank', () => { - it('uses the standard HTML5 empty field validation', () => { - cy.mockNext(200); - cy.visit(`/reset-password/fake_token?useIdapi=true`); - cy.get('button[type="submit"]').click(); - cy.get('input[name="password"]:invalid').should('have.length', 1); - }); - }); + context('Password field is left blank', () => { + it('uses the standard HTML5 empty field validation', () => { + cy.mockNext(200); + cy.visit(`/reset-password/fake_token?useIdapi=true`); + cy.get('button[type="submit"]').click(); + cy.get('input[name="password"]:invalid').should('have.length', 1); + }); + }); - context('Email shown on page', () => { - it('shows the users email address on the page', () => { - cy.mockNext(200, fakeValidationResponse()); - cy.visit(`/reset-password/fake_token?useIdapi=true`); - cy.contains(fakeValidationResponse().user.primaryEmailAddress); - }); - }); + context('Email shown on page', () => { + it('shows the users email address on the page', () => { + cy.mockNext(200, fakeValidationResponse()); + cy.visit(`/reset-password/fake_token?useIdapi=true`); + cy.contains(fakeValidationResponse().user.primaryEmailAddress); + }); + }); - context('Valid password entered', () => { - it('shows password change success screen, with a default redirect button.', () => { - cy.mockNext(200); - cy.mockNext(200, fakeSuccessResponse); - cy.intercept({ - method: 'GET', - url: 'https://api.pwnedpasswords.com/range/*', - }).as('breachCheck'); - cy.visit(`/reset-password/fake_token?useIdapi=true`); + context('Valid password entered', () => { + it('shows password change success screen, with a default redirect button.', () => { + cy.mockNext(200); + cy.mockNext(200, fakeSuccessResponse); + cy.intercept({ + method: 'GET', + url: 'https://api.pwnedpasswords.com/range/*', + }).as('breachCheck'); + cy.visit(`/reset-password/fake_token?useIdapi=true`); - cy.get('input[name="password"]').type('thisisalongandunbreachedpassword'); - cy.wait('@breachCheck'); - // Submit the button - cy.contains('Valid password'); - cy.get('button[type="submit"]').click(); - cy.contains('Password updated'); - cy.contains('Continue to the Guardian').should( - 'have.attr', - 'href', - `${Cypress.env('DEFAULT_RETURN_URI')}/`, - ); + cy.get('input[name="password"]').type('thisisalongandunbreachedpassword'); + cy.wait('@breachCheck'); + // Submit the button + cy.contains('Valid password'); + cy.get('button[type="submit"]').click(); + cy.contains('Password updated'); + cy.contains('Continue to the Guardian').should( + 'have.attr', + 'href', + `${Cypress.env('DEFAULT_RETURN_URI')}/`, + ); - // Not currently possible to test login cookie, - // Cookie is not set to domain we can access, even in cypress. - // e.g. - // cy.getCookie('GU_U') - // .should('have.property', 'value', 'FAKE_VALUE_0'); - }); + // Not currently possible to test login cookie, + // Cookie is not set to domain we can access, even in cypress. + // e.g. + // cy.getCookie('GU_U') + // .should('have.property', 'value', 'FAKE_VALUE_0'); + }); - it('shows password change success screen, with default redirect button, and users email', () => { - cy.mockNext(200, fakeValidationResponse()); - cy.mockNext(200, fakeSuccessResponse); - cy.intercept({ - method: 'GET', - url: 'https://api.pwnedpasswords.com/range/*', - }).as('breachCheck'); - cy.visit(`/reset-password/fake_token?useIdapi=true`); - cy.contains(fakeValidationResponse().user.primaryEmailAddress); + it('shows password change success screen, with default redirect button, and users email', () => { + cy.mockNext(200, fakeValidationResponse()); + cy.mockNext(200, fakeSuccessResponse); + cy.intercept({ + method: 'GET', + url: 'https://api.pwnedpasswords.com/range/*', + }).as('breachCheck'); + cy.visit(`/reset-password/fake_token?useIdapi=true`); + cy.contains(fakeValidationResponse().user.primaryEmailAddress); - cy.get('input[name="password"]').type('thisisalongandunbreachedpassword'); - cy.wait('@breachCheck'); - cy.get('button[type="submit"]').click(); - cy.contains('Password updated'); - cy.contains('Continue to the Guardian').should( - 'have.attr', - 'href', - `${Cypress.env('DEFAULT_RETURN_URI')}/`, - ); - cy.contains(fakeValidationResponse().user.primaryEmailAddress); - }); - }); + cy.get('input[name="password"]').type('thisisalongandunbreachedpassword'); + cy.wait('@breachCheck'); + cy.get('button[type="submit"]').click(); + cy.contains('Password updated'); + cy.contains('Continue to the Guardian').should( + 'have.attr', + 'href', + `${Cypress.env('DEFAULT_RETURN_URI')}/`, + ); + cy.contains(fakeValidationResponse().user.primaryEmailAddress); + }); + }); - context( - 'Valid password entered and a return url with a Guardian domain is specified.', - () => { - it('shows password change success screen, with a redirect button linking to the return url.', () => { - cy.mockNext(200); - cy.mockNext(200, fakeSuccessResponse); - cy.intercept({ - method: 'GET', - url: 'https://api.pwnedpasswords.com/range/*', - }).as('breachCheck'); - cy.visit( - `/reset-password/fake_token?returnUrl=https://news.theguardian.com&useIdapi=true`, - ); - cy.get('input[name="password"]').type( - 'thisisalongandunbreachedpassword', - ); - cy.wait('@breachCheck'); - cy.get('button[type="submit"]').click(); - cy.contains('Password updated'); - cy.contains('Continue to the Guardian').should( - 'have.attr', - 'href', - 'https://news.theguardian.com/', - ); - }); - }, - ); + context( + 'Valid password entered and a return url with a Guardian domain is specified.', + () => { + it('shows password change success screen, with a redirect button linking to the return url.', () => { + cy.mockNext(200); + cy.mockNext(200, fakeSuccessResponse); + cy.intercept({ + method: 'GET', + url: 'https://api.pwnedpasswords.com/range/*', + }).as('breachCheck'); + cy.visit( + `/reset-password/fake_token?returnUrl=https://news.theguardian.com&useIdapi=true`, + ); + cy.get('input[name="password"]').type( + 'thisisalongandunbreachedpassword', + ); + cy.wait('@breachCheck'); + cy.get('button[type="submit"]').click(); + cy.contains('Password updated'); + cy.contains('Continue to the Guardian').should( + 'have.attr', + 'href', + 'https://news.theguardian.com/', + ); + }); + }, + ); - context( - 'Valid password entered and an return url from a non-Guardian domain is specified.', - () => { - it('shows password change success screen, with a default redirect button.', () => { - cy.mockNext(200); - cy.mockNext(200, fakeSuccessResponse); - cy.intercept({ - method: 'GET', - url: 'https://api.pwnedpasswords.com/range/*', - }).as('breachCheck'); - cy.visit( - `/reset-password/fake_token?returnUrl=https://news.badsite.com&useIdapi=true`, - ); - cy.get('input[name="password"]').type( - 'thisisalongandunbreachedpassword', - ); - cy.wait('@breachCheck'); - cy.get('button[type="submit"]').click(); - cy.contains('Password updated'); - cy.contains('Continue to the Guardian').should( - 'have.attr', - 'href', - `${Cypress.env('DEFAULT_RETURN_URI')}/`, - ); - }); - }, - ); + context( + 'Valid password entered and an return url from a non-Guardian domain is specified.', + () => { + it('shows password change success screen, with a default redirect button.', () => { + cy.mockNext(200); + cy.mockNext(200, fakeSuccessResponse); + cy.intercept({ + method: 'GET', + url: 'https://api.pwnedpasswords.com/range/*', + }).as('breachCheck'); + cy.visit( + `/reset-password/fake_token?returnUrl=https://news.badsite.com&useIdapi=true`, + ); + cy.get('input[name="password"]').type( + 'thisisalongandunbreachedpassword', + ); + cy.wait('@breachCheck'); + cy.get('button[type="submit"]').click(); + cy.contains('Password updated'); + cy.contains('Continue to the Guardian').should( + 'have.attr', + 'href', + `${Cypress.env('DEFAULT_RETURN_URI')}/`, + ); + }); + }, + ); - context('password too short', () => { - it('shows an error showing the password length must be within certain limits', () => { - cy.mockNext(200); - cy.visit(`/reset-password/fake_token?useIdapi=true`); - cy.mockNext(200); - cy.get('input[name="password"]').type('p'); - cy.get('button[type="submit"]').focus(); - // Error is shown before clicking submit - cy.contains('At least 8'); - cy.get('button[type="submit"]').click(); - // Error still exists after clicking submit - cy.contains( - 'Please make sure your password is at least 8 characters long.', - ); - cy.intercept({ - method: 'GET', - url: 'https://api.pwnedpasswords.com/range/*', - }).as('breachCheck'); - cy.get('input[name="password"]').type('iamaveryuniqueandlongstring'); - cy.wait('@breachCheck'); - cy.contains('Valid password'); - }); - }); + context('password too short', () => { + it('shows an error showing the password length must be within certain limits', () => { + cy.mockNext(200); + cy.visit(`/reset-password/fake_token?useIdapi=true`); + cy.mockNext(200); + cy.get('input[name="password"]').type('p'); + cy.get('button[type="submit"]').focus(); + // Error is shown before clicking submit + cy.contains('At least 8'); + cy.get('button[type="submit"]').click(); + // Error still exists after clicking submit + cy.contains( + 'Please make sure your password is at least 8 characters long.', + ); + cy.intercept({ + method: 'GET', + url: 'https://api.pwnedpasswords.com/range/*', + }).as('breachCheck'); + cy.get('input[name="password"]').type('iamaveryuniqueandlongstring'); + cy.wait('@breachCheck'); + cy.contains('Valid password'); + }); + }); - context('password too long', () => { - it('shows an error showing the password length must be within certain limits', () => { - const excessivelyLongPassword = Array.from(Array(73), () => 'a').join(''); - cy.mockNext(200); - cy.intercept({ - method: 'GET', - url: 'https://api.pwnedpasswords.com/range/*', - }).as('breachCheck'); - cy.visit(`/reset-password/fake_token?useIdapi=true`); - cy.mockNext(200); - cy.get('input[name="password"]').type(excessivelyLongPassword); - cy.get('button[type="submit"]').focus(); - // Error is shown before clicking submit - cy.contains('Maximum of 72'); - cy.wait('@breachCheck'); - cy.get('button[type="submit"]').click(); - // Error still exists after clicking submit - cy.contains( - 'Please make sure your password is not longer than 72 characters.', - ); - cy.get('input[name="password"]').type( - '{selectall}{backspace}iamaveryuniqueandlongstring', - ); - cy.wait('@breachCheck'); - cy.contains('Valid password'); - }); - }); + context('password too long', () => { + it('shows an error showing the password length must be within certain limits', () => { + const excessivelyLongPassword = Array.from(Array(73), () => 'a').join(''); + cy.mockNext(200); + cy.intercept({ + method: 'GET', + url: 'https://api.pwnedpasswords.com/range/*', + }).as('breachCheck'); + cy.visit(`/reset-password/fake_token?useIdapi=true`); + cy.mockNext(200); + cy.get('input[name="password"]').type(excessivelyLongPassword); + cy.get('button[type="submit"]').focus(); + // Error is shown before clicking submit + cy.contains('Maximum of 72'); + cy.wait('@breachCheck'); + cy.get('button[type="submit"]').click(); + // Error still exists after clicking submit + cy.contains( + 'Please make sure your password is not longer than 72 characters.', + ); + cy.get('input[name="password"]').type( + '{selectall}{backspace}iamaveryuniqueandlongstring', + ); + cy.wait('@breachCheck'); + cy.contains('Valid password'); + }); + }); - context('General IDAPI failure on token read', () => { - it('displays the password resend page', () => { - cy.mockNext(500); - cy.visit(`/reset-password/fake_token?useIdapi=true`); - cy.contains('Link expired'); - }); - }); + context('General IDAPI failure on token read', () => { + it('displays the password resend page', () => { + cy.mockNext(500); + cy.visit(`/reset-password/fake_token?useIdapi=true`); + cy.contains('Link expired'); + }); + }); - context('General IDAPI failure on password change', () => { - it('displays a generic error message', () => { - cy.mockNext(200); - cy.mockNext(500); - cy.mockNext(200); - cy.intercept({ - method: 'GET', - url: 'https://api.pwnedpasswords.com/range/*', - }).as('breachCheck'); - cy.visit(`/reset-password/fake_token?useIdapi=true`); - cy.get('input[name="password"]').type('thisisalongandunbreachedpassword'); - cy.wait('@breachCheck'); - cy.get('button[type="submit"]').click(); - cy.contains( - 'There was a problem changing your password, please try again.', - ); - }); - }); + context('General IDAPI failure on password change', () => { + it('displays a generic error message', () => { + cy.mockNext(200); + cy.mockNext(500); + cy.mockNext(200); + cy.intercept({ + method: 'GET', + url: 'https://api.pwnedpasswords.com/range/*', + }).as('breachCheck'); + cy.visit(`/reset-password/fake_token?useIdapi=true`); + cy.get('input[name="password"]').type('thisisalongandunbreachedpassword'); + cy.wait('@breachCheck'); + cy.get('button[type="submit"]').click(); + cy.contains( + 'There was a problem changing your password, please try again.', + ); + }); + }); }); diff --git a/cypress/integration/mocked/consent_token.3.cy.ts b/cypress/integration/mocked/consent_token.3.cy.ts index cffc8f9616..77953d05d7 100644 --- a/cypress/integration/mocked/consent_token.3.cy.ts +++ b/cypress/integration/mocked/consent_token.3.cy.ts @@ -1,44 +1,44 @@ import { injectAndCheckAxe } from '../../support/cypress-axe'; describe('Consent token accept flow', () => { - beforeEach(() => { - cy.mockPurge(); - }); + beforeEach(() => { + cy.mockPurge(); + }); - context('a11y checks', () => { - it('Has no detectable a11y violations on consent token expired page', () => { - cy.visit('/consent-token/abc123/accept'); - injectAndCheckAxe(); - }); + context('a11y checks', () => { + it('Has no detectable a11y violations on consent token expired page', () => { + cy.visit('/consent-token/abc123/accept'); + injectAndCheckAxe(); + }); - it('Has no detectable a11y violations on consent token email sent page', () => { - cy.visit('/consent-token/abc123/accept'); - cy.get('button[type=submit]').click(); - injectAndCheckAxe(); - }); - }); + it('Has no detectable a11y violations on consent token email sent page', () => { + cy.visit('/consent-token/abc123/accept'); + cy.get('button[type=submit]').click(); + injectAndCheckAxe(); + }); + }); - context('consent token flow', () => { - // This test needs to be run mocked because we need to mock the - // expired token response from IDAPI. - it('shows the email sent page when supplied a valid, expired token', () => { - const expiredToken = 'expired-consent-token'; - cy.mockNext(403, { - error: { - status: 'error', - errors: [ - { - message: 'Token expired', - description: 'The link is no longer valid', - }, - ], - }, - }); - cy.visit(`/consent-token/${expiredToken}/accept`); - cy.contains('This link has expired.'); - cy.get('button[type=submit]').click(); - cy.url().should('include', '/consent-token/email-sent'); - cy.contains('Check your email inbox'); - }); - }); + context('consent token flow', () => { + // This test needs to be run mocked because we need to mock the + // expired token response from IDAPI. + it('shows the email sent page when supplied a valid, expired token', () => { + const expiredToken = 'expired-consent-token'; + cy.mockNext(403, { + error: { + status: 'error', + errors: [ + { + message: 'Token expired', + description: 'The link is no longer valid', + }, + ], + }, + }); + cy.visit(`/consent-token/${expiredToken}/accept`); + cy.contains('This link has expired.'); + cy.get('button[type=submit]').click(); + cy.url().should('include', '/consent-token/email-sent'); + cy.contains('Check your email inbox'); + }); + }); }); diff --git a/cypress/integration/mocked/email_input.2.cy.ts b/cypress/integration/mocked/email_input.2.cy.ts index 8b2bc7f37b..837c94b71c 100644 --- a/cypress/integration/mocked/email_input.2.cy.ts +++ b/cypress/integration/mocked/email_input.2.cy.ts @@ -1,37 +1,37 @@ describe('Email input component', () => { - beforeEach(() => { - cy.mockPurge(); - }); + beforeEach(() => { + cy.mockPurge(); + }); - it('should show an error message when nothing submitted ', () => { - cy.visit('/register?useIdapi=true'); - cy.get('[data-cy=main-form-submit-button]').click(); - cy.contains('Please enter your email.'); - }); + it('should show an error message when nothing submitted ', () => { + cy.visit('/register?useIdapi=true'); + cy.get('[data-cy=main-form-submit-button]').click(); + cy.contains('Please enter your email.'); + }); - it('should show an error message when an invalid email is submitted', () => { - cy.visit('/register?useIdapi=true'); - cy.get('input[name="email"]').type('invalid.email.com'); - cy.get('[data-cy=main-form-submit-button]').click(); - cy.contains('Please enter a valid email format.'); - }); + it('should show an error message when an invalid email is submitted', () => { + cy.visit('/register?useIdapi=true'); + cy.get('input[name="email"]').type('invalid.email.com'); + cy.get('[data-cy=main-form-submit-button]').click(); + cy.contains('Please enter a valid email format.'); + }); - it('does not show an error message when a valid email is submitted', () => { - cy.visit('/register?useIdapi=true'); - cy.get('input[name="email"]').type('test@email.com'); - cy.get('[data-cy=main-form-submit-button]').focus(); - cy.contains('Please enter a valid email format.').should('not.exist'); - cy.contains('Please enter your email.').should('not.exist'); - }); + it('does not show an error message when a valid email is submitted', () => { + cy.visit('/register?useIdapi=true'); + cy.get('input[name="email"]').type('test@email.com'); + cy.get('[data-cy=main-form-submit-button]').focus(); + cy.contains('Please enter a valid email format.').should('not.exist'); + cy.contains('Please enter your email.').should('not.exist'); + }); - it('should correct error once a valid email is submitted', () => { - cy.visit('/register?useIdapi=true'); - cy.get('input[name="email"]').type('invalid.email.com'); - cy.get('[data-cy=main-form-submit-button]').click(); - cy.contains('Please enter a valid email format.'); - cy.get('input[name="email"]').type('test@email.com'); - cy.get('[data-cy=main-form-submit-button]').focus(); - cy.contains('Please enter a valid email format.').should('not.exist'); - cy.contains('Please enter your email.').should('not.exist'); - }); + it('should correct error once a valid email is submitted', () => { + cy.visit('/register?useIdapi=true'); + cy.get('input[name="email"]').type('invalid.email.com'); + cy.get('[data-cy=main-form-submit-button]').click(); + cy.contains('Please enter a valid email format.'); + cy.get('input[name="email"]').type('test@email.com'); + cy.get('[data-cy=main-form-submit-button]').focus(); + cy.contains('Please enter a valid email format.').should('not.exist'); + cy.contains('Please enter your email.').should('not.exist'); + }); }); diff --git a/cypress/integration/mocked/email_templates.5.cy.ts b/cypress/integration/mocked/email_templates.5.cy.ts index ee804d72d7..2ba87b9d36 100644 --- a/cypress/integration/mocked/email_templates.5.cy.ts +++ b/cypress/integration/mocked/email_templates.5.cy.ts @@ -1,22 +1,22 @@ describe('Email template generation', () => { - it('renders an accidental email', () => { - cy.request('/email/accidental-email').then((response) => { - expect(response.body.plain).to.contain( - 'This email has been triggered accidentally.', - ); - expect(response.body.html).to.contain( - 'This email has been triggered accidentally.', - ); - }); - }); - it('returns a 404 error for an invalid template name', () => { - cy.request({ - url: '/email/invalid-template', - failOnStatusCode: false, - }).then((response) => { - expect(response.status).to.equal(404); - expect(response.body.plain).to.not.exist; - expect(response.body.html).to.not.exist; - }); - }); + it('renders an accidental email', () => { + cy.request('/email/accidental-email').then((response) => { + expect(response.body.plain).to.contain( + 'This email has been triggered accidentally.', + ); + expect(response.body.html).to.contain( + 'This email has been triggered accidentally.', + ); + }); + }); + it('returns a 404 error for an invalid template name', () => { + cy.request({ + url: '/email/invalid-template', + failOnStatusCode: false, + }).then((response) => { + expect(response.status).to.equal(404); + expect(response.body.plain).to.not.exist; + expect(response.body.html).to.not.exist; + }); + }); }); diff --git a/cypress/integration/mocked/okta_change_password.2.cy.ts b/cypress/integration/mocked/okta_change_password.2.cy.ts index ab6eaff341..d3d52af1ea 100644 --- a/cypress/integration/mocked/okta_change_password.2.cy.ts +++ b/cypress/integration/mocked/okta_change_password.2.cy.ts @@ -20,299 +20,299 @@ import { randomPassword } from '../../support/commands/testUser'; describe('Change password in Okta', () => { - context('reset password page', () => { - const email = 'mrtest@theguardian.com'; - - beforeEach(() => { - cy.mockPurge(); - }); - - const mockValidateRecoveryTokenSuccess = ( - date: Date = new Date(Date.now() + 1800000 /* 30min */), - ) => { - cy.mockNext(200, { - stateToken: 'stateToken', - expiresAt: date.toISOString(), - status: 'SUCCESS', - _embedded: { - user: { - id: '12345', - passwordChanged: new Date().toISOString(), - profile: { - login: email, - firstName: null, - lastName: null, - }, - }, - }, - }); - }; - - const mockValidateRecoveryTokenFailure = () => { - cy.mockNext(403, { - errorCode: 'E0000105', - errorSummary: - 'You have accessed an account recovery link that has expired or been previously used.', - errorLink: 'E0000105', - errorId: 'errorId', - errorCauses: [], - }); - }; - - const mockPasswordResetSuccess = ( - date: Date = new Date(Date.now() + 1800000 /* 30min */), - ) => { - cy.mockNext(200, { - expiresAt: date.toISOString(), - status: 'SUCCESS', - sessionToken: 'aValidSessionToken', - _embedded: { - user: { - id: '12345', - passwordChanged: new Date().toISOString(), - profile: { - login: email, - firstName: null, - lastName: null, - locale: 'en_US', - timeZone: 'America/Los_Angeles', - }, - }, - }, - }); - }; - - const mockUpdateUserSuccess = () => { - cy.mockNext(200, { - id: '12345', - status: 'SUCCESS', - profile: { - login: email, - email, - isGuardianUser: true, - }, - credentials: {}, - }); - }; - - const mockPasswordResetFailure = (cause: string) => { - cy.mockNext(403, { - errorCode: 'E0000080', - errorSummary: - 'The password does not meet the complexity requirements of the current password policy.', - errorLink: 'E0000080', - errorId: 'errorId', - errorCauses: [ - { - errorSummary: cause, - }, - ], - }); - }; - - const mockPasswordResetInvalidStateTokenFailure = () => { - cy.mockNext(403, { - errorCode: 'E0000011', - errorSummary: 'Invalid token provided', - errorLink: 'E0000011', - errorId: 'errorId', - errorCauses: [], - }); - }; - - const interceptBreachPasswordCheck = () => { - cy.intercept({ - method: 'GET', - url: 'https://api.pwnedpasswords.com/range/*', - }).as('breachCheck'); - }; - - it('shows the link expired page if the state token and the recovery token are invalid', () => { - interceptBreachPasswordCheck(); - - mockValidateRecoveryTokenSuccess(); // needs to succeed once for the page to display - mockPasswordResetInvalidStateTokenFailure(); - mockValidateRecoveryTokenFailure(); - - cy.visit('/reset-password/token'); - - cy.get('input[name="password"]').type(randomPassword()); - cy.get('button[type="submit"]').click(); - - cy.contains('Link expired'); - }); - - it('shows the password reset page with errors if the state token is invalid but the recovery token is still valid', () => { - interceptBreachPasswordCheck(); - - mockValidateRecoveryTokenSuccess(); - mockValidateRecoveryTokenSuccess(); - mockPasswordResetInvalidStateTokenFailure(); - mockValidateRecoveryTokenSuccess(); - - cy.visit('/reset-password/token'); - - cy.get('input[name="password"]').type(randomPassword()); - cy.get('button[type="submit"]').click(); - - cy.contains('Reset password'); - cy.contains(`Please enter your new password for ${email}`); - cy.contains( - 'There was a problem changing your password, please try again.', - ); - }); - - it('shows the link expired page if recovery token is invalid after submitting password', () => { - interceptBreachPasswordCheck(); - - mockValidateRecoveryTokenSuccess(); - mockValidateRecoveryTokenFailure(); - - cy.visit('/reset-password/token'); - cy.clearCookie('GU_GATEWAY_STATE'); - - cy.get('input[name="password"]').type(randomPassword()); - cy.get('button[type="submit"]').click(); - - cy.contains('Link expired'); - }); - - it('shows the link expired page if an unexpected error occurred when validating the recovery token', () => { - cy.mockNext(500); - cy.visit('/reset-password/token'); - cy.contains('Link expired'); - }); - - it('shows the reset password page with errors if an unexpected error occurred when resetting the password', () => { - interceptBreachPasswordCheck(); - - mockValidateRecoveryTokenSuccess(); - cy.mockNext(503); - mockValidateRecoveryTokenSuccess(); - - cy.visit('/reset-password/token'); - - cy.get('input[name="password"]').type(randomPassword()); - cy.get('button[type="submit"]').click(); - - cy.contains('Reset password'); - cy.contains(`Please enter your new password for ${email}`); - cy.contains( - 'There was a problem changing your password, please try again.', - ); - }); - - it('shows the reset password page with field error if the password is too short', () => { - interceptBreachPasswordCheck(); - - mockValidateRecoveryTokenSuccess(); - mockPasswordResetFailure( - 'Password requirements were not met. Password requirements: at least 6 characters.', - ); - mockValidateRecoveryTokenSuccess(); - - cy.visit('/reset-password/token'); - - // even though this test is for a short password, we enter a valid password here to bypass - // client-side password complexity checks in order to test the server-side response - cy.get('input[name="password"]').type(randomPassword()); - cy.get('button[type="submit"]').click(); - - cy.contains('Reset password'); - cy.contains( - 'Please make sure your password is at least 8 characters long.', - ); - }); - - it('shows the reset password page with field error if the password is too long', () => { - interceptBreachPasswordCheck(); - - mockValidateRecoveryTokenSuccess(); - mockPasswordResetFailure( - 'Password requirements were not met. Password requirements: maximum 72 characters.', - ); - mockValidateRecoveryTokenSuccess(); - - cy.visit('/reset-password/token'); - - cy.get('input[name="password"]').type(randomPassword()); - cy.get('button[type="submit"]').click(); - - cy.contains('Reset password'); - cy.contains( - 'Please make sure your password is not longer than 72 characters.', - ); - }); - - it('shows the reset password page with field error if the password is the same as the current password', () => { - interceptBreachPasswordCheck(); - - mockValidateRecoveryTokenSuccess(); - mockPasswordResetFailure('Password cannot be your current password'); - mockValidateRecoveryTokenSuccess(); - - cy.visit('/reset-password/token'); - - cy.get('input[name="password"]').type(randomPassword()); - cy.get('button[type="submit"]').click(); - - cy.contains('Reset password'); - cy.contains( - 'Please use a password that is different to your current password.', - ); - }); - - it('shows the reset password page with field error if the password is a common or breached password', () => { - interceptBreachPasswordCheck(); - - mockValidateRecoveryTokenSuccess(); - mockPasswordResetFailure( - 'This password was found in a list of commonly used passwords. Please try another password.', - ); - mockValidateRecoveryTokenSuccess(); - - cy.visit('/reset-password/token'); - - cy.get('input[name="password"]').type(randomPassword()); - cy.get('button[type="submit"]').click(); - - cy.contains('Reset password'); - cy.contains('Please use a password that is hard to guess.'); - }); - - it('shows the password updated page on successful update', () => { - interceptBreachPasswordCheck(); - - mockValidateRecoveryTokenSuccess(); - mockValidateRecoveryTokenSuccess(); - mockPasswordResetSuccess(); - mockUpdateUserSuccess(); - - cy.intercept( - `http://localhost:9000/oauth2/${Cypress.env( - 'OKTA_CUSTOM_OAUTH_SERVER', - )}/v1/authorize*`, - (req) => { - req.redirect( - `https://${Cypress.env('BASE_URI')}/reset-password/complete`, - ); - }, - ).as('authRedirect'); - - cy.visit('/reset-password/token'); - - cy.get('input[name="password"]').type('thisisalongandunbreachedpassword'); - cy.wait('@breachCheck'); - cy.get('button[type="submit"]').click(); - cy.wait('@authRedirect').then(() => { - cy.contains('Password updated'); - cy.contains(email); - cy.contains('Continue to the Guardian').should( - 'have.attr', - 'href', - `${Cypress.env('DEFAULT_RETURN_URI')}`, - ); - }); - }); - }); + context('reset password page', () => { + const email = 'mrtest@theguardian.com'; + + beforeEach(() => { + cy.mockPurge(); + }); + + const mockValidateRecoveryTokenSuccess = ( + date: Date = new Date(Date.now() + 1800000 /* 30min */), + ) => { + cy.mockNext(200, { + stateToken: 'stateToken', + expiresAt: date.toISOString(), + status: 'SUCCESS', + _embedded: { + user: { + id: '12345', + passwordChanged: new Date().toISOString(), + profile: { + login: email, + firstName: null, + lastName: null, + }, + }, + }, + }); + }; + + const mockValidateRecoveryTokenFailure = () => { + cy.mockNext(403, { + errorCode: 'E0000105', + errorSummary: + 'You have accessed an account recovery link that has expired or been previously used.', + errorLink: 'E0000105', + errorId: 'errorId', + errorCauses: [], + }); + }; + + const mockPasswordResetSuccess = ( + date: Date = new Date(Date.now() + 1800000 /* 30min */), + ) => { + cy.mockNext(200, { + expiresAt: date.toISOString(), + status: 'SUCCESS', + sessionToken: 'aValidSessionToken', + _embedded: { + user: { + id: '12345', + passwordChanged: new Date().toISOString(), + profile: { + login: email, + firstName: null, + lastName: null, + locale: 'en_US', + timeZone: 'America/Los_Angeles', + }, + }, + }, + }); + }; + + const mockUpdateUserSuccess = () => { + cy.mockNext(200, { + id: '12345', + status: 'SUCCESS', + profile: { + login: email, + email, + isGuardianUser: true, + }, + credentials: {}, + }); + }; + + const mockPasswordResetFailure = (cause: string) => { + cy.mockNext(403, { + errorCode: 'E0000080', + errorSummary: + 'The password does not meet the complexity requirements of the current password policy.', + errorLink: 'E0000080', + errorId: 'errorId', + errorCauses: [ + { + errorSummary: cause, + }, + ], + }); + }; + + const mockPasswordResetInvalidStateTokenFailure = () => { + cy.mockNext(403, { + errorCode: 'E0000011', + errorSummary: 'Invalid token provided', + errorLink: 'E0000011', + errorId: 'errorId', + errorCauses: [], + }); + }; + + const interceptBreachPasswordCheck = () => { + cy.intercept({ + method: 'GET', + url: 'https://api.pwnedpasswords.com/range/*', + }).as('breachCheck'); + }; + + it('shows the link expired page if the state token and the recovery token are invalid', () => { + interceptBreachPasswordCheck(); + + mockValidateRecoveryTokenSuccess(); // needs to succeed once for the page to display + mockPasswordResetInvalidStateTokenFailure(); + mockValidateRecoveryTokenFailure(); + + cy.visit('/reset-password/token'); + + cy.get('input[name="password"]').type(randomPassword()); + cy.get('button[type="submit"]').click(); + + cy.contains('Link expired'); + }); + + it('shows the password reset page with errors if the state token is invalid but the recovery token is still valid', () => { + interceptBreachPasswordCheck(); + + mockValidateRecoveryTokenSuccess(); + mockValidateRecoveryTokenSuccess(); + mockPasswordResetInvalidStateTokenFailure(); + mockValidateRecoveryTokenSuccess(); + + cy.visit('/reset-password/token'); + + cy.get('input[name="password"]').type(randomPassword()); + cy.get('button[type="submit"]').click(); + + cy.contains('Reset password'); + cy.contains(`Please enter your new password for ${email}`); + cy.contains( + 'There was a problem changing your password, please try again.', + ); + }); + + it('shows the link expired page if recovery token is invalid after submitting password', () => { + interceptBreachPasswordCheck(); + + mockValidateRecoveryTokenSuccess(); + mockValidateRecoveryTokenFailure(); + + cy.visit('/reset-password/token'); + cy.clearCookie('GU_GATEWAY_STATE'); + + cy.get('input[name="password"]').type(randomPassword()); + cy.get('button[type="submit"]').click(); + + cy.contains('Link expired'); + }); + + it('shows the link expired page if an unexpected error occurred when validating the recovery token', () => { + cy.mockNext(500); + cy.visit('/reset-password/token'); + cy.contains('Link expired'); + }); + + it('shows the reset password page with errors if an unexpected error occurred when resetting the password', () => { + interceptBreachPasswordCheck(); + + mockValidateRecoveryTokenSuccess(); + cy.mockNext(503); + mockValidateRecoveryTokenSuccess(); + + cy.visit('/reset-password/token'); + + cy.get('input[name="password"]').type(randomPassword()); + cy.get('button[type="submit"]').click(); + + cy.contains('Reset password'); + cy.contains(`Please enter your new password for ${email}`); + cy.contains( + 'There was a problem changing your password, please try again.', + ); + }); + + it('shows the reset password page with field error if the password is too short', () => { + interceptBreachPasswordCheck(); + + mockValidateRecoveryTokenSuccess(); + mockPasswordResetFailure( + 'Password requirements were not met. Password requirements: at least 6 characters.', + ); + mockValidateRecoveryTokenSuccess(); + + cy.visit('/reset-password/token'); + + // even though this test is for a short password, we enter a valid password here to bypass + // client-side password complexity checks in order to test the server-side response + cy.get('input[name="password"]').type(randomPassword()); + cy.get('button[type="submit"]').click(); + + cy.contains('Reset password'); + cy.contains( + 'Please make sure your password is at least 8 characters long.', + ); + }); + + it('shows the reset password page with field error if the password is too long', () => { + interceptBreachPasswordCheck(); + + mockValidateRecoveryTokenSuccess(); + mockPasswordResetFailure( + 'Password requirements were not met. Password requirements: maximum 72 characters.', + ); + mockValidateRecoveryTokenSuccess(); + + cy.visit('/reset-password/token'); + + cy.get('input[name="password"]').type(randomPassword()); + cy.get('button[type="submit"]').click(); + + cy.contains('Reset password'); + cy.contains( + 'Please make sure your password is not longer than 72 characters.', + ); + }); + + it('shows the reset password page with field error if the password is the same as the current password', () => { + interceptBreachPasswordCheck(); + + mockValidateRecoveryTokenSuccess(); + mockPasswordResetFailure('Password cannot be your current password'); + mockValidateRecoveryTokenSuccess(); + + cy.visit('/reset-password/token'); + + cy.get('input[name="password"]').type(randomPassword()); + cy.get('button[type="submit"]').click(); + + cy.contains('Reset password'); + cy.contains( + 'Please use a password that is different to your current password.', + ); + }); + + it('shows the reset password page with field error if the password is a common or breached password', () => { + interceptBreachPasswordCheck(); + + mockValidateRecoveryTokenSuccess(); + mockPasswordResetFailure( + 'This password was found in a list of commonly used passwords. Please try another password.', + ); + mockValidateRecoveryTokenSuccess(); + + cy.visit('/reset-password/token'); + + cy.get('input[name="password"]').type(randomPassword()); + cy.get('button[type="submit"]').click(); + + cy.contains('Reset password'); + cy.contains('Please use a password that is hard to guess.'); + }); + + it('shows the password updated page on successful update', () => { + interceptBreachPasswordCheck(); + + mockValidateRecoveryTokenSuccess(); + mockValidateRecoveryTokenSuccess(); + mockPasswordResetSuccess(); + mockUpdateUserSuccess(); + + cy.intercept( + `http://localhost:9000/oauth2/${Cypress.env( + 'OKTA_CUSTOM_OAUTH_SERVER', + )}/v1/authorize*`, + (req) => { + req.redirect( + `https://${Cypress.env('BASE_URI')}/reset-password/complete`, + ); + }, + ).as('authRedirect'); + + cy.visit('/reset-password/token'); + + cy.get('input[name="password"]').type('thisisalongandunbreachedpassword'); + cy.wait('@breachCheck'); + cy.get('button[type="submit"]').click(); + cy.wait('@authRedirect').then(() => { + cy.contains('Password updated'); + cy.contains(email); + cy.contains('Continue to the Guardian').should( + 'have.attr', + 'href', + `${Cypress.env('DEFAULT_RETURN_URI')}`, + ); + }); + }); + }); }); diff --git a/cypress/integration/mocked/okta_register.3.cy.ts b/cypress/integration/mocked/okta_register.3.cy.ts index 83a28139d9..c25361755b 100644 --- a/cypress/integration/mocked/okta_register.3.cy.ts +++ b/cypress/integration/mocked/okta_register.3.cy.ts @@ -1,160 +1,160 @@ describe('Okta Register flow', () => { - const setSidCookie = () => { - cy.setCookie('sid', `the_sid_cookie`, { - domain: Cypress.env('BASE_URI'), - }); - }; - - context('Signed in user posts to /register', () => { - beforeEach(() => { - cy.mockPurge(); - cy.clearCookies(); - // we visit the healthcheck page to make sure the cookies are cleared from the browser - cy.visit('/healthcheck'); - cy.disableCMP(); - }); - - it('should redirect to homepage if the sid Okta session cookie is valid', () => { - cy.mockPattern( - 200, - { - id: 'test', - login: 'user@example.com', - userId: 'userId', - status: 'ACTIVE', - expiresAt: '2016-01-03T09:13:17.000Z', - lastPasswordVerification: '2016-01-03T07:02:00.000Z', - lastFactorVerification: null, - amr: ['pwd'], - idp: { - id: '01a2bcdef3GHIJKLMNOP', - type: 'OKTA', - }, - mfaActive: true, - }, - '/api/v1/sessions/the_sid_cookie', - ); - - cy.mockPattern(204, {}, '/api/v1/users/userId/sessions'); - - cy.intercept('POST', '/register**').as('registerPost'); - - cy.visit('/register'); - - setSidCookie(); - - cy.get('input[name="email"]').type('example@example.com'); - cy.mockNext(200, { - userType: 'new', - }); - cy.mockNext(200, { - status: 'success', - errors: [], - }); - cy.get('[data-cy=main-form-submit-button]').click(); - - cy.contains('Sign in to the Guardian'); - cy.contains('You are signed in with'); - cy.contains('user@example.com'); - cy.contains('Continue') - .should('have.attr', 'href') - .and('include', '/signin/refresh') - .and( - 'include', - 'returnUrl=https%3A%2F%2Fm.code.dev-theguardian.com%2F', - ); - cy.contains('a', 'Sign in') - .should('have.attr', 'href') - .and( - 'include', - '/signout?returnUrl=https%253A%252F%252Fprofile.thegulocal.com%252Fsignin', - ); - cy.contains('Sign in with a different email'); - }); - - it('should redirect to /reauthenticate if the sid Okta session cookie is set, but invalid', () => { - cy.mockPattern(404, '/api/v1/sessions/the_sid_cookie'); - - cy.mockPattern(204, {}, '/api/v1/users/userId/sessions'); - - setSidCookie(); - - // visit healthcheck to set the cookie - cy.visit('/healthcheck'); - - cy.visit('/register'); - - cy.location('pathname').should('eq', '/reauthenticate'); - - cy.getCookie('sid').should('not.exist'); - }); - }); - - context('Signed in user visits /register', () => { - beforeEach(() => { - cy.mockPurge(); - }); - it('should redirect to homepage if the sid Okta session cookie is valid', () => { - cy.mockPattern( - 200, - { - id: 'test', - login: 'user@example.com', - userId: 'userId', - status: 'ACTIVE', - expiresAt: '2016-01-03T09:13:17.000Z', - lastPasswordVerification: '2016-01-03T07:02:00.000Z', - lastFactorVerification: null, - amr: ['pwd'], - idp: { - id: '01a2bcdef3GHIJKLMNOP', - type: 'OKTA', - }, - mfaActive: true, - }, - '/api/v1/sessions/the_sid_cookie', - ); - - cy.mockPattern(204, {}, '/api/v1/users/userId/sessions'); - - setSidCookie(); - - // disable the cmp on the redirect - cy.disableCMP(); - - cy.visit('/register'); - - cy.contains('Sign in to the Guardian'); - cy.contains('You are signed in with'); - cy.contains('user@example.com'); - cy.contains('Continue') - .should('have.attr', 'href') - .and('include', '/signin/refresh') - .and('include', 'returnUrl=https%3A%2F%2Fm.code.dev-theguardian.com'); - cy.contains('a', 'Sign in') - .should('have.attr', 'href') - .and( - 'include', - '/signout?returnUrl=https%253A%252F%252Fprofile.thegulocal.com%252Fsignin', - ); - cy.contains('Sign in with a different email'); - }); - - it('should redirect to /reauthenticate if the sid Okta session cookie is set but invalid', () => { - cy.mockPattern(404, '/api/v1/sessions/the_sid_cookie'); - - cy.mockPattern(204, {}, '/api/v1/users/userId/sessions'); - - setSidCookie(); - - // visit healthcheck to set the cookie - cy.visit('/healthcheck'); - - cy.visit('/register'); - - cy.location('pathname').should('eq', '/reauthenticate'); - - cy.getCookie('sid').should('not.exist'); - }); - }); + const setSidCookie = () => { + cy.setCookie('sid', `the_sid_cookie`, { + domain: Cypress.env('BASE_URI'), + }); + }; + + context('Signed in user posts to /register', () => { + beforeEach(() => { + cy.mockPurge(); + cy.clearCookies(); + // we visit the healthcheck page to make sure the cookies are cleared from the browser + cy.visit('/healthcheck'); + cy.disableCMP(); + }); + + it('should redirect to homepage if the sid Okta session cookie is valid', () => { + cy.mockPattern( + 200, + { + id: 'test', + login: 'user@example.com', + userId: 'userId', + status: 'ACTIVE', + expiresAt: '2016-01-03T09:13:17.000Z', + lastPasswordVerification: '2016-01-03T07:02:00.000Z', + lastFactorVerification: null, + amr: ['pwd'], + idp: { + id: '01a2bcdef3GHIJKLMNOP', + type: 'OKTA', + }, + mfaActive: true, + }, + '/api/v1/sessions/the_sid_cookie', + ); + + cy.mockPattern(204, {}, '/api/v1/users/userId/sessions'); + + cy.intercept('POST', '/register**').as('registerPost'); + + cy.visit('/register'); + + setSidCookie(); + + cy.get('input[name="email"]').type('example@example.com'); + cy.mockNext(200, { + userType: 'new', + }); + cy.mockNext(200, { + status: 'success', + errors: [], + }); + cy.get('[data-cy=main-form-submit-button]').click(); + + cy.contains('Sign in to the Guardian'); + cy.contains('You are signed in with'); + cy.contains('user@example.com'); + cy.contains('Continue') + .should('have.attr', 'href') + .and('include', '/signin/refresh') + .and( + 'include', + 'returnUrl=https%3A%2F%2Fm.code.dev-theguardian.com%2F', + ); + cy.contains('a', 'Sign in') + .should('have.attr', 'href') + .and( + 'include', + '/signout?returnUrl=https%253A%252F%252Fprofile.thegulocal.com%252Fsignin', + ); + cy.contains('Sign in with a different email'); + }); + + it('should redirect to /reauthenticate if the sid Okta session cookie is set, but invalid', () => { + cy.mockPattern(404, '/api/v1/sessions/the_sid_cookie'); + + cy.mockPattern(204, {}, '/api/v1/users/userId/sessions'); + + setSidCookie(); + + // visit healthcheck to set the cookie + cy.visit('/healthcheck'); + + cy.visit('/register'); + + cy.location('pathname').should('eq', '/reauthenticate'); + + cy.getCookie('sid').should('not.exist'); + }); + }); + + context('Signed in user visits /register', () => { + beforeEach(() => { + cy.mockPurge(); + }); + it('should redirect to homepage if the sid Okta session cookie is valid', () => { + cy.mockPattern( + 200, + { + id: 'test', + login: 'user@example.com', + userId: 'userId', + status: 'ACTIVE', + expiresAt: '2016-01-03T09:13:17.000Z', + lastPasswordVerification: '2016-01-03T07:02:00.000Z', + lastFactorVerification: null, + amr: ['pwd'], + idp: { + id: '01a2bcdef3GHIJKLMNOP', + type: 'OKTA', + }, + mfaActive: true, + }, + '/api/v1/sessions/the_sid_cookie', + ); + + cy.mockPattern(204, {}, '/api/v1/users/userId/sessions'); + + setSidCookie(); + + // disable the cmp on the redirect + cy.disableCMP(); + + cy.visit('/register'); + + cy.contains('Sign in to the Guardian'); + cy.contains('You are signed in with'); + cy.contains('user@example.com'); + cy.contains('Continue') + .should('have.attr', 'href') + .and('include', '/signin/refresh') + .and('include', 'returnUrl=https%3A%2F%2Fm.code.dev-theguardian.com'); + cy.contains('a', 'Sign in') + .should('have.attr', 'href') + .and( + 'include', + '/signout?returnUrl=https%253A%252F%252Fprofile.thegulocal.com%252Fsignin', + ); + cy.contains('Sign in with a different email'); + }); + + it('should redirect to /reauthenticate if the sid Okta session cookie is set but invalid', () => { + cy.mockPattern(404, '/api/v1/sessions/the_sid_cookie'); + + cy.mockPattern(204, {}, '/api/v1/users/userId/sessions'); + + setSidCookie(); + + // visit healthcheck to set the cookie + cy.visit('/healthcheck'); + + cy.visit('/register'); + + cy.location('pathname').should('eq', '/reauthenticate'); + + cy.getCookie('sid').should('not.exist'); + }); + }); }); diff --git a/cypress/integration/mocked/okta_send_reset_password.4.cy.ts b/cypress/integration/mocked/okta_send_reset_password.4.cy.ts index 62a7139164..2b581c84d0 100644 --- a/cypress/integration/mocked/okta_send_reset_password.4.cy.ts +++ b/cypress/integration/mocked/okta_send_reset_password.4.cy.ts @@ -1,282 +1,282 @@ import { UserResponse } from '@/server/models/okta/User'; describe('Send password reset email in Okta', () => { - const email = 'mrtest@theguardian.com'; + const email = 'mrtest@theguardian.com'; - const mockUserActiveWithPassword: UserResponse = { - id: 'test', - status: 'ACTIVE', - profile: { - login: 'mrtest@theguardian.com', - email: 'mrtest@theguardian.com', - }, - credentials: { - password: {}, - provider: { - type: 'OKTA', - name: 'OKTA', - }, - }, - }; + const mockUserActiveWithPassword: UserResponse = { + id: 'test', + status: 'ACTIVE', + profile: { + login: 'mrtest@theguardian.com', + email: 'mrtest@theguardian.com', + }, + credentials: { + password: {}, + provider: { + type: 'OKTA', + name: 'OKTA', + }, + }, + }; - const mockUserActiveWithoutPassword: UserResponse = { - id: 'test', - status: 'ACTIVE', - profile: { - login: 'mrtest@theguardian.com', - email: 'mrtest@theguardian.com', - }, - credentials: { - provider: { - type: 'OKTA', - name: 'OKTA', - }, - }, - }; + const mockUserActiveWithoutPassword: UserResponse = { + id: 'test', + status: 'ACTIVE', + profile: { + login: 'mrtest@theguardian.com', + email: 'mrtest@theguardian.com', + }, + credentials: { + provider: { + type: 'OKTA', + name: 'OKTA', + }, + }, + }; - const mockUserStaged: UserResponse = { - id: 'test', - status: 'STAGED', - profile: { - login: 'mrtest@theguardian.com', - email: 'mrtest@theguardian.com', - }, - credentials: { - provider: { - type: 'OKTA', - name: 'OKTA', - }, - }, - }; + const mockUserStaged: UserResponse = { + id: 'test', + status: 'STAGED', + profile: { + login: 'mrtest@theguardian.com', + email: 'mrtest@theguardian.com', + }, + credentials: { + provider: { + type: 'OKTA', + name: 'OKTA', + }, + }, + }; - const mockUserProvisioned: UserResponse = { - id: 'test', - status: 'PROVISIONED', - profile: { - login: 'mrtest@theguardian.com', - email: 'mrtest@theguardian.com', - }, - credentials: { - provider: { - type: 'OKTA', - name: 'OKTA', - }, - }, - }; + const mockUserProvisioned: UserResponse = { + id: 'test', + status: 'PROVISIONED', + profile: { + login: 'mrtest@theguardian.com', + email: 'mrtest@theguardian.com', + }, + credentials: { + provider: { + type: 'OKTA', + name: 'OKTA', + }, + }, + }; - const mockUserRecovery: UserResponse = { - id: 'test', - status: 'RECOVERY', - profile: { - login: 'mrtest@theguardian.com', - email: 'mrtest@theguardian.com', - }, - credentials: { - provider: { - type: 'OKTA', - name: 'OKTA', - }, - }, - }; + const mockUserRecovery: UserResponse = { + id: 'test', + status: 'RECOVERY', + profile: { + login: 'mrtest@theguardian.com', + email: 'mrtest@theguardian.com', + }, + credentials: { + provider: { + type: 'OKTA', + name: 'OKTA', + }, + }, + }; - const mockUserPasswordExpired: UserResponse = { - id: 'test', - status: 'PASSWORD_EXPIRED', - profile: { - login: 'mrtest@theguardian.com', - email: 'mrtest@theguardian.com', - }, - credentials: { - provider: { - type: 'OKTA', - name: 'OKTA', - }, - }, - }; + const mockUserPasswordExpired: UserResponse = { + id: 'test', + status: 'PASSWORD_EXPIRED', + profile: { + login: 'mrtest@theguardian.com', + email: 'mrtest@theguardian.com', + }, + credentials: { + provider: { + type: 'OKTA', + name: 'OKTA', + }, + }, + }; - beforeEach(() => { - cy.mockPurge(); - }); + beforeEach(() => { + cy.mockPurge(); + }); - context('send reset password email for ACTIVE user', () => { - it('shows email sent page when successful', () => { - cy.visit('/reset-password'); - cy.get('input[name="email"]').type(email); - cy.mockNext(200, mockUserActiveWithPassword); - cy.mockNext(200, { - resetPasswordUrl: - 'https://example.com/signin/reset-password/XE6wE17zmphl3KqAPFxO', - }); - cy.get('button[type="submit"]').click(); - cy.contains('Check your email inbox'); - cy.contains('email within 2 minutes'); - cy.contains('Resend email'); - }); - }); + context('send reset password email for ACTIVE user', () => { + it('shows email sent page when successful', () => { + cy.visit('/reset-password'); + cy.get('input[name="email"]').type(email); + cy.mockNext(200, mockUserActiveWithPassword); + cy.mockNext(200, { + resetPasswordUrl: + 'https://example.com/signin/reset-password/XE6wE17zmphl3KqAPFxO', + }); + cy.get('button[type="submit"]').click(); + cy.contains('Check your email inbox'); + cy.contains('email within 2 minutes'); + cy.contains('Resend email'); + }); + }); - context('send reset password email for ACTIVE user without password', () => { - it('shows email sent page when successful', () => { - cy.visit('/reset-password'); - cy.get('input[name="email"]').type(email); - cy.mockNext(200, mockUserActiveWithoutPassword); - cy.mockNext(403, { - errorCode: 'E0000006', - errorSummary: - 'You do not have permission to perform the requested action', - errorLink: 'E0000006', - errorId: 'errorId', - errorCauses: [], - }); - cy.mockNext(200, { - resetPasswordUrl: `https://${Cypress.env( - 'BASE_URI', - )}/reset_password/token_token_token_to`, - }); - cy.mockNext(200, { - stateToken: 'stateToken', - expiresAt: new Date(Date.now() + 1800000 /* 30min */), - status: 'SUCCESS', - _embedded: { - user: { - id: '12345', - passwordChanged: new Date().toISOString(), - profile: { - login: email, - firstName: null, - lastName: null, - }, - }, - }, - }); - cy.mockNext(200, { - expiresAt: new Date(Date.now() + 1800000 /* 30min */), - status: 'SUCCESS', - sessionToken: 'aValidSessionToken', - _embedded: { - user: { - id: '12345', - passwordChanged: new Date().toISOString(), - profile: { - login: email, - firstName: null, - lastName: null, - locale: 'en_US', - timeZone: 'America/Los_Angeles', - }, - }, - }, - }); - cy.mockNext(200, mockUserActiveWithoutPassword); - cy.mockNext(200, { - resetPasswordUrl: - 'https://example.com/signin/reset-password/XE6wE17zmphl3KqAPFxO', - }); - cy.get('button[type="submit"]').click(); - cy.contains('Check your email inbox'); - cy.contains('email within 2 minutes'); - cy.contains('Resend email'); - }); - }); + context('send reset password email for ACTIVE user without password', () => { + it('shows email sent page when successful', () => { + cy.visit('/reset-password'); + cy.get('input[name="email"]').type(email); + cy.mockNext(200, mockUserActiveWithoutPassword); + cy.mockNext(403, { + errorCode: 'E0000006', + errorSummary: + 'You do not have permission to perform the requested action', + errorLink: 'E0000006', + errorId: 'errorId', + errorCauses: [], + }); + cy.mockNext(200, { + resetPasswordUrl: `https://${Cypress.env( + 'BASE_URI', + )}/reset_password/token_token_token_to`, + }); + cy.mockNext(200, { + stateToken: 'stateToken', + expiresAt: new Date(Date.now() + 1800000 /* 30min */), + status: 'SUCCESS', + _embedded: { + user: { + id: '12345', + passwordChanged: new Date().toISOString(), + profile: { + login: email, + firstName: null, + lastName: null, + }, + }, + }, + }); + cy.mockNext(200, { + expiresAt: new Date(Date.now() + 1800000 /* 30min */), + status: 'SUCCESS', + sessionToken: 'aValidSessionToken', + _embedded: { + user: { + id: '12345', + passwordChanged: new Date().toISOString(), + profile: { + login: email, + firstName: null, + lastName: null, + locale: 'en_US', + timeZone: 'America/Los_Angeles', + }, + }, + }, + }); + cy.mockNext(200, mockUserActiveWithoutPassword); + cy.mockNext(200, { + resetPasswordUrl: + 'https://example.com/signin/reset-password/XE6wE17zmphl3KqAPFxO', + }); + cy.get('button[type="submit"]').click(); + cy.contains('Check your email inbox'); + cy.contains('email within 2 minutes'); + cy.contains('Resend email'); + }); + }); - context('send create password email for STAGED user', () => { - it('shows email sent page when successful', () => { - cy.visit('/reset-password'); - cy.get('input[name="email"]').type(email); - cy.mockNext(200, mockUserStaged); - cy.mockNext(200, { - activationUrl: `token_token_token_to`, - activationToken: `token_token_token_to`, - }); - cy.get('button[type="submit"]').click(); - cy.contains('Check your email inbox'); - cy.contains('email within 2 minutes'); - cy.contains('Resend email'); - }); - }); + context('send create password email for STAGED user', () => { + it('shows email sent page when successful', () => { + cy.visit('/reset-password'); + cy.get('input[name="email"]').type(email); + cy.mockNext(200, mockUserStaged); + cy.mockNext(200, { + activationUrl: `token_token_token_to`, + activationToken: `token_token_token_to`, + }); + cy.get('button[type="submit"]').click(); + cy.contains('Check your email inbox'); + cy.contains('email within 2 minutes'); + cy.contains('Resend email'); + }); + }); - context('send create password email for PROVISIONED user', () => { - it('shows email sent page when successful', () => { - cy.visit('/reset-password'); - cy.get('input[name="email"]').type(email); - cy.mockNext(200, mockUserProvisioned); - cy.mockNext(200, { - activationUrl: `token_token_token_to`, - activationToken: `token_token_token_to`, - }); - cy.get('button[type="submit"]').click(); - cy.contains('Check your email inbox'); - cy.contains('email within 2 minutes'); - cy.contains('Resend email'); - }); - }); + context('send create password email for PROVISIONED user', () => { + it('shows email sent page when successful', () => { + cy.visit('/reset-password'); + cy.get('input[name="email"]').type(email); + cy.mockNext(200, mockUserProvisioned); + cy.mockNext(200, { + activationUrl: `token_token_token_to`, + activationToken: `token_token_token_to`, + }); + cy.get('button[type="submit"]').click(); + cy.contains('Check your email inbox'); + cy.contains('email within 2 minutes'); + cy.contains('Resend email'); + }); + }); - context('send reset password email for RECOVERY user', () => { - it('shows email sent page when successful', () => { - cy.visit('/reset-password'); - cy.get('input[name="email"]').type(email); - cy.mockNext(200, mockUserRecovery); - cy.mockNext(200, { - resetPasswordUrl: `https://${Cypress.env( - 'BASE_URI', - )}/reset_password/token_token_token_to`, - }); - cy.get('button[type="submit"]').click(); - cy.contains('Check your email inbox'); - cy.contains('email within 2 minutes'); - cy.contains('Resend email'); - }); - }); + context('send reset password email for RECOVERY user', () => { + it('shows email sent page when successful', () => { + cy.visit('/reset-password'); + cy.get('input[name="email"]').type(email); + cy.mockNext(200, mockUserRecovery); + cy.mockNext(200, { + resetPasswordUrl: `https://${Cypress.env( + 'BASE_URI', + )}/reset_password/token_token_token_to`, + }); + cy.get('button[type="submit"]').click(); + cy.contains('Check your email inbox'); + cy.contains('email within 2 minutes'); + cy.contains('Resend email'); + }); + }); - context('send reset password email for PASSWORD_EXPIRED user', () => { - it('shows email sent page when successful', () => { - cy.visit('/reset-password'); - cy.get('input[name="email"]').type(email); - cy.mockNext(200, mockUserPasswordExpired); - cy.mockNext(200, { - resetPasswordUrl: `https://${Cypress.env( - 'BASE_URI', - )}/reset_password/token_token_token_to`, - }); - cy.get('button[type="submit"]').click(); - cy.contains('Check your email inbox'); - cy.contains('email within 2 minutes'); - cy.contains('Resend email'); - }); - }); + context('send reset password email for PASSWORD_EXPIRED user', () => { + it('shows email sent page when successful', () => { + cy.visit('/reset-password'); + cy.get('input[name="email"]').type(email); + cy.mockNext(200, mockUserPasswordExpired); + cy.mockNext(200, { + resetPasswordUrl: `https://${Cypress.env( + 'BASE_URI', + )}/reset_password/token_token_token_to`, + }); + cy.get('button[type="submit"]').click(); + cy.contains('Check your email inbox'); + cy.contains('email within 2 minutes'); + cy.contains('Resend email'); + }); + }); - context('user not found', () => { - it('shows email sent page even when user not found', () => { - cy.visit('/reset-password'); - cy.get('input[name="email"]').type(email); - cy.mockNext(404, { - errorCode: 'E0000007', - errorSummary: 'User not found', - errorLink: 'E0000007', - errorId: 'errorId', - errorCauses: [], - }); - cy.get('button[type="submit"]').click(); - cy.contains('Check your email inbox'); - cy.contains('email within 2 minutes'); - cy.contains('Resend email'); - }); - }); + context('user not found', () => { + it('shows email sent page even when user not found', () => { + cy.visit('/reset-password'); + cy.get('input[name="email"]').type(email); + cy.mockNext(404, { + errorCode: 'E0000007', + errorSummary: 'User not found', + errorLink: 'E0000007', + errorId: 'errorId', + errorCauses: [], + }); + cy.get('button[type="submit"]').click(); + cy.contains('Check your email inbox'); + cy.contains('email within 2 minutes'); + cy.contains('Resend email'); + }); + }); - context('generic error handling', () => { - it('shows a generic error when something goes wrong', () => { - cy.visit('/reset-password'); - cy.get('input[name="email"]').type(email); - cy.mockNext(200, mockUserActiveWithPassword); - cy.mockNext(403, { - errorCode: 'E0000006', - errorSummary: - 'You do not have permission to perform the requested action', - errorLink: 'E0000006', - errorId: 'errorId', - errorCauses: [], - }); - cy.get('button[type="submit"]').click(); - cy.contains('Sorry, something went wrong. Please try again.'); - }); - }); + context('generic error handling', () => { + it('shows a generic error when something goes wrong', () => { + cy.visit('/reset-password'); + cy.get('input[name="email"]').type(email); + cy.mockNext(200, mockUserActiveWithPassword); + cy.mockNext(403, { + errorCode: 'E0000006', + errorSummary: + 'You do not have permission to perform the requested action', + errorLink: 'E0000006', + errorId: 'errorId', + errorCauses: [], + }); + cy.get('button[type="submit"]').click(); + cy.contains('Sorry, something went wrong. Please try again.'); + }); + }); }); diff --git a/cypress/integration/mocked/okta_sign_in.6.cy.ts b/cypress/integration/mocked/okta_sign_in.6.cy.ts index bde0947b79..1f38c06bf5 100644 --- a/cypress/integration/mocked/okta_sign_in.6.cy.ts +++ b/cypress/integration/mocked/okta_sign_in.6.cy.ts @@ -1,262 +1,262 @@ describe('Sign in flow', () => { - context('Signing in - Okta', () => { - beforeEach(() => { - cy.mockPurge(); - }); + context('Signing in - Okta', () => { + beforeEach(() => { + cy.mockPurge(); + }); - it('loads the "signed in as" page if user is already authenticated', function () { - cy.mockPattern( - 200, - { - id: 'test', - login: 'user@example.com', - userId: 'userId', - status: 'ACTIVE', - expiresAt: '2016-01-03T09:13:17.000Z', - lastPasswordVerification: '2016-01-03T07:02:00.000Z', - lastFactorVerification: null, - amr: ['pwd'], - idp: { - id: '01a2bcdef3GHIJKLMNOP', - type: 'OKTA', - }, - mfaActive: true, - }, - '/api/v1/sessions/the_sid_cookie', - ); + it('loads the "signed in as" page if user is already authenticated', function () { + cy.mockPattern( + 200, + { + id: 'test', + login: 'user@example.com', + userId: 'userId', + status: 'ACTIVE', + expiresAt: '2016-01-03T09:13:17.000Z', + lastPasswordVerification: '2016-01-03T07:02:00.000Z', + lastFactorVerification: null, + amr: ['pwd'], + idp: { + id: '01a2bcdef3GHIJKLMNOP', + type: 'OKTA', + }, + mfaActive: true, + }, + '/api/v1/sessions/the_sid_cookie', + ); - cy.mockPattern(204, {}, '/api/v1/users/userId/sessions'); + cy.mockPattern(204, {}, '/api/v1/users/userId/sessions'); - cy.setCookie('sid', `the_sid_cookie`); + cy.setCookie('sid', `the_sid_cookie`); - // disable the cmp on the redirect - cy.disableCMP(); + // disable the cmp on the redirect + cy.disableCMP(); - cy.visit('/signin'); + cy.visit('/signin'); - cy.contains('Sign in to the Guardian'); - cy.contains('You are signed in with'); - cy.contains('user@example.com'); - cy.contains('Continue') - .should('have.attr', 'href') - .and('include', '/signin/refresh') - .and('include', 'returnUrl=https%3A%2F%2Fm.code.dev-theguardian.com'); - cy.contains('a', 'Sign in') - .should('have.attr', 'href') - .and( - 'include', - '/signout?returnUrl=https%253A%252F%252Fprofile.thegulocal.com%252Fsignin%253FreturnUrl%253Dhttps%25253A%25252F%25252Fm.code.dev-theguardian.com', - ); - cy.contains('Sign in with a different email'); - }); + cy.contains('Sign in to the Guardian'); + cy.contains('You are signed in with'); + cy.contains('user@example.com'); + cy.contains('Continue') + .should('have.attr', 'href') + .and('include', '/signin/refresh') + .and('include', 'returnUrl=https%3A%2F%2Fm.code.dev-theguardian.com'); + cy.contains('a', 'Sign in') + .should('have.attr', 'href') + .and( + 'include', + '/signout?returnUrl=https%253A%252F%252Fprofile.thegulocal.com%252Fsignin%253FreturnUrl%253Dhttps%25253A%25252F%25252Fm.code.dev-theguardian.com', + ); + cy.contains('Sign in with a different email'); + }); - it('loads the "signed in as" page if user is already authenticated and coming from native app and oauth flow', function () { - cy.mockPattern( - 200, - { - id: 'test', - login: 'user@example.com', - userId: 'userId', - status: 'ACTIVE', - expiresAt: '2016-01-03T09:13:17.000Z', - lastPasswordVerification: '2016-01-03T07:02:00.000Z', - lastFactorVerification: null, - amr: ['pwd'], - idp: { - id: '01a2bcdef3GHIJKLMNOP', - type: 'OKTA', - }, - mfaActive: true, - }, - '/api/v1/sessions/the_sid_cookie', - ); + it('loads the "signed in as" page if user is already authenticated and coming from native app and oauth flow', function () { + cy.mockPattern( + 200, + { + id: 'test', + login: 'user@example.com', + userId: 'userId', + status: 'ACTIVE', + expiresAt: '2016-01-03T09:13:17.000Z', + lastPasswordVerification: '2016-01-03T07:02:00.000Z', + lastFactorVerification: null, + amr: ['pwd'], + idp: { + id: '01a2bcdef3GHIJKLMNOP', + type: 'OKTA', + }, + mfaActive: true, + }, + '/api/v1/sessions/the_sid_cookie', + ); - cy.mockPattern(204, {}, '/api/v1/users/userId/sessions'); + cy.mockPattern(204, {}, '/api/v1/users/userId/sessions'); - cy.mockPattern( - 200, - { - id: '123', - label: 'ios_app', - settings: { - oauthClient: { - redirect_uris: [], - }, - }, - }, - '/api/v1/apps/123', - ); + cy.mockPattern( + 200, + { + id: '123', + label: 'ios_app', + settings: { + oauthClient: { + redirect_uris: [], + }, + }, + }, + '/api/v1/apps/123', + ); - cy.setCookie('sid', `the_sid_cookie`); + cy.setCookie('sid', `the_sid_cookie`); - // disable the cmp on the redirect - cy.disableCMP(); + // disable the cmp on the redirect + cy.disableCMP(); - cy.visit( - '/signin?appClientId=123&fromURI=/fromURI=/oauth2/v1/authorize/redirect?okta_key=oktaKey', - ); + cy.visit( + '/signin?appClientId=123&fromURI=/fromURI=/oauth2/v1/authorize/redirect?okta_key=oktaKey', + ); - cy.contains('Sign in to the Guardian app'); - cy.contains('You are signed in with'); - cy.contains('user@example.com'); - cy.contains('Continue') - .should('have.attr', 'href') - .and( - 'include', - '/fromURI=/oauth2/v1/authorize/redirect?okta_key=oktaKey', - ); - cy.contains('a', 'Sign in') - .should('have.attr', 'href') - .and( - 'include', - '/signout?returnUrl=https%253A%252F%252Fprofile.thegulocal.com%252Fsignin%253FappClientId%253D123%2526fromURI%253D%25252FfromURI%25253D%25252Foauth2%25252Fv1%25252Fauthorize%25252Fredirect%25253Fokta_key%25253DoktaKey%2526returnUrl%253Dhttps%25253A%25252F%25252Fm.code.dev-theguardian.com', - ); - cy.contains('Sign in with a different email'); - }); + cy.contains('Sign in to the Guardian app'); + cy.contains('You are signed in with'); + cy.contains('user@example.com'); + cy.contains('Continue') + .should('have.attr', 'href') + .and( + 'include', + '/fromURI=/oauth2/v1/authorize/redirect?okta_key=oktaKey', + ); + cy.contains('a', 'Sign in') + .should('have.attr', 'href') + .and( + 'include', + '/signout?returnUrl=https%253A%252F%252Fprofile.thegulocal.com%252Fsignin%253FappClientId%253D123%2526fromURI%253D%25252FfromURI%25253D%25252Foauth2%25252Fv1%25252Fauthorize%25252Fredirect%25253Fokta_key%25253DoktaKey%2526returnUrl%253Dhttps%25253A%25252F%25252Fm.code.dev-theguardian.com', + ); + cy.contains('Sign in with a different email'); + }); - it('shows an error message when okta authentication fails', function () { - cy.visit('/signin'); - cy.get('input[name="email"]').type('example@example.com'); - cy.get('input[name="password"]').type('password'); - cy.mockNext(401, { - errorCode: 'E0000004', - errorSummary: 'errorSummary', - errorLink: '', - errorId: 'errorId', - errorCauses: [], - }); - cy.get('[data-cy=main-form-submit-button]').click(); - cy.contains("Email and password don't match"); - }); + it('shows an error message when okta authentication fails', function () { + cy.visit('/signin'); + cy.get('input[name="email"]').type('example@example.com'); + cy.get('input[name="password"]').type('password'); + cy.mockNext(401, { + errorCode: 'E0000004', + errorSummary: 'errorSummary', + errorLink: '', + errorId: 'errorId', + errorCauses: [], + }); + cy.get('[data-cy=main-form-submit-button]').click(); + cy.contains("Email and password don't match"); + }); - it('shows a generic error message when okta rate limited', function () { - cy.visit('/signin'); - cy.get('input[name="email"]').type('example@example.com'); - cy.get('input[name="password"]').type('password'); - cy.mockNext(429, { - errorCode: 'E0000047', - errorSummary: 'errorSummary', - errorLink: '', - errorId: 'errorId', - errorCauses: [], - }); - cy.get('[data-cy=main-form-submit-button]').click(); - cy.contains('There was a problem signing in, please try again.'); - }); + it('shows a generic error message when okta rate limited', function () { + cy.visit('/signin'); + cy.get('input[name="email"]').type('example@example.com'); + cy.get('input[name="password"]').type('password'); + cy.mockNext(429, { + errorCode: 'E0000047', + errorSummary: 'errorSummary', + errorLink: '', + errorId: 'errorId', + errorCauses: [], + }); + cy.get('[data-cy=main-form-submit-button]').click(); + cy.contains('There was a problem signing in, please try again.'); + }); - it('shows a generic error message when okta api response unknown', function () { - cy.visit('/signin'); - cy.get('input[name="email"]').type('example@example.com'); - cy.get('input[name="password"]').type('password'); - cy.mockNext(403, { - errorCode: 'E0000147', - errorSummary: 'errorSummary', - errorLink: '', - errorId: 'errorId', - errorCauses: [], - }); - cy.get('[data-cy=main-form-submit-button]').click(); - cy.contains('There was a problem signing in, please try again.'); - }); + it('shows a generic error message when okta api response unknown', function () { + cy.visit('/signin'); + cy.get('input[name="email"]').type('example@example.com'); + cy.get('input[name="password"]').type('password'); + cy.mockNext(403, { + errorCode: 'E0000147', + errorSummary: 'errorSummary', + errorLink: '', + errorId: 'errorId', + errorCauses: [], + }); + cy.get('[data-cy=main-form-submit-button]').click(); + cy.contains('There was a problem signing in, please try again.'); + }); - it('loads the redirectUrl upon successful authentication for validated user', function () { - cy.visit('/signin?returnUrl=https%3A%2F%2Fwww.theguardian.com%2Fabout'); - cy.get('input[name="email"]').type('example@example.com'); - cy.get('input[name="password"]').type('password'); - cy.mockNext(200, { - expiresAt: '3000-01-01T00:00:00.000Z', - status: 'SUCCESS', - sessionToken: 'some-session-token', - _embedded: { - user: { - id: 'okta-id', - passwordChanged: '2020-01-01T00:00:00.000Z', - profile: { - login: 'test.man@example.com', - firstName: 'Test', - lastName: 'Man', - locale: 'en_GB', - timeZone: 'Europe/London', - }, - }, - }, - }); + it('loads the redirectUrl upon successful authentication for validated user', function () { + cy.visit('/signin?returnUrl=https%3A%2F%2Fwww.theguardian.com%2Fabout'); + cy.get('input[name="email"]').type('example@example.com'); + cy.get('input[name="password"]').type('password'); + cy.mockNext(200, { + expiresAt: '3000-01-01T00:00:00.000Z', + status: 'SUCCESS', + sessionToken: 'some-session-token', + _embedded: { + user: { + id: 'okta-id', + passwordChanged: '2020-01-01T00:00:00.000Z', + profile: { + login: 'test.man@example.com', + firstName: 'Test', + lastName: 'Man', + locale: 'en_GB', + timeZone: 'Europe/London', + }, + }, + }, + }); - cy.mockNext(200, [ - { - id: '123', - profile: { - name: 'GuardianUser-EmailValidated', - description: 'User has validated their email', - }, - }, - ]); + cy.mockNext(200, [ + { + id: '123', + profile: { + name: 'GuardianUser-EmailValidated', + description: 'User has validated their email', + }, + }, + ]); - // we can't actually check the authorization code flow - // so intercept the request and redirect to the guardian about page - cy.intercept( - `http://localhost:9000/oauth2/${Cypress.env( - 'OKTA_CUSTOM_OAUTH_SERVER', - )}/v1/authorize*`, - (req) => { - req.redirect('https://www.theguardian.com/about'); - }, - ).as('authRedirect'); + // we can't actually check the authorization code flow + // so intercept the request and redirect to the guardian about page + cy.intercept( + `http://localhost:9000/oauth2/${Cypress.env( + 'OKTA_CUSTOM_OAUTH_SERVER', + )}/v1/authorize*`, + (req) => { + req.redirect('https://www.theguardian.com/about'); + }, + ).as('authRedirect'); - cy.get('[data-cy=main-form-submit-button]').click(); - cy.wait('@authRedirect').then(() => { - cy.url().should('include', 'https://www.theguardian.com/about'); - }); - }); + cy.get('[data-cy=main-form-submit-button]').click(); + cy.wait('@authRedirect').then(() => { + cy.url().should('include', 'https://www.theguardian.com/about'); + }); + }); - it('redirects to the default url if no redirectUrl given', function () { - cy.visit('/signin'); - cy.get('input[name="email"]').type('example@example.com'); - cy.get('input[name="password"]').type('password'); - cy.mockNext(200, { - expiresAt: '3000-01-01T00:00:00.000Z', - status: 'SUCCESS', - sessionToken: 'some-session-token', - _embedded: { - user: { - id: 'okta-id', - passwordChanged: '2020-01-01T00:00:00.000Z', - profile: { - login: 'test.man@example.com', - firstName: 'Test', - lastName: 'Man', - locale: 'en_GB', - timeZone: 'Europe/London', - }, - }, - }, - }); + it('redirects to the default url if no redirectUrl given', function () { + cy.visit('/signin'); + cy.get('input[name="email"]').type('example@example.com'); + cy.get('input[name="password"]').type('password'); + cy.mockNext(200, { + expiresAt: '3000-01-01T00:00:00.000Z', + status: 'SUCCESS', + sessionToken: 'some-session-token', + _embedded: { + user: { + id: 'okta-id', + passwordChanged: '2020-01-01T00:00:00.000Z', + profile: { + login: 'test.man@example.com', + firstName: 'Test', + lastName: 'Man', + locale: 'en_GB', + timeZone: 'Europe/London', + }, + }, + }, + }); - cy.mockNext(200, [ - { - id: '123', - profile: { - name: 'GuardianUser-EmailValidated', - description: 'User has validated their email', - }, - }, - ]); + cy.mockNext(200, [ + { + id: '123', + profile: { + name: 'GuardianUser-EmailValidated', + description: 'User has validated their email', + }, + }, + ]); - // we can't actually check the authorization code flow - // so intercept the request and redirect to the default return URL - cy.intercept( - `http://localhost:9000/oauth2/${Cypress.env( - 'OKTA_CUSTOM_OAUTH_SERVER', - )}/v1/authorize*`, - (req) => { - req.redirect('https://m.code.dev-theguardian.com/'); - }, - ).as('authRedirect'); + // we can't actually check the authorization code flow + // so intercept the request and redirect to the default return URL + cy.intercept( + `http://localhost:9000/oauth2/${Cypress.env( + 'OKTA_CUSTOM_OAUTH_SERVER', + )}/v1/authorize*`, + (req) => { + req.redirect('https://m.code.dev-theguardian.com/'); + }, + ).as('authRedirect'); - cy.get('[data-cy=main-form-submit-button]').click(); - cy.wait('@authRedirect').then(() => { - cy.url().should('include', 'https://m.code.dev-theguardian.com/'); - }); - }); - }); + cy.get('[data-cy=main-form-submit-button]').click(); + cy.wait('@authRedirect').then(() => { + cy.url().should('include', 'https://m.code.dev-theguardian.com/'); + }); + }); + }); }); diff --git a/cypress/integration/mocked/onboarding_flow.1.cy.ts b/cypress/integration/mocked/onboarding_flow.1.cy.ts index c3cde62686..3f84e4db10 100644 --- a/cypress/integration/mocked/onboarding_flow.1.cy.ts +++ b/cypress/integration/mocked/onboarding_flow.1.cy.ts @@ -2,24 +2,24 @@ import { injectAndCheckAxe } from '../../support/cypress-axe'; import { - AUTH_REDIRECT_ENDPOINT, - authRedirectSignInRecentlyEmailValidated, + AUTH_REDIRECT_ENDPOINT, + authRedirectSignInRecentlyEmailValidated, } from '../../support/idapi/auth'; import { - CONSENTS_ENDPOINT, - CONSENT_ERRORS, - allConsents, - defaultUserConsent, - getUserConsents, - optedIntoPersonalisedAdvertisingUserConsent, - optedOutUserConsent, + CONSENTS_ENDPOINT, + CONSENT_ERRORS, + allConsents, + defaultUserConsent, + getUserConsents, + optedIntoPersonalisedAdvertisingUserConsent, + optedOutUserConsent, } from '../../support/idapi/consent'; import { setAuthCookies } from '../../support/idapi/cookie'; import { - USER_CONSENTS_ENDPOINT, - USER_ENDPOINT, - createUser, - verifiedUserWithNoConsent, + USER_CONSENTS_ENDPOINT, + USER_ENDPOINT, + createUser, + verifiedUserWithNoConsent, } from '../../support/idapi/user'; import CommunicationsPage from '../../support/pages/onboarding/communications_page'; import NewslettersPage from '../../support/pages/onboarding/newsletters_page'; @@ -28,15 +28,15 @@ import YourDataPage from '../../support/pages/onboarding/your_data_page'; import { setMvtId } from '../../support/commands/setMvtId'; import { - GEOLOCATION_CODES, - getGeoLocationHeaders, + GEOLOCATION_CODES, + getGeoLocationHeaders, } from '../../support/geolocation'; import { - NEWSLETTER_ENDPOINT, - NEWSLETTER_ERRORS, - NEWSLETTER_SUBSCRIPTION_ENDPOINT, - allNewsletters, - userNewsletters, + NEWSLETTER_ENDPOINT, + NEWSLETTER_ERRORS, + NEWSLETTER_SUBSCRIPTION_ENDPOINT, + allNewsletters, + userNewsletters, } from '../../support/idapi/newsletter'; import Onboarding from '../../support/pages/onboarding/onboarding_page'; import VerifyEmail from '../../support/pages/verify_email'; @@ -46,1014 +46,1014 @@ const { NEWSLETTERS } = NewslettersPage.CONTENT; // TODO: remove this once we have migrated fully to OAuth as we're unable to mock the OAuth flow to get Tokens // TODO: best thing to do will move as much as possible to the ete tests describe('Onboarding flow', () => { - beforeEach(() => { - cy.mockPurge(); - setMvtId('0'); - }); - - context('Full flow', () => { - beforeEach(() => { - setAuthCookies(); - cy.mockAll( - 200, - authRedirectSignInRecentlyEmailValidated, - AUTH_REDIRECT_ENDPOINT, - ); - cy.mockAll(200, allConsents, CONSENTS_ENDPOINT); - cy.mockAll(200, allNewsletters, NEWSLETTER_ENDPOINT); - cy.mockAll(200, verifiedUserWithNoConsent, USER_ENDPOINT); - cy.mockAll( - 200, - verifiedUserWithNoConsent.user.consents, - USER_CONSENTS_ENDPOINT, - ); - cy.mockAll(200, userNewsletters(), NEWSLETTER_SUBSCRIPTION_ENDPOINT); - }); - - it('goes through full flow, opt in all consents/newsletters, preserve returnUrl', () => { - const newslettersToSubscribe = [ - { listId: 4147 }, - { listId: 4156 }, - { listId: 4165 }, - ]; - - const consent = defaultUserConsent.map(({ id }) => { - // eslint-disable-next-line functional/no-let - let consented = true; - if (id.includes('_optout')) { - consented = false; - } - if (id.includes('personalised_advertising')) { - consented = true; - } - return { - id, - consented, - }; - }); - - const returnUrl = encodeURIComponent( - `https://www.theguardian.com/science/grrlscientist/2012/aug/07/3`, - ); - - cy.enableCMP(); - CommunicationsPage.gotoFlowStart({ - query: { - returnUrl, - useIdapi: 'true', - }, - }); - cy.acceptCMP(); - cy.setCookie('GU_geo_country', 'FR'); - cy.url().should('include', CommunicationsPage.URL); - cy.url().should('include', `returnUrl=${returnUrl}`); - cy.get('input[name=_cmpConsentedState]').should('have.value', 'true'); - CommunicationsPage.backButton().should('not.exist'); - CommunicationsPage.allCheckboxes().should('not.be.checked'); - CommunicationsPage.allCheckboxes().click({ multiple: true }); - - // mock form save success - cy.mockNext(200); - - CommunicationsPage.saveAndContinueButton().click(); - cy.lastPayloadsAre([ - [{ id: 'supporter', consented: true }], - { privateFields: { registrationLocation: 'Europe' } }, - ]); - - cy.url().should('include', NewslettersPage.URL); - cy.url().should('include', `returnUrl=${returnUrl}`); - - NewslettersPage.backButton() - .should('have.attr', 'href') - .and('include', CommunicationsPage.URL); - - NewslettersPage.allCheckboxes() - .should('not.be.checked') - .click({ multiple: true }); - - // mock form save success - cy.mockNext(200); - - NewslettersPage.saveAndContinueButton().click(); - - cy.lastPayloadsAre([ - [ - { id: '4147', subscribed: true }, - { id: '4156', subscribed: true }, - { id: '4165', subscribed: true }, - ], - [{ id: 'events', consented: true }], - ]); - - cy.url().should('include', YourDataPage.URL); - cy.url().should('include', `returnUrl=${returnUrl}`); - - YourDataPage.backButton() - .should('have.attr', 'href') - .and('include', NewslettersPage.URL); - - YourDataPage.personalisedAdvertisingOptInInput().should('not.be.checked'); - - YourDataPage.personalisedAdvertisingOptInSwitch().click(); - YourDataPage.marketingOptInSwitch().should('be.checked'); - YourDataPage.personalisedAdvertisingOptInInput().should('be.checked'); - // mock form save success - cy.mockNext(200); - - // user consents mock response for review of consents flow - cy.mockAll(200, createUser(consent), USER_ENDPOINT); - cy.mockAll(200, consent, USER_CONSENTS_ENDPOINT); - - // mock load user newsletters - cy.mockAll( - 200, - userNewsletters(newslettersToSubscribe), - NEWSLETTER_SUBSCRIPTION_ENDPOINT, - ); - - YourDataPage.saveAndContinueButton().click(); - // Explicity check '_optin' consents are in inverted back to '_optouts' when posted - cy.lastPayloadIs([ - { id: 'profiling_optout', consented: false }, - { id: 'personalised_advertising', consented: true }, - ]); - - cy.url().should('include', ReviewPage.URL); - cy.url().should('include', `returnUrl=${returnUrl}`); - - ReviewPage.backButton().should('not.exist'); - ReviewPage.saveAndContinueButton().should('not.exist'); - - // contains opted in newsletters - Object.values(ReviewPage.CONTENT.NEWSLETTERS).forEach((newsletter) => - cy.contains(newsletter), - ); - - // contains consents - cy.contains(ReviewPage.CONTENT.SUPPORTER_CONSENT); - cy.contains(ReviewPage.CONTENT.PROFILING_CONSENT); - cy.contains(ReviewPage.CONTENT.PERSONALISED_ADVERTISING_CONSENT); - - // does not contain messaging encouraging user to consider other newsletters - cy.contains(ReviewPage.CONTENT.NO_NEWSLETTERS_TITLE).should('not.exist'); - - ReviewPage.returnButton() - .should('have.attr', 'href') - .and('include', decodeURIComponent(returnUrl)); - }); - - it('goes through full flow, opt out all consents/newsletters, preserve returnUrl', () => { - const returnUrl = encodeURIComponent( - `https://www.theguardian.com/science/grrlscientist/2012/aug/07/3`, - ); - - CommunicationsPage.gotoFlowStart({ - query: { - returnUrl, - useIdapi: 'true', - }, - }); + beforeEach(() => { + cy.mockPurge(); + setMvtId('0'); + }); + + context('Full flow', () => { + beforeEach(() => { + setAuthCookies(); + cy.mockAll( + 200, + authRedirectSignInRecentlyEmailValidated, + AUTH_REDIRECT_ENDPOINT, + ); + cy.mockAll(200, allConsents, CONSENTS_ENDPOINT); + cy.mockAll(200, allNewsletters, NEWSLETTER_ENDPOINT); + cy.mockAll(200, verifiedUserWithNoConsent, USER_ENDPOINT); + cy.mockAll( + 200, + verifiedUserWithNoConsent.user.consents, + USER_CONSENTS_ENDPOINT, + ); + cy.mockAll(200, userNewsletters(), NEWSLETTER_SUBSCRIPTION_ENDPOINT); + }); + + it('goes through full flow, opt in all consents/newsletters, preserve returnUrl', () => { + const newslettersToSubscribe = [ + { listId: 4147 }, + { listId: 4156 }, + { listId: 4165 }, + ]; + + const consent = defaultUserConsent.map(({ id }) => { + // eslint-disable-next-line functional/no-let + let consented = true; + if (id.includes('_optout')) { + consented = false; + } + if (id.includes('personalised_advertising')) { + consented = true; + } + return { + id, + consented, + }; + }); + + const returnUrl = encodeURIComponent( + `https://www.theguardian.com/science/grrlscientist/2012/aug/07/3`, + ); + + cy.enableCMP(); + CommunicationsPage.gotoFlowStart({ + query: { + returnUrl, + useIdapi: 'true', + }, + }); + cy.acceptCMP(); + cy.setCookie('GU_geo_country', 'FR'); + cy.url().should('include', CommunicationsPage.URL); + cy.url().should('include', `returnUrl=${returnUrl}`); + cy.get('input[name=_cmpConsentedState]').should('have.value', 'true'); + CommunicationsPage.backButton().should('not.exist'); + CommunicationsPage.allCheckboxes().should('not.be.checked'); + CommunicationsPage.allCheckboxes().click({ multiple: true }); + + // mock form save success + cy.mockNext(200); + + CommunicationsPage.saveAndContinueButton().click(); + cy.lastPayloadsAre([ + [{ id: 'supporter', consented: true }], + { privateFields: { registrationLocation: 'Europe' } }, + ]); + + cy.url().should('include', NewslettersPage.URL); + cy.url().should('include', `returnUrl=${returnUrl}`); + + NewslettersPage.backButton() + .should('have.attr', 'href') + .and('include', CommunicationsPage.URL); + + NewslettersPage.allCheckboxes() + .should('not.be.checked') + .click({ multiple: true }); + + // mock form save success + cy.mockNext(200); + + NewslettersPage.saveAndContinueButton().click(); + + cy.lastPayloadsAre([ + [ + { id: '4147', subscribed: true }, + { id: '4156', subscribed: true }, + { id: '4165', subscribed: true }, + ], + [{ id: 'events', consented: true }], + ]); + + cy.url().should('include', YourDataPage.URL); + cy.url().should('include', `returnUrl=${returnUrl}`); + + YourDataPage.backButton() + .should('have.attr', 'href') + .and('include', NewslettersPage.URL); + + YourDataPage.personalisedAdvertisingOptInInput().should('not.be.checked'); + + YourDataPage.personalisedAdvertisingOptInSwitch().click(); + YourDataPage.marketingOptInSwitch().should('be.checked'); + YourDataPage.personalisedAdvertisingOptInInput().should('be.checked'); + // mock form save success + cy.mockNext(200); + + // user consents mock response for review of consents flow + cy.mockAll(200, createUser(consent), USER_ENDPOINT); + cy.mockAll(200, consent, USER_CONSENTS_ENDPOINT); + + // mock load user newsletters + cy.mockAll( + 200, + userNewsletters(newslettersToSubscribe), + NEWSLETTER_SUBSCRIPTION_ENDPOINT, + ); + + YourDataPage.saveAndContinueButton().click(); + // Explicity check '_optin' consents are in inverted back to '_optouts' when posted + cy.lastPayloadIs([ + { id: 'profiling_optout', consented: false }, + { id: 'personalised_advertising', consented: true }, + ]); + + cy.url().should('include', ReviewPage.URL); + cy.url().should('include', `returnUrl=${returnUrl}`); + + ReviewPage.backButton().should('not.exist'); + ReviewPage.saveAndContinueButton().should('not.exist'); + + // contains opted in newsletters + Object.values(ReviewPage.CONTENT.NEWSLETTERS).forEach((newsletter) => + cy.contains(newsletter), + ); + + // contains consents + cy.contains(ReviewPage.CONTENT.SUPPORTER_CONSENT); + cy.contains(ReviewPage.CONTENT.PROFILING_CONSENT); + cy.contains(ReviewPage.CONTENT.PERSONALISED_ADVERTISING_CONSENT); + + // does not contain messaging encouraging user to consider other newsletters + cy.contains(ReviewPage.CONTENT.NO_NEWSLETTERS_TITLE).should('not.exist'); + + ReviewPage.returnButton() + .should('have.attr', 'href') + .and('include', decodeURIComponent(returnUrl)); + }); + + it('goes through full flow, opt out all consents/newsletters, preserve returnUrl', () => { + const returnUrl = encodeURIComponent( + `https://www.theguardian.com/science/grrlscientist/2012/aug/07/3`, + ); + + CommunicationsPage.gotoFlowStart({ + query: { + returnUrl, + useIdapi: 'true', + }, + }); - cy.url().should('include', CommunicationsPage.URL); - cy.url().should('include', `returnUrl=${returnUrl}`); + cy.url().should('include', CommunicationsPage.URL); + cy.url().should('include', `returnUrl=${returnUrl}`); - CommunicationsPage.backButton().should('not.exist'); + CommunicationsPage.backButton().should('not.exist'); - CommunicationsPage.allCheckboxes().should('not.be.checked'); - cy.get('input[name=_cmpConsentedState]').should('have.value', 'false'); + CommunicationsPage.allCheckboxes().should('not.be.checked'); + cy.get('input[name=_cmpConsentedState]').should('have.value', 'false'); - // mock form save success - cy.mockNext(200); + // mock form save success + cy.mockNext(200); - CommunicationsPage.saveAndContinueButton().click(); + CommunicationsPage.saveAndContinueButton().click(); - // Use .lastPayloadsAre to ensure the IDAPI registrationLocation update is NOT posted - cy.lastPayloadsAre([[{ id: 'supporter', consented: false }]]); + // Use .lastPayloadsAre to ensure the IDAPI registrationLocation update is NOT posted + cy.lastPayloadsAre([[{ id: 'supporter', consented: false }]]); - cy.url().should('include', NewslettersPage.URL); - cy.url().should('include', `returnUrl=${returnUrl}`); + cy.url().should('include', NewslettersPage.URL); + cy.url().should('include', `returnUrl=${returnUrl}`); - NewslettersPage.backButton() - .should('have.attr', 'href') - .and('include', CommunicationsPage.URL); + NewslettersPage.backButton() + .should('have.attr', 'href') + .and('include', CommunicationsPage.URL); - // mock form save success - cy.mockNext(200); + // mock form save success + cy.mockNext(200); - cy.enableCMP(); - NewslettersPage.saveAndContinueButton().click(); - cy.lastPayloadsAre([[], [{ id: 'events', consented: false }]]); - cy.acceptCMP(); + cy.enableCMP(); + NewslettersPage.saveAndContinueButton().click(); + cy.lastPayloadsAre([[], [{ id: 'events', consented: false }]]); + cy.acceptCMP(); - cy.url().should('include', YourDataPage.URL); - cy.url().should('include', `returnUrl=${returnUrl}`); + cy.url().should('include', YourDataPage.URL); + cy.url().should('include', `returnUrl=${returnUrl}`); - YourDataPage.backButton() - .should('have.attr', 'href') - .and('include', NewslettersPage.URL); + YourDataPage.backButton() + .should('have.attr', 'href') + .and('include', NewslettersPage.URL); - YourDataPage.marketingOptInSwitch().should('be.checked'); - YourDataPage.marketingOptInClickableSection().click(); - YourDataPage.marketingOptInSwitch().should('not.be.checked'); - YourDataPage.personalisedAdvertisingOptInSwitch().should('exist'); - YourDataPage.personalisedAdvertisingOptInInput().should('not.be.checked'); - - // mock form save success - cy.mockNext(200); - - cy.mockAll(200, createUser(optedOutUserConsent), USER_ENDPOINT); - cy.mockAll(200, optedOutUserConsent, USER_CONSENTS_ENDPOINT); - - YourDataPage.saveAndContinueButton().click(); - // Explicity check '_optin' consents are in inverted back to '_optouts' when posted - cy.lastPayloadIs([ - { id: 'profiling_optout', consented: true }, - { id: 'personalised_advertising', consented: false }, - ]); - - cy.url().should('include', ReviewPage.URL); - cy.url().should('include', `returnUrl=${returnUrl}`); - - ReviewPage.backButton().should('not.exist'); - ReviewPage.saveAndContinueButton().should('not.exist'); - - // contains no opted in newsletters - Object.values(ReviewPage.CONTENT.NEWSLETTERS).forEach((newsletter) => - cy.contains(newsletter).should('not.exist'), - ); - - // contains no consents - cy.contains(ReviewPage.CONTENT.SUPPORTER_CONSENT).should('not.exist'); - cy.contains(ReviewPage.CONTENT.PROFILING_CONSENT).should('not.exist'); - cy.contains(ReviewPage.CONTENT.PERSONALISED_ADVERTISING_CONSENT).should( - 'not.exist', - ); - - // contains messaging encouraging user to explore other newsletters - cy.contains(ReviewPage.CONTENT.NO_NEWSLETTERS_TITLE); - - ReviewPage.returnButton() - .should('have.attr', 'href') - .and('include', decodeURIComponent(returnUrl)); - }); - - it('uses a default returnUrl if none provided', () => { - const returnUrl = encodeURIComponent(Cypress.env('DEFAULT_RETURN_URI')); - - CommunicationsPage.gotoFlowStart({ - query: { - useIdapi: 'true', - }, - }); - - cy.url().should('include', CommunicationsPage.URL); - cy.url().should('include', `returnUrl=${returnUrl}`); - - // mock form save success - cy.mockNext(200); - - CommunicationsPage.saveAndContinueButton().click(); - - cy.url().should('include', NewslettersPage.URL); - cy.url().should('include', `returnUrl=${returnUrl}`); - - // mock form save success - cy.mockNext(200); - - NewslettersPage.saveAndContinueButton().click(); - - cy.url().should('include', YourDataPage.URL); - cy.url().should('include', `returnUrl=${returnUrl}`); - - YourDataPage.marketingOptInClickableSection().click(); - - // mock form save success - cy.mockNext(200); - - YourDataPage.saveAndContinueButton().click(); - cy.url().should('include', ReviewPage.URL); - cy.url().should('include', `returnUrl=${returnUrl}`); - - ReviewPage.returnButton() - .should('have.attr', 'href') - .and('include', decodeURIComponent(returnUrl)); - }); - }); - - context('Login middleware', () => { - it('no sc_gu_u cookie, redirect to login page', () => { - const signInUrl = Cypress.env('SIGN_IN_PAGE_URL'); - cy.setCookie('GU_U', 'FAKE_GU_U'); - cy.setCookie('SC_GU_LA', 'FAKE_SC_GU_LA'); - - cy.request({ - url: `${Onboarding.URL}?useIdapi=true`, - followRedirect: false, - }).then((res) => { - expect(res.status).to.eq(303); - expect(res.redirectedToUrl).to.include(signInUrl); - }); - }); - - it('no sc_gu_la cookie, redirect to login page', () => { - const signInUrl = Cypress.env('SIGN_IN_PAGE_URL'); - cy.setCookie('GU_U', 'FAKE_GU_U'); - cy.setCookie('SC_GU_U', 'FAKE_SC_GU_U'); - - cy.request({ - url: `${Onboarding.URL}?useIdapi=true`, - followRedirect: false, - }).then((res) => { - expect(res.status).to.eq(303); - expect(res.redirectedToUrl).to.include(signInUrl); - }); - }); - - it('email not validated, go to verify email page', () => { - setAuthCookies(); - const emailNotValidatedResponse = { - signInStatus: 'signedInRecently', - emailValidated: false, - redirect: null, - }; - cy.mockAll(200, emailNotValidatedResponse, AUTH_REDIRECT_ENDPOINT); - - cy.request({ - url: `${Onboarding.URL}?useIdapi=true`, - followRedirect: false, - }).then((res) => { - expect(res.status).to.eq(303); - expect(res.redirectedToUrl).to.include(VerifyEmail.URL); - }); - }); - - it('recently logged in, go to consents flow', () => { - setAuthCookies(); - cy.mockAll( - 200, - authRedirectSignInRecentlyEmailValidated, - AUTH_REDIRECT_ENDPOINT, - ); - cy.request({ - url: `${Onboarding.URL}?useIdapi=true`, - followRedirect: false, - }).then((res) => { - expect(res.status).to.eq(303); - expect(res.redirectedToUrl).to.include(CommunicationsPage.URL); - }); - }); - - it('not recently logged in, follows supplied redirect', () => { - setAuthCookies(); - const emailNotValidatedResponse = { - signInStatus: 'signedInNotRecently', - emailValidated: true, - redirect: { - url: 'https://fakeloginfortest.code.dev-theguardian.co.uk', - }, - }; - cy.mockAll(200, emailNotValidatedResponse, AUTH_REDIRECT_ENDPOINT); - - cy.request({ - url: `${Onboarding.URL}?useIdapi=true`, - followRedirect: false, - }).then((res) => { - expect(res.status).to.eq(303); - expect(res.redirectedToUrl).to.include( - 'https://fakeloginfortest.code.dev-theguardian.co.uk', - ); - }); - }); - - it('if missing redirect information, it redirects to the default ', () => { - const signInUrl = Cypress.env('SIGN_IN_PAGE_URL'); - setAuthCookies(); - const emailNotValidatedResponse = { - signInStatus: 'signedInNotRecently', - emailValidated: true, - redirect: undefined, - }; - - cy.mockAll(200, emailNotValidatedResponse, AUTH_REDIRECT_ENDPOINT); - - cy.request({ - url: `${Onboarding.URL}?useIdapi=true`, - followRedirect: false, - }).then((res) => { - expect(res.status).to.eq(303); - expect(res.redirectedToUrl).to.include(signInUrl); - }); - }); - - it('on idapi error it redirects to the sign in page with the error flag set', () => { - setAuthCookies(); - cy.mockAll(502, 'gateway error', AUTH_REDIRECT_ENDPOINT); - cy.request({ - url: `${Onboarding.URL}?useIdapi=true`, - followRedirect: false, - }).then((res) => { - expect(res.status).to.eq(303); - expect(res.redirectedToUrl).to.include( - `error=signin-error-bad-request`, - ); - }); - }); - }); - - context('Contact options page', () => { - beforeEach(() => { - setAuthCookies(); - cy.mockAll( - 200, - authRedirectSignInRecentlyEmailValidated, - AUTH_REDIRECT_ENDPOINT, - ); - cy.mockAll(200, allConsents, CONSENTS_ENDPOINT); - }); - - it('has no detectable a11y violations', () => { - cy.mockAll( - 200, - verifiedUserWithNoConsent.user.consents, - USER_CONSENTS_ENDPOINT, - ); - CommunicationsPage.goto({ - query: { - useIdapi: true, - }, - }); - injectAndCheckAxe(); - }); - - it('had no detectable a11y voilations if previously selected consents', () => { - const consented = getUserConsents(['jobs', 'offers']); - cy.mockAll(200, consented, USER_CONSENTS_ENDPOINT); - CommunicationsPage.goto({ - query: { - useIdapi: true, - }, - }); - injectAndCheckAxe(); - }); - - it('has no detectable a11y violations on with an error', () => { - cy.mockAll(500, {}, USER_ENDPOINT); - CommunicationsPage.goto({ - query: { - useIdapi: true, - }, - }); - injectAndCheckAxe(); - }); - - it('shows correct contact options, none checked by default', () => { - cy.mockAll( - 200, - verifiedUserWithNoConsent.user.consents, - USER_CONSENTS_ENDPOINT, - ); - CommunicationsPage.goto({ - query: { - useIdapi: true, - }, - }); - CommunicationsPage.backButton().should('not.exist'); - CommunicationsPage.allCheckboxes().should('not.be.checked'); - }); - - it('shows any previously selected consents', () => { - const consented = getUserConsents(['supporter']); - cy.mockAll(200, consented, USER_CONSENTS_ENDPOINT); - CommunicationsPage.goto({ - query: { - useIdapi: true, - }, - }); - CommunicationsPage.backButton().should('not.exist'); - CommunicationsPage.consentCheckboxWithTitle( - 'Supporting the Guardian', - ).should('be.checked'); - }); - - it('displays a relevant error message on user end point failure', () => { - cy.mockAll(500, {}, USER_CONSENTS_ENDPOINT); - CommunicationsPage.goto({ - query: { - useIdapi: true, - }, - }); - CommunicationsPage.errorBanner().contains(CONSENT_ERRORS.GENERIC); - CommunicationsPage.backButton().should('not.exist'); - CommunicationsPage.saveAndContinueButton().should('not.exist'); - }); - - it('displays a relevant error on consents endpoint failure', () => { - cy.mockAll(500, {}, CONSENTS_ENDPOINT); - CommunicationsPage.goto({ - query: { - useIdapi: true, - }, - }); - CommunicationsPage.errorBanner().contains(CONSENT_ERRORS.GENERIC); - CommunicationsPage.backButton().should('not.exist'); - CommunicationsPage.saveAndContinueButton().should('not.exist'); - }); - }); - - context('Newsletters page', () => { - beforeEach(() => { - setAuthCookies(); - cy.mockAll( - 200, - authRedirectSignInRecentlyEmailValidated, - AUTH_REDIRECT_ENDPOINT, - ); - cy.mockAll(200, allNewsletters, NEWSLETTER_ENDPOINT); - cy.mockAll(200, userNewsletters(), NEWSLETTER_SUBSCRIPTION_ENDPOINT); - cy.mockAll(200, allConsents, CONSENTS_ENDPOINT); - cy.mockAll( - 200, - verifiedUserWithNoConsent.user.consents, - USER_CONSENTS_ENDPOINT, - ); - }); - - it('has no detectable a11y violations', () => { - const headers = getGeoLocationHeaders(GEOLOCATION_CODES.GB); - - cy.visit(NewslettersPage.URL, { headers, qs: { useIdapi: 'true' } }); - injectAndCheckAxe(); - }); - - it('had no detectable a11y voilations if previously selected newsletter', () => { - const newslettersToSubscribe = [{ listId: 4147 }, { listId: 4165 }]; - cy.mockAll( - 200, - userNewsletters(newslettersToSubscribe), - NEWSLETTER_SUBSCRIPTION_ENDPOINT, - ); - NewslettersPage.goto({ - query: { - useIdapi: true, - }, - }); - injectAndCheckAxe(); - }); - - it('has no detectable a11y violations on with an error', () => { - cy.mockAll(500, {}, NEWSLETTER_ENDPOINT); - NewslettersPage.goto({ - query: { - useIdapi: true, - }, - }); - injectAndCheckAxe(); - }); - - it('correct newsletters shown for uk, none checked by default', () => { - const headers = getGeoLocationHeaders(GEOLOCATION_CODES.GB); - - cy.visit(NewslettersPage.URL, { headers, qs: { useIdapi: 'true' } }); - - NewslettersPage.checkboxWithTitle(NEWSLETTERS.FIRST_EDITION_UK).should( - 'not.be.checked', - ); - NewslettersPage.checkboxWithTitle(NEWSLETTERS.LONG_READ).should( - 'not.be.checked', - ); - NewslettersPage.checkboxWithTitle(NEWSLETTERS.GREEN_LIGHT).should( - 'not.be.checked', - ); - NewslettersPage.checkboxWithTitle( - NewslettersPage.CONTENT.Consents.EVENTS, - ).should('not.be.checked'); - - CommunicationsPage.backButton().should('exist'); - CommunicationsPage.saveAndContinueButton().should('exist'); - }); - - it('correct newsletters shown for United States of America, none checked by default', () => { - const headers = getGeoLocationHeaders(GEOLOCATION_CODES.AMERICA); - - cy.visit(NewslettersPage.URL, { headers, qs: { useIdapi: 'true' } }); - - NewslettersPage.checkboxWithTitle(NEWSLETTERS.FIRST_THING_US).should( - 'not.be.checked', - ); - NewslettersPage.checkboxWithTitle(NEWSLETTERS.LONG_READ).should( - 'not.be.checked', - ); - NewslettersPage.checkboxWithTitle(NEWSLETTERS.GREEN_LIGHT).should( - 'not.be.checked', - ); - NewslettersPage.checkboxWithTitle( - NewslettersPage.CONTENT.Consents.EVENTS, - ).should('not.be.checked'); - - CommunicationsPage.backButton().should('exist'); - CommunicationsPage.saveAndContinueButton().should('exist'); - }); - - it('correct newsletters shown for permissioned United States browser, none checked by default', () => { - const headers = getGeoLocationHeaders(GEOLOCATION_CODES.AMERICA); - cy.setEncryptedStateCookie({ - isCmpConsented: true, - }); - - cy.visit(NewslettersPage.URL, { headers, qs: { useIdapi: 'true' } }); - - NewslettersPage.checkboxWithTitle(NEWSLETTERS.FIRST_THING_US).should( - 'not.be.checked', - ); - NewslettersPage.checkboxWithTitle(NEWSLETTERS.HEADLINES_US).should( - 'not.be.checked', - ); - NewslettersPage.checkboxWithTitle(NEWSLETTERS.GREEN_LIGHT).should( - 'not.be.checked', - ); - NewslettersPage.checkboxWithTitle(NEWSLETTERS.OPINION_US).should( - 'not.be.checked', - ); - - cy.contains(NewslettersPage.CONTENT.Consents.EVENTS).should('not.exist'); - - CommunicationsPage.backButton().should('exist'); - CommunicationsPage.saveAndContinueButton().should('exist'); - }); - - it('correct newsletters shown for Australia, none checked by default', () => { - const headers = getGeoLocationHeaders(GEOLOCATION_CODES.AUSTRALIA); - - cy.visit(NewslettersPage.URL, { headers, qs: { useIdapi: 'true' } }); - - NewslettersPage.checkboxWithTitle(NEWSLETTERS.MORNING_MAIL_AU).should( - 'not.be.checked', - ); - NewslettersPage.checkboxWithTitle(NEWSLETTERS.LONG_READ).should( - 'not.be.checked', - ); - NewslettersPage.checkboxWithTitle(NEWSLETTERS.GREEN_LIGHT).should( - 'not.be.checked', - ); - NewslettersPage.checkboxWithTitle( - NewslettersPage.CONTENT.Consents.EVENTS, - ).should('not.be.checked'); - - CommunicationsPage.backButton().should('exist'); - CommunicationsPage.saveAndContinueButton().should('exist'); - }); - - it('correct localised newsletters shown for permissioned Australian browser, none checked by default', () => { - const headers = getGeoLocationHeaders(GEOLOCATION_CODES.AUSTRALIA); - - cy.setEncryptedStateCookie({ - isCmpConsented: true, - }); - cy.visit(NewslettersPage.URL, { headers, qs: { useIdapi: 'true' } }); - - NewslettersPage.checkboxWithTitle(NEWSLETTERS.MORNING_MAIL_AU).should( - 'not.be.checked', - ); - NewslettersPage.checkboxWithTitle(NEWSLETTERS.AFTERNOON_UPDATE_AU).should( - 'not.be.checked', - ); - NewslettersPage.checkboxWithTitle(NEWSLETTERS.FIVE_GREAT_READS_AU).should( - 'not.be.checked', - ); - NewslettersPage.checkboxWithTitle(NEWSLETTERS.SAVED_FOR_LATER_AU).should( - 'not.be.checked', - ); - cy.contains(NewslettersPage.CONTENT.Consents.EVENTS).should('not.exist'); - - CommunicationsPage.backButton().should('exist'); - CommunicationsPage.saveAndContinueButton().should('exist'); - }); - - it('correct newsletters shown for rest of the world, none checked by default', () => { - const headers = getGeoLocationHeaders(GEOLOCATION_CODES.OTHERS); - - cy.visit(NewslettersPage.URL, { headers, qs: { useIdapi: 'true' } }); - - NewslettersPage.checkboxWithTitle(NEWSLETTERS.FIRST_EDITION_UK).should( - 'not.be.checked', - ); - NewslettersPage.checkboxWithTitle(NEWSLETTERS.LONG_READ).should( - 'not.be.checked', - ); - NewslettersPage.checkboxWithTitle(NEWSLETTERS.GREEN_LIGHT).should( - 'not.be.checked', - ); - NewslettersPage.checkboxWithTitle( - NewslettersPage.CONTENT.Consents.EVENTS, - ).should('not.be.checked'); - - CommunicationsPage.backButton().should('exist'); - CommunicationsPage.saveAndContinueButton().should('exist'); - }); - - it('show already selected newsletters / consents', () => { - const newslettersToSubscribe = [{ listId: 4147 }, { listId: 4165 }]; - cy.mockAll( - 200, - userNewsletters(newslettersToSubscribe), - NEWSLETTER_SUBSCRIPTION_ENDPOINT, - ); - const consented = getUserConsents(['events']); - cy.mockAll(200, consented, USER_CONSENTS_ENDPOINT); - NewslettersPage.goto({ - query: { - useIdapi: true, - }, - }); - - NewslettersPage.checkboxWithTitle(NEWSLETTERS.FIRST_EDITION_UK).should( - 'not.be.checked', - ); - NewslettersPage.checkboxWithTitle(NEWSLETTERS.LONG_READ).should( - 'be.checked', - ); - NewslettersPage.checkboxWithTitle(NEWSLETTERS.GREEN_LIGHT).should( - 'be.checked', - ); - NewslettersPage.checkboxWithTitle( - NewslettersPage.CONTENT.Consents.EVENTS, - ).should('be.checked'); - }); - - it('displays a relevant error on newsletters endpoint failure', () => { - cy.mockAll(500, {}, NEWSLETTER_ENDPOINT); - NewslettersPage.goto({ - query: { - useIdapi: true, - }, - }); - NewslettersPage.errorBanner().contains(NEWSLETTER_ERRORS.GENERIC); - NewslettersPage.backButton().should('not.exist'); - NewslettersPage.saveAndContinueButton().should('not.exist'); - }); - - it('displays a relevant error on newsletters subscription endpoint failure', () => { - cy.mockAll(500, {}, NEWSLETTER_SUBSCRIPTION_ENDPOINT); - NewslettersPage.goto({ - query: { - useIdapi: true, - }, - }); - NewslettersPage.errorBanner().contains(NEWSLETTER_ERRORS.GENERIC); - NewslettersPage.backButton().should('not.exist'); - NewslettersPage.saveAndContinueButton().should('not.exist'); - }); - }); - - context('Your data page', () => { - beforeEach(() => { - setAuthCookies(); - cy.mockAll( - 200, - authRedirectSignInRecentlyEmailValidated, - AUTH_REDIRECT_ENDPOINT, - ); - cy.mockAll(200, allConsents, CONSENTS_ENDPOINT); - cy.mockAll( - 200, - verifiedUserWithNoConsent.user.consents, - USER_CONSENTS_ENDPOINT, - ); - }); - - it('has no detectable a11y violations', () => { - YourDataPage.goto({ - query: { - useIdapi: true, - }, - }); - injectAndCheckAxe(); - }); - - it('had no detectable a11y voilations if previously selected consent', () => { - cy.mockAll(200, optedOutUserConsent, USER_CONSENTS_ENDPOINT); - YourDataPage.goto({ - query: { - useIdapi: true, - }, - }); - injectAndCheckAxe(); - }); - - it('has no detectable a11y violations on with an error', () => { - cy.mockAll(500, {}, CONSENTS_ENDPOINT); - YourDataPage.goto({ - query: { - useIdapi: true, - }, - }); - injectAndCheckAxe(); - }); - - it('displays the marketing profile opt in switch, toggled ON by default', () => { - YourDataPage.goto({ - query: { - useIdapi: true, - }, - }); - YourDataPage.marketingOptInSwitch().should('be.checked'); - }); - - it('displays the marketing profile opt in switch, toggled OFF if the user has previously opted out', () => { - cy.mockAll(200, optedOutUserConsent, USER_CONSENTS_ENDPOINT); - YourDataPage.goto({ - query: { - useIdapi: true, - }, - }); - YourDataPage.marketingOptInSwitch().should('not.be.checked'); - }); - - it('displays the personalised advertising permission if user has all CMP consents, toggled OFF by default', () => { - cy.enableCMP(); - YourDataPage.goto({ - query: { - useIdapi: true, - }, - }); - cy.acceptCMP(); - - YourDataPage.marketingOptInSwitch().should('be.checked'); - YourDataPage.personalisedAdvertisingOptInInput().should('not.be.checked'); - }); - - it('displays the personalised advertising permission if user has all CMP consents, toggled ON if user previously opted in', () => { - cy.enableCMP(); - cy.mockAll( - 200, - optedIntoPersonalisedAdvertisingUserConsent, - USER_CONSENTS_ENDPOINT, - ); - YourDataPage.goto({ - query: { - useIdapi: true, - }, - }); - cy.acceptCMP(); - - YourDataPage.marketingOptInSwitch().should('be.checked'); - YourDataPage.personalisedAdvertisingOptInInput().should('be.checked'); - }); - - it('does not display the personalised advertising permission if user does not have CMP consents set', () => { - YourDataPage.goto({ - query: { - useIdapi: true, - }, - }); - YourDataPage.personalisedAdvertisingOptIn().should('not.exist'); - }); - - it('does not display the personalised advertising permission if user has not accepted CMP consents', () => { - cy.enableCMP(); - YourDataPage.goto({ - query: { - useIdapi: true, - }, - }); - cy.declineCMP(); - - YourDataPage.personalisedAdvertisingOptIn().should('not.exist'); - }); - - it('does not display the personalised advertising permission if user has all CMP consents, but has an ad free gu cookie', () => { - cy.enableCMP(); - cy.setAdFreeCookie(); - YourDataPage.goto({ - query: { - useIdapi: true, - }, - }); - cy.acceptCMP(); - - YourDataPage.personalisedAdvertisingOptIn().should('not.exist'); - }); - - it('display a relevant error message on user end point failure', () => { - cy.mockAll(500, {}, USER_CONSENTS_ENDPOINT); - YourDataPage.goto({ - query: { - useIdapi: true, - }, - }); - YourDataPage.errorBanner().contains(CONSENT_ERRORS.GENERIC); - YourDataPage.backButton().should('not.exist'); - YourDataPage.saveAndContinueButton().should('not.exist'); - }); - - it('displays a relevant error on consents endpoint failure', () => { - cy.mockAll(500, {}, CONSENTS_ENDPOINT); - YourDataPage.goto({ - query: { - useIdapi: true, - }, - }); - YourDataPage.errorBanner().contains(CONSENT_ERRORS.GENERIC); - YourDataPage.backButton().should('not.exist'); - YourDataPage.saveAndContinueButton().should('not.exist'); - }); - }); - - context('Review page', () => { - beforeEach(() => { - setAuthCookies(); - cy.mockAll( - 200, - authRedirectSignInRecentlyEmailValidated, - AUTH_REDIRECT_ENDPOINT, - ); - cy.mockAll(200, allConsents, CONSENTS_ENDPOINT); - cy.mockAll( - 200, - verifiedUserWithNoConsent.user.consents, - USER_CONSENTS_ENDPOINT, - ); - cy.mockAll(200, allNewsletters, NEWSLETTER_ENDPOINT); - cy.mockAll(200, userNewsletters(), NEWSLETTER_SUBSCRIPTION_ENDPOINT); - }); - - it('has no detectable a11y violations', () => { - ReviewPage.goto({ - query: { - useIdapi: true, - }, - }); - injectAndCheckAxe(); - }); - - it('has no detectable a11y violations on with an error', () => { - cy.mockAll(500, {}, CONSENTS_ENDPOINT); - ReviewPage.goto({ - query: { - useIdapi: true, - }, - }); - injectAndCheckAxe(); - }); - - it('displays a relevant error if on consents endpoint failure', () => { - cy.mockAll(500, {}, CONSENTS_ENDPOINT); - ReviewPage.goto({ - query: { - useIdapi: true, - }, - }); - ReviewPage.errorBanner().contains(CONSENT_ERRORS.GENERIC); - }); - - it('display a relevant error message on user end point failure', () => { - cy.mockAll(500, {}, USER_CONSENTS_ENDPOINT); - ReviewPage.goto({ - query: { - useIdapi: true, - }, - }); - ReviewPage.errorBanner().contains(CONSENT_ERRORS.GENERIC); - }); - - it('displays a relevant error on newsletters endpoint failure', () => { - cy.mockAll(500, {}, NEWSLETTER_ENDPOINT); - ReviewPage.goto({ - query: { - useIdapi: true, - }, - }); - ReviewPage.errorBanner().contains(NEWSLETTER_ERRORS.GENERIC); - }); - - it('displays a relevant error on newsletters subscription endpoint failure', () => { - cy.mockAll(500, {}, NEWSLETTER_SUBSCRIPTION_ENDPOINT); - ReviewPage.goto({ - query: { - useIdapi: true, - }, - }); - ReviewPage.errorBanner().contains(NEWSLETTER_ERRORS.GENERIC); - }); - }); - - context('Not found', () => { - beforeEach(() => { - setAuthCookies(); - cy.mockAll( - 200, - authRedirectSignInRecentlyEmailValidated, - AUTH_REDIRECT_ENDPOINT, - ); - }); - - it('shows 404 page if onboarding page is not found', () => { - cy.visit('/consents/unknown', { - failOnStatusCode: false, - qs: { useIdapi: true }, - }); - cy.contains('the page does not exist'); - }); - }); + YourDataPage.marketingOptInSwitch().should('be.checked'); + YourDataPage.marketingOptInClickableSection().click(); + YourDataPage.marketingOptInSwitch().should('not.be.checked'); + YourDataPage.personalisedAdvertisingOptInSwitch().should('exist'); + YourDataPage.personalisedAdvertisingOptInInput().should('not.be.checked'); + + // mock form save success + cy.mockNext(200); + + cy.mockAll(200, createUser(optedOutUserConsent), USER_ENDPOINT); + cy.mockAll(200, optedOutUserConsent, USER_CONSENTS_ENDPOINT); + + YourDataPage.saveAndContinueButton().click(); + // Explicity check '_optin' consents are in inverted back to '_optouts' when posted + cy.lastPayloadIs([ + { id: 'profiling_optout', consented: true }, + { id: 'personalised_advertising', consented: false }, + ]); + + cy.url().should('include', ReviewPage.URL); + cy.url().should('include', `returnUrl=${returnUrl}`); + + ReviewPage.backButton().should('not.exist'); + ReviewPage.saveAndContinueButton().should('not.exist'); + + // contains no opted in newsletters + Object.values(ReviewPage.CONTENT.NEWSLETTERS).forEach((newsletter) => + cy.contains(newsletter).should('not.exist'), + ); + + // contains no consents + cy.contains(ReviewPage.CONTENT.SUPPORTER_CONSENT).should('not.exist'); + cy.contains(ReviewPage.CONTENT.PROFILING_CONSENT).should('not.exist'); + cy.contains(ReviewPage.CONTENT.PERSONALISED_ADVERTISING_CONSENT).should( + 'not.exist', + ); + + // contains messaging encouraging user to explore other newsletters + cy.contains(ReviewPage.CONTENT.NO_NEWSLETTERS_TITLE); + + ReviewPage.returnButton() + .should('have.attr', 'href') + .and('include', decodeURIComponent(returnUrl)); + }); + + it('uses a default returnUrl if none provided', () => { + const returnUrl = encodeURIComponent(Cypress.env('DEFAULT_RETURN_URI')); + + CommunicationsPage.gotoFlowStart({ + query: { + useIdapi: 'true', + }, + }); + + cy.url().should('include', CommunicationsPage.URL); + cy.url().should('include', `returnUrl=${returnUrl}`); + + // mock form save success + cy.mockNext(200); + + CommunicationsPage.saveAndContinueButton().click(); + + cy.url().should('include', NewslettersPage.URL); + cy.url().should('include', `returnUrl=${returnUrl}`); + + // mock form save success + cy.mockNext(200); + + NewslettersPage.saveAndContinueButton().click(); + + cy.url().should('include', YourDataPage.URL); + cy.url().should('include', `returnUrl=${returnUrl}`); + + YourDataPage.marketingOptInClickableSection().click(); + + // mock form save success + cy.mockNext(200); + + YourDataPage.saveAndContinueButton().click(); + cy.url().should('include', ReviewPage.URL); + cy.url().should('include', `returnUrl=${returnUrl}`); + + ReviewPage.returnButton() + .should('have.attr', 'href') + .and('include', decodeURIComponent(returnUrl)); + }); + }); + + context('Login middleware', () => { + it('no sc_gu_u cookie, redirect to login page', () => { + const signInUrl = Cypress.env('SIGN_IN_PAGE_URL'); + cy.setCookie('GU_U', 'FAKE_GU_U'); + cy.setCookie('SC_GU_LA', 'FAKE_SC_GU_LA'); + + cy.request({ + url: `${Onboarding.URL}?useIdapi=true`, + followRedirect: false, + }).then((res) => { + expect(res.status).to.eq(303); + expect(res.redirectedToUrl).to.include(signInUrl); + }); + }); + + it('no sc_gu_la cookie, redirect to login page', () => { + const signInUrl = Cypress.env('SIGN_IN_PAGE_URL'); + cy.setCookie('GU_U', 'FAKE_GU_U'); + cy.setCookie('SC_GU_U', 'FAKE_SC_GU_U'); + + cy.request({ + url: `${Onboarding.URL}?useIdapi=true`, + followRedirect: false, + }).then((res) => { + expect(res.status).to.eq(303); + expect(res.redirectedToUrl).to.include(signInUrl); + }); + }); + + it('email not validated, go to verify email page', () => { + setAuthCookies(); + const emailNotValidatedResponse = { + signInStatus: 'signedInRecently', + emailValidated: false, + redirect: null, + }; + cy.mockAll(200, emailNotValidatedResponse, AUTH_REDIRECT_ENDPOINT); + + cy.request({ + url: `${Onboarding.URL}?useIdapi=true`, + followRedirect: false, + }).then((res) => { + expect(res.status).to.eq(303); + expect(res.redirectedToUrl).to.include(VerifyEmail.URL); + }); + }); + + it('recently logged in, go to consents flow', () => { + setAuthCookies(); + cy.mockAll( + 200, + authRedirectSignInRecentlyEmailValidated, + AUTH_REDIRECT_ENDPOINT, + ); + cy.request({ + url: `${Onboarding.URL}?useIdapi=true`, + followRedirect: false, + }).then((res) => { + expect(res.status).to.eq(303); + expect(res.redirectedToUrl).to.include(CommunicationsPage.URL); + }); + }); + + it('not recently logged in, follows supplied redirect', () => { + setAuthCookies(); + const emailNotValidatedResponse = { + signInStatus: 'signedInNotRecently', + emailValidated: true, + redirect: { + url: 'https://fakeloginfortest.code.dev-theguardian.co.uk', + }, + }; + cy.mockAll(200, emailNotValidatedResponse, AUTH_REDIRECT_ENDPOINT); + + cy.request({ + url: `${Onboarding.URL}?useIdapi=true`, + followRedirect: false, + }).then((res) => { + expect(res.status).to.eq(303); + expect(res.redirectedToUrl).to.include( + 'https://fakeloginfortest.code.dev-theguardian.co.uk', + ); + }); + }); + + it('if missing redirect information, it redirects to the default ', () => { + const signInUrl = Cypress.env('SIGN_IN_PAGE_URL'); + setAuthCookies(); + const emailNotValidatedResponse = { + signInStatus: 'signedInNotRecently', + emailValidated: true, + redirect: undefined, + }; + + cy.mockAll(200, emailNotValidatedResponse, AUTH_REDIRECT_ENDPOINT); + + cy.request({ + url: `${Onboarding.URL}?useIdapi=true`, + followRedirect: false, + }).then((res) => { + expect(res.status).to.eq(303); + expect(res.redirectedToUrl).to.include(signInUrl); + }); + }); + + it('on idapi error it redirects to the sign in page with the error flag set', () => { + setAuthCookies(); + cy.mockAll(502, 'gateway error', AUTH_REDIRECT_ENDPOINT); + cy.request({ + url: `${Onboarding.URL}?useIdapi=true`, + followRedirect: false, + }).then((res) => { + expect(res.status).to.eq(303); + expect(res.redirectedToUrl).to.include( + `error=signin-error-bad-request`, + ); + }); + }); + }); + + context('Contact options page', () => { + beforeEach(() => { + setAuthCookies(); + cy.mockAll( + 200, + authRedirectSignInRecentlyEmailValidated, + AUTH_REDIRECT_ENDPOINT, + ); + cy.mockAll(200, allConsents, CONSENTS_ENDPOINT); + }); + + it('has no detectable a11y violations', () => { + cy.mockAll( + 200, + verifiedUserWithNoConsent.user.consents, + USER_CONSENTS_ENDPOINT, + ); + CommunicationsPage.goto({ + query: { + useIdapi: true, + }, + }); + injectAndCheckAxe(); + }); + + it('had no detectable a11y voilations if previously selected consents', () => { + const consented = getUserConsents(['jobs', 'offers']); + cy.mockAll(200, consented, USER_CONSENTS_ENDPOINT); + CommunicationsPage.goto({ + query: { + useIdapi: true, + }, + }); + injectAndCheckAxe(); + }); + + it('has no detectable a11y violations on with an error', () => { + cy.mockAll(500, {}, USER_ENDPOINT); + CommunicationsPage.goto({ + query: { + useIdapi: true, + }, + }); + injectAndCheckAxe(); + }); + + it('shows correct contact options, none checked by default', () => { + cy.mockAll( + 200, + verifiedUserWithNoConsent.user.consents, + USER_CONSENTS_ENDPOINT, + ); + CommunicationsPage.goto({ + query: { + useIdapi: true, + }, + }); + CommunicationsPage.backButton().should('not.exist'); + CommunicationsPage.allCheckboxes().should('not.be.checked'); + }); + + it('shows any previously selected consents', () => { + const consented = getUserConsents(['supporter']); + cy.mockAll(200, consented, USER_CONSENTS_ENDPOINT); + CommunicationsPage.goto({ + query: { + useIdapi: true, + }, + }); + CommunicationsPage.backButton().should('not.exist'); + CommunicationsPage.consentCheckboxWithTitle( + 'Supporting the Guardian', + ).should('be.checked'); + }); + + it('displays a relevant error message on user end point failure', () => { + cy.mockAll(500, {}, USER_CONSENTS_ENDPOINT); + CommunicationsPage.goto({ + query: { + useIdapi: true, + }, + }); + CommunicationsPage.errorBanner().contains(CONSENT_ERRORS.GENERIC); + CommunicationsPage.backButton().should('not.exist'); + CommunicationsPage.saveAndContinueButton().should('not.exist'); + }); + + it('displays a relevant error on consents endpoint failure', () => { + cy.mockAll(500, {}, CONSENTS_ENDPOINT); + CommunicationsPage.goto({ + query: { + useIdapi: true, + }, + }); + CommunicationsPage.errorBanner().contains(CONSENT_ERRORS.GENERIC); + CommunicationsPage.backButton().should('not.exist'); + CommunicationsPage.saveAndContinueButton().should('not.exist'); + }); + }); + + context('Newsletters page', () => { + beforeEach(() => { + setAuthCookies(); + cy.mockAll( + 200, + authRedirectSignInRecentlyEmailValidated, + AUTH_REDIRECT_ENDPOINT, + ); + cy.mockAll(200, allNewsletters, NEWSLETTER_ENDPOINT); + cy.mockAll(200, userNewsletters(), NEWSLETTER_SUBSCRIPTION_ENDPOINT); + cy.mockAll(200, allConsents, CONSENTS_ENDPOINT); + cy.mockAll( + 200, + verifiedUserWithNoConsent.user.consents, + USER_CONSENTS_ENDPOINT, + ); + }); + + it('has no detectable a11y violations', () => { + const headers = getGeoLocationHeaders(GEOLOCATION_CODES.GB); + + cy.visit(NewslettersPage.URL, { headers, qs: { useIdapi: 'true' } }); + injectAndCheckAxe(); + }); + + it('had no detectable a11y voilations if previously selected newsletter', () => { + const newslettersToSubscribe = [{ listId: 4147 }, { listId: 4165 }]; + cy.mockAll( + 200, + userNewsletters(newslettersToSubscribe), + NEWSLETTER_SUBSCRIPTION_ENDPOINT, + ); + NewslettersPage.goto({ + query: { + useIdapi: true, + }, + }); + injectAndCheckAxe(); + }); + + it('has no detectable a11y violations on with an error', () => { + cy.mockAll(500, {}, NEWSLETTER_ENDPOINT); + NewslettersPage.goto({ + query: { + useIdapi: true, + }, + }); + injectAndCheckAxe(); + }); + + it('correct newsletters shown for uk, none checked by default', () => { + const headers = getGeoLocationHeaders(GEOLOCATION_CODES.GB); + + cy.visit(NewslettersPage.URL, { headers, qs: { useIdapi: 'true' } }); + + NewslettersPage.checkboxWithTitle(NEWSLETTERS.FIRST_EDITION_UK).should( + 'not.be.checked', + ); + NewslettersPage.checkboxWithTitle(NEWSLETTERS.LONG_READ).should( + 'not.be.checked', + ); + NewslettersPage.checkboxWithTitle(NEWSLETTERS.GREEN_LIGHT).should( + 'not.be.checked', + ); + NewslettersPage.checkboxWithTitle( + NewslettersPage.CONTENT.Consents.EVENTS, + ).should('not.be.checked'); + + CommunicationsPage.backButton().should('exist'); + CommunicationsPage.saveAndContinueButton().should('exist'); + }); + + it('correct newsletters shown for United States of America, none checked by default', () => { + const headers = getGeoLocationHeaders(GEOLOCATION_CODES.AMERICA); + + cy.visit(NewslettersPage.URL, { headers, qs: { useIdapi: 'true' } }); + + NewslettersPage.checkboxWithTitle(NEWSLETTERS.FIRST_THING_US).should( + 'not.be.checked', + ); + NewslettersPage.checkboxWithTitle(NEWSLETTERS.LONG_READ).should( + 'not.be.checked', + ); + NewslettersPage.checkboxWithTitle(NEWSLETTERS.GREEN_LIGHT).should( + 'not.be.checked', + ); + NewslettersPage.checkboxWithTitle( + NewslettersPage.CONTENT.Consents.EVENTS, + ).should('not.be.checked'); + + CommunicationsPage.backButton().should('exist'); + CommunicationsPage.saveAndContinueButton().should('exist'); + }); + + it('correct newsletters shown for permissioned United States browser, none checked by default', () => { + const headers = getGeoLocationHeaders(GEOLOCATION_CODES.AMERICA); + cy.setEncryptedStateCookie({ + isCmpConsented: true, + }); + + cy.visit(NewslettersPage.URL, { headers, qs: { useIdapi: 'true' } }); + + NewslettersPage.checkboxWithTitle(NEWSLETTERS.FIRST_THING_US).should( + 'not.be.checked', + ); + NewslettersPage.checkboxWithTitle(NEWSLETTERS.HEADLINES_US).should( + 'not.be.checked', + ); + NewslettersPage.checkboxWithTitle(NEWSLETTERS.GREEN_LIGHT).should( + 'not.be.checked', + ); + NewslettersPage.checkboxWithTitle(NEWSLETTERS.OPINION_US).should( + 'not.be.checked', + ); + + cy.contains(NewslettersPage.CONTENT.Consents.EVENTS).should('not.exist'); + + CommunicationsPage.backButton().should('exist'); + CommunicationsPage.saveAndContinueButton().should('exist'); + }); + + it('correct newsletters shown for Australia, none checked by default', () => { + const headers = getGeoLocationHeaders(GEOLOCATION_CODES.AUSTRALIA); + + cy.visit(NewslettersPage.URL, { headers, qs: { useIdapi: 'true' } }); + + NewslettersPage.checkboxWithTitle(NEWSLETTERS.MORNING_MAIL_AU).should( + 'not.be.checked', + ); + NewslettersPage.checkboxWithTitle(NEWSLETTERS.LONG_READ).should( + 'not.be.checked', + ); + NewslettersPage.checkboxWithTitle(NEWSLETTERS.GREEN_LIGHT).should( + 'not.be.checked', + ); + NewslettersPage.checkboxWithTitle( + NewslettersPage.CONTENT.Consents.EVENTS, + ).should('not.be.checked'); + + CommunicationsPage.backButton().should('exist'); + CommunicationsPage.saveAndContinueButton().should('exist'); + }); + + it('correct localised newsletters shown for permissioned Australian browser, none checked by default', () => { + const headers = getGeoLocationHeaders(GEOLOCATION_CODES.AUSTRALIA); + + cy.setEncryptedStateCookie({ + isCmpConsented: true, + }); + cy.visit(NewslettersPage.URL, { headers, qs: { useIdapi: 'true' } }); + + NewslettersPage.checkboxWithTitle(NEWSLETTERS.MORNING_MAIL_AU).should( + 'not.be.checked', + ); + NewslettersPage.checkboxWithTitle(NEWSLETTERS.AFTERNOON_UPDATE_AU).should( + 'not.be.checked', + ); + NewslettersPage.checkboxWithTitle(NEWSLETTERS.FIVE_GREAT_READS_AU).should( + 'not.be.checked', + ); + NewslettersPage.checkboxWithTitle(NEWSLETTERS.SAVED_FOR_LATER_AU).should( + 'not.be.checked', + ); + cy.contains(NewslettersPage.CONTENT.Consents.EVENTS).should('not.exist'); + + CommunicationsPage.backButton().should('exist'); + CommunicationsPage.saveAndContinueButton().should('exist'); + }); + + it('correct newsletters shown for rest of the world, none checked by default', () => { + const headers = getGeoLocationHeaders(GEOLOCATION_CODES.OTHERS); + + cy.visit(NewslettersPage.URL, { headers, qs: { useIdapi: 'true' } }); + + NewslettersPage.checkboxWithTitle(NEWSLETTERS.FIRST_EDITION_UK).should( + 'not.be.checked', + ); + NewslettersPage.checkboxWithTitle(NEWSLETTERS.LONG_READ).should( + 'not.be.checked', + ); + NewslettersPage.checkboxWithTitle(NEWSLETTERS.GREEN_LIGHT).should( + 'not.be.checked', + ); + NewslettersPage.checkboxWithTitle( + NewslettersPage.CONTENT.Consents.EVENTS, + ).should('not.be.checked'); + + CommunicationsPage.backButton().should('exist'); + CommunicationsPage.saveAndContinueButton().should('exist'); + }); + + it('show already selected newsletters / consents', () => { + const newslettersToSubscribe = [{ listId: 4147 }, { listId: 4165 }]; + cy.mockAll( + 200, + userNewsletters(newslettersToSubscribe), + NEWSLETTER_SUBSCRIPTION_ENDPOINT, + ); + const consented = getUserConsents(['events']); + cy.mockAll(200, consented, USER_CONSENTS_ENDPOINT); + NewslettersPage.goto({ + query: { + useIdapi: true, + }, + }); + + NewslettersPage.checkboxWithTitle(NEWSLETTERS.FIRST_EDITION_UK).should( + 'not.be.checked', + ); + NewslettersPage.checkboxWithTitle(NEWSLETTERS.LONG_READ).should( + 'be.checked', + ); + NewslettersPage.checkboxWithTitle(NEWSLETTERS.GREEN_LIGHT).should( + 'be.checked', + ); + NewslettersPage.checkboxWithTitle( + NewslettersPage.CONTENT.Consents.EVENTS, + ).should('be.checked'); + }); + + it('displays a relevant error on newsletters endpoint failure', () => { + cy.mockAll(500, {}, NEWSLETTER_ENDPOINT); + NewslettersPage.goto({ + query: { + useIdapi: true, + }, + }); + NewslettersPage.errorBanner().contains(NEWSLETTER_ERRORS.GENERIC); + NewslettersPage.backButton().should('not.exist'); + NewslettersPage.saveAndContinueButton().should('not.exist'); + }); + + it('displays a relevant error on newsletters subscription endpoint failure', () => { + cy.mockAll(500, {}, NEWSLETTER_SUBSCRIPTION_ENDPOINT); + NewslettersPage.goto({ + query: { + useIdapi: true, + }, + }); + NewslettersPage.errorBanner().contains(NEWSLETTER_ERRORS.GENERIC); + NewslettersPage.backButton().should('not.exist'); + NewslettersPage.saveAndContinueButton().should('not.exist'); + }); + }); + + context('Your data page', () => { + beforeEach(() => { + setAuthCookies(); + cy.mockAll( + 200, + authRedirectSignInRecentlyEmailValidated, + AUTH_REDIRECT_ENDPOINT, + ); + cy.mockAll(200, allConsents, CONSENTS_ENDPOINT); + cy.mockAll( + 200, + verifiedUserWithNoConsent.user.consents, + USER_CONSENTS_ENDPOINT, + ); + }); + + it('has no detectable a11y violations', () => { + YourDataPage.goto({ + query: { + useIdapi: true, + }, + }); + injectAndCheckAxe(); + }); + + it('had no detectable a11y voilations if previously selected consent', () => { + cy.mockAll(200, optedOutUserConsent, USER_CONSENTS_ENDPOINT); + YourDataPage.goto({ + query: { + useIdapi: true, + }, + }); + injectAndCheckAxe(); + }); + + it('has no detectable a11y violations on with an error', () => { + cy.mockAll(500, {}, CONSENTS_ENDPOINT); + YourDataPage.goto({ + query: { + useIdapi: true, + }, + }); + injectAndCheckAxe(); + }); + + it('displays the marketing profile opt in switch, toggled ON by default', () => { + YourDataPage.goto({ + query: { + useIdapi: true, + }, + }); + YourDataPage.marketingOptInSwitch().should('be.checked'); + }); + + it('displays the marketing profile opt in switch, toggled OFF if the user has previously opted out', () => { + cy.mockAll(200, optedOutUserConsent, USER_CONSENTS_ENDPOINT); + YourDataPage.goto({ + query: { + useIdapi: true, + }, + }); + YourDataPage.marketingOptInSwitch().should('not.be.checked'); + }); + + it('displays the personalised advertising permission if user has all CMP consents, toggled OFF by default', () => { + cy.enableCMP(); + YourDataPage.goto({ + query: { + useIdapi: true, + }, + }); + cy.acceptCMP(); + + YourDataPage.marketingOptInSwitch().should('be.checked'); + YourDataPage.personalisedAdvertisingOptInInput().should('not.be.checked'); + }); + + it('displays the personalised advertising permission if user has all CMP consents, toggled ON if user previously opted in', () => { + cy.enableCMP(); + cy.mockAll( + 200, + optedIntoPersonalisedAdvertisingUserConsent, + USER_CONSENTS_ENDPOINT, + ); + YourDataPage.goto({ + query: { + useIdapi: true, + }, + }); + cy.acceptCMP(); + + YourDataPage.marketingOptInSwitch().should('be.checked'); + YourDataPage.personalisedAdvertisingOptInInput().should('be.checked'); + }); + + it('does not display the personalised advertising permission if user does not have CMP consents set', () => { + YourDataPage.goto({ + query: { + useIdapi: true, + }, + }); + YourDataPage.personalisedAdvertisingOptIn().should('not.exist'); + }); + + it('does not display the personalised advertising permission if user has not accepted CMP consents', () => { + cy.enableCMP(); + YourDataPage.goto({ + query: { + useIdapi: true, + }, + }); + cy.declineCMP(); + + YourDataPage.personalisedAdvertisingOptIn().should('not.exist'); + }); + + it('does not display the personalised advertising permission if user has all CMP consents, but has an ad free gu cookie', () => { + cy.enableCMP(); + cy.setAdFreeCookie(); + YourDataPage.goto({ + query: { + useIdapi: true, + }, + }); + cy.acceptCMP(); + + YourDataPage.personalisedAdvertisingOptIn().should('not.exist'); + }); + + it('display a relevant error message on user end point failure', () => { + cy.mockAll(500, {}, USER_CONSENTS_ENDPOINT); + YourDataPage.goto({ + query: { + useIdapi: true, + }, + }); + YourDataPage.errorBanner().contains(CONSENT_ERRORS.GENERIC); + YourDataPage.backButton().should('not.exist'); + YourDataPage.saveAndContinueButton().should('not.exist'); + }); + + it('displays a relevant error on consents endpoint failure', () => { + cy.mockAll(500, {}, CONSENTS_ENDPOINT); + YourDataPage.goto({ + query: { + useIdapi: true, + }, + }); + YourDataPage.errorBanner().contains(CONSENT_ERRORS.GENERIC); + YourDataPage.backButton().should('not.exist'); + YourDataPage.saveAndContinueButton().should('not.exist'); + }); + }); + + context('Review page', () => { + beforeEach(() => { + setAuthCookies(); + cy.mockAll( + 200, + authRedirectSignInRecentlyEmailValidated, + AUTH_REDIRECT_ENDPOINT, + ); + cy.mockAll(200, allConsents, CONSENTS_ENDPOINT); + cy.mockAll( + 200, + verifiedUserWithNoConsent.user.consents, + USER_CONSENTS_ENDPOINT, + ); + cy.mockAll(200, allNewsletters, NEWSLETTER_ENDPOINT); + cy.mockAll(200, userNewsletters(), NEWSLETTER_SUBSCRIPTION_ENDPOINT); + }); + + it('has no detectable a11y violations', () => { + ReviewPage.goto({ + query: { + useIdapi: true, + }, + }); + injectAndCheckAxe(); + }); + + it('has no detectable a11y violations on with an error', () => { + cy.mockAll(500, {}, CONSENTS_ENDPOINT); + ReviewPage.goto({ + query: { + useIdapi: true, + }, + }); + injectAndCheckAxe(); + }); + + it('displays a relevant error if on consents endpoint failure', () => { + cy.mockAll(500, {}, CONSENTS_ENDPOINT); + ReviewPage.goto({ + query: { + useIdapi: true, + }, + }); + ReviewPage.errorBanner().contains(CONSENT_ERRORS.GENERIC); + }); + + it('display a relevant error message on user end point failure', () => { + cy.mockAll(500, {}, USER_CONSENTS_ENDPOINT); + ReviewPage.goto({ + query: { + useIdapi: true, + }, + }); + ReviewPage.errorBanner().contains(CONSENT_ERRORS.GENERIC); + }); + + it('displays a relevant error on newsletters endpoint failure', () => { + cy.mockAll(500, {}, NEWSLETTER_ENDPOINT); + ReviewPage.goto({ + query: { + useIdapi: true, + }, + }); + ReviewPage.errorBanner().contains(NEWSLETTER_ERRORS.GENERIC); + }); + + it('displays a relevant error on newsletters subscription endpoint failure', () => { + cy.mockAll(500, {}, NEWSLETTER_SUBSCRIPTION_ENDPOINT); + ReviewPage.goto({ + query: { + useIdapi: true, + }, + }); + ReviewPage.errorBanner().contains(NEWSLETTER_ERRORS.GENERIC); + }); + }); + + context('Not found', () => { + beforeEach(() => { + setAuthCookies(); + cy.mockAll( + 200, + authRedirectSignInRecentlyEmailValidated, + AUTH_REDIRECT_ENDPOINT, + ); + }); + + it('shows 404 page if onboarding page is not found', () => { + cy.visit('/consents/unknown', { + failOnStatusCode: false, + qs: { useIdapi: true }, + }); + cy.contains('the page does not exist'); + }); + }); }); diff --git a/cypress/integration/mocked/post_sign_in_prompt.3.cy.ts b/cypress/integration/mocked/post_sign_in_prompt.3.cy.ts index c1b9fc2114..c35d683503 100644 --- a/cypress/integration/mocked/post_sign_in_prompt.3.cy.ts +++ b/cypress/integration/mocked/post_sign_in_prompt.3.cy.ts @@ -1,12 +1,12 @@ import { - authRedirectSignInRecentlyEmailValidated, - AUTH_REDIRECT_ENDPOINT, + authRedirectSignInRecentlyEmailValidated, + AUTH_REDIRECT_ENDPOINT, } from '../../support/idapi/auth'; import { allConsents, CONSENTS_ENDPOINT } from '../../support/idapi/consent'; import { - verifiedUserWithNoConsent, - createUser, - USER_ENDPOINT, + verifiedUserWithNoConsent, + createUser, + USER_ENDPOINT, } from '../../support/idapi/user'; import { setAuthCookies } from '../../support/idapi/cookie'; import { injectAndCheckAxe } from '../../support/cypress-axe'; @@ -14,129 +14,129 @@ import { injectAndCheckAxe } from '../../support/cypress-axe'; // TODO: remove this once we have migrated fully to OAuth as we're unable to mock the OAuth flow to get Tokens // TODO: best thing to do will move as much as possible to the ete tests describe('Post sign-in prompt', () => { - const defaultReturnUrl = 'https://m.code.dev-theguardian.com'; - const returnUrl = 'https://www.theguardian.com/about'; - - beforeEach(() => { - cy.mockPurge(); - setAuthCookies(); - cy.mockAll( - 200, - authRedirectSignInRecentlyEmailValidated, - AUTH_REDIRECT_ENDPOINT, - ); - cy.mockAll(200, allConsents, CONSENTS_ENDPOINT); - cy.intercept('GET', defaultReturnUrl, (req) => { - req.reply(200); - }); - cy.intercept('GET', returnUrl, (req) => { - req.reply(200); - }); - }); - - it('has no detectable a11y violations on prompt page', () => { - cy.mockNext(200, verifiedUserWithNoConsent.user.consents); - cy.visit('/signin/success?useIdapi=true'); - injectAndCheckAxe(); - }); - - it('allows user to opt in and continue', () => { - cy.mockNext(200, verifiedUserWithNoConsent.user.consents); - cy.visit('/signin/success?useIdapi=true'); - const checkbox = cy.findByLabelText('Yes, sign me up'); - checkbox.should('not.be.checked'); - checkbox.click(); - - // mock form save success - cy.mockNext(200, {}); - - cy.findByText('Continue to the Guardian').click(); - cy.lastPayloadIs([{ id: 'supporter', consented: true }]); - cy.url().should('include', defaultReturnUrl); - }); - - it('allows user to opt out and continue', () => { - const supporterConsent = allConsents.find(({ id }) => id === 'supporter'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const consentData = [{ ...supporterConsent!, consented: true }]; - cy.mockAll(200, createUser(consentData), USER_ENDPOINT); - cy.mockNext(200, consentData); - cy.visit('/signin/success?useIdapi=true'); - const checkbox = cy.findByLabelText('Yes, sign me up'); - checkbox.should('be.checked'); - checkbox.click(); - - // mock form save success - cy.mockNext(200, {}); - - cy.findByText('Continue to the Guardian').click(); - cy.lastPayloadIs([{ id: 'supporter', consented: false }]); - cy.url().should('include', defaultReturnUrl); - }); - - it('allows user to continue to different returnUrl', () => { - cy.mockNext(200, verifiedUserWithNoConsent.user.consents); - cy.visit( - `/signin/success?returnUrl=${encodeURIComponent( - returnUrl, - )}&useIdapi=true`, - ); - - // mock form save success - cy.mockNext(200, {}); - - cy.findByText('Continue to the Guardian').click(); - cy.lastPayloadIs([{ id: 'supporter', consented: false }]); - cy.url().should('include', returnUrl); - }); - - it('fails silently if submit fails, but user did not consent', () => { - cy.mockNext(200, verifiedUserWithNoConsent.user.consents); - cy.mockNext(500, {}); - cy.visit('/signin/success?useIdapi=true'); - - cy.findByText('Continue to the Guardian').click(); - cy.lastPayloadIs([{ id: 'supporter', consented: false }]); - cy.url().should('include', defaultReturnUrl); - }); - - it('shows error if submit fails and user did consent', () => { - cy.mockNext(200, verifiedUserWithNoConsent.user.consents); - cy.visit('/signin/success?useIdapi=true'); - cy.findByLabelText('Yes, sign me up').click(); - - cy.mockNext(500, undefined); - cy.mockNext(200, verifiedUserWithNoConsent.user.consents); - cy.findByText('Continue to the Guardian').click(); - cy.lastPayloadIs([{ id: 'supporter', consented: true }]); - cy.url().should('include', '/signin/success'); - cy.findByText( - 'There was a problem saving your choice, please try again.', - ).should('exist'); - }); - - it('redirects to returnUrl if fetching consents fails', () => { - cy.mockNext(500, {}); - /** - * Using cy.request instead of cy.visit as we only need to test the redirect - * and cy.intercept does not seem to work here - */ - cy.request({ - url: '/signin/success?useIdapi=true', - followRedirect: false, - }).then((response) => - expect(response.redirectedToUrl).to.contain(defaultReturnUrl), - ); - }); - - it('redirects to returnUrl if fetching user consents fails', () => { - cy.mockNext(500, undefined); - // Using cy.request instead of cy.visit, see above for reasoning. - cy.request({ - url: '/signin/success?useIdapi=true', - followRedirect: false, - }).then((response) => - expect(response.redirectedToUrl).to.contain(defaultReturnUrl), - ); - }); + const defaultReturnUrl = 'https://m.code.dev-theguardian.com'; + const returnUrl = 'https://www.theguardian.com/about'; + + beforeEach(() => { + cy.mockPurge(); + setAuthCookies(); + cy.mockAll( + 200, + authRedirectSignInRecentlyEmailValidated, + AUTH_REDIRECT_ENDPOINT, + ); + cy.mockAll(200, allConsents, CONSENTS_ENDPOINT); + cy.intercept('GET', defaultReturnUrl, (req) => { + req.reply(200); + }); + cy.intercept('GET', returnUrl, (req) => { + req.reply(200); + }); + }); + + it('has no detectable a11y violations on prompt page', () => { + cy.mockNext(200, verifiedUserWithNoConsent.user.consents); + cy.visit('/signin/success?useIdapi=true'); + injectAndCheckAxe(); + }); + + it('allows user to opt in and continue', () => { + cy.mockNext(200, verifiedUserWithNoConsent.user.consents); + cy.visit('/signin/success?useIdapi=true'); + const checkbox = cy.findByLabelText('Yes, sign me up'); + checkbox.should('not.be.checked'); + checkbox.click(); + + // mock form save success + cy.mockNext(200, {}); + + cy.findByText('Continue to the Guardian').click(); + cy.lastPayloadIs([{ id: 'supporter', consented: true }]); + cy.url().should('include', defaultReturnUrl); + }); + + it('allows user to opt out and continue', () => { + const supporterConsent = allConsents.find(({ id }) => id === 'supporter'); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const consentData = [{ ...supporterConsent!, consented: true }]; + cy.mockAll(200, createUser(consentData), USER_ENDPOINT); + cy.mockNext(200, consentData); + cy.visit('/signin/success?useIdapi=true'); + const checkbox = cy.findByLabelText('Yes, sign me up'); + checkbox.should('be.checked'); + checkbox.click(); + + // mock form save success + cy.mockNext(200, {}); + + cy.findByText('Continue to the Guardian').click(); + cy.lastPayloadIs([{ id: 'supporter', consented: false }]); + cy.url().should('include', defaultReturnUrl); + }); + + it('allows user to continue to different returnUrl', () => { + cy.mockNext(200, verifiedUserWithNoConsent.user.consents); + cy.visit( + `/signin/success?returnUrl=${encodeURIComponent( + returnUrl, + )}&useIdapi=true`, + ); + + // mock form save success + cy.mockNext(200, {}); + + cy.findByText('Continue to the Guardian').click(); + cy.lastPayloadIs([{ id: 'supporter', consented: false }]); + cy.url().should('include', returnUrl); + }); + + it('fails silently if submit fails, but user did not consent', () => { + cy.mockNext(200, verifiedUserWithNoConsent.user.consents); + cy.mockNext(500, {}); + cy.visit('/signin/success?useIdapi=true'); + + cy.findByText('Continue to the Guardian').click(); + cy.lastPayloadIs([{ id: 'supporter', consented: false }]); + cy.url().should('include', defaultReturnUrl); + }); + + it('shows error if submit fails and user did consent', () => { + cy.mockNext(200, verifiedUserWithNoConsent.user.consents); + cy.visit('/signin/success?useIdapi=true'); + cy.findByLabelText('Yes, sign me up').click(); + + cy.mockNext(500, undefined); + cy.mockNext(200, verifiedUserWithNoConsent.user.consents); + cy.findByText('Continue to the Guardian').click(); + cy.lastPayloadIs([{ id: 'supporter', consented: true }]); + cy.url().should('include', '/signin/success'); + cy.findByText( + 'There was a problem saving your choice, please try again.', + ).should('exist'); + }); + + it('redirects to returnUrl if fetching consents fails', () => { + cy.mockNext(500, {}); + /** + * Using cy.request instead of cy.visit as we only need to test the redirect + * and cy.intercept does not seem to work here + */ + cy.request({ + url: '/signin/success?useIdapi=true', + followRedirect: false, + }).then((response) => + expect(response.redirectedToUrl).to.contain(defaultReturnUrl), + ); + }); + + it('redirects to returnUrl if fetching user consents fails', () => { + cy.mockNext(500, undefined); + // Using cy.request instead of cy.visit, see above for reasoning. + cy.request({ + url: '/signin/success?useIdapi=true', + followRedirect: false, + }).then((response) => + expect(response.redirectedToUrl).to.contain(defaultReturnUrl), + ); + }); }); diff --git a/cypress/integration/mocked/register.6.cy.ts b/cypress/integration/mocked/register.6.cy.ts index 46890ca923..449f82e565 100644 --- a/cypress/integration/mocked/register.6.cy.ts +++ b/cypress/integration/mocked/register.6.cy.ts @@ -2,191 +2,191 @@ import { injectAndCheckAxe } from '../../support/cypress-axe'; import { invalidEmailAddress } from '../../support/idapi/guest'; describe('Registration flow', () => { - beforeEach(() => { - cy.mockPurge(); - }); - - context('A11y checks', () => { - it('Has no detectable a11y violations on registration page', () => { - cy.visit('/register?useIdapi=true'); - injectAndCheckAxe(); - }); - - it('Has no detectable a11y violations on registration page with error', () => { - cy.visit('/register?useIdapi=true'); - cy.get('input[name="email"]').type('Invalid email'); - cy.mockNext(500); - cy.get('[data-cy=main-form-submit-button]').click(); - injectAndCheckAxe(); - }); - }); - - context('Registering', () => { - it('shows a generic error message when registration fails', () => { - cy.visit( - '/register?returnUrl=https%3A%2F%2Fwww.theguardian.com%2Fabout&useIdapi=true', - ); - cy.get('input[name="email"]').type('example@example.com'); - cy.mockNext(200, { - userType: 'new', - }); - cy.mockNext(403, { - status: 'error', - errors: [ - { - message: '', - }, - ], - }); - cy.get('[data-cy=main-form-submit-button]').click(); - cy.contains('There was a problem registering, please try again.'); - }); - - it('shows a email field error message when no email is sent', () => { - cy.visit( - '/register?returnUrl=https%3A%2F%2Fwww.theguardian.com%2Fabout&useIdapi=true', - ); - cy.get('input[name="email"]').type('placeholder@example.com'); - cy.mockNext(200, { - userType: 'new', - }); - cy.mockNext(400, invalidEmailAddress); - cy.get('[data-cy=main-form-submit-button]').click(); - cy.contains('Please enter a valid email address.'); - }); - - it('shows recaptcha error message when reCAPTCHA token request fails', () => { - cy.visit( - '/register?returnUrl=https%3A%2F%2Fwww.theguardian.com%2Fabout&useIdapi=true', - ); - cy.get('input[name="email"]').type('placeholder@example.com'); - cy.intercept('POST', 'https://www.google.com/recaptcha/api2/**', { - statusCode: 500, - }); - cy.get('[data-cy=main-form-submit-button]').click(); - cy.contains('Google reCAPTCHA verification failed. Please try again.'); - }); - - it('shows detailed recaptcha error message when reCAPTCHA token request fails two times', () => { - // Intercept "Report this error" link because we just check it is linked to. - cy.intercept( - 'GET', - 'https://manage.theguardian.com/help-centre/contact-us', - { - statusCode: 200, - }, - ); - cy.visit( - '/register?returnUrl=https%3A%2F%2Fwww.theguardian.com%2Fabout&useIdapi=true', - ); - cy.get('input[name="email"]').type('placeholder@example.com'); - cy.intercept('POST', 'https://www.google.com/recaptcha/api2/**', { - statusCode: 500, - }); - cy.get('[data-cy=main-form-submit-button]').click(); - cy.contains('Google reCAPTCHA verification failed. Please try again.'); - cy.get('[data-cy=main-form-submit-button]').click(); - cy.contains('Google reCAPTCHA verification failed.'); - cy.contains('If the problem persists please try the following:'); - cy.contains('Report this error').click(); - cy.url().should( - 'eq', - 'https://manage.theguardian.com/help-centre/contact-us', - ); - }); - - it('redirects to email sent page upon successful guest registration', () => { - cy.visit( - '/register?returnUrl=https%3A%2F%2Fwww.theguardian.com%2Fabout&useIdapi=true', - ); - cy.get('input[name="email"]').type('example@example.com'); - cy.mockNext(200, { - userType: 'new', - }); - cy.mockNext(200, { - status: 'success', - errors: [], - }); - cy.get('[data-cy=main-form-submit-button]').click(); - cy.contains('Check your email inbox'); - cy.contains('example@example.com'); - }); - - it('redirects to email sent page when existing user with password attempts to register', () => { - cy.visit( - '/register?returnUrl=https%3A%2F%2Fwww.theguardian.com%2Fabout&useIdapi=true', - ); - cy.get('input[name="email"]').type('example@example.com'); - cy.mockNext(200, { - userType: 'current', - }); - cy.mockNext(200); - cy.get('[data-cy=main-form-submit-button]').click(); - cy.contains('Check your email inbox'); - cy.contains('example@example.com'); - }); - - it('redirects to email sent page when existing user without password attempts to register', () => { - cy.visit( - '/register?returnUrl=https%3A%2F%2Fwww.theguardian.com%2Fabout&useIdapi=true', - ); - cy.get('input[name="email"]').type('example@example.com'); - cy.mockNext(200, { - userType: 'guest', - }); - cy.mockNext(200); - cy.get('[data-cy=main-form-submit-button]').click(); - cy.contains('Check your email inbox'); - cy.contains('example@example.com'); - }); - - it('redirects to homepage when user with existing valid session visits register page', () => { - cy.setCookie('SC_GU_U', 'sessionvalue'); - cy.setCookie('SC_GU_LA', 'la_value'); - - cy.request({ - url: '/register', - followRedirect: false, - failOnStatusCode: false, - }).then((response) => { - expect(response.status).to.eq(302); - expect(response.redirectedToUrl).to.eq( - 'https://m.code.dev-theguardian.com/', - ); - }); - cy.mockNext(200, { - status: 'signedInRecently', - emailValidated: true, - }); - }); - it('redirects to /reauthenticate when user with existing invalid session visits register page', () => { - cy.setCookie('SC_GU_U', 'sessionvalue'); - cy.setCookie('SC_GU_LA', 'la_value'); - - cy.mockNext(200, { - status: 'signedInNotRecently', - emailValidated: true, - }); - cy.visit('/register?useIdapi=true'); - - cy.location('pathname').should('eq', '/reauthenticate'); - }); - }); - - context('General IDAPI failure', () => { - it('displays a generic error message', function () { - cy.mockNext(200, { - userType: 'new', - }); - cy.mockNext(500); - cy.visit( - '/register?returnUrl=https%3A%2F%2Flocalhost%3A8861%2Fsignin&useIdapi=true', - ); - - cy.get('input[name=email]').type('example@example.com'); - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains('There was a problem registering, please try again.'); - }); - }); + beforeEach(() => { + cy.mockPurge(); + }); + + context('A11y checks', () => { + it('Has no detectable a11y violations on registration page', () => { + cy.visit('/register?useIdapi=true'); + injectAndCheckAxe(); + }); + + it('Has no detectable a11y violations on registration page with error', () => { + cy.visit('/register?useIdapi=true'); + cy.get('input[name="email"]').type('Invalid email'); + cy.mockNext(500); + cy.get('[data-cy=main-form-submit-button]').click(); + injectAndCheckAxe(); + }); + }); + + context('Registering', () => { + it('shows a generic error message when registration fails', () => { + cy.visit( + '/register?returnUrl=https%3A%2F%2Fwww.theguardian.com%2Fabout&useIdapi=true', + ); + cy.get('input[name="email"]').type('example@example.com'); + cy.mockNext(200, { + userType: 'new', + }); + cy.mockNext(403, { + status: 'error', + errors: [ + { + message: '', + }, + ], + }); + cy.get('[data-cy=main-form-submit-button]').click(); + cy.contains('There was a problem registering, please try again.'); + }); + + it('shows a email field error message when no email is sent', () => { + cy.visit( + '/register?returnUrl=https%3A%2F%2Fwww.theguardian.com%2Fabout&useIdapi=true', + ); + cy.get('input[name="email"]').type('placeholder@example.com'); + cy.mockNext(200, { + userType: 'new', + }); + cy.mockNext(400, invalidEmailAddress); + cy.get('[data-cy=main-form-submit-button]').click(); + cy.contains('Please enter a valid email address.'); + }); + + it('shows recaptcha error message when reCAPTCHA token request fails', () => { + cy.visit( + '/register?returnUrl=https%3A%2F%2Fwww.theguardian.com%2Fabout&useIdapi=true', + ); + cy.get('input[name="email"]').type('placeholder@example.com'); + cy.intercept('POST', 'https://www.google.com/recaptcha/api2/**', { + statusCode: 500, + }); + cy.get('[data-cy=main-form-submit-button]').click(); + cy.contains('Google reCAPTCHA verification failed. Please try again.'); + }); + + it('shows detailed recaptcha error message when reCAPTCHA token request fails two times', () => { + // Intercept "Report this error" link because we just check it is linked to. + cy.intercept( + 'GET', + 'https://manage.theguardian.com/help-centre/contact-us', + { + statusCode: 200, + }, + ); + cy.visit( + '/register?returnUrl=https%3A%2F%2Fwww.theguardian.com%2Fabout&useIdapi=true', + ); + cy.get('input[name="email"]').type('placeholder@example.com'); + cy.intercept('POST', 'https://www.google.com/recaptcha/api2/**', { + statusCode: 500, + }); + cy.get('[data-cy=main-form-submit-button]').click(); + cy.contains('Google reCAPTCHA verification failed. Please try again.'); + cy.get('[data-cy=main-form-submit-button]').click(); + cy.contains('Google reCAPTCHA verification failed.'); + cy.contains('If the problem persists please try the following:'); + cy.contains('Report this error').click(); + cy.url().should( + 'eq', + 'https://manage.theguardian.com/help-centre/contact-us', + ); + }); + + it('redirects to email sent page upon successful guest registration', () => { + cy.visit( + '/register?returnUrl=https%3A%2F%2Fwww.theguardian.com%2Fabout&useIdapi=true', + ); + cy.get('input[name="email"]').type('example@example.com'); + cy.mockNext(200, { + userType: 'new', + }); + cy.mockNext(200, { + status: 'success', + errors: [], + }); + cy.get('[data-cy=main-form-submit-button]').click(); + cy.contains('Check your email inbox'); + cy.contains('example@example.com'); + }); + + it('redirects to email sent page when existing user with password attempts to register', () => { + cy.visit( + '/register?returnUrl=https%3A%2F%2Fwww.theguardian.com%2Fabout&useIdapi=true', + ); + cy.get('input[name="email"]').type('example@example.com'); + cy.mockNext(200, { + userType: 'current', + }); + cy.mockNext(200); + cy.get('[data-cy=main-form-submit-button]').click(); + cy.contains('Check your email inbox'); + cy.contains('example@example.com'); + }); + + it('redirects to email sent page when existing user without password attempts to register', () => { + cy.visit( + '/register?returnUrl=https%3A%2F%2Fwww.theguardian.com%2Fabout&useIdapi=true', + ); + cy.get('input[name="email"]').type('example@example.com'); + cy.mockNext(200, { + userType: 'guest', + }); + cy.mockNext(200); + cy.get('[data-cy=main-form-submit-button]').click(); + cy.contains('Check your email inbox'); + cy.contains('example@example.com'); + }); + + it('redirects to homepage when user with existing valid session visits register page', () => { + cy.setCookie('SC_GU_U', 'sessionvalue'); + cy.setCookie('SC_GU_LA', 'la_value'); + + cy.request({ + url: '/register', + followRedirect: false, + failOnStatusCode: false, + }).then((response) => { + expect(response.status).to.eq(302); + expect(response.redirectedToUrl).to.eq( + 'https://m.code.dev-theguardian.com/', + ); + }); + cy.mockNext(200, { + status: 'signedInRecently', + emailValidated: true, + }); + }); + it('redirects to /reauthenticate when user with existing invalid session visits register page', () => { + cy.setCookie('SC_GU_U', 'sessionvalue'); + cy.setCookie('SC_GU_LA', 'la_value'); + + cy.mockNext(200, { + status: 'signedInNotRecently', + emailValidated: true, + }); + cy.visit('/register?useIdapi=true'); + + cy.location('pathname').should('eq', '/reauthenticate'); + }); + }); + + context('General IDAPI failure', () => { + it('displays a generic error message', function () { + cy.mockNext(200, { + userType: 'new', + }); + cy.mockNext(500); + cy.visit( + '/register?returnUrl=https%3A%2F%2Flocalhost%3A8861%2Fsignin&useIdapi=true', + ); + + cy.get('input[name=email]').type('example@example.com'); + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains('There was a problem registering, please try again.'); + }); + }); }); diff --git a/cypress/integration/mocked/reset_password.5.cy.ts b/cypress/integration/mocked/reset_password.5.cy.ts index d2ea62400c..93f89a19cc 100644 --- a/cypress/integration/mocked/reset_password.5.cy.ts +++ b/cypress/integration/mocked/reset_password.5.cy.ts @@ -2,91 +2,91 @@ import { injectAndCheckAxe } from '../../support/cypress-axe'; import PageResetPassword from '../../support/pages/reset_password_page'; describe('Password reset flow', () => { - const page = new PageResetPassword(); + const page = new PageResetPassword(); - before(() => { - cy.mockPurge(); - cy.fixture('users').as('users'); - }); + before(() => { + cy.mockPurge(); + cy.fixture('users').as('users'); + }); - beforeEach(function () { - cy.mockPurge(); - page.goto(); - }); + beforeEach(function () { + cy.mockPurge(); + page.goto(); + }); - context('A11y checks', () => { - it('Has no detectable a11y violations on reset password page', () => { - injectAndCheckAxe(); - }); + context('A11y checks', () => { + it('Has no detectable a11y violations on reset password page', () => { + injectAndCheckAxe(); + }); - it('Has no detectable a11y violations on reset password page with error', () => { - cy.mockNext(500); - page.submitEmailAddress('example@example.com'); - injectAndCheckAxe(); - }); + it('Has no detectable a11y violations on reset password page with error', () => { + cy.mockNext(500); + page.submitEmailAddress('example@example.com'); + injectAndCheckAxe(); + }); - it('Has no detectable a11y violations on email sent page', function () { - const { email } = this.users.validEmail; - cy.mockNext(200); - page.submitEmailAddress(email); - injectAndCheckAxe(); - }); - }); + it('Has no detectable a11y violations on email sent page', function () { + const { email } = this.users.validEmail; + cy.mockNext(200); + page.submitEmailAddress(email); + injectAndCheckAxe(); + }); + }); - context('Valid email already exits', () => { - it('successfully submits the request', function () { - const { email } = this.users.validEmail; - cy.mockNext(200); - page.submitEmailAddress(email); - cy.contains('Check your email'); - }); - }); + context('Valid email already exits', () => { + it('successfully submits the request', function () { + const { email } = this.users.validEmail; + cy.mockNext(200); + page.submitEmailAddress(email); + cy.contains('Check your email'); + }); + }); - context('Email field is left blank', () => { - it('displays the standard HTML validation', () => { - page.clickResetPassword(); - page.invalidEmailAddressField().should('have.length', 1); - }); - }); + context('Email field is left blank', () => { + it('displays the standard HTML validation', () => { + page.clickResetPassword(); + page.invalidEmailAddressField().should('have.length', 1); + }); + }); - context('Email is invalid', () => { - it('displays the standard HTML validation', () => { - page.submitEmailAddress('bademail£'); - page.invalidEmailAddressField().should('have.length', 1); - }); - }); + context('Email is invalid', () => { + it('displays the standard HTML validation', () => { + page.submitEmailAddress('bademail£'); + page.invalidEmailAddressField().should('have.length', 1); + }); + }); - context('General IDAPI failure', () => { - it('displays a generic error message', function () { - const { email } = this.users.validEmail; - cy.mockNext(500); - page.submitEmailAddress(email); - cy.contains(PageResetPassword.CONTENT.ERRORS.GENERIC); - }); - }); + context('General IDAPI failure', () => { + it('displays a generic error message', function () { + const { email } = this.users.validEmail; + cy.mockNext(500); + page.submitEmailAddress(email); + cy.contains(PageResetPassword.CONTENT.ERRORS.GENERIC); + }); + }); - context('Recaptcha errors', () => { - it('shows recaptcha error message when reCAPTCHA token request fails', function () { - cy.intercept('POST', 'https://www.google.com/recaptcha/api2/**', { - statusCode: 500, - }); - const { email } = this.users.emailNotRegistered; - page.submitEmailAddress(email); - cy.contains('Google reCAPTCHA verification failed. Please try again.'); - }); + context('Recaptcha errors', () => { + it('shows recaptcha error message when reCAPTCHA token request fails', function () { + cy.intercept('POST', 'https://www.google.com/recaptcha/api2/**', { + statusCode: 500, + }); + const { email } = this.users.emailNotRegistered; + page.submitEmailAddress(email); + cy.contains('Google reCAPTCHA verification failed. Please try again.'); + }); - it('shows detailed recaptcha error message when reCAPTCHA token request fails two times', function () { - cy.intercept('POST', 'https://www.google.com/recaptcha/api2/**', { - statusCode: 500, - }); - const { email } = this.users.emailNotRegistered; - page.submitEmailAddress(email); - cy.contains('Google reCAPTCHA verification failed. Please try again.'); - page.emailAddressField().clear(); - page.submitEmailAddress(email); - cy.contains('Google reCAPTCHA verification failed.'); - cy.contains('If the problem persists please try the following:'); - cy.contains('userhelp@'); - }); - }); + it('shows detailed recaptcha error message when reCAPTCHA token request fails two times', function () { + cy.intercept('POST', 'https://www.google.com/recaptcha/api2/**', { + statusCode: 500, + }); + const { email } = this.users.emailNotRegistered; + page.submitEmailAddress(email); + cy.contains('Google reCAPTCHA verification failed. Please try again.'); + page.emailAddressField().clear(); + page.submitEmailAddress(email); + cy.contains('Google reCAPTCHA verification failed.'); + cy.contains('If the problem persists please try the following:'); + cy.contains('userhelp@'); + }); + }); }); diff --git a/cypress/integration/mocked/set_password.5.cy.ts b/cypress/integration/mocked/set_password.5.cy.ts index cd79fe026f..ef20ac5948 100644 --- a/cypress/integration/mocked/set_password.5.cy.ts +++ b/cypress/integration/mocked/set_password.5.cy.ts @@ -3,271 +3,271 @@ import { injectAndCheckAxe } from '../../support/cypress-axe'; describe('Password set/create flow', () => { - const fakeValidationRespone = (timeUntilExpiry?: number) => ({ - user: { - primaryEmailAddress: 'name@example.com', - }, - timeUntilExpiry, - }); + const fakeValidationRespone = (timeUntilExpiry?: number) => ({ + user: { + primaryEmailAddress: 'name@example.com', + }, + timeUntilExpiry, + }); - const fakeSuccessResponse = { - cookies: { - values: [ - { - key: 'GU_U', - value: 'FAKE_VALUE_0', - }, - { - key: 'SC_GU_LA', - value: 'FAKE_VALUE_1', - sessionCookie: true, - }, - { - key: 'SC_GU_U', - value: 'FAKE_VALUE_2', - }, - ], - expiresAt: 1, - }, - }; + const fakeSuccessResponse = { + cookies: { + values: [ + { + key: 'GU_U', + value: 'FAKE_VALUE_0', + }, + { + key: 'SC_GU_LA', + value: 'FAKE_VALUE_1', + sessionCookie: true, + }, + { + key: 'SC_GU_U', + value: 'FAKE_VALUE_2', + }, + ], + expiresAt: 1, + }, + }; - beforeEach(() => { - cy.mockPurge(); - }); + beforeEach(() => { + cy.mockPurge(); + }); - context('A11y checks', () => { - it('Has no detectable a11y violations on resend password page', () => { - cy.mockNext(500, { - status: 'error', - errors: [ - { - message: 'Invalid token', - }, - ], - }); - cy.visit('/set-password/fake_token?useIdapi=true'); - injectAndCheckAxe(); - }); + context('A11y checks', () => { + it('Has no detectable a11y violations on resend password page', () => { + cy.mockNext(500, { + status: 'error', + errors: [ + { + message: 'Invalid token', + }, + ], + }); + cy.visit('/set-password/fake_token?useIdapi=true'); + injectAndCheckAxe(); + }); - it('Has no detectable a11y violations on create/set password page', () => { - cy.mockNext(200); - cy.mockNext(200, fakeSuccessResponse); - cy.visit('/set-password/fake_token?useIdapi=true'); - injectAndCheckAxe(); - }); + it('Has no detectable a11y violations on create/set password page', () => { + cy.mockNext(200); + cy.mockNext(200, fakeSuccessResponse); + cy.visit('/set-password/fake_token?useIdapi=true'); + injectAndCheckAxe(); + }); - it('Has no detectable a11y violations on create/set password page with error', () => { - cy.mockNext(200); - cy.visit('/set-password/fake_token?useIdapi=true'); - cy.get('input[name="password"]').type('short'); - cy.get('button[type="submit"]').click(); - injectAndCheckAxe(); - }); + it('Has no detectable a11y violations on create/set password page with error', () => { + cy.mockNext(200); + cy.visit('/set-password/fake_token?useIdapi=true'); + cy.get('input[name="password"]').type('short'); + cy.get('button[type="submit"]').click(); + injectAndCheckAxe(); + }); - it('Has no detectable a11y violations on create/set password complete page', () => { - cy.mockNext(200); - cy.mockNext(200, fakeSuccessResponse); - cy.intercept({ - method: 'GET', - url: 'https://api.pwnedpasswords.com/range/*', - }).as('breachCheck'); - cy.visit('/set-password/fake_token?useIdapi=true'); - cy.get('input[name="password"]').type('thisisalongandunbreachedpassword'); - cy.wait('@breachCheck'); - cy.get('button[type="submit"]').click(); - injectAndCheckAxe(); - }); - }); + it('Has no detectable a11y violations on create/set password complete page', () => { + cy.mockNext(200); + cy.mockNext(200, fakeSuccessResponse); + cy.intercept({ + method: 'GET', + url: 'https://api.pwnedpasswords.com/range/*', + }).as('breachCheck'); + cy.visit('/set-password/fake_token?useIdapi=true'); + cy.get('input[name="password"]').type('thisisalongandunbreachedpassword'); + cy.wait('@breachCheck'); + cy.get('button[type="submit"]').click(); + injectAndCheckAxe(); + }); + }); - context('show / hide password eye button', () => { - it('clicking on the password eye shows the password and clicking it again hides it', () => { - cy.mockNext(200); - cy.visit('/set-password/fake_token?useIdapi=true'); - cy.get('input[name="password"]').should('have.attr', 'type', 'password'); - cy.get('input[name="password"]').type('some_password'); - cy.get('[data-cy=password-input-eye-button]').click(); - cy.get('input[name="password"]').should('have.attr', 'type', 'text'); - cy.get('[data-cy=password-input-eye-button]').click(); - cy.get('input[name="password"]').should('have.attr', 'type', 'password'); - }); - }); + context('show / hide password eye button', () => { + it('clicking on the password eye shows the password and clicking it again hides it', () => { + cy.mockNext(200); + cy.visit('/set-password/fake_token?useIdapi=true'); + cy.get('input[name="password"]').should('have.attr', 'type', 'password'); + cy.get('input[name="password"]').type('some_password'); + cy.get('[data-cy=password-input-eye-button]').click(); + cy.get('input[name="password"]').should('have.attr', 'type', 'text'); + cy.get('[data-cy=password-input-eye-button]').click(); + cy.get('input[name="password"]').should('have.attr', 'type', 'password'); + }); + }); - context('An expired/invalid token is used', () => { - it('shows a resend password page', () => { - cy.mockNext(500, { - status: 'error', - errors: [ - { - message: 'Invalid token', - }, - ], - }); - cy.visit('/set-password/fake_token?useIdapi=true'); - cy.contains('Link expired'); - }); + context('An expired/invalid token is used', () => { + it('shows a resend password page', () => { + cy.mockNext(500, { + status: 'error', + errors: [ + { + message: 'Invalid token', + }, + ], + }); + cy.visit('/set-password/fake_token?useIdapi=true'); + cy.contains('Link expired'); + }); - it('does not allow email resend if reCAPTCHA check fails', () => { - cy.mockNext(500, { - status: 'error', - errors: [ - { - message: 'Invalid token', - }, - ], - }); - cy.visit('/set-password/fake_token?useIdapi=true'); - cy.contains('Link expired'); - cy.get('input[name="email"]').type('some@email.com'); - cy.intercept('POST', 'https://www.google.com/recaptcha/api2/**', { - statusCode: 500, - }); - cy.get('button[type="submit"]').click(); - cy.contains('Google reCAPTCHA verification failed. Please try again.'); - cy.get('button[type="submit"]').click(); - cy.contains('Google reCAPTCHA verification failed.'); - cy.contains('If the problem persists please try the following:'); - cy.contains('userhelp@'); - }); + it('does not allow email resend if reCAPTCHA check fails', () => { + cy.mockNext(500, { + status: 'error', + errors: [ + { + message: 'Invalid token', + }, + ], + }); + cy.visit('/set-password/fake_token?useIdapi=true'); + cy.contains('Link expired'); + cy.get('input[name="email"]').type('some@email.com'); + cy.intercept('POST', 'https://www.google.com/recaptcha/api2/**', { + statusCode: 500, + }); + cy.get('button[type="submit"]').click(); + cy.contains('Google reCAPTCHA verification failed. Please try again.'); + cy.get('button[type="submit"]').click(); + cy.contains('Google reCAPTCHA verification failed.'); + cy.contains('If the problem persists please try the following:'); + cy.contains('userhelp@'); + }); - it('shows the session time out page if the token expires while on the set password page', () => { - cy.mockNext(200, fakeValidationRespone(1000)); - cy.visit('/set-password/fake_token?useIdapi=true'); - cy.contains('Session timed out'); - }); - }); + it('shows the session time out page if the token expires while on the set password page', () => { + cy.mockNext(200, fakeValidationRespone(1000)); + cy.visit('/set-password/fake_token?useIdapi=true'); + cy.contains('Session timed out'); + }); + }); - context('Email shown on page', () => { - it('shows the users email address on the page', () => { - cy.mockNext(200, fakeValidationRespone()); - cy.visit('/set-password/fake_token?useIdapi=true'); - cy.contains(fakeValidationRespone().user.primaryEmailAddress); - }); - }); + context('Email shown on page', () => { + it('shows the users email address on the page', () => { + cy.mockNext(200, fakeValidationRespone()); + cy.visit('/set-password/fake_token?useIdapi=true'); + cy.contains(fakeValidationRespone().user.primaryEmailAddress); + }); + }); - context('Valid password entered', () => { - it('shows password set success screen, with a default redirect button.', () => { - cy.mockNext(200); - cy.mockNext(200, fakeSuccessResponse); - cy.intercept({ - method: 'GET', - url: 'https://api.pwnedpasswords.com/range/*', - }).as('breachCheck'); - cy.visit('/set-password/fake_token?useIdapi=true'); + context('Valid password entered', () => { + it('shows password set success screen, with a default redirect button.', () => { + cy.mockNext(200); + cy.mockNext(200, fakeSuccessResponse); + cy.intercept({ + method: 'GET', + url: 'https://api.pwnedpasswords.com/range/*', + }).as('breachCheck'); + cy.visit('/set-password/fake_token?useIdapi=true'); - cy.get('input[name="password"]').type('thisisalongandunbreachedpassword'); - cy.wait('@breachCheck'); - cy.get('button[type="submit"]').click(); - cy.contains('Password created'); - cy.contains('Continue to the Guardian').should( - 'have.attr', - 'href', - `${Cypress.env('DEFAULT_RETURN_URI')}/`, - ); - }); + cy.get('input[name="password"]').type('thisisalongandunbreachedpassword'); + cy.wait('@breachCheck'); + cy.get('button[type="submit"]').click(); + cy.contains('Password created'); + cy.contains('Continue to the Guardian').should( + 'have.attr', + 'href', + `${Cypress.env('DEFAULT_RETURN_URI')}/`, + ); + }); - it('shows password change success screen, with default redirect button, and users email', () => { - cy.mockNext(200, fakeValidationRespone()); - cy.mockNext(200, fakeSuccessResponse); - cy.intercept({ - method: 'GET', - url: 'https://api.pwnedpasswords.com/range/*', - }).as('breachCheck'); - cy.visit('/set-password/fake_token?useIdapi=true'); - cy.contains(fakeValidationRespone().user.primaryEmailAddress); + it('shows password change success screen, with default redirect button, and users email', () => { + cy.mockNext(200, fakeValidationRespone()); + cy.mockNext(200, fakeSuccessResponse); + cy.intercept({ + method: 'GET', + url: 'https://api.pwnedpasswords.com/range/*', + }).as('breachCheck'); + cy.visit('/set-password/fake_token?useIdapi=true'); + cy.contains(fakeValidationRespone().user.primaryEmailAddress); - cy.get('input[name="password"]').type('thisisalongandunbreachedpassword'); - cy.wait('@breachCheck'); - cy.get('button[type="submit"]').click(); - cy.contains('Password created'); - cy.contains('Continue to the Guardian').should( - 'have.attr', - 'href', - `${Cypress.env('DEFAULT_RETURN_URI')}/`, - ); - cy.contains(fakeValidationRespone().user.primaryEmailAddress); - }); - }); + cy.get('input[name="password"]').type('thisisalongandunbreachedpassword'); + cy.wait('@breachCheck'); + cy.get('button[type="submit"]').click(); + cy.contains('Password created'); + cy.contains('Continue to the Guardian').should( + 'have.attr', + 'href', + `${Cypress.env('DEFAULT_RETURN_URI')}/`, + ); + cy.contains(fakeValidationRespone().user.primaryEmailAddress); + }); + }); - context( - 'Valid password entered and a return url with a Guardian domain is specified.', - () => { - it('shows password created success screen, with a redirect button linking to the return url.', () => { - cy.mockNext(200); - cy.mockNext(200, fakeSuccessResponse); - cy.intercept({ - method: 'GET', - url: 'https://api.pwnedpasswords.com/range/*', - }).as('breachCheck'); - cy.visit( - '/set-password/fake_token?returnUrl=https://news.theguardian.com&useIdapi=true', - ); - cy.get('input[name="password"]').type( - 'thisisalongandunbreachedpassword', - ); - cy.wait('@breachCheck'); - cy.get('button[type="submit"]').click(); - cy.contains('Password created'); - cy.contains('Continue to the Guardian').should( - 'have.attr', - 'href', - 'https://news.theguardian.com/', - ); - }); - }, - ); + context( + 'Valid password entered and a return url with a Guardian domain is specified.', + () => { + it('shows password created success screen, with a redirect button linking to the return url.', () => { + cy.mockNext(200); + cy.mockNext(200, fakeSuccessResponse); + cy.intercept({ + method: 'GET', + url: 'https://api.pwnedpasswords.com/range/*', + }).as('breachCheck'); + cy.visit( + '/set-password/fake_token?returnUrl=https://news.theguardian.com&useIdapi=true', + ); + cy.get('input[name="password"]').type( + 'thisisalongandunbreachedpassword', + ); + cy.wait('@breachCheck'); + cy.get('button[type="submit"]').click(); + cy.contains('Password created'); + cy.contains('Continue to the Guardian').should( + 'have.attr', + 'href', + 'https://news.theguardian.com/', + ); + }); + }, + ); - context( - 'Valid password entered and an return url from a non-Guardian domain is specified.', - () => { - it('shows password created success screen, with a default redirect button.', () => { - cy.mockNext(200); - cy.mockNext(200, fakeSuccessResponse); - cy.intercept({ - method: 'GET', - url: 'https://api.pwnedpasswords.com/range/*', - }).as('breachCheck'); - cy.visit( - '/set-password/fake_token?returnUrl=https://news.badsite.com&useIdapi=true', - ); - cy.get('input[name="password"]').type( - 'thisisalongandunbreachedpassword', - ); - cy.wait('@breachCheck'); - cy.get('button[type="submit"]').click(); - cy.contains('Password created'); - cy.contains('Continue to the Guardian').should( - 'have.attr', - 'href', - `${Cypress.env('DEFAULT_RETURN_URI')}/`, - ); - }); - }, - ); + context( + 'Valid password entered and an return url from a non-Guardian domain is specified.', + () => { + it('shows password created success screen, with a default redirect button.', () => { + cy.mockNext(200); + cy.mockNext(200, fakeSuccessResponse); + cy.intercept({ + method: 'GET', + url: 'https://api.pwnedpasswords.com/range/*', + }).as('breachCheck'); + cy.visit( + '/set-password/fake_token?returnUrl=https://news.badsite.com&useIdapi=true', + ); + cy.get('input[name="password"]').type( + 'thisisalongandunbreachedpassword', + ); + cy.wait('@breachCheck'); + cy.get('button[type="submit"]').click(); + cy.contains('Password created'); + cy.contains('Continue to the Guardian').should( + 'have.attr', + 'href', + `${Cypress.env('DEFAULT_RETURN_URI')}/`, + ); + }); + }, + ); - context('General IDAPI failure on token read', () => { - it('displays the password resend page', () => { - cy.mockNext(500); - cy.visit('/set-password/fake_token?useIdapi=true'); - cy.contains('Link expired'); - }); - }); + context('General IDAPI failure on token read', () => { + it('displays the password resend page', () => { + cy.mockNext(500); + cy.visit('/set-password/fake_token?useIdapi=true'); + cy.contains('Link expired'); + }); + }); - context('General IDAPI failure on password change', () => { - it('displays a generic error message', () => { - cy.mockNext(200); - cy.mockNext(500); - cy.mockNext(200); - cy.intercept({ - method: 'GET', - url: 'https://api.pwnedpasswords.com/range/*', - }).as('breachCheck'); - cy.visit('/set-password/fake_token?useIdapi=true'); - cy.get('input[name="password"]').type('thisisalongandunbreachedpassword'); - cy.wait('@breachCheck'); - cy.get('button[type="submit"]').click(); - cy.contains( - 'There was a problem changing your password, please try again.', - ); - }); - }); + context('General IDAPI failure on password change', () => { + it('displays a generic error message', () => { + cy.mockNext(200); + cy.mockNext(500); + cy.mockNext(200); + cy.intercept({ + method: 'GET', + url: 'https://api.pwnedpasswords.com/range/*', + }).as('breachCheck'); + cy.visit('/set-password/fake_token?useIdapi=true'); + cy.get('input[name="password"]').type('thisisalongandunbreachedpassword'); + cy.wait('@breachCheck'); + cy.get('button[type="submit"]').click(); + cy.contains( + 'There was a problem changing your password, please try again.', + ); + }); + }); }); diff --git a/cypress/integration/mocked/sign_in.2.cy.ts b/cypress/integration/mocked/sign_in.2.cy.ts index d47cc7f4dc..9001ecc56e 100644 --- a/cypress/integration/mocked/sign_in.2.cy.ts +++ b/cypress/integration/mocked/sign_in.2.cy.ts @@ -1,297 +1,297 @@ import { injectAndCheckAxe } from '../../support/cypress-axe'; import { allConsents, CONSENTS_ENDPOINT } from '../../support/idapi/consent'; import { - verifiedUserWithNoConsent, - USER_CONSENTS_ENDPOINT, + verifiedUserWithNoConsent, + USER_CONSENTS_ENDPOINT, } from '../../support/idapi/user'; describe('Sign in flow', () => { - beforeEach(() => { - cy.mockPurge(); - }); + beforeEach(() => { + cy.mockPurge(); + }); - context('A11y checks', () => { - it('Has no detectable a11y violations on sign in page', () => { - cy.visit('/signin?useIdapi=true'); - injectAndCheckAxe(); - }); + context('A11y checks', () => { + it('Has no detectable a11y violations on sign in page', () => { + cy.visit('/signin?useIdapi=true'); + injectAndCheckAxe(); + }); - it('Has no detectable a11y violations on sign in page with error', () => { - cy.visit('/signin?useIdapi=true'); - cy.get('input[name="email"]').type('Invalid email'); - cy.get('input[name="password"]').type('Invalid password'); - cy.mockNext(500); - cy.get('[data-cy=main-form-submit-button]').click(); - injectAndCheckAxe(); - }); - }); + it('Has no detectable a11y violations on sign in page with error', () => { + cy.visit('/signin?useIdapi=true'); + cy.get('input[name="email"]').type('Invalid email'); + cy.get('input[name="password"]').type('Invalid password'); + cy.mockNext(500); + cy.get('[data-cy=main-form-submit-button]').click(); + injectAndCheckAxe(); + }); + }); - context('Signing in', () => { - const defaultReturnUrl = 'https://m.code.dev-theguardian.com'; - it('shows an error message when sign in fails', function () { - cy.visit( - '/signin?returnUrl=https%3A%2F%2Fwww.theguardian.com%2Fabout&useIdapi=true', - ); - cy.get('input[name="email"]').type('example@example.com'); - cy.get('input[name="password"]').type('password'); - cy.mockNext(403, { - status: 'error', - errors: [ - { - message: 'Invalid email or password', - }, - ], - }); - cy.get('[data-cy=main-form-submit-button]').click(); - cy.contains("Email and password don't match"); - cy.get('input[name="email"]').should('have.value', 'example@example.com'); - }); + context('Signing in', () => { + const defaultReturnUrl = 'https://m.code.dev-theguardian.com'; + it('shows an error message when sign in fails', function () { + cy.visit( + '/signin?returnUrl=https%3A%2F%2Fwww.theguardian.com%2Fabout&useIdapi=true', + ); + cy.get('input[name="email"]').type('example@example.com'); + cy.get('input[name="password"]').type('password'); + cy.mockNext(403, { + status: 'error', + errors: [ + { + message: 'Invalid email or password', + }, + ], + }); + cy.get('[data-cy=main-form-submit-button]').click(); + cy.contains("Email and password don't match"); + cy.get('input[name="email"]').should('have.value', 'example@example.com'); + }); - it('shows a generic error message when the api error response is not known', function () { - cy.visit( - '/signin?returnUrl=https%3A%2F%2Fwww.theguardian.com%2Fabout&useIdapi=true', - ); - cy.get('input[name="email"]').type('example@example.com'); - cy.get('input[name="password"]').type('password'); - cy.mockNext(403, { - status: 'error', - errors: [ - { - message: 'Bloopity flub', - }, - ], - }); - cy.get('[data-cy=main-form-submit-button]').click(); - cy.contains('There was a problem signing in, please try again'); - }); + it('shows a generic error message when the api error response is not known', function () { + cy.visit( + '/signin?returnUrl=https%3A%2F%2Fwww.theguardian.com%2Fabout&useIdapi=true', + ); + cy.get('input[name="email"]').type('example@example.com'); + cy.get('input[name="password"]').type('password'); + cy.mockNext(403, { + status: 'error', + errors: [ + { + message: 'Bloopity flub', + }, + ], + }); + cy.get('[data-cy=main-form-submit-button]').click(); + cy.contains('There was a problem signing in, please try again'); + }); - it('loads the returnUrl upon successful authentication', function () { - const returnUrl = 'https://www.theguardian.com/about'; - // Intercept the external redirect page. - // We just want to check that the redirect happens, not that the page loads. - cy.intercept('GET', returnUrl, (req) => { - req.reply(200); - }); - cy.visit( - `/signin?returnUrl=${encodeURIComponent(returnUrl)}&useIdapi=true`, - ); - cy.get('input[name="email"]').type('example@example.com'); - cy.get('input[name="password"]').type('password'); - cy.mockNext(200, { - cookies: { - values: [{ key: 'key', value: 'value' }], - expiresAt: new Date(Date.now() + 1800000 /* 30min */).toISOString(), - }, - }); - cy.get('[data-cy=main-form-submit-button]').click(); - cy.url().should('eq', returnUrl); - }); + it('loads the returnUrl upon successful authentication', function () { + const returnUrl = 'https://www.theguardian.com/about'; + // Intercept the external redirect page. + // We just want to check that the redirect happens, not that the page loads. + cy.intercept('GET', returnUrl, (req) => { + req.reply(200); + }); + cy.visit( + `/signin?returnUrl=${encodeURIComponent(returnUrl)}&useIdapi=true`, + ); + cy.get('input[name="email"]').type('example@example.com'); + cy.get('input[name="password"]').type('password'); + cy.mockNext(200, { + cookies: { + values: [{ key: 'key', value: 'value' }], + expiresAt: new Date(Date.now() + 1800000 /* 30min */).toISOString(), + }, + }); + cy.get('[data-cy=main-form-submit-button]').click(); + cy.url().should('eq', returnUrl); + }); - it('redirects to the default url if no returnUrl given', function () { - // Intercept the external redirect page. - // We just want to check that the redirect happens, not that the page loads. - cy.intercept('GET', 'https://m.code.dev-theguardian.com/', (req) => { - req.reply(200); - }); + it('redirects to the default url if no returnUrl given', function () { + // Intercept the external redirect page. + // We just want to check that the redirect happens, not that the page loads. + cy.intercept('GET', 'https://m.code.dev-theguardian.com/', (req) => { + req.reply(200); + }); - cy.visit('/signin?useIdapi=true'); - cy.get('input[name="email"]').type('example@example.com'); - cy.get('input[name="password"]').type('password'); - cy.mockNext(200, { - cookies: { - values: [{ key: 'key', value: 'value' }], - expiresAt: new Date(Date.now() + 1800000 /* 30min */).toISOString(), - }, - }); - cy.get('[data-cy=main-form-submit-button]').click(); - cy.url().should('include', defaultReturnUrl); - }); + cy.visit('/signin?useIdapi=true'); + cy.get('input[name="email"]').type('example@example.com'); + cy.get('input[name="password"]').type('password'); + cy.mockNext(200, { + cookies: { + values: [{ key: 'key', value: 'value' }], + expiresAt: new Date(Date.now() + 1800000 /* 30min */).toISOString(), + }, + }); + cy.get('[data-cy=main-form-submit-button]').click(); + cy.url().should('include', defaultReturnUrl); + }); - it('auto-fills the email field when encryptedEmail is successfully decrypted', () => { - cy.mockNext(200, { - status: 'ok', - email: 'test@test.com', - }); - cy.visit(`/signin?encryptedEmail=bdfalrbagbgu&useIdapi=true`); - cy.get('input[name="email"]').should('have.value', 'test@test.com'); - }); + it('auto-fills the email field when encryptedEmail is successfully decrypted', () => { + cy.mockNext(200, { + status: 'ok', + email: 'test@test.com', + }); + cy.visit(`/signin?encryptedEmail=bdfalrbagbgu&useIdapi=true`); + cy.get('input[name="email"]').should('have.value', 'test@test.com'); + }); - it('shows recaptcha error message when reCAPTCHA token request fails', () => { - cy.visit( - '/signin?returnUrl=https%3A%2F%2Fwww.theguardian.com%2Fabout&useIdapi=true', - ); - cy.get('input[name="email"]').type('placeholder@example.com'); - cy.get('input[name="password"]').type('definitelynotarealpassword'); - cy.intercept('POST', 'https://www.google.com/recaptcha/api2/**', { - statusCode: 500, - }); - cy.get('[data-cy=main-form-submit-button]').click(); - cy.contains('Google reCAPTCHA verification failed. Please try again.'); - }); + it('shows recaptcha error message when reCAPTCHA token request fails', () => { + cy.visit( + '/signin?returnUrl=https%3A%2F%2Fwww.theguardian.com%2Fabout&useIdapi=true', + ); + cy.get('input[name="email"]').type('placeholder@example.com'); + cy.get('input[name="password"]').type('definitelynotarealpassword'); + cy.intercept('POST', 'https://www.google.com/recaptcha/api2/**', { + statusCode: 500, + }); + cy.get('[data-cy=main-form-submit-button]').click(); + cy.contains('Google reCAPTCHA verification failed. Please try again.'); + }); - it('shows detailed recaptcha error message when reCAPTCHA token request fails two times', () => { - // Intercept "Report this error" link because we just check it is linked to. - cy.intercept( - 'GET', - 'https://manage.theguardian.com/help-centre/contact-us', - { - statusCode: 200, - }, - ); - cy.visit( - '/signin?returnUrl=https%3A%2F%2Fwww.theguardian.com%2Fabout&useIdapi=true', - ); - cy.get('input[name="email"]').type('placeholder@example.com'); - cy.get('input[name="password"]').type('definitelynotarealpassword'); - cy.intercept('POST', 'https://www.google.com/recaptcha/api2/**', { - statusCode: 500, - }); - cy.get('[data-cy=main-form-submit-button]').click(); - cy.contains('Google reCAPTCHA verification failed. Please try again.'); - cy.get('[data-cy=main-form-submit-button]').click(); - cy.contains('Google reCAPTCHA verification failed.'); - cy.contains('If the problem persists please try the following:'); - cy.contains('Report this error').click(); - cy.url().should( - 'eq', - 'https://manage.theguardian.com/help-centre/contact-us', - ); - }); + it('shows detailed recaptcha error message when reCAPTCHA token request fails two times', () => { + // Intercept "Report this error" link because we just check it is linked to. + cy.intercept( + 'GET', + 'https://manage.theguardian.com/help-centre/contact-us', + { + statusCode: 200, + }, + ); + cy.visit( + '/signin?returnUrl=https%3A%2F%2Fwww.theguardian.com%2Fabout&useIdapi=true', + ); + cy.get('input[name="email"]').type('placeholder@example.com'); + cy.get('input[name="password"]').type('definitelynotarealpassword'); + cy.intercept('POST', 'https://www.google.com/recaptcha/api2/**', { + statusCode: 500, + }); + cy.get('[data-cy=main-form-submit-button]').click(); + cy.contains('Google reCAPTCHA verification failed. Please try again.'); + cy.get('[data-cy=main-form-submit-button]').click(); + cy.contains('Google reCAPTCHA verification failed.'); + cy.contains('If the problem persists please try the following:'); + cy.contains('Report this error').click(); + cy.url().should( + 'eq', + 'https://manage.theguardian.com/help-centre/contact-us', + ); + }); - it('redirects to homepage when user with existing valid session visits signin page', () => { - cy.setCookie('SC_GU_U', 'sessionvalue'); - cy.setCookie('SC_GU_LA', 'la_value'); + it('redirects to homepage when user with existing valid session visits signin page', () => { + cy.setCookie('SC_GU_U', 'sessionvalue'); + cy.setCookie('SC_GU_LA', 'la_value'); - cy.request({ - url: '/signin', - followRedirect: false, - failOnStatusCode: false, - }).then((response) => { - expect(response.status).to.eq(302); - expect(response.redirectedToUrl).to.eq( - 'https://m.code.dev-theguardian.com/', - ); - }); - cy.mockNext(200, { - status: 'signedInRecently', - emailValidated: true, - }); - }); + cy.request({ + url: '/signin', + followRedirect: false, + failOnStatusCode: false, + }).then((response) => { + expect(response.status).to.eq(302); + expect(response.redirectedToUrl).to.eq( + 'https://m.code.dev-theguardian.com/', + ); + }); + cy.mockNext(200, { + status: 'signedInRecently', + emailValidated: true, + }); + }); - it('redirects to /reauthenticate when user with existing invalid session visits signin page', () => { - cy.setCookie('SC_GU_U', 'sessionvalue'); - cy.setCookie('SC_GU_LA', 'la_value'); + it('redirects to /reauthenticate when user with existing invalid session visits signin page', () => { + cy.setCookie('SC_GU_U', 'sessionvalue'); + cy.setCookie('SC_GU_LA', 'la_value'); - cy.mockNext(200, { - status: 'signedInNotRecently', - emailValidated: true, - }); - cy.visit('/signin?useIdapi=true'); + cy.mockNext(200, { + status: 'signedInNotRecently', + emailValidated: true, + }); + cy.visit('/signin?useIdapi=true'); - cy.location('pathname').should('eq', '/reauthenticate'); - }); - }); + cy.location('pathname').should('eq', '/reauthenticate'); + }); + }); - context('General IDAPI failure', () => { - it('displays a generic error message', function () { - cy.mockNext(500); - cy.visit( - '/signin?returnUrl=https%3A%2F%2Flocalhost%3A8861%2Fsignin&useIdapi=true', - ); - cy.get('input[name="email"]').type('example@example.com'); - cy.get('input[name="password"]').type('password'); - cy.get('[data-cy=main-form-submit-button]').click(); - cy.contains('There was a problem signing in, please try again'); - }); - }); + context('General IDAPI failure', () => { + it('displays a generic error message', function () { + cy.mockNext(500); + cy.visit( + '/signin?returnUrl=https%3A%2F%2Flocalhost%3A8861%2Fsignin&useIdapi=true', + ); + cy.get('input[name="email"]').type('example@example.com'); + cy.get('input[name="password"]').type('password'); + cy.get('[data-cy=main-form-submit-button]').click(); + cy.contains('There was a problem signing in, please try again'); + }); + }); - context('Redirect to post sign-in prompt', () => { - const defaultReturnUrl = 'https://m.code.dev-theguardian.com'; - const returnUrl = 'https://www.theguardian.com/about'; + context('Redirect to post sign-in prompt', () => { + const defaultReturnUrl = 'https://m.code.dev-theguardian.com'; + const returnUrl = 'https://www.theguardian.com/about'; - beforeEach(() => { - cy.mockAll(200, allConsents, CONSENTS_ENDPOINT); - cy.mockAll( - 200, - verifiedUserWithNoConsent.user.consents, - USER_CONSENTS_ENDPOINT, - ); - // Intercept the pages that we would redirect to. - // We just want to check that the redirect happens, not that the page loads. - cy.intercept('GET', '/signin/success*', (req) => { - req.reply(200); - }); - cy.intercept('GET', defaultReturnUrl, (req) => { - req.reply(200); - }); - cy.intercept('GET', returnUrl, (req) => { - req.reply(200); - }); - }); + beforeEach(() => { + cy.mockAll(200, allConsents, CONSENTS_ENDPOINT); + cy.mockAll( + 200, + verifiedUserWithNoConsent.user.consents, + USER_CONSENTS_ENDPOINT, + ); + // Intercept the pages that we would redirect to. + // We just want to check that the redirect happens, not that the page loads. + cy.intercept('GET', '/signin/success*', (req) => { + req.reply(200); + }); + cy.intercept('GET', defaultReturnUrl, (req) => { + req.reply(200); + }); + cy.intercept('GET', returnUrl, (req) => { + req.reply(200); + }); + }); - const signIn = (returnUrl = defaultReturnUrl) => { - cy.visit( - `/signin?returnUrl=${encodeURIComponent(returnUrl)}&useIdapi=true`, - ); - cy.get('input[name="email"]').type('example@example.com'); - cy.get('input[name="password"]').type('password'); - cy.mockNext(200, { - cookies: { - values: [{ key: 'SC_GU_U', value: 'something' }], - expiresAt: new Date(Date.now() + 1800000 /* 30min */).toISOString(), - }, - }); - cy.get('[data-cy=main-form-submit-button]').click(); - }; + const signIn = (returnUrl = defaultReturnUrl) => { + cy.visit( + `/signin?returnUrl=${encodeURIComponent(returnUrl)}&useIdapi=true`, + ); + cy.get('input[name="email"]').type('example@example.com'); + cy.get('input[name="password"]').type('password'); + cy.mockNext(200, { + cookies: { + values: [{ key: 'SC_GU_U', value: 'something' }], + expiresAt: new Date(Date.now() + 1800000 /* 30min */).toISOString(), + }, + }); + cy.get('[data-cy=main-form-submit-button]').click(); + }; - it('if all conditions met', () => { - signIn(); - cy.url().should('include', `/signin/success`); - cy.url().should('include', encodeURIComponent(defaultReturnUrl)); - }); + it('if all conditions met', () => { + signIn(); + cy.url().should('include', `/signin/success`); + cy.url().should('include', encodeURIComponent(defaultReturnUrl)); + }); - it('unless returnUrl is not default', () => { - signIn(returnUrl); - cy.url().should('include', returnUrl); - }); + it('unless returnUrl is not default', () => { + signIn(returnUrl); + cy.url().should('include', returnUrl); + }); - it('unless experiment already viewed', () => { - cy.setCookie( - 'GU_ran_experiments', - new URLSearchParams({ - OptInPromptPostSignIn: Date.now().toString(), - }).toString(), - ); - signIn(); - cy.url().should('include', defaultReturnUrl); - }); + it('unless experiment already viewed', () => { + cy.setCookie( + 'GU_ran_experiments', + new URLSearchParams({ + OptInPromptPostSignIn: Date.now().toString(), + }).toString(), + ); + signIn(); + cy.url().should('include', defaultReturnUrl); + }); - it('if no consent information for user', () => { - cy.mockAll(200, [], USER_CONSENTS_ENDPOINT); - signIn(); - cy.url().should('include', `/signin/success`); - cy.url().should('include', encodeURIComponent(defaultReturnUrl)); - }); + it('if no consent information for user', () => { + cy.mockAll(200, [], USER_CONSENTS_ENDPOINT); + signIn(); + cy.url().should('include', `/signin/success`); + cy.url().should('include', encodeURIComponent(defaultReturnUrl)); + }); - it('unless user has already consented', () => { - const supporterConsent = allConsents.find(({ id }) => id === 'supporter'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const consentData = [{ ...supporterConsent!, consented: true }]; - cy.mockAll(200, consentData, USER_CONSENTS_ENDPOINT); - signIn(); - cy.url().should('include', defaultReturnUrl); - }); + it('unless user has already consented', () => { + const supporterConsent = allConsents.find(({ id }) => id === 'supporter'); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const consentData = [{ ...supporterConsent!, consented: true }]; + cy.mockAll(200, consentData, USER_CONSENTS_ENDPOINT); + signIn(); + cy.url().should('include', defaultReturnUrl); + }); - it('unless fetching consents fails', () => { - cy.mockAll(500, undefined, CONSENTS_ENDPOINT); - signIn(); - cy.url().should('include', defaultReturnUrl); - }); + it('unless fetching consents fails', () => { + cy.mockAll(500, undefined, CONSENTS_ENDPOINT); + signIn(); + cy.url().should('include', defaultReturnUrl); + }); - it('unless fetching user consents fails', () => { - cy.mockAll(500, undefined, USER_CONSENTS_ENDPOINT); - signIn(); - cy.url().should('include', defaultReturnUrl); - }); - }); + it('unless fetching user consents fails', () => { + cy.mockAll(500, undefined, USER_CONSENTS_ENDPOINT); + signIn(); + cy.url().should('include', defaultReturnUrl); + }); + }); }); diff --git a/cypress/integration/mocked/sign_out.2.cy.ts b/cypress/integration/mocked/sign_out.2.cy.ts index 251c715217..09115be6ee 100644 --- a/cypress/integration/mocked/sign_out.2.cy.ts +++ b/cypress/integration/mocked/sign_out.2.cy.ts @@ -1,66 +1,66 @@ import { authCookieResponse } from '../../support/idapi/cookie'; describe('Sign out flow', () => { - const DotComCookies = [ - 'gu_user_features_expiry', - 'gu_paying_member', - 'gu_recurring_contributor', - 'gu_digital_subscriber', - ]; + const DotComCookies = [ + 'gu_user_features_expiry', + 'gu_paying_member', + 'gu_recurring_contributor', + 'gu_digital_subscriber', + ]; - context('Signs a user out', () => { - beforeEach(() => { - cy.mockPurge(); - // Disable redirect to /signin/success by default - cy.setCookie( - 'GU_ran_experiments', - new URLSearchParams({ - OptInPromptPostSignIn: Date.now().toString(), - }).toString(), - ); - }); + context('Signs a user out', () => { + beforeEach(() => { + cy.mockPurge(); + // Disable redirect to /signin/success by default + cy.setCookie( + 'GU_ran_experiments', + new URLSearchParams({ + OptInPromptPostSignIn: Date.now().toString(), + }).toString(), + ); + }); - it('Removes IDAPI log in cookies and dotcom cookies when signing out', () => { - const returnUrl = `https://${Cypress.env('BASE_URI')}/reset-password`; - // Intercept the external redirect page. - // We just want to check that the redirect happens, not that the page loads. - cy.visit( - `/signin?returnUrl=${encodeURIComponent(returnUrl)}&useIdapi=true`, - ); - cy.get('input[name="email"]').type('example@example.com'); - cy.get('input[name="password"]').type('password'); - cy.mockNext(200, { - status: 'ok', - ...authCookieResponse, - }); - cy.get('[data-cy=main-form-submit-button]').click(); - cy.url().should('eq', returnUrl); + it('Removes IDAPI log in cookies and dotcom cookies when signing out', () => { + const returnUrl = `https://${Cypress.env('BASE_URI')}/reset-password`; + // Intercept the external redirect page. + // We just want to check that the redirect happens, not that the page loads. + cy.visit( + `/signin?returnUrl=${encodeURIComponent(returnUrl)}&useIdapi=true`, + ); + cy.get('input[name="email"]').type('example@example.com'); + cy.get('input[name="password"]').type('password'); + cy.mockNext(200, { + status: 'ok', + ...authCookieResponse, + }); + cy.get('[data-cy=main-form-submit-button]').click(); + cy.url().should('eq', returnUrl); - cy.getCookie('SC_GU_U').should('exist'); - cy.getCookie('SC_GU_LA').should('exist'); - cy.getCookie('GU_U').should('exist'); + cy.getCookie('SC_GU_U').should('exist'); + cy.getCookie('SC_GU_LA').should('exist'); + cy.getCookie('GU_U').should('exist'); - cy.mockNext(200, { - status: 'ok', - cookies: { - values: [ - { - key: 'GU_SO', - value: 'the_GU_SO_cookie', - }, - ], - expiresAt: new Date(Date.now() + 1800000 /* 30min */).toISOString(), - }, - }); + cy.mockNext(200, { + status: 'ok', + cookies: { + values: [ + { + key: 'GU_SO', + value: 'the_GU_SO_cookie', + }, + ], + expiresAt: new Date(Date.now() + 1800000 /* 30min */).toISOString(), + }, + }); - cy.visit(`/signout?returnUrl=${encodeURIComponent(returnUrl)}`); - cy.getCookie('GU_SO').should('exist'); - cy.getCookie('SC_GU_U').should('not.exist'); - cy.getCookie('SC_GU_LA').should('not.exist'); - cy.getCookie('GU_U').should('not.exist'); - DotComCookies.forEach((cookie) => { - cy.getCookie(cookie).should('not.exist'); - }); - }); - }); + cy.visit(`/signout?returnUrl=${encodeURIComponent(returnUrl)}`); + cy.getCookie('GU_SO').should('exist'); + cy.getCookie('SC_GU_U').should('not.exist'); + cy.getCookie('SC_GU_LA').should('not.exist'); + cy.getCookie('GU_U').should('not.exist'); + DotComCookies.forEach((cookie) => { + cy.getCookie(cookie).should('not.exist'); + }); + }); + }); }); diff --git a/cypress/integration/mocked/unsubscribe.5.cy.ts b/cypress/integration/mocked/unsubscribe.5.cy.ts index b8cccca7e2..8fed9c3194 100644 --- a/cypress/integration/mocked/unsubscribe.5.cy.ts +++ b/cypress/integration/mocked/unsubscribe.5.cy.ts @@ -1,65 +1,65 @@ import { injectAndCheckAxe } from '../../support/cypress-axe'; describe('Unsubscribe newsletter/marketing email', () => { - beforeEach(() => { - cy.mockPurge(); - }); + beforeEach(() => { + cy.mockPurge(); + }); - context('a11y checks', () => { - it('Has no detectable a11y violations on unsubscribe complete page', () => { - cy.mockNext(200); - cy.visit( - '/unsubscribe/newsletter/pushing-buttons%3A1000000%3A1677075570/token', - ); - injectAndCheckAxe(); - }); + context('a11y checks', () => { + it('Has no detectable a11y violations on unsubscribe complete page', () => { + cy.mockNext(200); + cy.visit( + '/unsubscribe/newsletter/pushing-buttons%3A1000000%3A1677075570/token', + ); + injectAndCheckAxe(); + }); - it('Has no detectable a11y violations on unsubscribe error page', () => { - cy.mockNext(400); - cy.visit( - '/unsubscribe/newsletter/pushing-buttons%3A1000000%3A1677075570/token', - ); - injectAndCheckAxe(); - }); - }); + it('Has no detectable a11y violations on unsubscribe error page', () => { + cy.mockNext(400); + cy.visit( + '/unsubscribe/newsletter/pushing-buttons%3A1000000%3A1677075570/token', + ); + injectAndCheckAxe(); + }); + }); - context('unsubscribe flow', () => { - it('should be able to unsubscribe from a newsletter', () => { - cy.mockNext(200); - cy.visit( - '/unsubscribe/newsletter/pushing-buttons%3A1000000%3A1677075570/token', - ); - cy.contains('You have been unsubscribed.'); - }); + context('unsubscribe flow', () => { + it('should be able to unsubscribe from a newsletter', () => { + cy.mockNext(200); + cy.visit( + '/unsubscribe/newsletter/pushing-buttons%3A1000000%3A1677075570/token', + ); + cy.contains('You have been unsubscribed.'); + }); - it('should be able to unsubscribe from a marketing email', () => { - cy.mockNext(200); - cy.visit('/unsubscribe/marketing/supporter%3A1000000%3A1677075570/token'); - cy.contains('You have been unsubscribed.'); - }); + it('should be able to unsubscribe from a marketing email', () => { + cy.mockNext(200); + cy.visit('/unsubscribe/marketing/supporter%3A1000000%3A1677075570/token'); + cy.contains('You have been unsubscribed.'); + }); - it('should be able to handle a unsubscribe error if emailType is not newsletter/marketing', () => { - cy.visit('/unsubscribe/fake/supporter%3A1000000%3A1677075570/token'); - cy.contains('Unable to unsubscribe.'); - }); + it('should be able to handle a unsubscribe error if emailType is not newsletter/marketing', () => { + cy.visit('/unsubscribe/fake/supporter%3A1000000%3A1677075570/token'); + cy.contains('Unable to unsubscribe.'); + }); - it('should be able to handle a unsubscribe error if data is not valid', () => { - cy.visit( - '/unsubscribe/newsletter/pushing-buttons%3A1000000%3A16770755abc70/token', - ); - cy.contains('Unable to unsubscribe.'); + it('should be able to handle a unsubscribe error if data is not valid', () => { + cy.visit( + '/unsubscribe/newsletter/pushing-buttons%3A1000000%3A16770755abc70/token', + ); + cy.contains('Unable to unsubscribe.'); - cy.visit('/unsubscribe/newsletter/pushing-buttons%3A1000000%3A/token'); - cy.contains('Unable to unsubscribe.'); + cy.visit('/unsubscribe/newsletter/pushing-buttons%3A1000000%3A/token'); + cy.contains('Unable to unsubscribe.'); - cy.visit('/unsubscribe/newsletter/pushing-buttons-bad-data/token'); - cy.contains('Unable to unsubscribe.'); - }); + cy.visit('/unsubscribe/newsletter/pushing-buttons-bad-data/token'); + cy.contains('Unable to unsubscribe.'); + }); - it('should be able to handle a unsubscribe error if api error', () => { - cy.mockNext(400); - cy.visit('/unsubscribe/fake/supporter%3A1000000%3A1677075570/token'); - cy.contains('Unable to unsubscribe.'); - }); - }); + it('should be able to handle a unsubscribe error if api error', () => { + cy.mockNext(400); + cy.visit('/unsubscribe/fake/supporter%3A1000000%3A1677075570/token'); + cy.contains('Unable to unsubscribe.'); + }); + }); }); diff --git a/cypress/integration/mocked/verify_email.4.cy.ts b/cypress/integration/mocked/verify_email.4.cy.ts index eede2df06a..02f1f4abdb 100644 --- a/cypress/integration/mocked/verify_email.4.cy.ts +++ b/cypress/integration/mocked/verify_email.4.cy.ts @@ -5,317 +5,317 @@ import { allConsents } from '../../support/idapi/consent'; import { authCookieResponse, setAuthCookies } from '../../support/idapi/cookie'; import { verifiedUserWithNoConsent } from '../../support/idapi/user'; import { - validationTokenExpired, - validationTokenInvalid, + validationTokenExpired, + validationTokenInvalid, } from '../../support/idapi/verify_email'; import VerifyEmail from '../../support/pages/verify_email'; describe('Verify email flow', () => { - const verifyEmailFlow = new VerifyEmail(); + const verifyEmailFlow = new VerifyEmail(); - beforeEach(() => { - cy.mockPurge(); - cy.visit('/signin?useIdapi=true'); - }); + beforeEach(() => { + cy.mockPurge(); + cy.visit('/signin?useIdapi=true'); + }); - context('A11y checks', () => { - it('has no detectable a11y violations on logged out resend validation email page', () => { - // mock token expired - cy.mockNext(403, validationTokenExpired); + context('A11y checks', () => { + it('has no detectable a11y violations on logged out resend validation email page', () => { + // mock token expired + cy.mockNext(403, validationTokenExpired); - // go to verify email endpont - verifyEmailFlow.goto('expiredtoken', { failOnStatusCode: false }); + // go to verify email endpont + verifyEmailFlow.goto('expiredtoken', { failOnStatusCode: false }); - injectAndCheckAxe(); - }); + injectAndCheckAxe(); + }); - it('has no detectable a11y violations on logged in resend validation email page', () => { - // set logged in cookies - setAuthCookies(); + it('has no detectable a11y violations on logged in resend validation email page', () => { + // set logged in cookies + setAuthCookies(); - // mock token expired - cy.mockNext(403, validationTokenExpired); + // mock token expired + cy.mockNext(403, validationTokenExpired); - // mock user response - cy.mockNext(200, verifiedUserWithNoConsent); + // mock user response + cy.mockNext(200, verifiedUserWithNoConsent); - // mock user response again for sending validation email - cy.mockNext(200, verifiedUserWithNoConsent); + // mock user response again for sending validation email + cy.mockNext(200, verifiedUserWithNoConsent); - // mock validation email sent - cy.mockNext(200); + // mock validation email sent + cy.mockNext(200); - // go to verify email endpont - verifyEmailFlow.goto('expiredtoken', { failOnStatusCode: false }); + // go to verify email endpont + verifyEmailFlow.goto('expiredtoken', { failOnStatusCode: false }); - // check a11y before clicking send email - injectAndCheckAxe(); + // check a11y before clicking send email + injectAndCheckAxe(); - // click on send link button - cy.contains(VerifyEmail.CONTENT.SEND_LINK).click(); - // check a11y after clicking send email - injectAndCheckAxe(); - }); - }); + // click on send link button + cy.contains(VerifyEmail.CONTENT.SEND_LINK).click(); + // check a11y after clicking send email + injectAndCheckAxe(); + }); + }); - context('Verify email', () => { - it('successfully verifies the email using a token and sets auth cookies', () => { - // mock validation success response (200 with auth cookies) - cy.mockNext(200, authCookieResponse); + context('Verify email', () => { + it('successfully verifies the email using a token and sets auth cookies', () => { + // mock validation success response (200 with auth cookies) + cy.mockNext(200, authCookieResponse); - // set these cookies manually - // TODO: can cypress set the automatically? - setAuthCookies(); + // set these cookies manually + // TODO: can cypress set the automatically? + setAuthCookies(); - // set successful auth using login middleware - cy.mockNext(200, authRedirectSignInRecentlyEmailValidated); + // set successful auth using login middleware + cy.mockNext(200, authRedirectSignInRecentlyEmailValidated); - // all newsletters mock response for first page of consents flow - cy.mockNext(200, allConsents); + // all newsletters mock response for first page of consents flow + cy.mockNext(200, allConsents); - // user newsletters mock response for first page of consents flow - cy.mockNext(200, verifiedUserWithNoConsent.user.consents); + // user newsletters mock response for first page of consents flow + cy.mockNext(200, verifiedUserWithNoConsent.user.consents); - // go to verify email endpoint - verifyEmailFlow.goto('avalidtoken', { query: { useIdapi: 'true' } }); + // go to verify email endpoint + verifyEmailFlow.goto('avalidtoken', { query: { useIdapi: 'true' } }); - // check if verified email text exists - cy.contains(VerifyEmail.CONTENT.EMAIL_VERIFIED); + // check if verified email text exists + cy.contains(VerifyEmail.CONTENT.EMAIL_VERIFIED); - // check if we're on the consents flow - cy.url().should('include', '/consents'); - }); + // check if we're on the consents flow + cy.url().should('include', '/consents'); + }); - it('successfully verifies the email using a token and sets auth cookies, and preserves encoded return url', () => { - // mock validation success response (200 with auth cookies) - cy.mockNext(200, authCookieResponse); + it('successfully verifies the email using a token and sets auth cookies, and preserves encoded return url', () => { + // mock validation success response (200 with auth cookies) + cy.mockNext(200, authCookieResponse); - // set these cookies manually - // TODO: can cypress set the automatically? - setAuthCookies(); + // set these cookies manually + // TODO: can cypress set the automatically? + setAuthCookies(); - // set successful auth using login middleware - cy.mockNext(200, authRedirectSignInRecentlyEmailValidated); + // set successful auth using login middleware + cy.mockNext(200, authRedirectSignInRecentlyEmailValidated); - // all newsletters mock response for first page of consents flow - cy.mockNext(200, allConsents); + // all newsletters mock response for first page of consents flow + cy.mockNext(200, allConsents); - // user newsletters mock response for first page of consents flow - cy.mockNext(200, verifiedUserWithNoConsent.user.consents); + // user newsletters mock response for first page of consents flow + cy.mockNext(200, verifiedUserWithNoConsent.user.consents); - const returnUrl = encodeURIComponent( - `https://www.theguardian.com/science/grrlscientist/2012/aug/07/3`, - ); - - // go to verify email endpoint - verifyEmailFlow.goto('avalidtoken', { - query: { - returnUrl, - useIdapi: 'true', - }, - }); - - // check if verified email text exists - cy.contains(VerifyEmail.CONTENT.EMAIL_VERIFIED); - - // check if we're on the consents flow - cy.url().should('include', '/consents'); - - // check return url exists - cy.url().should('include', `returnUrl=${returnUrl}`); - }); + const returnUrl = encodeURIComponent( + `https://www.theguardian.com/science/grrlscientist/2012/aug/07/3`, + ); + + // go to verify email endpoint + verifyEmailFlow.goto('avalidtoken', { + query: { + returnUrl, + useIdapi: 'true', + }, + }); + + // check if verified email text exists + cy.contains(VerifyEmail.CONTENT.EMAIL_VERIFIED); + + // check if we're on the consents flow + cy.url().should('include', '/consents'); + + // check return url exists + cy.url().should('include', `returnUrl=${returnUrl}`); + }); - it('successfully verifies the email using a token and sets auth cookies, and preserves and encodes return url', () => { - // mock validation success response (200 with auth cookies) - cy.mockNext(200, authCookieResponse); + it('successfully verifies the email using a token and sets auth cookies, and preserves and encodes return url', () => { + // mock validation success response (200 with auth cookies) + cy.mockNext(200, authCookieResponse); - // set these cookies manually - // TODO: can cypress set the automatically? - setAuthCookies(); - - // set successful auth using login middleware - cy.mockNext(200, authRedirectSignInRecentlyEmailValidated); - - // all newsletters mock response for first page of consents flow - cy.mockNext(200, allConsents); - - // user newsletters mock response for first page of consents flow - cy.mockNext(200, verifiedUserWithNoConsent.user.consents); - - const returnUrl = `https://www.theguardian.com/science/grrlscientist/2012/aug/07/3`; - - // go to verify email endpoint - verifyEmailFlow.goto('avalidtoken', { - query: { - returnUrl, - useIdapi: 'true', - }, - }); - - // check if verified email text exists - cy.contains(VerifyEmail.CONTENT.EMAIL_VERIFIED); - - // check if we're on the consents flow - cy.url().should('include', '/consents'); + // set these cookies manually + // TODO: can cypress set the automatically? + setAuthCookies(); + + // set successful auth using login middleware + cy.mockNext(200, authRedirectSignInRecentlyEmailValidated); + + // all newsletters mock response for first page of consents flow + cy.mockNext(200, allConsents); + + // user newsletters mock response for first page of consents flow + cy.mockNext(200, verifiedUserWithNoConsent.user.consents); + + const returnUrl = `https://www.theguardian.com/science/grrlscientist/2012/aug/07/3`; + + // go to verify email endpoint + verifyEmailFlow.goto('avalidtoken', { + query: { + returnUrl, + useIdapi: 'true', + }, + }); + + // check if verified email text exists + cy.contains(VerifyEmail.CONTENT.EMAIL_VERIFIED); + + // check if we're on the consents flow + cy.url().should('include', '/consents'); - // check return url exists - cy.url().should('include', `returnUrl=${encodeURIComponent(returnUrl)}`); - }); + // check return url exists + cy.url().should('include', `returnUrl=${encodeURIComponent(returnUrl)}`); + }); - it('verification token is expired, logged out, shows page to sign in to resend validation email', () => { - const signInUrl = Cypress.env('SIGN_IN_PAGE_URL'); - // mock token expired - cy.mockNext(403, validationTokenExpired); + it('verification token is expired, logged out, shows page to sign in to resend validation email', () => { + const signInUrl = Cypress.env('SIGN_IN_PAGE_URL'); + // mock token expired + cy.mockNext(403, validationTokenExpired); - // go to verify email endpont - verifyEmailFlow.goto('expiredtoken', { - failOnStatusCode: false, - query: { useIdapi: 'true' }, - }); - - cy.contains(VerifyEmail.CONTENT.LINK_EXPIRED); - cy.contains(VerifyEmail.CONTENT.TOKEN_EXPIRED); - - // check sign in link - cy.contains('a', VerifyEmail.CONTENT.SIGN_IN) - .should('have.attr', 'href') - .and( - 'include', - `${signInUrl}?returnUrl=${encodeURIComponent( - `https://${Cypress.env('BASE_URI')}/verify-email`, - )}`, - ); - }); - - it('verification token is invalid, logged out, shows page to sign in to resend validation email', () => { - const signInUrl = Cypress.env('SIGN_IN_PAGE_URL'); - // mock token invalid - cy.mockNext(403, validationTokenInvalid); - - // go to verify email endpont - verifyEmailFlow.goto('aninvalidtoken', { - failOnStatusCode: false, - query: { useIdapi: 'true' }, - }); - - cy.contains(VerifyEmail.CONTENT.LINK_EXPIRED); - cy.contains(VerifyEmail.CONTENT.TOKEN_EXPIRED); - - // check sign in link - cy.contains('a', VerifyEmail.CONTENT.SIGN_IN) - .should('have.attr', 'href') - .and( - 'include', - `${signInUrl}?returnUrl=${encodeURIComponent( - `https://${Cypress.env('BASE_URI')}/verify-email`, - )}`, - ); - }); - - it('verification token is expired, logged in, shows page to to resend validation email', () => { - // set logged in cookies - setAuthCookies(); - - // mock token expired - cy.mockNext(403, validationTokenExpired); - - // mock user response - cy.mockNext(200, verifiedUserWithNoConsent); - - // mock user response again for sending validation email - cy.mockNext(200, verifiedUserWithNoConsent); - - // mock validation email sent - cy.mockNext(200); - - // go to verify email endpont - verifyEmailFlow.goto('expiredtoken', { - failOnStatusCode: false, - query: { useIdapi: 'true' }, - }); - - cy.contains(VerifyEmail.CONTENT.VERIFY_EMAIL); - cy.contains(VerifyEmail.CONTENT.CONFIRM_EMAIL); - cy.contains(verifiedUserWithNoConsent.user.primaryEmailAddress); - - // click on send link button - cy.contains(VerifyEmail.CONTENT.SEND_LINK).click(); - - // expect email sent page - cy.contains(VerifyEmail.CONTENT.EMAIL_SENT); - }); - - it('verification token is invalid, logged in, shows page to to resend validation email, and send email', () => { - // set logged in cookies - setAuthCookies(); - - // mock token invalid - cy.mockNext(403, validationTokenInvalid); - - // mock user response - cy.mockNext(200, verifiedUserWithNoConsent); - - // mock user response again for sending validation email - cy.mockNext(200, verifiedUserWithNoConsent); - - // mock validation email sent - cy.mockNext(200); - - // go to verify email endpont - verifyEmailFlow.goto('expiredtoken', { - failOnStatusCode: false, - query: { useIdapi: 'true' }, - }); - - cy.contains(VerifyEmail.CONTENT.VERIFY_EMAIL); - cy.contains(VerifyEmail.CONTENT.CONFIRM_EMAIL); - cy.contains(verifiedUserWithNoConsent.user.primaryEmailAddress); - - // click on send link button - cy.contains(VerifyEmail.CONTENT.SEND_LINK).click(); - - // expect email sent page - cy.contains(VerifyEmail.CONTENT.EMAIL_SENT); - }); - - it('verification token is invalid, logged in, shows page to to resend validation email and shows recaptcha error message when reCAPTCHA token request fails', () => { - // set logged in cookies - setAuthCookies(); - - // mock token invalid - cy.mockNext(403, validationTokenInvalid); - - // mock user response - cy.mockNext(200, verifiedUserWithNoConsent); - - // mock user response again for sending validation email - cy.mockNext(200, verifiedUserWithNoConsent); - - // mock validation email sent - cy.mockNext(200); - - // go to verify email endpont - verifyEmailFlow.goto('expiredtoken', { - failOnStatusCode: false, - query: { useIdapi: 'true' }, - }); + // go to verify email endpont + verifyEmailFlow.goto('expiredtoken', { + failOnStatusCode: false, + query: { useIdapi: 'true' }, + }); + + cy.contains(VerifyEmail.CONTENT.LINK_EXPIRED); + cy.contains(VerifyEmail.CONTENT.TOKEN_EXPIRED); + + // check sign in link + cy.contains('a', VerifyEmail.CONTENT.SIGN_IN) + .should('have.attr', 'href') + .and( + 'include', + `${signInUrl}?returnUrl=${encodeURIComponent( + `https://${Cypress.env('BASE_URI')}/verify-email`, + )}`, + ); + }); + + it('verification token is invalid, logged out, shows page to sign in to resend validation email', () => { + const signInUrl = Cypress.env('SIGN_IN_PAGE_URL'); + // mock token invalid + cy.mockNext(403, validationTokenInvalid); + + // go to verify email endpont + verifyEmailFlow.goto('aninvalidtoken', { + failOnStatusCode: false, + query: { useIdapi: 'true' }, + }); + + cy.contains(VerifyEmail.CONTENT.LINK_EXPIRED); + cy.contains(VerifyEmail.CONTENT.TOKEN_EXPIRED); + + // check sign in link + cy.contains('a', VerifyEmail.CONTENT.SIGN_IN) + .should('have.attr', 'href') + .and( + 'include', + `${signInUrl}?returnUrl=${encodeURIComponent( + `https://${Cypress.env('BASE_URI')}/verify-email`, + )}`, + ); + }); + + it('verification token is expired, logged in, shows page to to resend validation email', () => { + // set logged in cookies + setAuthCookies(); + + // mock token expired + cy.mockNext(403, validationTokenExpired); + + // mock user response + cy.mockNext(200, verifiedUserWithNoConsent); + + // mock user response again for sending validation email + cy.mockNext(200, verifiedUserWithNoConsent); + + // mock validation email sent + cy.mockNext(200); + + // go to verify email endpont + verifyEmailFlow.goto('expiredtoken', { + failOnStatusCode: false, + query: { useIdapi: 'true' }, + }); + + cy.contains(VerifyEmail.CONTENT.VERIFY_EMAIL); + cy.contains(VerifyEmail.CONTENT.CONFIRM_EMAIL); + cy.contains(verifiedUserWithNoConsent.user.primaryEmailAddress); + + // click on send link button + cy.contains(VerifyEmail.CONTENT.SEND_LINK).click(); + + // expect email sent page + cy.contains(VerifyEmail.CONTENT.EMAIL_SENT); + }); + + it('verification token is invalid, logged in, shows page to to resend validation email, and send email', () => { + // set logged in cookies + setAuthCookies(); + + // mock token invalid + cy.mockNext(403, validationTokenInvalid); + + // mock user response + cy.mockNext(200, verifiedUserWithNoConsent); + + // mock user response again for sending validation email + cy.mockNext(200, verifiedUserWithNoConsent); + + // mock validation email sent + cy.mockNext(200); + + // go to verify email endpont + verifyEmailFlow.goto('expiredtoken', { + failOnStatusCode: false, + query: { useIdapi: 'true' }, + }); + + cy.contains(VerifyEmail.CONTENT.VERIFY_EMAIL); + cy.contains(VerifyEmail.CONTENT.CONFIRM_EMAIL); + cy.contains(verifiedUserWithNoConsent.user.primaryEmailAddress); + + // click on send link button + cy.contains(VerifyEmail.CONTENT.SEND_LINK).click(); + + // expect email sent page + cy.contains(VerifyEmail.CONTENT.EMAIL_SENT); + }); + + it('verification token is invalid, logged in, shows page to to resend validation email and shows recaptcha error message when reCAPTCHA token request fails', () => { + // set logged in cookies + setAuthCookies(); + + // mock token invalid + cy.mockNext(403, validationTokenInvalid); + + // mock user response + cy.mockNext(200, verifiedUserWithNoConsent); + + // mock user response again for sending validation email + cy.mockNext(200, verifiedUserWithNoConsent); + + // mock validation email sent + cy.mockNext(200); + + // go to verify email endpont + verifyEmailFlow.goto('expiredtoken', { + failOnStatusCode: false, + query: { useIdapi: 'true' }, + }); - cy.contains(VerifyEmail.CONTENT.VERIFY_EMAIL); - cy.contains(VerifyEmail.CONTENT.CONFIRM_EMAIL); - cy.contains(verifiedUserWithNoConsent.user.primaryEmailAddress); + cy.contains(VerifyEmail.CONTENT.VERIFY_EMAIL); + cy.contains(VerifyEmail.CONTENT.CONFIRM_EMAIL); + cy.contains(verifiedUserWithNoConsent.user.primaryEmailAddress); - cy.intercept('POST', 'https://www.google.com/recaptcha/api2/**', { - statusCode: 500, - }); + cy.intercept('POST', 'https://www.google.com/recaptcha/api2/**', { + statusCode: 500, + }); - cy.contains(VerifyEmail.CONTENT.SEND_LINK).click(); - cy.contains('Google reCAPTCHA verification failed. Please try again.'); + cy.contains(VerifyEmail.CONTENT.SEND_LINK).click(); + cy.contains('Google reCAPTCHA verification failed. Please try again.'); - // show extended reCaptcha message on the second failure - cy.contains(VerifyEmail.CONTENT.SEND_LINK).click(); - cy.contains('Google reCAPTCHA verification failed.'); - cy.contains('If the problem persists please try the following:'); - cy.contains('userhelp@'); - }); - }); + // show extended reCaptcha message on the second failure + cy.contains(VerifyEmail.CONTENT.SEND_LINK).click(); + cy.contains('Google reCAPTCHA verification failed.'); + cy.contains('If the problem persists please try the following:'); + cy.contains('userhelp@'); + }); + }); }); diff --git a/cypress/integration/mocked/welcome.4.cy.ts b/cypress/integration/mocked/welcome.4.cy.ts index 48d11d9e8f..ce6edee69d 100644 --- a/cypress/integration/mocked/welcome.4.cy.ts +++ b/cypress/integration/mocked/welcome.4.cy.ts @@ -1,329 +1,329 @@ import { injectAndCheckAxe } from '../../support/cypress-axe'; import { - authRedirectSignInRecentlyEmailValidated, - AUTH_REDIRECT_ENDPOINT, + authRedirectSignInRecentlyEmailValidated, + AUTH_REDIRECT_ENDPOINT, } from '../../support/idapi/auth'; import { allConsents, CONSENTS_ENDPOINT } from '../../support/idapi/consent'; import { setAuthCookies } from '../../support/idapi/cookie'; import { - verifiedUserWithNoConsent, - USER_ENDPOINT, - USER_CONSENTS_ENDPOINT, + verifiedUserWithNoConsent, + USER_ENDPOINT, + USER_CONSENTS_ENDPOINT, } from '../../support/idapi/user'; import CommunicationsPage from '../../support/pages/onboarding/communications_page'; describe('Welcome and set password page', () => { - const defaultEmail = 'someone@theguardian.com'; - const checkTokenSuccessResponse = ( - timeUntilExpiry: number | null = null, - email = defaultEmail, - ) => ({ - user: { - primaryEmailAddress: email, - }, - timeUntilExpiry, - }); - - const fakeCookieSuccessResponse = { - cookies: { - values: [ - { - key: 'GU_U', - value: 'FAKE_VALUE_0', - }, - { - key: 'SC_GU_LA', - value: 'FAKE_VALUE_1', - sessionCookie: true, - }, - { - key: 'SC_GU_U', - value: 'FAKE_VALUE_2', - }, - ], - expiresAt: new Date(Date.now() + 1800000 /* 30min */).toISOString(), - }, - }; - - const fakeGroupAddResponse = { - status: 'ok', - groupCode: 'GRS', - }; - - const setPasswordAndSignIn = () => { - cy.mockNext(200, checkTokenSuccessResponse()); - cy.intercept({ - method: 'GET', - url: 'https://api.pwnedpasswords.com/range/*', - }).as('breachCheck'); - cy.mockNext(200, fakeCookieSuccessResponse); - cy.mockAll( - 200, - authRedirectSignInRecentlyEmailValidated, - AUTH_REDIRECT_ENDPOINT, - ); - cy.mockAll(200, allConsents, CONSENTS_ENDPOINT); - // cy.mockAll(200, verifiedUserWithNoConsent, USER_ENDPOINT); - cy.mockAll( - 200, - verifiedUserWithNoConsent.user.consents, - USER_CONSENTS_ENDPOINT, - ); - cy.visit(`/welcome/fake_token?useIdapi=true`); - cy.get('input[name="password"]').type('thisisalongandunbreachedpassword'); - cy.wait('@breachCheck'); - cy.get('button[type="submit"]').click(); - }; - - beforeEach(() => { - cy.mockPurge(); - }); - - context('A11y checks', () => { - it('has no detectable a11y violations on the set password page', () => { - cy.mockNext(200, checkTokenSuccessResponse()); - cy.visit(`/welcome/fake_token?useIdapi=true`); - injectAndCheckAxe(); - }); - - it('has no detectable a11y violations on set password page with global error', () => { - cy.mockNext(200, checkTokenSuccessResponse()); - cy.visit(`/welcome/fake_token?useIdapi=true`); - cy.mockNext(500); - cy.get('input[name="password"]').type('short'); - cy.get('button[type="submit"]').click(); - injectAndCheckAxe(); - }); - - it('has no detectable a11y violations on the resend page', () => { - cy.visit(`/welcome/resend?useIdapi=true`); - injectAndCheckAxe(); - }); - - it('has no detectable a11y violations on the resend page with global error', () => { - cy.visit(`/welcome/resend?useIdapi=true`); - - cy.mockNext(500); - cy.get('input[name="email"]').type( - checkTokenSuccessResponse().user.primaryEmailAddress, - ); - cy.get('button[type="submit"]').click(); - injectAndCheckAxe(); - }); - - it('has no detectable a11y violations on the email sent page with resend box', () => { - cy.visit(`/welcome/resend?useIdapi=true`); - - cy.mockNext(200); - cy.get('input[name="email"]').type( - checkTokenSuccessResponse().user.primaryEmailAddress, - ); - cy.get('button[type="submit"]').click(); - injectAndCheckAxe(); - }); - - it('has no detectable a11y violations on the email sent page without resend box', () => { - cy.visit(`/welcome/email-sent?useIdapi=true`); - injectAndCheckAxe(); - }); - }); - - // successful token, set password page is displayed, redirect to consents flow if valid password - context('A valid token is used and set password page is displayed', () => { - it('redirects to onboarding flow if a valid password is set', () => { - setPasswordAndSignIn(); - cy.contains('Thank you for registering'); - }); - - it('shows a different message if the user has set a password and then clicked the back button', () => { - setPasswordAndSignIn(); - cy.go('back'); - cy.contains(`Password already set for ${defaultEmail}`); - cy.contains('Continue').click(); - cy.contains('Thank you for registering'); - }); - - it('redirects to onboarding flow if valid password is set and preserves returnUrl', () => { - const returnUrl = encodeURIComponent( - `https://www.theguardian.com/science/grrlscientist/2012/aug/07/3`, - ); - const query = new URLSearchParams({ returnUrl }).toString(); - - cy.mockNext(200, checkTokenSuccessResponse()); - cy.intercept({ - method: 'GET', - url: 'https://api.pwnedpasswords.com/range/*', - }).as('breachCheck'); - cy.mockNext(200, fakeCookieSuccessResponse); - cy.mockAll( - 200, - authRedirectSignInRecentlyEmailValidated, - AUTH_REDIRECT_ENDPOINT, - ); - cy.mockAll(200, allConsents, CONSENTS_ENDPOINT); - cy.mockAll(200, verifiedUserWithNoConsent, USER_ENDPOINT); - setAuthCookies(); - - cy.visit(`/welcome/fake_token?${query}&useIdapi=true`); - cy.get('input[name="password"]').type('thisisalongandunbreachedpassword'); - cy.wait('@breachCheck'); - cy.get('button[type="submit"]').click(); - cy.contains('Thank you for registering'); - cy.url().should('include', CommunicationsPage.URL); - cy.url().should('include', `returnUrl=${returnUrl}`); - }); - - it('redirects to onboarding flow and adds Jobs user to the GRS group if valid password is set and preserves returnUrl', () => { - const returnUrl = encodeURIComponent( - `https://www.theguardian.com/science/grrlscientist/2012/aug/07/3`, - ); - const clientId = 'jobs'; - const query = new URLSearchParams({ returnUrl, clientId }).toString(); - - cy.mockNext(200, checkTokenSuccessResponse()); - cy.intercept({ - method: 'GET', - url: 'https://api.pwnedpasswords.com/range/*', - }).as('breachCheck'); - cy.mockNext(200, fakeCookieSuccessResponse); - cy.mockAll(200, fakeGroupAddResponse, '/user/me/group/GRS'); - cy.mockAll( - 200, - authRedirectSignInRecentlyEmailValidated, - AUTH_REDIRECT_ENDPOINT, - ); - cy.mockAll(200, allConsents, CONSENTS_ENDPOINT); - cy.mockAll(200, verifiedUserWithNoConsent, USER_ENDPOINT); - setAuthCookies(); - - cy.visit(`/welcome/fake_token?${query}&useIdapi=true`); - cy.get('input[name="firstName"]').type('First Name'); - cy.get('input[name="secondName"]').type('Last Name'); - cy.get('input[name="password"]').type('thisisalongandunbreachedpassword'); - cy.wait('@breachCheck'); - cy.get('button[type="submit"]').click(); - cy.contains('Thank you for registering'); - cy.url().should('include', CommunicationsPage.URL); - cy.url().should('include', `returnUrl=${returnUrl}`); - cy.url().should('include', `clientId=${clientId}`); - }); - - it('shows an error if the password is invalid', () => { - cy.mockNext(200, checkTokenSuccessResponse()); - cy.mockNext(400, { - status: 'error', - errors: [ - { - message: 'Breached password', - }, - ], - }); - cy.mockNext(200, checkTokenSuccessResponse()); - cy.visit(`/welcome/fake_token?useIdapi=true`); - cy.get('input[name="password"]').type('password'); - cy.get('button[type="submit"]').click(); - cy.contains('Please use a password that is hard to guess.'); - }); - - it('shows prompt to create password', () => { - cy.mockNext(200, checkTokenSuccessResponse()); - cy.intercept({ - method: 'GET', - url: 'https://api.pwnedpasswords.com/range/*', - }).as('breachCheck'); - cy.visit(`/welcome/fake_token?useIdapi=true`); - cy.contains(`Please complete your details for ${defaultEmail}`); - }); - - it('shows prompt to create password without email if none exists', () => { - cy.mockNext(200, checkTokenSuccessResponse(null, '')); - cy.intercept({ - method: 'GET', - url: 'https://api.pwnedpasswords.com/range/*', - }).as('breachCheck'); - cy.visit(`/welcome/fake_token?useIdapi=true`); - cy.contains(`Please complete your details for your new account`); - }); - }); - - context('An expired/invalid token is used', () => { - it('shows the link expired page to type email, and on submit shows the email sent page, with a button to resend the email', () => { - cy.mockNext(500, { - status: 'error', - errors: [ - { - message: 'Invalid token', - }, - ], - }); - cy.mockNext(200); - cy.mockNext(200); - cy.visit(`/welcome/fake_token?useIdapi=true`); - cy.contains('Link expired'); - cy.get('input[name="email"]').type( - checkTokenSuccessResponse().user.primaryEmailAddress, - ); - cy.get('button[type="submit"]').click(); - cy.contains('Check your email inbox'); - cy.contains(checkTokenSuccessResponse().user.primaryEmailAddress); - cy.contains('Resend email'); - }); - - it('shows the session time out page if the token expires while on the set password page', () => { - cy.mockNext(200, checkTokenSuccessResponse(1000)); - cy.visit(`/welcome/fake_token?useIdapi=true`); - cy.contains('Session timed out'); - }); - }); - - context('Email sent page', () => { - it('resends email if button exists', () => { - cy.visit(`/welcome/resend?useIdapi=true`); - - cy.mockNext(200); - cy.get('input[name="email"]').type( - checkTokenSuccessResponse().user.primaryEmailAddress, - ); - cy.get('button[type="submit"]').click(); - - cy.mockNext(200); - cy.contains('Check your email inbox'); - cy.get('button[type="submit"]').click(); - cy.contains('Check your email inbox'); - }); - - it('fails to resend email if reCAPTCHA check is unsuccessful', () => { - cy.visit(`/welcome/resend?useIdapi=true`); - - cy.mockNext(200); - - cy.get('input[name="email"]').type( - checkTokenSuccessResponse().user.primaryEmailAddress, - ); - - cy.intercept('POST', 'https://www.google.com/recaptcha/api2/**', { - statusCode: 500, - }); - cy.get('button[type="submit"]').click(); - cy.contains('Google reCAPTCHA verification failed. Please try again.'); - cy.get('button[type="submit"]').click(); - cy.contains('Google reCAPTCHA verification failed.'); - cy.contains('If the problem persists please try the following:'); - cy.contains('userhelp@'); - }); - - it('takes user back to link expired page if "Change email address" clicked', () => { - cy.visit(`/welcome/resend?useIdapi=true`); - - cy.mockNext(200); - cy.get('input[name="email"]').type( - checkTokenSuccessResponse().user.primaryEmailAddress, - ); - cy.get('button[type="submit"]').click(); - - cy.contains('Change email address').click(); - - cy.contains('Link expired'); - }); - }); + const defaultEmail = 'someone@theguardian.com'; + const checkTokenSuccessResponse = ( + timeUntilExpiry: number | null = null, + email = defaultEmail, + ) => ({ + user: { + primaryEmailAddress: email, + }, + timeUntilExpiry, + }); + + const fakeCookieSuccessResponse = { + cookies: { + values: [ + { + key: 'GU_U', + value: 'FAKE_VALUE_0', + }, + { + key: 'SC_GU_LA', + value: 'FAKE_VALUE_1', + sessionCookie: true, + }, + { + key: 'SC_GU_U', + value: 'FAKE_VALUE_2', + }, + ], + expiresAt: new Date(Date.now() + 1800000 /* 30min */).toISOString(), + }, + }; + + const fakeGroupAddResponse = { + status: 'ok', + groupCode: 'GRS', + }; + + const setPasswordAndSignIn = () => { + cy.mockNext(200, checkTokenSuccessResponse()); + cy.intercept({ + method: 'GET', + url: 'https://api.pwnedpasswords.com/range/*', + }).as('breachCheck'); + cy.mockNext(200, fakeCookieSuccessResponse); + cy.mockAll( + 200, + authRedirectSignInRecentlyEmailValidated, + AUTH_REDIRECT_ENDPOINT, + ); + cy.mockAll(200, allConsents, CONSENTS_ENDPOINT); + // cy.mockAll(200, verifiedUserWithNoConsent, USER_ENDPOINT); + cy.mockAll( + 200, + verifiedUserWithNoConsent.user.consents, + USER_CONSENTS_ENDPOINT, + ); + cy.visit(`/welcome/fake_token?useIdapi=true`); + cy.get('input[name="password"]').type('thisisalongandunbreachedpassword'); + cy.wait('@breachCheck'); + cy.get('button[type="submit"]').click(); + }; + + beforeEach(() => { + cy.mockPurge(); + }); + + context('A11y checks', () => { + it('has no detectable a11y violations on the set password page', () => { + cy.mockNext(200, checkTokenSuccessResponse()); + cy.visit(`/welcome/fake_token?useIdapi=true`); + injectAndCheckAxe(); + }); + + it('has no detectable a11y violations on set password page with global error', () => { + cy.mockNext(200, checkTokenSuccessResponse()); + cy.visit(`/welcome/fake_token?useIdapi=true`); + cy.mockNext(500); + cy.get('input[name="password"]').type('short'); + cy.get('button[type="submit"]').click(); + injectAndCheckAxe(); + }); + + it('has no detectable a11y violations on the resend page', () => { + cy.visit(`/welcome/resend?useIdapi=true`); + injectAndCheckAxe(); + }); + + it('has no detectable a11y violations on the resend page with global error', () => { + cy.visit(`/welcome/resend?useIdapi=true`); + + cy.mockNext(500); + cy.get('input[name="email"]').type( + checkTokenSuccessResponse().user.primaryEmailAddress, + ); + cy.get('button[type="submit"]').click(); + injectAndCheckAxe(); + }); + + it('has no detectable a11y violations on the email sent page with resend box', () => { + cy.visit(`/welcome/resend?useIdapi=true`); + + cy.mockNext(200); + cy.get('input[name="email"]').type( + checkTokenSuccessResponse().user.primaryEmailAddress, + ); + cy.get('button[type="submit"]').click(); + injectAndCheckAxe(); + }); + + it('has no detectable a11y violations on the email sent page without resend box', () => { + cy.visit(`/welcome/email-sent?useIdapi=true`); + injectAndCheckAxe(); + }); + }); + + // successful token, set password page is displayed, redirect to consents flow if valid password + context('A valid token is used and set password page is displayed', () => { + it('redirects to onboarding flow if a valid password is set', () => { + setPasswordAndSignIn(); + cy.contains('Thank you for registering'); + }); + + it('shows a different message if the user has set a password and then clicked the back button', () => { + setPasswordAndSignIn(); + cy.go('back'); + cy.contains(`Password already set for ${defaultEmail}`); + cy.contains('Continue').click(); + cy.contains('Thank you for registering'); + }); + + it('redirects to onboarding flow if valid password is set and preserves returnUrl', () => { + const returnUrl = encodeURIComponent( + `https://www.theguardian.com/science/grrlscientist/2012/aug/07/3`, + ); + const query = new URLSearchParams({ returnUrl }).toString(); + + cy.mockNext(200, checkTokenSuccessResponse()); + cy.intercept({ + method: 'GET', + url: 'https://api.pwnedpasswords.com/range/*', + }).as('breachCheck'); + cy.mockNext(200, fakeCookieSuccessResponse); + cy.mockAll( + 200, + authRedirectSignInRecentlyEmailValidated, + AUTH_REDIRECT_ENDPOINT, + ); + cy.mockAll(200, allConsents, CONSENTS_ENDPOINT); + cy.mockAll(200, verifiedUserWithNoConsent, USER_ENDPOINT); + setAuthCookies(); + + cy.visit(`/welcome/fake_token?${query}&useIdapi=true`); + cy.get('input[name="password"]').type('thisisalongandunbreachedpassword'); + cy.wait('@breachCheck'); + cy.get('button[type="submit"]').click(); + cy.contains('Thank you for registering'); + cy.url().should('include', CommunicationsPage.URL); + cy.url().should('include', `returnUrl=${returnUrl}`); + }); + + it('redirects to onboarding flow and adds Jobs user to the GRS group if valid password is set and preserves returnUrl', () => { + const returnUrl = encodeURIComponent( + `https://www.theguardian.com/science/grrlscientist/2012/aug/07/3`, + ); + const clientId = 'jobs'; + const query = new URLSearchParams({ returnUrl, clientId }).toString(); + + cy.mockNext(200, checkTokenSuccessResponse()); + cy.intercept({ + method: 'GET', + url: 'https://api.pwnedpasswords.com/range/*', + }).as('breachCheck'); + cy.mockNext(200, fakeCookieSuccessResponse); + cy.mockAll(200, fakeGroupAddResponse, '/user/me/group/GRS'); + cy.mockAll( + 200, + authRedirectSignInRecentlyEmailValidated, + AUTH_REDIRECT_ENDPOINT, + ); + cy.mockAll(200, allConsents, CONSENTS_ENDPOINT); + cy.mockAll(200, verifiedUserWithNoConsent, USER_ENDPOINT); + setAuthCookies(); + + cy.visit(`/welcome/fake_token?${query}&useIdapi=true`); + cy.get('input[name="firstName"]').type('First Name'); + cy.get('input[name="secondName"]').type('Last Name'); + cy.get('input[name="password"]').type('thisisalongandunbreachedpassword'); + cy.wait('@breachCheck'); + cy.get('button[type="submit"]').click(); + cy.contains('Thank you for registering'); + cy.url().should('include', CommunicationsPage.URL); + cy.url().should('include', `returnUrl=${returnUrl}`); + cy.url().should('include', `clientId=${clientId}`); + }); + + it('shows an error if the password is invalid', () => { + cy.mockNext(200, checkTokenSuccessResponse()); + cy.mockNext(400, { + status: 'error', + errors: [ + { + message: 'Breached password', + }, + ], + }); + cy.mockNext(200, checkTokenSuccessResponse()); + cy.visit(`/welcome/fake_token?useIdapi=true`); + cy.get('input[name="password"]').type('password'); + cy.get('button[type="submit"]').click(); + cy.contains('Please use a password that is hard to guess.'); + }); + + it('shows prompt to create password', () => { + cy.mockNext(200, checkTokenSuccessResponse()); + cy.intercept({ + method: 'GET', + url: 'https://api.pwnedpasswords.com/range/*', + }).as('breachCheck'); + cy.visit(`/welcome/fake_token?useIdapi=true`); + cy.contains(`Please complete your details for ${defaultEmail}`); + }); + + it('shows prompt to create password without email if none exists', () => { + cy.mockNext(200, checkTokenSuccessResponse(null, '')); + cy.intercept({ + method: 'GET', + url: 'https://api.pwnedpasswords.com/range/*', + }).as('breachCheck'); + cy.visit(`/welcome/fake_token?useIdapi=true`); + cy.contains(`Please complete your details for your new account`); + }); + }); + + context('An expired/invalid token is used', () => { + it('shows the link expired page to type email, and on submit shows the email sent page, with a button to resend the email', () => { + cy.mockNext(500, { + status: 'error', + errors: [ + { + message: 'Invalid token', + }, + ], + }); + cy.mockNext(200); + cy.mockNext(200); + cy.visit(`/welcome/fake_token?useIdapi=true`); + cy.contains('Link expired'); + cy.get('input[name="email"]').type( + checkTokenSuccessResponse().user.primaryEmailAddress, + ); + cy.get('button[type="submit"]').click(); + cy.contains('Check your email inbox'); + cy.contains(checkTokenSuccessResponse().user.primaryEmailAddress); + cy.contains('Resend email'); + }); + + it('shows the session time out page if the token expires while on the set password page', () => { + cy.mockNext(200, checkTokenSuccessResponse(1000)); + cy.visit(`/welcome/fake_token?useIdapi=true`); + cy.contains('Session timed out'); + }); + }); + + context('Email sent page', () => { + it('resends email if button exists', () => { + cy.visit(`/welcome/resend?useIdapi=true`); + + cy.mockNext(200); + cy.get('input[name="email"]').type( + checkTokenSuccessResponse().user.primaryEmailAddress, + ); + cy.get('button[type="submit"]').click(); + + cy.mockNext(200); + cy.contains('Check your email inbox'); + cy.get('button[type="submit"]').click(); + cy.contains('Check your email inbox'); + }); + + it('fails to resend email if reCAPTCHA check is unsuccessful', () => { + cy.visit(`/welcome/resend?useIdapi=true`); + + cy.mockNext(200); + + cy.get('input[name="email"]').type( + checkTokenSuccessResponse().user.primaryEmailAddress, + ); + + cy.intercept('POST', 'https://www.google.com/recaptcha/api2/**', { + statusCode: 500, + }); + cy.get('button[type="submit"]').click(); + cy.contains('Google reCAPTCHA verification failed. Please try again.'); + cy.get('button[type="submit"]').click(); + cy.contains('Google reCAPTCHA verification failed.'); + cy.contains('If the problem persists please try the following:'); + cy.contains('userhelp@'); + }); + + it('takes user back to link expired page if "Change email address" clicked', () => { + cy.visit(`/welcome/resend?useIdapi=true`); + + cy.mockNext(200); + cy.get('input[name="email"]').type( + checkTokenSuccessResponse().user.primaryEmailAddress, + ); + cy.get('button[type="submit"]').click(); + + cy.contains('Change email address').click(); + + cy.contains('Link expired'); + }); + }); }); diff --git a/cypress/integration/shared/sign_in.shared.ts b/cypress/integration/shared/sign_in.shared.ts index e9d33410b2..73614d8c69 100644 --- a/cypress/integration/shared/sign_in.shared.ts +++ b/cypress/integration/shared/sign_in.shared.ts @@ -1,487 +1,487 @@ const returnUrl = - 'https://www.theguardian.com/world/2013/jun/09/edward-snowden-nsa-whistleblower-surveillance'; + 'https://www.theguardian.com/world/2013/jun/09/edward-snowden-nsa-whistleblower-surveillance'; export const beforeEach = () => { - // Disable redirect to /signin/success by default - cy.setCookie( - 'GU_ran_experiments', - new URLSearchParams({ - OptInPromptPostSignIn: Date.now().toString(), - }).toString(), - ); + // Disable redirect to /signin/success by default + cy.setCookie( + 'GU_ran_experiments', + new URLSearchParams({ + OptInPromptPostSignIn: Date.now().toString(), + }).toString(), + ); }; export const linksToTheGoogleTermsOfServicePage = (isIdapi = false) => { - return [ - 'links to the Google terms of service page', - () => { - const googleTermsUrl = 'https://policies.google.com/terms'; - // Intercept the external redirect page. - // We just want to check that the redirect happens, not that the page loads. - cy.intercept('GET', googleTermsUrl, (req) => { - req.reply(200); - }); - const visitUrl = isIdapi ? '/signin?useIdapi=true' : '/signin'; - cy.visit(visitUrl); - cy.contains('terms of service').click(); - cy.url().should('eq', googleTermsUrl); - }, - ] as const; + return [ + 'links to the Google terms of service page', + () => { + const googleTermsUrl = 'https://policies.google.com/terms'; + // Intercept the external redirect page. + // We just want to check that the redirect happens, not that the page loads. + cy.intercept('GET', googleTermsUrl, (req) => { + req.reply(200); + }); + const visitUrl = isIdapi ? '/signin?useIdapi=true' : '/signin'; + cy.visit(visitUrl); + cy.contains('terms of service').click(); + cy.url().should('eq', googleTermsUrl); + }, + ] as const; }; export const linksToTheGooglePrivacyPolicyPage = (isIdapi = false) => { - return [ - 'links to the Google privacy policy page', - () => { - const googlePrivacyPolicyUrl = 'https://policies.google.com/privacy'; - // Intercept the external redirect page. - // We just want to check that the redirect happens, not that the page loads. - cy.intercept('GET', googlePrivacyPolicyUrl, (req) => { - req.reply(200); - }); - const visitUrl = isIdapi ? '/signin?useIdapi=true' : '/signin'; - cy.visit(visitUrl); - cy.contains('This service is protected by reCAPTCHA and the Google') - .contains('privacy policy') - .click(); - cy.url().should('eq', googlePrivacyPolicyUrl); - }, - ] as const; + return [ + 'links to the Google privacy policy page', + () => { + const googlePrivacyPolicyUrl = 'https://policies.google.com/privacy'; + // Intercept the external redirect page. + // We just want to check that the redirect happens, not that the page loads. + cy.intercept('GET', googlePrivacyPolicyUrl, (req) => { + req.reply(200); + }); + const visitUrl = isIdapi ? '/signin?useIdapi=true' : '/signin'; + cy.visit(visitUrl); + cy.contains('This service is protected by reCAPTCHA and the Google') + .contains('privacy policy') + .click(); + cy.url().should('eq', googlePrivacyPolicyUrl); + }, + ] as const; }; export const linksToTheGuardianTermsAndConditionsPage = (isIdapi = false) => { - return [ - 'links to the Guardian terms and conditions page', - () => { - const guardianTermsOfServiceUrl = - 'https://www.theguardian.com/help/terms-of-service'; - // Intercept the external redirect page. - // We just want to check that the redirect happens, not that the page loads. - cy.intercept('GET', guardianTermsOfServiceUrl, (req) => { - req.reply(200); - }); - const visitUrl = isIdapi ? '/signin?useIdapi=true' : '/signin'; - cy.visit(visitUrl); - cy.contains('terms & conditions').click(); - cy.url().should('eq', guardianTermsOfServiceUrl); - }, - ] as const; + return [ + 'links to the Guardian terms and conditions page', + () => { + const guardianTermsOfServiceUrl = + 'https://www.theguardian.com/help/terms-of-service'; + // Intercept the external redirect page. + // We just want to check that the redirect happens, not that the page loads. + cy.intercept('GET', guardianTermsOfServiceUrl, (req) => { + req.reply(200); + }); + const visitUrl = isIdapi ? '/signin?useIdapi=true' : '/signin'; + cy.visit(visitUrl); + cy.contains('terms & conditions').click(); + cy.url().should('eq', guardianTermsOfServiceUrl); + }, + ] as const; }; export const linksToTheGuardianPrivacyPolicyPage = (isIdapi = false) => { - return [ - 'links to the Guardian privacy policy page', - () => { - const guardianPrivacyPolicyUrl = - 'https://www.theguardian.com/help/privacy-policy'; - // Intercept the external redirect page. - // We just want to check that the redirect happens, not that the page loads. - cy.intercept('GET', guardianPrivacyPolicyUrl, (req) => { - req.reply(200); - }); - const visitUrl = isIdapi ? '/signin?useIdapi=true' : '/signin'; - cy.visit(visitUrl); - cy.contains('For information about how we use your data') - .contains('privacy policy') - .click(); - cy.url().should('eq', guardianPrivacyPolicyUrl); - }, - ] as const; + return [ + 'links to the Guardian privacy policy page', + () => { + const guardianPrivacyPolicyUrl = + 'https://www.theguardian.com/help/privacy-policy'; + // Intercept the external redirect page. + // We just want to check that the redirect happens, not that the page loads. + cy.intercept('GET', guardianPrivacyPolicyUrl, (req) => { + req.reply(200); + }); + const visitUrl = isIdapi ? '/signin?useIdapi=true' : '/signin'; + cy.visit(visitUrl); + cy.contains('For information about how we use your data') + .contains('privacy policy') + .click(); + cy.url().should('eq', guardianPrivacyPolicyUrl); + }, + ] as const; }; export const linksToTheGuardianJobsTermsAndConditionsPageWhenJobsClientIdSet = ( - isIdapi = false, + isIdapi = false, ) => { - return [ - 'links to the Guardian jobs terms and conditions page when jobs clientId set', - () => { - const guardianJobsTermsOfServiceUrl = - 'https://jobs.theguardian.com/terms-and-conditions/'; - // Intercept the external redirect page. - // We just want to check that the redirect happens, not that the page loads. - cy.intercept('GET', guardianJobsTermsOfServiceUrl, (req) => { - req.reply(200); - }); - const visitUrl = isIdapi - ? '/signin?clientId=jobs&useIdapi=true' - : '/signin?clientId=jobs'; - cy.visit(visitUrl); - cy.contains('Guardian Jobs terms & conditions').click(); - cy.url().should('eq', guardianJobsTermsOfServiceUrl); - }, - ] as const; + return [ + 'links to the Guardian jobs terms and conditions page when jobs clientId set', + () => { + const guardianJobsTermsOfServiceUrl = + 'https://jobs.theguardian.com/terms-and-conditions/'; + // Intercept the external redirect page. + // We just want to check that the redirect happens, not that the page loads. + cy.intercept('GET', guardianJobsTermsOfServiceUrl, (req) => { + req.reply(200); + }); + const visitUrl = isIdapi + ? '/signin?clientId=jobs&useIdapi=true' + : '/signin?clientId=jobs'; + cy.visit(visitUrl); + cy.contains('Guardian Jobs terms & conditions').click(); + cy.url().should('eq', guardianJobsTermsOfServiceUrl); + }, + ] as const; }; export const linksToTheGuardianJobsPrivacyPolicyPageWhenJobsClientIdSet = ( - isIdapi = false, + isIdapi = false, ) => { - return [ - 'links to the Guardian jobs privacy policy page when jobs clientId set', - () => { - const guardianJobsPrivacyPolicyUrl = - 'https://jobs.theguardian.com/privacy-policy/'; - // Intercept the external redirect page. - // We just want to check that the redirect happens, not that the page loads. - cy.intercept('GET', guardianJobsPrivacyPolicyUrl, (req) => { - req.reply(200); - }); - const visitUrl = isIdapi - ? '/signin?clientId=jobs&useIdapi=true' - : '/signin?clientId=jobs'; - cy.visit(visitUrl); - cy.contains('For information about how we use your data') - .contains('Guardian Jobs privacy policy') - .click(); - cy.url().should('eq', guardianJobsPrivacyPolicyUrl); - }, - ] as const; + return [ + 'links to the Guardian jobs privacy policy page when jobs clientId set', + () => { + const guardianJobsPrivacyPolicyUrl = + 'https://jobs.theguardian.com/privacy-policy/'; + // Intercept the external redirect page. + // We just want to check that the redirect happens, not that the page loads. + cy.intercept('GET', guardianJobsPrivacyPolicyUrl, (req) => { + req.reply(200); + }); + const visitUrl = isIdapi + ? '/signin?clientId=jobs&useIdapi=true' + : '/signin?clientId=jobs'; + cy.visit(visitUrl); + cy.contains('For information about how we use your data') + .contains('Guardian Jobs privacy policy') + .click(); + cy.url().should('eq', guardianJobsPrivacyPolicyUrl); + }, + ] as const; }; export const persistsTheClientIdWhenNavigatingAway = (isIdapi = false) => { - return [ - 'persists the clientId when navigating away', - () => { - const visitUrl = isIdapi - ? '/signin?clientId=jobs&useIdapi=true' - : '/signin?clientId=jobs'; - cy.visit(visitUrl); - cy.contains('Register').click(); - cy.url().should('contain', 'clientId=jobs'); - }, - ] as const; + return [ + 'persists the clientId when navigating away', + () => { + const visitUrl = isIdapi + ? '/signin?clientId=jobs&useIdapi=true' + : '/signin?clientId=jobs'; + cy.visit(visitUrl); + cy.contains('Register').click(); + cy.url().should('contain', 'clientId=jobs'); + }, + ] as const; }; export const appliesFormValidationToEmailAndPasswordInputFields = ( - isIdapi = false, + isIdapi = false, ) => { - return [ - 'applies form validation to email and password input fields', - () => { - const visitUrl = isIdapi ? '/signin?useIdapi=true' : '/signin'; - cy.visit(visitUrl); - - cy.get('form').within(() => { - cy.get('input:invalid').should('have.length', 2); - cy.get('input[name=email]').type('not an email'); - cy.get('input:invalid').should('have.length', 2); - cy.get('input[name=email]').type('emailaddress@inavalidformat.com'); - cy.get('input:invalid').should('have.length', 1); - cy.get('input[name=password]').type('password'); - cy.get('input:invalid').should('have.length', 0); - }); - }, - ] as const; + return [ + 'applies form validation to email and password input fields', + () => { + const visitUrl = isIdapi ? '/signin?useIdapi=true' : '/signin'; + cy.visit(visitUrl); + + cy.get('form').within(() => { + cy.get('input:invalid').should('have.length', 2); + cy.get('input[name=email]').type('not an email'); + cy.get('input:invalid').should('have.length', 2); + cy.get('input[name=email]').type('emailaddress@inavalidformat.com'); + cy.get('input:invalid').should('have.length', 1); + cy.get('input[name=password]').type('password'); + cy.get('input:invalid').should('have.length', 0); + }); + }, + ] as const; }; export const showsAMessageWhenCredentialsAreInvalid = (isIdapi = false) => { - return [ - 'shows a message when credentials are invalid', - () => { - const visitUrl = isIdapi ? '/signin?useIdapi=true' : '/signin'; - cy.visit(visitUrl); - cy.get('input[name=email]').type('invalid@doesnotexist.com'); - cy.get('input[name=password]').type('password'); - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.contains("Email and password don't match"); - }, - ] as const; + return [ + 'shows a message when credentials are invalid', + () => { + const visitUrl = isIdapi ? '/signin?useIdapi=true' : '/signin'; + cy.visit(visitUrl); + cy.get('input[name=email]').type('invalid@doesnotexist.com'); + cy.get('input[name=password]').type('password'); + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.contains("Email and password don't match"); + }, + ] as const; }; export const correctlySignsInAnExistingUser = (isIdapi = false) => { - return [ - 'correctly signs in an existing user', - () => { - // Intercept the external redirect page. - // We just want to check that the redirect happens, not that the page loads. - cy.intercept('GET', 'https://m.code.dev-theguardian.com/', (req) => { - req.reply(200); - }); - cy - .createTestUser({ - isUserEmailValidated: true, - }) - ?.then(({ emailAddress, finalPassword }) => { - const visitUrl = isIdapi ? '/signin?useIdapi=true' : '/signin'; - cy.visit(visitUrl); - cy.get('input[name=email]').type(emailAddress); - cy.get('input[name=password]').type(finalPassword); - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.url().should('include', 'https://m.code.dev-theguardian.com/'); - }); - }, - ] as const; + return [ + 'correctly signs in an existing user', + () => { + // Intercept the external redirect page. + // We just want to check that the redirect happens, not that the page loads. + cy.intercept('GET', 'https://m.code.dev-theguardian.com/', (req) => { + req.reply(200); + }); + cy + .createTestUser({ + isUserEmailValidated: true, + }) + ?.then(({ emailAddress, finalPassword }) => { + const visitUrl = isIdapi ? '/signin?useIdapi=true' : '/signin'; + cy.visit(visitUrl); + cy.get('input[name=email]').type(emailAddress); + cy.get('input[name=password]').type(finalPassword); + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.url().should('include', 'https://m.code.dev-theguardian.com/'); + }); + }, + ] as const; }; export const navigatesToResetPassword = (isIdapi = false) => { - return [ - 'navigates to reset password', - () => { - const visitUrl = isIdapi ? '/signin?useIdapi=true' : '/signin'; - cy.visit(visitUrl); - cy.contains('Reset password').click(); - cy.contains('Forgot password'); - }, - ] as const; + return [ + 'navigates to reset password', + () => { + const visitUrl = isIdapi ? '/signin?useIdapi=true' : '/signin'; + cy.visit(visitUrl); + cy.contains('Reset password').click(); + cy.contains('Forgot password'); + }, + ] as const; }; export const navigatesToRegistration = (isIdapi = false) => { - return [ - 'navigates to registration', - () => { - const visitUrl = isIdapi ? '/signin?useIdapi=true' : '/signin'; - cy.visit(visitUrl); - cy.contains('Register').click(); - cy.get('[data-cy="main-form-submit-button"]').should('be.visible'); - }, - ] as const; + return [ + 'navigates to registration', + () => { + const visitUrl = isIdapi ? '/signin?useIdapi=true' : '/signin'; + cy.visit(visitUrl); + cy.contains('Register').click(); + cy.get('[data-cy="main-form-submit-button"]').should('be.visible'); + }, + ] as const; }; export const respectsTheReturnUrlQueryParam = (isIdapi = false) => { - return [ - 'respects the returnUrl query param', - () => { - // Intercept the external redirect page. - // We just want to check that the redirect happens, not that the page loads. - cy.intercept('GET', returnUrl, (req) => { - req.reply(200); - }); - cy - .createTestUser({ - isUserEmailValidated: true, - }) - ?.then(({ emailAddress, finalPassword }) => { - const visitUrl = isIdapi - ? `/signin?returnUrl=${encodeURIComponent(returnUrl)}&useIdapi=true` - : `/signin?returnUrl=${encodeURIComponent(returnUrl)}`; - cy.visit(visitUrl); - cy.get('input[name=email]').type(emailAddress); - cy.get('input[name=password]').type(finalPassword); - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.url().should('eq', returnUrl); - }); - }, - ] as const; + return [ + 'respects the returnUrl query param', + () => { + // Intercept the external redirect page. + // We just want to check that the redirect happens, not that the page loads. + cy.intercept('GET', returnUrl, (req) => { + req.reply(200); + }); + cy + .createTestUser({ + isUserEmailValidated: true, + }) + ?.then(({ emailAddress, finalPassword }) => { + const visitUrl = isIdapi + ? `/signin?returnUrl=${encodeURIComponent(returnUrl)}&useIdapi=true` + : `/signin?returnUrl=${encodeURIComponent(returnUrl)}`; + cy.visit(visitUrl); + cy.get('input[name=email]').type(emailAddress); + cy.get('input[name=password]').type(finalPassword); + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.url().should('eq', returnUrl); + }); + }, + ] as const; }; export const redirectsCorrectlyForSocialSignIn = (isIdapi = false) => { - return [ - 'redirects correctly for social sign in', - () => { - const visitUrl = isIdapi - ? `/signin?returnUrl=${encodeURIComponent(returnUrl)}&useIdapi=true` - : `/signin?returnUrl=${encodeURIComponent(returnUrl)}`; - cy.visit(visitUrl); - cy.get('[data-cy="google-sign-in-button"]').should( - 'have.attr', - 'href', - `/signin/google?returnUrl=${encodeURIComponent(returnUrl)}${ - isIdapi ? '&useIdapi=true' : '' - }`, - ); - cy.get('[data-cy="apple-sign-in-button"]').should( - 'have.attr', - 'href', - `/signin/apple?returnUrl=${encodeURIComponent(returnUrl)}${ - isIdapi ? '&useIdapi=true' : '' - }`, - ); - }, - ] as const; + return [ + 'redirects correctly for social sign in', + () => { + const visitUrl = isIdapi + ? `/signin?returnUrl=${encodeURIComponent(returnUrl)}&useIdapi=true` + : `/signin?returnUrl=${encodeURIComponent(returnUrl)}`; + cy.visit(visitUrl); + cy.get('[data-cy="google-sign-in-button"]').should( + 'have.attr', + 'href', + `/signin/google?returnUrl=${encodeURIComponent(returnUrl)}${ + isIdapi ? '&useIdapi=true' : '' + }`, + ); + cy.get('[data-cy="apple-sign-in-button"]').should( + 'have.attr', + 'href', + `/signin/apple?returnUrl=${encodeURIComponent(returnUrl)}${ + isIdapi ? '&useIdapi=true' : '' + }`, + ); + }, + ] as const; }; export const removesEncryptedEmailParameterFromQueryString = ( - isIdapi = false, + isIdapi = false, ) => { - return [ - 'removes encryptedEmail parameter from query string', - () => { - const encryptedEmailParam = 'encryptedEmail=bhvlabgflbgyil'; - const visitUrl = isIdapi - ? `/signin?${encryptedEmailParam}&useIdapi=true` - : `/signin?${encryptedEmailParam}`; - cy.visit(visitUrl); - - cy.location('search').should('not.contain', encryptedEmailParam); - }, - ] as const; + return [ + 'removes encryptedEmail parameter from query string', + () => { + const encryptedEmailParam = 'encryptedEmail=bhvlabgflbgyil'; + const visitUrl = isIdapi + ? `/signin?${encryptedEmailParam}&useIdapi=true` + : `/signin?${encryptedEmailParam}`; + cy.visit(visitUrl); + + cy.location('search').should('not.contain', encryptedEmailParam); + }, + ] as const; }; export const removesEncryptedEmailParameterAndPreservesAllOtherValidParameters = - (isIdapi = false) => { - return [ - 'removes encryptedEmail parameter and preserves all other valid parameters', - () => { - const encryptedEmailParam = 'encryptedEmail=bhvlabgflbgyil'; - const visitUrl = isIdapi - ? `/signin?returnUrl=${encodeURIComponent( - returnUrl, - )}&${encryptedEmailParam}&refViewId=12345&useIdapi=true` - : `/signin?returnUrl=${encodeURIComponent( - returnUrl, - )}&${encryptedEmailParam}&refViewId=12345`; - cy.visit(visitUrl); - - cy.location('search').should('not.contain', encryptedEmailParam); - cy.location('search').should('contain', 'refViewId=12345'); - cy.location('search').should('contain', encodeURIComponent(returnUrl)); - }, - ] as const; - }; + (isIdapi = false) => { + return [ + 'removes encryptedEmail parameter and preserves all other valid parameters', + () => { + const encryptedEmailParam = 'encryptedEmail=bhvlabgflbgyil'; + const visitUrl = isIdapi + ? `/signin?returnUrl=${encodeURIComponent( + returnUrl, + )}&${encryptedEmailParam}&refViewId=12345&useIdapi=true` + : `/signin?returnUrl=${encodeURIComponent( + returnUrl, + )}&${encryptedEmailParam}&refViewId=12345`; + cy.visit(visitUrl); + + cy.location('search').should('not.contain', encryptedEmailParam); + cy.location('search').should('contain', 'refViewId=12345'); + cy.location('search').should('contain', encodeURIComponent(returnUrl)); + }, + ] as const; + }; export const hitsAccessTokenRateLimitAndRecoversTokenAfterTimeout = ( - isIdapi = false, + isIdapi = false, ) => { - return [ - 'hits access token rate limit and recovers token after timeout', - () => { - // Intercept the external redirect page. - // We just want to check that the redirect happens, not that the page loads. - cy.intercept('GET', 'https://m.code.dev-theguardian.com/', (req) => { - req.reply(200); - }); - cy - .createTestUser({ - isUserEmailValidated: true, - }) - ?.then(({ emailAddress, finalPassword }) => { - const visitUrl = isIdapi ? '/signin?useIdapi=true' : '/signin'; - cy.visit(visitUrl); - cy.get('input[name=email]').type(emailAddress); - cy.get('input[name=password]').type(finalPassword); - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.url().should('include', 'https://m.code.dev-theguardian.com/'); - - // We visit reauthenticate here because if we visit /signin or - // /register, the logged in user guard will redirect us away before - // the ratelimiter has a chance to work - const reauthUrl = isIdapi - ? '/reauthenticate?useIdapi=true' - : '/reauthenticate'; - cy.visit(reauthUrl); - cy.contains('Sign'); - Cypress._.times(6, () => cy.reload()); - cy.contains('Rate limit exceeded'); - }); - }, - ] as const; + return [ + 'hits access token rate limit and recovers token after timeout', + () => { + // Intercept the external redirect page. + // We just want to check that the redirect happens, not that the page loads. + cy.intercept('GET', 'https://m.code.dev-theguardian.com/', (req) => { + req.reply(200); + }); + cy + .createTestUser({ + isUserEmailValidated: true, + }) + ?.then(({ emailAddress, finalPassword }) => { + const visitUrl = isIdapi ? '/signin?useIdapi=true' : '/signin'; + cy.visit(visitUrl); + cy.get('input[name=email]').type(emailAddress); + cy.get('input[name=password]').type(finalPassword); + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.url().should('include', 'https://m.code.dev-theguardian.com/'); + + // We visit reauthenticate here because if we visit /signin or + // /register, the logged in user guard will redirect us away before + // the ratelimiter has a chance to work + const reauthUrl = isIdapi + ? '/reauthenticate?useIdapi=true' + : '/reauthenticate'; + cy.visit(reauthUrl); + cy.contains('Sign'); + Cypress._.times(6, () => cy.reload()); + cy.contains('Rate limit exceeded'); + }); + }, + ] as const; }; export const showsAnErrorMessageAndInformationParagraphWhenAccountLinkingRequiredErrorParameterIsPresent = - (isIdapi = false) => { - return [ - 'shows an error message and information paragraph when accountLinkingRequired error parameter is present', - () => { - const visitUrl = isIdapi - ? '/signin?error=accountLinkingRequired&useIdapi=true' - : '/signin?error=accountLinkingRequired'; - cy.visit(visitUrl); - cy.contains( - 'We cannot sign you in with your social account credentials. Please enter your account password below to sign in.', - ); - cy.contains('Account already exists'); - }, - ] as const; - }; + (isIdapi = false) => { + return [ + 'shows an error message and information paragraph when accountLinkingRequired error parameter is present', + () => { + const visitUrl = isIdapi + ? '/signin?error=accountLinkingRequired&useIdapi=true' + : '/signin?error=accountLinkingRequired'; + cy.visit(visitUrl); + cy.contains( + 'We cannot sign you in with your social account credentials. Please enter your account password below to sign in.', + ); + cy.contains('Account already exists'); + }, + ] as const; + }; export const doesNotDisplaySocialButtonsWhenAccountLinkingRequiredErrorParameterIsPresent = - (isIdapi = false) => { - return [ - 'does not display social buttons when accountLinkingRequired error parameter is present', - () => { - const visitUrl = isIdapi - ? '/signin?error=accountLinkingRequired&useIdapi=true' - : '/signin?error=accountLinkingRequired'; - cy.visit(visitUrl); - cy.get('[data-cy="google-sign-in-button"]').should('not.exist'); - cy.get('[data-cy="apple-sign-in-button"]').should('not.exist'); - }, - ] as const; - }; + (isIdapi = false) => { + return [ + 'does not display social buttons when accountLinkingRequired error parameter is present', + () => { + const visitUrl = isIdapi + ? '/signin?error=accountLinkingRequired&useIdapi=true' + : '/signin?error=accountLinkingRequired'; + cy.visit(visitUrl); + cy.get('[data-cy="google-sign-in-button"]').should('not.exist'); + cy.get('[data-cy="apple-sign-in-button"]').should('not.exist'); + }, + ] as const; + }; export const showsRecaptchaErrorsWhenTheUserTriesToSignInOfflineAndAllowsSignInWhenBackOnline = - (isIdapi = false) => { - return [ - 'shows reCAPTCHA errors when the user tries to sign in offline and allows sign in when back online', - () => { - // Intercept the external redirect page. - // We just want to check that the redirect happens, not that the page loads. - cy.intercept('GET', 'https://m.code.dev-theguardian.com/', (req) => { - req.reply(200); - }); - cy - .createTestUser({ - isUserEmailValidated: true, - }) - ?.then(({ emailAddress, finalPassword }) => { - const visitUrl = isIdapi ? '/signin?useIdapi=true' : '/signin'; - cy.visit(visitUrl); - - // Simulate going offline by failing to reCAPTCHA POST request. - cy.intercept({ - method: 'POST', - url: 'https://www.google.com/recaptcha/api2/**', - times: 1, - }); - - cy.get('input[name=email]').type(emailAddress); - cy.get('input[name=password]').type(finalPassword); - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.contains( - 'Google reCAPTCHA verification failed. Please try again.', - ); - - // On second click, an expanded error is shown. - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains('Google reCAPTCHA verification failed.'); - cy.contains('Report this error').should( - 'have.attr', - 'href', - 'https://manage.theguardian.com/help-centre/contact-us', - ); - cy.contains('If the problem persists please try the following:'); - - cy.get('[data-cy="main-form-submit-button"]').click(); - - cy.contains( - 'Google reCAPTCHA verification failed. Please try again.', - ).should('not.exist'); - - cy.url().should('include', 'https://m.code.dev-theguardian.com/'); - }); - }, - ] as const; - }; + (isIdapi = false) => { + return [ + 'shows reCAPTCHA errors when the user tries to sign in offline and allows sign in when back online', + () => { + // Intercept the external redirect page. + // We just want to check that the redirect happens, not that the page loads. + cy.intercept('GET', 'https://m.code.dev-theguardian.com/', (req) => { + req.reply(200); + }); + cy + .createTestUser({ + isUserEmailValidated: true, + }) + ?.then(({ emailAddress, finalPassword }) => { + const visitUrl = isIdapi ? '/signin?useIdapi=true' : '/signin'; + cy.visit(visitUrl); + + // Simulate going offline by failing to reCAPTCHA POST request. + cy.intercept({ + method: 'POST', + url: 'https://www.google.com/recaptcha/api2/**', + times: 1, + }); + + cy.get('input[name=email]').type(emailAddress); + cy.get('input[name=password]').type(finalPassword); + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.contains( + 'Google reCAPTCHA verification failed. Please try again.', + ); + + // On second click, an expanded error is shown. + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains('Google reCAPTCHA verification failed.'); + cy.contains('Report this error').should( + 'have.attr', + 'href', + 'https://manage.theguardian.com/help-centre/contact-us', + ); + cy.contains('If the problem persists please try the following:'); + + cy.get('[data-cy="main-form-submit-button"]').click(); + + cy.contains( + 'Google reCAPTCHA verification failed. Please try again.', + ).should('not.exist'); + + cy.url().should('include', 'https://m.code.dev-theguardian.com/'); + }); + }, + ] as const; + }; export const redirectsToOptInPrompt = (isIdapi = false) => { - return [ - 'redirects user to prompt if they are not a supporter', - () => { - // Intercept the prompt page - // We just want to check that the redirect happens, not that the page loads. - cy.intercept('GET', '/signin/success*', (req) => { - req.reply(200); - }); - // Enable the opt in prompt "experiment" - cy.clearCookie('GU_ran_experiments'); - cy - .createTestUser({ - isUserEmailValidated: true, - }) - ?.then(({ emailAddress, finalPassword }) => { - const visitUrl = isIdapi ? '/signin?useIdapi=true' : '/signin'; - cy.visit(visitUrl); - cy.get('input[name=email]').type(emailAddress); - cy.get('input[name=password]').type(finalPassword); - cy.get('[data-cy="main-form-submit-button"]').click(); - cy.url().should('include', `/signin/success`); - cy.url().should( - 'include', - `returnUrl=${encodeURIComponent( - 'https://m.code.dev-theguardian.com/', - )}`, - ); - }); - }, - ] as const; + return [ + 'redirects user to prompt if they are not a supporter', + () => { + // Intercept the prompt page + // We just want to check that the redirect happens, not that the page loads. + cy.intercept('GET', '/signin/success*', (req) => { + req.reply(200); + }); + // Enable the opt in prompt "experiment" + cy.clearCookie('GU_ran_experiments'); + cy + .createTestUser({ + isUserEmailValidated: true, + }) + ?.then(({ emailAddress, finalPassword }) => { + const visitUrl = isIdapi ? '/signin?useIdapi=true' : '/signin'; + cy.visit(visitUrl); + cy.get('input[name=email]').type(emailAddress); + cy.get('input[name=password]').type(finalPassword); + cy.get('[data-cy="main-form-submit-button"]').click(); + cy.url().should('include', `/signin/success`); + cy.url().should( + 'include', + `returnUrl=${encodeURIComponent( + 'https://m.code.dev-theguardian.com/', + )}`, + ); + }); + }, + ] as const; }; diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index b25a46461c..b3cd1c6d74 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -12,30 +12,30 @@ import { lastPayloadIs } from './commands/lastPayloadIs'; import { lastPayloadsAre } from './commands/lastPayloadsAre'; import { checkForEmailAndGetDetails } from './commands/getEmailDetails'; import { - disableCMP, - enableCMP, - acceptCMP, - declineCMP, + disableCMP, + enableCMP, + acceptCMP, + declineCMP, } from './commands/manageCmp'; import { - getTestOktaUser, - activateTestOktaUser, - createTestUser, - resetOktaUserPassword, - expireOktaUserPassword, - suspendOktaUser, - addOktaUserToGroup, - findEmailValidatedOktaGroupId, - getOktaUserGroups, - getTestUserDetails, - addToGRS, - updateTestUser, - updateOktaTestUserProfile, - getCurrentOktaSession, - closeCurrentOktaSession, - subscribeToNewsletter, - subscribeToMarketingConsent, - sendConsentEmail, + getTestOktaUser, + activateTestOktaUser, + createTestUser, + resetOktaUserPassword, + expireOktaUserPassword, + suspendOktaUser, + addOktaUserToGroup, + findEmailValidatedOktaGroupId, + getOktaUserGroups, + getTestUserDetails, + addToGRS, + updateTestUser, + updateOktaTestUserProfile, + getCurrentOktaSession, + closeCurrentOktaSession, + subscribeToNewsletter, + subscribeToMarketingConsent, + sendConsentEmail, } from './commands/testUser'; Cypress.Commands.add('mockNext', mockNext); @@ -61,8 +61,8 @@ Cypress.Commands.add('expireOktaUserPassword', expireOktaUserPassword); Cypress.Commands.add('suspendOktaUser', suspendOktaUser); Cypress.Commands.add('addOktaUserToGroup', addOktaUserToGroup); Cypress.Commands.add( - 'findEmailValidatedOktaGroupId', - findEmailValidatedOktaGroupId, + 'findEmailValidatedOktaGroupId', + findEmailValidatedOktaGroupId, ); Cypress.Commands.add('getOktaUserGroups', getOktaUserGroups); Cypress.Commands.add('getTestUserDetails', getTestUserDetails); @@ -73,7 +73,7 @@ Cypress.Commands.add('getCurrentOktaSession', getCurrentOktaSession); Cypress.Commands.add('closeCurrentOktaSession', closeCurrentOktaSession); Cypress.Commands.add('subscribeToNewsletter', subscribeToNewsletter); Cypress.Commands.add( - 'subscribeToMarketingConsent', - subscribeToMarketingConsent, + 'subscribeToMarketingConsent', + subscribeToMarketingConsent, ); Cypress.Commands.add('sendConsentEmail', sendConsentEmail); diff --git a/cypress/support/commands/getEmailDetails.ts b/cypress/support/commands/getEmailDetails.ts index d6eb1ff97c..0ff5de19ee 100644 --- a/cypress/support/commands/getEmailDetails.ts +++ b/cypress/support/commands/getEmailDetails.ts @@ -1,11 +1,11 @@ import { Link, Message } from 'cypress-mailosaur'; declare global { - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace Cypress { - interface Chainable { - checkForEmailAndGetDetails: typeof checkForEmailAndGetDetails; - } - } + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Cypress { + interface Chainable { + checkForEmailAndGetDetails: typeof checkForEmailAndGetDetails; + } + } } /** @@ -18,34 +18,34 @@ declare global { * @returns The email details. */ export const checkForEmailAndGetDetails = ( - sentTo: string, - receivedAfter?: Date, - tokenMatcher?: RegExp, - deleteAfter = true, + sentTo: string, + receivedAfter?: Date, + tokenMatcher?: RegExp, + deleteAfter = true, ) => { - return cy - .mailosaurGetMessage( - Cypress.env('MAILOSAUR_SERVER_ID'), - { sentTo }, - { - receivedAfter, - }, - ) - .then((message: Message) => { - return getEmailDetails(message, tokenMatcher).then((details) => { - if (deleteAfter) { - cy.mailosaurDeleteMessage(details.id); - } - return cy.wrap(details); - }); - }); + return cy + .mailosaurGetMessage( + Cypress.env('MAILOSAUR_SERVER_ID'), + { sentTo }, + { + receivedAfter, + }, + ) + .then((message: Message) => { + return getEmailDetails(message, tokenMatcher).then((details) => { + if (deleteAfter) { + cy.mailosaurDeleteMessage(details.id); + } + return cy.wrap(details); + }); + }); }; type EmailDetails = { - id: string; - body: string; - token?: string; - links: Link[]; + id: string; + body: string; + token?: string; + links: Link[]; }; /** @@ -56,22 +56,22 @@ type EmailDetails = { * @returns The email details. */ const getEmailDetails = (email: Message, tokenMatcher?: RegExp) => { - const { id, html } = email; - const { body, links } = html || {}; - if (id === undefined || body === undefined || links === undefined) { - throw new Error('Email details not found'); - } - // eslint-disable-next-line functional/no-let - let token = undefined; - if (tokenMatcher) { - const match = body.match(tokenMatcher); - if (match === null) { - throw new Error( - 'Unable to find token in the email body with the given regex', - ); - } - token = match[1]; - } + const { id, html } = email; + const { body, links } = html || {}; + if (id === undefined || body === undefined || links === undefined) { + throw new Error('Email details not found'); + } + // eslint-disable-next-line functional/no-let + let token = undefined; + if (tokenMatcher) { + const match = body.match(tokenMatcher); + if (match === null) { + throw new Error( + 'Unable to find token in the email body with the given regex', + ); + } + token = match[1]; + } - return cy.wrap({ id, body, token, links }); + return cy.wrap({ id, body, token, links }); }; diff --git a/cypress/support/commands/lastPayloadIs.ts b/cypress/support/commands/lastPayloadIs.ts index 1ad6590229..4a6ed87ffb 100644 --- a/cypress/support/commands/lastPayloadIs.ts +++ b/cypress/support/commands/lastPayloadIs.ts @@ -3,12 +3,12 @@ */ declare global { - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace Cypress { - interface Chainable { - lastPayloadIs: typeof lastPayloadIs; - } - } + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Cypress { + interface Chainable { + lastPayloadIs: typeof lastPayloadIs; + } + } } /** @@ -17,9 +17,9 @@ declare global { * @returns A chainable cypress command. */ export const lastPayloadIs = (expectedPayload: Array) => { - return cy - .request(Cypress.env('mockingEndpoint') + '/payload') - .then((response) => { - expect(response.body).to.deep.equal(expectedPayload); - }); + return cy + .request(Cypress.env('mockingEndpoint') + '/payload') + .then((response) => { + expect(response.body).to.deep.equal(expectedPayload); + }); }; diff --git a/cypress/support/commands/lastPayloadsAre.ts b/cypress/support/commands/lastPayloadsAre.ts index a4288885ce..756c7ad496 100644 --- a/cypress/support/commands/lastPayloadsAre.ts +++ b/cypress/support/commands/lastPayloadsAre.ts @@ -3,12 +3,12 @@ */ declare global { - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace Cypress { - interface Chainable { - lastPayloadsAre: typeof lastPayloadsAre; - } - } + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Cypress { + interface Chainable { + lastPayloadsAre: typeof lastPayloadsAre; + } + } } /** @@ -17,12 +17,12 @@ declare global { * @returns A chainable cypress command. */ export const lastPayloadsAre = (expectedPayloads: Array) => { - return cy - .request(Cypress.env('mockingEndpoint') + '/payloads') - .then((response) => { - const slice = response.body.slice(-expectedPayloads.length); - expectedPayloads.forEach((expectedPayload) => { - expect(slice).to.deep.include(expectedPayload); - }); - }); + return cy + .request(Cypress.env('mockingEndpoint') + '/payloads') + .then((response) => { + const slice = response.body.slice(-expectedPayloads.length); + expectedPayloads.forEach((expectedPayload) => { + expect(slice).to.deep.include(expectedPayload); + }); + }); }; diff --git a/cypress/support/commands/manageCmp.ts b/cypress/support/commands/manageCmp.ts index 9dc501ceef..daabc4390f 100644 --- a/cypress/support/commands/manageCmp.ts +++ b/cypress/support/commands/manageCmp.ts @@ -1,49 +1,49 @@ declare global { - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace Cypress { - interface Chainable { - disableCMP: typeof disableCMP; - enableCMP: typeof enableCMP; - acceptCMP: typeof acceptCMP; - declineCMP: typeof declineCMP; - } - } + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Cypress { + interface Chainable { + disableCMP: typeof disableCMP; + enableCMP: typeof enableCMP; + acceptCMP: typeof acceptCMP; + declineCMP: typeof declineCMP; + } + } } export const disableCMP = () => { - return cy.setCookie('gu-cmp-disabled', 'true'); + return cy.setCookie('gu-cmp-disabled', 'true'); }; export const enableCMP = () => { - return cy.setCookie('gu-cmp-disabled', 'false'); + return cy.setCookie('gu-cmp-disabled', 'false'); }; // CMP helpers work for GB/tcfv2 region only // const cmpIframe = () => { - return cy - .get('iframe[id^="sp_message_iframe"]') - .its('0.contentDocument.body') - .should('not.be.empty') - .then(cy.wrap); + return cy + .get('iframe[id^="sp_message_iframe"]') + .its('0.contentDocument.body') + .should('not.be.empty') + .then(cy.wrap); }; const privacySettingsIframe = () => { - return cy - .get('[src*="https://cdn.privacy-mgmt.com/privacy-manager"]') - .its('0.contentDocument.body') - .should('not.be.empty') - .then(cy.wrap); + return cy + .get('[src*="https://cdn.privacy-mgmt.com/privacy-manager"]') + .its('0.contentDocument.body') + .should('not.be.empty') + .then(cy.wrap); }; export const acceptCMP = () => { - cmpIframe().find("[title='Yes, I’m happy']").click().wait(2000); + cmpIframe().find("[title='Yes, I’m happy']").click().wait(2000); }; export const declineCMP = () => { - cmpIframe().find("[title='Manage or reject cookies']").click(); - privacySettingsIframe().contains('Privacy settings'); - privacySettingsIframe() - .find("[title='Reject all']", { timeout: 2000 }) - .click() - .wait(2000); + cmpIframe().find("[title='Manage or reject cookies']").click(); + privacySettingsIframe().contains('Privacy settings'); + privacySettingsIframe() + .find("[title='Reject all']", { timeout: 2000 }) + .click() + .wait(2000); }; diff --git a/cypress/support/commands/mockAll.ts b/cypress/support/commands/mockAll.ts index 5d3fd05f91..5c1cae84d8 100644 --- a/cypress/support/commands/mockAll.ts +++ b/cypress/support/commands/mockAll.ts @@ -1,10 +1,10 @@ declare global { - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace Cypress { - interface Chainable { - mockAll: typeof mockAll; - } - } + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Cypress { + interface Chainable { + mockAll: typeof mockAll; + } + } } /** @@ -14,22 +14,22 @@ declare global { * @param path URL to mock */ export const mockAll = (status: number, body = {}, path = '') => { - const getMockOptions = (status: number, body: object) => { - const payload = { - body, - path, - status, - }; - return { - headers: { - 'Content-Type': 'application/json', - 'x-status': status, - }, - method: 'POST', - body: JSON.stringify(payload), - url: Cypress.env('mockingEndpoint') + '/permanent', - }; - }; + const getMockOptions = (status: number, body: object) => { + const payload = { + body, + path, + status, + }; + return { + headers: { + 'Content-Type': 'application/json', + 'x-status': status, + }, + method: 'POST', + body: JSON.stringify(payload), + url: Cypress.env('mockingEndpoint') + '/permanent', + }; + }; - return cy.request(getMockOptions(status, body)); + return cy.request(getMockOptions(status, body)); }; diff --git a/cypress/support/commands/mockNext.ts b/cypress/support/commands/mockNext.ts index 268e3bc756..5b2ef8a511 100644 --- a/cypress/support/commands/mockNext.ts +++ b/cypress/support/commands/mockNext.ts @@ -1,10 +1,10 @@ declare global { - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace Cypress { - interface Chainable { - mockNext: typeof mockNext; - } - } + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Cypress { + interface Chainable { + mockNext: typeof mockNext; + } + } } /** @@ -13,15 +13,15 @@ declare global { * @param body Mocked Request Body */ export const mockNext = (status: number, body = {}) => { - const getMockOptions = (status: number, body: object) => ({ - headers: { - 'Content-Type': 'application/json', - 'x-status': status, - }, - method: 'POST', - body: JSON.stringify(body), - url: Cypress.env('mockingEndpoint'), - }); + const getMockOptions = (status: number, body: object) => ({ + headers: { + 'Content-Type': 'application/json', + 'x-status': status, + }, + method: 'POST', + body: JSON.stringify(body), + url: Cypress.env('mockingEndpoint'), + }); - cy.request(getMockOptions(status, body)); + cy.request(getMockOptions(status, body)); }; diff --git a/cypress/support/commands/mockPattern.ts b/cypress/support/commands/mockPattern.ts index 0d48bcbab7..ade8c44cb0 100644 --- a/cypress/support/commands/mockPattern.ts +++ b/cypress/support/commands/mockPattern.ts @@ -1,10 +1,10 @@ declare global { - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace Cypress { - interface Chainable { - mockPattern: typeof mockPattern; - } - } + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Cypress { + interface Chainable { + mockPattern: typeof mockPattern; + } + } } /** @@ -14,22 +14,22 @@ declare global { * @param pattern Pattern to match against */ export const mockPattern = (status: number, body = {}, pattern = '') => { - const getMockOptions = (status: number, body: object) => { - const payload = { - body, - pattern, - status, - }; - return { - headers: { - 'Content-Type': 'application/json', - 'x-status': status, - }, - method: 'POST', - body: JSON.stringify(payload), - url: Cypress.env('mockingEndpoint') + '/permanent-pattern', - }; - }; + const getMockOptions = (status: number, body: object) => { + const payload = { + body, + pattern, + status, + }; + return { + headers: { + 'Content-Type': 'application/json', + 'x-status': status, + }, + method: 'POST', + body: JSON.stringify(payload), + url: Cypress.env('mockingEndpoint') + '/permanent-pattern', + }; + }; - return cy.request(getMockOptions(status, body)); + return cy.request(getMockOptions(status, body)); }; diff --git a/cypress/support/commands/mockPurge.ts b/cypress/support/commands/mockPurge.ts index a2d8d39b7a..85de3555dc 100644 --- a/cypress/support/commands/mockPurge.ts +++ b/cypress/support/commands/mockPurge.ts @@ -1,10 +1,10 @@ declare global { - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace Cypress { - interface Chainable { - mockPurge: typeof mockPurge; - } - } + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Cypress { + interface Chainable { + mockPurge: typeof mockPurge; + } + } } /** @@ -12,5 +12,5 @@ declare global { * @return {Chainable} */ export const mockPurge = () => { - return cy.request(Cypress.env('mockingEndpoint') + '/purge'); + return cy.request(Cypress.env('mockingEndpoint') + '/purge'); }; diff --git a/cypress/support/commands/network.ts b/cypress/support/commands/network.ts index 8fb350db4e..502931f6c0 100644 --- a/cypress/support/commands/network.ts +++ b/cypress/support/commands/network.ts @@ -1,10 +1,10 @@ declare global { - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace Cypress { - interface Chainable { - network: typeof network; - } - } + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Cypress { + interface Chainable { + network: typeof network; + } + } } // Adds a command that allows you to emulate going offline. @@ -16,18 +16,18 @@ declare global { * @param options Option to toggle the offline state. */ export const network = (options: { offline: boolean }) => { - Cypress.automation('remote:debugger:protocol', { - command: 'Network.enable', - }); + Cypress.automation('remote:debugger:protocol', { + command: 'Network.enable', + }); - Cypress.automation('remote:debugger:protocol', { - command: 'Network.emulateNetworkConditions', - params: { - offline: options.offline, - latency: 0, - downloadThroughput: -1, - uploadThroughput: -1, - connectionType: 'none', - }, - }); + Cypress.automation('remote:debugger:protocol', { + command: 'Network.emulateNetworkConditions', + params: { + offline: options.offline, + latency: 0, + downloadThroughput: -1, + uploadThroughput: -1, + connectionType: 'none', + }, + }); }; diff --git a/cypress/support/commands/setAdFreeCookie.ts b/cypress/support/commands/setAdFreeCookie.ts index e1dd300d81..eb12b8b53e 100644 --- a/cypress/support/commands/setAdFreeCookie.ts +++ b/cypress/support/commands/setAdFreeCookie.ts @@ -1,10 +1,10 @@ declare global { - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace Cypress { - interface Chainable { - setAdFreeCookie: typeof setAdFreeCookie; - } - } + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Cypress { + interface Chainable { + setAdFreeCookie: typeof setAdFreeCookie; + } + } } /** @@ -14,8 +14,8 @@ declare global { * @param expiryInDays when the cookie should expire (can also be in the past eg. -1) */ export const setAdFreeCookie = (expiryInDays = 1) => { - const tz = Date.now() + 1000 * 60 * 60 * 24 * expiryInDays; - return cy.setCookie('GU_AF1', tz.toString(), { - log: true, - }); + const tz = Date.now() + 1000 * 60 * 60 * 24 * expiryInDays; + return cy.setCookie('GU_AF1', tz.toString(), { + log: true, + }); }; diff --git a/cypress/support/commands/setEncryptedStateCookie.ts b/cypress/support/commands/setEncryptedStateCookie.ts index 4508b95d39..9c00c5f73a 100644 --- a/cypress/support/commands/setEncryptedStateCookie.ts +++ b/cypress/support/commands/setEncryptedStateCookie.ts @@ -2,12 +2,12 @@ import { encrypt } from '../../../src/server/lib/crypto'; import { EncryptedState } from '../../../src/shared/model/EncryptedState'; declare global { - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace Cypress { - interface Chainable { - setEncryptedStateCookie: typeof setEncryptedStateCookie; - } - } + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Cypress { + interface Chainable { + setEncryptedStateCookie: typeof setEncryptedStateCookie; + } + } } /** @@ -15,11 +15,11 @@ declare global { * @param str Value to set the mvtId cookie to */ export const setEncryptedStateCookie = (state: EncryptedState) => { - const encrypted = encrypt( - JSON.stringify(state), - Cypress.env('ENCRYPTION_SECRET_KEY'), - ); - return cy.setCookie('GU_GATEWAY_STATE', encrypted, { - log: true, - }); + const encrypted = encrypt( + JSON.stringify(state), + Cypress.env('ENCRYPTION_SECRET_KEY'), + ); + return cy.setCookie('GU_GATEWAY_STATE', encrypted, { + log: true, + }); }; diff --git a/cypress/support/commands/setMvtId.ts b/cypress/support/commands/setMvtId.ts index 159cd9d2f9..c3e8711ff4 100644 --- a/cypress/support/commands/setMvtId.ts +++ b/cypress/support/commands/setMvtId.ts @@ -1,10 +1,10 @@ declare global { - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace Cypress { - interface Chainable { - setMvtId: typeof setMvtId; - } - } + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Cypress { + interface Chainable { + setMvtId: typeof setMvtId; + } + } } /** @@ -12,7 +12,7 @@ declare global { * @param str Value to set the mvtId cookie to */ export const setMvtId = (str: string) => { - return cy.setCookie('GU_mvt_id', str, { - log: true, - }); + return cy.setCookie('GU_mvt_id', str, { + log: true, + }); }; diff --git a/cypress/support/commands/testUser.ts b/cypress/support/commands/testUser.ts index 232511402b..11818224f4 100644 --- a/cypress/support/commands/testUser.ts +++ b/cypress/support/commands/testUser.ts @@ -1,539 +1,539 @@ import { v4 as uuidv4 } from 'uuid'; import { - TokenResponse, - UserResponse, - SessionResponse, + TokenResponse, + UserResponse, + SessionResponse, } from '@/server/models/okta/User'; import { Group } from '@/server/models/okta/Group'; declare global { - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace Cypress { - interface Chainable { - createTestUser: typeof createTestUser; - getTestOktaUser: typeof getTestOktaUser; - activateTestOktaUser: typeof activateTestOktaUser; - resetOktaUserPassword: typeof resetOktaUserPassword; - expireOktaUserPassword: typeof expireOktaUserPassword; - suspendOktaUser: typeof suspendOktaUser; - addOktaUserToGroup: typeof addOktaUserToGroup; - findEmailValidatedOktaGroupId: typeof findEmailValidatedOktaGroupId; - getOktaUserGroups: typeof getOktaUserGroups; - getTestUserDetails: typeof getTestUserDetails; - addToGRS: typeof addToGRS; - updateTestUser: typeof updateTestUser; - updateOktaTestUserProfile: typeof updateOktaTestUserProfile; - getCurrentOktaSession: typeof getCurrentOktaSession; - closeCurrentOktaSession: typeof closeCurrentOktaSession; - subscribeToNewsletter: typeof subscribeToNewsletter; - subscribeToMarketingConsent: typeof subscribeToMarketingConsent; - sendConsentEmail: typeof sendConsentEmail; - } - } + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Cypress { + interface Chainable { + createTestUser: typeof createTestUser; + getTestOktaUser: typeof getTestOktaUser; + activateTestOktaUser: typeof activateTestOktaUser; + resetOktaUserPassword: typeof resetOktaUserPassword; + expireOktaUserPassword: typeof expireOktaUserPassword; + suspendOktaUser: typeof suspendOktaUser; + addOktaUserToGroup: typeof addOktaUserToGroup; + findEmailValidatedOktaGroupId: typeof findEmailValidatedOktaGroupId; + getOktaUserGroups: typeof getOktaUserGroups; + getTestUserDetails: typeof getTestUserDetails; + addToGRS: typeof addToGRS; + updateTestUser: typeof updateTestUser; + updateOktaTestUserProfile: typeof updateOktaTestUserProfile; + getCurrentOktaSession: typeof getCurrentOktaSession; + closeCurrentOktaSession: typeof closeCurrentOktaSession; + subscribeToNewsletter: typeof subscribeToNewsletter; + subscribeToMarketingConsent: typeof subscribeToMarketingConsent; + sendConsentEmail: typeof sendConsentEmail; + } + } } type Networks = 'apple' | 'google'; type SocialLink = { - socialId: number; - network: Networks; + socialId: number; + network: Networks; }; type IDAPITestUserOptions = { - primaryEmailAddress?: `${string}@${string}.mailosaur.net`; - isUserEmailValidated?: boolean; - socialLinks?: SocialLink[]; - password?: string; - deleteAfterMinute?: boolean; - isGuestUser?: boolean; + primaryEmailAddress?: `${string}@${string}.mailosaur.net`; + isUserEmailValidated?: boolean; + socialLinks?: SocialLink[]; + password?: string; + deleteAfterMinute?: boolean; + isGuestUser?: boolean; }; /* More fields exist in the user profile, but we only care about the ones we define in the interfaces below. */ interface IDAPIUserProfile { - id: string; - privateFields: { - firstName?: string; - secondName?: string; - }; - userGroups: { - path: string; - packageCode: string; - joinedDate: string; - }[]; + id: string; + privateFields: { + firstName?: string; + secondName?: string; + }; + userGroups: { + path: string; + packageCode: string; + joinedDate: string; + }[]; } interface OktaUserProfile { - isJobsUser?: boolean; - firstName?: string; - lastName?: string; + isJobsUser?: boolean; + firstName?: string; + lastName?: string; } type IDAPITestUserResponse = [ - { - key: 'GU_U'; - value: string; - }, - { - key: 'SC_GU_LA'; - sessionCookie: boolean; - value: string; - }, - { - key: 'SC_GU_U'; - value: string; - }, + { + key: 'GU_U'; + value: string; + }, + { + key: 'SC_GU_LA'; + sessionCookie: boolean; + value: string; + }, + { + key: 'SC_GU_U'; + value: string; + }, ]; export const randomMailosaurEmail = () => { - return uuidv4() + '@' + Cypress.env('MAILOSAUR_SERVER_ID') + '.mailosaur.net'; + return uuidv4() + '@' + Cypress.env('MAILOSAUR_SERVER_ID') + '.mailosaur.net'; }; export const randomPassword = () => uuidv4(); export const getTestUserDetails = () => - cy.getCookie('SC_GU_U').then((cookie) => - cy - .request({ - url: Cypress.env('IDAPI_BASE_URL') + '/user/me', - method: 'GET', - headers: { - 'Content-Type': 'application/json', - 'X-GU-ID-Client-Access-Token': `Bearer ${Cypress.env( - 'IDAPI_CLIENT_ACCESS_TOKEN', - )}`, - 'X-GU-ID-FOWARDED-SC-GU-U': cookie?.value, - }, - retryOnStatusCodeFailure: true, - }) - .then((res) => - cy.wrap({ - status: res.body.status, - user: res.body.user as IDAPIUserProfile, - }), - ), - ); + cy.getCookie('SC_GU_U').then((cookie) => + cy + .request({ + url: Cypress.env('IDAPI_BASE_URL') + '/user/me', + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'X-GU-ID-Client-Access-Token': `Bearer ${Cypress.env( + 'IDAPI_CLIENT_ACCESS_TOKEN', + )}`, + 'X-GU-ID-FOWARDED-SC-GU-U': cookie?.value, + }, + retryOnStatusCodeFailure: true, + }) + .then((res) => + cy.wrap({ + status: res.body.status, + user: res.body.user as IDAPIUserProfile, + }), + ), + ); export const updateTestUser = (body: object) => - cy.getCookie('SC_GU_U').then((cookie) => - cy - .request({ - url: Cypress.env('IDAPI_BASE_URL') + '/user/me', - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-GU-ID-Client-Access-Token': `Bearer ${Cypress.env( - 'IDAPI_CLIENT_ACCESS_TOKEN', - )}`, - 'X-GU-ID-FOWARDED-SC-GU-U': cookie?.value, - }, - body: JSON.stringify(body) || undefined, - retryOnStatusCodeFailure: true, - }) - .then((res) => { - cy.wrap({ - status: res.body.status, - }); - }), - ); + cy.getCookie('SC_GU_U').then((cookie) => + cy + .request({ + url: Cypress.env('IDAPI_BASE_URL') + '/user/me', + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-GU-ID-Client-Access-Token': `Bearer ${Cypress.env( + 'IDAPI_CLIENT_ACCESS_TOKEN', + )}`, + 'X-GU-ID-FOWARDED-SC-GU-U': cookie?.value, + }, + body: JSON.stringify(body) || undefined, + retryOnStatusCodeFailure: true, + }) + .then((res) => { + cy.wrap({ + status: res.body.status, + }); + }), + ); export const addToGRS = () => - cy.getCookie('SC_GU_U').then((cookie) => - cy - .request({ - url: Cypress.env('IDAPI_BASE_URL') + '/user/me/group/GRS', - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-GU-ID-Client-Access-Token': `Bearer ${Cypress.env( - 'IDAPI_CLIENT_ACCESS_TOKEN', - )}`, - 'X-GU-ID-FOWARDED-SC-GU-U': cookie?.value, - }, - retryOnStatusCodeFailure: true, - }) - .then((res) => { - cy.wrap({ - status: res.body.status, - }); - }), - ); + cy.getCookie('SC_GU_U').then((cookie) => + cy + .request({ + url: Cypress.env('IDAPI_BASE_URL') + '/user/me/group/GRS', + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-GU-ID-Client-Access-Token': `Bearer ${Cypress.env( + 'IDAPI_CLIENT_ACCESS_TOKEN', + )}`, + 'X-GU-ID-FOWARDED-SC-GU-U': cookie?.value, + }, + retryOnStatusCodeFailure: true, + }) + .then((res) => { + cy.wrap({ + status: res.body.status, + }); + }), + ); export const subscribeToNewsletter = (newsletterListId: string) => - cy.getCookie('SC_GU_U').then((cookie) => { - try { - return cy.request({ - url: Cypress.env('IDAPI_BASE_URL') + '/users/me/newsletters', - method: 'PATCH', - headers: { - 'Content-Type': 'application/json', - 'X-GU-ID-Client-Access-Token': `Bearer ${Cypress.env( - 'IDAPI_CLIENT_ACCESS_TOKEN', - )}`, - 'X-GU-ID-FOWARDED-SC-GU-U': cookie?.value, - }, - body: JSON.stringify([ - { - id: newsletterListId, - subscribed: true, - }, - ]), - retryOnStatusCodeFailure: true, - }); - } catch (error) { - throw new Error('Failed to subscribe user to newsletter: ' + error); - } - }); + cy.getCookie('SC_GU_U').then((cookie) => { + try { + return cy.request({ + url: Cypress.env('IDAPI_BASE_URL') + '/users/me/newsletters', + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + 'X-GU-ID-Client-Access-Token': `Bearer ${Cypress.env( + 'IDAPI_CLIENT_ACCESS_TOKEN', + )}`, + 'X-GU-ID-FOWARDED-SC-GU-U': cookie?.value, + }, + body: JSON.stringify([ + { + id: newsletterListId, + subscribed: true, + }, + ]), + retryOnStatusCodeFailure: true, + }); + } catch (error) { + throw new Error('Failed to subscribe user to newsletter: ' + error); + } + }); export const subscribeToMarketingConsent = (marketingId: string) => - cy.getCookie('SC_GU_U').then((cookie) => { - try { - return cy.request({ - url: Cypress.env('IDAPI_BASE_URL') + '/users/me/consents', - method: 'PATCH', - headers: { - 'Content-Type': 'application/json', - 'X-GU-ID-Client-Access-Token': `Bearer ${Cypress.env( - 'IDAPI_CLIENT_ACCESS_TOKEN', - )}`, - 'X-GU-ID-FOWARDED-SC-GU-U': cookie?.value, - }, - body: JSON.stringify([ - { - id: marketingId, - consented: true, - }, - ]), - }); - } catch (error) { - throw new Error( - 'Failed to subscribe user to marketing consent: ' + error, - ); - } - }); + cy.getCookie('SC_GU_U').then((cookie) => { + try { + return cy.request({ + url: Cypress.env('IDAPI_BASE_URL') + '/users/me/consents', + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + 'X-GU-ID-Client-Access-Token': `Bearer ${Cypress.env( + 'IDAPI_CLIENT_ACCESS_TOKEN', + )}`, + 'X-GU-ID-FOWARDED-SC-GU-U': cookie?.value, + }, + body: JSON.stringify([ + { + id: marketingId, + consented: true, + }, + ]), + }); + } catch (error) { + throw new Error( + 'Failed to subscribe user to marketing consent: ' + error, + ); + } + }); export const createTestUser = ({ - primaryEmailAddress, - password, - socialLinks = [], - isUserEmailValidated = false, - deleteAfterMinute = true, - isGuestUser = false, + primaryEmailAddress, + password, + socialLinks = [], + isUserEmailValidated = false, + deleteAfterMinute = true, + isGuestUser = false, }: IDAPITestUserOptions) => { - // Generate a random email address if none is provided. - const finalEmail = primaryEmailAddress || randomMailosaurEmail(); - // Generate a random password if none is provided. - const finalPassword = password || uuidv4(); - try { - return cy - .request({ - url: Cypress.env('IDAPI_BASE_URL') + '/user/test', - method: 'POST', - headers: { - 'X-GU-ID-Client-Access-Token': `Bearer ${Cypress.env( - 'IDAPI_CLIENT_ACCESS_TOKEN', - )}`, - }, - body: { - primaryEmailAddress: finalEmail, - isUserEmailValidated, - socialLinks, - password: finalPassword, - deleteAfterMinute, - isGuestUser, - } as IDAPITestUserOptions, - retryOnStatusCodeFailure: true, - }) - .then((res) => { - return cy.wrap({ - emailAddress: finalEmail, - cookies: res.body.values as IDAPITestUserResponse, - finalPassword, - }); - }); - } catch (error) { - throw new Error('Failed to create IDAPI test user: ' + error); - } + // Generate a random email address if none is provided. + const finalEmail = primaryEmailAddress || randomMailosaurEmail(); + // Generate a random password if none is provided. + const finalPassword = password || uuidv4(); + try { + return cy + .request({ + url: Cypress.env('IDAPI_BASE_URL') + '/user/test', + method: 'POST', + headers: { + 'X-GU-ID-Client-Access-Token': `Bearer ${Cypress.env( + 'IDAPI_CLIENT_ACCESS_TOKEN', + )}`, + }, + body: { + primaryEmailAddress: finalEmail, + isUserEmailValidated, + socialLinks, + password: finalPassword, + deleteAfterMinute, + isGuestUser, + } as IDAPITestUserOptions, + retryOnStatusCodeFailure: true, + }) + .then((res) => { + return cy.wrap({ + emailAddress: finalEmail, + cookies: res.body.values as IDAPITestUserResponse, + finalPassword, + }); + }); + } catch (error) { + throw new Error('Failed to create IDAPI test user: ' + error); + } }; export const sendConsentEmail = ({ - emailAddress, - consents, - newsletters, + emailAddress, + consents, + newsletters, }: { - emailAddress: string; - consents?: string[]; - newsletters?: string[]; + emailAddress: string; + consents?: string[]; + newsletters?: string[]; }) => { - return cy.getCookie('SC_GU_U').then((cookie) => { - try { - return cy.request({ - url: Cypress.env('IDAPI_BASE_URL') + '/consent-email', - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-GU-ID-Client-Access-Token': `Bearer ${Cypress.env( - 'IDAPI_CLIENT_ACCESS_TOKEN', - )}`, - 'X-GU-ID-FOWARDED-SC-GU-U': cookie?.value, - }, - body: JSON.stringify([ - { - email: emailAddress, - 'set-consents': consents, - 'set-lists': newsletters, - }, - ]), - retryOnStatusCodeFailure: true, - }); - } catch (error) { - throw new Error('Failed to send consents email: ' + error); - } - }); + return cy.getCookie('SC_GU_U').then((cookie) => { + try { + return cy.request({ + url: Cypress.env('IDAPI_BASE_URL') + '/consent-email', + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-GU-ID-Client-Access-Token': `Bearer ${Cypress.env( + 'IDAPI_CLIENT_ACCESS_TOKEN', + )}`, + 'X-GU-ID-FOWARDED-SC-GU-U': cookie?.value, + }, + body: JSON.stringify([ + { + email: emailAddress, + 'set-consents': consents, + 'set-lists': newsletters, + }, + ]), + retryOnStatusCodeFailure: true, + }); + } catch (error) { + throw new Error('Failed to send consents email: ' + error); + } + }); }; export const getTestOktaUser = (id: string) => { - try { - return cy - .request({ - url: `${Cypress.env('OKTA_ORG_URL')}/api/v1/users/${id}`, - method: 'GET', - headers: { - Authorization: `SSWS ${Cypress.env('OKTA_API_TOKEN')}`, - }, - retryOnStatusCodeFailure: true, - }) - .then((res) => { - const user = res.body as UserResponse; - return cy.wrap(user); - }); - } catch (error) { - throw new Error('Failed to create Okta test user: ' + error); - } + try { + return cy + .request({ + url: `${Cypress.env('OKTA_ORG_URL')}/api/v1/users/${id}`, + method: 'GET', + headers: { + Authorization: `SSWS ${Cypress.env('OKTA_API_TOKEN')}`, + }, + retryOnStatusCodeFailure: true, + }) + .then((res) => { + const user = res.body as UserResponse; + return cy.wrap(user); + }); + } catch (error) { + throw new Error('Failed to create Okta test user: ' + error); + } }; export const updateOktaTestUserProfile = ( - id: string, - profile: OktaUserProfile, + id: string, + profile: OktaUserProfile, ) => { - try { - return cy - .request({ - url: `${Cypress.env('OKTA_ORG_URL')}/api/v1/users/${id}`, - method: 'POST', - headers: { - Authorization: `SSWS ${Cypress.env('OKTA_API_TOKEN')}`, - }, - body: { - profile, - }, - retryOnStatusCodeFailure: true, - }) - .then((res) => { - const token = res.body as TokenResponse; - return cy.wrap(token); - }); - } catch (error) { - throw new Error('Failed to update Okta test user: ' + error); - } + try { + return cy + .request({ + url: `${Cypress.env('OKTA_ORG_URL')}/api/v1/users/${id}`, + method: 'POST', + headers: { + Authorization: `SSWS ${Cypress.env('OKTA_API_TOKEN')}`, + }, + body: { + profile, + }, + retryOnStatusCodeFailure: true, + }) + .then((res) => { + const token = res.body as TokenResponse; + return cy.wrap(token); + }); + } catch (error) { + throw new Error('Failed to update Okta test user: ' + error); + } }; export const activateTestOktaUser = (id: string) => { - try { - return cy - .request({ - url: `${Cypress.env( - 'OKTA_ORG_URL', - )}/api/v1/users/${id}/lifecycle/activate`, - method: 'POST', - headers: { - Authorization: `SSWS ${Cypress.env('OKTA_API_TOKEN')}`, - }, - qs: { - sendEmail: false, - }, - retryOnStatusCodeFailure: true, - }) - .then((res) => { - const token = res.body as TokenResponse; - return cy.wrap(token); - }); - } catch (error) { - throw new Error('Failed to activate Okta test user: ' + error); - } + try { + return cy + .request({ + url: `${Cypress.env( + 'OKTA_ORG_URL', + )}/api/v1/users/${id}/lifecycle/activate`, + method: 'POST', + headers: { + Authorization: `SSWS ${Cypress.env('OKTA_API_TOKEN')}`, + }, + qs: { + sendEmail: false, + }, + retryOnStatusCodeFailure: true, + }) + .then((res) => { + const token = res.body as TokenResponse; + return cy.wrap(token); + }); + } catch (error) { + throw new Error('Failed to activate Okta test user: ' + error); + } }; export const resetOktaUserPassword = (id: string) => { - try { - return cy - .request({ - url: `${Cypress.env( - 'OKTA_ORG_URL', - )}/api/v1/users/${id}/lifecycle/reset_password`, - method: 'POST', - headers: { - Authorization: `SSWS ${Cypress.env('OKTA_API_TOKEN')}`, - }, - qs: { - sendEmail: false, - }, - retryOnStatusCodeFailure: true, - }) - .then((res) => { - const token = res.body.resetPasswordUrl.split('/').slice(-1)[0]; - return cy.wrap({ token } as TokenResponse); - }); - } catch (error) { - throw new Error('Failed to reset password for Okta test user: ' + error); - } + try { + return cy + .request({ + url: `${Cypress.env( + 'OKTA_ORG_URL', + )}/api/v1/users/${id}/lifecycle/reset_password`, + method: 'POST', + headers: { + Authorization: `SSWS ${Cypress.env('OKTA_API_TOKEN')}`, + }, + qs: { + sendEmail: false, + }, + retryOnStatusCodeFailure: true, + }) + .then((res) => { + const token = res.body.resetPasswordUrl.split('/').slice(-1)[0]; + return cy.wrap({ token } as TokenResponse); + }); + } catch (error) { + throw new Error('Failed to reset password for Okta test user: ' + error); + } }; export const expireOktaUserPassword = (id: string) => { - try { - return cy - .request({ - url: `${Cypress.env( - 'OKTA_ORG_URL', - )}/api/v1/users/${id}/lifecycle/expire_password`, - method: 'POST', - headers: { - Authorization: `SSWS ${Cypress.env('OKTA_API_TOKEN')}`, - }, - retryOnStatusCodeFailure: true, - }) - .then((res) => { - const user = res.body as UserResponse; - return cy.wrap(user); - }); - } catch (error) { - throw new Error('Failed to expire password for Okta test user: ' + error); - } + try { + return cy + .request({ + url: `${Cypress.env( + 'OKTA_ORG_URL', + )}/api/v1/users/${id}/lifecycle/expire_password`, + method: 'POST', + headers: { + Authorization: `SSWS ${Cypress.env('OKTA_API_TOKEN')}`, + }, + retryOnStatusCodeFailure: true, + }) + .then((res) => { + const user = res.body as UserResponse; + return cy.wrap(user); + }); + } catch (error) { + throw new Error('Failed to expire password for Okta test user: ' + error); + } }; export const suspendOktaUser = (id: string) => { - try { - return cy - .request({ - url: `${Cypress.env( - 'OKTA_ORG_URL', - )}/api/v1/users/${id}/lifecycle/suspend`, - method: 'POST', - headers: { - Authorization: `SSWS ${Cypress.env('OKTA_API_TOKEN')}`, - }, - retryOnStatusCodeFailure: true, - }) - .then(() => { - return cy.wrap(true); - }); - } catch (error) { - throw new Error('Failed to suspend Okta test user: ' + error); - } + try { + return cy + .request({ + url: `${Cypress.env( + 'OKTA_ORG_URL', + )}/api/v1/users/${id}/lifecycle/suspend`, + method: 'POST', + headers: { + Authorization: `SSWS ${Cypress.env('OKTA_API_TOKEN')}`, + }, + retryOnStatusCodeFailure: true, + }) + .then(() => { + return cy.wrap(true); + }); + } catch (error) { + throw new Error('Failed to suspend Okta test user: ' + error); + } }; export const getOktaUserGroups = (id: string) => { - try { - return cy - .request({ - url: `${Cypress.env('OKTA_ORG_URL')}/api/v1/users/${id}/groups`, - method: 'GET', - headers: { - Authorization: `SSWS ${Cypress.env('OKTA_API_TOKEN')}`, - }, - retryOnStatusCodeFailure: true, - }) - .then((res) => { - const user = res.body as Group[]; - return cy.wrap(user); - }); - } catch (error) { - throw new Error('Failed to get user groups: ' + error); - } + try { + return cy + .request({ + url: `${Cypress.env('OKTA_ORG_URL')}/api/v1/users/${id}/groups`, + method: 'GET', + headers: { + Authorization: `SSWS ${Cypress.env('OKTA_API_TOKEN')}`, + }, + retryOnStatusCodeFailure: true, + }) + .then((res) => { + const user = res.body as Group[]; + return cy.wrap(user); + }); + } catch (error) { + throw new Error('Failed to get user groups: ' + error); + } }; export const addOktaUserToGroup = (id: string, groupId: string) => { - try { - return cy - .request({ - url: `${Cypress.env( - 'OKTA_ORG_URL', - )}/api/v1/groups/${groupId}/users/${id}`, - method: 'PUT', - headers: { - Authorization: `SSWS ${Cypress.env('OKTA_API_TOKEN')}`, - }, - retryOnStatusCodeFailure: true, - }) - .then(() => { - return cy.wrap(true); - }); - } catch (error) { - throw new Error('Failed to add Okta test user to group: ' + error); - } + try { + return cy + .request({ + url: `${Cypress.env( + 'OKTA_ORG_URL', + )}/api/v1/groups/${groupId}/users/${id}`, + method: 'PUT', + headers: { + Authorization: `SSWS ${Cypress.env('OKTA_API_TOKEN')}`, + }, + retryOnStatusCodeFailure: true, + }) + .then(() => { + return cy.wrap(true); + }); + } catch (error) { + throw new Error('Failed to add Okta test user to group: ' + error); + } }; export const findEmailValidatedOktaGroupId = () => { - try { - return cy - .request({ - url: `${Cypress.env( - 'OKTA_ORG_URL', - )}/api/v1/groups?q=GuardianUser-EmailValidated`, - method: 'GET', - headers: { - Authorization: `SSWS ${Cypress.env('OKTA_API_TOKEN')}`, - }, - retryOnStatusCodeFailure: true, - }) - .then((res) => { - const group = res.body[0]?.id as string | undefined; - if (!group) { - throw new Error('Failed to find Okta group'); - } - return cy.wrap(group); - }); - } catch (error) { - throw new Error( - 'Failed to get ID of GuardianUser-EmailValidated group: ' + error, - ); - } + try { + return cy + .request({ + url: `${Cypress.env( + 'OKTA_ORG_URL', + )}/api/v1/groups?q=GuardianUser-EmailValidated`, + method: 'GET', + headers: { + Authorization: `SSWS ${Cypress.env('OKTA_API_TOKEN')}`, + }, + retryOnStatusCodeFailure: true, + }) + .then((res) => { + const group = res.body[0]?.id as string | undefined; + if (!group) { + throw new Error('Failed to find Okta group'); + } + return cy.wrap(group); + }); + } catch (error) { + throw new Error( + 'Failed to get ID of GuardianUser-EmailValidated group: ' + error, + ); + } }; export const getCurrentOktaSession = (sid: string) => { - try { - return cy - .request({ - url: `${Cypress.env('OKTA_ORG_URL')}/api/v1/sessions/${sid}`, - method: 'GET', - headers: { - Authorization: `SSWS ${Cypress.env('OKTA_API_TOKEN')}`, - }, - retryOnStatusCodeFailure: true, - }) - .then((res) => { - const session = res.body as SessionResponse; - return cy.wrap(session); - }); - } catch (error) { - throw new Error('Failed to get current Okta session: ' + error); - } + try { + return cy + .request({ + url: `${Cypress.env('OKTA_ORG_URL')}/api/v1/sessions/${sid}`, + method: 'GET', + headers: { + Authorization: `SSWS ${Cypress.env('OKTA_API_TOKEN')}`, + }, + retryOnStatusCodeFailure: true, + }) + .then((res) => { + const session = res.body as SessionResponse; + return cy.wrap(session); + }); + } catch (error) { + throw new Error('Failed to get current Okta session: ' + error); + } }; export const closeCurrentOktaSession = (sid: string | undefined) => { - try { - return cy - .request({ - url: `${Cypress.env('OKTA_ORG_URL')}/api/v1/sessions/${sid}`, - method: 'DELETE', - headers: { - Authorization: `SSWS ${Cypress.env('OKTA_API_TOKEN')}`, - }, - retryOnStatusCodeFailure: true, - }) - .then((res) => { - if (res.status === 204 || res.status == 404) { - return true; - } - return false; - }); - } catch (error) { - throw new Error('Failed to close current Okta session: ' + error); - } + try { + return cy + .request({ + url: `${Cypress.env('OKTA_ORG_URL')}/api/v1/sessions/${sid}`, + method: 'DELETE', + headers: { + Authorization: `SSWS ${Cypress.env('OKTA_API_TOKEN')}`, + }, + retryOnStatusCodeFailure: true, + }) + .then((res) => { + if (res.status === 204 || res.status == 404) { + return true; + } + return false; + }); + } catch (error) { + throw new Error('Failed to close current Okta session: ' + error); + } }; diff --git a/cypress/support/cypress-axe.ts b/cypress/support/cypress-axe.ts index afb3298996..f5cec678ec 100644 --- a/cypress/support/cypress-axe.ts +++ b/cypress/support/cypress-axe.ts @@ -1,36 +1,36 @@ import axe = require('axe-core'); export const terminalLog = (violations: axe.Result[]) => { - cy.task( - 'log', - `${violations.length} accessibility violation${ - violations.length === 1 ? '' : 's' - } ${violations.length === 1 ? 'was' : 'were'} detected`, - ); - // pluck specific keys to keep the table readable - const violationData = violations.map( - ({ id, impact, description, nodes }) => ({ - id, - impact, - description, - nodes: nodes.length, - }), - ); + cy.task( + 'log', + `${violations.length} accessibility violation${ + violations.length === 1 ? '' : 's' + } ${violations.length === 1 ? 'was' : 'were'} detected`, + ); + // pluck specific keys to keep the table readable + const violationData = violations.map( + ({ id, impact, description, nodes }) => ({ + id, + impact, + description, + nodes: nodes.length, + }), + ); - cy.task('table', violationData); + cy.task('table', violationData); }; // for use on page reloads after axction taken export const injectAndCheckAxe = () => { - cy.injectAxe(); - cy.configureAxe({ - rules: [ - { - selector: '[data-cy="exclude-a11y-check"]', - matches: 'color-contrast-evaluate', - id: 'color-contrast', - }, - ], - }); - cy.checkA11y(undefined, undefined, terminalLog); + cy.injectAxe(); + cy.configureAxe({ + rules: [ + { + selector: '[data-cy="exclude-a11y-check"]', + matches: 'color-contrast-evaluate', + id: 'color-contrast', + }, + ], + }); + cy.checkA11y(undefined, undefined, terminalLog); }; diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts index 3ef40775c9..d08134dfba 100644 --- a/cypress/support/e2e.ts +++ b/cypress/support/e2e.ts @@ -31,17 +31,17 @@ export const MOCKING_ENDPOINT = 'localhost:9000/mock'; import './commands'; beforeEach(function () { - cy.disableCMP(); + cy.disableCMP(); }); Cypress.on('uncaught:exception', (err) => { - // We don't want to throw an error if the consent framework isn't loaded in the tests - // https://github.com/guardian/consent-management-platform/blob/main/src/onConsentChange.ts#L34 - if (err.message.includes('no IAB consent framework found on the page')) { - // eslint-disable-next-line no-console - console.warn(err); - return false; - } - - return true; + // We don't want to throw an error if the consent framework isn't loaded in the tests + // https://github.com/guardian/consent-management-platform/blob/main/src/onConsentChange.ts#L34 + if (err.message.includes('no IAB consent framework found on the page')) { + // eslint-disable-next-line no-console + console.warn(err); + return false; + } + + return true; }); diff --git a/cypress/support/geolocation.ts b/cypress/support/geolocation.ts index 6144d006bb..7892f03334 100644 --- a/cypress/support/geolocation.ts +++ b/cypress/support/geolocation.ts @@ -1,10 +1,10 @@ export const getGeoLocationHeaders = (countryCode = 'GB') => { - return { 'x-gu-geolocation': countryCode }; + return { 'x-gu-geolocation': countryCode }; }; export const GEOLOCATION_CODES = { - GB: 'GB', - AMERICA: 'US', - AUSTRALIA: 'AU', - OTHERS: 'ROW', + GB: 'GB', + AMERICA: 'US', + AUSTRALIA: 'AU', + OTHERS: 'ROW', }; diff --git a/cypress/support/idapi/auth.ts b/cypress/support/idapi/auth.ts index 7cea3d43a7..0712ba5638 100644 --- a/cypress/support/idapi/auth.ts +++ b/cypress/support/idapi/auth.ts @@ -1,7 +1,7 @@ export const AUTH_REDIRECT_ENDPOINT = '/auth/redirect'; export const authRedirectSignInRecentlyEmailValidated = { - signInStatus: 'signedInRecently', - emailValidated: true, - redirect: null, + signInStatus: 'signedInRecently', + emailValidated: true, + redirect: null, }; diff --git a/cypress/support/idapi/consent.ts b/cypress/support/idapi/consent.ts index 9e8547c5f4..68ac8d54c4 100644 --- a/cypress/support/idapi/consent.ts +++ b/cypress/support/idapi/consent.ts @@ -1,114 +1,114 @@ export const CONSENT_ERRORS = { - GENERIC: 'There was a problem saving your choice, please try again.', + GENERIC: 'There was a problem saving your choice, please try again.', }; export const CONSENTS_ENDPOINT = '/consents'; export const allConsents = [ - { - id: 'sms', - isOptOut: false, - isChannel: true, - name: 'SMS', - description: - "I would like to receive updates about the Guardian products and services I've selected above by SMS (text messages).", - }, - { - id: 'holidays', - isOptOut: false, - isChannel: false, - name: 'Holidays & Vacations', - description: - 'Ideas and inspiration for your next trip away, as well as the latest offers from Guardian Holidays in the UK and Guardian Vacations in the US.', - }, - { - id: 'market_research_optout', - isOptOut: true, - isChannel: false, - name: 'Allow the Guardian to contact me for market research purposes', - description: - 'From time to time we may contact you for market research purposes inviting you to complete a survey, or take part in a group discussion. Normally, this invitation would be sent via email, but we may also contact you by phone.', - }, - { - id: 'offers', - isOptOut: false, - isChannel: false, - name: 'Offers', - description: - 'Offers and competitions from the Guardian and other carefully selected and trusted partners that we think you might like. Don’t worry, we won’t share your personal information with them. Available in the UK, Aus and US.', - }, - { - id: 'post_optout', - isOptOut: true, - isChannel: false, - name: 'Allow the Guardian to send communications by post', - }, - { - id: 'profiling_optout', - isOptOut: true, - isChannel: false, - name: 'Allow the Guardian to analyse this data to improve marketing content', - }, - { - id: 'phone_optout', - isOptOut: true, - isChannel: true, - name: 'Allow the Guardian to send communications by telephone', - }, - { - id: 'supporter', - isOptOut: false, - isChannel: false, - name: 'Supporting the Guardian', - description: - 'News and offers from the Guardian, The Observer and Guardian Weekly, on the ways to read and support our journalism. Already a member, subscriber or contributor? Opt in here to receive your regular emails and updates.', - }, - { - id: 'jobs', - isOptOut: false, - isChannel: false, - name: 'Jobs', - description: - 'Receive tips, Job Match recommendations, and advice from Guardian Jobs on taking your next career step.', - }, - { - id: 'events', - isOptOut: false, - isChannel: false, - name: 'Events & Masterclasses', - description: - 'Learn from leading minds at our Guardian live events, including discussions and debates, short courses and bespoke training. Available in the UK, Aus and US.', - }, - { - id: 'personalised_advertising', - isOptOut: false, - isChannel: false, - name: 'Allow personalised advertising using this data - this supports the Guardian', - }, + { + id: 'sms', + isOptOut: false, + isChannel: true, + name: 'SMS', + description: + "I would like to receive updates about the Guardian products and services I've selected above by SMS (text messages).", + }, + { + id: 'holidays', + isOptOut: false, + isChannel: false, + name: 'Holidays & Vacations', + description: + 'Ideas and inspiration for your next trip away, as well as the latest offers from Guardian Holidays in the UK and Guardian Vacations in the US.', + }, + { + id: 'market_research_optout', + isOptOut: true, + isChannel: false, + name: 'Allow the Guardian to contact me for market research purposes', + description: + 'From time to time we may contact you for market research purposes inviting you to complete a survey, or take part in a group discussion. Normally, this invitation would be sent via email, but we may also contact you by phone.', + }, + { + id: 'offers', + isOptOut: false, + isChannel: false, + name: 'Offers', + description: + 'Offers and competitions from the Guardian and other carefully selected and trusted partners that we think you might like. Don’t worry, we won’t share your personal information with them. Available in the UK, Aus and US.', + }, + { + id: 'post_optout', + isOptOut: true, + isChannel: false, + name: 'Allow the Guardian to send communications by post', + }, + { + id: 'profiling_optout', + isOptOut: true, + isChannel: false, + name: 'Allow the Guardian to analyse this data to improve marketing content', + }, + { + id: 'phone_optout', + isOptOut: true, + isChannel: true, + name: 'Allow the Guardian to send communications by telephone', + }, + { + id: 'supporter', + isOptOut: false, + isChannel: false, + name: 'Supporting the Guardian', + description: + 'News and offers from the Guardian, The Observer and Guardian Weekly, on the ways to read and support our journalism. Already a member, subscriber or contributor? Opt in here to receive your regular emails and updates.', + }, + { + id: 'jobs', + isOptOut: false, + isChannel: false, + name: 'Jobs', + description: + 'Receive tips, Job Match recommendations, and advice from Guardian Jobs on taking your next career step.', + }, + { + id: 'events', + isOptOut: false, + isChannel: false, + name: 'Events & Masterclasses', + description: + 'Learn from leading minds at our Guardian live events, including discussions and debates, short courses and bespoke training. Available in the UK, Aus and US.', + }, + { + id: 'personalised_advertising', + isOptOut: false, + isChannel: false, + name: 'Allow personalised advertising using this data - this supports the Guardian', + }, ]; export const defaultUserConsent = allConsents.map(({ id }) => ({ - id, - consented: false, + id, + consented: false, })); export const getUserConsents = (consented: Array) => { - if (!consented.length) { - return defaultUserConsent; - } - return allConsents.map(({ id }) => { - return { - id, - consented: consented.includes(id), - }; - }); + if (!consented.length) { + return defaultUserConsent; + } + return allConsents.map(({ id }) => { + return { + id, + consented: consented.includes(id), + }; + }); }; export const optedOutUserConsent = getUserConsents([ - 'profiling_optout', - 'market_research_optout', + 'profiling_optout', + 'market_research_optout', ]); export const optedIntoPersonalisedAdvertisingUserConsent = getUserConsents([ - 'personalised_advertising', + 'personalised_advertising', ]); diff --git a/cypress/support/idapi/cookie.ts b/cypress/support/idapi/cookie.ts index 3e410f6076..37360d3680 100644 --- a/cypress/support/idapi/cookie.ts +++ b/cypress/support/idapi/cookie.ts @@ -1,28 +1,28 @@ import ms from 'ms'; export const authCookieResponse = { - cookies: { - values: [ - { - key: 'GU_U', - value: 'FAKE_GU_U', - }, - { - key: 'SC_GU_LA', - value: 'FAKE_SC_GU_LA', - sessionCookie: true, - }, - { - key: 'SC_GU_U', - value: 'FAKE_SC_GU_U', - }, - ], - expiresAt: ms('30d') * 1000, - }, + cookies: { + values: [ + { + key: 'GU_U', + value: 'FAKE_GU_U', + }, + { + key: 'SC_GU_LA', + value: 'FAKE_SC_GU_LA', + sessionCookie: true, + }, + { + key: 'SC_GU_U', + value: 'FAKE_SC_GU_U', + }, + ], + expiresAt: ms('30d') * 1000, + }, }; export const setAuthCookies = () => { - cy.setCookie('GU_U', 'FAKE_GU_U'); - cy.setCookie('SC_GU_U', 'FAKE_SC_GU_U'); - cy.setCookie('SC_GU_LA', 'FAKE_SC_GU_LA'); + cy.setCookie('GU_U', 'FAKE_GU_U'); + cy.setCookie('SC_GU_U', 'FAKE_SC_GU_U'); + cy.setCookie('SC_GU_LA', 'FAKE_SC_GU_LA'); }; diff --git a/cypress/support/idapi/guest.ts b/cypress/support/idapi/guest.ts index 568e99915c..43963e3301 100644 --- a/cypress/support/idapi/guest.ts +++ b/cypress/support/idapi/guest.ts @@ -1,28 +1,28 @@ export const GUEST_ENDPOINT = '/guest'; export const GUEST_ERRORS = { - GENERIC: 'There was a problem registering, please try again.', - EMAIL_INVALID: 'Please enter a valid email address.', + GENERIC: 'There was a problem registering, please try again.', + EMAIL_INVALID: 'Please enter a valid email address.', }; export const invalidEmailAddress = { - status: 'error', - errors: [ - { - message: 'Invalid emailAddress:', - description: 'Please enter a valid email address', - context: 'user.primaryEmailAddress', - }, - ], + status: 'error', + errors: [ + { + message: 'Invalid emailAddress:', + description: 'Please enter a valid email address', + context: 'user.primaryEmailAddress', + }, + ], }; export const emailAddressInUse = { - status: 'error', - errors: [ - { - message: 'Email in use', - description: 'This email has already been taken', - context: 'user.primaryEmailAddress', - }, - ], + status: 'error', + errors: [ + { + message: 'Email in use', + description: 'This email has already been taken', + context: 'user.primaryEmailAddress', + }, + ], }; diff --git a/cypress/support/idapi/newsletter.ts b/cypress/support/idapi/newsletter.ts index 0e1307f1f9..1a1aecbc21 100644 --- a/cypress/support/idapi/newsletter.ts +++ b/cypress/support/idapi/newsletter.ts @@ -2,102 +2,102 @@ export const NEWSLETTER_ENDPOINT = '/newsletters'; export const NEWSLETTER_SUBSCRIPTION_ENDPOINT = '/users/me/newsletters'; export const NEWSLETTER_ERRORS = { - GENERIC: - 'There was a problem displaying newsletter options, please try again.', + GENERIC: + 'There was a problem displaying newsletter options, please try again.', }; export const allNewsletters = [ - { - id: 'morning-mail', - name: "Guardian Australia's Morning Mail", - description: - 'Our Australian morning briefing email breaks down the key national and international stories of the day and why they matter', - frequency: 'Every weekday', - exactTargetListId: 4148, - }, - { - id: 'afternoon-update', - name: "Guardian Australia's Afternoon Update", - description: - 'Our Australian afternoon update email breaks down the key national and international stories of the day and why they matter', - frequency: 'Every weekday', - exactTargetListId: 6023, - }, - { - id: 'five-great-reads', - name: 'Five Great Reads', - description: - 'Each week our editors select five of the most interesting, entertaining and thoughtful reads published by Guardian Australia and our international colleagues. Sign up to receive it in your inbox every Saturday morning', - frequency: 'Weekly', - exactTargetListId: 6019, - }, - { - id: 'saved-for-later', - name: 'Saved for Later', - description: - "Catch up on the fun stuff with Guardian Australia's culture and lifestyle rundown of pop culture, trends and tips", - frequency: 'Weekly', - exactTargetListId: 6003, - }, - { - id: 'morning-briefing', - name: 'First Edition', - description: - 'Archie Bland and Nimo Omer take you through the top stories and what they mean, free every weekday morning', - frequency: 'Every weekday', - exactTargetListId: 4156, - }, - { - id: 'us-morning-newsletter', - name: 'First Thing: the US morning briefing', - description: - "Stay informed with a summary of the top stories from the US and the day's must-reads from across the Guardian", - frequency: 'Every weekday', - exactTargetListId: 4300, - }, - { - id: 'today-us', - name: 'The Guardian Headlines US', - description: - 'For US readers, we offer a regional edition of our daily email, delivering the most important headlines every morning', - frequency: 'Every day', - exactTargetListId: 4152, - }, - { - id: 'best-of-opinion-us', - name: 'The Best of Guardian Opinion: US Edition', - description: - "Join the debate on America's most pressing issues with the US edition of our daily op-ed selection", - frequency: 'Every weekday', - exactTargetListId: 4162, - }, - { - id: 'green-light', - name: 'Green Light', - description: - 'In each weekly edition our editors highlight the most important environment stories of the week including data, opinion pieces and background guides. We’ll also flag up our best video, picture galleries, podcasts, blogs and green living guides', - frequency: 'Weekly', - exactTargetListId: 4147, - }, - { - id: 'the-long-read', - name: 'The Long Read', - description: - 'Get lost in a great story. From politics to fashion, international investigations to new thinking, culture to crime - we’ll bring you the biggest ideas and the arguments that matter. Sign up to have the Guardian’s award-winning long reads emailed to you every Saturday morning', - frequency: 'Every Saturday', - exactTargetListId: 4165, - }, + { + id: 'morning-mail', + name: "Guardian Australia's Morning Mail", + description: + 'Our Australian morning briefing email breaks down the key national and international stories of the day and why they matter', + frequency: 'Every weekday', + exactTargetListId: 4148, + }, + { + id: 'afternoon-update', + name: "Guardian Australia's Afternoon Update", + description: + 'Our Australian afternoon update email breaks down the key national and international stories of the day and why they matter', + frequency: 'Every weekday', + exactTargetListId: 6023, + }, + { + id: 'five-great-reads', + name: 'Five Great Reads', + description: + 'Each week our editors select five of the most interesting, entertaining and thoughtful reads published by Guardian Australia and our international colleagues. Sign up to receive it in your inbox every Saturday morning', + frequency: 'Weekly', + exactTargetListId: 6019, + }, + { + id: 'saved-for-later', + name: 'Saved for Later', + description: + "Catch up on the fun stuff with Guardian Australia's culture and lifestyle rundown of pop culture, trends and tips", + frequency: 'Weekly', + exactTargetListId: 6003, + }, + { + id: 'morning-briefing', + name: 'First Edition', + description: + 'Archie Bland and Nimo Omer take you through the top stories and what they mean, free every weekday morning', + frequency: 'Every weekday', + exactTargetListId: 4156, + }, + { + id: 'us-morning-newsletter', + name: 'First Thing: the US morning briefing', + description: + "Stay informed with a summary of the top stories from the US and the day's must-reads from across the Guardian", + frequency: 'Every weekday', + exactTargetListId: 4300, + }, + { + id: 'today-us', + name: 'The Guardian Headlines US', + description: + 'For US readers, we offer a regional edition of our daily email, delivering the most important headlines every morning', + frequency: 'Every day', + exactTargetListId: 4152, + }, + { + id: 'best-of-opinion-us', + name: 'The Best of Guardian Opinion: US Edition', + description: + "Join the debate on America's most pressing issues with the US edition of our daily op-ed selection", + frequency: 'Every weekday', + exactTargetListId: 4162, + }, + { + id: 'green-light', + name: 'Green Light', + description: + 'In each weekly edition our editors highlight the most important environment stories of the week including data, opinion pieces and background guides. We’ll also flag up our best video, picture galleries, podcasts, blogs and green living guides', + frequency: 'Weekly', + exactTargetListId: 4147, + }, + { + id: 'the-long-read', + name: 'The Long Read', + description: + 'Get lost in a great story. From politics to fashion, international investigations to new thinking, culture to crime - we’ll bring you the biggest ideas and the arguments that matter. Sign up to have the Guardian’s award-winning long reads emailed to you every Saturday morning', + frequency: 'Every Saturday', + exactTargetListId: 4165, + }, ]; export const userNewsletters = ( - subscriptions: Array<{ listId: number }> = [], + subscriptions: Array<{ listId: number }> = [], ) => { - return { - result: { - htmlPreference: 'HTML', - subscriptions, - globalSubscriptionStatus: 'opted_in', - }, - status: 'ok', - }; + return { + result: { + htmlPreference: 'HTML', + subscriptions, + globalSubscriptionStatus: 'opted_in', + }, + status: 'ok', + }; }; diff --git a/cypress/support/idapi/user.ts b/cypress/support/idapi/user.ts index 36cd0eb00d..bf92d07c6d 100644 --- a/cypress/support/idapi/user.ts +++ b/cypress/support/idapi/user.ts @@ -5,18 +5,18 @@ export const USER_ENDPOINT = '/user/me'; export const USER_CONSENTS_ENDPOINT = '/users/me/consents'; export const USER_ERRORS = { - GENERIC: 'There was a problem retrieving your details, please try again.', + GENERIC: 'There was a problem retrieving your details, please try again.', }; export const createUser = (consents = defaultUserConsent) => ({ - status: 'ok', - user: { - primaryEmailAddress: 'a.reader@example.com', - statusFields: { - userEmailValidated: true, - }, - consents, - }, + status: 'ok', + user: { + primaryEmailAddress: 'a.reader@example.com', + statusFields: { + userEmailValidated: true, + }, + consents, + }, }); export const verifiedUserWithNoConsent = createUser(); diff --git a/cypress/support/idapi/verify_email.ts b/cypress/support/idapi/verify_email.ts index e1bff7079d..ef541c8849 100644 --- a/cypress/support/idapi/verify_email.ts +++ b/cypress/support/idapi/verify_email.ts @@ -1,27 +1,27 @@ export const validationTokenExpired = { - error: { - status: 'error', - errors: [ - { - message: 'Token expired', - description: 'The activation token is no longer valid', - }, - ], - }, - status: 403, - level: 'error', + error: { + status: 'error', + errors: [ + { + message: 'Token expired', + description: 'The activation token is no longer valid', + }, + ], + }, + status: 403, + level: 'error', }; export const validationTokenInvalid = { - error: { - status: 'error', - errors: [ - { - message: 'Invalid token', - description: 'The token you supplied could not be parsed', - }, - ], - }, - status: 403, - level: 'error', + error: { + status: 'error', + errors: [ + { + message: 'Invalid token', + description: 'The token you supplied could not be parsed', + }, + ], + }, + status: 403, + level: 'error', }; diff --git a/cypress/support/okta/userStatuses.ts b/cypress/support/okta/userStatuses.ts index cf80608fe6..c170ad1f60 100644 --- a/cypress/support/okta/userStatuses.ts +++ b/cypress/support/okta/userStatuses.ts @@ -1,10 +1,10 @@ const userStatuses = [ - false, - 'ACTIVE', - 'PROVISIONED', - 'STAGED', - 'RECOVERY', - 'PASSWORD_EXPIRED', + false, + 'ACTIVE', + 'PROVISIONED', + 'STAGED', + 'RECOVERY', + 'PASSWORD_EXPIRED', ]; export default userStatuses; diff --git a/cypress/support/pages/onboarding/communications_page.ts b/cypress/support/pages/onboarding/communications_page.ts index b950e1ca93..4e389cea2a 100644 --- a/cypress/support/pages/onboarding/communications_page.ts +++ b/cypress/support/pages/onboarding/communications_page.ts @@ -1,16 +1,16 @@ import OnboardingPage from './onboarding_page'; class CommunicationsPage extends OnboardingPage { - static URL = '/consents/communication'; - static CONTENT = { - ...OnboardingPage.CONTENT, - OPT_OUT_MESSAGE: - 'I do NOT wish to be contacted by the Guardian for market research purposes.', - }; + static URL = '/consents/communication'; + static CONTENT = { + ...OnboardingPage.CONTENT, + OPT_OUT_MESSAGE: + 'I do NOT wish to be contacted by the Guardian for market research purposes.', + }; - static consentCheckboxWithTitle(title: string) { - return cy.contains(title).siblings().find('input[type="checkbox"]'); - } + static consentCheckboxWithTitle(title: string) { + return cy.contains(title).siblings().find('input[type="checkbox"]'); + } } export default CommunicationsPage; diff --git a/cypress/support/pages/onboarding/newsletters_page.ts b/cypress/support/pages/onboarding/newsletters_page.ts index 881256536a..fbbfedce6e 100644 --- a/cypress/support/pages/onboarding/newsletters_page.ts +++ b/cypress/support/pages/onboarding/newsletters_page.ts @@ -1,28 +1,28 @@ import OnboardingPage from './onboarding_page'; class NewslettersPage extends OnboardingPage { - static CONTENT = { - ...OnboardingPage.CONTENT, - NEWSLETTERS: { - FIRST_EDITION_UK: 'First Edition', - FIRST_THING_US: 'First Thing: the US morning briefing', - HEADLINES_US: 'The Guardian Headlines US', - OPINION_US: 'The Best of Guardian Opinion: US Edition', - LONG_READ: 'The Long Read', - GREEN_LIGHT: 'Green Light', - MORNING_MAIL_AU: "Guardian Australia's Morning Mail", - AFTERNOON_UPDATE_AU: "Guardian Australia's Afternoon Update", - FIVE_GREAT_READS_AU: 'Five Great Reads', - SAVED_FOR_LATER_AU: 'Saved for Later', - }, - Consents: { - EVENTS: 'Events & Masterclasses', - }, - }; - static checkboxWithTitle(title: string) { - return cy.contains(title).parent().find('input[type="checkbox"]'); - } - static URL = '/consents/newsletters'; + static CONTENT = { + ...OnboardingPage.CONTENT, + NEWSLETTERS: { + FIRST_EDITION_UK: 'First Edition', + FIRST_THING_US: 'First Thing: the US morning briefing', + HEADLINES_US: 'The Guardian Headlines US', + OPINION_US: 'The Best of Guardian Opinion: US Edition', + LONG_READ: 'The Long Read', + GREEN_LIGHT: 'Green Light', + MORNING_MAIL_AU: "Guardian Australia's Morning Mail", + AFTERNOON_UPDATE_AU: "Guardian Australia's Afternoon Update", + FIVE_GREAT_READS_AU: 'Five Great Reads', + SAVED_FOR_LATER_AU: 'Saved for Later', + }, + Consents: { + EVENTS: 'Events & Masterclasses', + }, + }; + static checkboxWithTitle(title: string) { + return cy.contains(title).parent().find('input[type="checkbox"]'); + } + static URL = '/consents/newsletters'; } export default NewslettersPage; diff --git a/cypress/support/pages/onboarding/onboarding_page.ts b/cypress/support/pages/onboarding/onboarding_page.ts index d523163586..408950e52c 100644 --- a/cypress/support/pages/onboarding/onboarding_page.ts +++ b/cypress/support/pages/onboarding/onboarding_page.ts @@ -1,44 +1,44 @@ class Onboarding { - static URL = '/consents'; - - static CONTENT = { - CONTINUE_BUTTON: 'Continue', - PREVIOUS_BUTTON: 'Go back', - REPORT_ERROR_LINK: 'Report this error', - }; - - static backButton() { - return cy.contains(Onboarding.CONTENT.PREVIOUS_BUTTON); - } - - static saveAndContinueButton() { - return cy.contains(Onboarding.CONTENT.CONTINUE_BUTTON); - } - - static allCheckboxes() { - // @TODO: This is generic selector based approach, make a page specific user based approach, e.g. use contains - return cy.get('[type="checkbox"]'); - } - - static errorBanner() { - return cy.contains(Onboarding.CONTENT.REPORT_ERROR_LINK).parent(); - } - - static goto({ query = {} } = {}) { - const querystring = new URLSearchParams(query).toString(); - - cy.visit(`${this.URL}${querystring ? `?${querystring}` : ''}`, { - failOnStatusCode: false, - }); - } - - static gotoFlowStart({ failOnStatusCode = true, query = {} } = {}) { - const querystring = new URLSearchParams(query).toString(); - - cy.visit(`${Onboarding.URL}${querystring ? `?${querystring}` : ''}`, { - failOnStatusCode, - }); - } + static URL = '/consents'; + + static CONTENT = { + CONTINUE_BUTTON: 'Continue', + PREVIOUS_BUTTON: 'Go back', + REPORT_ERROR_LINK: 'Report this error', + }; + + static backButton() { + return cy.contains(Onboarding.CONTENT.PREVIOUS_BUTTON); + } + + static saveAndContinueButton() { + return cy.contains(Onboarding.CONTENT.CONTINUE_BUTTON); + } + + static allCheckboxes() { + // @TODO: This is generic selector based approach, make a page specific user based approach, e.g. use contains + return cy.get('[type="checkbox"]'); + } + + static errorBanner() { + return cy.contains(Onboarding.CONTENT.REPORT_ERROR_LINK).parent(); + } + + static goto({ query = {} } = {}) { + const querystring = new URLSearchParams(query).toString(); + + cy.visit(`${this.URL}${querystring ? `?${querystring}` : ''}`, { + failOnStatusCode: false, + }); + } + + static gotoFlowStart({ failOnStatusCode = true, query = {} } = {}) { + const querystring = new URLSearchParams(query).toString(); + + cy.visit(`${Onboarding.URL}${querystring ? `?${querystring}` : ''}`, { + failOnStatusCode, + }); + } } export default Onboarding; diff --git a/cypress/support/pages/onboarding/review_page.ts b/cypress/support/pages/onboarding/review_page.ts index e5264ef22f..e7276c97ca 100644 --- a/cypress/support/pages/onboarding/review_page.ts +++ b/cypress/support/pages/onboarding/review_page.ts @@ -1,25 +1,25 @@ import OnboardingPage from './onboarding_page'; class ReviewPage extends OnboardingPage { - static URL = '/consents/review'; - static CONTENT = { - ...OnboardingPage.CONTENT, - NEWSLETTERS: { - GREEN_LIGHT: 'Green Light', - LONG_READ: 'The Long Read', - FIRST_EDITION_UK: 'First Edition', - }, - SUPPORTER_CONSENT: 'Supporting the Guardian', - PROFILING_CONSENT: 'Allow analysis of my data for marketing', - PERSONALISED_ADVERTISING_CONSENT: - 'Allow personalised advertising using my data - this supports the Guardian', - BUTTON_RETURN_GUARDIAN: 'Return to the Guardian', - NO_NEWSLETTERS_TITLE: 'Didn’t find anything you like?', - }; + static URL = '/consents/review'; + static CONTENT = { + ...OnboardingPage.CONTENT, + NEWSLETTERS: { + GREEN_LIGHT: 'Green Light', + LONG_READ: 'The Long Read', + FIRST_EDITION_UK: 'First Edition', + }, + SUPPORTER_CONSENT: 'Supporting the Guardian', + PROFILING_CONSENT: 'Allow analysis of my data for marketing', + PERSONALISED_ADVERTISING_CONSENT: + 'Allow personalised advertising using my data - this supports the Guardian', + BUTTON_RETURN_GUARDIAN: 'Return to the Guardian', + NO_NEWSLETTERS_TITLE: 'Didn’t find anything you like?', + }; - static returnButton() { - return cy.contains(ReviewPage.CONTENT.BUTTON_RETURN_GUARDIAN); - } + static returnButton() { + return cy.contains(ReviewPage.CONTENT.BUTTON_RETURN_GUARDIAN); + } } export default ReviewPage; diff --git a/cypress/support/pages/onboarding/your_data_page.ts b/cypress/support/pages/onboarding/your_data_page.ts index 968ee4f5a2..2e693e37b5 100644 --- a/cypress/support/pages/onboarding/your_data_page.ts +++ b/cypress/support/pages/onboarding/your_data_page.ts @@ -1,35 +1,35 @@ import OnboardingPage from './onboarding_page'; class YourDataPage extends OnboardingPage { - static URL = '/consents/data'; - static CONTENT = { - ...OnboardingPage.CONTENT, - OPT_IN_MESSAGE: - 'Allow the Guardian to analyse this data to improve marketing content', - ADVERTISING_MESSAGE: - 'Allow personalised advertising using this data - this supports the Guardian', - }; + static URL = '/consents/data'; + static CONTENT = { + ...OnboardingPage.CONTENT, + OPT_IN_MESSAGE: + 'Allow the Guardian to analyse this data to improve marketing content', + ADVERTISING_MESSAGE: + 'Allow personalised advertising using this data - this supports the Guardian', + }; - static marketingOptInClickableSection() { - return cy.contains(this.CONTENT.OPT_IN_MESSAGE); - } + static marketingOptInClickableSection() { + return cy.contains(this.CONTENT.OPT_IN_MESSAGE); + } - static marketingOptInSwitch() { - return cy.contains(this.CONTENT.OPT_IN_MESSAGE).find('input'); - } + static marketingOptInSwitch() { + return cy.contains(this.CONTENT.OPT_IN_MESSAGE).find('input'); + } - static personalisedAdvertisingOptIn() { - return cy.contains(this.CONTENT.ADVERTISING_MESSAGE); - } + static personalisedAdvertisingOptIn() { + return cy.contains(this.CONTENT.ADVERTISING_MESSAGE); + } - // use to verify checked/unchecked value - static personalisedAdvertisingOptInInput() { - return cy.contains(this.CONTENT.ADVERTISING_MESSAGE).find('input'); - } - // use to "click" because the checkbok input has no css visibility - static personalisedAdvertisingOptInSwitch() { - return cy.contains(this.CONTENT.ADVERTISING_MESSAGE).find('span'); - } + // use to verify checked/unchecked value + static personalisedAdvertisingOptInInput() { + return cy.contains(this.CONTENT.ADVERTISING_MESSAGE).find('input'); + } + // use to "click" because the checkbok input has no css visibility + static personalisedAdvertisingOptInSwitch() { + return cy.contains(this.CONTENT.ADVERTISING_MESSAGE).find('span'); + } } export default YourDataPage; diff --git a/cypress/support/pages/reset_password_page.ts b/cypress/support/pages/reset_password_page.ts index 5e8f363d29..0c0391e0a2 100644 --- a/cypress/support/pages/reset_password_page.ts +++ b/cypress/support/pages/reset_password_page.ts @@ -1,34 +1,34 @@ class ResetPasswordPage { - static URL = '/reset-password?useIdapi=true'; - static CONTENT = { - ERRORS: { - GENERIC: 'There was a problem setting your password, please try again.', - NO_ACCOUNT: - 'There is no account for that email address, please check for typos or create an account', - NO_EMAIL: 'Email field must not be blank.', - }, - }; + static URL = '/reset-password?useIdapi=true'; + static CONTENT = { + ERRORS: { + GENERIC: 'There was a problem setting your password, please try again.', + NO_ACCOUNT: + 'There is no account for that email address, please check for typos or create an account', + NO_EMAIL: 'Email field must not be blank.', + }, + }; - goto() { - cy.visit(ResetPasswordPage.URL); - } + goto() { + cy.visit(ResetPasswordPage.URL); + } - emailAddressField() { - return cy.get('input[name="email"]'); - } + emailAddressField() { + return cy.get('input[name="email"]'); + } - invalidEmailAddressField() { - return cy.get('input[name="email"]:invalid'); - } + invalidEmailAddressField() { + return cy.get('input[name="email"]:invalid'); + } - submitEmailAddress(email: string) { - this.emailAddressField().type(email); - this.clickResetPassword(); - } + submitEmailAddress(email: string) { + this.emailAddressField().type(email); + this.clickResetPassword(); + } - clickResetPassword() { - cy.contains('Reset password').click(); - } + clickResetPassword() { + cy.contains('Reset password').click(); + } } export default ResetPasswordPage; diff --git a/cypress/support/pages/verify_email.ts b/cypress/support/pages/verify_email.ts index 9012ae03b8..3cf08d8db4 100644 --- a/cypress/support/pages/verify_email.ts +++ b/cypress/support/pages/verify_email.ts @@ -1,30 +1,30 @@ class VerifyEmail { - static URL = '/verify-email'; + static URL = '/verify-email'; - static CONTENT = { - EMAIL_VERIFIED: 'Your email has been verified', - LINK_EXPIRED: 'Link Expired', - TOKEN_EXPIRED: 'Your email confirmation link has expired', - VERIFY_EMAIL: 'Verify Email', - CONFIRM_EMAIL: - 'You need to confirm your email address to continue securely:', - SEND_LINK: 'Send verification link', - EMAIL_SENT: 'Email Sent. Please check your inbox and follow the link.', - SIGN_IN: 'Sign in', - }; + static CONTENT = { + EMAIL_VERIFIED: 'Your email has been verified', + LINK_EXPIRED: 'Link Expired', + TOKEN_EXPIRED: 'Your email confirmation link has expired', + VERIFY_EMAIL: 'Verify Email', + CONFIRM_EMAIL: + 'You need to confirm your email address to continue securely:', + SEND_LINK: 'Send verification link', + EMAIL_SENT: 'Email Sent. Please check your inbox and follow the link.', + SIGN_IN: 'Sign in', + }; - goto(token: string, { failOnStatusCode = true, query = {} } = {}) { - const querystring = new URLSearchParams(query).toString(); + goto(token: string, { failOnStatusCode = true, query = {} } = {}) { + const querystring = new URLSearchParams(query).toString(); - cy.visit( - `${VerifyEmail.URL}${token ? `/${token}` : ''}${ - querystring ? `?${querystring}` : '' - }`, - { - failOnStatusCode, - }, - ); - } + cy.visit( + `${VerifyEmail.URL}${token ? `/${token}` : ''}${ + querystring ? `?${querystring}` : '' + }`, + { + failOnStatusCode, + }, + ); + } } export default VerifyEmail; diff --git a/cypress/support/types/ResponseFixture.ts b/cypress/support/types/ResponseFixture.ts index 2cfbe4e101..90f01dbc3b 100644 --- a/cypress/support/types/ResponseFixture.ts +++ b/cypress/support/types/ResponseFixture.ts @@ -1,6 +1,6 @@ type ResponseFixture = { - code: number; - response: object; + code: number; + response: object; }; export default ResponseFixture; diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json index a95a41f359..f1af4ce37c 100644 --- a/cypress/tsconfig.json +++ b/cypress/tsconfig.json @@ -1,19 +1,19 @@ { - "compilerOptions": { - "resolveJsonModule": true, - "module": "commonjs", - "target": "ES2019", - "lib": ["ES2019", "DOM"], - "noEmit": true, - // be explicit about types included - // to avoid clashing with Jest types - "types": ["node", "cypress", "@testing-library/cypress"], - "esModuleInterop": true, - "strict": true, - "skipLibCheck": true, - "paths": { - "@/*": ["../src/*"] - } - }, - "include": ["**/*.ts"] + "compilerOptions": { + "resolveJsonModule": true, + "module": "commonjs", + "target": "ES2019", + "lib": ["ES2019", "DOM"], + "noEmit": true, + // be explicit about types included + // to avoid clashing with Jest types + "types": ["node", "cypress", "@testing-library/cypress"], + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "paths": { + "@/*": ["../src/*"] + } + }, + "include": ["**/*.ts"] } diff --git a/docs/development.md b/docs/development.md index ef9d0704f1..f1992ee945 100644 --- a/docs/development.md +++ b/docs/development.md @@ -12,8 +12,8 @@ The [`renderer`](../src/server/lib/renderer.ts) method abstracts the rendering a // example of rendering a route // the url and pageTitle are both typed so new values must be added to the model before they will be accepted const html: string = renderer('/reset-password/email-sent', { - pageTitle: 'Check your inbox', - requestState: res.locals, + pageTitle: 'Check your inbox', + requestState: res.locals, }); // do stuff with the html markup @@ -182,14 +182,14 @@ import { ErrorSummary } from './ErrorSummary'; // export metadata about the component export default { - title: 'Components/ErrorSummary', - component: ErrorSummary, + title: 'Components/ErrorSummary', + component: ErrorSummary, } as ComponentMeta; // Define a template (optional) const Template: ComponentStory = ({ - error = 'There has been an error', - ...otherProps + error = 'There has been an error', + ...otherProps }) => ; // export a story (using template) @@ -203,7 +203,7 @@ Default.storyName = 'default'; // export another story export const WithErrorContext = Template.bind({}); WithErrorContext.args = { - context: "Here's some more information about this error", + context: "Here's some more information about this error", }; WithErrorContext.storyName = 'with errror context'; ``` @@ -212,8 +212,8 @@ Each story has to export a default metadata object with information on the title ```tsx export default { - title: 'Components/ErrorSummary', - component: ErrorSummary, + title: 'Components/ErrorSummary', + component: ErrorSummary, } as ComponentMeta; /** @@ -309,14 +309,14 @@ import useClientState from '@/client/lib/hooks/useClientState'; // export some react component export const TestComponent = () => { - // get the client state from the context - const clientState = useClientState(); - // extract the data we need from the state - const { test } = clientState; - - // use the test state - return

{test}

; - // renders

This is some test string!

+ // get the client state from the context + const clientState = useClientState(); + // extract the data we need from the state + const { test } = clientState; + + // use the test state + return

{test}

; + // renders

This is some test string!

}; ``` @@ -330,24 +330,24 @@ import useClientState from '@/client/lib/hooks/useClientState'; // export some react component export const TestContainer = () => { - // get the client state from the context - const clientState = useClientState(); - // extract the data we need from the state - const { test } = clientState; + // get the client state from the context + const clientState = useClientState(); + // extract the data we need from the state + const { test } = clientState; - // use the test state - return ; + // use the test state + return ; }; // presentation component import React from 'react'; interface Props { - foo: string; + foo: string; } export const TestComponent = ({ foo }: Props) => { - return

{foo}

; + return

{foo}

; }; ``` @@ -429,24 +429,24 @@ import { addQueryParamsToPath } from '@/shared/lib/queryParams'; // export some react component export const TestContainer = () => { - // get the client state from the context - const clientState = useClientState(); - // extract the queryParams we need from the state - const { queryParams } = clientState; - // extract the values we need from the queryParam - const { clientId, error } = queryParams; - - // turn all the query params into a query string (only PersistableQueryParams by default) - const queryString = addQueryParamsToPath('', queryParams); - - // pass these to our presentation components - return ( - - ); + // get the client state from the context + const clientState = useClientState(); + // extract the queryParams we need from the state + const { queryParams } = clientState; + // extract the values we need from the queryParam + const { clientId, error } = queryParams; + + // turn all the query params into a query string (only PersistableQueryParams by default) + const queryString = addQueryParamsToPath('', queryParams); + + // pass these to our presentation components + return ( + + ); }; ``` @@ -482,9 +482,9 @@ import { textSans, neutral } from '@guardian/source-foundations'; // style the tag using the css string literal const p = css` - color: ${neutral[100]}; - margin: 0; - ${textSans.small()}; + color: ${neutral[100]}; + margin: 0; + ${textSans.small()}; `; // example component with the css attribute to add the styling diff --git a/docs/rate-limit/token-bucket.md b/docs/rate-limit/token-bucket.md index 83f1043187..1278b3c7e8 100644 --- a/docs/rate-limit/token-bucket.md +++ b/docs/rate-limit/token-bucket.md @@ -23,9 +23,9 @@ Knowing this time delta and the rate at which new tokens are added, we can deriv * @param {number} maximumTimeBeforeTokenExpiry The maximum duration in seconds before the bucket is removed from Redis. */ export interface BucketConfiguration { - capacity: number; - addTokenMs: number; - maximumTimeBeforeTokenExpiry?: number; + capacity: number; + addTokenMs: number; + maximumTimeBeforeTokenExpiry?: number; } ``` @@ -107,15 +107,15 @@ Example: ```typescript await rateLimit({ - name: '/signin', - redisClient, - bucketConfiguration: { - globalBucket: { addTokenMs: 500, capacity: 5 }, - ipBucket: { addTokenMs: 500, capacity: 2 }, - }, - bucketValues: { - ip: '127.0.0.1', - }, + name: '/signin', + redisClient, + bucketConfiguration: { + globalBucket: { addTokenMs: 500, capacity: 5 }, + ipBucket: { addTokenMs: 500, capacity: 2 }, + }, + bucketValues: { + ip: '127.0.0.1', + }, }); ``` diff --git a/docs/setup.md b/docs/setup.md index 01517ed067..2ac256baf7 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -64,14 +64,14 @@ Here's a quick-start configuration that you can copy-paste into `.ratelimit.json ```json { - "enabled": true, - "settings": { - "logOnly": false, - "trackBucketCapacity": false - }, - "defaultBuckets": { - "globalBucket": { "capacity": 500, "addTokenMs": 50 } - } + "enabled": true, + "settings": { + "logOnly": false, + "trackBucketCapacity": false + }, + "defaultBuckets": { + "globalBucket": { "capacity": 500, "addTokenMs": 50 } + } } ``` diff --git a/jest.config.js b/jest.config.js index 5de1e2a8b8..1a9df04d2a 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,31 +1,31 @@ /* eslint-disable functional/immutable-data */ module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - clearMocks: false, - moduleNameMapper: { - '@/([^\\.]*)$': '/src/$1', - '\\.(jpg|ico|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': - '/__mocks__/fileMock.js', - // need to alias preact to itself in order to use the cjs version, same with testing-library - '^preact(/(.*)|$)': 'preact$1', - '^@testing-library/react$': '@testing-library/preact', - '^@testing-library/react-hooks$': '@testing-library/preact-hooks', - '^@testing-library/preact$': '@testing-library/preact', - '^@testing-library/preact-hooks$': '@testing-library/preact-hooks', - }, - testPathIgnorePatterns: [ - '/cypress/', - '/node_modules/', - 'utils', - '/src/server/lib/__tests__/sharedConfig.ts', - ], - transform: { - '': [ - 'ts-jest', - { - isolatedModules: true, - }, - ], - }, + preset: 'ts-jest', + testEnvironment: 'node', + clearMocks: false, + moduleNameMapper: { + '@/([^\\.]*)$': '/src/$1', + '\\.(jpg|ico|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': + '/__mocks__/fileMock.js', + // need to alias preact to itself in order to use the cjs version, same with testing-library + '^preact(/(.*)|$)': 'preact$1', + '^@testing-library/react$': '@testing-library/preact', + '^@testing-library/react-hooks$': '@testing-library/preact-hooks', + '^@testing-library/preact$': '@testing-library/preact', + '^@testing-library/preact-hooks$': '@testing-library/preact-hooks', + }, + testPathIgnorePatterns: [ + '/cypress/', + '/node_modules/', + 'utils', + '/src/server/lib/__tests__/sharedConfig.ts', + ], + transform: { + '': [ + 'ts-jest', + { + isolatedModules: true, + }, + ], + }, }; diff --git a/package.json b/package.json index d5e9844eec..6d6d086244 100644 --- a/package.json +++ b/package.json @@ -1,168 +1,169 @@ { - "name": "gateway", - "version": "1.0.0", - "author": "idenititydev@theguardian.com", - "license": "Apache-2.0", - "private": true, - "scripts": { - "build": "ENVIRONMENT=production webpack --mode production && yarn install --modules-folder ./build/node_modules --production --frozen-lockfile", - "build:analyze": "WEBPACK_ANALYZE_BUNDLE=true yarn build", - "cypress": "cypress", - "cypress:ci": "cypress run", - "cypress:ete": "./cypress-ete.sh", - "cypress:ete:okta": "./cypress-ete-okta.sh", - "e2e:server": "yarn mock-server & yarn start", - "lint": "eslint . --ext .ts,.tsx", - "lint:fix": "yarn lint --fix", - "mock-server": "node util/mock-server.js", - "riffraff": "node-riffraff-artifact", - "start": "node ./build/server.js", - "test": "yarn lint && yarn tsc && yarn test:unit", - "test:unit": "jest --ci", - "tsc": "tsc --noEmit --skipLibCheck", - "tsc:cypress": "tsc --noEmit --skipLibCheck --project cypress/tsconfig.json", - "watch:cypress": "yarn tsc:cypress --watch", - "watch:server": "mkdir -p ./build && touch ./build/server.js && nodemon --inspect ./build/server.js --watch ./build/server.js --watch .ratelimit.json", - "watch": "webpack --watch --config webpack.development.js", - "wait-on:server": "wait-on https://profile.thegulocal.com/healthcheck -t 60000", - "wait-on:mock-server": "wait-on http://localhost:9000/healthcheck -t 60000", - "storybook": "storybook dev -p 6006", - "build-storybook": "storybook build", - "prettier": "prettier . --check", - "prettier:fix": "prettier . --write", - "prepare": "husky install", - "clear:cache": "rm -rf ./node_modules/.cache", - "ts-prune": "ts-prune -i .stories.tsx", - "gen:okta-login": "tsc --project ./scripts/okta/tsconfig.json && webpack --config ./scripts/okta/webpack.config.js", - "gen:ratelimit:schema": "yarn ts-node --transpileOnly ./scripts/generate-rate-limit-schema.ts", - "postinstall": "yarn gen:ratelimit:schema" - }, - "devDependencies": { - "@babel/core": "^7.22.8", - "@babel/preset-env": "^7.22.7", - "@babel/preset-react": "^7.22.5", - "@babel/preset-typescript": "^7.22.5", - "@emotion/babel-preset-css-prop": "^11.11.0", - "@guardian/eslint-plugin-source-foundations": "^13.0.0", - "@guardian/eslint-plugin-source-react-components": "^17.0.0", - "@guardian/node-riffraff-artifact": "^0.3.2", - "@storybook/addon-actions": "^7.0.26", - "@storybook/addon-essentials": "^7.0.26", - "@storybook/addon-links": "^7.0.26", - "@storybook/addon-styling": "^1.3.2", - "@storybook/react": "^7.0.26", - "@storybook/react-webpack5": "^7.0.26", - "@swc/core": "^1.3.68", - "@swc/plugin-emotion": "^2.5.69", - "@testing-library/cypress": "^9.0.0", - "@testing-library/jest-dom": "^5.16.5", - "@testing-library/preact": "^3.2.3", - "@testing-library/preact-hooks": "^1.1.0", - "@testing-library/react": "npm:@testing-library/preact", - "@testing-library/react-hooks": "npm:@testing-library/preact-hooks", - "@types/compression": "^1.7.2", - "@types/cookie-parser": "^1.4.3", - "@types/csurf": "^1.11.2", - "@types/express": "^4.17.17", - "@types/ioredis-mock": "^8.2.2", - "@types/jest": "^29.5.2", - "@types/mjml": "^4.7.1", - "@types/ms": "^0.7.31", - "@types/node": "^20.4.1", - "@types/react": "^18.2.14", - "@types/react-dom": "^18.2.6", - "@types/serialize-javascript": "^5.0.2", - "@types/uuid": "^9.0.2", - "@typescript-eslint/eslint-plugin": "^5.61.0", - "@typescript-eslint/parser": "^5.61.0", - "assets-webpack-plugin": "^7.1.1", - "axe-core": "^4.7.2", - "chalk-rainbow": "^1.0.0", - "chromatic": "^6.19.9", - "copy-webpack-plugin": "^11.0.0", - "cypress": "12.17.0", - "cypress-axe": "^1.4.0", - "cypress-mailosaur": "2.13.0", - "dotenv-webpack": "^8.0.1", - "eslint": "^8.44.0", - "eslint-config-prettier": "^8.8.0", - "eslint-plugin-functional": "^5.0.8", - "eslint-plugin-jsx-a11y": "^6.7.1", - "eslint-plugin-prettier": "^5.0.0-alpha.2", - "eslint-plugin-react": "^7.32.2", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-storybook": "^0.6.12", - "file-loader": "^6.2.0", - "fork-ts-checker-notifier-webpack-plugin": "^7.0.0", - "fork-ts-checker-webpack-plugin": "^8.0.0", - "husky": "^8.0.3", - "image-webpack-loader": "^8.1.0", - "ioredis-mock": "^8.7.0", - "jest": "^29.6.1", - "jest-environment-jsdom": "^29.6.1", - "jest-mock": "^29.6.1", - "nodemon": "^3.0.1", - "prettier": "^3.0.0", - "react-docgen-typescript-plugin": "^1.0.5", - "require-from-string": "^2.0.2", - "snyk": "^1.1187.0", - "stmux": "^1.8.7", - "storybook": "^7.0.26", - "swc-loader": "^0.2.3", - "terser-webpack-plugin": "^5.3.9", - "ts-jest": "^29.1.1", - "ts-node": "^10.9.1", - "ts-prune": "^0.10.3", - "typescript": "^5.1.6", - "wait-on": "^7.0.1", - "webpack": "^5.88.1", - "webpack-bundle-analyzer": "^4.9.0", - "webpack-cli": "^5.1.4", - "webpack-merge": "^5.9.0", - "webpack-node-externals": "^3.0.0", - "zod-to-json-schema": "^3.21.3" - }, - "dependencies": { - "@aws-sdk/client-cloudwatch": "^3.363.0", - "@aws-sdk/client-kinesis": "^3.363.0", - "@aws-sdk/client-sesv2": "^3.363.0", - "@aws-sdk/credential-providers": "^3.363.0", - "@aws-sdk/node-http-handler": "^3.360.0", - "@emotion/react": "^11.11.1", - "@faire/mjml-react": "^3.3.0", - "@guardian/ab-core": "5.0.0", - "@guardian/ab-react": "4.0.1", - "@guardian/consent-management-platform": "^13.5.0", - "@guardian/libs": "^15.1.0", - "@guardian/source-foundations": "^12.0.0", - "@guardian/source-react-components": "^15.0.1", - "@guardian/source-react-components-development-kitchen": "^13.0.1", - "@okta/jwt-verifier": "^3.0.1", - "@sentry/browser": "^7.57.0", - "@types/supertest": "^2.0.12", - "awesome-debounce-promise": "^2.1.0", - "bowser": "^2.11.0", - "compression": "^1.7.4", - "cookie-parser": "^1.4.6", - "csurf": "^1.11.0", - "deepmerge": "^4.3.1", - "express": "^4.18.2", - "helmet": "^7.0.0", - "ioredis": "^5.3.2", - "mjml": "^4.14.1", - "mjml-browser": "^4.14.1", - "ms": "^2.1.3", - "openid-client": "^5.4.3", - "ophan-tracker-js": "^1.4.0", - "preact": "^10.16.0", - "preact-render-to-string": "^6.1.0", - "react": "npm:@preact/compat", - "react-dom": "npm:@preact/compat", - "serialize-javascript": "^6.0.1", - "supertest": "^6.3.3", - "tslib": "^2.6.0", - "winston": "^3.9.0", - "winston-transport": "^4.5.0", - "zod": "^3.21.4" - } + "name": "gateway", + "version": "1.0.0", + "author": "idenititydev@theguardian.com", + "license": "Apache-2.0", + "private": true, + "scripts": { + "build": "ENVIRONMENT=production webpack --mode production && yarn install --modules-folder ./build/node_modules --production --frozen-lockfile", + "build:analyze": "WEBPACK_ANALYZE_BUNDLE=true yarn build", + "cypress": "cypress", + "cypress:ci": "cypress run", + "cypress:ete": "./cypress-ete.sh", + "cypress:ete:okta": "./cypress-ete-okta.sh", + "e2e:server": "yarn mock-server & yarn start", + "lint": "eslint . --ext .ts,.tsx", + "lint:fix": "yarn lint --fix", + "mock-server": "node util/mock-server.js", + "riffraff": "node-riffraff-artifact", + "start": "node ./build/server.js", + "test": "yarn lint && yarn tsc && yarn test:unit", + "test:unit": "jest --ci", + "tsc": "tsc --noEmit --skipLibCheck", + "tsc:cypress": "tsc --noEmit --skipLibCheck --project cypress/tsconfig.json", + "watch:cypress": "yarn tsc:cypress --watch", + "watch:server": "mkdir -p ./build && touch ./build/server.js && nodemon --inspect ./build/server.js --watch ./build/server.js --watch .ratelimit.json", + "watch": "webpack --watch --config webpack.development.js", + "wait-on:server": "wait-on https://profile.thegulocal.com/healthcheck -t 60000", + "wait-on:mock-server": "wait-on http://localhost:9000/healthcheck -t 60000", + "storybook": "storybook dev -p 6006", + "build-storybook": "storybook build", + "prettier": "prettier . --check", + "prettier:fix": "prettier . --write", + "prepare": "husky install", + "clear:cache": "rm -rf ./node_modules/.cache", + "ts-prune": "ts-prune -i .stories.tsx", + "gen:okta-login": "tsc --project ./scripts/okta/tsconfig.json && webpack --config ./scripts/okta/webpack.config.js", + "gen:ratelimit:schema": "yarn ts-node --transpileOnly ./scripts/generate-rate-limit-schema.ts", + "postinstall": "yarn gen:ratelimit:schema" + }, + "devDependencies": { + "@babel/core": "^7.22.8", + "@babel/preset-env": "^7.22.7", + "@babel/preset-react": "^7.22.5", + "@babel/preset-typescript": "^7.22.5", + "@emotion/babel-preset-css-prop": "^11.11.0", + "@guardian/eslint-plugin-source-foundations": "^13.0.0", + "@guardian/eslint-plugin-source-react-components": "^17.0.0", + "@guardian/node-riffraff-artifact": "^0.3.2", + "@guardian/prettier": "^4.0.0", + "@storybook/addon-actions": "^7.0.26", + "@storybook/addon-essentials": "^7.0.26", + "@storybook/addon-links": "^7.0.26", + "@storybook/addon-styling": "^1.3.2", + "@storybook/react": "^7.0.26", + "@storybook/react-webpack5": "^7.0.26", + "@swc/core": "^1.3.68", + "@swc/plugin-emotion": "^2.5.69", + "@testing-library/cypress": "^9.0.0", + "@testing-library/jest-dom": "^5.16.5", + "@testing-library/preact": "^3.2.3", + "@testing-library/preact-hooks": "^1.1.0", + "@testing-library/react": "npm:@testing-library/preact", + "@testing-library/react-hooks": "npm:@testing-library/preact-hooks", + "@types/compression": "^1.7.2", + "@types/cookie-parser": "^1.4.3", + "@types/csurf": "^1.11.2", + "@types/express": "^4.17.17", + "@types/ioredis-mock": "^8.2.2", + "@types/jest": "^29.5.2", + "@types/mjml": "^4.7.1", + "@types/ms": "^0.7.31", + "@types/node": "^20.4.1", + "@types/react": "^18.2.14", + "@types/react-dom": "^18.2.6", + "@types/serialize-javascript": "^5.0.2", + "@types/uuid": "^9.0.2", + "@typescript-eslint/eslint-plugin": "^5.61.0", + "@typescript-eslint/parser": "^5.61.0", + "assets-webpack-plugin": "^7.1.1", + "axe-core": "^4.7.2", + "chalk-rainbow": "^1.0.0", + "chromatic": "^6.19.9", + "copy-webpack-plugin": "^11.0.0", + "cypress": "12.17.0", + "cypress-axe": "^1.4.0", + "cypress-mailosaur": "2.13.0", + "dotenv-webpack": "^8.0.1", + "eslint": "^8.44.0", + "eslint-config-prettier": "^8.8.0", + "eslint-plugin-functional": "^5.0.8", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-prettier": "^5.0.0-alpha.2", + "eslint-plugin-react": "^7.32.2", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-storybook": "^0.6.12", + "file-loader": "^6.2.0", + "fork-ts-checker-notifier-webpack-plugin": "^7.0.0", + "fork-ts-checker-webpack-plugin": "^8.0.0", + "husky": "^8.0.3", + "image-webpack-loader": "^8.1.0", + "ioredis-mock": "^8.7.0", + "jest": "^29.6.1", + "jest-environment-jsdom": "^29.6.1", + "jest-mock": "^29.6.1", + "nodemon": "^3.0.1", + "prettier": "^3.0.0", + "react-docgen-typescript-plugin": "^1.0.5", + "require-from-string": "^2.0.2", + "snyk": "^1.1187.0", + "stmux": "^1.8.7", + "storybook": "^7.0.26", + "swc-loader": "^0.2.3", + "terser-webpack-plugin": "^5.3.9", + "ts-jest": "^29.1.1", + "ts-node": "^10.9.1", + "ts-prune": "^0.10.3", + "typescript": "^5.1.6", + "wait-on": "^7.0.1", + "webpack": "^5.88.1", + "webpack-bundle-analyzer": "^4.9.0", + "webpack-cli": "^5.1.4", + "webpack-merge": "^5.9.0", + "webpack-node-externals": "^3.0.0", + "zod-to-json-schema": "^3.21.3" + }, + "dependencies": { + "@aws-sdk/client-cloudwatch": "^3.363.0", + "@aws-sdk/client-kinesis": "^3.363.0", + "@aws-sdk/client-sesv2": "^3.363.0", + "@aws-sdk/credential-providers": "^3.363.0", + "@aws-sdk/node-http-handler": "^3.360.0", + "@emotion/react": "^11.11.1", + "@faire/mjml-react": "^3.3.0", + "@guardian/ab-core": "5.0.0", + "@guardian/ab-react": "4.0.1", + "@guardian/consent-management-platform": "^13.5.0", + "@guardian/libs": "^15.1.0", + "@guardian/source-foundations": "^12.0.0", + "@guardian/source-react-components": "^15.0.1", + "@guardian/source-react-components-development-kitchen": "^13.0.1", + "@okta/jwt-verifier": "^3.0.1", + "@sentry/browser": "^7.57.0", + "@types/supertest": "^2.0.12", + "awesome-debounce-promise": "^2.1.0", + "bowser": "^2.11.0", + "compression": "^1.7.4", + "cookie-parser": "^1.4.6", + "csurf": "^1.11.0", + "deepmerge": "^4.3.1", + "express": "^4.18.2", + "helmet": "^7.0.0", + "ioredis": "^5.3.2", + "mjml": "^4.14.1", + "mjml-browser": "^4.14.1", + "ms": "^2.1.3", + "openid-client": "^5.4.3", + "ophan-tracker-js": "^1.4.0", + "preact": "^10.16.0", + "preact-render-to-string": "^6.1.0", + "react": "npm:@preact/compat", + "react-dom": "npm:@preact/compat", + "serialize-javascript": "^6.0.1", + "supertest": "^6.3.3", + "tslib": "^2.6.0", + "winston": "^3.9.0", + "winston-transport": "^4.5.0", + "zod": "^3.21.4" + } } diff --git a/scripts/banner.js b/scripts/banner.js index b62be8dcf2..90ded458d7 100644 --- a/scripts/banner.js +++ b/scripts/banner.js @@ -17,7 +17,7 @@ const banner = ` `.split('\n'); banner.forEach((row) => { - console.log(chalkRainbow(' ' + row.replace('$msg', messages))); + console.log(chalkRainbow(' ' + row.replace('$msg', messages))); }); //source -> http://www.asciiartfarts.com/ diff --git a/scripts/check-node.js b/scripts/check-node.js index d39470dafe..4dead226e5 100644 --- a/scripts/check-node.js +++ b/scripts/check-node.js @@ -5,34 +5,34 @@ const readFile = promisify(require('fs').readFile); const ensure = require('./ensure'); (async () => { - try { - const [semver] = await ensure('semver'); + try { + const [semver] = await ensure('semver'); - const nodeVersion = process.version.match(/^v(\d+\.\d+\.\d+)/)[1]; - const nvmrcVersion = ( - await readFile(join(__dirname, '..', '.nvmrc'), 'utf8') - ).trim(); + const nodeVersion = process.version.match(/^v(\d+\.\d+\.\d+)/)[1]; + const nvmrcVersion = ( + await readFile(join(__dirname, '..', '.nvmrc'), 'utf8') + ).trim(); - if (!semver.satisfies(nodeVersion, nvmrcVersion)) { - const { warn, prompt, log } = require('./log'); - warn( - `gateway requires Node v${nvmrcVersion}`, - `You are using v${nodeVersion}`, - ); - if (process.env.NVM_DIR) { - prompt('Run `nvm install` and try again.'); - log('See also: https://git.io/vKTnK'); - } else { - prompt( - `NVM can make managing Node versions a lot easier:`, - 'https://github.com/creationix/nvm', - ); - } - process.exit(1); - } - } catch (e) { - // eslint-disable-next-line no-console - console.log(e); - process.exit(1); - } + if (!semver.satisfies(nodeVersion, nvmrcVersion)) { + const { warn, prompt, log } = require('./log'); + warn( + `gateway requires Node v${nvmrcVersion}`, + `You are using v${nodeVersion}`, + ); + if (process.env.NVM_DIR) { + prompt('Run `nvm install` and try again.'); + log('See also: https://git.io/vKTnK'); + } else { + prompt( + `NVM can make managing Node versions a lot easier:`, + 'https://github.com/creationix/nvm', + ); + } + process.exit(1); + } + } catch (e) { + // eslint-disable-next-line no-console + console.log(e); + process.exit(1); + } })(); diff --git a/scripts/check-yarn.js b/scripts/check-yarn.js index eb3e6987e0..1aeee26b6a 100644 --- a/scripts/check-yarn.js +++ b/scripts/check-yarn.js @@ -7,26 +7,26 @@ const ensure = require('./ensure'); const YARN_MIN_VERSION = '1.x'; (async () => { - try { - // This will fail if yarn isn't installed, and force into the catch, - // where we install yarn with NPM (mainly for CI) - const { stdout: version } = await exec('yarn', ['--version']); + try { + // This will fail if yarn isn't installed, and force into the catch, + // where we install yarn with NPM (mainly for CI) + const { stdout: version } = await exec('yarn', ['--version']); - const [semver] = await ensure('semver'); + const [semver] = await ensure('semver'); - if (!semver.satisfies(version, YARN_MIN_VERSION)) { - const { warn, prompt, log } = require('./log'); - warn( - `gateway requires Yarn >=${YARN_MIN_VERSION}`, - `You are using v${version}`, - ); - prompt('Please install yarn 1.x'); - log('https://classic.yarnpkg.com/en/docs/install'); + if (!semver.satisfies(version, YARN_MIN_VERSION)) { + const { warn, prompt, log } = require('./log'); + warn( + `gateway requires Yarn >=${YARN_MIN_VERSION}`, + `You are using v${version}`, + ); + prompt('Please install yarn 1.x'); + log('https://classic.yarnpkg.com/en/docs/install'); - process.exit(1); - } - } catch (e) { - require('./log').log(`Installing yarn`); - await exec('npm', ['i', '-g', `yarn@${YARN_MIN_VERSION}`]); - } + process.exit(1); + } + } catch (e) { + require('./log').log(`Installing yarn`); + await exec('npm', ['i', '-g', `yarn@${YARN_MIN_VERSION}`]); + } })(); diff --git a/scripts/ensure.js b/scripts/ensure.js index 8de643313b..c6ca6c5114 100644 --- a/scripts/ensure.js +++ b/scripts/ensure.js @@ -5,24 +5,24 @@ const { log } = require('./log'); module.exports = (...packages) => - new Promise((resolve) => { - try { - resolve(packages.map(require)); - } catch (e) { - log(`Pre-installing dependency (${packages.join(', ')})...`); - require('child_process') - .spawn('npm', ['i', ...packages, '--no-save']) - .on('close', (code) => { - if (code !== 0) { - process.exit(code); - } - try { - resolve(packages.map(require)); - } catch (e2) { - // eslint-disable-next-line no-console - console.log(e2); - process.exit(1); - } - }); - } - }); + new Promise((resolve) => { + try { + resolve(packages.map(require)); + } catch (e) { + log(`Pre-installing dependency (${packages.join(', ')})...`); + require('child_process') + .spawn('npm', ['i', ...packages, '--no-save']) + .on('close', (code) => { + if (code !== 0) { + process.exit(code); + } + try { + resolve(packages.map(require)); + } catch (e2) { + // eslint-disable-next-line no-console + console.log(e2); + process.exit(1); + } + }); + } + }); diff --git a/scripts/generate-rate-limit-schema.ts b/scripts/generate-rate-limit-schema.ts index 3497134742..6ff4cf362b 100644 --- a/scripts/generate-rate-limit-schema.ts +++ b/scripts/generate-rate-limit-schema.ts @@ -6,13 +6,13 @@ process.stdout.write('✨ Generating rate limit configuration schema ✨\n'); // Generate a JSON schema for the rate limiter configuration. const jsonSchema = zodToJsonSchema( - rateLimiterConfigurationSchema, - 'rateLimiterConfigurationSchema', + rateLimiterConfigurationSchema, + 'rateLimiterConfigurationSchema', ); // Write the pretty printed JSON schema to .ratelimit.schema.json. // We use this for autocompletion when editing the rate limit configuration file (and example). writeFileSync( - '.ratelimit.schema.json', - JSON.stringify(jsonSchema, null, 2) + '\n', + '.ratelimit.schema.json', + JSON.stringify(jsonSchema, null, 2) + '\n', ); diff --git a/scripts/log.js b/scripts/log.js index db85a25e0b..dba4d2e5bb 100644 --- a/scripts/log.js +++ b/scripts/log.js @@ -1,7 +1,7 @@ // BRANDED LOGGING const capitalize = (str) => - str.replace(/^([a-z])/, (match) => match.toUpperCase()); + str.replace(/^([a-z])/, (match) => match.toUpperCase()); // we could use chalk, but this saves needing to pre-install it // if this is a first run @@ -11,8 +11,8 @@ const dim = '\x1b[2m'; const reset = '\x1b[0m'; const logIt = (messages = [], color = dim) => { - // eslint-disable-next-line no-console - console.log(`${color}%s${reset}`, capitalize(messages.join('\n'))); + // eslint-disable-next-line no-console + console.log(`${color}%s${reset}`, capitalize(messages.join('\n'))); }; const log = (...messages) => logIt(messages); @@ -22,22 +22,22 @@ const prompt = (...messages) => logIt(messages, yellow); // can be used as a normal script too const [, , messages, method] = process.argv; if (messages) { - switch (method) { - case 'warn': - warn(messages); - break; - case 'prompt': - prompt(messages); - break; - default: - log(messages); - break; - } + switch (method) { + case 'warn': + warn(messages); + break; + case 'prompt': + prompt(messages); + break; + default: + log(messages); + break; + } } // exports for modules to use module.exports = { - log, - warn, - prompt, + log, + warn, + prompt, }; diff --git a/scripts/okta/__tests__/getClientId.test.ts b/scripts/okta/__tests__/getClientId.test.ts index 5c7eb8b3fb..3c36d10baa 100644 --- a/scripts/okta/__tests__/getClientId.test.ts +++ b/scripts/okta/__tests__/getClientId.test.ts @@ -1,19 +1,19 @@ import { getClientId } from '../lib/helper'; describe('getClientId', () => { - it('should return undefined if no context is passed', () => { - expect(getClientId()).toBeUndefined(); - }); + it('should return undefined if no context is passed', () => { + expect(getClientId()).toBeUndefined(); + }); - it('should return undefined if no target is passed', () => { - expect(getClientId({})).toBeUndefined(); - }); + it('should return undefined if no target is passed', () => { + expect(getClientId({})).toBeUndefined(); + }); - it('should return undefined if no clientId is passed', () => { - expect(getClientId({ target: {} })).toBeUndefined(); - }); + it('should return undefined if no clientId is passed', () => { + expect(getClientId({ target: {} })).toBeUndefined(); + }); - it('should return the clientId if it is passed', () => { - expect(getClientId({ target: { clientId: 'test' } })).toEqual('test'); - }); + it('should return the clientId if it is passed', () => { + expect(getClientId({ target: { clientId: 'test' } })).toEqual('test'); + }); }); diff --git a/scripts/okta/__tests__/getRedirectUrl.test.ts b/scripts/okta/__tests__/getRedirectUrl.test.ts index ef7ffaea49..8f03c85ec1 100644 --- a/scripts/okta/__tests__/getRedirectUrl.test.ts +++ b/scripts/okta/__tests__/getRedirectUrl.test.ts @@ -1,195 +1,195 @@ import { getRedirectUrl } from '../lib/helper'; describe('getRedirectUrl', () => { - it('should return /signin if no valid params passed', () => { - expect(getRedirectUrl('', '', '', {})).toBe(`/signin?`); - }); + it('should return /signin if no valid params passed', () => { + expect(getRedirectUrl('', '', '', {})).toBe(`/signin?`); + }); - it('should return /welcome/:token if no valid params passed but the activation_token', () => { - expect(getRedirectUrl('?activation_token=123', '', '', {})).toBe( - `/welcome/123?`, - ); - }); + it('should return /welcome/:token if no valid params passed but the activation_token', () => { + expect(getRedirectUrl('?activation_token=123', '', '', {})).toBe( + `/welcome/123?`, + ); + }); - it('should return /signin with valid query params', () => { - expect( - getRedirectUrl('', '', '', { - getRequestContext: () => ({ - target: { - clientId: 'test123', - label: 'testabc', - }, - }), - getSignInWidgetConfig: () => ({ - relayState: '/testFromURI', - }), - }), - ).toBe('/signin?fromURI=%2FtestFromURI&appClientId=test123'); - }); + it('should return /signin with valid query params', () => { + expect( + getRedirectUrl('', '', '', { + getRequestContext: () => ({ + target: { + clientId: 'test123', + label: 'testabc', + }, + }), + getSignInWidgetConfig: () => ({ + relayState: '/testFromURI', + }), + }), + ).toBe('/signin?fromURI=%2FtestFromURI&appClientId=test123'); + }); - it('should return /welcome/:token with valid query params and activation_token', () => { - expect( - getRedirectUrl('?activation_token=123', '', '', { - getRequestContext: () => ({ - target: { - clientId: 'test123', - label: 'testabc', - }, - }), - getSignInWidgetConfig: () => ({ - relayState: '/testFromURI', - }), - }), - ).toBe('/welcome/123?fromURI=%2FtestFromURI&appClientId=test123'); - }); + it('should return /welcome/:token with valid query params and activation_token', () => { + expect( + getRedirectUrl('?activation_token=123', '', '', { + getRequestContext: () => ({ + target: { + clientId: 'test123', + label: 'testabc', + }, + }), + getSignInWidgetConfig: () => ({ + relayState: '/testFromURI', + }), + }), + ).toBe('/welcome/123?fromURI=%2FtestFromURI&appClientId=test123'); + }); - it('should return /signin with valid query params and third party return url and third party client id', () => { - expect( - getRedirectUrl('', 'https://profile.theguardian.com', '', { - getRequestContext: () => ({ - target: { - clientId: 'test123', - label: 'jobs_site', - }, - }), - getSignInWidgetConfig: () => ({ - relayState: '/testFromURI', - }), - }), - ).toBe( - '/signin?fromURI=%2FtestFromURI&appClientId=test123&clientId=jobs&returnUrl=https%253A%252F%252Fjobs.theguardian.com', - ); - }); + it('should return /signin with valid query params and third party return url and third party client id', () => { + expect( + getRedirectUrl('', 'https://profile.theguardian.com', '', { + getRequestContext: () => ({ + target: { + clientId: 'test123', + label: 'jobs_site', + }, + }), + getSignInWidgetConfig: () => ({ + relayState: '/testFromURI', + }), + }), + ).toBe( + '/signin?fromURI=%2FtestFromURI&appClientId=test123&clientId=jobs&returnUrl=https%253A%252F%252Fjobs.theguardian.com', + ); + }); - it('should return /welcome/:token with valid query params and third party return url and third party client id', () => { - expect( - getRedirectUrl( - '?activation_token=123', - 'https://profile.theguardian.com', - '', - { - getRequestContext: () => ({ - target: { - clientId: 'test123', - label: 'jobs_site', - }, - }), - getSignInWidgetConfig: () => ({ - relayState: '/testFromURI', - }), - }, - ), - ).toBe( - '/welcome/123?fromURI=%2FtestFromURI&appClientId=test123&clientId=jobs&returnUrl=https%253A%252F%252Fjobs.theguardian.com', - ); - }); + it('should return /welcome/:token with valid query params and third party return url and third party client id', () => { + expect( + getRedirectUrl( + '?activation_token=123', + 'https://profile.theguardian.com', + '', + { + getRequestContext: () => ({ + target: { + clientId: 'test123', + label: 'jobs_site', + }, + }), + getSignInWidgetConfig: () => ({ + relayState: '/testFromURI', + }), + }, + ), + ).toBe( + '/welcome/123?fromURI=%2FtestFromURI&appClientId=test123&clientId=jobs&returnUrl=https%253A%252F%252Fjobs.theguardian.com', + ); + }); - it('should return /reset-password/:token with valid query params and third party return url and third party client id', () => { - expect( - getRedirectUrl( - '?reset_password_token=123', - 'https://profile.theguardian.com', - '', - { - getRequestContext: () => ({ - target: { - clientId: 'test123', - label: 'jobs_site', - }, - }), - getSignInWidgetConfig: () => ({ - relayState: '/testFromURI', - }), - }, - ), - ).toBe( - '/reset-password/123?fromURI=%2FtestFromURI&appClientId=test123&clientId=jobs&returnUrl=https%253A%252F%252Fjobs.theguardian.com', - ); - }); + it('should return /reset-password/:token with valid query params and third party return url and third party client id', () => { + expect( + getRedirectUrl( + '?reset_password_token=123', + 'https://profile.theguardian.com', + '', + { + getRequestContext: () => ({ + target: { + clientId: 'test123', + label: 'jobs_site', + }, + }), + getSignInWidgetConfig: () => ({ + relayState: '/testFromURI', + }), + }, + ), + ).toBe( + '/reset-password/123?fromURI=%2FtestFromURI&appClientId=test123&clientId=jobs&returnUrl=https%253A%252F%252Fjobs.theguardian.com', + ); + }); - it('should return /set-password/:token with valid query params and third party return url and third party client id', () => { - expect( - getRedirectUrl( - '?set_password_token=123', - 'https://profile.theguardian.com', - '', - { - getRequestContext: () => ({ - target: { - clientId: 'test123', - label: 'jobs_site', - }, - }), - getSignInWidgetConfig: () => ({ - relayState: '/testFromURI', - }), - }, - ), - ).toBe( - '/set-password/123?fromURI=%2FtestFromURI&appClientId=test123&clientId=jobs&returnUrl=https%253A%252F%252Fjobs.theguardian.com', - ); - }); + it('should return /set-password/:token with valid query params and third party return url and third party client id', () => { + expect( + getRedirectUrl( + '?set_password_token=123', + 'https://profile.theguardian.com', + '', + { + getRequestContext: () => ({ + target: { + clientId: 'test123', + label: 'jobs_site', + }, + }), + getSignInWidgetConfig: () => ({ + relayState: '/testFromURI', + }), + }, + ), + ).toBe( + '/set-password/123?fromURI=%2FtestFromURI&appClientId=test123&clientId=jobs&returnUrl=https%253A%252F%252Fjobs.theguardian.com', + ); + }); - it('should return /signin only with clientId if force_fallback is set', () => { - expect( - getRedirectUrl( - '?force_fallback=true&client_id=test123', - 'https://profile.theguardian.com', - '', - { - getRequestContext: () => ({ - target: { - clientId: 'test123', - label: 'jobs_site', - }, - }), - getSignInWidgetConfig: () => ({ - relayState: '/testFromURI', - }), - }, - ), - ).toBe('/signin?appClientId=test123'); - }); + it('should return /signin only with clientId if force_fallback is set', () => { + expect( + getRedirectUrl( + '?force_fallback=true&client_id=test123', + 'https://profile.theguardian.com', + '', + { + getRequestContext: () => ({ + target: { + clientId: 'test123', + label: 'jobs_site', + }, + }), + getSignInWidgetConfig: () => ({ + relayState: '/testFromURI', + }), + }, + ), + ).toBe('/signin?appClientId=test123'); + }); - it('should return /welcome/:token only with clientId if force_fallback is set', () => { - expect( - getRedirectUrl( - '?force_fallback=true&client_id=test123&activation_token=123', - 'https://profile.theguardian.com', - '', - { - getRequestContext: () => ({ - target: { - clientId: 'test123', - label: 'jobs_site', - }, - }), - getSignInWidgetConfig: () => ({ - relayState: '/testFromURI', - }), - }, - ), - ).toBe('/welcome/123?appClientId=test123'); - }); + it('should return /welcome/:token only with clientId if force_fallback is set', () => { + expect( + getRedirectUrl( + '?force_fallback=true&client_id=test123&activation_token=123', + 'https://profile.theguardian.com', + '', + { + getRequestContext: () => ({ + target: { + clientId: 'test123', + label: 'jobs_site', + }, + }), + getSignInWidgetConfig: () => ({ + relayState: '/testFromURI', + }), + }, + ), + ).toBe('/welcome/123?appClientId=test123'); + }); - it('should get fromURI from location and queryparams if path starts with /oauth2/ and fromURI is not in the okta config', () => { - expect( - getRedirectUrl( - '?client_id=test123&prompt=login', - 'https://profile.theguardian.com', - '/oauth2/v1/authorize', - { - getRequestContext: () => ({ - target: { - clientId: 'test123', - label: 'jobs_site', - }, - }), - getSignInWidgetConfig: () => ({}), - }, - ), - ).toBe( - '/signin?fromURI=%2Foauth2%2Fv1%2Fauthorize%3Fclient_id%3Dtest123&appClientId=test123&clientId=jobs&returnUrl=https%253A%252F%252Fjobs.theguardian.com', - ); - }); + it('should get fromURI from location and queryparams if path starts with /oauth2/ and fromURI is not in the okta config', () => { + expect( + getRedirectUrl( + '?client_id=test123&prompt=login', + 'https://profile.theguardian.com', + '/oauth2/v1/authorize', + { + getRequestContext: () => ({ + target: { + clientId: 'test123', + label: 'jobs_site', + }, + }), + getSignInWidgetConfig: () => ({}), + }, + ), + ).toBe( + '/signin?fromURI=%2Foauth2%2Fv1%2Fauthorize%3Fclient_id%3Dtest123&appClientId=test123&clientId=jobs&returnUrl=https%253A%252F%252Fjobs.theguardian.com', + ); + }); }); diff --git a/scripts/okta/__tests__/getRelayState.test.ts b/scripts/okta/__tests__/getRelayState.test.ts index f8ffb5b2fd..a2dda7cc96 100644 --- a/scripts/okta/__tests__/getRelayState.test.ts +++ b/scripts/okta/__tests__/getRelayState.test.ts @@ -1,15 +1,15 @@ import { getRelayState } from '../lib/helper'; describe('getRelayState', () => { - it('should return undefined if no config is passed', () => { - expect(getRelayState()).toBeUndefined(); - }); + it('should return undefined if no config is passed', () => { + expect(getRelayState()).toBeUndefined(); + }); - it('should return undefined if no relayState is passed', () => { - expect(getRelayState({})).toBeUndefined(); - }); + it('should return undefined if no relayState is passed', () => { + expect(getRelayState({})).toBeUndefined(); + }); - it('should return the relayState if it is passed', () => { - expect(getRelayState({ relayState: 'test' })).toEqual('test'); - }); + it('should return the relayState if it is passed', () => { + expect(getRelayState({ relayState: 'test' })).toEqual('test'); + }); }); diff --git a/scripts/okta/__tests__/getThirdPartyClientId.test.ts b/scripts/okta/__tests__/getThirdPartyClientId.test.ts index 122b925cac..126ec434d9 100644 --- a/scripts/okta/__tests__/getThirdPartyClientId.test.ts +++ b/scripts/okta/__tests__/getThirdPartyClientId.test.ts @@ -1,21 +1,21 @@ import { getThirdPartyClientId } from '../lib/helper'; describe('getThirdPartyClientId', () => { - it('should return undefined if no context is passed', () => { - expect(getThirdPartyClientId()).toBeUndefined(); - }); + it('should return undefined if no context is passed', () => { + expect(getThirdPartyClientId()).toBeUndefined(); + }); - it('should return undefined if no target is passed', () => { - expect(getThirdPartyClientId({})).toBeUndefined(); - }); + it('should return undefined if no target is passed', () => { + expect(getThirdPartyClientId({})).toBeUndefined(); + }); - it('should return undefined if no label is passed', () => { - expect(getThirdPartyClientId({ target: {} })).toBeUndefined(); - }); + it('should return undefined if no label is passed', () => { + expect(getThirdPartyClientId({ target: {} })).toBeUndefined(); + }); - it('should return the third party client id if it is passed', () => { - expect(getThirdPartyClientId({ target: { label: 'jobs_site' } })).toEqual( - 'jobs', - ); - }); + it('should return the third party client id if it is passed', () => { + expect(getThirdPartyClientId({ target: { label: 'jobs_site' } })).toEqual( + 'jobs', + ); + }); }); diff --git a/scripts/okta/__tests__/getThirdPartyReturnUrl.test.ts b/scripts/okta/__tests__/getThirdPartyReturnUrl.test.ts index 680eaf1488..2abdbe6e99 100644 --- a/scripts/okta/__tests__/getThirdPartyReturnUrl.test.ts +++ b/scripts/okta/__tests__/getThirdPartyReturnUrl.test.ts @@ -1,29 +1,29 @@ import { getThirdPartyReturnUrl } from '../lib/helper'; describe('getThirdPartyReturnUrl', () => { - it('should return undefined if no context is passed', () => { - expect( - getThirdPartyReturnUrl('https://profile.theguardian.com'), - ).toBeUndefined(); - }); + it('should return undefined if no context is passed', () => { + expect( + getThirdPartyReturnUrl('https://profile.theguardian.com'), + ).toBeUndefined(); + }); - it('should return undefined if no target is passed', () => { - expect( - getThirdPartyReturnUrl('https://profile.theguardian.com', {}), - ).toBeUndefined(); - }); + it('should return undefined if no target is passed', () => { + expect( + getThirdPartyReturnUrl('https://profile.theguardian.com', {}), + ).toBeUndefined(); + }); - it('should return undefined if no label is passed', () => { - expect( - getThirdPartyReturnUrl('https://profile.theguardian.com', { target: {} }), - ).toBeUndefined(); - }); + it('should return undefined if no label is passed', () => { + expect( + getThirdPartyReturnUrl('https://profile.theguardian.com', { target: {} }), + ).toBeUndefined(); + }); - it('should return the third party return url if it is passed', () => { - expect( - getThirdPartyReturnUrl('https://profile.theguardian.com', { - target: { label: 'jobs_site' }, - }), - ).toEqual('https%3A%2F%2Fjobs.theguardian.com'); - }); + it('should return the third party return url if it is passed', () => { + expect( + getThirdPartyReturnUrl('https://profile.theguardian.com', { + target: { label: 'jobs_site' }, + }), + ).toEqual('https%3A%2F%2Fjobs.theguardian.com'); + }); }); diff --git a/scripts/okta/lib/helper.ts b/scripts/okta/lib/helper.ts index 5ae6421265..72e338a987 100644 --- a/scripts/okta/lib/helper.ts +++ b/scripts/okta/lib/helper.ts @@ -17,7 +17,7 @@ * You can view more options by console logging the `window.OktaUtil.getSignInWidgetConfig()` object if needed on the Okta hosted sign in page. */ interface SignInWidgetConfig { - relayState?: string; + relayState?: string; } /** @@ -34,10 +34,10 @@ interface SignInWidgetConfig { * You can view more options by console logging the `window.OktaUtil.getRequestContext()` object if needed on the Okta hosted sign in page. */ interface RequestContext { - target?: { - clientId?: string; - label?: string; - }; + target?: { + clientId?: string; + label?: string; + }; } /** @@ -51,8 +51,8 @@ interface RequestContext { * You can view more options by console logging the `window.OktaUtil` object if needed on the Okta hosted sign in page. */ export interface OktaUtil { - getSignInWidgetConfig?: () => SignInWidgetConfig; - getRequestContext?: () => RequestContext; + getSignInWidgetConfig?: () => SignInWidgetConfig; + getRequestContext?: () => RequestContext; } /** @@ -63,9 +63,9 @@ export interface OktaUtil { * @returns string | undefined */ export const getRelayState = ( - signInWidgetConfig?: SignInWidgetConfig, + signInWidgetConfig?: SignInWidgetConfig, ): string | undefined => { - return signInWidgetConfig?.relayState; + return signInWidgetConfig?.relayState; }; /** @@ -76,9 +76,9 @@ export const getRelayState = ( * @returns string | undefined */ export const getClientId = ( - requestContext?: RequestContext, + requestContext?: RequestContext, ): string | undefined => { - return requestContext?.target?.clientId; + return requestContext?.target?.clientId; }; /** @@ -89,14 +89,14 @@ export const getClientId = ( * @returns string | undefined */ export const getThirdPartyClientId = ( - requestContext?: RequestContext, + requestContext?: RequestContext, ): string | undefined => { - if (requestContext?.target?.label) { - switch (requestContext?.target?.label) { - case 'jobs_site': - return 'jobs'; - } - } + if (requestContext?.target?.label) { + switch (requestContext?.target?.label) { + case 'jobs_site': + return 'jobs'; + } + } }; /** @@ -107,15 +107,15 @@ export const getThirdPartyClientId = ( * @returns string | undefined */ export const getThirdPartyReturnUrl = ( - locationOrigin: string, - requestContext?: RequestContext, + locationOrigin: string, + requestContext?: RequestContext, ): string | undefined => { - if (requestContext?.target?.label) { - switch (requestContext?.target?.label) { - case 'jobs_site': - return encodeURIComponent(locationOrigin.replace('profile', 'jobs')); - } - } + if (requestContext?.target?.label) { + switch (requestContext?.target?.label) { + case 'jobs_site': + return encodeURIComponent(locationOrigin.replace('profile', 'jobs')); + } + } }; /** @@ -133,107 +133,107 @@ export const getThirdPartyReturnUrl = ( * @returns string */ export const getRedirectUrl = ( - locationSearch: string, - locationOrigin: string, - locationPathname: string, - oktaUtil?: OktaUtil, + locationSearch: string, + locationOrigin: string, + locationPathname: string, + oktaUtil?: OktaUtil, ): string => { - // set up params class to hold the parameters we'll be passing to our own login page - const params = new URLSearchParams(); - - // parse the current search params on the page - const searchParams = new URLSearchParams(locationSearch); - - // force fallback flag, used to test fallback behaviour - const forceFallback = searchParams.get('force_fallback'); - - // Variable holders for the Okta params we want to pass to our own login page - // This is the URI to redirect to after the user has logged in and has a session set to complete the Authorization Code Flow from the SDK. - let fromURI: string | undefined; - - // This is the client ID of the application that is calling the SDK and in turn performing the Authorization Code Flow. This parameter can be used to customise the experience our pages. We attempt to get it from the OktaUtil object, with the existing search parameters as a fallback option - let clientId: string | undefined; - - // third party `clientId` query param in Identity, used for `jobs` (Guardian Jobs) - // where we need to add this to the query params we send to gateway - let thirdPartyClientId: string | undefined; - - // third party `returnUrl` query param in Identity, used for `jobs` (Guardian Jobs) - // where we need to add this to the query params we send to gateway - let thirdPartyReturnUrl: string | undefined; - - // attempt to get the parameters we need from the Okta hosted login page OktaUtil object - if (oktaUtil && !forceFallback) { - // try getting fromURI from OktaUtil signInWidgetConfig (property is called called relayState) - const signInWidgetConfig = oktaUtil?.getSignInWidgetConfig?.(); - fromURI = getRelayState(signInWidgetConfig); - - // try getting clientId from OktaUtil requestContext - const requestContext = oktaUtil?.getRequestContext?.(); - clientId = getClientId(requestContext); - - // determine if this is a third party client e.g. jobs and set the thirdPartyClientId and thirdPartyReturnUrl - thirdPartyClientId = getThirdPartyClientId(requestContext); - thirdPartyReturnUrl = getThirdPartyReturnUrl( - locationOrigin, - requestContext, - ); - } - - // if we're unable to get clientId from OktaUtil, try to get it from the search params where it will exist - if (!clientId || forceFallback) { - clientId = searchParams.get('client_id') || undefined; - } - - // if fromURI doesn't exist, which is the case when prompt="login" is set and the user is already logged in - // we pass the current url as the fromURI so that the user completes the OAuth flow after login - // as all the parameters we need are in the url - // however this works only in the case when the pathname starts with /oauth2/ - if (!fromURI && locationPathname.startsWith('/oauth2/')) { - // delete the prompt parameter so that the user doesn't get stuck in a login loop - searchParams.delete('prompt'); - // set the fromURI parameter - params.set('fromURI', locationPathname + '?' + searchParams.toString()); - } - - // add the parameters to the params class - if (fromURI) { - params.set('fromURI', fromURI); - } - if (clientId) { - params.set('appClientId', clientId); - } - if (thirdPartyClientId) { - params.set('clientId', thirdPartyClientId); - } - if (thirdPartyReturnUrl) { - params.set('returnUrl', thirdPartyReturnUrl); - } - - // check the Okta hosted login page query params for an activation token for welcome page - const activationToken = searchParams.get('activation_token'); - - // check for reset password token - const resetPasswordToken = searchParams.get('reset_password_token'); - - // check for set password token - const setPasswordToken = searchParams.get('set_password_token'); - - // if we have an activation token, we know we need to go to the create password/welcome page - if (activationToken) { - return `/welcome/${activationToken}?${params.toString()}`; - } - - // if we have a reset password token, we know we need to go to the reset password page - if (resetPasswordToken) { - return `/reset-password/${resetPasswordToken}?${params.toString()}`; - } - - // if we have a set password token, we know we need to go to the set password page - if (setPasswordToken) { - return `/set-password/${setPasswordToken}?${params.toString()}`; - } - - // if we don't have any tokens, we need to go to the sign in page - return `/signin?${params.toString()}`; + // set up params class to hold the parameters we'll be passing to our own login page + const params = new URLSearchParams(); + + // parse the current search params on the page + const searchParams = new URLSearchParams(locationSearch); + + // force fallback flag, used to test fallback behaviour + const forceFallback = searchParams.get('force_fallback'); + + // Variable holders for the Okta params we want to pass to our own login page + // This is the URI to redirect to after the user has logged in and has a session set to complete the Authorization Code Flow from the SDK. + let fromURI: string | undefined; + + // This is the client ID of the application that is calling the SDK and in turn performing the Authorization Code Flow. This parameter can be used to customise the experience our pages. We attempt to get it from the OktaUtil object, with the existing search parameters as a fallback option + let clientId: string | undefined; + + // third party `clientId` query param in Identity, used for `jobs` (Guardian Jobs) + // where we need to add this to the query params we send to gateway + let thirdPartyClientId: string | undefined; + + // third party `returnUrl` query param in Identity, used for `jobs` (Guardian Jobs) + // where we need to add this to the query params we send to gateway + let thirdPartyReturnUrl: string | undefined; + + // attempt to get the parameters we need from the Okta hosted login page OktaUtil object + if (oktaUtil && !forceFallback) { + // try getting fromURI from OktaUtil signInWidgetConfig (property is called called relayState) + const signInWidgetConfig = oktaUtil?.getSignInWidgetConfig?.(); + fromURI = getRelayState(signInWidgetConfig); + + // try getting clientId from OktaUtil requestContext + const requestContext = oktaUtil?.getRequestContext?.(); + clientId = getClientId(requestContext); + + // determine if this is a third party client e.g. jobs and set the thirdPartyClientId and thirdPartyReturnUrl + thirdPartyClientId = getThirdPartyClientId(requestContext); + thirdPartyReturnUrl = getThirdPartyReturnUrl( + locationOrigin, + requestContext, + ); + } + + // if we're unable to get clientId from OktaUtil, try to get it from the search params where it will exist + if (!clientId || forceFallback) { + clientId = searchParams.get('client_id') || undefined; + } + + // if fromURI doesn't exist, which is the case when prompt="login" is set and the user is already logged in + // we pass the current url as the fromURI so that the user completes the OAuth flow after login + // as all the parameters we need are in the url + // however this works only in the case when the pathname starts with /oauth2/ + if (!fromURI && locationPathname.startsWith('/oauth2/')) { + // delete the prompt parameter so that the user doesn't get stuck in a login loop + searchParams.delete('prompt'); + // set the fromURI parameter + params.set('fromURI', locationPathname + '?' + searchParams.toString()); + } + + // add the parameters to the params class + if (fromURI) { + params.set('fromURI', fromURI); + } + if (clientId) { + params.set('appClientId', clientId); + } + if (thirdPartyClientId) { + params.set('clientId', thirdPartyClientId); + } + if (thirdPartyReturnUrl) { + params.set('returnUrl', thirdPartyReturnUrl); + } + + // check the Okta hosted login page query params for an activation token for welcome page + const activationToken = searchParams.get('activation_token'); + + // check for reset password token + const resetPasswordToken = searchParams.get('reset_password_token'); + + // check for set password token + const setPasswordToken = searchParams.get('set_password_token'); + + // if we have an activation token, we know we need to go to the create password/welcome page + if (activationToken) { + return `/welcome/${activationToken}?${params.toString()}`; + } + + // if we have a reset password token, we know we need to go to the reset password page + if (resetPasswordToken) { + return `/reset-password/${resetPasswordToken}?${params.toString()}`; + } + + // if we have a set password token, we know we need to go to the set password page + if (setPasswordToken) { + return `/set-password/${setPasswordToken}?${params.toString()}`; + } + + // if we don't have any tokens, we need to go to the sign in page + return `/signin?${params.toString()}`; }; diff --git a/scripts/okta/login-default.html b/scripts/okta/login-default.html index 6f213977cd..e98af78690 100644 --- a/scripts/okta/login-default.html +++ b/scripts/okta/login-default.html @@ -1,46 +1,46 @@ - - - - - - - - + + + + + + + + - {{pageTitle}} - {{{SignInWidgetResources}}} - - - -
+ {{pageTitle}} + {{{SignInWidgetResources}}} + + + +
- - {{{OktaUtil}}} + {{{OktaUtil}}} - - + // Render the Okta Sign-In Widget + var oktaSignIn = new OktaSignIn(config); + oktaSignIn.renderEl( + { el: '#okta-login-container' }, + OktaUtil.completeLogin, + function (error) { + // Logs errors that occur when configuring the widget. + // Remove or replace this with your own custom error handler. + console.log(error.message, error); + }, + ); + + diff --git a/scripts/okta/okta-login.html b/scripts/okta/okta-login.html index cf287d1e68..1ec7518128 100644 --- a/scripts/okta/okta-login.html +++ b/scripts/okta/okta-login.html @@ -1,18 +1,18 @@ - - - - - - The Guardian | Login - - - - {{{OktaUtil}}} - - + + + + + + The Guardian | Login + + + + {{{OktaUtil}}} + + diff --git a/scripts/okta/okta-login.ts b/scripts/okta/okta-login.ts index e278512aac..bf36d7fce1 100644 --- a/scripts/okta/okta-login.ts +++ b/scripts/okta/okta-login.ts @@ -31,10 +31,10 @@ import { getRedirectUrl } from './lib/helper'; // each will have a different query string and different config objects which are handled as appropriate in helper.ts. const redirectUrl = getRedirectUrl( - window.location.search, - window.location.origin, - window.location.pathname, - window.OktaUtil, + window.location.search, + window.location.origin, + window.location.pathname, + window.OktaUtil, ); window.location.replace(redirectUrl); diff --git a/scripts/okta/tsconfig.json b/scripts/okta/tsconfig.json index a0020bc36e..852fe2ca7a 100644 --- a/scripts/okta/tsconfig.json +++ b/scripts/okta/tsconfig.json @@ -1,17 +1,17 @@ { - "compilerOptions": { - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "module": "AMD", - "noUnusedLocals": true, - "outDir": ".", - "strict": true, - "target": "ES5", - "lib": ["ES2022", "DOM"], - "skipLibCheck": true, - "noEmit": true, - "removeComments": true, - "outFile": "./okta.js" - }, - "files": ["okta-login.ts"] + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "module": "AMD", + "noUnusedLocals": true, + "outDir": ".", + "strict": true, + "target": "ES5", + "lib": ["ES2022", "DOM"], + "skipLibCheck": true, + "noEmit": true, + "removeComments": true, + "outFile": "./okta.js" + }, + "files": ["okta-login.ts"] } diff --git a/scripts/okta/window.d.ts b/scripts/okta/window.d.ts index 3a30065ee2..c04c177f1c 100644 --- a/scripts/okta/window.d.ts +++ b/scripts/okta/window.d.ts @@ -3,7 +3,7 @@ import { OktaUtil } from './lib/helper'; export {}; declare global { - interface Window { - OktaUtil?: OktaUtil; - } + interface Window { + OktaUtil?: OktaUtil; + } } diff --git a/src/client/.well-known/assetlinks.json b/src/client/.well-known/assetlinks.json index 175126ec5d..23e71e5166 100644 --- a/src/client/.well-known/assetlinks.json +++ b/src/client/.well-known/assetlinks.json @@ -1,35 +1,35 @@ [ - { - "relation": ["delegate_permission/common.get_login_creds"], - "target": { - "namespace": "web", - "site": "https://profile.code.dev-theguardian.com" - } - }, - { - "relation": [ - "delegate_permission/common.handle_all_urls", - "delegate_permission/common.get_login_creds" - ], - "target": { - "namespace": "android_app", - "package_name": "com.guardian", - "sha256_cert_fingerprints": [ - "49:65:42:9E:AD:99:25:1C:84:C2:D5:57:D4:E6:F3:6C:7B:C3:B3:5C:1E:A1:8E:19:96:BC:CA:13:E5:5A:9E:7D" - ] - } - }, - { - "relation": [ - "delegate_permission/common.handle_all_urls", - "delegate_permission/common.get_login_creds" - ], - "target": { - "namespace": "android_app", - "package_name": "com.guardian.debug", - "sha256_cert_fingerprints": [ - "1B:5D:35:D1:3F:22:4A:6B:53:64:DA:8C:EA:2D:DE:96:A3:F9:9E:83:63:F5:71:71:00:6D:DB:4A:9C:F4:CD:93" - ] - } - } + { + "relation": ["delegate_permission/common.get_login_creds"], + "target": { + "namespace": "web", + "site": "https://profile.code.dev-theguardian.com" + } + }, + { + "relation": [ + "delegate_permission/common.handle_all_urls", + "delegate_permission/common.get_login_creds" + ], + "target": { + "namespace": "android_app", + "package_name": "com.guardian", + "sha256_cert_fingerprints": [ + "49:65:42:9E:AD:99:25:1C:84:C2:D5:57:D4:E6:F3:6C:7B:C3:B3:5C:1E:A1:8E:19:96:BC:CA:13:E5:5A:9E:7D" + ] + } + }, + { + "relation": [ + "delegate_permission/common.handle_all_urls", + "delegate_permission/common.get_login_creds" + ], + "target": { + "namespace": "android_app", + "package_name": "com.guardian.debug", + "sha256_cert_fingerprints": [ + "1B:5D:35:D1:3F:22:4A:6B:53:64:DA:8C:EA:2D:DE:96:A3:F9:9E:83:63:F5:71:71:00:6D:DB:4A:9C:F4:CD:93" + ] + } + } ] diff --git a/src/client/__tests__/EmailInput.test.tsx b/src/client/__tests__/EmailInput.test.tsx index fc77d187e0..40a41f596a 100644 --- a/src/client/__tests__/EmailInput.test.tsx +++ b/src/client/__tests__/EmailInput.test.tsx @@ -8,79 +8,79 @@ import '@testing-library/jest-dom/extend-expect'; import { EmailInput } from '../components/EmailInput'; const setup = () => { - const utils = render(); - const emailInput = utils.getByDisplayValue( - 'test@email.com', - ) as HTMLInputElement; - return { - emailInput, - ...utils, - }; + const utils = render(); + const emailInput = utils.getByDisplayValue( + 'test@email.com', + ) as HTMLInputElement; + return { + emailInput, + ...utils, + }; }; test('errors with nothing submitted', async () => { - const { emailInput, queryByText } = setup(); - // Input empty text to trigger error - fireEvent.input(emailInput, { target: { value: '' } }); - expect(emailInput.value).toEqual(''); + const { emailInput, queryByText } = setup(); + // Input empty text to trigger error + fireEvent.input(emailInput, { target: { value: '' } }); + expect(emailInput.value).toEqual(''); - // Check error message - const error = queryByText('Please enter your email.'); - expect(error).toBeInTheDocument(); + // Check error message + const error = queryByText('Please enter your email.'); + expect(error).toBeInTheDocument(); }); // test doesn't seem to work with @testing-library/preact // despite the fact that it works in the browser, cypress, and storybook // and used to work with @testing-library/react test.skip('errors with an invalid email', async () => { - const { emailInput, queryByText } = setup(); - // Input text and blur to trigger error - fireEvent.change(emailInput, { target: { value: 'invalid.email.com' } }); - fireEvent.blur(emailInput); - expect(emailInput.value).toEqual('invalid.email.com'); + const { emailInput, queryByText } = setup(); + // Input text and blur to trigger error + fireEvent.change(emailInput, { target: { value: 'invalid.email.com' } }); + fireEvent.blur(emailInput); + expect(emailInput.value).toEqual('invalid.email.com'); - // Check error message - const error = queryByText('Please enter a valid email format.'); - expect(error).toBeInTheDocument(); + // Check error message + const error = queryByText('Please enter a valid email format.'); + expect(error).toBeInTheDocument(); }); test('does not error with a valid email', async () => { - const { emailInput, queryByText } = setup(); - // Input text and blur to trigger error - fireEvent.change(emailInput, { target: { value: 'valid@email.com' } }); - fireEvent.blur(emailInput); - expect(emailInput.value).toEqual('valid@email.com'); + const { emailInput, queryByText } = setup(); + // Input text and blur to trigger error + fireEvent.change(emailInput, { target: { value: 'valid@email.com' } }); + fireEvent.blur(emailInput); + expect(emailInput.value).toEqual('valid@email.com'); - // Check error message - const invalidEmailError = queryByText('Please enter a valid email format.'); - const noInputError = queryByText('Please enter your email.'); + // Check error message + const invalidEmailError = queryByText('Please enter a valid email format.'); + const noInputError = queryByText('Please enter your email.'); - expect(invalidEmailError).not.toBeInTheDocument(); - expect(noInputError).not.toBeInTheDocument(); + expect(invalidEmailError).not.toBeInTheDocument(); + expect(noInputError).not.toBeInTheDocument(); }); // test doesn't seem to work with @testing-library/preact // despite the fact that it works in the browser, cypress, and storybook // and used to work with @testing-library/react test.skip('error is corrected once a valid email is submitted', async () => { - const { emailInput, queryByText } = setup(); - // Input text and blur to trigger error - fireEvent.change(emailInput, { target: { value: 'invalid.email.com' } }); - fireEvent.blur(emailInput); - expect(emailInput.value).toEqual('invalid.email.com'); + const { emailInput, queryByText } = setup(); + // Input text and blur to trigger error + fireEvent.change(emailInput, { target: { value: 'invalid.email.com' } }); + fireEvent.blur(emailInput); + expect(emailInput.value).toEqual('invalid.email.com'); - // Check error message - const error = queryByText('Please enter a valid email format.'); - expect(error).toBeInTheDocument(); + // Check error message + const error = queryByText('Please enter a valid email format.'); + expect(error).toBeInTheDocument(); - // Input text and blur to trigger error - fireEvent.change(emailInput, { target: { value: 'valid@email.com' } }); - fireEvent.blur(emailInput); - expect(emailInput.value).toEqual('valid@email.com'); + // Input text and blur to trigger error + fireEvent.change(emailInput, { target: { value: 'valid@email.com' } }); + fireEvent.blur(emailInput); + expect(emailInput.value).toEqual('valid@email.com'); - // Check error message - const invalidEmailError = queryByText('Please enter a valid email format.'); - const noInputError = queryByText('Please enter your email.'); - expect(invalidEmailError).not.toBeInTheDocument(); - expect(noInputError).not.toBeInTheDocument(); + // Check error message + const invalidEmailError = queryByText('Please enter a valid email format.'); + const noInputError = queryByText('Please enter your email.'); + expect(invalidEmailError).not.toBeInTheDocument(); + expect(noInputError).not.toBeInTheDocument(); }); diff --git a/src/client/__tests__/MainForm.test.tsx b/src/client/__tests__/MainForm.test.tsx index 4780154d6b..41c9b50db2 100644 --- a/src/client/__tests__/MainForm.test.tsx +++ b/src/client/__tests__/MainForm.test.tsx @@ -11,318 +11,318 @@ import { MainForm, MainFormProps } from '../components/MainForm'; import { RenderOptions } from '@/client/lib/hooks/useRecaptcha'; import { DetailedRecaptchaError } from '../components/DetailedRecaptchaError'; import { - setupRecaptchaScriptMutationObserver, - setupRecaptchaObject, + setupRecaptchaScriptMutationObserver, + setupRecaptchaObject, } from '@/client/lib/hooks/__tests__/utils/useRecaptchaTestUtils'; const setup = (extraProps?: Partial) => - render(); + render(); beforeEach(() => { - // Ensure that the mocked recaptcha script is loaded before each test. - // The whole grecaptcha object is then mocked in the test setup file, as if the script had really loaded. - // It will cause the useRecaptcha hook to error, unless the grecaptcha methods are also mocked. - // An example of mocking the methods to feign success can be found in this test which is part of the suite: - // "submits the form when the reCAPTCHA validation check is successful" - setupRecaptchaScriptMutationObserver(); - setupRecaptchaObject(); + // Ensure that the mocked recaptcha script is loaded before each test. + // The whole grecaptcha object is then mocked in the test setup file, as if the script had really loaded. + // It will cause the useRecaptcha hook to error, unless the grecaptcha methods are also mocked. + // An example of mocking the methods to feign success can be found in this test which is part of the suite: + // "submits the form when the reCAPTCHA validation check is successful" + setupRecaptchaScriptMutationObserver(); + setupRecaptchaObject(); }); test('terms and conditions in the document when hasGuardianTerms is true', async () => { - const { queryByText } = setup({ hasGuardianTerms: true }); - const terms = queryByText( - 'For information about how we use your data, see our', - { exact: false }, - ); - await waitFor(() => { - expect(terms).toBeInTheDocument(); - }); + const { queryByText } = setup({ hasGuardianTerms: true }); + const terms = queryByText( + 'For information about how we use your data, see our', + { exact: false }, + ); + await waitFor(() => { + expect(terms).toBeInTheDocument(); + }); }); test('terms and conditions not in the document when hasGuardianTerms is false', async () => { - const { queryByText } = setup({ hasGuardianTerms: false }); - const terms = queryByText( - 'For information about how we use your data, see our', - { exact: false }, - ); - await waitFor(() => { - expect(terms).toBeNull(); - }); + const { queryByText } = setup({ hasGuardianTerms: false }); + const terms = queryByText( + 'For information about how we use your data, see our', + { exact: false }, + ); + await waitFor(() => { + expect(terms).toBeNull(); + }); }); test('does not render the reCAPTCHA terms and conditions when recaptchaSiteKey is undefined', async () => { - const { queryByText } = setup(); - const terms = queryByText( - 'This service is protected by reCAPTCHA and the Google', - { exact: false }, - ); - await waitFor(() => { - expect(terms).toBeNull(); - }); + const { queryByText } = setup(); + const terms = queryByText( + 'This service is protected by reCAPTCHA and the Google', + { exact: false }, + ); + await waitFor(() => { + expect(terms).toBeNull(); + }); }); test('renders the reCAPTCHA terms and conditions when recaptchaSiteKey is defined', async () => { - const { queryByText } = setup({ recaptchaSiteKey: 'invalid-key' }); - const terms = queryByText( - 'This service is protected by reCAPTCHA and the Google', - { exact: false }, - ); - await waitFor(() => { - expect(terms).toBeInTheDocument(); - }); + const { queryByText } = setup({ recaptchaSiteKey: 'invalid-key' }); + const terms = queryByText( + 'This service is protected by reCAPTCHA and the Google', + { exact: false }, + ); + await waitFor(() => { + expect(terms).toBeInTheDocument(); + }); }); test('calls method to set an error message when reCAPTCHA does not load correctly', async () => { - const setRecaptchaErrorMessage = jest.fn(); - setup({ - recaptchaSiteKey: 'invalid-key', - setRecaptchaErrorMessage, - }); - await waitFor(() => { - expect(setRecaptchaErrorMessage).toHaveBeenCalledWith( - 'Google reCAPTCHA verification failed. Please try again.', - ); - }); + const setRecaptchaErrorMessage = jest.fn(); + setup({ + recaptchaSiteKey: 'invalid-key', + setRecaptchaErrorMessage, + }); + await waitFor(() => { + expect(setRecaptchaErrorMessage).toHaveBeenCalledWith( + 'Google reCAPTCHA verification failed. Please try again.', + ); + }); }); test('calls the form submit override method if defined', async () => { - const mockedSubmitOverride = jest.fn(); - const { findByText } = setup({ - onSubmit: mockedSubmitOverride, - recaptchaSiteKey: 'invalid-key', - }); + const mockedSubmitOverride = jest.fn(); + const { findByText } = setup({ + onSubmit: mockedSubmitOverride, + recaptchaSiteKey: 'invalid-key', + }); - const submitButton = await findByText('Submit'); + const submitButton = await findByText('Submit'); - await waitFor(() => { - expect(mockedSubmitOverride).toBeCalledTimes(0); - }); + await waitFor(() => { + expect(mockedSubmitOverride).toBeCalledTimes(0); + }); - act(() => { - fireEvent.click(submitButton); - }); + act(() => { + fireEvent.click(submitButton); + }); - await waitFor(() => { - expect(mockedSubmitOverride).toBeCalledTimes(1); - }); + await waitFor(() => { + expect(mockedSubmitOverride).toBeCalledTimes(1); + }); }); test('disables the form submit button when disableOnSubmit is set', async () => { - const { findByText } = setup({ - disableOnSubmit: true, - onSubmit: (e) => { - e.preventDefault(); // Jest does not implement form submit, so we make sure to preventDefault here. - return { errorOccurred: false }; - }, - }); + const { findByText } = setup({ + disableOnSubmit: true, + onSubmit: (e) => { + e.preventDefault(); // Jest does not implement form submit, so we make sure to preventDefault here. + return { errorOccurred: false }; + }, + }); - const submitButton = await findByText('Submit'); + const submitButton = await findByText('Submit'); - expect(submitButton).not.toBeDisabled(); + expect(submitButton).not.toBeDisabled(); - act(() => { - fireEvent.click(submitButton); - }); + act(() => { + fireEvent.click(submitButton); + }); - await waitFor(() => { - expect(submitButton).toBeDisabled(); - }); + await waitFor(() => { + expect(submitButton).toBeDisabled(); + }); }); test('enables the form submit button when onSubmit returns an error state', async () => { - const { findByText } = setup({ - disableOnSubmit: true, - onSubmit: (e) => { - e.preventDefault(); // Jest does not implement form submit, so we make sure to preventDefault here. - return { errorOccurred: true }; - }, - }); + const { findByText } = setup({ + disableOnSubmit: true, + onSubmit: (e) => { + e.preventDefault(); // Jest does not implement form submit, so we make sure to preventDefault here. + return { errorOccurred: true }; + }, + }); - const submitButton = await findByText('Submit'); + const submitButton = await findByText('Submit'); - expect(submitButton).not.toBeDisabled(); + expect(submitButton).not.toBeDisabled(); - act(() => { - fireEvent.click(submitButton); - }); + act(() => { + fireEvent.click(submitButton); + }); - await waitFor(() => { - expect(submitButton).not.toBeDisabled(); - }); + await waitFor(() => { + expect(submitButton).not.toBeDisabled(); + }); }); test('sets error message and context and prevents form submission when the reCAPTCHA check is unsuccessful', async () => { - // Extend the mocked Google Recaptcha object so that the success callback is called with a valid token. - const windowSpy = jest.spyOn(global.window, 'grecaptcha', 'get'); - const mockedGrecaptchaRender = jest.fn().mockReturnValue(0); - const mockedGrecaptchaReset = jest.fn(); - const mockedGrecaptchaReady = jest.fn((callback) => callback()); - - // Mock Google Recaptcha calling the success callback with a valid token. - const mockedGrecaptchaExecute = jest.fn(() => { - const renderOptions: RenderOptions = - mockedGrecaptchaRender.mock.calls[0][1]; - // Simulate the error callback being called after the recaptcha check. - setTimeout(() => { - renderOptions['error-callback'](undefined); - }, 10); - }); - - const setRecaptchaErrorMessage = jest.fn(); - const setRecaptchaErrorContext = jest.fn(); - - const mockedFormSubmit = jest.fn(); - // eslint-disable-next-line functional/immutable-data - Object.defineProperty(window.HTMLFormElement.prototype, 'submit', { - value: mockedFormSubmit, - }); - - const { findByText } = setup({ - recaptchaSiteKey: 'public-recaptcha-token', - setRecaptchaErrorMessage, - setRecaptchaErrorContext, - disableOnSubmit: true, - }); - - await waitFor(() => { - // Simulate the grecaptcha object loading in after the hook is executed. - windowSpy.mockImplementation(() => ({ - ready: mockedGrecaptchaReady, - render: mockedGrecaptchaRender, - execute: mockedGrecaptchaExecute, - reset: mockedGrecaptchaReset, - })); - }); - - // Check that the recaptcha render method was called. - await waitFor(() => { - expect(mockedGrecaptchaRender).toHaveBeenCalledWith( - 'recaptcha', - expect.anything(), - ); - }); - - const submitButton = await findByText('Submit'); - - expect(submitButton).not.toBeDisabled(); - - act(() => { - fireEvent.click(submitButton); - }); - - // Check that a recaptcha check has been requested. - await waitFor(() => { - expect(submitButton).toBeDisabled(); - - expect(mockedGrecaptchaReset).toHaveBeenCalledTimes(1); - expect(mockedGrecaptchaExecute).toHaveBeenCalledTimes(1); - // Check form is not submitted. - expect(mockedFormSubmit).toHaveBeenCalledTimes(0); - }); - - // Check that initial error message is shown. - await waitFor(() => { - // Ensure that the submit button is re-enabled if the recaptcha check fails. - expect(submitButton).not.toBeDisabled(); - - expect(setRecaptchaErrorMessage).toBeCalledTimes(1); - expect(setRecaptchaErrorMessage).toHaveBeenCalledWith( - 'Google reCAPTCHA verification failed. Please try again.', - ); - expect(setRecaptchaErrorContext).not.toBeCalled(); - }); - - act(() => { - fireEvent.click(submitButton); - }); - - // Check that a second recaptcha check has been requested. - await waitFor(() => { - expect(mockedGrecaptchaReset).toHaveBeenCalledTimes(2); - expect(mockedGrecaptchaExecute).toHaveBeenCalledTimes(2); - }); - - // Check that second error message is shown and error context is rendered. - await waitFor(() => { - expect(setRecaptchaErrorMessage).toBeCalledTimes(2); - expect(setRecaptchaErrorMessage).toHaveBeenCalledWith( - 'Google reCAPTCHA verification failed.', - ); - expect(setRecaptchaErrorContext).toBeCalledTimes(1); - expect(setRecaptchaErrorContext).toHaveBeenCalledWith( - expect.objectContaining({ type: DetailedRecaptchaError }), - ); - // Check form is not submitted. - expect(mockedFormSubmit).toHaveBeenCalledTimes(0); - }); - - windowSpy.mockRestore(); + // Extend the mocked Google Recaptcha object so that the success callback is called with a valid token. + const windowSpy = jest.spyOn(global.window, 'grecaptcha', 'get'); + const mockedGrecaptchaRender = jest.fn().mockReturnValue(0); + const mockedGrecaptchaReset = jest.fn(); + const mockedGrecaptchaReady = jest.fn((callback) => callback()); + + // Mock Google Recaptcha calling the success callback with a valid token. + const mockedGrecaptchaExecute = jest.fn(() => { + const renderOptions: RenderOptions = + mockedGrecaptchaRender.mock.calls[0][1]; + // Simulate the error callback being called after the recaptcha check. + setTimeout(() => { + renderOptions['error-callback'](undefined); + }, 10); + }); + + const setRecaptchaErrorMessage = jest.fn(); + const setRecaptchaErrorContext = jest.fn(); + + const mockedFormSubmit = jest.fn(); + // eslint-disable-next-line functional/immutable-data + Object.defineProperty(window.HTMLFormElement.prototype, 'submit', { + value: mockedFormSubmit, + }); + + const { findByText } = setup({ + recaptchaSiteKey: 'public-recaptcha-token', + setRecaptchaErrorMessage, + setRecaptchaErrorContext, + disableOnSubmit: true, + }); + + await waitFor(() => { + // Simulate the grecaptcha object loading in after the hook is executed. + windowSpy.mockImplementation(() => ({ + ready: mockedGrecaptchaReady, + render: mockedGrecaptchaRender, + execute: mockedGrecaptchaExecute, + reset: mockedGrecaptchaReset, + })); + }); + + // Check that the recaptcha render method was called. + await waitFor(() => { + expect(mockedGrecaptchaRender).toHaveBeenCalledWith( + 'recaptcha', + expect.anything(), + ); + }); + + const submitButton = await findByText('Submit'); + + expect(submitButton).not.toBeDisabled(); + + act(() => { + fireEvent.click(submitButton); + }); + + // Check that a recaptcha check has been requested. + await waitFor(() => { + expect(submitButton).toBeDisabled(); + + expect(mockedGrecaptchaReset).toHaveBeenCalledTimes(1); + expect(mockedGrecaptchaExecute).toHaveBeenCalledTimes(1); + // Check form is not submitted. + expect(mockedFormSubmit).toHaveBeenCalledTimes(0); + }); + + // Check that initial error message is shown. + await waitFor(() => { + // Ensure that the submit button is re-enabled if the recaptcha check fails. + expect(submitButton).not.toBeDisabled(); + + expect(setRecaptchaErrorMessage).toBeCalledTimes(1); + expect(setRecaptchaErrorMessage).toHaveBeenCalledWith( + 'Google reCAPTCHA verification failed. Please try again.', + ); + expect(setRecaptchaErrorContext).not.toBeCalled(); + }); + + act(() => { + fireEvent.click(submitButton); + }); + + // Check that a second recaptcha check has been requested. + await waitFor(() => { + expect(mockedGrecaptchaReset).toHaveBeenCalledTimes(2); + expect(mockedGrecaptchaExecute).toHaveBeenCalledTimes(2); + }); + + // Check that second error message is shown and error context is rendered. + await waitFor(() => { + expect(setRecaptchaErrorMessage).toBeCalledTimes(2); + expect(setRecaptchaErrorMessage).toHaveBeenCalledWith( + 'Google reCAPTCHA verification failed.', + ); + expect(setRecaptchaErrorContext).toBeCalledTimes(1); + expect(setRecaptchaErrorContext).toHaveBeenCalledWith( + expect.objectContaining({ type: DetailedRecaptchaError }), + ); + // Check form is not submitted. + expect(mockedFormSubmit).toHaveBeenCalledTimes(0); + }); + + windowSpy.mockRestore(); }); test('submits the form when the reCAPTCHA validation check is successful', async () => { - // Extend the mocked Google Recaptcha object so that the success callback is called with a valid token. - const windowSpy = jest.spyOn(global.window, 'grecaptcha', 'get'); - const mockedGrecaptchaRender = jest.fn().mockReturnValue(0); - const mockedGrecaptchaReset = jest.fn(); - const mockedGrecaptchaReady = jest.fn((callback) => { - callback(); - }); - // Mock Google Recaptcha calling the success callback with a valid token. - const mockedGrecaptchaExecute = jest.fn(() => { - const renderOptions: RenderOptions = - mockedGrecaptchaRender.mock.calls[0][1]; - renderOptions.callback('valid-token'); - }); - - const mockedFormSubmit = jest.fn(); - // eslint-disable-next-line functional/immutable-data - Object.defineProperty(window.HTMLFormElement.prototype, 'submit', { - value: mockedFormSubmit, - }); - - const { findByText } = setup({ - recaptchaSiteKey: 'public-recaptcha-token', - }); - - await waitFor(() => { - // Simulate the grecaptcha object loading in after the hook is executed. - windowSpy.mockImplementation(() => ({ - ready: mockedGrecaptchaReady, - render: mockedGrecaptchaRender, - execute: mockedGrecaptchaExecute, - reset: mockedGrecaptchaReset, - })); - }); - - await waitFor(() => { - // Check that the recaptcha render method was called. - expect(mockedGrecaptchaRender).toHaveBeenCalledWith( - 'recaptcha', - expect.anything(), - ); - }); - - const submitButton = await findByText('Submit'); - - act(() => { - fireEvent.click(submitButton); - }); - - await waitFor(() => { - // Check that a recaptcha check has been requested. - expect(mockedGrecaptchaReset).toHaveBeenCalledTimes(1); - expect(mockedGrecaptchaExecute).toHaveBeenCalledTimes(1); - // Check that the form is successfully submitted. - expect(mockedFormSubmit).toHaveBeenCalledTimes(1); - }); - - windowSpy.mockRestore(); + // Extend the mocked Google Recaptcha object so that the success callback is called with a valid token. + const windowSpy = jest.spyOn(global.window, 'grecaptcha', 'get'); + const mockedGrecaptchaRender = jest.fn().mockReturnValue(0); + const mockedGrecaptchaReset = jest.fn(); + const mockedGrecaptchaReady = jest.fn((callback) => { + callback(); + }); + // Mock Google Recaptcha calling the success callback with a valid token. + const mockedGrecaptchaExecute = jest.fn(() => { + const renderOptions: RenderOptions = + mockedGrecaptchaRender.mock.calls[0][1]; + renderOptions.callback('valid-token'); + }); + + const mockedFormSubmit = jest.fn(); + // eslint-disable-next-line functional/immutable-data + Object.defineProperty(window.HTMLFormElement.prototype, 'submit', { + value: mockedFormSubmit, + }); + + const { findByText } = setup({ + recaptchaSiteKey: 'public-recaptcha-token', + }); + + await waitFor(() => { + // Simulate the grecaptcha object loading in after the hook is executed. + windowSpy.mockImplementation(() => ({ + ready: mockedGrecaptchaReady, + render: mockedGrecaptchaRender, + execute: mockedGrecaptchaExecute, + reset: mockedGrecaptchaReset, + })); + }); + + await waitFor(() => { + // Check that the recaptcha render method was called. + expect(mockedGrecaptchaRender).toHaveBeenCalledWith( + 'recaptcha', + expect.anything(), + ); + }); + + const submitButton = await findByText('Submit'); + + act(() => { + fireEvent.click(submitButton); + }); + + await waitFor(() => { + // Check that a recaptcha check has been requested. + expect(mockedGrecaptchaReset).toHaveBeenCalledTimes(1); + expect(mockedGrecaptchaExecute).toHaveBeenCalledTimes(1); + // Check that the form is successfully submitted. + expect(mockedFormSubmit).toHaveBeenCalledTimes(1); + }); + + windowSpy.mockRestore(); }); test('Shows an error inside the form when formError is set', async () => { - const errorText = 'Alas, poor Yorick, an error has occurred.'; - const { queryByText } = setup({ formErrorMessageFromParent: errorText }); - await waitFor(() => { - expect(queryByText(errorText, { exact: false })).toBeInTheDocument(); - }); + const errorText = 'Alas, poor Yorick, an error has occurred.'; + const { queryByText } = setup({ formErrorMessageFromParent: errorText }); + await waitFor(() => { + expect(queryByText(errorText, { exact: false })).toBeInTheDocument(); + }); }); diff --git a/src/client/__tests__/Registration.test.tsx b/src/client/__tests__/Registration.test.tsx index f24694557e..6962509971 100644 --- a/src/client/__tests__/Registration.test.tsx +++ b/src/client/__tests__/Registration.test.tsx @@ -9,95 +9,95 @@ import '@testing-library/jest-dom/extend-expect'; import { Registration, RegistrationProps } from '../pages/Registration'; const setup = (extraProps?: Partial) => - render( - , - ); + render( + , + ); test('Default terms and conditions in document when clientId not set', async () => { - const { queryByText } = setup({ - queryParams: { - returnUrl: 'https://www.theguardian.com/uk', - }, - }); + const { queryByText } = setup({ + queryParams: { + returnUrl: 'https://www.theguardian.com/uk', + }, + }); - /** - * A helper function to check if the specified text appears in any tag in the document, - * including that tag's children. - */ - const queryByTextContent = (text: string) => { - // Passing function to testing-library's `queryByText` - return queryByText((content, element) => { - const hasText = (element: Element | null) => - element?.textContent === text; - const elementHasText = hasText(element); - const childrenDontHaveText = Array.from(element?.children || []).every( - (child) => !hasText(child), - ); - return elementHasText && childrenDontHaveText; - }); - }; + /** + * A helper function to check if the specified text appears in any tag in the document, + * including that tag's children. + */ + const queryByTextContent = (text: string) => { + // Passing function to testing-library's `queryByText` + return queryByText((content, element) => { + const hasText = (element: Element | null) => + element?.textContent === text; + const elementHasText = hasText(element); + const childrenDontHaveText = Array.from(element?.children || []).every( + (child) => !hasText(child), + ); + return elementHasText && childrenDontHaveText; + }); + }; - const defaultTerms = queryByTextContent( - 'By proceeding, you agree to our terms & conditions.', - ); - const privacyPolicy = queryByTextContent( - 'For information about how we use your data, see our privacy policy.', - ); - const jobsTerms = queryByTextContent( - 'By proceeding, you agree to our Guardian Jobs terms & conditions.', - ); - await waitFor(() => { - expect(defaultTerms).toBeInTheDocument(); - expect(privacyPolicy).toBeInTheDocument(); - expect(jobsTerms).not.toBeInTheDocument(); - }); + const defaultTerms = queryByTextContent( + 'By proceeding, you agree to our terms & conditions.', + ); + const privacyPolicy = queryByTextContent( + 'For information about how we use your data, see our privacy policy.', + ); + const jobsTerms = queryByTextContent( + 'By proceeding, you agree to our Guardian Jobs terms & conditions.', + ); + await waitFor(() => { + expect(defaultTerms).toBeInTheDocument(); + expect(privacyPolicy).toBeInTheDocument(); + expect(jobsTerms).not.toBeInTheDocument(); + }); }); test('Jobs terms and conditions in document when clientId equals "jobs"', async () => { - const { queryByText } = setup({ - queryParams: { - returnUrl: 'https://www.theguardian.com/uk', - clientId: 'jobs', - }, - }); + const { queryByText } = setup({ + queryParams: { + returnUrl: 'https://www.theguardian.com/uk', + clientId: 'jobs', + }, + }); - /** - * A helper function to check if the specified text appears in any tag in the document, - * including that tag's children. - */ - const queryByTextContent = (text: string) => { - // Passing function to testing-library's `queryByText` - return queryByText((content, element) => { - const hasText = (element: Element | null) => - element?.textContent === text; - const elementHasText = hasText(element); - const childrenDontHaveText = Array.from(element?.children || []).every( - (child) => !hasText(child), - ); - return elementHasText && childrenDontHaveText; - }); - }; + /** + * A helper function to check if the specified text appears in any tag in the document, + * including that tag's children. + */ + const queryByTextContent = (text: string) => { + // Passing function to testing-library's `queryByText` + return queryByText((content, element) => { + const hasText = (element: Element | null) => + element?.textContent === text; + const elementHasText = hasText(element); + const childrenDontHaveText = Array.from(element?.children || []).every( + (child) => !hasText(child), + ); + return elementHasText && childrenDontHaveText; + }); + }; - const defaultTerms = queryByTextContent( - 'By proceeding, you agree to our terms & conditions', - ); - const privacyPolicy = queryByTextContent( - 'For information about how we use your data, see our Guardian Jobs privacy policy.', - ); - const jobsTerms = queryByTextContent( - 'By proceeding, you agree to our Guardian Jobs terms & conditions.', - ); - await waitFor(() => { - expect(defaultTerms).not.toBeInTheDocument(); - expect(privacyPolicy).toBeInTheDocument(); - expect(jobsTerms).toBeInTheDocument(); - }); + const defaultTerms = queryByTextContent( + 'By proceeding, you agree to our terms & conditions', + ); + const privacyPolicy = queryByTextContent( + 'For information about how we use your data, see our Guardian Jobs privacy policy.', + ); + const jobsTerms = queryByTextContent( + 'By proceeding, you agree to our Guardian Jobs terms & conditions.', + ); + await waitFor(() => { + expect(defaultTerms).not.toBeInTheDocument(); + expect(privacyPolicy).toBeInTheDocument(); + expect(jobsTerms).toBeInTheDocument(); + }); }); diff --git a/src/client/__tests__/SignIn.test.tsx b/src/client/__tests__/SignIn.test.tsx index 09d9f2b02d..947cfdd72b 100644 --- a/src/client/__tests__/SignIn.test.tsx +++ b/src/client/__tests__/SignIn.test.tsx @@ -9,128 +9,128 @@ import '@testing-library/jest-dom/extend-expect'; import { SignIn, SignInProps } from '../pages/SignIn'; const setup = (extraProps?: Partial) => - render( - , - ); + render( + , + ); test('Register button not shown when reauthenticate is set to true', async () => { - const { queryByText } = setup({ isReauthenticate: true }); - await waitFor(() => { - // This may need to be made a bit more specific if we ever put the word - // 'Register' on the page but it works for the moment! - expect(queryByText('Register')).not.toBeInTheDocument(); - }); + const { queryByText } = setup({ isReauthenticate: true }); + await waitFor(() => { + // This may need to be made a bit more specific if we ever put the word + // 'Register' on the page but it works for the moment! + expect(queryByText('Register')).not.toBeInTheDocument(); + }); }); test('Default terms and conditions in document when clientId not set', async () => { - const { queryByText } = setup({ - queryParams: { - returnUrl: 'https://www.theguardian.com/uk', - }, - }); + const { queryByText } = setup({ + queryParams: { + returnUrl: 'https://www.theguardian.com/uk', + }, + }); - /** - * A helper function to check if the specified text appears in any tag in the document, - * including that tag's children. - */ - const queryByTextContent = (text: string) => { - // Passing function to testing-library's `queryByText` - return queryByText((content, element) => { - const hasText = (element: Element | null) => - element?.textContent === text; - const elementHasText = hasText(element); - const childrenDontHaveText = Array.from(element?.children || []).every( - (child) => !hasText(child), - ); - return elementHasText && childrenDontHaveText; - }); - }; + /** + * A helper function to check if the specified text appears in any tag in the document, + * including that tag's children. + */ + const queryByTextContent = (text: string) => { + // Passing function to testing-library's `queryByText` + return queryByText((content, element) => { + const hasText = (element: Element | null) => + element?.textContent === text; + const elementHasText = hasText(element); + const childrenDontHaveText = Array.from(element?.children || []).every( + (child) => !hasText(child), + ); + return elementHasText && childrenDontHaveText; + }); + }; - const defaultTerms = queryByTextContent( - 'By proceeding, you agree to our terms & conditions.', - ); - const privacyPolicy = queryByTextContent( - 'For information about how we use your data, see our privacy policy.', - ); - const jobsTerms = queryByTextContent( - 'By proceeding, you agree to our Guardian Jobs terms & conditions.', - ); - await waitFor(() => { - expect(defaultTerms).toBeInTheDocument(); - expect(privacyPolicy).toBeInTheDocument(); - expect(jobsTerms).not.toBeInTheDocument(); - }); + const defaultTerms = queryByTextContent( + 'By proceeding, you agree to our terms & conditions.', + ); + const privacyPolicy = queryByTextContent( + 'For information about how we use your data, see our privacy policy.', + ); + const jobsTerms = queryByTextContent( + 'By proceeding, you agree to our Guardian Jobs terms & conditions.', + ); + await waitFor(() => { + expect(defaultTerms).toBeInTheDocument(); + expect(privacyPolicy).toBeInTheDocument(); + expect(jobsTerms).not.toBeInTheDocument(); + }); }); test('Jobs terms and conditions in document when clientId equals "jobs"', async () => { - const { queryByText } = setup({ - queryParams: { - returnUrl: 'https://www.theguardian.com/uk', - clientId: 'jobs', - }, - }); + const { queryByText } = setup({ + queryParams: { + returnUrl: 'https://www.theguardian.com/uk', + clientId: 'jobs', + }, + }); - /** - * A helper function to check if the specified text appears in any tag in the document, - * including that tag's children. - */ - const queryByTextContent = (text: string) => { - // Passing function to testing-library's `queryByText` - return queryByText((content, element) => { - const hasText = (element: Element | null) => - element?.textContent === text; - const elementHasText = hasText(element); - const childrenDontHaveText = Array.from(element?.children || []).every( - (child) => !hasText(child), - ); - return elementHasText && childrenDontHaveText; - }); - }; + /** + * A helper function to check if the specified text appears in any tag in the document, + * including that tag's children. + */ + const queryByTextContent = (text: string) => { + // Passing function to testing-library's `queryByText` + return queryByText((content, element) => { + const hasText = (element: Element | null) => + element?.textContent === text; + const elementHasText = hasText(element); + const childrenDontHaveText = Array.from(element?.children || []).every( + (child) => !hasText(child), + ); + return elementHasText && childrenDontHaveText; + }); + }; - const defaultTerms = queryByTextContent( - 'By proceeding, you agree to our terms & conditions', - ); - const privacyPolicy = queryByTextContent( - 'For information about how we use your data, see our Guardian Jobs privacy policy.', - ); - const jobsTerms = queryByTextContent( - 'By proceeding, you agree to our Guardian Jobs terms & conditions.', - ); - await waitFor(() => { - expect(defaultTerms).not.toBeInTheDocument(); - expect(privacyPolicy).toBeInTheDocument(); - expect(jobsTerms).toBeInTheDocument(); - }); + const defaultTerms = queryByTextContent( + 'By proceeding, you agree to our terms & conditions', + ); + const privacyPolicy = queryByTextContent( + 'For information about how we use your data, see our Guardian Jobs privacy policy.', + ); + const jobsTerms = queryByTextContent( + 'By proceeding, you agree to our Guardian Jobs terms & conditions.', + ); + await waitFor(() => { + expect(defaultTerms).not.toBeInTheDocument(); + expect(privacyPolicy).toBeInTheDocument(); + expect(jobsTerms).toBeInTheDocument(); + }); }); test('Shows an error inside the form when formError is set', async () => { - const errorText = 'Alas, poor Yorick, an error has occurred.'; - const { getByTestId } = setup({ - formError: errorText, - }); - const mainForm = getByTestId('main-form'); - const formError = within(mainForm).queryByText(errorText, { exact: false }); - await waitFor(() => { - expect(formError).toBeInTheDocument(); - }); + const errorText = 'Alas, poor Yorick, an error has occurred.'; + const { getByTestId } = setup({ + formError: errorText, + }); + const mainForm = getByTestId('main-form'); + const formError = within(mainForm).queryByText(errorText, { exact: false }); + await waitFor(() => { + expect(formError).toBeInTheDocument(); + }); }); test('Shows an error outside the form when pageError is set', async () => { - const errorText = 'Alas, poor Yorick, an error has occurred.'; - const { getByTestId, queryByText } = setup({ - pageError: errorText, - }); - const mainForm = getByTestId('main-form'); - const formError = within(mainForm).queryByText(errorText, { exact: false }); - await waitFor(() => { - expect(formError).not.toBeInTheDocument(); - expect(queryByText(errorText)).toBeInTheDocument(); - }); + const errorText = 'Alas, poor Yorick, an error has occurred.'; + const { getByTestId, queryByText } = setup({ + pageError: errorText, + }); + const mainForm = getByTestId('main-form'); + const formError = within(mainForm).queryByText(errorText, { exact: false }); + await waitFor(() => { + expect(formError).not.toBeInTheDocument(); + expect(queryByText(errorText)).toBeInTheDocument(); + }); }); diff --git a/src/client/app.tsx b/src/client/app.tsx index fe6bb6ec8b..a2b8f7a3c6 100644 --- a/src/client/app.tsx +++ b/src/client/app.tsx @@ -10,59 +10,59 @@ import { tests } from '@/shared/model/experiments/abTests'; import { useAB } from '@guardian/ab-react'; interface Props extends ClientState { - location: string; + location: string; } export const App = (props: Props) => { - // initialise the AB Test Framework: - // load the AB Hook - const ABTestAPI = useAB(); + // initialise the AB Test Framework: + // load the AB Hook + const ABTestAPI = useAB(); - // use effect to initialise and register events if needed - useEffect(() => { - // get all runnable tests - const allRunnableTests = ABTestAPI.allRunnableTests(tests); - // track them in ophan - ABTestAPI.trackABTests(allRunnableTests); - // register any impression events - ABTestAPI.registerImpressionEvents(allRunnableTests); - // register any completion events - ABTestAPI.registerCompleteEvents(allRunnableTests); - }, [ABTestAPI]); + // use effect to initialise and register events if needed + useEffect(() => { + // get all runnable tests + const allRunnableTests = ABTestAPI.allRunnableTests(tests); + // track them in ophan + ABTestAPI.trackABTests(allRunnableTests); + // register any impression events + ABTestAPI.registerImpressionEvents(allRunnableTests); + // register any completion events + ABTestAPI.registerCompleteEvents(allRunnableTests); + }, [ABTestAPI]); - return ( - <> -