0
Fork 0
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:
simeng-li 2022-12-16 10:17:32 +08:00 committed by GitHub
parent 57a28be292
commit 61f00449da
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 333 additions and 53 deletions

View file

@ -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);
});
});

View file

@ -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);
});
});

View file

@ -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);
});

View file

@ -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);

View file

@ -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 };