From c4c48b9a5742e4fa07623002640200dc1eaeac43 Mon Sep 17 00:00:00 2001 From: Severiano Badajoz Date: Thu, 8 Oct 2020 11:02:40 -0600 Subject: [PATCH] create e2e test for auth buttons (#1907) This PR adds a few helpful additions regarding authentication. Changes: * e2e tests are now run on test_oauth via a passed config.yaml * node dev server correctly handles `/login` and `/logout` endpoints to make developing for auth easier * Introduced auth e2e tests to check that buttons display and work --- client/Makefile | 4 +- client/__tests__/e2e/cellxgeneActions.js | 11 +++-- client/__tests__/e2e/e2e.test.js | 12 +++++ client/__tests__/e2e/test_config.yaml | 47 ++++++++++++++++++++ client/server/development.js | 30 +++++++++---- client/src/components/menubar/authButtons.js | 6 ++- 6 files changed, 94 insertions(+), 16 deletions(-) create mode 100644 client/__tests__/e2e/test_config.yaml diff --git a/client/Makefile b/client/Makefile index 3b36337a..eb5c1a78 100644 --- a/client/Makefile +++ b/client/Makefile @@ -3,6 +3,8 @@ include ../common.mk ANNOTATIONS := $(if $(ANNOTATIONS),$(ANNOTATIONS),../server/test/fixtures/pbmc3k-annotations.csv) ANNOTATIONS_FILENAME := $(shell basename $(ANNOTATIONS)) +CXG_CONFIG := $(if $(CXG_CONFIG), $(CXG_CONFIG), ./__tests__/e2e/test_config.yaml) + # Packaging .PHONY: clean clean: @@ -31,7 +33,7 @@ start-frontend: .PHONY: smoke-test smoke-test: start_server_and_test \ - 'CXG_OPTIONS="--disable-annotations" $(MAKE) start-server' \ + 'CXG_OPTIONS="--config-file $(CXG_CONFIG)" $(MAKE) start-server' \ $(CXG_SERVER_PORT) \ 'CXG_URL_BASE="http://localhost:$(CXG_SERVER_PORT)" npm run e2e -- --verbose false' diff --git a/client/__tests__/e2e/cellxgeneActions.js b/client/__tests__/e2e/cellxgeneActions.js index 9fda69b1..7ffcc8cf 100644 --- a/client/__tests__/e2e/cellxgeneActions.js +++ b/client/__tests__/e2e/cellxgeneActions.js @@ -322,7 +322,7 @@ export async function login() { await goToPage(appUrlBase); - await clickOn("auth-button"); + await clickOn("log-in"); // (thuang): Auth0 form is unstable and unsafe for input until verified await waitUntilFormFieldStable('[name="email"]'); @@ -341,16 +341,15 @@ export async function login() { } export async function logout() { - await clickOnUntil("menu", async () => { - await expect(page).toMatch("Log Out"); - + await clickOnUntil("user-info", async () => { + await waitByID("log-out"); await Promise.all([ page.waitForNavigation({ waitUntil: "networkidle0" }), - expect(page).toClick("a", { text: "Log Out" }), + clickOn("log-out"), ]); }); - await expect(page).toMatch("Log In"); + await waitByID("log-in"); } async function waitUntilFormFieldStable(selector) { diff --git a/client/__tests__/e2e/e2e.test.js b/client/__tests__/e2e/e2e.test.js index 05f52314..2bd8ea8c 100644 --- a/client/__tests__/e2e/e2e.test.js +++ b/client/__tests__/e2e/e2e.test.js @@ -17,6 +17,7 @@ import { goToPage, typeInto, waitByID, + clickOnUntil, } from "./puppeteerUtils"; import { @@ -521,6 +522,17 @@ test("lasso moves after pan", async () => { expect(panCount).toBe(initialCount); }); +describe("auth buttons", () => { + test("login then logout", async () => { + await goToPage(appUrlBase); + await clickOnUntil("log-in", async () => { + await page.waitForNavigation({ waitUntil: "networkidle0" }); + await waitByID("user-info"); + }); + await logout(); + }); +}); + const conditionalDescribe = process.env.TEST_AUTH_INTEGRATION === "true" ? describe : describe.skip; diff --git a/client/__tests__/e2e/test_config.yaml b/client/__tests__/e2e/test_config.yaml new file mode 100644 index 00000000..11576878 --- /dev/null +++ b/client/__tests__/e2e/test_config.yaml @@ -0,0 +1,47 @@ +server: + app: + force_https: true + + # By default, cellxgene will serve api requests from the same base url as the webpage. + # In general api_base_url and web_base_url will not need to be set. + # There are two reasons to set these parameters: + # 1. Oauth authentication is used; the oauth server will redirect back to the api_base_url after login, + # which then redirects back to the web_base_url. If the web_base_url is not set, it will default to + # the api_base_url. If oauth authentication is used, the api_base_url must be set. + # For a local test (where the server runs on "http://localhost:"), then the api_base_url may be + # set to the string "local". + # 2. The cellxgene deploymnent is in an environment where the webpage and api have + # different base urls. In this case both api_base_url and web_base_url must be set. + # It is up to the server admin to ensure that the networking is setup correctly for this environment. + api_base_url: http://localhost:5005 + web_base_url: http://localhost:3000 + + authentication: + # The authentication types may be "none", "session", "oauth" + # none: No authentication support, features like user_annotations must not be enabled. + # session: A session based userid is automatically generated. (no params needed) + # oauth: oauth2 is used for authentication; parameters are defined in params_oauth. + type: test + +dataset: + app: + about_legal_tos: null + about_legal_privacy: null + + presentation: + max_categories: 1000 + custom_colors: true + + user_annotations: + enable: false + type: local_file_csv + local_file_csv: + directory: null + file: null + ontology: + enable: false + obo_location: null + + embeddings: + names: [] + enable_reembedding: false diff --git a/client/server/development.js b/client/server/development.js index 0b3e3811..1530856e 100644 --- a/client/server/development.js +++ b/client/server/development.js @@ -1,5 +1,3 @@ -const path = require("path"); -const historyApiFallback = require("connect-history-api-fallback"); const chalk = require("chalk"); const express = require("express"); const favicon = require("serve-favicon"); @@ -11,35 +9,51 @@ const utils = require("./utils"); process.env.NODE_ENV = "development"; const CLIENT_PORT = process.env.CXG_CLIENT_PORT; +const { CXG_SERVER_PORT } = process.env; + +const API = { + prefix: `http://localhost:${CXG_SERVER_PORT}/`, +}; // Set up compiler const compiler = webpack(config); -compiler.plugin("invalid", () => { +compiler.hooks.invalid.tap("invalid", () => { utils.clearConsole(); console.log("Compiling..."); }); -compiler.plugin("done", (stats) => { +compiler.hooks.done.tap("done", (stats) => { utils.formatStats(stats, CLIENT_PORT); }); // Launch server const app = express(); -app.use(historyApiFallback({ verbose: false })); - app.use( devMiddleware(compiler, { logLevel: "warn", publicPath: config.output.publicPath, + index: true, }) ); app.use(favicon("./favicon.png")); -app.get("*", (req, res) => { - res.sendFile(path.resolve("index.html")); +app.get("/login", async (req, res) => { + try { + res.redirect(`${API.prefix}login?dataset=http://localhost:${CLIENT_PORT}`); + } catch (err) { + console.error(err); + } +}); + +app.get("/logout", async (req, res) => { + try { + res.redirect(`${API.prefix}logout?dataset=http://localhost:${CLIENT_PORT}`); + } catch (err) { + console.error(err); + } }); app.listen(CLIENT_PORT, (err) => { diff --git a/client/src/components/menubar/authButtons.js b/client/src/components/menubar/authButtons.js index 60ace82b..129125ec 100644 --- a/client/src/components/menubar/authButtons.js +++ b/client/src/components/menubar/authButtons.js @@ -51,7 +51,11 @@ const Auth = React.memo((props) => { return ( -