import { ConnectorError, ConnectorErrorCodes, GetConnectorConfig } from '@logto/connector-types'; import nock from 'nock'; import { AlipayConnector } from '.'; import { alipayEndpoint, authorizationEndpoint } from './constant'; import { mockedAlipayConfig, mockedAlipayConfigWithValidPrivateKey } from './mock'; import { AlipayConfig } from './types'; const getConnectorConfig = jest.fn() as GetConnectorConfig; const alipayMethods = new AlipayConnector(getConnectorConfig); describe('validateConfig', () => { afterEach(() => { jest.clearAllMocks(); }); it('should pass on valid config', async () => { await expect( alipayMethods.validateConfig({ appId: 'appId', privateKey: 'privateKey', signType: 'RSA' }) ).resolves.not.toThrow(); }); it('should throw on empty config', async () => { await expect(alipayMethods.validateConfig({})).rejects.toThrowError(); }); it('should throw when missing required properties', async () => { await expect(alipayMethods.validateConfig({ appId: 'appId' })).rejects.toThrowError(); }); }); describe('getAuthorizationUri', () => { afterEach(() => { jest.clearAllMocks(); }); it('should get a valid uri by redirectUri and state', async () => { jest.spyOn(alipayMethods, 'getConfig').mockResolvedValueOnce(mockedAlipayConfig); const authorizationUri = await alipayMethods.getAuthorizationUri( 'http://localhost:3001/callback', 'some_state' ); expect(authorizationUri).toEqual( `${authorizationEndpoint}?app_id=2021000000000000&redirect_uri=http%3A%2F%2Flocalhost%3A3001%2Fcallback&scope=auth_user&state=some_state` ); }); }); describe('getAccessToken', () => { afterEach(() => { nock.cleanAll(); jest.clearAllMocks(); }); const alipayEndpointUrl = new URL(alipayEndpoint); it('should get an accessToken by exchanging with code', async () => { jest .spyOn(alipayMethods, 'getConfig') .mockResolvedValueOnce(mockedAlipayConfigWithValidPrivateKey); nock(alipayEndpointUrl.origin) .post(alipayEndpointUrl.pathname) .query(true) .reply(200, { alipay_system_oauth_token_response: { user_id: '2088000000000000', access_token: 'access_token', expires_in: '3600', refresh_token: 'refresh_token', re_expires_in: '7200', // Expiring time of refresh token, in seconds }, sign: '', }); const response = await alipayMethods.getAccessToken('code'); const { accessToken } = response; expect(accessToken).toEqual('access_token'); }); it('should throw when accessToken is empty', async () => { jest .spyOn(alipayMethods, 'getConfig') .mockResolvedValueOnce(mockedAlipayConfigWithValidPrivateKey); nock(alipayEndpointUrl.origin) .post(alipayEndpointUrl.pathname) .query(true) .reply(200, { alipay_system_oauth_token_response: { user_id: '2088000000000000', access_token: undefined, expires_in: '3600', refresh_token: 'refresh_token', re_expires_in: '7200', // Expiring time of refresh token, in seconds }, sign: '', }); await expect(alipayMethods.getAccessToken('code')).rejects.toMatchError( new ConnectorError(ConnectorErrorCodes.SocialAuthCodeInvalid) ); }); it('should fail with wrong code', async () => { jest .spyOn(alipayMethods, 'getConfig') .mockResolvedValueOnce(mockedAlipayConfigWithValidPrivateKey); nock(alipayEndpointUrl.origin) .post(alipayEndpointUrl.pathname) .query(true) .reply(200, { error_response: { code: '20001', msg: 'Invalid code', sub_code: 'isv.code-invalid ', }, sign: '', }); await expect(alipayMethods.getAccessToken('wrong_code')).rejects.toMatchError( new ConnectorError(ConnectorErrorCodes.SocialAuthCodeInvalid, 'Invalid code') ); }); }); describe('getUserInfo', () => { afterEach(() => { nock.cleanAll(); jest.clearAllMocks(); }); const alipayEndpointUrl = new URL(alipayEndpoint); it('should get userInfo with accessToken', async () => { jest .spyOn(alipayMethods, 'getConfig') .mockResolvedValueOnce(mockedAlipayConfigWithValidPrivateKey); nock(alipayEndpointUrl.origin) .post(alipayEndpointUrl.pathname) .query(true) .reply(200, { alipay_user_info_share_response: { code: '10000', msg: 'Success', user_id: '2088000000000000', nick_name: 'PlayboyEric', avatar: 'https://www.alipay.com/xxx.jpg', }, sign: '', }); const { id, name, avatar } = await alipayMethods.getUserInfo({ accessToken: 'access_token' }); expect(id).toEqual('2088000000000000'); expect(name).toEqual('PlayboyEric'); expect(avatar).toEqual('https://www.alipay.com/xxx.jpg'); }); it('should throw with wrong accessToken', async () => { jest .spyOn(alipayMethods, 'getConfig') .mockResolvedValueOnce(mockedAlipayConfigWithValidPrivateKey); nock(alipayEndpointUrl.origin) .post(alipayEndpointUrl.pathname) .query(true) .reply(200, { alipay_user_info_share_response: { code: '20001', msg: 'Invalid auth token', sub_code: 'aop.invalid-auth-token', }, sign: '', }); await expect( alipayMethods.getUserInfo({ accessToken: 'wrong_access_token' }) ).rejects.toMatchError( new ConnectorError(ConnectorErrorCodes.SocialAccessTokenInvalid, 'Invalid auth token') ); }); it('should throw General error with other response error codes', async () => { jest .spyOn(alipayMethods, 'getConfig') .mockResolvedValueOnce(mockedAlipayConfigWithValidPrivateKey); nock(alipayEndpointUrl.origin) .post(alipayEndpointUrl.pathname) .query(true) .reply(200, { alipay_user_info_share_response: { code: '40002', msg: 'Invalid parameter', sub_code: 'isv.invalid-parameter', }, sign: '', }); await expect( alipayMethods.getUserInfo({ accessToken: 'wrong_access_token' }) ).rejects.toMatchError(new ConnectorError(ConnectorErrorCodes.General)); }); it('should throw with right accessToken but empty userInfo', async () => { jest .spyOn(alipayMethods, 'getConfig') .mockResolvedValueOnce(mockedAlipayConfigWithValidPrivateKey); nock(alipayEndpointUrl.origin) .post(alipayEndpointUrl.pathname) .query(true) .reply(200, { alipay_user_info_share_response: { code: '10000', msg: 'Success', user_id: undefined, nick_name: 'PlayboyEric', avatar: 'https://www.alipay.com/xxx.jpg', }, sign: '', }); await expect(alipayMethods.getUserInfo({ accessToken: 'access_token' })).rejects.toMatchError( new ConnectorError(ConnectorErrorCodes.InvalidResponse) ); }); it('should throw with other request errors', async () => { jest .spyOn(alipayMethods, 'getConfig') .mockResolvedValueOnce(mockedAlipayConfigWithValidPrivateKey); nock(alipayEndpointUrl.origin).post(alipayEndpointUrl.pathname).query(true).reply(500); await expect(alipayMethods.getUserInfo({ accessToken: 'access_token' })).rejects.toThrow(); }); });