Create Cypress command to log in
I'll use this to test outfit saving!
This commit is contained in:
parent
9f2629ae61
commit
531ca3c22f
5 changed files with 113 additions and 22 deletions
6
cypress/integration/WardrobePage/Outfit saving.spec.js
Normal file
6
cypress/integration/WardrobePage/Outfit saving.spec.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
describe("WardrobePage: Outfit saving", () => {
|
||||||
|
it("logs in", () => {
|
||||||
|
cy.logInAs("dti-test");
|
||||||
|
cy.visit("/");
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,6 +1,12 @@
|
||||||
|
require("dotenv").config();
|
||||||
const { initPlugin } = require("cypress-plugin-snapshots/plugin");
|
const { initPlugin } = require("cypress-plugin-snapshots/plugin");
|
||||||
|
|
||||||
module.exports = (on, config) => {
|
module.exports = (on, config) => {
|
||||||
initPlugin(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;
|
return config;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1 +1,42 @@
|
||||||
import "cypress-plugin-snapshots/commands";
|
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));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { setContext } from "@apollo/client/link/context";
|
||||||
import { createPersistedQueryLink } from "apollo-link-persisted-queries";
|
import { createPersistedQueryLink } from "apollo-link-persisted-queries";
|
||||||
|
|
||||||
import cachedZones from "./cached-data/zones.json";
|
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
|
// 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
|
// requests. This happens a lot - e.g. reusing data from item search on the
|
||||||
|
@ -142,22 +143,8 @@ const buildAuthLink = (getAuth0) =>
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for auth0 to stop loading, so we can maybe get a token! We'll do
|
const token = await getAccessToken(getAuth0);
|
||||||
// this hackily by checking every 100ms until it's true.
|
if (token) {
|
||||||
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 {
|
return {
|
||||||
headers: {
|
headers: {
|
||||||
...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 = {};
|
const initialCache = {};
|
||||||
for (const zone of cachedZones) {
|
for (const zone of cachedZones) {
|
||||||
initialCache[`Zone:${zone.id}`] = { __typename: "Zone", ...zone };
|
initialCache[`Zone:${zone.id}`] = { __typename: "Zone", ...zone };
|
||||||
|
|
|
@ -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) {
|
if (isLoading || !isAuthenticated) {
|
||||||
return { isLoading, isLoggedIn: false, id: null, username: null };
|
return { isLoading, isLoggedIn: false, id: null, username: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Users created correctly should have these attributes... but I'm
|
return { isLoading, isLoggedIn: true, ...getUserInfo(user) };
|
||||||
// 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, 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;
|
export default useCurrentUser;
|
||||||
|
|
Loading…
Reference in a new issue