mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
185 lines
6.4 KiB
TypeScript
185 lines
6.4 KiB
TypeScript
import nock from 'nock';
|
|
|
|
import { ConnectorError, ConnectorErrorCodes } from '@logto/connector-kit';
|
|
|
|
import { accessTokenEndpoint, codeEndpoint, userInfoEndpoint } from './constant.js';
|
|
import createConnector, { buildAuthorizationUri, getAccessToken } from './index.js';
|
|
import { mockedFeishuConfig } from './mock.js';
|
|
|
|
const getConfig = vi.fn().mockResolvedValue(mockedFeishuConfig);
|
|
|
|
describe('getAuthorizationUri', () => {
|
|
afterEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it('should build authorization uri', function () {
|
|
const url = buildAuthorizationUri('123', 'http://localhost:3000', '123');
|
|
expect(url).toBe(
|
|
`${codeEndpoint}?client_id=123&redirect_uri=http%3A%2F%2Flocalhost%3A3000&response_type=code&state=123`
|
|
);
|
|
});
|
|
|
|
it('should get a valid uri by redirectUri and state', async () => {
|
|
const connector = await createConnector({ getConfig });
|
|
const authorizationUri = await connector.getAuthorizationUri(
|
|
{
|
|
state: 'some_state',
|
|
redirectUri: 'http://localhost:3001/callback',
|
|
connectorId: 'some_connector_id',
|
|
connectorFactoryId: 'some_connector_factory_id',
|
|
jti: 'some_jti',
|
|
headers: {},
|
|
},
|
|
vi.fn()
|
|
);
|
|
expect(authorizationUri).toEqual(
|
|
`${codeEndpoint}?client_id=1112233&redirect_uri=http%3A%2F%2Flocalhost%3A3001%2Fcallback&response_type=code&state=some_state`
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('getAccessToken', () => {
|
|
afterEach(() => {
|
|
nock.cleanAll();
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
const accessTokenUrl = new URL(accessTokenEndpoint);
|
|
|
|
it('should get an accessToken by exchanging with code', async () => {
|
|
nock(accessTokenUrl.origin).post(accessTokenUrl.pathname).query(true).reply(200, {
|
|
access_token: 'some_access_token',
|
|
token_type: 'Bearer',
|
|
expires_in: 7200,
|
|
refresh_token: 'some_refresh_token',
|
|
refresh_expires_in: 7200,
|
|
});
|
|
const response = await getAccessToken('code', '123', '123', 'http://localhost:3000');
|
|
const { accessToken } = response;
|
|
expect(accessToken).toEqual('some_access_token');
|
|
});
|
|
|
|
it('should throw when accessToken is empty', async () => {
|
|
nock(accessTokenUrl.origin).post(accessTokenUrl.pathname).query(true).reply(200, {
|
|
access_token: '',
|
|
token_type: 'Bearer',
|
|
expires_in: 7200,
|
|
refresh_token: 'some_refresh_token',
|
|
refresh_expires_in: 7200,
|
|
});
|
|
|
|
await expect(
|
|
getAccessToken('code', '123', '123', 'http://localhost:3000')
|
|
).rejects.toStrictEqual(
|
|
new ConnectorError(ConnectorErrorCodes.SocialAuthCodeInvalid, 'access_token is empty')
|
|
);
|
|
});
|
|
|
|
it('should fail with wrong code', async () => {
|
|
nock(accessTokenUrl.origin).post(accessTokenUrl.pathname).query(true).reply(400, {
|
|
error: 'invalid_grant',
|
|
error_description: 'invalid code',
|
|
});
|
|
|
|
await expect(
|
|
getAccessToken('code', '123', '123', 'http://localhost:3000')
|
|
).rejects.toStrictEqual(
|
|
new ConnectorError(ConnectorErrorCodes.SocialAuthCodeInvalid, 'invalid code')
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('getUserInfo', () => {
|
|
afterEach(() => {
|
|
nock.cleanAll();
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
beforeEach(() => {
|
|
nock(accessTokenUrl.origin).post(accessTokenUrl.pathname).query(true).reply(200, {
|
|
access_token: 'some_access_token',
|
|
token_type: 'Bearer',
|
|
expires_in: 7200,
|
|
refresh_token: 'some_refresh_token',
|
|
refresh_expires_in: 7200,
|
|
});
|
|
});
|
|
|
|
const userInfoUrl = new URL(userInfoEndpoint);
|
|
const accessTokenUrl = new URL(accessTokenEndpoint);
|
|
|
|
it('should get userInfo with accessToken', async () => {
|
|
const jsonResponse = Object.freeze({
|
|
sub: 'ou_caecc734c2e3328a62489fe0648c4b98779515d3',
|
|
name: '李雷',
|
|
picture: 'https://www.feishu.cn/avatar',
|
|
open_id: 'ou_caecc734c2e3328a62489fe0648c4b98779515d3',
|
|
union_id: 'on_d89jhsdhjsajkda7828enjdj328ydhhw3u43yjhdj',
|
|
en_name: 'Lilei',
|
|
tenant_key: '736588c92lxf175d',
|
|
avatar_url: 'www.feishu.cn/avatar/icon',
|
|
avatar_thumb: 'www.feishu.cn/avatar/icon_thumb',
|
|
avatar_middle: 'www.feishu.cn/avatar/icon_middle',
|
|
avatar_big: 'www.feishu.cn/avatar/icon_big',
|
|
email: 'zhangsan@feishu.cn',
|
|
user_id: '5d9bdxxx',
|
|
employee_no: '111222333',
|
|
mobile: '+86130xxxx0000',
|
|
});
|
|
nock(userInfoUrl.origin).get(userInfoUrl.pathname).query(true).once().reply(200, jsonResponse);
|
|
|
|
const connector = await createConnector({ getConfig });
|
|
const { id, name, avatar, rawData } = await connector.getUserInfo(
|
|
{
|
|
code: 'code',
|
|
redirectUri: 'http://localhost:3000',
|
|
},
|
|
vi.fn()
|
|
);
|
|
expect(id).toEqual('ou_caecc734c2e3328a62489fe0648c4b98779515d3');
|
|
expect(name).toEqual('李雷');
|
|
expect(avatar).toEqual('www.feishu.cn/avatar/icon');
|
|
expect(rawData).toEqual(jsonResponse);
|
|
});
|
|
|
|
it('throw General error if code not provided in input', async () => {
|
|
const connector = await createConnector({ getConfig });
|
|
await expect(connector.getUserInfo({}, vi.fn())).rejects.toStrictEqual(
|
|
new ConnectorError(ConnectorErrorCodes.InvalidResponse, '{}')
|
|
);
|
|
});
|
|
|
|
it('should throw SocialAccessTokenInvalid with code invalid_token', async () => {
|
|
nock(userInfoUrl.origin).get(userInfoUrl.pathname).query(true).reply(400, {
|
|
error: 'invalid_token',
|
|
error_description: 'invalid access token',
|
|
});
|
|
const connector = await createConnector({ getConfig });
|
|
await expect(
|
|
connector.getUserInfo({ code: 'error_code', redirectUri: 'http://localhost:3000' }, vi.fn())
|
|
).rejects.toStrictEqual(
|
|
new ConnectorError(ConnectorErrorCodes.SocialAccessTokenInvalid, 'invalid access token')
|
|
);
|
|
});
|
|
|
|
it('should throw with right accessToken but empty userInfo', async () => {
|
|
nock(userInfoUrl.origin).get(userInfoUrl.pathname).query(true).reply(200, {
|
|
sub: '',
|
|
});
|
|
const connector = await createConnector({ getConfig });
|
|
await expect(
|
|
connector.getUserInfo({ code: 'code', redirectUri: 'http://localhost:3000' }, vi.fn())
|
|
).rejects.toStrictEqual(
|
|
new ConnectorError(ConnectorErrorCodes.InvalidResponse, 'invalid user response')
|
|
);
|
|
});
|
|
|
|
it('should throw with other request errors', async () => {
|
|
nock(userInfoUrl.origin).get(userInfoUrl.pathname).query(true).reply(500);
|
|
const connector = await createConnector({ getConfig });
|
|
await expect(
|
|
connector.getUserInfo({ code: 'code', redirectUri: 'http://localhost:3000' }, vi.fn())
|
|
).rejects.toThrow();
|
|
});
|
|
});
|