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

test: add sad paths for social interaction flow (#4300)

This commit is contained in:
Xiao Yijun 2023-08-09 12:10:49 +08:00 committed by GitHub
parent 2660a1e132
commit dfde117b6d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 367 additions and 0 deletions

View file

@ -0,0 +1,367 @@
import { ConnectorType } from '@logto/connector-kit';
import { InteractionEvent } from '@logto/schemas';
import { type Optional } from '@silverhand/essentials';
import {
mockEmailConnectorId,
mockSmsConnectorId,
mockSocialConnectorId,
} from '#src/__mocks__/connectors-mock.js';
import { suspendUser } from '#src/api/admin-user.js';
import {
createSocialAuthorizationUri,
patchInteractionIdentifiers,
putInteraction,
putInteractionEvent,
putInteractionProfile,
} from '#src/api/interaction.js';
import { initClient, logoutClient, processSession } from '#src/helpers/client.js';
import {
clearConnectorsByTypes,
setSocialConnector,
setEmailConnector,
setSmsConnector,
} from '#src/helpers/connector.js';
import { expectRejects } from '#src/helpers/index.js';
import { enableAllPasswordSignInMethods } from '#src/helpers/sign-in-experience.js';
import { generateEmail, generatePhone, generateUserId } from '#src/utils.js';
const state = 'foo_state';
const redirectUri = 'http://foo.dev/callback';
const code = 'auth_code_foo';
describe('Social identifier interaction sad path', () => {
const connectorIdMap = new Map<string, string>();
const userSocialId = generateUserId();
// eslint-disable-next-line @silverhand/fp/no-let
let createdUserId: Optional<string>;
beforeAll(async () => {
await enableAllPasswordSignInMethods();
await clearConnectorsByTypes([ConnectorType.Social, ConnectorType.Email, ConnectorType.Sms]);
const { id: socialConnectorId } = await setSocialConnector();
const { id: emailConnectorId } = await setEmailConnector();
const { id: smsConnectorId } = await setSmsConnector();
connectorIdMap.set(mockSocialConnectorId, socialConnectorId);
connectorIdMap.set(mockEmailConnectorId, emailConnectorId);
connectorIdMap.set(mockSmsConnectorId, smsConnectorId);
});
afterAll(async () => {
await clearConnectorsByTypes([ConnectorType.Social, ConnectorType.Email, ConnectorType.Sms]);
});
/**
* Note: As currently we can not prepare a social identities through admin api, register a user manually.
*/
it('register with social', async () => {
const client = await initClient();
const connectorId = connectorIdMap.get(mockSocialConnectorId) ?? '';
await client.successSend(putInteraction, {
event: InteractionEvent.SignIn,
});
await client.successSend(createSocialAuthorizationUri, { state, redirectUri, connectorId });
await client.successSend(patchInteractionIdentifiers, {
connectorId,
connectorData: { state, redirectUri, code, userId: userSocialId },
});
await expectRejects(client.submitInteraction(), {
code: 'user.identity_not_exist',
statusCode: 422,
});
await client.successSend(putInteractionEvent, { event: InteractionEvent.Register });
await client.successSend(putInteractionProfile, { connectorId });
const { redirectTo } = await client.submitInteraction();
// eslint-disable-next-line @silverhand/fp/no-mutation
createdUserId = await processSession(client, redirectTo);
await logoutClient(client);
});
it('Should fail to sign-in if state is an empty string', async () => {
const client = await initClient();
const connectorId = connectorIdMap.get(mockSocialConnectorId) ?? '';
await client.successSend(putInteraction, {
event: InteractionEvent.SignIn,
});
await expectRejects(
client.send(createSocialAuthorizationUri, {
state: '',
redirectUri,
connectorId,
}),
{
code: 'session.insufficient_info',
statusCode: 400,
}
);
});
it('Should fail to sign-in if an invalid redirectUri is provided', async () => {
const client = await initClient();
const connectorId = connectorIdMap.get(mockSocialConnectorId) ?? '';
await client.successSend(putInteraction, {
event: InteractionEvent.SignIn,
});
await expectRejects(
client.send(createSocialAuthorizationUri, {
state,
redirectUri: 'invalid_uri',
connectorId,
}),
{
code: 'guard.invalid_input',
statusCode: 400,
}
);
});
it('Should fail to sign-in if the related connector is not a social connector', async () => {
const client = await initClient();
await client.successSend(putInteraction, {
event: InteractionEvent.SignIn,
});
// Use email connector id
await expectRejects(
client.send(createSocialAuthorizationUri, {
state,
redirectUri,
connectorId: connectorIdMap.get(mockEmailConnectorId) ?? '',
}),
{
code: 'connector.unexpected_type',
statusCode: 400,
}
);
// Use sms connector id
await expectRejects(
client.send(createSocialAuthorizationUri, {
state,
redirectUri,
connectorId: connectorIdMap.get(mockSmsConnectorId) ?? '',
}),
{
code: 'connector.unexpected_type',
statusCode: 400,
}
);
});
it('Should fail to sign-in if the related connector is not found', async () => {
const client = await initClient();
await client.successSend(putInteraction, {
event: InteractionEvent.SignIn,
});
await expectRejects(
client.send(createSocialAuthorizationUri, {
state,
redirectUri,
connectorId: 'invalid_connector_id',
}),
{
code: 'entity.not_found',
statusCode: 404,
}
);
});
it('Should fail to update interaction identifiers if related connector id is incorrect', async () => {
const client = await initClient();
const connectorId = connectorIdMap.get(mockSocialConnectorId) ?? '';
await client.successSend(putInteraction, {
event: InteractionEvent.SignIn,
});
await client.successSend(createSocialAuthorizationUri, { state, redirectUri, connectorId });
await expectRejects(
client.send(patchInteractionIdentifiers, {
connectorId: connectorIdMap.get(mockEmailConnectorId) ?? '',
connectorData: { state, redirectUri, code, socialUserId: userSocialId },
}),
{
code: 'session.invalid_connector_id',
statusCode: 422,
}
);
});
it('Should fail to update interaction identifiers if related connector id is not exist', async () => {
const client = await initClient();
const connectorId = connectorIdMap.get(mockSocialConnectorId) ?? '';
await client.successSend(putInteraction, {
event: InteractionEvent.SignIn,
});
await client.successSend(createSocialAuthorizationUri, { state, redirectUri, connectorId });
await expectRejects(
client.send(patchInteractionIdentifiers, {
connectorId: 'non_exist_connector_id',
connectorData: { state, redirectUri, code, socialUserId: userSocialId },
}),
{
code: 'session.invalid_connector_id',
statusCode: 422,
}
);
});
it('Should fail to update verified identifier if the social interaction is not verified', async () => {
const client = await initClient();
const connectorId = connectorIdMap.get(mockSocialConnectorId) ?? '';
await client.successSend(putInteraction, {
event: InteractionEvent.SignIn,
});
await client.successSend(createSocialAuthorizationUri, { state, redirectUri, connectorId });
// Email
await expectRejects(
client.send(patchInteractionIdentifiers, {
connectorId,
email: generateEmail(),
}),
{
code: 'session.connector_session_not_found',
statusCode: 400,
}
);
// Phone
await expectRejects(
client.send(patchInteractionIdentifiers, {
connectorId,
phone: generatePhone(),
}),
{
code: 'session.connector_session_not_found',
statusCode: 400,
}
);
});
it('Should fail to update verified identifiers if related identifiers are not belong to the user', async () => {
const client = await initClient();
const connectorId = connectorIdMap.get(mockSocialConnectorId) ?? '';
await client.successSend(putInteraction, {
event: InteractionEvent.SignIn,
});
await client.successSend(createSocialAuthorizationUri, { state, redirectUri, connectorId });
// Verify social interaction
await client.successSend(patchInteractionIdentifiers, {
connectorId,
connectorData: { state, redirectUri, code, userId: userSocialId },
});
// Email
await expectRejects(
client.send(patchInteractionIdentifiers, {
connectorId,
email: generateEmail(),
}),
{
code: 'session.connector_session_not_found',
statusCode: 400,
}
);
// Phone
await expectRejects(
client.send(patchInteractionIdentifiers, {
connectorId,
phone: generatePhone(),
}),
{
code: 'session.connector_session_not_found',
statusCode: 400,
}
);
});
it('Should fail to sign in with social if related user is suspended', async () => {
const userId = createdUserId ?? '';
await suspendUser(userId, true);
const client = await initClient();
const connectorId = connectorIdMap.get(mockSocialConnectorId) ?? '';
await client.successSend(putInteraction, {
event: InteractionEvent.SignIn,
});
await client.successSend(createSocialAuthorizationUri, { state, redirectUri, connectorId });
await client.successSend(patchInteractionIdentifiers, {
connectorId,
connectorData: { state, redirectUri, code, userId: userSocialId },
});
await expectRejects(client.submitInteraction(), {
code: 'user.suspended',
statusCode: 401,
});
// Reset
await suspendUser(userId, false);
});
it('Should fail to sync profile if related connector id is incorrect on social user registration', async () => {
const newUserSocialId = generateUserId();
const client = await initClient();
const socialEmail = generateEmail();
const socialConnectorId = connectorIdMap.get(mockSocialConnectorId) ?? '';
const smsConnectorId = connectorIdMap.get(mockSmsConnectorId) ?? '';
await client.successSend(putInteraction, {
event: InteractionEvent.SignIn,
});
await client.successSend(createSocialAuthorizationUri, {
state,
redirectUri,
connectorId: socialConnectorId,
});
await client.successSend(patchInteractionIdentifiers, {
connectorId: socialConnectorId,
connectorData: { state, redirectUri, code, userId: newUserSocialId, email: socialEmail },
});
await expectRejects(client.submitInteraction(), {
code: 'user.identity_not_exist',
statusCode: 422,
});
await client.successSend(putInteractionEvent, { event: InteractionEvent.Register });
await client.successSend(putInteractionProfile, {
connectorId: smsConnectorId,
});
await expectRejects(client.submitInteraction(), {
code: 'session.connector_session_not_found',
statusCode: 404,
});
});
});