diff --git a/site/e2e/api.ts b/site/e2e/api.ts index c100c16d36..940dcf11ab 100644 --- a/site/e2e/api.ts +++ b/site/e2e/api.ts @@ -1,6 +1,7 @@ import type { Page } from "@playwright/test"; import { expect } from "@playwright/test"; import * as API from "api/api"; +import type { SerpentOption } from "api/typesGenerated"; import { coderPort } from "./constants"; import { findSessionToken, randomName } from "./helpers"; @@ -49,67 +50,92 @@ export const createGroup = async (orgId: string) => { return group; }; -export async function verifyConfigFlag( +export async function verifyConfigFlagBoolean( page: Page, config: API.DeploymentConfig, flag: string, ) { + const opt = findConfigOption(config, flag); + const type = opt.value ? "option-enabled" : "option-disabled"; + const value = opt.value ? "Enabled" : "Disabled"; + + const configOption = page.locator( + `div.options-table .option-${flag} .${type}`, + ); + await expect(configOption).toHaveText(value); +} + +export async function verifyConfigFlagNumber( + page: Page, + config: API.DeploymentConfig, + flag: string, +) { + const opt = findConfigOption(config, flag); + const configOption = page.locator( + `div.options-table .option-${flag} .option-value-number`, + ); + await expect(configOption).toHaveText(String(opt.value)); +} + +export async function verifyConfigFlagString( + page: Page, + config: API.DeploymentConfig, + flag: string, +) { + const opt = findConfigOption(config, flag); + + const configOption = page.locator( + `div.options-table .option-${flag} .option-value-string`, + ); + await expect(configOption).toHaveText(opt.value); +} + +export async function verifyConfigFlagArray( + page: Page, + config: API.DeploymentConfig, + flag: string, +) { + const opt = findConfigOption(config, flag); + const configOption = page.locator( + `div.options-table .option-${flag} .option-array`, + ); + + // Verify array of options with simple dots + for (const item of opt.value) { + await expect(configOption.locator("li", { hasText: item })).toBeVisible(); + } +} + +export async function verifyConfigFlagEntries( + page: Page, + config: API.DeploymentConfig, + flag: string, +) { + const opt = findConfigOption(config, flag); + const configOption = page.locator( + `div.options-table .option-${flag} .option-array`, + ); + + // Verify array of options with green marks. + Object.entries(opt.value) + .sort((a, b) => a[0].localeCompare(b[0])) + .map(async ([item]) => { + await expect( + configOption.locator(`.option-array-item-${item}.option-enabled`, { + hasText: item, + }), + ).toBeVisible(); + }); +} + +export function findConfigOption( + config: API.DeploymentConfig, + flag: string, +): SerpentOption { const opt = config.options.find((option) => option.flag === flag); if (opt === undefined) { // must be undefined as `false` is expected throw new Error(`Option with env ${flag} has undefined value.`); } - - // Map option type to test class name. - let type: string; - let value = opt.value; - - if (typeof value === "boolean") { - // Boolean options map to string (Enabled/Disabled). - type = value ? "option-enabled" : "option-disabled"; - value = value ? "Enabled" : "Disabled"; - } else if (typeof value === "number") { - type = "option-value-number"; - value = String(value); - } else if (!value || value.length === 0) { - type = "option-value-empty"; - } else if (typeof value === "string") { - type = "option-value-string"; - } else if (typeof value === "object") { - type = "option-array"; - } else { - type = "option-value-json"; - } - - // Special cases - if (opt.flag === "strict-transport-security" && opt.value === 0) { - type = "option-value-string"; - value = "Disabled"; // Display "Disabled" instead of zero seconds. - } - - const configOption = page.locator( - `div.options-table .option-${flag} .${type}`, - ); - - // Verify array of options with green marks. - if (typeof value === "object" && !Array.isArray(value)) { - Object.entries(value) - .sort((a, b) => a[0].localeCompare(b[0])) - .map(async ([item]) => { - await expect( - configOption.locator(`.option-array-item-${item}.option-enabled`, { - hasText: item, - }), - ).toBeVisible(); - }); - return; - } - // Verify array of options with simmple dots - if (Array.isArray(value)) { - for (const item of value) { - await expect(configOption.locator("li", { hasText: item })).toBeVisible(); - } - return; - } - await expect(configOption).toHaveText(String(value)); + return opt; } diff --git a/site/e2e/tests/deployment/security.spec.ts b/site/e2e/tests/deployment/security.spec.ts index 4aa81f109a..ede966260c 100644 --- a/site/e2e/tests/deployment/security.spec.ts +++ b/site/e2e/tests/deployment/security.spec.ts @@ -1,6 +1,14 @@ -import { test } from "@playwright/test"; +import type { Page } from "@playwright/test"; +import { expect, test } from "@playwright/test"; +import type * as API from "api/api"; import { getDeploymentConfig } from "api/api"; -import { setupApiCalls, verifyConfigFlag } from "../../api"; +import { + findConfigOption, + setupApiCalls, + verifyConfigFlagBoolean, + verifyConfigFlagNumber, + verifyConfigFlagString, +} from "../../api"; test("enabled security settings", async ({ page }) => { await setupApiCalls(page); @@ -8,21 +16,32 @@ test("enabled security settings", async ({ page }) => { await page.goto("/deployment/security", { waitUntil: "domcontentloaded" }); - const flags = [ - "ssh-keygen-algorithm", - "secure-auth-cookie", - "disable-owner-workspace-access", + await verifyConfigFlagString(page, config, "ssh-keygen-algorithm"); + await verifyConfigFlagBoolean(page, config, "secure-auth-cookie"); + await verifyConfigFlagBoolean(page, config, "disable-owner-workspace-access"); - "tls-redirect-http-to-https", - "strict-transport-security", - "tls-address", - "tls-allow-insecure-ciphers", - "tls-client-auth", - "tls-enable", - "tls-min-version", - ]; - - for (const flag of flags) { - await verifyConfigFlag(page, config, flag); - } + await verifyConfigFlagBoolean(page, config, "tls-redirect-http-to-https"); + await verifyStrictTransportSecurity(page, config); + await verifyConfigFlagString(page, config, "tls-address"); + await verifyConfigFlagBoolean(page, config, "tls-allow-insecure-ciphers"); + await verifyConfigFlagString(page, config, "tls-client-auth"); + await verifyConfigFlagBoolean(page, config, "tls-enable"); + await verifyConfigFlagString(page, config, "tls-min-version"); }); + +async function verifyStrictTransportSecurity( + page: Page, + config: API.DeploymentConfig, +) { + const flag = "strict-transport-security"; + const opt = findConfigOption(config, flag); + if (opt.value !== 0) { + await verifyConfigFlagNumber(page, config, flag); + return; + } + + const configOption = page.locator( + `div.options-table .option-${flag} .option-value-string`, + ); + await expect(configOption).toHaveText("Disabled"); +} diff --git a/site/e2e/tests/deployment/userAuth.spec.ts b/site/e2e/tests/deployment/userAuth.spec.ts index 68e28fe6b7..cf656c99fa 100644 --- a/site/e2e/tests/deployment/userAuth.spec.ts +++ b/site/e2e/tests/deployment/userAuth.spec.ts @@ -1,6 +1,12 @@ import { test } from "@playwright/test"; import { getDeploymentConfig } from "api/api"; -import { setupApiCalls, verifyConfigFlag } from "../../api"; +import { + setupApiCalls, + verifyConfigFlagArray, + verifyConfigFlagBoolean, + verifyConfigFlagEntries, + verifyConfigFlagString, +} from "../../api"; test("login with OIDC", async ({ page }) => { await setupApiCalls(page); @@ -8,26 +14,20 @@ test("login with OIDC", async ({ page }) => { await page.goto("/deployment/userauth", { waitUntil: "domcontentloaded" }); - const flags = [ - "oidc-group-auto-create", - "oidc-allow-signups", - "oidc-auth-url-params", - "oidc-client-id", - "oidc-email-domain", - "oidc-email-field", - "oidc-group-mapping", - "oidc-ignore-email-verified", - "oidc-ignore-userinfo", - "oidc-issuer-url", - "oidc-group-regex-filter", - "oidc-scopes", - "oidc-user-role-mapping", - "oidc-username-field", - "oidc-sign-in-text", - "oidc-icon-url", - ]; - - for (const flag of flags) { - await verifyConfigFlag(page, config, flag); - } + await verifyConfigFlagBoolean(page, config, "oidc-group-auto-create"); + await verifyConfigFlagBoolean(page, config, "oidc-allow-signups"); + await verifyConfigFlagEntries(page, config, "oidc-auth-url-params"); + await verifyConfigFlagString(page, config, "oidc-client-id"); + await verifyConfigFlagArray(page, config, "oidc-email-domain"); + await verifyConfigFlagString(page, config, "oidc-email-field"); + await verifyConfigFlagEntries(page, config, "oidc-group-mapping"); + await verifyConfigFlagBoolean(page, config, "oidc-ignore-email-verified"); + await verifyConfigFlagBoolean(page, config, "oidc-ignore-userinfo"); + await verifyConfigFlagString(page, config, "oidc-issuer-url"); + await verifyConfigFlagString(page, config, "oidc-group-regex-filter"); + await verifyConfigFlagArray(page, config, "oidc-scopes"); + await verifyConfigFlagEntries(page, config, "oidc-user-role-mapping"); + await verifyConfigFlagString(page, config, "oidc-username-field"); + await verifyConfigFlagString(page, config, "oidc-sign-in-text"); + await verifyConfigFlagString(page, config, "oidc-icon-url"); });