0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00
logto/packages/connectors/connector-feishu-web/src/index.test.ts

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();
});
});