mirror of https://github.com/coder/coder.git
chore: add e2e test against an external auth provider during workspace creation (#12985)
This commit is contained in:
parent
75223dfd8b
commit
319fd5bf1d
|
@ -31,6 +31,7 @@ import {
|
|||
type Resource,
|
||||
Response,
|
||||
type RichParameter,
|
||||
type ExternalAuthProviderResource,
|
||||
} from "./provisionerGenerated";
|
||||
|
||||
// requiresEnterpriseLicense will skip the test if we're not running with an enterprise license
|
||||
|
@ -49,6 +50,7 @@ export const createWorkspace = async (
|
|||
templateName: string,
|
||||
richParameters: RichParameter[] = [],
|
||||
buildParameters: WorkspaceBuildParameter[] = [],
|
||||
useExternalAuthProvider: string | undefined = undefined,
|
||||
): Promise<string> => {
|
||||
await page.goto(`/templates/${templateName}/workspace`, {
|
||||
waitUntil: "domcontentloaded",
|
||||
|
@ -59,6 +61,25 @@ export const createWorkspace = async (
|
|||
await page.getByLabel("name").fill(name);
|
||||
|
||||
await fillParameters(page, richParameters, buildParameters);
|
||||
|
||||
if (useExternalAuthProvider !== undefined) {
|
||||
// Create a new context for the popup which will be created when clicking the button
|
||||
const popupPromise = page.waitForEvent("popup");
|
||||
|
||||
// Find the "Login with <Provider>" button
|
||||
const externalAuthLoginButton = page
|
||||
.getByRole("button")
|
||||
.getByText("Login with GitHub");
|
||||
await expect(externalAuthLoginButton).toBeVisible();
|
||||
|
||||
// Click it
|
||||
await externalAuthLoginButton.click();
|
||||
|
||||
// Wait for authentication to occur
|
||||
const popup = await popupPromise;
|
||||
await popup.waitForSelector("text=You are now authenticated.");
|
||||
}
|
||||
|
||||
await page.getByTestId("form-submit").click();
|
||||
|
||||
await expectUrl(page).toHavePathName("/@admin/" + name);
|
||||
|
@ -648,6 +669,37 @@ export const echoResponsesWithParameters = (
|
|||
};
|
||||
};
|
||||
|
||||
export const echoResponsesWithExternalAuth = (
|
||||
providers: ExternalAuthProviderResource[],
|
||||
): EchoProvisionerResponses => {
|
||||
return {
|
||||
parse: [
|
||||
{
|
||||
parse: {},
|
||||
},
|
||||
],
|
||||
plan: [
|
||||
{
|
||||
plan: {
|
||||
externalAuthProviders: providers,
|
||||
},
|
||||
},
|
||||
],
|
||||
apply: [
|
||||
{
|
||||
apply: {
|
||||
externalAuthProviders: providers,
|
||||
resources: [
|
||||
{
|
||||
name: "example",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
export const fillParameters = async (
|
||||
page: Page,
|
||||
richParameters: RichParameter[] = [],
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import type { Page } from "@playwright/test";
|
||||
import type { BrowserContext, Page } from "@playwright/test";
|
||||
import http from "http";
|
||||
import { coderPort, gitAuth } from "./constants";
|
||||
|
||||
export const beforeCoderTest = async (page: Page) => {
|
||||
// eslint-disable-next-line no-console -- Show everything that was printed with console.log()
|
||||
|
@ -45,6 +47,41 @@ export const beforeCoderTest = async (page: Page) => {
|
|||
});
|
||||
};
|
||||
|
||||
export const resetExternalAuthKey = async (context: BrowserContext) => {
|
||||
// Find the session token so we can destroy the external auth link between tests, to ensure valid authentication happens each time.
|
||||
const cookies = await context.cookies();
|
||||
const sessionCookie = cookies.find((c) => c.name === "coder_session_token");
|
||||
const options = {
|
||||
method: "DELETE",
|
||||
hostname: "127.0.0.1",
|
||||
port: coderPort,
|
||||
path: `/api/v2/external-auth/${gitAuth.webProvider}?coder_session_token=${sessionCookie?.value}`,
|
||||
};
|
||||
|
||||
const req = http.request(options, (res) => {
|
||||
let data = "";
|
||||
res.on("data", (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
res.on("end", () => {
|
||||
// Both 200 (key deleted successfully) and 500 (key was not found) are valid responses.
|
||||
if (res.statusCode !== 200 && res.statusCode !== 500) {
|
||||
console.error("failed to delete external auth link", data);
|
||||
throw new Error(
|
||||
`failed to delete external auth link: HTTP response ${res.statusCode}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on("error", (err) => {
|
||||
throw err.message;
|
||||
});
|
||||
|
||||
req.end();
|
||||
};
|
||||
|
||||
const isApiCall = (urlString: string): boolean => {
|
||||
const url = new URL(urlString);
|
||||
const apiPath = "/api/v2";
|
||||
|
|
|
@ -115,7 +115,7 @@ export default defineConfig({
|
|||
// Tests for Deployment / User Authentication / OIDC
|
||||
CODER_OIDC_ISSUER_URL: "https://accounts.google.com",
|
||||
CODER_OIDC_EMAIL_DOMAIN: "coder.com",
|
||||
CODER_OIDC_CLIENT_ID: "1234567890", // FIXME: https://github.com/coder/coder/issues/12585
|
||||
CODER_OIDC_CLIENT_ID: "1234567890",
|
||||
CODER_OIDC_CLIENT_SECRET: "1234567890Secret",
|
||||
CODER_OIDC_ALLOW_SIGNUPS: "false",
|
||||
CODER_OIDC_SIGN_IN_TEXT: "Hello",
|
||||
|
|
|
@ -2,8 +2,37 @@ import type { Endpoints } from "@octokit/types";
|
|||
import { test } from "@playwright/test";
|
||||
import type { ExternalAuthDevice } from "api/typesGenerated";
|
||||
import { gitAuth } from "../constants";
|
||||
import { Awaiter, createServer } from "../helpers";
|
||||
import { beforeCoderTest } from "../hooks";
|
||||
import {
|
||||
Awaiter,
|
||||
createServer,
|
||||
createTemplate,
|
||||
createWorkspace,
|
||||
echoResponsesWithExternalAuth,
|
||||
} from "../helpers";
|
||||
import { beforeCoderTest, resetExternalAuthKey } from "../hooks";
|
||||
|
||||
test.beforeAll(async ({ baseURL }) => {
|
||||
const srv = await createServer(gitAuth.webPort);
|
||||
|
||||
// The GitHub validate endpoint returns the currently authenticated user!
|
||||
srv.use(gitAuth.validatePath, (req, res) => {
|
||||
res.write(JSON.stringify(ghUser));
|
||||
res.end();
|
||||
});
|
||||
srv.use(gitAuth.tokenPath, (req, res) => {
|
||||
const r = (Math.random() + 1).toString(36).substring(7);
|
||||
res.write(JSON.stringify({ access_token: r }));
|
||||
res.end();
|
||||
});
|
||||
srv.use(gitAuth.authPath, (req, res) => {
|
||||
res.redirect(
|
||||
`${baseURL}/external-auth/${gitAuth.webProvider}/callback?code=1234&state=` +
|
||||
req.query.state,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ context }) => resetExternalAuthKey(context));
|
||||
|
||||
test.beforeEach(({ page }) => beforeCoderTest(page));
|
||||
|
||||
|
@ -57,23 +86,7 @@ test("external auth device", async ({ page }) => {
|
|||
await page.waitForSelector("text=1 organization authorized");
|
||||
});
|
||||
|
||||
test("external auth web", async ({ baseURL, page }) => {
|
||||
const srv = await createServer(gitAuth.webPort);
|
||||
// The GitHub validate endpoint returns the currently authenticated user!
|
||||
srv.use(gitAuth.validatePath, (req, res) => {
|
||||
res.write(JSON.stringify(ghUser));
|
||||
res.end();
|
||||
});
|
||||
srv.use(gitAuth.tokenPath, (req, res) => {
|
||||
res.write(JSON.stringify({ access_token: "hello-world" }));
|
||||
res.end();
|
||||
});
|
||||
srv.use(gitAuth.authPath, (req, res) => {
|
||||
res.redirect(
|
||||
`${baseURL}/external-auth/${gitAuth.webProvider}/callback?code=1234&state=` +
|
||||
req.query.state,
|
||||
);
|
||||
});
|
||||
test("external auth web", async ({ page }) => {
|
||||
await page.goto(`/external-auth/${gitAuth.webProvider}`, {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
|
@ -81,6 +94,17 @@ test("external auth web", async ({ baseURL, page }) => {
|
|||
await page.waitForSelector("text=You've authenticated with GitHub!");
|
||||
});
|
||||
|
||||
test("successful external auth from workspace", async ({ page }) => {
|
||||
const templateName = await createTemplate(
|
||||
page,
|
||||
echoResponsesWithExternalAuth([
|
||||
{ id: gitAuth.webProvider, optional: false },
|
||||
]),
|
||||
);
|
||||
|
||||
await createWorkspace(page, templateName, [], [], gitAuth.webProvider);
|
||||
});
|
||||
|
||||
const ghUser: Endpoints["GET /user"]["response"]["data"] = {
|
||||
login: "kylecarbs",
|
||||
id: 7122116,
|
||||
|
|
Loading…
Reference in New Issue