mirror of
https://github.com/logto-io/logto.git
synced 2025-01-20 21:32:31 -05:00
feat(test): add password and passcode identifier tests (#2664)
This commit is contained in:
parent
5b647cf7cb
commit
6bead4a319
13 changed files with 522 additions and 38 deletions
|
@ -12,9 +12,10 @@
|
|||
"scripts": {
|
||||
"build": "rm -rf lib/ && tsc -p tsconfig.test.json --sourcemap",
|
||||
"test:only": "NODE_OPTIONS=--experimental-vm-modules jest",
|
||||
"test": "pnpm build && pnpm test:api && pnpm test:ui",
|
||||
"test": "pnpm build && pnpm test:api && pnpm test:ui && pnpm test:interaction",
|
||||
"test:api": "pnpm test:only -i ./lib/tests/api",
|
||||
"test:ui": "pnpm test:only -i --config=jest.config.ui.js ./lib/tests/ui",
|
||||
"test:interaction": "pnpm test:only -i ./lib/tests/interaction",
|
||||
"lint": "eslint --ext .ts src",
|
||||
"lint:report": "pnpm lint --format json --output-file report.json",
|
||||
"start": "pnpm test"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { Connector, ConnectorResponse } from '@logto/schemas';
|
||||
import type { Connector, ConnectorResponse, CreateConnector } from '@logto/schemas';
|
||||
|
||||
import { authedAdminApi } from './api.js';
|
||||
|
||||
|
@ -9,11 +9,13 @@ export const getConnector = async (connectorId: string) =>
|
|||
authedAdminApi.get(`connectors/${connectorId}`).json<ConnectorResponse>();
|
||||
|
||||
// FIXME @Darcy: correct use of `id` and `connectorId`.
|
||||
export const postConnector = async (connectorId: string, metadata?: Record<string, unknown>) =>
|
||||
export const postConnector = async (
|
||||
payload: Pick<CreateConnector, 'connectorId' | 'config' | 'metadata' | 'syncProfile'>
|
||||
) =>
|
||||
authedAdminApi
|
||||
.post({
|
||||
url: `connectors`,
|
||||
json: { connectorId, metadata },
|
||||
json: payload,
|
||||
})
|
||||
.json<Connector>();
|
||||
|
||||
|
|
|
@ -1,11 +1,4 @@
|
|||
import { Event } from '@logto/schemas';
|
||||
import type {
|
||||
IdentifierPayload,
|
||||
PhonePasswordPayload,
|
||||
EmailPasswordPayload,
|
||||
Profile,
|
||||
UsernamePasswordPayload,
|
||||
} from '@logto/schemas';
|
||||
import type { Event, IdentifierPayload, Profile } from '@logto/schemas';
|
||||
|
||||
import api from './api.js';
|
||||
|
||||
|
@ -19,19 +12,37 @@ export type interactionPayload = {
|
|||
profile?: Profile;
|
||||
};
|
||||
|
||||
export const signInWithPasswordIdentifiers = async (
|
||||
identifier: UsernamePasswordPayload | EmailPasswordPayload | PhonePasswordPayload,
|
||||
cookie: string
|
||||
) =>
|
||||
export const putInteraction = async (payload: interactionPayload, cookie: string) =>
|
||||
api
|
||||
.put('interaction', {
|
||||
headers: {
|
||||
cookie,
|
||||
},
|
||||
json: {
|
||||
event: Event.SignIn,
|
||||
identifier,
|
||||
},
|
||||
headers: { cookie },
|
||||
json: payload,
|
||||
followRedirect: false,
|
||||
})
|
||||
.json<RedirectResponse>();
|
||||
|
||||
export const patchInteraction = async (payload: interactionPayload, cookie: string) =>
|
||||
api
|
||||
.patch('interaction', {
|
||||
headers: { cookie },
|
||||
json: payload,
|
||||
followRedirect: false,
|
||||
})
|
||||
.json<RedirectResponse>();
|
||||
|
||||
export type VerificationPasscodePayload =
|
||||
| {
|
||||
event: Event;
|
||||
email: string;
|
||||
}
|
||||
| { event: Event; phone: string };
|
||||
|
||||
export const sendVerificationPasscode = async (
|
||||
payload: VerificationPasscodePayload,
|
||||
cookie: string
|
||||
) =>
|
||||
api.post('verification/passcode', {
|
||||
headers: { cookie },
|
||||
json: payload,
|
||||
followRedirect: false,
|
||||
});
|
||||
|
|
|
@ -69,7 +69,8 @@ describe('admin console user management', () => {
|
|||
});
|
||||
|
||||
it('should delete user identities successfully', async () => {
|
||||
const { id } = await postConnector(mockSocialConnectorId);
|
||||
// @darcy FIXME: merge post and update
|
||||
const { id } = await postConnector({ connectorId: mockSocialConnectorId });
|
||||
await updateConnectorConfig(id, mockSocialConnectorConfig);
|
||||
|
||||
const createdUserId = await bindSocialToNewCreatedUser(id);
|
||||
|
|
|
@ -45,7 +45,8 @@ test('connector set-up flow', async () => {
|
|||
{ connectorId: mockEmailConnectorId, config: mockEmailConnectorConfig },
|
||||
{ connectorId: mockSocialConnectorId, config: mockSocialConnectorConfig },
|
||||
].map(async ({ connectorId, config }) => {
|
||||
const { id } = await postConnector(connectorId);
|
||||
// @darcy FIXME: should call post method directly
|
||||
const { id } = await postConnector({ connectorId });
|
||||
connectorIdMap.set(connectorId, id);
|
||||
const updatedConnector = await updateConnectorConfig(id, config);
|
||||
expect(updatedConnector.config).toEqual(config);
|
||||
|
@ -71,12 +72,14 @@ test('connector set-up flow', async () => {
|
|||
/*
|
||||
* Change to another SMS/Email connector
|
||||
*/
|
||||
const { id } = await postConnector(mockStandardEmailConnectorId, {
|
||||
target: 'mock-standard-mail',
|
||||
}); // TODO [LOG-4862]: update mock connector
|
||||
// @darcy FIXME: should call post method directly
|
||||
const { id } = await postConnector({
|
||||
connectorId: mockStandardEmailConnectorId,
|
||||
metadata: { target: 'mock-standard-mail' },
|
||||
});
|
||||
await updateConnectorConfig(id, mockStandardEmailConnectorConfig, {
|
||||
target: 'mock-standard-mail',
|
||||
}); // TODO [LOG-4862]: update mock connector
|
||||
});
|
||||
connectorIdMap.set(mockStandardEmailConnectorId, id);
|
||||
const currentConnectors = await listConnectors();
|
||||
expect(
|
||||
|
@ -133,7 +136,7 @@ test('send SMS/email test message', async () => {
|
|||
await Promise.all(
|
||||
[{ connectorId: mockSmsConnectorId }, { connectorId: mockEmailConnectorId }].map(
|
||||
async ({ connectorId }) => {
|
||||
const { id } = await postConnector(connectorId);
|
||||
const { id } = await postConnector({ connectorId });
|
||||
connectorIdMap.set(connectorId, id);
|
||||
}
|
||||
)
|
||||
|
|
|
@ -68,11 +68,7 @@ describe('email and password flow', () => {
|
|||
assert(localPart && domain, new Error('Email address local part or domain is empty'));
|
||||
|
||||
beforeAll(async () => {
|
||||
const { id } = await postConnector(mockEmailConnectorId);
|
||||
await updateConnectorConfig(id, mockEmailConnectorConfig);
|
||||
connectorIdMap.set(mockEmailConnectorId, id);
|
||||
|
||||
await setSignUpIdentifier(signUpIdentifiers.email, true);
|
||||
await setSignUpIdentifier(signUpIdentifiers.none, true);
|
||||
await setSignInMethod([
|
||||
{
|
||||
identifier: SignInIdentifier.Email,
|
||||
|
@ -109,7 +105,7 @@ describe('email passwordless flow', () => {
|
|||
);
|
||||
connectorIdMap.clear();
|
||||
|
||||
const { id } = await postConnector(mockEmailConnectorId);
|
||||
const { id } = await postConnector({ connectorId: mockEmailConnectorId });
|
||||
await updateConnectorConfig(id, mockEmailConnectorConfig);
|
||||
connectorIdMap.set(mockEmailConnectorId, id);
|
||||
|
||||
|
@ -208,7 +204,7 @@ describe('sms passwordless flow', () => {
|
|||
);
|
||||
connectorIdMap.clear();
|
||||
|
||||
const { id } = await postConnector(mockSmsConnectorId);
|
||||
const { id } = await postConnector({ connectorId: mockSmsConnectorId });
|
||||
await updateConnectorConfig(id, mockSmsConnectorConfig);
|
||||
connectorIdMap.set(mockSmsConnectorId, id);
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ describe('social sign-in and register', () => {
|
|||
const socialUserId = crypto.randomUUID();
|
||||
|
||||
beforeAll(async () => {
|
||||
const { id } = await postConnector(mockSocialConnectorId);
|
||||
const { id } = await postConnector({ connectorId: mockSocialConnectorId });
|
||||
connectorIdMap.set(mockSocialConnectorId, id);
|
||||
await updateConnectorConfig(id, mockSocialConnectorConfig);
|
||||
|
||||
|
|
|
@ -0,0 +1,213 @@
|
|||
import { ConnectorType, Event, SignInIdentifier } from '@logto/schemas';
|
||||
import { assert } from '@silverhand/essentials';
|
||||
|
||||
import {
|
||||
sendVerificationPasscode,
|
||||
putInteraction,
|
||||
patchInteraction,
|
||||
deleteUser,
|
||||
updateSignInExperience,
|
||||
} from '#src/api/index.js';
|
||||
import { readPasscode } from '#src/helpers.js';
|
||||
import { generateEmail, generatePhone } from '#src/utils.js';
|
||||
|
||||
import { initClient, processSessionAndLogout } from './utils/client.js';
|
||||
import { clearConnectorsByTypes, setEmailConnector, setSmsConnector } from './utils/connector.js';
|
||||
import { enableAllPasscodeSignInMethods } from './utils/sign-in-experience.js';
|
||||
import { generateNewUser } from './utils/user.js';
|
||||
|
||||
describe('Sign-In flow using passcode identifiers', () => {
|
||||
beforeAll(async () => {
|
||||
await clearConnectorsByTypes([ConnectorType.Email, ConnectorType.Sms]);
|
||||
await setEmailConnector();
|
||||
await setSmsConnector();
|
||||
await enableAllPasscodeSignInMethods();
|
||||
});
|
||||
afterAll(async () => {
|
||||
await clearConnectorsByTypes([ConnectorType.Email, ConnectorType.Sms]);
|
||||
});
|
||||
|
||||
it('sign-in with email and passcode', async () => {
|
||||
const { userProfile, user } = await generateNewUser({ primaryEmail: true });
|
||||
const client = await initClient();
|
||||
assert(client.interactionCookie, new Error('Session not found'));
|
||||
|
||||
await expect(
|
||||
sendVerificationPasscode(
|
||||
{
|
||||
event: Event.SignIn,
|
||||
email: userProfile.primaryEmail,
|
||||
},
|
||||
client.interactionCookie
|
||||
)
|
||||
).resolves.not.toThrow();
|
||||
|
||||
const passcodeRecord = await readPasscode();
|
||||
|
||||
expect(passcodeRecord).toMatchObject({
|
||||
address: userProfile.primaryEmail,
|
||||
type: Event.SignIn,
|
||||
});
|
||||
|
||||
const { code } = passcodeRecord;
|
||||
|
||||
const { redirectTo } = await putInteraction(
|
||||
{
|
||||
event: Event.SignIn,
|
||||
identifier: {
|
||||
email: userProfile.primaryEmail,
|
||||
passcode: code,
|
||||
},
|
||||
},
|
||||
client.interactionCookie
|
||||
);
|
||||
|
||||
await processSessionAndLogout(client, redirectTo);
|
||||
|
||||
await deleteUser(user.id);
|
||||
});
|
||||
|
||||
it('sign-in with phone and passcode', async () => {
|
||||
const { userProfile, user } = await generateNewUser({ primaryPhone: true });
|
||||
const client = await initClient();
|
||||
assert(client.interactionCookie, new Error('Session not found'));
|
||||
|
||||
await expect(
|
||||
sendVerificationPasscode(
|
||||
{
|
||||
event: Event.SignIn,
|
||||
phone: userProfile.primaryPhone,
|
||||
},
|
||||
client.interactionCookie
|
||||
)
|
||||
).resolves.not.toThrow();
|
||||
|
||||
const passcodeRecord = await readPasscode();
|
||||
|
||||
expect(passcodeRecord).toMatchObject({
|
||||
phone: userProfile.primaryPhone,
|
||||
type: Event.SignIn,
|
||||
});
|
||||
|
||||
const { code } = passcodeRecord;
|
||||
|
||||
const { redirectTo } = await putInteraction(
|
||||
{
|
||||
event: Event.SignIn,
|
||||
identifier: {
|
||||
phone: userProfile.primaryPhone,
|
||||
passcode: code,
|
||||
},
|
||||
},
|
||||
client.interactionCookie
|
||||
);
|
||||
|
||||
await processSessionAndLogout(client, redirectTo);
|
||||
|
||||
await deleteUser(user.id);
|
||||
});
|
||||
|
||||
it('sign-in with non-exist email account with passcode', async () => {
|
||||
const newEmail = generateEmail();
|
||||
|
||||
// Enable email sign-up
|
||||
await updateSignInExperience({
|
||||
signUp: { identifiers: [SignInIdentifier.Email], password: false, verify: true },
|
||||
});
|
||||
|
||||
const client = await initClient();
|
||||
assert(client.interactionCookie, new Error('Session not found'));
|
||||
|
||||
await expect(
|
||||
sendVerificationPasscode(
|
||||
{
|
||||
event: Event.SignIn,
|
||||
email: newEmail,
|
||||
},
|
||||
client.interactionCookie
|
||||
)
|
||||
).resolves.not.toThrow();
|
||||
|
||||
const passcodeRecord = await readPasscode();
|
||||
|
||||
const { code } = passcodeRecord;
|
||||
|
||||
// TODO: @simeng use expectRequestError after https://github.com/logto-io/logto/pull/2639/ PR merged
|
||||
await expect(
|
||||
putInteraction(
|
||||
{
|
||||
event: Event.SignIn,
|
||||
identifier: {
|
||||
email: newEmail,
|
||||
passcode: code,
|
||||
},
|
||||
},
|
||||
client.interactionCookie
|
||||
)
|
||||
).rejects.toThrow();
|
||||
|
||||
const { redirectTo } = await patchInteraction(
|
||||
{
|
||||
event: Event.Register,
|
||||
profile: {
|
||||
email: newEmail,
|
||||
},
|
||||
},
|
||||
client.interactionCookie
|
||||
);
|
||||
|
||||
await processSessionAndLogout(client, redirectTo);
|
||||
});
|
||||
|
||||
it('sign-in with non-exist phone account with passcode', async () => {
|
||||
const newPhone = generatePhone();
|
||||
|
||||
// Enable phone sign-up
|
||||
await updateSignInExperience({
|
||||
signUp: { identifiers: [SignInIdentifier.Sms], password: false, verify: true },
|
||||
});
|
||||
|
||||
const client = await initClient();
|
||||
assert(client.interactionCookie, new Error('Session not found'));
|
||||
|
||||
await expect(
|
||||
sendVerificationPasscode(
|
||||
{
|
||||
event: Event.SignIn,
|
||||
phone: newPhone,
|
||||
},
|
||||
client.interactionCookie
|
||||
)
|
||||
).resolves.not.toThrow();
|
||||
|
||||
const passcodeRecord = await readPasscode();
|
||||
|
||||
const { code } = passcodeRecord;
|
||||
|
||||
// TODO: @simeng use expectRequestError after https://github.com/logto-io/logto/pull/2639/ PR merged
|
||||
await expect(
|
||||
putInteraction(
|
||||
{
|
||||
event: Event.SignIn,
|
||||
identifier: {
|
||||
phone: newPhone,
|
||||
passcode: code,
|
||||
},
|
||||
},
|
||||
client.interactionCookie
|
||||
)
|
||||
).rejects.toThrow();
|
||||
|
||||
const { redirectTo } = await patchInteraction(
|
||||
{
|
||||
event: Event.Register,
|
||||
profile: {
|
||||
phone: newPhone,
|
||||
},
|
||||
},
|
||||
client.interactionCookie
|
||||
);
|
||||
|
||||
await processSessionAndLogout(client, redirectTo);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,98 @@
|
|||
import { Event } from '@logto/schemas';
|
||||
import { assert } from '@silverhand/essentials';
|
||||
|
||||
import { putInteraction, deleteUser } from '#src/api/index.js';
|
||||
import MockClient from '#src/client/index.js';
|
||||
|
||||
import { enableAllPasswordSignInMethods } from './utils/sign-in-experience.js';
|
||||
import { generateNewUser } from './utils/user.js';
|
||||
|
||||
describe('Sign-In flow using password identifiers', () => {
|
||||
beforeAll(async () => {
|
||||
await enableAllPasswordSignInMethods();
|
||||
});
|
||||
|
||||
it('sign-in with username and password', async () => {
|
||||
const { userProfile, user } = await generateNewUser({ username: true });
|
||||
const client = new MockClient();
|
||||
await client.initSession();
|
||||
assert(client.interactionCookie, new Error('Session not found'));
|
||||
|
||||
const { redirectTo } = await putInteraction(
|
||||
{
|
||||
event: Event.SignIn,
|
||||
identifier: {
|
||||
username: userProfile.username,
|
||||
password: userProfile.password,
|
||||
},
|
||||
},
|
||||
client.interactionCookie
|
||||
);
|
||||
|
||||
await client.processSession(redirectTo);
|
||||
|
||||
await expect(client.isAuthenticated()).resolves.toBe(true);
|
||||
|
||||
await client.signOut();
|
||||
|
||||
await expect(client.isAuthenticated()).resolves.toBe(false);
|
||||
|
||||
await deleteUser(user.id);
|
||||
});
|
||||
|
||||
it('sign-in with email and password', async () => {
|
||||
const { userProfile, user } = await generateNewUser({ primaryEmail: true });
|
||||
const client = new MockClient();
|
||||
await client.initSession();
|
||||
assert(client.interactionCookie, new Error('Session not found'));
|
||||
|
||||
const { redirectTo } = await putInteraction(
|
||||
{
|
||||
event: Event.SignIn,
|
||||
identifier: {
|
||||
email: userProfile.primaryEmail,
|
||||
password: userProfile.password,
|
||||
},
|
||||
},
|
||||
client.interactionCookie
|
||||
);
|
||||
|
||||
await client.processSession(redirectTo);
|
||||
|
||||
await expect(client.isAuthenticated()).resolves.toBe(true);
|
||||
|
||||
await client.signOut();
|
||||
|
||||
await expect(client.isAuthenticated()).resolves.toBe(false);
|
||||
|
||||
await deleteUser(user.id);
|
||||
});
|
||||
|
||||
it('sign-in with phone and password', async () => {
|
||||
const { userProfile, user } = await generateNewUser({ primaryPhone: true });
|
||||
const client = new MockClient();
|
||||
await client.initSession();
|
||||
assert(client.interactionCookie, new Error('Session not found'));
|
||||
|
||||
const { redirectTo } = await putInteraction(
|
||||
{
|
||||
event: Event.SignIn,
|
||||
identifier: {
|
||||
phone: userProfile.primaryPhone,
|
||||
password: userProfile.password,
|
||||
},
|
||||
},
|
||||
client.interactionCookie
|
||||
);
|
||||
|
||||
await client.processSession(redirectTo);
|
||||
|
||||
await expect(client.isAuthenticated()).resolves.toBe(true);
|
||||
|
||||
await client.signOut();
|
||||
|
||||
await expect(client.isAuthenticated()).resolves.toBe(false);
|
||||
|
||||
await deleteUser(user.id);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,18 @@
|
|||
import MockClient from '#src/client/index.js';
|
||||
|
||||
export const initClient = async () => {
|
||||
const client = new MockClient();
|
||||
await client.initSession();
|
||||
|
||||
return client;
|
||||
};
|
||||
|
||||
export const processSessionAndLogout = async (client: MockClient, redirectTo: string) => {
|
||||
await client.processSession(redirectTo);
|
||||
|
||||
await expect(client.isAuthenticated()).resolves.toBe(true);
|
||||
|
||||
await client.signOut();
|
||||
|
||||
await expect(client.isAuthenticated()).resolves.toBe(false);
|
||||
};
|
|
@ -0,0 +1,27 @@
|
|||
import type { ConnectorType } from '@logto/schemas';
|
||||
|
||||
import {
|
||||
mockEmailConnectorConfig,
|
||||
mockEmailConnectorId,
|
||||
mockSmsConnectorConfig,
|
||||
mockSmsConnectorId,
|
||||
} from '#src/__mocks__/connectors-mock.js';
|
||||
import { listConnectors, deleteConnectorById, postConnector } from '#src/api/index.js';
|
||||
|
||||
export const clearConnectorsByTypes = async (types: ConnectorType[]) => {
|
||||
const connectors = await listConnectors();
|
||||
const targetConnectors = connectors.filter((connector) => types.includes(connector.type));
|
||||
await Promise.all(targetConnectors.map(async (connector) => deleteConnectorById(connector.id)));
|
||||
};
|
||||
|
||||
export const setEmailConnector = async () =>
|
||||
postConnector({
|
||||
connectorId: mockEmailConnectorId,
|
||||
config: mockEmailConnectorConfig,
|
||||
});
|
||||
|
||||
export const setSmsConnector = async () =>
|
||||
postConnector({
|
||||
connectorId: mockSmsConnectorId,
|
||||
config: mockSmsConnectorConfig,
|
||||
});
|
|
@ -0,0 +1,74 @@
|
|||
import type { SignInExperience } from '@logto/schemas';
|
||||
import { SignInMode, SignInIdentifier } from '@logto/schemas';
|
||||
|
||||
import { updateSignInExperience } from '#src/api/index.js';
|
||||
|
||||
const defaultSignUpMethod = {
|
||||
identifiers: [],
|
||||
password: false,
|
||||
verify: false,
|
||||
};
|
||||
|
||||
const defaultPasswordSignInMethods = [
|
||||
{
|
||||
identifier: SignInIdentifier.Username,
|
||||
password: true,
|
||||
verificationCode: false,
|
||||
isPasswordPrimary: false,
|
||||
},
|
||||
{
|
||||
identifier: SignInIdentifier.Email,
|
||||
password: true,
|
||||
verificationCode: false,
|
||||
isPasswordPrimary: false,
|
||||
},
|
||||
{
|
||||
identifier: SignInIdentifier.Sms,
|
||||
password: true,
|
||||
verificationCode: false,
|
||||
isPasswordPrimary: false,
|
||||
},
|
||||
];
|
||||
|
||||
const defaultPasscodeSignInMethods = [
|
||||
{
|
||||
identifier: SignInIdentifier.Username,
|
||||
password: true,
|
||||
verificationCode: false,
|
||||
isPasswordPrimary: false,
|
||||
},
|
||||
{
|
||||
identifier: SignInIdentifier.Email,
|
||||
password: true,
|
||||
verificationCode: true,
|
||||
isPasswordPrimary: false,
|
||||
},
|
||||
{
|
||||
identifier: SignInIdentifier.Sms,
|
||||
password: true,
|
||||
verificationCode: true,
|
||||
isPasswordPrimary: false,
|
||||
},
|
||||
];
|
||||
|
||||
export const enableAllPasswordSignInMethods = async (
|
||||
signUp: SignInExperience['signUp'] = defaultSignUpMethod
|
||||
) =>
|
||||
updateSignInExperience({
|
||||
signInMode: SignInMode.SignInAndRegister,
|
||||
signUp,
|
||||
signIn: {
|
||||
methods: defaultPasswordSignInMethods,
|
||||
},
|
||||
});
|
||||
|
||||
export const enableAllPasscodeSignInMethods = async (
|
||||
signUp: SignInExperience['signUp'] = defaultSignUpMethod
|
||||
) =>
|
||||
updateSignInExperience({
|
||||
signInMode: SignInMode.SignInAndRegister,
|
||||
signUp,
|
||||
signIn: {
|
||||
methods: defaultPasscodeSignInMethods,
|
||||
},
|
||||
});
|
|
@ -0,0 +1,40 @@
|
|||
import { createUser } from '#src/api/index.js';
|
||||
import {
|
||||
generateUsername,
|
||||
generateEmail,
|
||||
generatePhone,
|
||||
generatePassword,
|
||||
generateName,
|
||||
} from '#src/utils.js';
|
||||
|
||||
export type NewUserProfileOptions = {
|
||||
username?: true;
|
||||
primaryEmail?: true;
|
||||
primaryPhone?: true;
|
||||
};
|
||||
|
||||
export const generateNewUser = async <T extends NewUserProfileOptions>({
|
||||
username,
|
||||
primaryEmail,
|
||||
primaryPhone,
|
||||
}: T) => {
|
||||
type UserProfile = {
|
||||
password: string;
|
||||
name: string;
|
||||
} & {
|
||||
[K in keyof T]: T[K] extends true ? string : never;
|
||||
};
|
||||
|
||||
// @ts-expect-error - TS can't map the type of userProfile to the UserProfile defined above
|
||||
const userProfile: UserProfile = {
|
||||
password: generatePassword(),
|
||||
name: generateName(),
|
||||
...(username ? { username: generateUsername() } : {}),
|
||||
...(primaryEmail ? { primaryEmail: generateEmail() } : {}),
|
||||
...(primaryPhone ? { primaryPhone: generatePhone() } : {}),
|
||||
};
|
||||
|
||||
const user = await createUser(userProfile);
|
||||
|
||||
return { user, userProfile };
|
||||
};
|
Loading…
Add table
Reference in a new issue