Skip to content

Commit

Permalink
test(core): add register integration tests
Browse files Browse the repository at this point in the history
add register integration tests
  • Loading branch information
simeng-li committed Jul 15, 2024
1 parent 6d9663c commit 403a37c
Show file tree
Hide file tree
Showing 6 changed files with 303 additions and 12 deletions.
2 changes: 1 addition & 1 deletion packages/core/src/routes/experience/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export default function experienceApiRoutes<T extends AnonymousRouter>(
router.post(
`${experienceRoutes.prefix}/submit`,
koaGuard({
status: [200],
status: [200, 400],
response: z.object({
redirectTo: z.string(),
}),
Expand Down
10 changes: 4 additions & 6 deletions packages/integration-tests/src/client/experience/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,10 @@ export const identifyUser = async (cookie: string, payload: IdentificationApiPay

export class ExperienceClient extends MockClient {
public async identifyUser(payload: IdentificationApiPayload) {
return api
.post(experienceRoutes.identification, {
headers: { cookie: this.interactionCookie },
json: payload,
})
.json();
return api.post(experienceRoutes.identification, {
headers: { cookie: this.interactionCookie },
json: payload,
});
}

public async updateInteractionEvent(payload: { interactionEvent: InteractionEvent }) {
Expand Down
84 changes: 84 additions & 0 deletions packages/integration-tests/src/helpers/experience/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* @fileoverview This file contains the successful interaction flow helper functions that use the experience APIs.
*/

import { type SocialUserInfo } from '@logto/connector-kit';
import {
InteractionEvent,
SignInIdentifier,
Expand All @@ -12,7 +13,12 @@ import {
import { type ExperienceClient } from '#src/client/experience/index.js';

import { initExperienceClient, logoutClient, processSession } from '../client.js';
import { expectRejects } from '../index.js';

import {
successFullyCreateSocialVerification,
successFullyVerifySocialAuthorization,
} from './social-verification.js';
import {
successfullySendVerificationCode,
successfullyVerifyVerificationCode,
Expand Down Expand Up @@ -96,3 +102,81 @@ export const identifyUserWithUsernamePassword = async (

return { verificationId };
};

export const registerNewUserWithVerificationCode = async (
identifier: VerificationCodeIdentifier
) => {
const client = await initExperienceClient();

await client.initInteraction({ interactionEvent: InteractionEvent.Register });

const { verificationId, code } = await successfullySendVerificationCode(client, {
identifier,
interactionEvent: InteractionEvent.Register,
});

const verifiedVerificationId = await successfullyVerifyVerificationCode(client, {
identifier,
verificationId,
code,
});

await client.identifyUser({
verificationId: verifiedVerificationId,
});

const { redirectTo } = await client.submitInteraction();

const userId = await processSession(client, redirectTo);
await logoutClient(client);

return userId;
};

export const signInWithSocial = async (
connectorId: string,
socialUserInfo: SocialUserInfo,
registerNewUser = false
) => {
const state = 'state';
const redirectUri = 'http://localhost:3000';

const client = await initExperienceClient();
await client.initInteraction({ interactionEvent: InteractionEvent.SignIn });

const { verificationId } = await successFullyCreateSocialVerification(client, connectorId, {
redirectUri,
state,
});

await successFullyVerifySocialAuthorization(client, connectorId, {
verificationId,
connectorData: {
state,
redirectUri,
code: 'fake_code',
userId: socialUserInfo.id,
email: socialUserInfo.email,
},
});

if (registerNewUser) {
await expectRejects(client.identifyUser({ verificationId }), {
code: 'user.identity_not_exist',
status: 404,
});

await client.updateInteractionEvent({ interactionEvent: InteractionEvent.Register });
await client.identifyUser({ verificationId });
} else {
await client.identifyUser({
verificationId,
});
}

const { redirectTo } = await client.submitInteraction();
const userId = await processSession(client, redirectTo);
await logoutClient(client);

return userId;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import {
InteractionEvent,
SignInIdentifier,
type VerificationCodeIdentifier,
} from '@logto/schemas';

import { deleteUser } from '#src/api/admin-user.js';
import { initExperienceClient, logoutClient, processSession } from '#src/helpers/client.js';
import { setEmailConnector, setSmsConnector } from '#src/helpers/connector.js';
import { registerNewUserWithVerificationCode } from '#src/helpers/experience/index.js';
import {
successfullySendVerificationCode,
successfullyVerifyVerificationCode,
} from '#src/helpers/experience/verification-code.js';
import { expectRejects } from '#src/helpers/index.js';
import { enableAllVerificationCodeSignInMethods } from '#src/helpers/sign-in-experience.js';
import { generateNewUser } from '#src/helpers/user.js';
import { devFeatureTest, generateEmail, generatePhone } from '#src/utils.js';

const verificationIdentifierType: readonly [SignInIdentifier.Email, SignInIdentifier.Phone] =
Object.freeze([SignInIdentifier.Email, SignInIdentifier.Phone]);

const identifiersTypeToUserProfile = Object.freeze({
email: 'primaryEmail',
phone: 'primaryPhone',
});

devFeatureTest.describe('Register interaction with verification code happy path', () => {
beforeAll(async () => {
await Promise.all([setEmailConnector(), setSmsConnector()]);
await enableAllVerificationCodeSignInMethods({
identifiers: [SignInIdentifier.Email, SignInIdentifier.Phone],
password: false,
verify: true,
});
});

it.each(verificationIdentifierType)(
'Should register with verification code using %p successfully',
async (identifier) => {
const userId = await registerNewUserWithVerificationCode({
type: identifier,
value: identifier === SignInIdentifier.Email ? generateEmail() : generatePhone(),
});

await deleteUser(userId);
}
);

it.each(verificationIdentifierType)(
'Should fail to sign-up with existing %p identifier and directly sign-in instead ',
async (identifierType) => {
const { userProfile, user } = await generateNewUser({
[identifiersTypeToUserProfile[identifierType]]: true,
});

const identifier: VerificationCodeIdentifier = {
type: identifierType,
value: userProfile[identifiersTypeToUserProfile[identifierType]]!,
};

const client = await initExperienceClient();
await client.initInteraction({ interactionEvent: InteractionEvent.Register });

const { verificationId, code } = await successfullySendVerificationCode(client, {
identifier,
interactionEvent: InteractionEvent.Register,
});

await successfullyVerifyVerificationCode(client, {
identifier,
verificationId,
code,
});

await expectRejects(
client.identifyUser({
verificationId,
}),
{
code: `user.${identifierType}_already_in_use`,
status: 422,
}
);

await client.updateInteractionEvent({
interactionEvent: InteractionEvent.SignIn,
});

await client.identifyUser({
verificationId,
});

const { redirectTo } = await client.submitInteraction();
await processSession(client, redirectTo);
await logoutClient(client);

await deleteUser(user.id);
}
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { ConnectorType } from '@logto/connector-kit';
import { generateStandardId } from '@logto/shared';

import { mockSocialConnectorId } from '#src/__mocks__/connectors-mock.js';
import { deleteUser, getUser } from '#src/api/admin-user.js';
import { clearConnectorsByTypes, setSocialConnector } from '#src/helpers/connector.js';
import { signInWithSocial } from '#src/helpers/experience/index.js';
import { devFeatureTest, generateEmail } from '#src/utils.js';

devFeatureTest.describe('social sign-in and sign-up', () => {
const connectorIdMap = new Map<string, string>();
const socialUserId = generateStandardId();
const email = generateEmail();

beforeAll(async () => {
await clearConnectorsByTypes([ConnectorType.Social]);

const { id: socialConnectorId } = await setSocialConnector();
connectorIdMap.set(mockSocialConnectorId, socialConnectorId);
});

afterAll(async () => {
await clearConnectorsByTypes([ConnectorType.Social]);
});

it('should successfully sign-up with social and sync email', async () => {
const userId = await signInWithSocial(
connectorIdMap.get(mockSocialConnectorId)!,
{
id: socialUserId,
email,
},
true
);

const { primaryEmail } = await getUser(userId);
expect(primaryEmail).toBe(email);
});

it('should successfully sign-in with social', async () => {
const userId = await signInWithSocial(connectorIdMap.get(mockSocialConnectorId)!, {
id: socialUserId,
email,
});

await deleteUser(userId);
});
});
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import { SignInIdentifier } from '@logto/schemas';
import {
InteractionEvent,
SignInIdentifier,
type VerificationCodeIdentifier,
} from '@logto/schemas';

import { deleteUser } from '#src/api/admin-user.js';
import { initExperienceClient, logoutClient, processSession } from '#src/helpers/client.js';
import { setEmailConnector, setSmsConnector } from '#src/helpers/connector.js';
import { signInWithVerificationCode } from '#src/helpers/experience/index.js';
import {
successfullySendVerificationCode,
successfullyVerifyVerificationCode,
} from '#src/helpers/experience/verification-code.js';
import { expectRejects } from '#src/helpers/index.js';
import { enableAllVerificationCodeSignInMethods } from '#src/helpers/sign-in-experience.js';
import { generateNewUser } from '#src/helpers/user.js';
import { devFeatureTest } from '#src/utils.js';
import { devFeatureTest, generateEmail, generatePhone } from '#src/utils.js';

const verificationIdentifierType: readonly [SignInIdentifier.Email, SignInIdentifier.Phone] =
Object.freeze([SignInIdentifier.Email, SignInIdentifier.Phone]);
Expand All @@ -15,14 +25,18 @@ const identifiersTypeToUserProfile = Object.freeze({
phone: 'primaryPhone',
});

devFeatureTest.describe('Sign-in with verification code happy path', () => {
devFeatureTest.describe('Sign-in with verification code', () => {
beforeAll(async () => {
await Promise.all([setEmailConnector(), setSmsConnector()]);
await enableAllVerificationCodeSignInMethods();
await enableAllVerificationCodeSignInMethods({
identifiers: [SignInIdentifier.Email, SignInIdentifier.Phone],
password: false,
verify: true,
});
});

it.each(verificationIdentifierType)(
'Should sign-in with verification code using %p',
'should sign-in with verification code using %p',
async (identifier) => {
const { userProfile, user } = await generateNewUser({
[identifiersTypeToUserProfile[identifier]]: true,
Expand All @@ -37,4 +51,50 @@ devFeatureTest.describe('Sign-in with verification code happy path', () => {
await deleteUser(user.id);
}
);

it.each(verificationIdentifierType)(
'should fail to sign-in with non-existing %p identifier and directly sign-up instead',
async (type) => {
const identifier: VerificationCodeIdentifier = {
type,
value: type === SignInIdentifier.Email ? generateEmail() : generatePhone(),
};

const client = await initExperienceClient();
await client.initInteraction({ interactionEvent: InteractionEvent.SignIn });

const { verificationId, code } = await successfullySendVerificationCode(client, {
identifier,
interactionEvent: InteractionEvent.SignIn,
});

await successfullyVerifyVerificationCode(client, {
identifier,
verificationId,
code,
});

await expectRejects(
client.identifyUser({
verificationId,
}),
{
code: 'user.user_not_exist',
status: 404,
}
);

await client.updateInteractionEvent({ interactionEvent: InteractionEvent.Register });

await client.identifyUser({
verificationId,
});

const { redirectTo } = await client.submitInteraction();
const userId = await processSession(client, redirectTo);
await logoutClient(client);

await deleteUser(userId);
}
);
});

0 comments on commit 403a37c

Please sign in to comment.