2022-04-28 02:55:49 -05:00
|
|
|
import { ConnectorError, ConnectorErrorCodes, GetConnectorConfig } from '@logto/connector-types';
|
|
|
|
import nock from 'nock';
|
|
|
|
|
2022-05-24 00:54:37 -05:00
|
|
|
import WeChatNativeConnector from '.';
|
2022-05-13 01:22:37 -05:00
|
|
|
import { accessTokenEndpoint, authorizationEndpoint, userInfoEndpoint } from './constant';
|
2022-04-28 02:55:49 -05:00
|
|
|
import { mockedConfig } from './mock';
|
2022-05-13 01:22:37 -05:00
|
|
|
import { WeChatNativeConfig } from './types';
|
2022-04-28 02:55:49 -05:00
|
|
|
|
|
|
|
const getConnectorConfig = jest.fn() as GetConnectorConfig<WeChatNativeConfig>;
|
|
|
|
|
|
|
|
const weChatNativeMethods = new WeChatNativeConnector(getConnectorConfig);
|
|
|
|
|
|
|
|
beforeAll(() => {
|
|
|
|
jest.spyOn(weChatNativeMethods, 'getConfig').mockResolvedValue(mockedConfig);
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('getAuthorizationUri', () => {
|
|
|
|
afterEach(() => {
|
|
|
|
jest.clearAllMocks();
|
|
|
|
});
|
|
|
|
|
2022-05-19 21:22:12 -05:00
|
|
|
it('should get a valid uri', async () => {
|
2022-05-29 00:59:00 -05:00
|
|
|
const authorizationUri = await weChatNativeMethods.getAuthorizationUri({
|
|
|
|
state: 'dummy-state',
|
|
|
|
redirectUri: 'dummy-redirect-uri',
|
|
|
|
});
|
2022-05-25 01:23:53 -05:00
|
|
|
expect(authorizationUri).toEqual(
|
|
|
|
`${authorizationEndpoint}?app_id=%3Capp-id%3E&state=dummy-state`
|
2022-04-28 02:55:49 -05:00
|
|
|
);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
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 weChatNativeMethods.getAccessToken('code');
|
|
|
|
expect(accessToken).toEqual('access_token');
|
|
|
|
expect(openid).toEqual('openid');
|
|
|
|
});
|
|
|
|
|
2022-06-06 23:06:37 -05:00
|
|
|
it('throws SocialAuthCodeInvalid error if errcode is 40029', async () => {
|
2022-04-28 02:55:49 -05:00
|
|
|
nock(accessTokenEndpointUrl.origin)
|
|
|
|
.get(accessTokenEndpointUrl.pathname)
|
|
|
|
.query(parameters)
|
2022-06-06 23:06:37 -05:00
|
|
|
.reply(200, { errcode: 40_029, errmsg: 'invalid code' });
|
2022-04-28 02:55:49 -05:00
|
|
|
await expect(weChatNativeMethods.getAccessToken('code')).rejects.toMatchError(
|
2022-06-09 21:41:45 -05:00
|
|
|
new ConnectorError(ConnectorErrorCodes.SocialAuthCodeInvalid, 'invalid code')
|
2022-04-28 02:55:49 -05:00
|
|
|
);
|
|
|
|
});
|
2022-06-06 23:06:37 -05:00
|
|
|
|
2022-06-09 21:41:45 -05:00
|
|
|
it('throws SocialAuthCodeInvalid error if errcode is 40163', async () => {
|
2022-06-06 23:06:37 -05:00
|
|
|
nock(accessTokenEndpointUrl.origin)
|
|
|
|
.get(accessTokenEndpointUrl.pathname)
|
|
|
|
.query(true)
|
2022-06-09 21:41:45 -05:00
|
|
|
.reply(200, { errcode: 40_163, errmsg: 'code been used' });
|
|
|
|
await expect(weChatNativeMethods.getAccessToken('code')).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' });
|
2022-06-06 23:06:37 -05:00
|
|
|
await expect(weChatNativeMethods.getAccessToken('wrong_code')).rejects.toMatchError(
|
2022-06-09 21:41:45 -05:00
|
|
|
new ConnectorError(ConnectorErrorCodes.General, 'system error')
|
2022-06-06 23:06:37 -05:00
|
|
|
);
|
|
|
|
});
|
2022-04-28 02:55:49 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
describe('validateConfig', () => {
|
|
|
|
it('should pass on valid config', async () => {
|
|
|
|
await expect(
|
|
|
|
weChatNativeMethods.validateConfig({ appId: 'appId', appSecret: 'appSecret' })
|
|
|
|
).resolves.not.toThrow();
|
|
|
|
});
|
|
|
|
it('should throw on empty config', async () => {
|
|
|
|
await expect(weChatNativeMethods.validateConfig({})).rejects.toThrowError();
|
|
|
|
});
|
|
|
|
it('should throw when missing appSecret', async () => {
|
|
|
|
await expect(weChatNativeMethods.validateConfig({ appId: 'appId' })).rejects.toThrowError();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2022-05-29 00:59:00 -05:00
|
|
|
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',
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2022-04-28 02:55:49 -05:00
|
|
|
describe('getUserInfo', () => {
|
2022-05-29 00:59:00 -05:00
|
|
|
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',
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2022-04-28 02:55:49 -05:00
|
|
|
afterEach(() => {
|
|
|
|
nock.cleanAll();
|
|
|
|
jest.clearAllMocks();
|
|
|
|
});
|
|
|
|
|
|
|
|
const userInfoEndpointUrl = new URL(userInfoEndpoint);
|
2022-05-29 00:59:00 -05:00
|
|
|
const parameters = new URLSearchParams({ access_token: 'access_token', openid: 'openid' });
|
2022-04-28 02:55:49 -05:00
|
|
|
|
|
|
|
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',
|
|
|
|
});
|
2022-05-29 00:59:00 -05:00
|
|
|
const socialUserInfo = await weChatNativeMethods.getUserInfo({ code: 'code' });
|
2022-04-28 02:55:49 -05:00
|
|
|
expect(socialUserInfo).toMatchObject({
|
|
|
|
id: 'this_is_an_arbitrary_wechat_union_id',
|
|
|
|
avatar: 'https://github.com/images/error/octocat_happy.gif',
|
|
|
|
name: 'wechat bot',
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2022-05-27 08:21:23 -05:00
|
|
|
it('throws error if `openid` is missing', async () => {
|
2022-05-29 00:59:00 -05:00
|
|
|
nockNoOpenIdAccessTokenResponse();
|
2022-06-06 23:06:37 -05:00
|
|
|
nock(userInfoEndpointUrl.origin)
|
|
|
|
.get(userInfoEndpointUrl.pathname)
|
|
|
|
.query(parameters)
|
|
|
|
.reply(200, {
|
|
|
|
errcode: 41_009,
|
|
|
|
errmsg: 'missing openid',
|
|
|
|
});
|
2022-05-29 00:59:00 -05:00
|
|
|
await expect(weChatNativeMethods.getUserInfo({ code: 'code' })).rejects.toMatchError(
|
2022-06-06 23:06:37 -05:00
|
|
|
new Error('missing openid')
|
2022-05-29 00:59:00 -05:00
|
|
|
);
|
2022-05-27 08:21:23 -05:00
|
|
|
});
|
|
|
|
|
2022-04-28 02:55:49 -05:00
|
|
|
it('throws SocialAccessTokenInvalid error if errcode is 40001', async () => {
|
|
|
|
nock(userInfoEndpointUrl.origin)
|
|
|
|
.get(userInfoEndpointUrl.pathname)
|
|
|
|
.query(parameters)
|
2022-06-06 23:06:37 -05:00
|
|
|
.reply(200, { errcode: 40_001, errmsg: 'invalid credential' });
|
2022-05-29 00:59:00 -05:00
|
|
|
await expect(weChatNativeMethods.getUserInfo({ code: 'code' })).rejects.toMatchError(
|
2022-06-09 21:41:45 -05:00
|
|
|
new ConnectorError(ConnectorErrorCodes.SocialAccessTokenInvalid, 'invalid credential')
|
2022-05-29 00:59:00 -05:00
|
|
|
);
|
2022-04-28 02:55:49 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
it('throws unrecognized error', async () => {
|
|
|
|
nock(userInfoEndpointUrl.origin).get(userInfoEndpointUrl.pathname).query(parameters).reply(500);
|
2022-05-29 00:59:00 -05:00
|
|
|
await expect(weChatNativeMethods.getUserInfo({ code: 'code' })).rejects.toThrow();
|
2022-04-28 02:55:49 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
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' });
|
2022-05-29 00:59:00 -05:00
|
|
|
await expect(weChatNativeMethods.getUserInfo({ code: 'code' })).rejects.toMatchError(
|
|
|
|
new Error('invalid openid')
|
|
|
|
);
|
2022-04-28 02:55:49 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
it('throws SocialAccessTokenInvalid error if response code is 401', async () => {
|
2022-05-29 00:59:00 -05:00
|
|
|
nock(userInfoEndpointUrl.origin).get(userInfoEndpointUrl.pathname).query(parameters).reply(401);
|
|
|
|
await expect(weChatNativeMethods.getUserInfo({ code: 'code' })).rejects.toMatchError(
|
|
|
|
new ConnectorError(ConnectorErrorCodes.SocialAccessTokenInvalid)
|
|
|
|
);
|
2022-04-28 02:55:49 -05:00
|
|
|
});
|
|
|
|
});
|