mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-05 05:18:12 +08:00
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
This commit is contained in:
+3
-1
@@ -3,6 +3,8 @@ include ../common.mk
|
|||||||
ANNOTATIONS := $(if $(ANNOTATIONS),$(ANNOTATIONS),../server/test/fixtures/pbmc3k-annotations.csv)
|
ANNOTATIONS := $(if $(ANNOTATIONS),$(ANNOTATIONS),../server/test/fixtures/pbmc3k-annotations.csv)
|
||||||
ANNOTATIONS_FILENAME := $(shell basename $(ANNOTATIONS))
|
ANNOTATIONS_FILENAME := $(shell basename $(ANNOTATIONS))
|
||||||
|
|
||||||
|
CXG_CONFIG := $(if $(CXG_CONFIG), $(CXG_CONFIG), ./__tests__/e2e/test_config.yaml)
|
||||||
|
|
||||||
# Packaging
|
# Packaging
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
@@ -31,7 +33,7 @@ start-frontend:
|
|||||||
.PHONY: smoke-test
|
.PHONY: smoke-test
|
||||||
smoke-test:
|
smoke-test:
|
||||||
start_server_and_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_SERVER_PORT) \
|
||||||
'CXG_URL_BASE="http://localhost:$(CXG_SERVER_PORT)" npm run e2e -- --verbose false'
|
'CXG_URL_BASE="http://localhost:$(CXG_SERVER_PORT)" npm run e2e -- --verbose false'
|
||||||
|
|
||||||
|
|||||||
@@ -322,7 +322,7 @@ export async function login() {
|
|||||||
|
|
||||||
await goToPage(appUrlBase);
|
await goToPage(appUrlBase);
|
||||||
|
|
||||||
await clickOn("auth-button");
|
await clickOn("log-in");
|
||||||
|
|
||||||
// (thuang): Auth0 form is unstable and unsafe for input until verified
|
// (thuang): Auth0 form is unstable and unsafe for input until verified
|
||||||
await waitUntilFormFieldStable('[name="email"]');
|
await waitUntilFormFieldStable('[name="email"]');
|
||||||
@@ -341,16 +341,15 @@ export async function login() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function logout() {
|
export async function logout() {
|
||||||
await clickOnUntil("menu", async () => {
|
await clickOnUntil("user-info", async () => {
|
||||||
await expect(page).toMatch("Log Out");
|
await waitByID("log-out");
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
page.waitForNavigation({ waitUntil: "networkidle0" }),
|
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) {
|
async function waitUntilFormFieldStable(selector) {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
goToPage,
|
goToPage,
|
||||||
typeInto,
|
typeInto,
|
||||||
waitByID,
|
waitByID,
|
||||||
|
clickOnUntil,
|
||||||
} from "./puppeteerUtils";
|
} from "./puppeteerUtils";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -521,6 +522,17 @@ test("lasso moves after pan", async () => {
|
|||||||
expect(panCount).toBe(initialCount);
|
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 =
|
const conditionalDescribe =
|
||||||
process.env.TEST_AUTH_INTEGRATION === "true" ? describe : describe.skip;
|
process.env.TEST_AUTH_INTEGRATION === "true" ? describe : describe.skip;
|
||||||
|
|
||||||
|
|||||||
@@ -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:<port>"), 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
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
const path = require("path");
|
|
||||||
const historyApiFallback = require("connect-history-api-fallback");
|
|
||||||
const chalk = require("chalk");
|
const chalk = require("chalk");
|
||||||
const express = require("express");
|
const express = require("express");
|
||||||
const favicon = require("serve-favicon");
|
const favicon = require("serve-favicon");
|
||||||
@@ -11,35 +9,51 @@ const utils = require("./utils");
|
|||||||
process.env.NODE_ENV = "development";
|
process.env.NODE_ENV = "development";
|
||||||
|
|
||||||
const CLIENT_PORT = process.env.CXG_CLIENT_PORT;
|
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
|
// Set up compiler
|
||||||
const compiler = webpack(config);
|
const compiler = webpack(config);
|
||||||
|
|
||||||
compiler.plugin("invalid", () => {
|
compiler.hooks.invalid.tap("invalid", () => {
|
||||||
utils.clearConsole();
|
utils.clearConsole();
|
||||||
console.log("Compiling...");
|
console.log("Compiling...");
|
||||||
});
|
});
|
||||||
|
|
||||||
compiler.plugin("done", (stats) => {
|
compiler.hooks.done.tap("done", (stats) => {
|
||||||
utils.formatStats(stats, CLIENT_PORT);
|
utils.formatStats(stats, CLIENT_PORT);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Launch server
|
// Launch server
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
app.use(historyApiFallback({ verbose: false }));
|
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
devMiddleware(compiler, {
|
devMiddleware(compiler, {
|
||||||
logLevel: "warn",
|
logLevel: "warn",
|
||||||
publicPath: config.output.publicPath,
|
publicPath: config.output.publicPath,
|
||||||
|
index: true,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
app.use(favicon("./favicon.png"));
|
app.use(favicon("./favicon.png"));
|
||||||
|
|
||||||
app.get("*", (req, res) => {
|
app.get("/login", async (req, res) => {
|
||||||
res.sendFile(path.resolve("index.html"));
|
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) => {
|
app.listen(CLIENT_PORT, (err) => {
|
||||||
|
|||||||
@@ -51,7 +51,11 @@ const Auth = React.memo((props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover content={PopoverContent}>
|
<Popover content={PopoverContent}>
|
||||||
<Button className={styles.menubarButton} style={{ padding: 0 }}>
|
<Button
|
||||||
|
data-testid="user-info"
|
||||||
|
className={styles.menubarButton}
|
||||||
|
style={{ padding: 0 }}
|
||||||
|
>
|
||||||
{userinfo?.picture ? (
|
{userinfo?.picture ? (
|
||||||
<img alt="profile" src={userinfo?.picture} />
|
<img alt="profile" src={userinfo?.picture} />
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
Reference in New Issue
Block a user