mirror of
https://github.com/logto-io/logto.git
synced 2025-01-20 21:32:31 -05:00
282 lines
9.3 KiB
TypeScript
282 lines
9.3 KiB
TypeScript
|
import { ConnectorError, ConnectorErrorCodes, ValidateConfig } from '@logto/connector-types';
|
||
|
import { Connector, ConnectorType } from '@logto/schemas';
|
||
|
|
||
|
import { mockConnectorInstanceList, mockMetadata, mockConnector } from '@/__mocks__';
|
||
|
import { ConnectorMetadata } from '@/connectors/types';
|
||
|
import RequestError from '@/errors/RequestError';
|
||
|
import { updateConnector } from '@/queries/connector';
|
||
|
import assertThat from '@/utils/assert-that';
|
||
|
import { createRequester } from '@/utils/test-utils';
|
||
|
|
||
|
import connectorRoutes from './connector';
|
||
|
|
||
|
type ConnectorInstance = {
|
||
|
connector: Connector;
|
||
|
metadata: ConnectorMetadata;
|
||
|
validateConfig?: ValidateConfig;
|
||
|
sendMessage?: unknown;
|
||
|
};
|
||
|
|
||
|
const getConnectorInstancesPlaceHolder = jest.fn() as jest.MockedFunction<
|
||
|
() => Promise<ConnectorInstance[]>
|
||
|
>;
|
||
|
const getConnectorInstanceByIdPlaceHolder = jest.fn(async (connectorId: string) => {
|
||
|
const connectorInstances = await getConnectorInstancesPlaceHolder();
|
||
|
const connector = connectorInstances.find(({ connector }) => connector.id === connectorId);
|
||
|
assertThat(
|
||
|
connector,
|
||
|
new RequestError({
|
||
|
code: 'entity.not_found',
|
||
|
connectorId,
|
||
|
status: 404,
|
||
|
})
|
||
|
);
|
||
|
|
||
|
return {
|
||
|
...connector,
|
||
|
validateConfig: validateConfigPlaceHolder,
|
||
|
sendMessage: sendMessagePlaceHolder,
|
||
|
};
|
||
|
});
|
||
|
const validateConfigPlaceHolder = jest.fn();
|
||
|
const sendMessagePlaceHolder = jest.fn();
|
||
|
|
||
|
jest.mock('@/queries/connector', () => ({
|
||
|
updateConnector: jest.fn(),
|
||
|
}));
|
||
|
jest.mock('@/connectors', () => ({
|
||
|
getConnectorInstances: async () => getConnectorInstancesPlaceHolder(),
|
||
|
getConnectorInstanceById: async (connectorId: string) =>
|
||
|
getConnectorInstanceByIdPlaceHolder(connectorId),
|
||
|
}));
|
||
|
|
||
|
describe('connector PATCH routes', () => {
|
||
|
const connectorRequest = createRequester({ authedRoutes: connectorRoutes });
|
||
|
|
||
|
describe('PATCH /connectors/:id/enabled', () => {
|
||
|
afterEach(() => {
|
||
|
jest.clearAllMocks();
|
||
|
});
|
||
|
|
||
|
it('throws if connector can not be found (locally)', async () => {
|
||
|
getConnectorInstancesPlaceHolder.mockResolvedValueOnce(mockConnectorInstanceList.slice(1));
|
||
|
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 () => {
|
||
|
getConnectorInstancesPlaceHolder.mockResolvedValueOnce([]);
|
||
|
const response = await connectorRequest
|
||
|
.patch('/connectors/id0/enabled')
|
||
|
.send({ enabled: true });
|
||
|
expect(response).toHaveProperty('statusCode', 404);
|
||
|
});
|
||
|
|
||
|
it('enables one of the social connectors (with valid config)', async () => {
|
||
|
getConnectorInstancesPlaceHolder.mockResolvedValueOnce([
|
||
|
{
|
||
|
connector: mockConnector,
|
||
|
metadata: { ...mockMetadata, type: ConnectorType.Social },
|
||
|
},
|
||
|
]);
|
||
|
const response = await connectorRequest
|
||
|
.patch('/connectors/id/enabled')
|
||
|
.send({ enabled: true });
|
||
|
expect(updateConnector).toHaveBeenCalledWith(
|
||
|
expect.objectContaining({
|
||
|
where: { id: 'id' },
|
||
|
set: { enabled: true },
|
||
|
})
|
||
|
);
|
||
|
expect(response.body).toMatchObject({
|
||
|
metadata: { ...mockMetadata, type: ConnectorType.Social },
|
||
|
});
|
||
|
expect(response).toHaveProperty('statusCode', 200);
|
||
|
});
|
||
|
|
||
|
it('enables one of the social connectors (with invalid config)', async () => {
|
||
|
validateConfigPlaceHolder.mockImplementationOnce(async () => {
|
||
|
throw new ConnectorError(ConnectorErrorCodes.InvalidConfig);
|
||
|
});
|
||
|
getConnectorInstancesPlaceHolder.mockResolvedValueOnce([
|
||
|
{
|
||
|
connector: mockConnector,
|
||
|
metadata: mockMetadata,
|
||
|
},
|
||
|
]);
|
||
|
const response = await connectorRequest
|
||
|
.patch('/connectors/id/enabled')
|
||
|
.send({ enabled: true });
|
||
|
expect(response).toHaveProperty('statusCode', 500);
|
||
|
});
|
||
|
|
||
|
it('disables one of the social connectors', async () => {
|
||
|
getConnectorInstancesPlaceHolder.mockResolvedValueOnce([
|
||
|
{
|
||
|
connector: mockConnector,
|
||
|
metadata: mockMetadata,
|
||
|
},
|
||
|
]);
|
||
|
const response = await connectorRequest
|
||
|
.patch('/connectors/id/enabled')
|
||
|
.send({ enabled: false });
|
||
|
expect(updateConnector).toHaveBeenCalledWith(
|
||
|
expect.objectContaining({
|
||
|
where: { id: 'id' },
|
||
|
set: { enabled: false },
|
||
|
})
|
||
|
);
|
||
|
expect(response.body).toMatchObject({
|
||
|
metadata: mockMetadata,
|
||
|
});
|
||
|
expect(response).toHaveProperty('statusCode', 200);
|
||
|
});
|
||
|
|
||
|
it('enables one of the email/sms connectors (with valid config)', async () => {
|
||
|
getConnectorInstancesPlaceHolder.mockResolvedValueOnce(mockConnectorInstanceList);
|
||
|
const mockedMetadata = {
|
||
|
...mockMetadata,
|
||
|
id: 'id1',
|
||
|
type: ConnectorType.SMS,
|
||
|
};
|
||
|
const mockedConnector = {
|
||
|
...mockConnector,
|
||
|
id: 'id1',
|
||
|
};
|
||
|
getConnectorInstanceByIdPlaceHolder.mockResolvedValueOnce({
|
||
|
connector: mockedConnector,
|
||
|
metadata: mockedMetadata,
|
||
|
validateConfig: jest.fn(),
|
||
|
sendMessage: jest.fn(),
|
||
|
});
|
||
|
const response = await connectorRequest
|
||
|
.patch('/connectors/id1/enabled')
|
||
|
.send({ enabled: true });
|
||
|
expect(response).toHaveProperty('statusCode', 200);
|
||
|
expect(updateConnector).toHaveBeenNthCalledWith(
|
||
|
1,
|
||
|
expect.objectContaining({
|
||
|
where: { id: 'id1' },
|
||
|
set: { enabled: false },
|
||
|
})
|
||
|
);
|
||
|
expect(updateConnector).toHaveBeenNthCalledWith(
|
||
|
2,
|
||
|
expect.objectContaining({
|
||
|
where: { id: 'id5' },
|
||
|
set: { enabled: false },
|
||
|
})
|
||
|
);
|
||
|
expect(updateConnector).toHaveBeenNthCalledWith(
|
||
|
3,
|
||
|
expect.objectContaining({
|
||
|
where: { id: 'id1' },
|
||
|
set: { enabled: true },
|
||
|
})
|
||
|
);
|
||
|
expect(response.body).toMatchObject({
|
||
|
metadata: mockedMetadata,
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('enables one of the email/sms connectors (with invalid config)', async () => {
|
||
|
validateConfigPlaceHolder.mockImplementationOnce(async () => {
|
||
|
throw new ConnectorError(ConnectorErrorCodes.InvalidConfig);
|
||
|
});
|
||
|
getConnectorInstancesPlaceHolder.mockResolvedValueOnce([
|
||
|
{
|
||
|
connector: mockConnector,
|
||
|
metadata: {
|
||
|
...mockMetadata,
|
||
|
type: ConnectorType.SMS,
|
||
|
},
|
||
|
},
|
||
|
]);
|
||
|
const response = await connectorRequest
|
||
|
.patch('/connectors/id/enabled')
|
||
|
.send({ enabled: true });
|
||
|
expect(response).toHaveProperty('statusCode', 500);
|
||
|
});
|
||
|
|
||
|
it('disables one of the email/sms connectors', async () => {
|
||
|
getConnectorInstancesPlaceHolder.mockResolvedValueOnce([
|
||
|
{
|
||
|
connector: mockConnector,
|
||
|
metadata: mockMetadata,
|
||
|
},
|
||
|
]);
|
||
|
const response = await connectorRequest
|
||
|
.patch('/connectors/id/enabled')
|
||
|
.send({ enabled: false });
|
||
|
expect(updateConnector).toHaveBeenCalledWith(
|
||
|
expect.objectContaining({
|
||
|
where: { id: 'id' },
|
||
|
set: { enabled: false },
|
||
|
})
|
||
|
);
|
||
|
expect(response.body).toMatchObject({
|
||
|
metadata: mockMetadata,
|
||
|
});
|
||
|
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 () => {
|
||
|
getConnectorInstancesPlaceHolder.mockResolvedValueOnce(mockConnectorInstanceList.slice(0, 1));
|
||
|
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 () => {
|
||
|
getConnectorInstancesPlaceHolder.mockResolvedValueOnce([]);
|
||
|
const response = await connectorRequest.patch('/connectors/id0').send({});
|
||
|
expect(response).toHaveProperty('statusCode', 404);
|
||
|
});
|
||
|
|
||
|
it('config validation fails', async () => {
|
||
|
validateConfigPlaceHolder.mockImplementationOnce(async () => {
|
||
|
throw new ConnectorError(ConnectorErrorCodes.InvalidConfig);
|
||
|
});
|
||
|
getConnectorInstancesPlaceHolder.mockResolvedValueOnce([
|
||
|
{
|
||
|
connector: mockConnector,
|
||
|
metadata: mockMetadata,
|
||
|
},
|
||
|
]);
|
||
|
const response = await connectorRequest
|
||
|
.patch('/connectors/id')
|
||
|
.send({ config: { cliend_id: 'client_id', client_secret: 'client_secret' } });
|
||
|
expect(response).toHaveProperty('statusCode', 500);
|
||
|
});
|
||
|
|
||
|
it('successfully updates connector configs', async () => {
|
||
|
getConnectorInstancesPlaceHolder.mockResolvedValueOnce([
|
||
|
{
|
||
|
connector: mockConnector,
|
||
|
metadata: mockMetadata,
|
||
|
},
|
||
|
]);
|
||
|
const response = await connectorRequest
|
||
|
.patch('/connectors/id')
|
||
|
.send({ config: { cliend_id: 'client_id', client_secret: 'client_secret' } });
|
||
|
expect(updateConnector).toHaveBeenCalledWith(
|
||
|
expect.objectContaining({
|
||
|
where: { id: 'id' },
|
||
|
set: { config: { cliend_id: 'client_id', client_secret: 'client_secret' } },
|
||
|
})
|
||
|
);
|
||
|
expect(response.body).toMatchObject({
|
||
|
metadata: mockMetadata,
|
||
|
});
|
||
|
expect(response).toHaveProperty('statusCode', 200);
|
||
|
});
|
||
|
});
|
||
|
});
|