0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-01-27 21:39:16 -05:00
logto/packages/connectors/connector-wechat-native/src/index.test.ts

219 lines
7.4 KiB
TypeScript
Raw Normal View History

import { ConnectorError, ConnectorErrorCodes } from '@logto/connector-kit';
import nock from 'nock';
import { accessTokenEndpoint, authorizationEndpoint, userInfoEndpoint } from './constant.js';
import createConnector, { getAccessToken } from './index.js';
import { mockedConfig } from './mock.js';
const { jest } = import.meta;
const getConfig = jest.fn().mockResolvedValue(mockedConfig);
describe('getAuthorizationUri', () => {
afterEach(() => {
jest.clearAllMocks();
});
it('should get a valid uri', async () => {
const connector = await createConnector({ getConfig });
const authorizationUri = await connector.getAuthorizationUri(
{
state: 'dummy-state',
redirectUri: 'dummy-redirect-uri',
connectorId: 'dummy-connector-id',
connectorFactoryId: 'dummy-connector-factory-id',
jti: 'dummy-jti',
headers: {},
},
jest.fn()
);
expect(authorizationUri).toEqual(
`${authorizationEndpoint}?app_id=%3Capp-id%3E&state=dummy-state`
);
});
});
describe('getAccessToken', () => {
afterEach(() => {
nock.cleanAll();
jest.clearAllMocks();
});
const accessTokenEndpointUrl = new URL(accessTokenEndpoint);
const parameters = new URLSearchParams({
appid: '<app-id>',
secret: '<app-secret>',
code: 'code',
grant_type: 'authorization_code',
});
it('should get an accessToken by exchanging with code', async () => {
nock(accessTokenEndpointUrl.origin)
.get(accessTokenEndpointUrl.pathname)
.query(parameters)
.reply(200, {
access_token: 'access_token',
openid: 'openid',
});
const { accessToken, openid } = await getAccessToken('code', mockedConfig);
expect(accessToken).toEqual('access_token');
expect(openid).toEqual('openid');
});
it('throws SocialAuthCodeInvalid error if errcode is 40029', async () => {
nock(accessTokenEndpointUrl.origin)
.get(accessTokenEndpointUrl.pathname)
.query(parameters)
.reply(200, { errcode: 40_029, errmsg: 'invalid code' });
await expect(getAccessToken('code', mockedConfig)).rejects.toMatchError(
new ConnectorError(ConnectorErrorCodes.SocialAuthCodeInvalid, 'invalid code')
);
});
it('throws SocialAuthCodeInvalid error if errcode is 40163', async () => {
nock(accessTokenEndpointUrl.origin)
.get(accessTokenEndpointUrl.pathname)
.query(true)
.reply(200, { errcode: 40_163, errmsg: 'code been used' });
await expect(getAccessToken('code', mockedConfig)).rejects.toMatchError(
new ConnectorError(ConnectorErrorCodes.SocialAuthCodeInvalid, 'code been used')
);
});
it('throws error with message otherwise', async () => {
nock(accessTokenEndpointUrl.origin)
.get(accessTokenEndpointUrl.pathname)
.query(true)
.reply(200, { errcode: -1, errmsg: 'system error' });
await expect(getAccessToken('wrong_code', mockedConfig)).rejects.toMatchError(
new ConnectorError(ConnectorErrorCodes.General, {
errorDescription: 'system error',
errcode: -1,
})
);
});
});
const nockNoOpenIdAccessTokenResponse = () => {
const accessTokenEndpointUrl = new URL(accessTokenEndpoint);
const parameters = new URLSearchParams({
appid: '<app-id>',
secret: '<app-secret>',
code: 'code',
grant_type: 'authorization_code',
});
nock(accessTokenEndpointUrl.origin)
.get(accessTokenEndpointUrl.pathname)
.query(parameters)
.reply(200, {
access_token: 'access_token',
});
};
describe('getUserInfo', () => {
beforeEach(() => {
const accessTokenEndpointUrl = new URL(accessTokenEndpoint);
const parameters = new URLSearchParams({
appid: '<app-id>',
secret: '<app-secret>',
code: 'code',
grant_type: 'authorization_code',
});
nock(accessTokenEndpointUrl.origin)
.get(accessTokenEndpointUrl.pathname)
.query(parameters)
.reply(200, {
access_token: 'access_token',
openid: 'openid',
});
});
afterEach(() => {
nock.cleanAll();
jest.clearAllMocks();
});
const userInfoEndpointUrl = new URL(userInfoEndpoint);
const parameters = new URLSearchParams({ access_token: 'access_token', openid: 'openid' });
it('should get valid SocialUserInfo', async () => {
nock(userInfoEndpointUrl.origin).get(userInfoEndpointUrl.pathname).query(parameters).reply(0, {
unionid: 'this_is_an_arbitrary_wechat_union_id',
headimgurl: 'https://github.com/images/error/octocat_happy.gif',
nickname: 'wechat bot',
});
const connector = await createConnector({ getConfig });
const socialUserInfo = await connector.getUserInfo({ code: 'code' }, jest.fn());
expect(socialUserInfo).toMatchObject({
id: 'this_is_an_arbitrary_wechat_union_id',
avatar: 'https://github.com/images/error/octocat_happy.gif',
name: 'wechat bot',
});
});
it('throws General error if code not provided in input', async () => {
const connector = await createConnector({ getConfig });
await expect(connector.getUserInfo({}, jest.fn())).rejects.toMatchError(
new ConnectorError(ConnectorErrorCodes.General, '{}')
);
});
it('throws error if `openid` is missing', async () => {
nockNoOpenIdAccessTokenResponse();
nock(userInfoEndpointUrl.origin)
.get(userInfoEndpointUrl.pathname)
.query(parameters)
.reply(200, {
errcode: 41_009,
errmsg: 'missing openid',
});
const connector = await createConnector({ getConfig });
await expect(connector.getUserInfo({ code: 'code' }, jest.fn())).rejects.toMatchError(
new ConnectorError(ConnectorErrorCodes.General, {
errorDescription: 'missing openid',
errcode: 41_009,
})
);
});
it('throws SocialAccessTokenInvalid error if errcode is 40001', async () => {
nock(userInfoEndpointUrl.origin)
.get(userInfoEndpointUrl.pathname)
.query(parameters)
.reply(200, { errcode: 40_001, errmsg: 'invalid credential' });
const connector = await createConnector({ getConfig });
await expect(connector.getUserInfo({ code: 'code' }, jest.fn())).rejects.toMatchError(
new ConnectorError(ConnectorErrorCodes.SocialAccessTokenInvalid, 'invalid credential')
);
});
it('throws unrecognized error', async () => {
nock(userInfoEndpointUrl.origin).get(userInfoEndpointUrl.pathname).query(parameters).reply(500);
const connector = await createConnector({ getConfig });
await expect(connector.getUserInfo({ code: 'code' }, jest.fn())).rejects.toThrow();
});
it('throws Error if request failed and errcode is not 40001', async () => {
nock(userInfoEndpointUrl.origin)
.get(userInfoEndpointUrl.pathname)
.query(parameters)
.reply(200, { errcode: 40_003, errmsg: 'invalid openid' });
const connector = await createConnector({ getConfig });
await expect(connector.getUserInfo({ code: 'code' }, jest.fn())).rejects.toMatchError(
new ConnectorError(ConnectorErrorCodes.General, {
errorDescription: 'invalid openid',
errcode: 40_003,
})
);
});
it('throws SocialAccessTokenInvalid error if response code is 401', async () => {
nock(userInfoEndpointUrl.origin).get(userInfoEndpointUrl.pathname).query(parameters).reply(401);
const connector = await createConnector({ getConfig });
await expect(connector.getUserInfo({ code: 'code' }, jest.fn())).rejects.toMatchError(
new ConnectorError(ConnectorErrorCodes.SocialAccessTokenInvalid)
);
});
});