0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00
logto/packages/integration-tests/src/utils.ts

134 lines
4.6 KiB
TypeScript

import crypto, { randomInt } from 'node:crypto';
import path from 'node:path';
import { generateStandardId } from '@logto/shared';
import { assert } from '@silverhand/essentials';
import { type Page } from 'puppeteer';
import { isDevFeaturesEnabled } from './constants.js';
export const generateName = () => crypto.randomUUID();
export const generateUserId = () => crypto.randomUUID();
export const generateUsername = () => `usr_${crypto.randomUUID().replaceAll('-', '_')}`;
export const generatePassword = () => `pwd_${crypto.randomUUID()}`;
export const generateResourceName = () => `res_${crypto.randomUUID()}`;
export const generateResourceIndicator = () => `https://${crypto.randomUUID()}.logto.io`;
export const generateEmail = (domain = 'logto.io') =>
`${crypto.randomUUID().toLowerCase()}@${domain}`;
export const generateScopeName = () => `sc:${crypto.randomUUID()}`;
export const generateRoleName = () => `role_${crypto.randomUUID()}`;
export const generateDomain = () => `${crypto.randomUUID().toLowerCase().slice(0, 5)}.example.com`;
export const generateSsoConnectorName = () => `sso_${crypto.randomUUID()}`;
export const generatePhone = (isE164?: boolean) => {
const plus = isE164 ? '+' : '';
const countryAndAreaCode = '1310'; // California, US
const validCentralOfficeCodes = [
'205',
'208',
'215',
'216',
'220',
'228',
'229',
'230',
'231',
'232',
];
const centralOfficeCode =
validCentralOfficeCodes[randomInt(0, validCentralOfficeCodes.length)] ?? '205';
const phoneNumber = randomInt(0, 10_000).toString().padStart(4, '0');
return plus + countryAndAreaCode + centralOfficeCode + phoneNumber;
};
export const formatPhoneNumberToInternational = (phoneNumber: string) =>
phoneNumber.slice(0, 2) +
' ' +
phoneNumber.slice(2, 5) +
' ' +
phoneNumber.slice(5, 8) +
' ' +
phoneNumber.slice(8);
export const waitFor = async (ms: number) =>
new Promise((resolve) => {
setTimeout(resolve, ms);
});
export const getAccessTokenPayload = (accessToken: string): Record<string, unknown> => {
const payloadPart = accessToken.split('.')[1];
assert(typeof payloadPart === 'string', new Error('Invalid access token'));
const payload = Buffer.from(payloadPart, 'base64').toString();
// eslint-disable-next-line no-restricted-syntax
return JSON.parse(payload) as Record<string, unknown>;
};
export const appendPathname = (pathname: string, baseUrl: URL) =>
new URL(path.join(baseUrl.pathname, pathname), baseUrl);
/**
* Run an action and simultaneously wait for navigation to complete. This is
* useful for actions that trigger navigation, such as clicking a link or
* submitting a form.
*/
export const expectNavigation = async <T>(
action: Promise<T>,
page: Page = global.page
): Promise<T> => {
const [_, result] = await Promise.all([
/**
* We should call `waitForNavigation` before the action or the `waitForNavigation` will encounter a timeout error randomly,
* since sometimes the action is too fast and the `waitForNavigation` is not called before the navigation is completed.
*/
page.waitForNavigation({ waitUntil: 'networkidle0' }),
action,
]);
return result;
};
/**
* Build the string for a CSS selector that matches a class name.
*
* Since we use CSS modules, the class names are prefixed with a hash followed by a `_`.
* For example, the class name `foo` will be transformed to `abc123_foo`. This function
* returns a selector that matches any class name that contains `_foo`.
*
* It is accurate enough for our tests, as long as our class names are camelCased.
*/
export const cls = <C extends string>(className: C) => `[class*=_${className}]` as const;
/**
* Build the string for a CSS selector that matches a class name for a `<div>` element.
* This is a shorthand for `div${cls(className)}`.
*
* @example
* ```ts
* dcls('foo') // => 'div[class*=_foo]'
* ```
*
* @see {@link cls}
*/
export const dcls = <C extends string>(className: C) => `div${cls(className)}` as const;
/** Build the string for a CSS selector that matches a `<div>` element with `aria-modal=true`. */
export const dmodal = () => `div[aria-modal=true]`;
/**
* Generate a random test name that starts with `test_` and followed by 4 random characters.
*
* @example
* ```ts
* generateTestName() // => 'test_abc1'
* ```
*/
export const generateTestName = () => `test_${generateStandardId(4)}`;
export const randomString = () => crypto.randomBytes(8).toString('hex');
export const devFeatureTest = Object.freeze({
it: isDevFeaturesEnabled ? it : it.skip,
describe: isDevFeaturesEnabled ? describe : describe.skip,
});