Files
coder/site/e2e/api.ts
T
Bruno Quaresma 5cdda2ea7d chore: replace date-fns by dayjs (#18022)
This change replaces date-fns with dayjs throughout the codebase for
more consistent date/time handling and to reduce bundle size. It also
tries to make the formatting and usage consistent.

**Why dayjs over date-fns?**
Just because we were using dayjs more broadly. Its formatting
capabilities, were also easier to extend.
2025-05-25 00:32:36 -03:00

314 lines
7.7 KiB
TypeScript

import type { Page } from "@playwright/test";
import { expect } from "@playwright/test";
import { API, type DeploymentConfig } from "api/api";
import type { SerpentOption } from "api/typesGenerated";
import dayjs from "dayjs";
import duration from "dayjs/plugin/duration";
import relativeTime from "dayjs/plugin/relativeTime";
dayjs.extend(duration);
dayjs.extend(relativeTime);
import { humanDuration } from "utils/time";
import { coderPort, defaultPassword } from "./constants";
import { type LoginOptions, findSessionToken, randomName } from "./helpers";
let currentOrgId: string;
export const setupApiCalls = async (page: Page) => {
API.setHost(`http://127.0.0.1:${coderPort}`);
const token = await findSessionToken(page);
API.setSessionToken(token);
};
export const getCurrentOrgId = async (): Promise<string> => {
if (currentOrgId) {
return currentOrgId;
}
const currentUser = await API.getAuthenticatedUser();
currentOrgId = currentUser.organization_ids[0];
return currentOrgId;
};
export const createUser = async (...orgIds: string[]) => {
const name = randomName();
const user = await API.createUser({
email: `${name}@coder.com`,
username: name,
name: name,
password: defaultPassword,
login_type: "password",
organization_ids: orgIds,
user_status: null,
});
return user;
};
type CreateOrganizationMemberOptions = {
username?: string;
email?: string;
password?: string;
orgRoles: Record<string, string[]>;
};
export const createOrganizationMember = async ({
username = randomName(),
email = `${username}@coder.com`,
password = defaultPassword,
orgRoles,
}: CreateOrganizationMemberOptions): Promise<LoginOptions> => {
const name = randomName();
const user = await API.createUser({
email,
username,
name: username,
password,
login_type: "password",
organization_ids: Object.keys(orgRoles),
user_status: null,
});
for (const [org, roles] of Object.entries(orgRoles)) {
API.updateOrganizationMemberRoles(org, user.id, roles);
}
return {
username: user.username,
email: user.email,
password,
};
};
export const createGroup = async (orgId: string) => {
const name = randomName();
const group = await API.createGroup(orgId, {
name,
display_name: `Display ${name}`,
avatar_url: "/emojis/1f60d.png",
quota_allowance: 0,
});
return group;
};
export const createOrganization = async (name = randomName()) => {
const org = await API.createOrganization({
name,
display_name: `Org ${name}`,
description: `Org description ${name}`,
icon: "/emojis/1f957.png",
});
return org;
};
export const deleteOrganization = async (orgName: string) => {
await API.deleteOrganization(orgName);
};
export const createOrganizationWithName = async (name: string) => {
const org = await API.createOrganization({
name,
display_name: `${name}`,
description: `Org description ${name}`,
icon: "/emojis/1f957.png",
});
return org;
};
export const createOrganizationSyncSettings = async () => {
const settings = await API.patchOrganizationIdpSyncSettings({
field: "organization-field-test",
mapping: {
"idp-org-1": [
"fbd2116a-8961-4954-87ae-e4575bd29ce0",
"13de3eb4-9b4f-49e7-b0f8-0c3728a0d2e2",
],
"idp-org-2": ["6b39f0f1-6ad8-4981-b2fc-d52aef53ff1b"],
},
organization_assign_default: true,
});
return settings;
};
export const createGroupSyncSettings = async (orgId: string) => {
const settings = await API.patchGroupIdpSyncSettings(
{
field: "group-field-test",
mapping: {
"idp-group-1": [
"fbd2116a-8961-4954-87ae-e4575bd29ce0",
"13de3eb4-9b4f-49e7-b0f8-0c3728a0d2e2",
],
"idp-group-2": ["6b39f0f1-6ad8-4981-b2fc-d52aef53ff1b"],
},
regex_filter: "@[a-zA-Z0-9]+",
auto_create_missing_groups: true,
},
orgId,
);
return settings;
};
export const createRoleSyncSettings = async (orgId: string) => {
const settings = await API.patchRoleIdpSyncSettings(
{
field: "role-field-test",
mapping: {
"idp-role-1": [
"fbd2116a-8961-4954-87ae-e4575bd29ce0",
"13de3eb4-9b4f-49e7-b0f8-0c3728a0d2e2",
],
"idp-role-2": ["6b39f0f1-6ad8-4981-b2fc-d52aef53ff1b"],
},
},
orgId,
);
return settings;
};
export const createCustomRole = async (
orgId: string,
name: string,
displayName: string,
) => {
const role = await API.createOrganizationRole(orgId, {
name,
display_name: displayName,
organization_id: orgId,
site_permissions: [],
organization_permissions: [
{
negate: false,
resource_type: "organization_member",
action: "create",
},
{
negate: false,
resource_type: "organization_member",
action: "delete",
},
{
negate: false,
resource_type: "organization_member",
action: "read",
},
{
negate: false,
resource_type: "organization_member",
action: "update",
},
],
user_permissions: [],
});
return role;
};
export async function verifyConfigFlagBoolean(
page: Page,
config: 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: 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: DeploymentConfig,
flag: string,
) {
const opt = findConfigOption(config, flag);
const configOption = page.locator(
`div.options-table .option-${flag} .option-value-string`,
);
// biome-ignore lint/suspicious/noExplicitAny: opt.value is any
await expect(configOption).toHaveText(opt.value as any);
}
export async function verifyConfigFlagArray(
page: Page,
config: 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
// biome-ignore lint/suspicious/noExplicitAny: opt.value is any
for (const item of opt.value as any) {
await expect(configOption.locator("li", { hasText: item })).toBeVisible();
}
}
export async function verifyConfigFlagEntries(
page: Page,
config: 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.
// biome-ignore lint/suspicious/noExplicitAny: opt.value is any
Object.entries(opt.value as any)
.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 async function verifyConfigFlagDuration(
page: Page,
config: DeploymentConfig,
flag: string,
) {
const opt = findConfigOption(config, flag);
if (typeof opt.value !== "number") {
throw new Error(
`Option with env ${flag} should be a number, but got ${typeof opt.value}.`,
);
}
const configOption = page.locator(
`div.options-table .option-${flag} .option-value-string`,
);
await expect(configOption).toHaveText(humanDuration(opt.value / 1e6));
}
export function findConfigOption(
config: 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.`);
}
return opt;
}