Skip to content

Commit a4d778e

Browse files
committed
yaaay
1 parent 924e7dc commit a4d778e

File tree

10 files changed

+239
-93
lines changed

10 files changed

+239
-93
lines changed

site/e2e/api.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,9 @@ import { findSessionToken, randomName } from "./helpers";
99
let currentOrgId: string;
1010

1111
export const setupApiCalls = async (page: Page) => {
12-
try {
13-
const token = await findSessionToken(page);
14-
API.setSessionToken(token);
15-
} catch {
16-
// If this fails, we have an unauthenticated client.
17-
}
18-
1912
API.setHost(`http://127.0.0.1:${coderPort}`);
13+
const token = await findSessionToken(page);
14+
API.setSessionToken(token);
2015
};
2116

2217
export const getCurrentOrgId = async (): Promise<string> => {

site/e2e/constants.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as path from "node:path";
2+
import { string } from "yup";
23

34
export const coderMain = path.join(__dirname, "../../enterprise/cmd/coder");
45

@@ -15,11 +16,30 @@ export const coderdPProfPort = 6062;
1516

1617
// The name of the organization that should be used by default when needed.
1718
export const defaultOrganizationName = "coder";
19+
export const defaultPassword = "SomeSecurePassword!";
1820

19-
// Credentials for the first user
20-
export const username = "admin";
21-
export const password = "SomeSecurePassword!";
22-
export const email = "admin@coder.com";
21+
// Credentials for users
22+
export const users = {
23+
admin: {
24+
username: "admin",
25+
password: defaultPassword,
26+
email: "admin@coder.com",
27+
},
28+
auditor: {
29+
username: "auditor",
30+
password: defaultPassword,
31+
email: "auditor@coder.com",
32+
roles: ["Template Admin", "Auditor"],
33+
},
34+
user: {
35+
username: "user",
36+
password: defaultPassword,
37+
email: "user@coder.com",
38+
},
39+
} satisfies Record<
40+
string,
41+
{ username: string; password: string; email: string; roles?: string[] }
42+
>;
2343

2444
export const gitAuth = {
2545
deviceProvider: "device",

site/e2e/helpers.ts

Lines changed: 71 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ import {
1818
coderMain,
1919
coderPort,
2020
defaultOrganizationName,
21-
email,
21+
users,
2222
license,
23-
password,
2423
premiumTestsRequired,
2524
prometheusPort,
2625
requireTerraformTests,
26+
defaultPassword,
2727
} from "./constants";
2828
import { expectUrl } from "./expectUrl";
2929
import {
@@ -62,40 +62,74 @@ export function requireTerraformProvisioner() {
6262
}
6363

6464
type LoginOptions = {
65+
username: string;
6566
email: string;
6667
password: string;
6768
};
6869

69-
export async function login(page: Page, options?: LoginOptions) {
70+
export async function login(page: Page, options: LoginOptions = users.admin) {
71+
const ctx = page.context();
72+
// biome-ignore lint/suspicious/noExplicitAny: shenanigans to make life easier
73+
(ctx as any)[Symbol.for("currentUser")] = undefined;
74+
await ctx.clearCookies();
7075
await page.goto("/login");
71-
await page.getByLabel("Email").fill(options?.email || email);
72-
await page.getByLabel("Password").fill(options?.password || password);
76+
await page.getByLabel("Email").fill(options.email);
77+
await page.getByLabel("Password").fill(options.password);
7378
await page.getByRole("button", { name: "Sign In" }).click();
7479
await expectUrl(page).toHavePathName("/workspaces");
80+
// biome-ignore lint/suspicious/noExplicitAny: shenanigans to make life easier
81+
(ctx as any)[Symbol.for("currentUser")] = options;
7582
}
7683

84+
export function currentUser(page: Page): LoginOptions {
85+
const ctx = page.context();
86+
// biome-ignore lint/suspicious/noExplicitAny: shenanigans to make life easier
87+
const user = (ctx as any)[Symbol.for("currentUser")];
88+
89+
if (!user) {
90+
throw new Error("page context does not have a user. did you call `login`?");
91+
}
92+
93+
return user;
94+
}
95+
96+
type CreateWorkspaceOptions = {
97+
richParameters?: RichParameter[];
98+
buildParameters?: WorkspaceBuildParameter[];
99+
useExternalAuth?: boolean;
100+
};
101+
77102
/**
78103
* createWorkspace creates a workspace for a template. It does not wait for it
79104
* to be running, but it does navigate to the page.
80105
*/
81106
export const createWorkspace = async (
82107
page: Page,
83-
templateName: string,
84-
richParameters: RichParameter[] = [],
85-
buildParameters: WorkspaceBuildParameter[] = [],
86-
useExternalAuthProvider: string | undefined = undefined,
108+
template: string | { organization: string; name: string },
109+
options: CreateWorkspaceOptions = {},
87110
): Promise<string> => {
88-
await page.goto(`/templates/${templateName}/workspace`, {
111+
const {
112+
richParameters = [],
113+
buildParameters = [],
114+
useExternalAuth,
115+
} = options;
116+
117+
const templatePath =
118+
typeof template === "string"
119+
? template
120+
: `${template.organization}/${template.name}`;
121+
122+
await page.goto(`/templates/${templatePath}/workspace`, {
89123
waitUntil: "domcontentloaded",
90124
});
91-
await expectUrl(page).toHavePathName(`/templates/${templateName}/workspace`);
125+
await expectUrl(page).toHavePathName(`/templates/${templatePath}/workspace`);
92126

93127
const name = randomName();
94128
await page.getByLabel("name").fill(name);
95129

96130
await fillParameters(page, richParameters, buildParameters);
97131

98-
if (useExternalAuthProvider !== undefined) {
132+
if (useExternalAuth) {
99133
// Create a new context for the popup which will be created when clicking the button
100134
const popupPromise = page.waitForEvent("popup");
101135

@@ -115,7 +149,9 @@ export const createWorkspace = async (
115149

116150
await page.getByTestId("form-submit").click();
117151

118-
await expectUrl(page).toHavePathName(`/@admin/${name}`);
152+
const user = currentUser(page);
153+
154+
await expectUrl(page).toHavePathName(`/@${user.username}/${name}`);
119155

120156
await page.waitForSelector("[data-testid='build-status'] >> text=Running", {
121157
state: "visible",
@@ -228,6 +264,12 @@ export const createTemplate = async (
228264
const orgPicker = page.getByLabel("Belongs to *");
229265
const organizationsEnabled = await orgPicker.isVisible();
230266
if (organizationsEnabled) {
267+
if (orgName !== defaultOrganizationName) {
268+
throw new Error(
269+
`No provisioners registered for ${orgName}, creating this template will fail`,
270+
);
271+
}
272+
231273
await orgPicker.click();
232274
await page.getByText(orgName, { exact: true }).click();
233275
}
@@ -673,8 +715,9 @@ const createTemplateVersionTar = async (
673715
);
674716
};
675717

676-
export const randomName = () => {
677-
return randomUUID().slice(0, 8);
718+
export const randomName = (annotation?: string) => {
719+
const base = randomUUID().slice(0, 8);
720+
return annotation ? `${annotation}-${base}` : base;
678721
};
679722

680723
/**
@@ -1016,6 +1059,7 @@ type UserValues = {
10161059
username: string;
10171060
email: string;
10181061
password: string;
1062+
roles: string[];
10191063
};
10201064

10211065
export async function createUser(
@@ -1033,7 +1077,8 @@ export async function createUser(
10331077
const username = userValues.username ?? randomName();
10341078
const name = userValues.name ?? username;
10351079
const email = userValues.email ?? `${username}@coder.com`;
1036-
const password = userValues.password || "s3cure&password!";
1080+
const password = userValues.password || defaultPassword;
1081+
const roles = userValues.roles ?? [];
10371082

10381083
await page.getByLabel("Username").fill(username);
10391084
if (name) {
@@ -1050,10 +1095,18 @@ export async function createUser(
10501095
await expect(page.getByText("Successfully created user.")).toBeVisible();
10511096

10521097
await expect(page).toHaveTitle("Users - Coder");
1053-
await expect(page.locator("tr", { hasText: email })).toBeVisible();
1098+
const addedRow = page.locator("tr", { hasText: email });
1099+
await expect(addedRow).toBeVisible();
1100+
1101+
// Give them a role
1102+
await addedRow.getByLabel("Edit user roles").click();
1103+
for (const role of roles) {
1104+
await page.getByText(role, { exact: true }).click();
1105+
}
1106+
await page.mouse.click(10, 10); // close the popover by clicking outside of it
10541107

10551108
await page.goto(returnTo, { waitUntil: "domcontentloaded" });
1056-
return { name, username, email, password };
1109+
return { name, username, email, password, roles };
10571110
}
10581111

10591112
export async function createOrganization(page: Page): Promise<{

site/e2e/setup/createFirstUser.spec.ts renamed to site/e2e/setup/createUsers.spec.ts

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { expect, test } from "@playwright/test";
22
import { API } from "api/api";
33
import { Language } from "pages/CreateUserPage/CreateUserForm";
4-
import { setupApiCalls } from "../api";
5-
import * as constants from "../constants";
4+
import { coderPort, license, premiumTestsRequired, users } from "../constants";
65
import { expectUrl } from "../expectUrl";
6+
import { createUser } from "../helpers";
77

88
test("setup deployment", async ({ page }) => {
99
await page.goto("/", { waitUntil: "domcontentloaded" });
10-
await setupApiCalls(page);
10+
API.setHost(`http://127.0.0.1:${coderPort}`);
1111
const exists = await API.hasFirstUser();
1212
// First user already exists, abort early. All tests execute this as a dependency,
1313
// if you run multiple tests in the UI, this will fail unless we check this.
@@ -16,27 +16,35 @@ test("setup deployment", async ({ page }) => {
1616
}
1717

1818
// Setup first user
19-
await page.getByLabel(Language.usernameLabel).fill(constants.username);
20-
await page.getByLabel(Language.emailLabel).fill(constants.email);
21-
await page.getByLabel(Language.passwordLabel).fill(constants.password);
19+
await page.getByLabel(Language.usernameLabel).fill(users.admin.username);
20+
await page.getByLabel(Language.emailLabel).fill(users.admin.email);
21+
await page.getByLabel(Language.passwordLabel).fill(users.admin.password);
2222
await page.getByTestId("create").click();
2323

2424
await expectUrl(page).toHavePathName("/workspaces");
25-
2625
await page.getByTestId("button-select-template").isVisible();
2726

27+
for (const user of Object.values(users)) {
28+
// Already created as first user
29+
if (user.username === "admin") {
30+
continue;
31+
}
32+
33+
await createUser(page, user);
34+
}
35+
2836
// Setup license
29-
if (constants.premiumTestsRequired || constants.license) {
37+
if (premiumTestsRequired || license) {
3038
// Make sure that we have something that looks like a real license
31-
expect(constants.license).toBeTruthy();
32-
expect(constants.license.length).toBeGreaterThan(92); // the signature alone should be this long
33-
expect(constants.license.split(".").length).toBe(3); // otherwise it's invalid
39+
expect(license).toBeTruthy();
40+
expect(license.length).toBeGreaterThan(92); // the signature alone should be this long
41+
expect(license.split(".").length).toBe(3); // otherwise it's invalid
3442

3543
await page.goto("/deployment/licenses", { waitUntil: "domcontentloaded" });
3644
await expect(page).toHaveTitle("License Settings - Coder");
3745

3846
await page.getByText("Add a license").click();
39-
await page.getByRole("textbox").fill(constants.license);
47+
await page.getByRole("textbox").fill(license);
4048
await page.getByText("Upload License").click();
4149

4250
await expect(

site/e2e/tests/auditLogs.oldspec.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { expect, test } from "@playwright/test";
2+
import {
3+
createTemplate,
4+
createWorkspace,
5+
login,
6+
requiresLicense,
7+
} from "../helpers";
8+
import { beforeCoderTest } from "../hooks";
9+
10+
test.beforeEach(async ({ page }) => {
11+
beforeCoderTest(page);
12+
await login(page);
13+
});
14+
15+
test("inspecting and filtering audit logs", async ({ page }) => {
16+
requiresLicense();
17+
18+
const userName = "admin";
19+
// Do some stuff that should show up in the audit logs
20+
const templateName = await createTemplate(page);
21+
const workspaceName = await createWorkspace(page, templateName);
22+
23+
// Go to the audit history
24+
await page.goto("/audit");
25+
26+
const loginMessage = `${userName} logged in`;
27+
const startedWorkspaceMessage = `${userName} started workspace ${workspaceName}`;
28+
29+
// Make sure those things we did all actually show up
30+
await expect(page.getByText(loginMessage).first()).toBeVisible();
31+
32+
await page.getByText("All actions").click();
33+
await page.getByText("Create", { exact: true }).click();
34+
35+
await expect(
36+
page.getByText(`${userName} created template ${templateName}`),
37+
).toBeVisible();
38+
await expect(
39+
page.getByText(`${userName} created workspace ${workspaceName}`),
40+
).toBeVisible();
41+
42+
// Make sure we can inspect the details of the log item
43+
const createdWorkspace = page.locator(".MuiTableRow-root", {
44+
hasText: `${userName} created workspace ${workspaceName}`,
45+
});
46+
await createdWorkspace.getByLabel("open-dropdown").click();
47+
await expect(
48+
createdWorkspace.getByText(`automatic_updates: "never"`),
49+
).toBeVisible();
50+
await expect(
51+
createdWorkspace.getByText(`name: "${workspaceName}"`),
52+
).toBeVisible();
53+
await page.getByLabel("Clear search").click();
54+
await expect(page.getByText("All actions")).toBeVisible();
55+
56+
await page.getByText("All actions").click();
57+
await page.getByText("Start", { exact: true }).click();
58+
await expect(page.getByText(startedWorkspaceMessage)).toBeVisible();
59+
await page.getByLabel("Clear search").click();
60+
await expect(page.getByText("All actions")).toBeVisible();
61+
62+
// Filter by resource type
63+
await page.getByText("All resource types").click();
64+
const workspaceBuildsOption = page.getByText("Workspace Build");
65+
await workspaceBuildsOption.scrollIntoViewIfNeeded({ timeout: 5000 });
66+
await workspaceBuildsOption.click();
67+
// Our workspace build should be visible
68+
await expect(page.getByText(startedWorkspaceMessage)).toBeVisible();
69+
// Logins should no longer be visible
70+
await expect(page.getByText(loginMessage)).not.toBeVisible();
71+
await page.getByLabel("Clear search").click();
72+
await expect(page.getByText("All resource types")).toBeVisible();
73+
74+
// Filter by action type
75+
await page.getByText("All actions").click();
76+
await page.getByText("Login").click();
77+
// Logins should be visible
78+
await expect(page.getByText(loginMessage).first()).toBeVisible();
79+
// Our workspace build should no longer be visible
80+
await expect(page.getByText(startedWorkspaceMessage)).not.toBeVisible();
81+
});

0 commit comments

Comments
 (0)