mirror of
https://github.com/logto-io/logto.git
synced 2025-03-17 22:31:28 -05:00
feat(test): add username, phone, email register integration test (#2671)
This commit is contained in:
parent
57a28be292
commit
61f00449da
5 changed files with 333 additions and 53 deletions
|
@ -0,0 +1,280 @@
|
|||
import { ConnectorType, Event, SignInIdentifier } from '@logto/schemas';
|
||||
import { assert } from '@silverhand/essentials';
|
||||
|
||||
import {
|
||||
sendVerificationPasscode,
|
||||
putInteraction,
|
||||
patchInteraction,
|
||||
deleteUser,
|
||||
} from '#src/api/index.js';
|
||||
import { readPasscode, expectRejects } from '#src/helpers.js';
|
||||
|
||||
import { initClient, processSession, logoutClient } from './utils/client.js';
|
||||
import { clearConnectorsByTypes, setEmailConnector, setSmsConnector } from './utils/connector.js';
|
||||
import {
|
||||
enableAllPasscodeSignInMethods,
|
||||
enableAllPasswordSignInMethods,
|
||||
} from './utils/sign-in-experience.js';
|
||||
import { generateNewUserProfile, generateNewUser } from './utils/user.js';
|
||||
|
||||
describe('Register with username and password', () => {
|
||||
it('register with username and password', async () => {
|
||||
await enableAllPasswordSignInMethods({
|
||||
identifiers: [SignInIdentifier.Username],
|
||||
password: true,
|
||||
verify: false,
|
||||
});
|
||||
|
||||
const { username, password } = generateNewUserProfile({ username: true, password: true });
|
||||
const client = await initClient();
|
||||
assert(client.interactionCookie, new Error('Session not found'));
|
||||
|
||||
const { redirectTo } = await putInteraction(
|
||||
{
|
||||
event: Event.Register,
|
||||
profile: {
|
||||
username,
|
||||
password,
|
||||
},
|
||||
},
|
||||
client.interactionCookie
|
||||
);
|
||||
|
||||
const id = await processSession(client, redirectTo);
|
||||
await logoutClient(client);
|
||||
await deleteUser(id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Register with passwordless identifier', () => {
|
||||
beforeAll(async () => {
|
||||
await clearConnectorsByTypes([ConnectorType.Email, ConnectorType.Sms]);
|
||||
await setEmailConnector();
|
||||
await setSmsConnector();
|
||||
});
|
||||
afterAll(async () => {
|
||||
await clearConnectorsByTypes([ConnectorType.Email, ConnectorType.Sms]);
|
||||
});
|
||||
|
||||
it('register with email', async () => {
|
||||
await enableAllPasscodeSignInMethods({
|
||||
identifiers: [SignInIdentifier.Email],
|
||||
password: false,
|
||||
verify: true,
|
||||
});
|
||||
|
||||
const { primaryEmail } = generateNewUserProfile({ primaryEmail: true });
|
||||
const client = await initClient();
|
||||
assert(client.interactionCookie, new Error('Session not found'));
|
||||
|
||||
await expect(
|
||||
sendVerificationPasscode(
|
||||
{
|
||||
event: Event.Register,
|
||||
email: primaryEmail,
|
||||
},
|
||||
client.interactionCookie
|
||||
)
|
||||
).resolves.not.toThrow();
|
||||
|
||||
const passcodeRecord = await readPasscode();
|
||||
|
||||
expect(passcodeRecord).toMatchObject({
|
||||
address: primaryEmail,
|
||||
type: Event.Register,
|
||||
});
|
||||
|
||||
const { code } = passcodeRecord;
|
||||
|
||||
const { redirectTo } = await putInteraction(
|
||||
{
|
||||
event: Event.Register,
|
||||
identifier: {
|
||||
email: primaryEmail,
|
||||
passcode: code,
|
||||
},
|
||||
profile: {
|
||||
email: primaryEmail,
|
||||
},
|
||||
},
|
||||
client.interactionCookie
|
||||
);
|
||||
|
||||
const id = await processSession(client, redirectTo);
|
||||
await logoutClient(client);
|
||||
await deleteUser(id);
|
||||
});
|
||||
|
||||
it('register with phone', async () => {
|
||||
await enableAllPasscodeSignInMethods({
|
||||
identifiers: [SignInIdentifier.Sms],
|
||||
password: false,
|
||||
verify: true,
|
||||
});
|
||||
|
||||
const { primaryPhone } = generateNewUserProfile({ primaryPhone: true });
|
||||
const client = await initClient();
|
||||
assert(client.interactionCookie, new Error('Session not found'));
|
||||
|
||||
await expect(
|
||||
sendVerificationPasscode(
|
||||
{
|
||||
event: Event.Register,
|
||||
phone: primaryPhone,
|
||||
},
|
||||
client.interactionCookie
|
||||
)
|
||||
).resolves.not.toThrow();
|
||||
|
||||
const passcodeRecord = await readPasscode();
|
||||
|
||||
expect(passcodeRecord).toMatchObject({
|
||||
phone: primaryPhone,
|
||||
type: Event.Register,
|
||||
});
|
||||
|
||||
const { code } = passcodeRecord;
|
||||
|
||||
const { redirectTo } = await putInteraction(
|
||||
{
|
||||
event: Event.Register,
|
||||
identifier: {
|
||||
phone: primaryPhone,
|
||||
passcode: code,
|
||||
},
|
||||
profile: {
|
||||
phone: primaryPhone,
|
||||
},
|
||||
},
|
||||
client.interactionCookie
|
||||
);
|
||||
|
||||
const id = await processSession(client, redirectTo);
|
||||
await logoutClient(client);
|
||||
await deleteUser(id);
|
||||
});
|
||||
|
||||
it('register with exiting email', async () => {
|
||||
const {
|
||||
user,
|
||||
userProfile: { primaryEmail },
|
||||
} = await generateNewUser({ primaryEmail: true });
|
||||
|
||||
await enableAllPasscodeSignInMethods({
|
||||
identifiers: [SignInIdentifier.Email],
|
||||
password: false,
|
||||
verify: true,
|
||||
});
|
||||
|
||||
const client = await initClient();
|
||||
assert(client.interactionCookie, new Error('Session not found'));
|
||||
|
||||
await expect(
|
||||
sendVerificationPasscode(
|
||||
{
|
||||
event: Event.Register,
|
||||
email: primaryEmail,
|
||||
},
|
||||
client.interactionCookie
|
||||
)
|
||||
).resolves.not.toThrow();
|
||||
|
||||
const passcodeRecord = await readPasscode();
|
||||
|
||||
expect(passcodeRecord).toMatchObject({
|
||||
address: primaryEmail,
|
||||
type: Event.Register,
|
||||
});
|
||||
|
||||
const { code } = passcodeRecord;
|
||||
|
||||
await expectRejects(
|
||||
putInteraction(
|
||||
{
|
||||
event: Event.Register,
|
||||
identifier: {
|
||||
email: primaryEmail,
|
||||
passcode: code,
|
||||
},
|
||||
profile: {
|
||||
email: primaryEmail,
|
||||
},
|
||||
},
|
||||
client.interactionCookie
|
||||
),
|
||||
'user.email_already_in_use'
|
||||
);
|
||||
|
||||
const { redirectTo } = await patchInteraction(
|
||||
{
|
||||
event: Event.SignIn,
|
||||
},
|
||||
client.interactionCookie
|
||||
);
|
||||
await processSession(client, redirectTo);
|
||||
await logoutClient(client);
|
||||
await deleteUser(user.id);
|
||||
});
|
||||
|
||||
it('register with exiting phone', async () => {
|
||||
const {
|
||||
user,
|
||||
userProfile: { primaryPhone },
|
||||
} = await generateNewUser({ primaryPhone: true });
|
||||
|
||||
await enableAllPasscodeSignInMethods({
|
||||
identifiers: [SignInIdentifier.Sms],
|
||||
password: false,
|
||||
verify: true,
|
||||
});
|
||||
|
||||
const client = await initClient();
|
||||
assert(client.interactionCookie, new Error('Session not found'));
|
||||
|
||||
await expect(
|
||||
sendVerificationPasscode(
|
||||
{
|
||||
event: Event.Register,
|
||||
phone: primaryPhone,
|
||||
},
|
||||
client.interactionCookie
|
||||
)
|
||||
).resolves.not.toThrow();
|
||||
|
||||
const passcodeRecord = await readPasscode();
|
||||
|
||||
expect(passcodeRecord).toMatchObject({
|
||||
phone: primaryPhone,
|
||||
type: Event.Register,
|
||||
});
|
||||
|
||||
const { code } = passcodeRecord;
|
||||
|
||||
await expectRejects(
|
||||
putInteraction(
|
||||
{
|
||||
event: Event.Register,
|
||||
identifier: {
|
||||
phone: primaryPhone,
|
||||
passcode: code,
|
||||
},
|
||||
profile: {
|
||||
phone: primaryPhone,
|
||||
},
|
||||
},
|
||||
client.interactionCookie
|
||||
),
|
||||
'user.phone_already_in_use'
|
||||
);
|
||||
|
||||
const { redirectTo } = await patchInteraction(
|
||||
{
|
||||
event: Event.SignIn,
|
||||
},
|
||||
client.interactionCookie
|
||||
);
|
||||
await processSession(client, redirectTo);
|
||||
await logoutClient(client);
|
||||
await deleteUser(user.id);
|
||||
});
|
||||
});
|
|
@ -8,10 +8,10 @@ import {
|
|||
deleteUser,
|
||||
updateSignInExperience,
|
||||
} from '#src/api/index.js';
|
||||
import { readPasscode } from '#src/helpers.js';
|
||||
import { expectRejects, readPasscode } from '#src/helpers.js';
|
||||
import { generateEmail, generatePhone } from '#src/utils.js';
|
||||
|
||||
import { initClient, processSessionAndLogout } from './utils/client.js';
|
||||
import { initClient, processSession, logoutClient } 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';
|
||||
|
@ -62,8 +62,8 @@ describe('Sign-In flow using passcode identifiers', () => {
|
|||
client.interactionCookie
|
||||
);
|
||||
|
||||
await processSessionAndLogout(client, redirectTo);
|
||||
|
||||
await processSession(client, redirectTo);
|
||||
await logoutClient(client);
|
||||
await deleteUser(user.id);
|
||||
});
|
||||
|
||||
|
@ -102,8 +102,8 @@ describe('Sign-In flow using passcode identifiers', () => {
|
|||
client.interactionCookie
|
||||
);
|
||||
|
||||
await processSessionAndLogout(client, redirectTo);
|
||||
|
||||
await processSession(client, redirectTo);
|
||||
await logoutClient(client);
|
||||
await deleteUser(user.id);
|
||||
});
|
||||
|
||||
|
@ -132,8 +132,7 @@ describe('Sign-In flow using passcode identifiers', () => {
|
|||
|
||||
const { code } = passcodeRecord;
|
||||
|
||||
// TODO: @simeng use expectRequestError after https://github.com/logto-io/logto/pull/2639/ PR merged
|
||||
await expect(
|
||||
await expectRejects(
|
||||
putInteraction(
|
||||
{
|
||||
event: Event.SignIn,
|
||||
|
@ -143,8 +142,9 @@ describe('Sign-In flow using passcode identifiers', () => {
|
|||
},
|
||||
},
|
||||
client.interactionCookie
|
||||
)
|
||||
).rejects.toThrow();
|
||||
),
|
||||
'user.user_not_exist'
|
||||
);
|
||||
|
||||
const { redirectTo } = await patchInteraction(
|
||||
{
|
||||
|
@ -156,7 +156,9 @@ describe('Sign-In flow using passcode identifiers', () => {
|
|||
client.interactionCookie
|
||||
);
|
||||
|
||||
await processSessionAndLogout(client, redirectTo);
|
||||
const id = await processSession(client, redirectTo);
|
||||
await logoutClient(client);
|
||||
await deleteUser(id);
|
||||
});
|
||||
|
||||
it('sign-in with non-exist phone account with passcode', async () => {
|
||||
|
@ -184,8 +186,7 @@ describe('Sign-In flow using passcode identifiers', () => {
|
|||
|
||||
const { code } = passcodeRecord;
|
||||
|
||||
// TODO: @simeng use expectRequestError after https://github.com/logto-io/logto/pull/2639/ PR merged
|
||||
await expect(
|
||||
await expectRejects(
|
||||
putInteraction(
|
||||
{
|
||||
event: Event.SignIn,
|
||||
|
@ -195,8 +196,9 @@ describe('Sign-In flow using passcode identifiers', () => {
|
|||
},
|
||||
},
|
||||
client.interactionCookie
|
||||
)
|
||||
).rejects.toThrow();
|
||||
),
|
||||
'user.user_not_exist'
|
||||
);
|
||||
|
||||
const { redirectTo } = await patchInteraction(
|
||||
{
|
||||
|
@ -208,6 +210,8 @@ describe('Sign-In flow using passcode identifiers', () => {
|
|||
client.interactionCookie
|
||||
);
|
||||
|
||||
await processSessionAndLogout(client, redirectTo);
|
||||
const id = await processSession(client, redirectTo);
|
||||
await logoutClient(client);
|
||||
await deleteUser(id);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,8 +2,8 @@ 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 { initClient, processSession, logoutClient } from './utils/client.js';
|
||||
import { enableAllPasswordSignInMethods } from './utils/sign-in-experience.js';
|
||||
import { generateNewUser } from './utils/user.js';
|
||||
|
||||
|
@ -13,9 +13,8 @@ describe('Sign-In flow using password identifiers', () => {
|
|||
});
|
||||
|
||||
it('sign-in with username and password', async () => {
|
||||
const { userProfile, user } = await generateNewUser({ username: true });
|
||||
const client = new MockClient();
|
||||
await client.initSession();
|
||||
const { userProfile, user } = await generateNewUser({ username: true, password: true });
|
||||
const client = await initClient();
|
||||
assert(client.interactionCookie, new Error('Session not found'));
|
||||
|
||||
const { redirectTo } = await putInteraction(
|
||||
|
@ -29,21 +28,15 @@ describe('Sign-In flow using password identifiers', () => {
|
|||
client.interactionCookie
|
||||
);
|
||||
|
||||
await client.processSession(redirectTo);
|
||||
|
||||
await expect(client.isAuthenticated()).resolves.toBe(true);
|
||||
|
||||
await client.signOut();
|
||||
|
||||
await expect(client.isAuthenticated()).resolves.toBe(false);
|
||||
await processSession(client, redirectTo);
|
||||
await logoutClient(client);
|
||||
|
||||
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();
|
||||
const { userProfile, user } = await generateNewUser({ primaryEmail: true, password: true });
|
||||
const client = await initClient();
|
||||
assert(client.interactionCookie, new Error('Session not found'));
|
||||
|
||||
const { redirectTo } = await putInteraction(
|
||||
|
@ -57,21 +50,15 @@ describe('Sign-In flow using password identifiers', () => {
|
|||
client.interactionCookie
|
||||
);
|
||||
|
||||
await client.processSession(redirectTo);
|
||||
|
||||
await expect(client.isAuthenticated()).resolves.toBe(true);
|
||||
|
||||
await client.signOut();
|
||||
|
||||
await expect(client.isAuthenticated()).resolves.toBe(false);
|
||||
await processSession(client, redirectTo);
|
||||
await logoutClient(client);
|
||||
|
||||
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();
|
||||
const { userProfile, user } = await generateNewUser({ primaryPhone: true, password: true });
|
||||
const client = await initClient();
|
||||
assert(client.interactionCookie, new Error('Session not found'));
|
||||
|
||||
const { redirectTo } = await putInteraction(
|
||||
|
@ -85,13 +72,8 @@ describe('Sign-In flow using password identifiers', () => {
|
|||
client.interactionCookie
|
||||
);
|
||||
|
||||
await client.processSession(redirectTo);
|
||||
|
||||
await expect(client.isAuthenticated()).resolves.toBe(true);
|
||||
|
||||
await client.signOut();
|
||||
|
||||
await expect(client.isAuthenticated()).resolves.toBe(false);
|
||||
await processSession(client, redirectTo);
|
||||
await logoutClient(client);
|
||||
|
||||
await deleteUser(user.id);
|
||||
});
|
||||
|
|
|
@ -7,11 +7,17 @@ export const initClient = async () => {
|
|||
return client;
|
||||
};
|
||||
|
||||
export const processSessionAndLogout = async (client: MockClient, redirectTo: string) => {
|
||||
export const processSession = async (client: MockClient, redirectTo: string) => {
|
||||
await client.processSession(redirectTo);
|
||||
|
||||
await expect(client.isAuthenticated()).resolves.toBe(true);
|
||||
|
||||
const { sub } = await client.getIdTokenClaims();
|
||||
|
||||
return sub;
|
||||
};
|
||||
|
||||
export const logoutClient = async (client: MockClient) => {
|
||||
await client.signOut();
|
||||
|
||||
await expect(client.isAuthenticated()).resolves.toBe(false);
|
||||
|
|
|
@ -9,31 +9,39 @@ import {
|
|||
|
||||
export type NewUserProfileOptions = {
|
||||
username?: true;
|
||||
password?: true;
|
||||
name?: true;
|
||||
primaryEmail?: true;
|
||||
primaryPhone?: true;
|
||||
};
|
||||
|
||||
export const generateNewUser = async <T extends NewUserProfileOptions>({
|
||||
export const generateNewUserProfile = <T extends NewUserProfileOptions>({
|
||||
username,
|
||||
password,
|
||||
name,
|
||||
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() } : {}),
|
||||
...(password ? { password: generatePassword() } : {}),
|
||||
...(name ? { name: generateName() } : {}),
|
||||
...(primaryEmail ? { primaryEmail: generateEmail() } : {}),
|
||||
...(primaryPhone ? { primaryPhone: generatePhone() } : {}),
|
||||
};
|
||||
|
||||
return userProfile;
|
||||
};
|
||||
|
||||
export const generateNewUser = async <T extends NewUserProfileOptions>(options: T) => {
|
||||
const userProfile = generateNewUserProfile(options);
|
||||
|
||||
const user = await createUser(userProfile);
|
||||
|
||||
return { user, userProfile };
|
||||
|
|
Loading…
Add table
Reference in a new issue