0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-30 20:33:54 -05:00

feat(test): add fulfill user profile integration tests (#2710)

This commit is contained in:
simeng-li 2022-12-27 10:29:01 +08:00 committed by GitHub
parent dc2c8ece22
commit 54b0574e18
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 499 additions and 19 deletions

View file

@ -227,6 +227,17 @@ describe('session -> interactionRoutes', () => {
provider: createMockProvider(jest.fn().mockResolvedValue(baseProviderMock)), provider: createMockProvider(jest.fn().mockResolvedValue(baseProviderMock)),
}); });
it('PUT /interaction/profile', async () => {
const body = {
email: 'email@logto.io',
};
const response = await sessionRequest.put(path).send(body);
expect(verifyProfileSettings).toBeCalled();
expect(getInteractionStorage).toBeCalled();
expect(storeInteractionResult).toBeCalled();
expect(response.status).toEqual(204);
});
it('PATCH /interaction/profile', async () => { it('PATCH /interaction/profile', async () => {
const body = { const body = {
email: 'email@logto.io', email: 'email@logto.io',

View file

@ -166,6 +166,37 @@ export default function interactionRoutes<T extends AnonymousRouter>(
} }
); );
// Replace Interaction Profile
router.put(
`${interactionPrefix}/profile`,
koaGuard({
body: profileGuard,
}),
koaInteractionSie(),
async (ctx, next) => {
const profilePayload = ctx.guard.body;
const { signInExperience, interactionDetails } = ctx;
verifyProfileSettings(profilePayload, signInExperience);
// Check interaction exists
getInteractionStorage(interactionDetails.result);
await storeInteractionResult(
{
profile: profilePayload,
},
ctx,
provider,
true
);
ctx.status = 204;
return next();
}
);
// Update Interaction Profile // Update Interaction Profile
router.patch( router.patch(
`${interactionPrefix}/profile`, `${interactionPrefix}/profile`,

View file

@ -44,6 +44,15 @@ export const patchInteractionProfile = async (cookie: string, payload: Profile)
}) })
.json(); .json();
export const putInteractionProfile = async (cookie: string, payload: Profile) =>
api
.put('interaction/profile', {
headers: { cookie },
json: payload,
followRedirect: false,
})
.json();
export const deleteInteractionProfile = async (cookie: string) => export const deleteInteractionProfile = async (cookie: string) =>
api api
.delete('interaction/profile', { .delete('interaction/profile', {

View file

@ -5,6 +5,7 @@ import {
sendVerificationPasscode, sendVerificationPasscode,
deleteUser, deleteUser,
patchInteractionIdentifiers, patchInteractionIdentifiers,
putInteractionProfile,
patchInteractionProfile, patchInteractionProfile,
} from '#src/api/index.js'; } from '#src/api/index.js';
import { expectRejects, readPasscode } from '#src/helpers.js'; import { expectRejects, readPasscode } from '#src/helpers.js';
@ -61,7 +62,7 @@ describe('reset password', () => {
await expectRejects(client.submitInteraction(), 'user.new_password_required_in_profile'); await expectRejects(client.submitInteraction(), 'user.new_password_required_in_profile');
await client.successSend(patchInteractionProfile, { password: userProfile.password }); await client.successSend(putInteractionProfile, { password: userProfile.password });
await expectRejects(client.submitInteraction(), 'user.same_password'); await expectRejects(client.submitInteraction(), 'user.same_password');
@ -115,7 +116,7 @@ describe('reset password', () => {
await expectRejects(client.submitInteraction(), 'user.new_password_required_in_profile'); await expectRejects(client.submitInteraction(), 'user.new_password_required_in_profile');
await client.successSend(patchInteractionProfile, { password: userProfile.password }); await client.successSend(putInteractionProfile, { password: userProfile.password });
await expectRejects(client.submitInteraction(), 'user.same_password'); await expectRejects(client.submitInteraction(), 'user.same_password');

View file

@ -6,6 +6,7 @@ import {
putInteraction, putInteraction,
deleteUser, deleteUser,
patchInteractionIdentifiers, patchInteractionIdentifiers,
putInteractionProfile,
patchInteractionProfile, patchInteractionProfile,
deleteInteractionProfile, deleteInteractionProfile,
putInteractionEvent, putInteractionEvent,
@ -90,7 +91,7 @@ describe('Register with passwordless identifier', () => {
passcode: code, passcode: code,
}); });
await client.successSend(patchInteractionProfile, { await client.successSend(putInteractionProfile, {
email: primaryEmail, email: primaryEmail,
}); });
@ -101,6 +102,68 @@ describe('Register with passwordless identifier', () => {
await deleteUser(id); await deleteUser(id);
}); });
it('register with email and fulfill password', async () => {
await enableAllPasscodeSignInMethods({
identifiers: [SignInIdentifier.Email],
password: true,
verify: true,
});
const { primaryEmail, password } = generateNewUserProfile({
primaryEmail: true,
password: true,
});
const client = await initClient();
await client.successSend(putInteraction, {
event: InteractionEvent.Register,
});
await client.successSend(sendVerificationPasscode, {
event: InteractionEvent.Register,
email: primaryEmail,
});
const passcodeRecord = await readPasscode();
const { code } = passcodeRecord;
await client.successSend(patchInteractionIdentifiers, {
email: primaryEmail,
passcode: code,
});
await client.successSend(putInteractionProfile, {
email: primaryEmail,
});
await expectRejects(client.submitInteraction(), 'user.missing_profile');
await client.successSend(patchInteractionProfile, {
password,
});
const { redirectTo } = await client.submitInteraction();
await processSession(client, redirectTo);
await logoutClient(client);
// SignIn with email and password
await client.initSession();
await client.successSend(putInteraction, {
event: InteractionEvent.SignIn,
identifier: {
email: primaryEmail,
password,
},
});
const { redirectTo: redirectTo2 } = await client.submitInteraction();
const id = await processSession(client, redirectTo2);
await logoutClient(client);
await deleteUser(id);
});
it('register with phone', async () => { it('register with phone', async () => {
await enableAllPasscodeSignInMethods({ await enableAllPasscodeSignInMethods({
identifiers: [SignInIdentifier.Sms], identifiers: [SignInIdentifier.Sms],
@ -134,7 +197,7 @@ describe('Register with passwordless identifier', () => {
passcode: code, passcode: code,
}); });
await client.successSend(patchInteractionProfile, { await client.successSend(putInteractionProfile, {
phone: primaryPhone, phone: primaryPhone,
}); });
@ -145,6 +208,67 @@ describe('Register with passwordless identifier', () => {
await deleteUser(id); await deleteUser(id);
}); });
it('register with phone and fulfill password', async () => {
await enableAllPasscodeSignInMethods({
identifiers: [SignInIdentifier.Sms],
password: true,
verify: true,
});
const { primaryPhone, password } = generateNewUserProfile({
primaryPhone: true,
password: true,
});
const client = await initClient();
await client.successSend(putInteraction, {
event: InteractionEvent.Register,
});
await client.successSend(sendVerificationPasscode, {
event: InteractionEvent.Register,
phone: primaryPhone,
});
const { code } = await readPasscode();
await client.successSend(patchInteractionIdentifiers, {
phone: primaryPhone,
passcode: code,
});
await client.successSend(putInteractionProfile, {
phone: primaryPhone,
});
await expectRejects(client.submitInteraction(), 'user.missing_profile');
await client.successSend(patchInteractionProfile, {
password,
});
const { redirectTo } = await client.submitInteraction();
await processSession(client, redirectTo);
await logoutClient(client);
// SignIn with phone and password
await client.initSession();
await client.successSend(putInteraction, {
event: InteractionEvent.SignIn,
identifier: {
phone: primaryPhone,
password,
},
});
const { redirectTo: redirectTo2 } = await client.submitInteraction();
const id = await processSession(client, redirectTo2);
await logoutClient(client);
await deleteUser(id);
});
it('register with exiting email', async () => { it('register with exiting email', async () => {
const { const {
user, user,
@ -182,7 +306,7 @@ describe('Register with passwordless identifier', () => {
passcode: code, passcode: code,
}); });
await client.successSend(patchInteractionProfile, { await client.successSend(putInteractionProfile, {
email: primaryEmail, email: primaryEmail,
}); });
@ -235,7 +359,7 @@ describe('Register with passwordless identifier', () => {
passcode: code, passcode: code,
}); });
await client.successSend(patchInteractionProfile, { await client.successSend(putInteractionProfile, {
phone: primaryPhone, phone: primaryPhone,
}); });

View file

@ -4,7 +4,7 @@ import {
sendVerificationPasscode, sendVerificationPasscode,
putInteraction, putInteraction,
putInteractionEvent, putInteractionEvent,
patchInteractionProfile, putInteractionProfile,
patchInteractionIdentifiers, patchInteractionIdentifiers,
deleteUser, deleteUser,
updateSignInExperience, updateSignInExperience,
@ -15,7 +15,7 @@ import { generateEmail, generatePhone } from '#src/utils.js';
import { initClient, processSession, logoutClient } from './utils/client.js'; import { initClient, processSession, logoutClient } from './utils/client.js';
import { clearConnectorsByTypes, setEmailConnector, setSmsConnector } from './utils/connector.js'; import { clearConnectorsByTypes, setEmailConnector, setSmsConnector } from './utils/connector.js';
import { enableAllPasscodeSignInMethods } from './utils/sign-in-experience.js'; import { enableAllPasscodeSignInMethods } from './utils/sign-in-experience.js';
import { generateNewUser } from './utils/user.js'; import { generateNewUser, generateNewUserProfile } from './utils/user.js';
describe('Sign-In flow using passcode identifiers', () => { describe('Sign-In flow using passcode identifiers', () => {
beforeAll(async () => { beforeAll(async () => {
@ -127,7 +127,7 @@ describe('Sign-In flow using passcode identifiers', () => {
await expectRejects(client.submitInteraction(), 'user.user_not_exist'); await expectRejects(client.submitInteraction(), 'user.user_not_exist');
await client.successSend(putInteractionEvent, { event: InteractionEvent.Register }); await client.successSend(putInteractionEvent, { event: InteractionEvent.Register });
await client.successSend(patchInteractionProfile, { email: newEmail }); await client.successSend(putInteractionProfile, { email: newEmail });
const { redirectTo } = await client.submitInteraction(); const { redirectTo } = await client.submitInteraction();
@ -167,7 +167,7 @@ describe('Sign-In flow using passcode identifiers', () => {
await expectRejects(client.submitInteraction(), 'user.user_not_exist'); await expectRejects(client.submitInteraction(), 'user.user_not_exist');
await client.successSend(putInteractionEvent, { event: InteractionEvent.Register }); await client.successSend(putInteractionEvent, { event: InteractionEvent.Register });
await client.successSend(patchInteractionProfile, { phone: newPhone }); await client.successSend(putInteractionProfile, { phone: newPhone });
const { redirectTo } = await client.submitInteraction(); const { redirectTo } = await client.submitInteraction();
@ -175,4 +175,170 @@ describe('Sign-In flow using passcode identifiers', () => {
await logoutClient(client); await logoutClient(client);
await deleteUser(id); await deleteUser(id);
}); });
// Fulfill the username and password
it('email passcode sign-in', async () => {
await updateSignInExperience({
signUp: {
identifiers: [SignInIdentifier.Username],
password: true,
verify: false,
},
});
const { userProfile, user } = await generateNewUser({ primaryEmail: true });
const { username, password } = generateNewUserProfile({ username: true, password: true });
// SignIn with email
const client = await initClient();
await client.successSend(putInteraction, {
event: InteractionEvent.SignIn,
});
await client.successSend(sendVerificationPasscode, {
event: InteractionEvent.SignIn,
email: userProfile.primaryEmail,
});
const { code } = await readPasscode();
await client.successSend(patchInteractionIdentifiers, {
email: userProfile.primaryEmail,
passcode: code,
});
await expectRejects(client.submitInteraction(), 'user.missing_profile');
// Fulfill user profile
await client.successSend(putInteractionProfile, {
username,
password,
});
const { redirectTo } = await client.submitInteraction();
await processSession(client, redirectTo);
await logoutClient(client);
await client.initSession();
// SignIn with username and password
await client.successSend(putInteraction, {
event: InteractionEvent.SignIn,
identifier: {
username,
password,
},
});
const { redirectTo: redirectTo2 } = await client.submitInteraction();
await processSession(client, redirectTo2);
await logoutClient(client);
await deleteUser(user.id);
});
it('email passcode sign-in with existing password', async () => {
await updateSignInExperience({
signUp: {
identifiers: [SignInIdentifier.Username],
password: true,
verify: false,
},
});
const { userProfile, user } = await generateNewUser({ primaryEmail: true, password: true });
const { username, password } = generateNewUserProfile({ username: true, password: true });
// SignIn with email
const client = await initClient();
await client.successSend(putInteraction, {
event: InteractionEvent.SignIn,
});
await client.successSend(sendVerificationPasscode, {
event: InteractionEvent.SignIn,
email: userProfile.primaryEmail,
});
const { code } = await readPasscode();
await client.successSend(patchInteractionIdentifiers, {
email: userProfile.primaryEmail,
passcode: code,
});
await expectRejects(client.submitInteraction(), 'user.missing_profile');
// Fulfill user profile with existing password
await client.successSend(putInteractionProfile, {
username,
password,
});
await expectRejects(client.submitInteraction(), 'user.password_exists_in_profile');
await client.successSend(putInteractionProfile, {
username,
});
const { redirectTo } = await client.submitInteraction();
await processSession(client, redirectTo);
await logoutClient(client);
await deleteUser(user.id);
});
it('email passcode sign-in with registered username', async () => {
await updateSignInExperience({
signUp: {
identifiers: [SignInIdentifier.Username],
password: true,
verify: false,
},
});
const { userProfile, user } = await generateNewUser({ primaryEmail: true });
const { userProfile: userProfile2, user: user2 } = await generateNewUser({ username: true });
const { username, password } = generateNewUserProfile({ username: true, password: true });
// SignIn with email
const client = await initClient();
await client.successSend(putInteraction, {
event: InteractionEvent.SignIn,
});
await client.successSend(sendVerificationPasscode, {
event: InteractionEvent.SignIn,
email: userProfile.primaryEmail,
});
const { code } = await readPasscode();
await client.successSend(patchInteractionIdentifiers, {
email: userProfile.primaryEmail,
passcode: code,
});
await expectRejects(client.submitInteraction(), 'user.missing_profile');
// Fulfill user profile with existing password
await client.successSend(putInteractionProfile, {
username: userProfile2.username,
password,
});
await expectRejects(client.submitInteraction(), 'user.username_already_in_use');
await client.successSend(putInteractionProfile, {
username,
password,
});
const { redirectTo } = await client.submitInteraction();
await processSession(client, redirectTo);
await logoutClient(client);
await deleteUser(user.id);
await deleteUser(user2.id);
});
}); });

View file

@ -1,14 +1,32 @@
import { InteractionEvent } from '@logto/schemas'; import { InteractionEvent, ConnectorType, SignInIdentifier } from '@logto/schemas';
import { putInteraction, deleteUser } from '#src/api/index.js'; import {
putInteraction,
sendVerificationPasscode,
patchInteractionIdentifiers,
putInteractionProfile,
deleteUser,
} from '#src/api/index.js';
import { readPasscode, expectRejects } from '#src/helpers.js';
import { initClient, processSession, logoutClient } from './utils/client.js'; import { initClient, processSession, logoutClient } from './utils/client.js';
import { enableAllPasswordSignInMethods } from './utils/sign-in-experience.js'; import { clearConnectorsByTypes, setSmsConnector, setEmailConnector } from './utils/connector.js';
import { generateNewUser } from './utils/user.js'; import {
enableAllPasswordSignInMethods,
enableAllPasscodeSignInMethods,
} from './utils/sign-in-experience.js';
import { generateNewUser, generateNewUserProfile } from './utils/user.js';
describe('Sign-In flow using password identifiers', () => { describe('Sign-In flow using password identifiers', () => {
beforeAll(async () => { beforeAll(async () => {
await enableAllPasswordSignInMethods(); await enableAllPasswordSignInMethods();
await clearConnectorsByTypes([ConnectorType.Sms, ConnectorType.Email]);
await setSmsConnector();
await setEmailConnector();
});
afterAll(async () => {
await clearConnectorsByTypes([ConnectorType.Sms, ConnectorType.Email]);
}); });
it('sign-in with username and password', async () => { it('sign-in with username and password', async () => {
@ -70,4 +88,124 @@ describe('Sign-In flow using password identifiers', () => {
await deleteUser(user.id); await deleteUser(user.id);
}); });
// Fulfill the email address
it('sign-in with username and password and fulfill the email', async () => {
await enableAllPasscodeSignInMethods({
identifiers: [SignInIdentifier.Email],
password: true,
verify: true,
});
const { userProfile, user } = await generateNewUser({ username: true, password: true });
const { primaryEmail } = generateNewUserProfile({ primaryEmail: true });
const client = await initClient();
await client.successSend(putInteraction, {
event: InteractionEvent.SignIn,
identifier: {
username: userProfile.username,
password: userProfile.password,
},
});
await expectRejects(client.submitInteraction(), 'user.missing_profile');
await client.successSend(sendVerificationPasscode, {
event: InteractionEvent.SignIn,
email: primaryEmail,
});
const { code } = await readPasscode();
await client.successSend(patchInteractionIdentifiers, {
email: primaryEmail,
passcode: code,
});
await client.successSend(putInteractionProfile, {
email: primaryEmail,
});
const { redirectTo } = await client.submitInteraction();
await processSession(client, redirectTo);
await logoutClient(client);
// SignIn with email and password
await client.initSession();
await client.successSend(putInteraction, {
event: InteractionEvent.SignIn,
identifier: {
email: primaryEmail,
password: userProfile.password,
},
});
const { redirectTo: redirectTo2 } = await client.submitInteraction();
await processSession(client, redirectTo2);
await logoutClient(client);
await deleteUser(user.id);
});
// Fulfill the phone number
it('sign-in with username and password and fulfill the phone number', async () => {
await enableAllPasscodeSignInMethods({
identifiers: [SignInIdentifier.Sms, SignInIdentifier.Email],
password: true,
verify: true,
});
const { userProfile, user } = await generateNewUser({ username: true, password: true });
const { primaryPhone } = generateNewUserProfile({ primaryPhone: true });
const client = await initClient();
await client.successSend(putInteraction, {
event: InteractionEvent.SignIn,
identifier: {
username: userProfile.username,
password: userProfile.password,
},
});
await expectRejects(client.submitInteraction(), 'user.missing_profile');
await client.successSend(sendVerificationPasscode, {
event: InteractionEvent.SignIn,
phone: primaryPhone,
});
const { code } = await readPasscode();
await client.successSend(patchInteractionIdentifiers, {
phone: primaryPhone,
passcode: code,
});
await client.successSend(putInteractionProfile, {
phone: primaryPhone,
});
const { redirectTo } = await client.submitInteraction();
await processSession(client, redirectTo);
await logoutClient(client);
// SignIn with new phone and password
await client.initSession();
await client.successSend(putInteraction, {
event: InteractionEvent.SignIn,
identifier: {
phone: primaryPhone,
password: userProfile.password,
},
});
const { redirectTo: redirectTo2 } = await client.submitInteraction();
await processSession(client, redirectTo2);
await logoutClient(client);
await deleteUser(user.id);
});
}); });

View file

@ -7,7 +7,7 @@ import {
deleteUser, deleteUser,
putInteractionEvent, putInteractionEvent,
patchInteractionIdentifiers, patchInteractionIdentifiers,
patchInteractionProfile, putInteractionProfile,
} from '#src/api/index.js'; } from '#src/api/index.js';
import { expectRejects } from '#src/helpers.js'; import { expectRejects } from '#src/helpers.js';
import { generateUserId } from '#src/utils.js'; import { generateUserId } from '#src/utils.js';
@ -61,7 +61,7 @@ describe('Social Identifier Interactions', () => {
await expectRejects(client.submitInteraction(), 'user.identity_not_exist'); await expectRejects(client.submitInteraction(), 'user.identity_not_exist');
await client.successSend(putInteractionEvent, { event: InteractionEvent.Register }); await client.successSend(putInteractionEvent, { event: InteractionEvent.Register });
await client.successSend(patchInteractionProfile, { connectorId }); await client.successSend(putInteractionProfile, { connectorId });
const { redirectTo } = await client.submitInteraction(); const { redirectTo } = await client.submitInteraction();
@ -120,7 +120,7 @@ describe('Social Identifier Interactions', () => {
await expectRejects(client.submitInteraction(), 'user.identity_not_exist'); await expectRejects(client.submitInteraction(), 'user.identity_not_exist');
await client.successSend(patchInteractionIdentifiers, { username, password }); await client.successSend(patchInteractionIdentifiers, { username, password });
await client.successSend(patchInteractionProfile, { connectorId }); await client.successSend(putInteractionProfile, { connectorId });
const { redirectTo } = await client.submitInteraction(); const { redirectTo } = await client.submitInteraction();
@ -179,7 +179,7 @@ describe('Social Identifier Interactions', () => {
await expectRejects(client.submitInteraction(), 'user.identity_not_exist'); await expectRejects(client.submitInteraction(), 'user.identity_not_exist');
await client.successSend(patchInteractionIdentifiers, { connectorId, identityType: 'email' }); await client.successSend(patchInteractionIdentifiers, { connectorId, identityType: 'email' });
await client.successSend(patchInteractionProfile, { connectorId }); await client.successSend(putInteractionProfile, { connectorId });
const { redirectTo } = await client.submitInteraction(); const { redirectTo } = await client.submitInteraction();

View file

@ -35,7 +35,7 @@ const defaultPasscodeSignInMethods = [
identifier: SignInIdentifier.Username, identifier: SignInIdentifier.Username,
password: true, password: true,
verificationCode: false, verificationCode: false,
isPasswordPrimary: false, isPasswordPrimary: true,
}, },
{ {
identifier: SignInIdentifier.Email, identifier: SignInIdentifier.Email,