mirror of
https://github.com/logto-io/logto.git
synced 2025-04-07 23:01:25 -05:00
test(core): add UTs for connector route (#357)
This commit is contained in:
parent
012db5e07d
commit
67aad46a2c
2 changed files with 613 additions and 0 deletions
packages/core/src
455
packages/core/src/routes/connector.test.ts
Normal file
455
packages/core/src/routes/connector.test.ts
Normal file
|
@ -0,0 +1,455 @@
|
|||
import { Connector, ConnectorType } from '@logto/schemas';
|
||||
|
||||
import {
|
||||
ConnectorError,
|
||||
ConnectorErrorCodes,
|
||||
ConnectorMetadata,
|
||||
ValidateConfig,
|
||||
} from '@/connectors/types';
|
||||
import RequestError from '@/errors/RequestError';
|
||||
import { updateConnector } from '@/queries/connector';
|
||||
import assertThat from '@/utils/assert-that';
|
||||
import { mockConnectorList, mockConnectorInstanceList } from '@/utils/mock';
|
||||
import { createRequester } from '@/utils/test-utils';
|
||||
|
||||
import connectorRoutes from './connector';
|
||||
|
||||
type ConnectorInstance = {
|
||||
connector: Connector;
|
||||
metadata: ConnectorMetadata;
|
||||
validateConfig?: ValidateConfig;
|
||||
};
|
||||
|
||||
const findConnectorByIdPlaceHolder = jest.fn() as jest.MockedFunction<
|
||||
(connectorId: string) => Promise<Connector>
|
||||
>;
|
||||
const getConnectorInstanceByIdPlaceHolder = jest.fn() as jest.MockedFunction<
|
||||
(connectorId: string) => Promise<ConnectorInstance>
|
||||
>;
|
||||
const getConnectorInstancesPlaceHolder = jest.fn() as jest.MockedFunction<
|
||||
() => Promise<ConnectorInstance[]>
|
||||
>;
|
||||
|
||||
jest.mock('@/queries/connector', () => ({
|
||||
findConnectorById: async (connectorId: string) => findConnectorByIdPlaceHolder(connectorId),
|
||||
findAllConnectors: jest.fn(),
|
||||
updateConnector: jest.fn(),
|
||||
}));
|
||||
jest.mock('@/connectors', () => ({
|
||||
getConnectorInstanceById: async (connectorId: string) =>
|
||||
getConnectorInstanceByIdPlaceHolder(connectorId),
|
||||
getConnectorInstances: async () => getConnectorInstancesPlaceHolder(),
|
||||
}));
|
||||
|
||||
describe('connector route', () => {
|
||||
const connectorRequest = createRequester({ authedRoutes: connectorRoutes });
|
||||
|
||||
describe('GET /connectors', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('throws if more than one email connector is enabled', async () => {
|
||||
getConnectorInstancesPlaceHolder.mockResolvedValue(mockConnectorInstanceList);
|
||||
const response = await connectorRequest.get('/connectors').send({});
|
||||
expect(response).toHaveProperty('statusCode', 400);
|
||||
});
|
||||
|
||||
it('throws if more than one SMS connector is enabled', async () => {
|
||||
getConnectorInstancesPlaceHolder.mockResolvedValue(
|
||||
mockConnectorInstanceList.filter(
|
||||
(connecotrInstance) => connecotrInstance.metadata.type !== ConnectorType.Email
|
||||
)
|
||||
);
|
||||
const response = await connectorRequest.get('/connectors').send({});
|
||||
expect(response).toHaveProperty('statusCode', 400);
|
||||
});
|
||||
|
||||
it('shows all connectors', async () => {
|
||||
getConnectorInstancesPlaceHolder.mockResolvedValue(
|
||||
mockConnectorInstanceList.filter(
|
||||
(connecotrInstance) => connecotrInstance.metadata.type === ConnectorType.Social
|
||||
)
|
||||
);
|
||||
const response = await connectorRequest.get('/connectors').send({});
|
||||
expect(response).toHaveProperty('statusCode', 200);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /connectors/:id', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('throws when connector can not be found by given connectorId (locally)', async () => {
|
||||
getConnectorInstanceByIdPlaceHolder.mockImplementationOnce(async (_id: string) => {
|
||||
const found = mockConnectorInstanceList.find(
|
||||
(connectorInstance) => connectorInstance.metadata.id === 'connector'
|
||||
);
|
||||
assertThat(found, new RequestError({ code: 'entity.not_found', status: 404 }));
|
||||
|
||||
return found;
|
||||
});
|
||||
const response = await connectorRequest.get('/connectors/findConnector').send({});
|
||||
expect(response).toHaveProperty('statusCode', 404);
|
||||
});
|
||||
|
||||
it('throws when connector can not be found by given connectorId (remotely)', async () => {
|
||||
getConnectorInstanceByIdPlaceHolder.mockImplementationOnce(async (id: string) => {
|
||||
const foundConnectorInstance = mockConnectorInstanceList.find(
|
||||
(connectorInstance) => connectorInstance.metadata.id === id
|
||||
);
|
||||
assertThat(
|
||||
foundConnectorInstance,
|
||||
new RequestError({ code: 'entity.not_found', status: 404 })
|
||||
);
|
||||
|
||||
const foundConnector = mockConnectorList.find((connector) => connector.id === 'connector0');
|
||||
assertThat(foundConnector, 'entity.not_found');
|
||||
|
||||
return { foundConnector, ...foundConnectorInstance };
|
||||
});
|
||||
const response = await connectorRequest.get('/connectors/connector_0').send({});
|
||||
expect(response).toHaveProperty('statusCode', 400);
|
||||
});
|
||||
|
||||
it('shows found connector information', async () => {
|
||||
getConnectorInstanceByIdPlaceHolder.mockImplementationOnce(async (id: string) => {
|
||||
const foundConnectorInstance = mockConnectorInstanceList.find(
|
||||
(connectorInstance) => connectorInstance.metadata.id === id
|
||||
);
|
||||
assertThat(
|
||||
foundConnectorInstance,
|
||||
new RequestError({ code: 'entity.not_found', status: 404 })
|
||||
);
|
||||
|
||||
const foundConnector = mockConnectorList.find((connector) => connector.id === id);
|
||||
assertThat(foundConnector, 'entity.not_found');
|
||||
|
||||
return { foundConnector, ...foundConnectorInstance };
|
||||
});
|
||||
const response = await connectorRequest.get('/connectors/connector_0').send({});
|
||||
expect(response).toHaveProperty('statusCode', 200);
|
||||
});
|
||||
});
|
||||
|
||||
describe('PATCH /connectors/:id/enabled', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('throws if connector can not be found (locally)', async () => {
|
||||
getConnectorInstanceByIdPlaceHolder.mockImplementationOnce(async (_id: string) => {
|
||||
const found = mockConnectorInstanceList.find(
|
||||
(connectorInstance) => connectorInstance.metadata.id === 'connector'
|
||||
);
|
||||
assertThat(found, new RequestError({ code: 'entity.not_found', status: 404 }));
|
||||
|
||||
return found;
|
||||
});
|
||||
const response = await connectorRequest
|
||||
.patch('/connectors/findConnector/enabled')
|
||||
.send({ enabled: true });
|
||||
expect(response).toHaveProperty('statusCode', 404);
|
||||
});
|
||||
|
||||
it('throws if connector can not be found (remotely)', async () => {
|
||||
getConnectorInstanceByIdPlaceHolder.mockImplementationOnce(async (id: string) => {
|
||||
const foundConnectorInstance = mockConnectorInstanceList.find(
|
||||
(connectorInstance) => connectorInstance.metadata.id === id
|
||||
);
|
||||
assertThat(
|
||||
foundConnectorInstance,
|
||||
new RequestError({ code: 'entity.not_found', status: 404 })
|
||||
);
|
||||
|
||||
const foundConnector = mockConnectorList.find((connector) => connector.id === 'connector0');
|
||||
assertThat(foundConnector, 'entity.not_found');
|
||||
|
||||
return { foundConnector, ...foundConnectorInstance };
|
||||
});
|
||||
const response = await connectorRequest
|
||||
.patch('/connectors/connector_0/enabled')
|
||||
.send({ enabled: true });
|
||||
expect(response).toHaveProperty('statusCode', 400);
|
||||
});
|
||||
|
||||
it('enables one of the social connectors', async () => {
|
||||
getConnectorInstanceByIdPlaceHolder.mockImplementationOnce(async (_id: string) => {
|
||||
return {
|
||||
connector: {
|
||||
id: 'connector_0',
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_123,
|
||||
},
|
||||
metadata: {
|
||||
id: 'connector_0',
|
||||
type: ConnectorType.Social,
|
||||
name: {},
|
||||
logo: './logo.png',
|
||||
description: {},
|
||||
},
|
||||
};
|
||||
});
|
||||
const response = await connectorRequest
|
||||
.patch('/connectors/connector_0/enabled')
|
||||
.send({ enabled: true });
|
||||
expect(updateConnector).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
where: { id: 'connector_0' },
|
||||
set: { enabled: true },
|
||||
})
|
||||
);
|
||||
expect(response.body).toMatchObject({
|
||||
metadata: {
|
||||
id: 'connector_0',
|
||||
type: ConnectorType.Social,
|
||||
name: {},
|
||||
logo: './logo.png',
|
||||
description: {},
|
||||
},
|
||||
});
|
||||
expect(response).toHaveProperty('statusCode', 200);
|
||||
});
|
||||
|
||||
it('disables one of the social connectors', async () => {
|
||||
getConnectorInstanceByIdPlaceHolder.mockImplementationOnce(async (_id: string) => {
|
||||
return {
|
||||
connector: {
|
||||
id: 'connector_0',
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_123,
|
||||
},
|
||||
metadata: {
|
||||
id: 'connector_0',
|
||||
type: ConnectorType.Social,
|
||||
name: {},
|
||||
logo: './logo.png',
|
||||
description: {},
|
||||
},
|
||||
};
|
||||
});
|
||||
const response = await connectorRequest
|
||||
.patch('/connectors/connector_0/enabled')
|
||||
.send({ enabled: false });
|
||||
expect(updateConnector).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
where: { id: 'connector_0' },
|
||||
set: { enabled: false },
|
||||
})
|
||||
);
|
||||
expect(response.body).toMatchObject({
|
||||
metadata: {
|
||||
id: 'connector_0',
|
||||
type: ConnectorType.Social,
|
||||
name: {},
|
||||
logo: './logo.png',
|
||||
description: {},
|
||||
},
|
||||
});
|
||||
expect(response).toHaveProperty('statusCode', 200);
|
||||
});
|
||||
|
||||
it('enables one of the email/sms connectors', async () => {
|
||||
getConnectorInstancesPlaceHolder.mockResolvedValueOnce(mockConnectorInstanceList);
|
||||
getConnectorInstanceByIdPlaceHolder.mockImplementationOnce(async (_id: string) => {
|
||||
return {
|
||||
connector: {
|
||||
id: 'connector_1',
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_234,
|
||||
},
|
||||
metadata: {
|
||||
id: 'connector_1',
|
||||
type: ConnectorType.SMS,
|
||||
name: {},
|
||||
logo: './logo.png',
|
||||
description: {},
|
||||
},
|
||||
};
|
||||
});
|
||||
const response = await connectorRequest
|
||||
.patch('/connectors/connector_1/enabled')
|
||||
.send({ enabled: true });
|
||||
expect(updateConnector).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.objectContaining({
|
||||
where: { id: 'connector_1' },
|
||||
set: { enabled: false },
|
||||
})
|
||||
);
|
||||
expect(updateConnector).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
expect.objectContaining({
|
||||
where: { id: 'connector_5' },
|
||||
set: { enabled: false },
|
||||
})
|
||||
);
|
||||
expect(updateConnector).toHaveBeenNthCalledWith(
|
||||
3,
|
||||
expect.objectContaining({
|
||||
where: { id: 'connector_1' },
|
||||
set: { enabled: true },
|
||||
})
|
||||
);
|
||||
expect(response.body).toMatchObject({
|
||||
metadata: {
|
||||
id: 'connector_1',
|
||||
type: ConnectorType.SMS,
|
||||
name: {},
|
||||
logo: './logo.png',
|
||||
description: {},
|
||||
},
|
||||
});
|
||||
expect(response).toHaveProperty('statusCode', 200);
|
||||
});
|
||||
|
||||
it('disables one of the email/sms connectors', async () => {
|
||||
getConnectorInstanceByIdPlaceHolder.mockImplementationOnce(async (_id: string) => {
|
||||
return {
|
||||
connector: {
|
||||
id: 'connector_4',
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_567,
|
||||
},
|
||||
metadata: {
|
||||
id: 'connector_4',
|
||||
type: ConnectorType.Email,
|
||||
name: {},
|
||||
logo: './logo.png',
|
||||
description: {},
|
||||
},
|
||||
};
|
||||
});
|
||||
const response = await connectorRequest
|
||||
.patch('/connectors/connector_4/enabled')
|
||||
.send({ enabled: false });
|
||||
expect(updateConnector).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
where: { id: 'connector_4' },
|
||||
set: { enabled: false },
|
||||
})
|
||||
);
|
||||
expect(response.body).toMatchObject({
|
||||
metadata: {
|
||||
id: 'connector_4',
|
||||
type: ConnectorType.Email,
|
||||
name: {},
|
||||
logo: './logo.png',
|
||||
description: {},
|
||||
},
|
||||
});
|
||||
expect(response).toHaveProperty('statusCode', 200);
|
||||
});
|
||||
});
|
||||
|
||||
describe('PATCH /connectors/:id', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('throws when connector can not be found by given connectorId (locally)', async () => {
|
||||
getConnectorInstanceByIdPlaceHolder.mockImplementationOnce(async (_id: string) => {
|
||||
const found = mockConnectorInstanceList.find(
|
||||
(connectorInstance) => connectorInstance.metadata.id === 'connector'
|
||||
);
|
||||
assertThat(found, new RequestError({ code: 'entity.not_found', status: 404 }));
|
||||
|
||||
return found;
|
||||
});
|
||||
const response = await connectorRequest.patch('/connectors/findConnector').send({});
|
||||
expect(response).toHaveProperty('statusCode', 404);
|
||||
});
|
||||
|
||||
it('throws when connector can not be found by given connectorId (remotely)', async () => {
|
||||
getConnectorInstanceByIdPlaceHolder.mockImplementationOnce(async (id: string) => {
|
||||
const foundConnectorInstance = mockConnectorInstanceList.find(
|
||||
(connectorInstance) => connectorInstance.metadata.id === id
|
||||
);
|
||||
assertThat(
|
||||
foundConnectorInstance,
|
||||
new RequestError({ code: 'entity.not_found', status: 404 })
|
||||
);
|
||||
|
||||
const foundConnector = mockConnectorList.find((connector) => connector.id === 'connector0');
|
||||
assertThat(foundConnector, 'entity.not_found');
|
||||
|
||||
return { foundConnector, ...foundConnectorInstance };
|
||||
});
|
||||
const response = await connectorRequest.patch('/connectors/connector_0').send({});
|
||||
expect(response).toHaveProperty('statusCode', 400);
|
||||
});
|
||||
|
||||
it('config validation fails', async () => {
|
||||
getConnectorInstanceByIdPlaceHolder.mockImplementationOnce(async (_id: string) => {
|
||||
return {
|
||||
connector: {
|
||||
id: 'connector_0',
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_123,
|
||||
},
|
||||
metadata: {
|
||||
id: 'connector_0',
|
||||
type: ConnectorType.Social,
|
||||
name: {},
|
||||
logo: './logo.png',
|
||||
description: {},
|
||||
},
|
||||
validateConfig: async (_config) => {
|
||||
throw new ConnectorError(ConnectorErrorCodes.InvalidConfig);
|
||||
},
|
||||
};
|
||||
});
|
||||
const response = await connectorRequest
|
||||
.patch('/connectors/connector_0')
|
||||
.send({ config: { cliend_id: 'client_id', client_secret: 'client_secret' } });
|
||||
expect(response).toHaveProperty('statusCode', 500);
|
||||
});
|
||||
|
||||
it('successfully updates connector configs', async () => {
|
||||
getConnectorInstanceByIdPlaceHolder.mockImplementationOnce(async (_id: string) => {
|
||||
return {
|
||||
connector: {
|
||||
id: 'connector_0',
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_123,
|
||||
},
|
||||
metadata: {
|
||||
id: 'connector_0',
|
||||
type: ConnectorType.Social,
|
||||
name: {},
|
||||
logo: './logo.png',
|
||||
description: {},
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
validateConfig: async (_config) => {},
|
||||
};
|
||||
});
|
||||
const response = await connectorRequest
|
||||
.patch('/connectors/connector_0')
|
||||
.send({ config: { cliend_id: 'client_id', client_secret: 'client_secret' } });
|
||||
expect(updateConnector).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
where: { id: 'connector_0' },
|
||||
set: { config: { cliend_id: 'client_id', client_secret: 'client_secret' } },
|
||||
})
|
||||
);
|
||||
expect(response.body).toMatchObject({
|
||||
metadata: {
|
||||
id: 'connector_0',
|
||||
type: ConnectorType.Social,
|
||||
name: {},
|
||||
logo: './logo.png',
|
||||
description: {},
|
||||
},
|
||||
});
|
||||
expect(response).toHaveProperty('statusCode', 200);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -11,11 +11,13 @@ import {
|
|||
BrandingStyle,
|
||||
Language,
|
||||
Connector,
|
||||
ConnectorMetadata,
|
||||
Passcode,
|
||||
PasscodeType,
|
||||
UserLog,
|
||||
UserLogType,
|
||||
UserLogResult,
|
||||
ConnectorType,
|
||||
} from '@logto/schemas';
|
||||
import pick from 'lodash.pick';
|
||||
|
||||
|
@ -188,6 +190,162 @@ export const mockConnector: Connector = {
|
|||
createdAt: 1_645_334_775_356,
|
||||
};
|
||||
|
||||
export const mockConnectorList: Connector[] = [
|
||||
{
|
||||
id: 'connector_0',
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_123,
|
||||
},
|
||||
{
|
||||
id: 'connector_1',
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_234,
|
||||
},
|
||||
{
|
||||
id: 'connector_2',
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_345,
|
||||
},
|
||||
{
|
||||
id: 'connector_3',
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_456,
|
||||
},
|
||||
{
|
||||
id: 'connector_4',
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_567,
|
||||
},
|
||||
{
|
||||
id: 'connector_5',
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_567,
|
||||
},
|
||||
{
|
||||
id: 'connector_6',
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_567,
|
||||
},
|
||||
];
|
||||
|
||||
export const mockConnectorInstanceList: Array<{
|
||||
connector: Connector;
|
||||
metadata: ConnectorMetadata;
|
||||
}> = [
|
||||
{
|
||||
connector: {
|
||||
id: 'connector_0',
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_123,
|
||||
},
|
||||
metadata: {
|
||||
id: 'connector_0',
|
||||
type: ConnectorType.Social,
|
||||
name: {},
|
||||
logo: './logo.png',
|
||||
description: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
connector: {
|
||||
id: 'connector_1',
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_234,
|
||||
},
|
||||
metadata: {
|
||||
id: 'connector_1',
|
||||
type: ConnectorType.SMS,
|
||||
name: {},
|
||||
logo: './logo.png',
|
||||
description: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
connector: {
|
||||
id: 'connector_2',
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_345,
|
||||
},
|
||||
metadata: {
|
||||
id: 'connector_2',
|
||||
type: ConnectorType.Social,
|
||||
name: {},
|
||||
logo: './logo.png',
|
||||
description: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
connector: {
|
||||
id: 'connector_3',
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_456,
|
||||
},
|
||||
metadata: {
|
||||
id: 'connector_3',
|
||||
type: ConnectorType.Social,
|
||||
name: {},
|
||||
logo: './logo.png',
|
||||
description: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
connector: {
|
||||
id: 'connector_4',
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_567,
|
||||
},
|
||||
metadata: {
|
||||
id: 'connector_4',
|
||||
type: ConnectorType.Email,
|
||||
name: {},
|
||||
logo: './logo.png',
|
||||
description: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
connector: {
|
||||
id: 'connector_5',
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_567,
|
||||
},
|
||||
metadata: {
|
||||
id: 'connector_5',
|
||||
type: ConnectorType.SMS,
|
||||
name: {},
|
||||
logo: './logo.png',
|
||||
description: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
connector: {
|
||||
id: 'connector_6',
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_567,
|
||||
},
|
||||
metadata: {
|
||||
id: 'connector_6',
|
||||
type: ConnectorType.Email,
|
||||
name: {},
|
||||
logo: './logo.png',
|
||||
description: {},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const mockPasscode: Passcode = {
|
||||
id: 'foo',
|
||||
interactionJti: 'jti',
|
||||
|
|
Loading…
Add table
Reference in a new issue