From 531ca3c22f23d87f65de90e015416e3f60d10939 Mon Sep 17 00:00:00 2001 From: Matchu Date: Mon, 19 Apr 2021 02:16:31 -0700 Subject: [PATCH] Create Cypress command to log in I'll use this to test outfit saving! --- .../WardrobePage/Outfit saving.spec.js | 6 +++ cypress/plugins/index.js | 6 +++ cypress/support/commands.js | 41 +++++++++++++++++ src/app/apolloClient.js | 46 ++++++++++++------- src/app/components/useCurrentUser.js | 36 ++++++++++++--- 5 files changed, 113 insertions(+), 22 deletions(-) create mode 100644 cypress/integration/WardrobePage/Outfit saving.spec.js diff --git a/cypress/integration/WardrobePage/Outfit saving.spec.js b/cypress/integration/WardrobePage/Outfit saving.spec.js new file mode 100644 index 0000000..de79507 --- /dev/null +++ b/cypress/integration/WardrobePage/Outfit saving.spec.js @@ -0,0 +1,6 @@ +describe("WardrobePage: Outfit saving", () => { + it("logs in", () => { + cy.logInAs("dti-test"); + cy.visit("/"); + }); +}); diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js index bb34830..b23d762 100644 --- a/cypress/plugins/index.js +++ b/cypress/plugins/index.js @@ -1,6 +1,12 @@ +require("dotenv").config(); const { initPlugin } = require("cypress-plugin-snapshots/plugin"); module.exports = (on, config) => { initPlugin(on, config); + + config.env.AUTH0_TEST_CLIENT_ID = process.env.AUTH0_TEST_CLIENT_ID; + config.env.AUTH0_TEST_CLIENT_SECRET = process.env.AUTH0_TEST_CLIENT_SECRET; + config.env.DTI_TEST_USER_PASSWORD = process.env.DTI_TEST_USER_PASSWORD; + return config; }; diff --git a/cypress/support/commands.js b/cypress/support/commands.js index e1cb3a1..bbaf9a4 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -1 +1,42 @@ import "cypress-plugin-snapshots/commands"; +import * as jwt from "jsonwebtoken"; + +// https://docs.cypress.io/guides/testing-strategies/auth0-authentication#Custom-Command-for-Auth0-Authentication +// Adapted and simplified by a lot though! +Cypress.Commands.add("logInAs", (username) => { + if (username !== "dti-test") { + throw new Error(`Cypress can only log in as dti-test right now`); + } + const password = Cypress.env("DTI_TEST_USER_PASSWORD"); + + const client_id = Cypress.env("AUTH0_TEST_CLIENT_ID"); + const client_secret = Cypress.env("AUTH0_TEST_CLIENT_SECRET"); + const audience = "https://impress-2020.openneo.net/api"; + const scope = ""; + + // TODO: This grant doesn't seem to include the custom username field. The + // app is generally resilient to that, but yeah, it means the username + // might not show up in the global header UI during these tests. + cy.request({ + method: "POST", + url: `https://openneo.us.auth0.com/oauth/token`, + body: { + grant_type: "password", + username, + password, + audience, + scope, + client_id, + client_secret, + }, + }).then(({ body }) => { + const decodedUser = jwt.decode(body.access_token); + + const auth0Cypress = { + encodedToken: body.access_token, + decodedUser, + }; + + window.localStorage.setItem("auth0Cypress", JSON.stringify(auth0Cypress)); + }); +}); diff --git a/src/app/apolloClient.js b/src/app/apolloClient.js index 54bd85f..947dd1d 100644 --- a/src/app/apolloClient.js +++ b/src/app/apolloClient.js @@ -3,6 +3,7 @@ import { setContext } from "@apollo/client/link/context"; import { createPersistedQueryLink } from "apollo-link-persisted-queries"; import cachedZones from "./cached-data/zones.json"; +import { readCypressLoginData } from "./components/useCurrentUser"; // Teach Apollo to load certain fields from the cache, to avoid extra network // requests. This happens a lot - e.g. reusing data from item search on the @@ -142,22 +143,8 @@ const buildAuthLink = (getAuth0) => return; } - // Wait for auth0 to stop loading, so we can maybe get a token! We'll do - // this hackily by checking every 100ms until it's true. - await new Promise((resolve) => { - function check() { - if (getAuth0().isLoading) { - setTimeout(check, 100); - } else { - resolve(); - } - } - check(); - }); - - const { isAuthenticated, getAccessTokenSilently } = getAuth0(); - if (isAuthenticated) { - const token = await getAccessTokenSilently(); + const token = await getAccessToken(getAuth0); + if (token) { return { headers: { ...headers, @@ -167,6 +154,33 @@ const buildAuthLink = (getAuth0) => } }); +async function getAccessToken(getAuth0) { + // Our Cypress tests store login data separately. Use it if available! + const cypressToken = readCypressLoginData()?.encodedToken; + if (cypressToken) { + return cypressToken; + } + + // Otherwise, wait for auth0 to stop loading, so we can maybe get a token! + // We'll do this hackily by checking every 100ms until it's true. + await new Promise((resolve) => { + function check() { + if (getAuth0().isLoading) { + setTimeout(check, 100); + } else { + resolve(); + } + } + check(); + }); + + const { isAuthenticated, getAccessTokenSilently } = getAuth0(); + if (isAuthenticated) { + const token = await getAccessTokenSilently(); + return token; + } +} + const initialCache = {}; for (const zone of cachedZones) { initialCache[`Zone:${zone.id}`] = { __typename: "Zone", ...zone }; diff --git a/src/app/components/useCurrentUser.js b/src/app/components/useCurrentUser.js index 843efd9..9943c18 100644 --- a/src/app/components/useCurrentUser.js +++ b/src/app/components/useCurrentUser.js @@ -24,17 +24,41 @@ function useCurrentUser() { }; } + // Additionally, our Cypress tests do an actual end-to-end login as a test + // user. Use that token if present! + const cypressUser = readCypressLoginData()?.decodedUser; + if (cypressUser) { + return { isLoading: false, isLoggedIn: true, ...getUserInfo(cypressUser) }; + } + if (isLoading || !isAuthenticated) { return { isLoading, isLoggedIn: false, id: null, username: null }; } - // NOTE: Users created correctly should have these attributes... but I'm - // coding defensively, because third-party integrations are always a - // bit of a thing, and I don't want failures to crash us! - const id = user.sub?.match(/^auth0\|impress-([0-9]+)$/)?.[1]; - const username = user["https://oauth.impress-2020.openneo.net/username"]; + return { isLoading, isLoggedIn: true, ...getUserInfo(user) }; +} - return { isLoading, isLoggedIn: true, id, username }; +export function readCypressLoginData() { + const cypressUserJsonString = window.localStorage.getItem("auth0Cypress"); + if (!cypressUserJsonString) { + return null; + } + + try { + return JSON.parse(cypressUserJsonString); + } catch (e) { + console.warn( + "Could not parse auth0Cypress token in localStorage; ignoring." + ); + return null; + } +} + +function getUserInfo(user) { + return { + id: user.sub?.match(/^auth0\|impress-([0-9]+)$/)?.[1], + username: user["https://oauth.impress-2020.openneo.net/username"], + }; } export default useCurrentUser;