mirror of
https://github.com/logto-io/logto.git
synced 2025-01-06 20:40:08 -05:00
test(test): add social integration tests (#1721)
* test(test): add social integration tests add social integration tests * fix(test): align format align format * fix(test): cr update cr update * fix(test): cr update cr update * fix(test): cr update cr update
This commit is contained in:
parent
ff151b2010
commit
f257e4deb2
4 changed files with 202 additions and 3 deletions
|
@ -10,6 +10,7 @@ import {
|
|||
SocialConnectorInstance,
|
||||
GetConnectorConfig,
|
||||
} from '@logto/connector-types';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { defaultMetadata } from './constant';
|
||||
import { mockSocialConfigGuard, MockSocialConfig } from './types';
|
||||
|
@ -46,7 +47,17 @@ export default class MockSocialConnector implements SocialConnectorInstance<Mock
|
|||
|
||||
public getAccessToken = async () => randomUUID();
|
||||
|
||||
public getUserInfo: GetUserInfo = async () => ({
|
||||
id: `mock-social-sub-${randomUUID()}`,
|
||||
});
|
||||
public getUserInfo: GetUserInfo = async (data) => {
|
||||
const dataGuard = z.object({ code: z.string(), userId: z.optional(z.string()) });
|
||||
const result = dataGuard.safeParse(data);
|
||||
|
||||
if (!result.success) {
|
||||
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, JSON.stringify(data));
|
||||
}
|
||||
|
||||
// For mock use only. Use to track the created user entity
|
||||
return {
|
||||
id: result.data.userId ?? `mock-social-sub-${randomUUID()}`,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -157,3 +157,42 @@ export const verifySignInUserWithSmsPasscode = (
|
|||
},
|
||||
})
|
||||
.json<RedirectResponse>();
|
||||
|
||||
export const signInWithSocial = (
|
||||
payload: {
|
||||
connectorId: string;
|
||||
state: string;
|
||||
redirectUri: string;
|
||||
},
|
||||
interactionCookie: string
|
||||
) =>
|
||||
api
|
||||
.post('session/sign-in/social', { headers: { cookie: interactionCookie }, json: payload })
|
||||
.json<RedirectResponse>();
|
||||
|
||||
export const getAuthWithSocial = (
|
||||
payload: { connectorId: string; data: unknown },
|
||||
interactionCookie: string
|
||||
) =>
|
||||
api
|
||||
.post('session/sign-in/social/auth', {
|
||||
headers: { cookie: interactionCookie },
|
||||
json: payload,
|
||||
})
|
||||
.json<RedirectResponse>();
|
||||
|
||||
export const registerWithSocial = (connectorId: string, interactionCookie: string) =>
|
||||
api
|
||||
.post('session/register/social', {
|
||||
headers: { cookie: interactionCookie },
|
||||
json: { connectorId },
|
||||
})
|
||||
.json<RedirectResponse>();
|
||||
|
||||
export const bindWithSocial = (connectorId: string, interactionCookie: string) =>
|
||||
api
|
||||
.post('session/bind-social', {
|
||||
headers: { cookie: interactionCookie },
|
||||
json: { connectorId },
|
||||
})
|
||||
.json<RedirectResponse>();
|
||||
|
|
|
@ -95,6 +95,10 @@ export default class MockClient {
|
|||
return this.logto.isAuthenticated;
|
||||
}
|
||||
|
||||
public getIdTokenClaims() {
|
||||
return this.logto.getIdTokenClaims();
|
||||
}
|
||||
|
||||
private readonly consent = async () => {
|
||||
// Note: If sign in action completed successfully, we will get `_session.sig` in the cookie.
|
||||
assert(this.interactionCookie, new Error('Session not found'));
|
||||
|
|
145
packages/integration-tests/tests/social-session.test.ts
Normal file
145
packages/integration-tests/tests/social-session.test.ts
Normal file
|
@ -0,0 +1,145 @@
|
|||
import { assert } from '@silverhand/essentials';
|
||||
import { HTTPError } from 'got';
|
||||
|
||||
import {
|
||||
mockSocialConnectorId,
|
||||
mockSocialConnectorTarget,
|
||||
mockSocialConnectorConfig,
|
||||
} from '@/__mocks__/connectors-mock';
|
||||
import {
|
||||
signInWithSocial,
|
||||
getAuthWithSocial,
|
||||
registerWithSocial,
|
||||
bindWithSocial,
|
||||
signInWithUsernameAndPassword,
|
||||
getUser,
|
||||
} from '@/api';
|
||||
import MockClient from '@/client';
|
||||
import { setUpConnector, createUserByAdmin } from '@/helpers';
|
||||
import { generateUsername, generatePassword } from '@/utils';
|
||||
|
||||
const state = 'foo_state';
|
||||
const redirectUri = 'http://foo.dev/callback';
|
||||
const code = 'auth_code_foo';
|
||||
|
||||
describe('social sign-in and register', () => {
|
||||
const socialUserId = crypto.randomUUID();
|
||||
|
||||
beforeAll(async () => {
|
||||
await setUpConnector(mockSocialConnectorId, mockSocialConnectorConfig);
|
||||
});
|
||||
|
||||
it('register with social', async () => {
|
||||
const client = new MockClient();
|
||||
|
||||
await client.initSession();
|
||||
assert(client.interactionCookie, new Error('Session not found'));
|
||||
|
||||
await expect(
|
||||
signInWithSocial(
|
||||
{ state, connectorId: mockSocialConnectorId, redirectUri },
|
||||
client.interactionCookie
|
||||
)
|
||||
).resolves.toBeTruthy();
|
||||
|
||||
const response = await getAuthWithSocial(
|
||||
{
|
||||
connectorId: mockSocialConnectorId,
|
||||
data: { state, redirectUri, code, userId: socialUserId },
|
||||
},
|
||||
client.interactionCookie
|
||||
).catch((error: unknown) => error);
|
||||
|
||||
// User with social does not exist
|
||||
expect(response instanceof HTTPError && response.response.statusCode === 422).toBe(true);
|
||||
|
||||
// Register with social
|
||||
const { redirectTo } = await registerWithSocial(
|
||||
mockSocialConnectorId,
|
||||
client.interactionCookie
|
||||
);
|
||||
|
||||
await client.processSession(redirectTo);
|
||||
|
||||
expect(client.isAuthenticated).toBeTruthy();
|
||||
});
|
||||
|
||||
/*
|
||||
* Note: As currently we can not prepare a social identities through admin api.
|
||||
* The sign-in test case MUST run concurrently after the register test case
|
||||
*/
|
||||
it('Sign-In with social', async () => {
|
||||
const client = new MockClient();
|
||||
|
||||
await client.initSession();
|
||||
assert(client.interactionCookie, new Error('Session not found'));
|
||||
|
||||
await expect(
|
||||
signInWithSocial(
|
||||
{ state, connectorId: mockSocialConnectorId, redirectUri },
|
||||
client.interactionCookie
|
||||
)
|
||||
).resolves.toBeTruthy();
|
||||
|
||||
const { redirectTo } = await getAuthWithSocial(
|
||||
{
|
||||
connectorId: mockSocialConnectorId,
|
||||
data: { state, redirectUri, code, userId: socialUserId },
|
||||
},
|
||||
client.interactionCookie
|
||||
);
|
||||
|
||||
await client.processSession(redirectTo);
|
||||
|
||||
expect(client.isAuthenticated).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('social bind account', () => {
|
||||
const username = generateUsername();
|
||||
const password = generatePassword();
|
||||
|
||||
beforeAll(async () => {
|
||||
await createUserByAdmin(username, password);
|
||||
});
|
||||
|
||||
it('bind new social account', async () => {
|
||||
const client = new MockClient();
|
||||
|
||||
await client.initSession();
|
||||
assert(client.interactionCookie, new Error('Session not found'));
|
||||
|
||||
await expect(
|
||||
signInWithSocial(
|
||||
{ state, connectorId: mockSocialConnectorId, redirectUri },
|
||||
client.interactionCookie
|
||||
)
|
||||
).resolves.toBeTruthy();
|
||||
|
||||
const response = await getAuthWithSocial(
|
||||
{ connectorId: mockSocialConnectorId, data: { state, redirectUri, code } },
|
||||
client.interactionCookie
|
||||
).catch((error: unknown) => error);
|
||||
|
||||
// User with social does not exist
|
||||
expect(response instanceof HTTPError && response.response.statusCode === 422).toBe(true);
|
||||
|
||||
const { redirectTo } = await signInWithUsernameAndPassword(
|
||||
username,
|
||||
password,
|
||||
client.interactionCookie
|
||||
);
|
||||
|
||||
await expect(
|
||||
bindWithSocial(mockSocialConnectorId, client.interactionCookie)
|
||||
).resolves.not.toThrow();
|
||||
|
||||
await client.processSession(redirectTo);
|
||||
|
||||
// User should bind with social identities
|
||||
const { sub } = client.getIdTokenClaims();
|
||||
const user = await getUser(sub);
|
||||
|
||||
expect(user.identities).toHaveProperty(mockSocialConnectorTarget);
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue