mirror of
https://github.com/logto-io/logto.git
synced 2025-03-17 22:31:28 -05:00
refactor(core): reorg connector routes and UTs (#3700)
This commit is contained in:
parent
020a811016
commit
2e036eae1f
8 changed files with 626 additions and 406 deletions
142
packages/core/src/routes/connector/config-testing.test.ts
Normal file
142
packages/core/src/routes/connector/config-testing.test.ts
Normal file
|
@ -0,0 +1,142 @@
|
|||
import type { ConnectorFactory } from '@logto/cli/lib/connector/index.js';
|
||||
import { VerificationCodeType } from '@logto/connector-kit';
|
||||
import type { EmailConnector, SmsConnector } from '@logto/connector-kit';
|
||||
import { ConnectorType } from '@logto/schemas';
|
||||
import { pickDefault, createMockUtils } from '@logto/shared/esm';
|
||||
|
||||
import { mockMetadata, mockConnectorFactory } from '#src/__mocks__/index.js';
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import { MockTenant } from '#src/test-utils/tenant.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
import type { LogtoConnector } from '#src/utils/connectors/types.js';
|
||||
import { createRequester } from '#src/utils/test-utils.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
const { mockEsmWithActual } = createMockUtils(jest);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
const getLogtoConnectors = jest.fn<Promise<LogtoConnector[]>, []>();
|
||||
|
||||
const { loadConnectorFactories } = await mockEsmWithActual(
|
||||
'#src/utils/connectors/index.js',
|
||||
() => ({
|
||||
loadConnectorFactories: jest.fn(),
|
||||
})
|
||||
);
|
||||
|
||||
const { buildRawConnector } = await mockEsmWithActual('@logto/cli/lib/connector/index.js', () => ({
|
||||
buildRawConnector: jest.fn(),
|
||||
}));
|
||||
|
||||
const tenantContext = new MockTenant(
|
||||
undefined,
|
||||
{},
|
||||
{
|
||||
getLogtoConnectors,
|
||||
getLogtoConnectorById: async (connectorId: string) => {
|
||||
const connectors = await getLogtoConnectors();
|
||||
const connector = connectors.find(({ dbEntry }) => dbEntry.id === connectorId);
|
||||
assertThat(
|
||||
connector,
|
||||
new RequestError({
|
||||
code: 'entity.not_found',
|
||||
connectorId,
|
||||
status: 404,
|
||||
})
|
||||
);
|
||||
|
||||
return connector;
|
||||
},
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
const connectorConfigTestingRoutes = await pickDefault(import('./config-testing.js'));
|
||||
|
||||
describe('connector services route', () => {
|
||||
const connectorRequest = createRequester({
|
||||
authedRoutes: connectorConfigTestingRoutes,
|
||||
tenantContext,
|
||||
});
|
||||
|
||||
describe('POST /connectors/:factoryId/test', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should get SMS connector and send test message', async () => {
|
||||
const sendMessage = jest.fn();
|
||||
const mockedSmsConnectorFactory: ConnectorFactory<SmsConnector> = {
|
||||
...mockConnectorFactory,
|
||||
metadata: mockMetadata,
|
||||
type: ConnectorType.Sms,
|
||||
createConnector: jest.fn(),
|
||||
};
|
||||
loadConnectorFactories.mockResolvedValueOnce([mockedSmsConnectorFactory]);
|
||||
buildRawConnector.mockResolvedValueOnce({ rawConnector: { sendMessage } });
|
||||
const response = await connectorRequest
|
||||
.post('/connectors/id/test')
|
||||
.send({ phone: '12345678901', config: { test: 123 } });
|
||||
expect(sendMessage).toHaveBeenCalledTimes(1);
|
||||
expect(sendMessage).toHaveBeenCalledWith(
|
||||
{
|
||||
to: '12345678901',
|
||||
type: VerificationCodeType.Generic,
|
||||
payload: {
|
||||
code: '000000',
|
||||
},
|
||||
},
|
||||
{ test: 123 }
|
||||
);
|
||||
expect(response).toHaveProperty('statusCode', 204);
|
||||
});
|
||||
|
||||
it('should get email connector and send test message', async () => {
|
||||
const sendMessage = jest.fn();
|
||||
const mockedEmailConnectorFactory: ConnectorFactory<EmailConnector> = {
|
||||
...mockConnectorFactory,
|
||||
metadata: mockMetadata,
|
||||
type: ConnectorType.Email,
|
||||
createConnector: jest.fn(),
|
||||
};
|
||||
loadConnectorFactories.mockResolvedValueOnce([mockedEmailConnectorFactory]);
|
||||
buildRawConnector.mockResolvedValueOnce({ rawConnector: { sendMessage } });
|
||||
const response = await connectorRequest
|
||||
.post('/connectors/id/test')
|
||||
.send({ email: 'test@email.com', config: { test: 123 } });
|
||||
expect(sendMessage).toHaveBeenCalledTimes(1);
|
||||
expect(sendMessage).toHaveBeenCalledWith(
|
||||
{
|
||||
to: 'test@email.com',
|
||||
type: VerificationCodeType.Generic,
|
||||
payload: {
|
||||
code: '000000',
|
||||
},
|
||||
},
|
||||
{ test: 123 }
|
||||
);
|
||||
expect(response).toHaveProperty('statusCode', 204);
|
||||
});
|
||||
|
||||
it('should throw when neither phone nor email is provided', async () => {
|
||||
const response = await connectorRequest.post('/connectors/id/test').send({});
|
||||
expect(response).toHaveProperty('statusCode', 400);
|
||||
});
|
||||
|
||||
it('should throw when sms connector is not found', async () => {
|
||||
getLogtoConnectors.mockResolvedValueOnce([]);
|
||||
const response = await connectorRequest
|
||||
.post('/connectors/id/test')
|
||||
.send({ phone: '12345678901' });
|
||||
expect(response).toHaveProperty('statusCode', 400);
|
||||
});
|
||||
|
||||
it('should throw when email connector is not found', async () => {
|
||||
getLogtoConnectors.mockResolvedValueOnce([]);
|
||||
const response = await connectorRequest
|
||||
.post('/connectors/id/test')
|
||||
.send({ email: 'test@email.com' });
|
||||
expect(response).toHaveProperty('statusCode', 400);
|
||||
});
|
||||
});
|
||||
});
|
83
packages/core/src/routes/connector/config-testing.ts
Normal file
83
packages/core/src/routes/connector/config-testing.ts
Normal file
|
@ -0,0 +1,83 @@
|
|||
import { buildRawConnector } from '@logto/cli/lib/connector/index.js';
|
||||
import type { ConnectorFactory } from '@logto/cli/lib/connector/index.js';
|
||||
import {
|
||||
type SmsConnector,
|
||||
type EmailConnector,
|
||||
demoConnectorIds,
|
||||
VerificationCodeType,
|
||||
} from '@logto/connector-kit';
|
||||
import { phoneRegEx, emailRegEx } from '@logto/core-kit';
|
||||
import { arbitraryObjectGuard, ConnectorType } from '@logto/schemas';
|
||||
import { string, object } from 'zod';
|
||||
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import koaGuard from '#src/middleware/koa-guard.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
import { loadConnectorFactories } from '#src/utils/connectors/index.js';
|
||||
|
||||
import type { AuthedRouter, RouterInitArgs } from '../types.js';
|
||||
|
||||
export default function connectorConfigTestingRoutes<T extends AuthedRouter>(
|
||||
...[router]: RouterInitArgs<T>
|
||||
) {
|
||||
router.post(
|
||||
'/connectors/:factoryId/test',
|
||||
koaGuard({
|
||||
params: object({ factoryId: string().min(1) }),
|
||||
body: object({
|
||||
phone: string().regex(phoneRegEx).optional(),
|
||||
email: string().regex(emailRegEx).optional(),
|
||||
config: arbitraryObjectGuard,
|
||||
}),
|
||||
}),
|
||||
async (ctx, next) => {
|
||||
const {
|
||||
params: { factoryId },
|
||||
body,
|
||||
} = ctx.guard;
|
||||
const { phone, email, config } = body;
|
||||
|
||||
const subject = phone ?? email;
|
||||
assertThat(subject, new RequestError({ code: 'guard.invalid_input' }));
|
||||
|
||||
const connectorFactories = await loadConnectorFactories();
|
||||
const connectorFactory = connectorFactories
|
||||
.filter(
|
||||
(factory): factory is ConnectorFactory<SmsConnector> | ConnectorFactory<EmailConnector> =>
|
||||
factory.type === ConnectorType.Email || factory.type === ConnectorType.Sms
|
||||
)
|
||||
.find(({ metadata: { id } }) => id === factoryId && !demoConnectorIds.includes(id));
|
||||
const expectType = phone ? ConnectorType.Sms : ConnectorType.Email;
|
||||
|
||||
assertThat(
|
||||
connectorFactory,
|
||||
new RequestError({
|
||||
code: 'connector.not_found',
|
||||
type: expectType,
|
||||
factoryId,
|
||||
})
|
||||
);
|
||||
|
||||
assertThat(connectorFactory.type === expectType, 'connector.unexpected_type');
|
||||
|
||||
const {
|
||||
rawConnector: { sendMessage },
|
||||
} = await buildRawConnector<SmsConnector | EmailConnector>(connectorFactory);
|
||||
|
||||
await sendMessage(
|
||||
{
|
||||
to: subject,
|
||||
type: VerificationCodeType.Generic,
|
||||
payload: {
|
||||
code: '000000',
|
||||
},
|
||||
},
|
||||
config
|
||||
);
|
||||
|
||||
ctx.status = 204;
|
||||
|
||||
return next();
|
||||
}
|
||||
);
|
||||
}
|
112
packages/core/src/routes/connector/index.delete.test.ts
Normal file
112
packages/core/src/routes/connector/index.delete.test.ts
Normal file
|
@ -0,0 +1,112 @@
|
|||
import { ConnectorType } from '@logto/schemas';
|
||||
import { pickDefault, createMockUtils } from '@logto/shared/esm';
|
||||
|
||||
import { mockConnector, mockConnectorFactory } from '#src/__mocks__/index.js';
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import type Queries from '#src/tenants/Queries.js';
|
||||
import { MockTenant } from '#src/test-utils/tenant.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
import type { LogtoConnector } from '#src/utils/connectors/types.js';
|
||||
import { createRequester } from '#src/utils/test-utils.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
const { mockEsmWithActual } = createMockUtils(jest);
|
||||
|
||||
const removeUnavailableSocialConnectorTargets = jest.fn();
|
||||
|
||||
const getLogtoConnectors: jest.MockedFunction<() => Promise<LogtoConnector[]>> = jest.fn();
|
||||
const getLogtoConnectorById: jest.MockedFunction<(connectorId: string) => Promise<LogtoConnector>> =
|
||||
jest.fn(async (connectorId: string) => {
|
||||
const connectors = await getLogtoConnectors();
|
||||
const connector = connectors.find(({ dbEntry }) => dbEntry.id === connectorId);
|
||||
|
||||
assertThat(
|
||||
connector,
|
||||
new RequestError({
|
||||
code: 'entity.not_found',
|
||||
connectorId,
|
||||
status: 404,
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
...connector,
|
||||
sendMessage: sendMessagePlaceHolder,
|
||||
};
|
||||
});
|
||||
|
||||
const sendMessagePlaceHolder = jest.fn();
|
||||
|
||||
const connectorQueries = {
|
||||
findConnectorById: jest.fn(),
|
||||
deleteConnectorById: jest.fn(),
|
||||
} satisfies Partial<Queries['connectors']>;
|
||||
const { findConnectorById, deleteConnectorById } = connectorQueries;
|
||||
|
||||
const { loadConnectorFactories } = await mockEsmWithActual(
|
||||
'#src/utils/connectors/index.js',
|
||||
() => ({
|
||||
loadConnectorFactories: jest.fn(),
|
||||
})
|
||||
);
|
||||
|
||||
const tenantContext = new MockTenant(
|
||||
undefined,
|
||||
{ connectors: connectorQueries },
|
||||
{
|
||||
getLogtoConnectors,
|
||||
getLogtoConnectorById,
|
||||
},
|
||||
{
|
||||
signInExperiences: { removeUnavailableSocialConnectorTargets },
|
||||
}
|
||||
);
|
||||
|
||||
const connectorDataRoutes = await pickDefault(import('./index.js'));
|
||||
|
||||
describe('connector data routes', () => {
|
||||
const connectorRequest = createRequester({ authedRoutes: connectorDataRoutes, tenantContext });
|
||||
|
||||
describe('DELETE /connectors/:id', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('delete connector instance and remove unavailable social connector targets', async () => {
|
||||
findConnectorById.mockResolvedValueOnce(mockConnector);
|
||||
loadConnectorFactories.mockResolvedValueOnce([mockConnectorFactory]);
|
||||
await connectorRequest.delete('/connectors/id').send({});
|
||||
expect(deleteConnectorById).toHaveBeenCalledTimes(1);
|
||||
expect(removeUnavailableSocialConnectorTargets).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('delete connector instance (connector factory is not social type)', async () => {
|
||||
findConnectorById.mockResolvedValueOnce(mockConnector);
|
||||
loadConnectorFactories.mockResolvedValueOnce([
|
||||
{ ...mockConnectorFactory, type: ConnectorType.Sms },
|
||||
]);
|
||||
await connectorRequest.delete('/connectors/id').send({});
|
||||
expect(deleteConnectorById).toHaveBeenCalledTimes(1);
|
||||
expect(removeUnavailableSocialConnectorTargets).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('delete connector instance (connector factory is not found)', async () => {
|
||||
findConnectorById.mockResolvedValueOnce(mockConnector);
|
||||
loadConnectorFactories.mockResolvedValueOnce([]);
|
||||
await connectorRequest.delete('/connectors/id').send({});
|
||||
expect(deleteConnectorById).toHaveBeenCalledTimes(1);
|
||||
expect(removeUnavailableSocialConnectorTargets).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('throws when connector not exists with `id`', async () => {
|
||||
// eslint-disable-next-line unicorn/no-useless-undefined
|
||||
findConnectorById.mockResolvedValueOnce(undefined);
|
||||
const response = await connectorRequest.delete('/connectors/id').send({});
|
||||
expect(response).toHaveProperty('statusCode', 500);
|
||||
});
|
||||
});
|
||||
});
|
158
packages/core/src/routes/connector/index.get.test.ts
Normal file
158
packages/core/src/routes/connector/index.get.test.ts
Normal file
|
@ -0,0 +1,158 @@
|
|||
import { ConnectorType } from '@logto/schemas';
|
||||
import { pickDefault, createMockUtils } from '@logto/shared/esm';
|
||||
|
||||
import {
|
||||
mockMetadata0,
|
||||
mockMetadata1,
|
||||
mockMetadata2,
|
||||
mockMetadata3,
|
||||
mockConnectorFactory,
|
||||
mockLogtoConnectorList,
|
||||
} from '#src/__mocks__/index.js';
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import { MockTenant } from '#src/test-utils/tenant.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
import type { LogtoConnector } from '#src/utils/connectors/types.js';
|
||||
import { createRequester } from '#src/utils/test-utils.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
const { mockEsm, mockEsmWithActual } = createMockUtils(jest);
|
||||
|
||||
mockEsm('#src/utils/connectors/platform.js', () => ({
|
||||
checkSocialConnectorTargetAndPlatformUniqueness: jest.fn(),
|
||||
}));
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
const getLogtoConnectors = jest.fn<Promise<LogtoConnector[]>, []>();
|
||||
|
||||
const { loadConnectorFactories } = await mockEsmWithActual(
|
||||
'#src/utils/connectors/index.js',
|
||||
() => ({
|
||||
loadConnectorFactories: jest.fn(),
|
||||
})
|
||||
);
|
||||
|
||||
const tenantContext = new MockTenant(
|
||||
undefined,
|
||||
{},
|
||||
{
|
||||
getLogtoConnectors,
|
||||
getLogtoConnectorById: async (connectorId: string) => {
|
||||
const connectors = await getLogtoConnectors();
|
||||
const connector = connectors.find(({ dbEntry }) => dbEntry.id === connectorId);
|
||||
assertThat(
|
||||
connector,
|
||||
new RequestError({
|
||||
code: 'entity.not_found',
|
||||
connectorId,
|
||||
status: 404,
|
||||
})
|
||||
);
|
||||
|
||||
return connector;
|
||||
},
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
const connectorDataRoutes = await pickDefault(import('./index.js'));
|
||||
|
||||
describe('connector data route', () => {
|
||||
const connectorRequest = createRequester({ authedRoutes: connectorDataRoutes, tenantContext });
|
||||
|
||||
describe('GET /connectors', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('throws if more than one email connector exists', async () => {
|
||||
getLogtoConnectors.mockResolvedValueOnce(mockLogtoConnectorList);
|
||||
const response = await connectorRequest.get('/connectors').send({});
|
||||
expect(response).toHaveProperty('statusCode', 400);
|
||||
});
|
||||
|
||||
it('throws if more than one SMS connector exists', async () => {
|
||||
getLogtoConnectors.mockResolvedValueOnce(
|
||||
mockLogtoConnectorList.filter((connector) => connector.type !== ConnectorType.Email)
|
||||
);
|
||||
const response = await connectorRequest.get('/connectors').send({});
|
||||
expect(response).toHaveProperty('statusCode', 400);
|
||||
});
|
||||
|
||||
it('shows all connectors', async () => {
|
||||
getLogtoConnectors.mockResolvedValueOnce(
|
||||
mockLogtoConnectorList.filter((connector) => connector.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 () => {
|
||||
getLogtoConnectors.mockResolvedValueOnce(mockLogtoConnectorList.slice(2));
|
||||
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 () => {
|
||||
getLogtoConnectors.mockResolvedValueOnce([]);
|
||||
const response = await connectorRequest.get('/connectors/id0').send({});
|
||||
expect(response).toHaveProperty('statusCode', 404);
|
||||
});
|
||||
|
||||
it('shows found connector information', async () => {
|
||||
getLogtoConnectors.mockResolvedValueOnce(mockLogtoConnectorList);
|
||||
const response = await connectorRequest.get('/connectors/id0').send({});
|
||||
expect(response).toHaveProperty('statusCode', 200);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /connector-factories', () => {
|
||||
it('show all connector factories', async () => {
|
||||
loadConnectorFactories.mockResolvedValueOnce([
|
||||
{ ...mockConnectorFactory, metadata: mockMetadata0, type: ConnectorType.Sms },
|
||||
{ ...mockConnectorFactory, metadata: mockMetadata1, type: ConnectorType.Social },
|
||||
{ ...mockConnectorFactory, metadata: mockMetadata2, type: ConnectorType.Email },
|
||||
{ ...mockConnectorFactory, metadata: mockMetadata3, type: ConnectorType.Social },
|
||||
]);
|
||||
const response = await connectorRequest.get('/connector-factories').send({});
|
||||
expect(response.body).toMatchObject([
|
||||
{ ...mockMetadata0, type: ConnectorType.Sms },
|
||||
{ ...mockMetadata1, type: ConnectorType.Social },
|
||||
{ ...mockMetadata2, type: ConnectorType.Email },
|
||||
{ ...mockMetadata3, type: ConnectorType.Social },
|
||||
]);
|
||||
expect(response).toHaveProperty('statusCode', 200);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /connector-factories/:id', () => {
|
||||
it('throws when connector factory can not be found by given id', async () => {
|
||||
loadConnectorFactories.mockResolvedValueOnce([
|
||||
{ ...mockConnectorFactory, metadata: mockMetadata0, type: ConnectorType.Sms },
|
||||
{ ...mockConnectorFactory, metadata: mockMetadata1, type: ConnectorType.Social },
|
||||
{ ...mockConnectorFactory, metadata: mockMetadata2, type: ConnectorType.Email },
|
||||
{ ...mockConnectorFactory, metadata: mockMetadata3, type: ConnectorType.Social },
|
||||
]);
|
||||
const response = await connectorRequest.get('/connector-factories/findConnector').send({});
|
||||
expect(response).toHaveProperty('statusCode', 404);
|
||||
});
|
||||
|
||||
it('show picked connector factory', async () => {
|
||||
loadConnectorFactories.mockResolvedValueOnce([
|
||||
{ ...mockConnectorFactory, metadata: mockMetadata0, type: ConnectorType.Sms },
|
||||
{ ...mockConnectorFactory, metadata: mockMetadata1, type: ConnectorType.Social },
|
||||
{ ...mockConnectorFactory, metadata: mockMetadata2, type: ConnectorType.Email },
|
||||
{ ...mockConnectorFactory, metadata: mockMetadata3, type: ConnectorType.Social },
|
||||
]);
|
||||
const response = await connectorRequest.get('/connector-factories/id2').send({});
|
||||
expect(response.body).toMatchObject({ ...mockMetadata2, type: ConnectorType.Email });
|
||||
expect(response).toHaveProperty('statusCode', 200);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -9,6 +9,7 @@ import {
|
|||
mockLogtoConnector,
|
||||
} from '#src/__mocks__/index.js';
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import type Queries from '#src/tenants/Queries.js';
|
||||
import { MockTenant } from '#src/test-utils/tenant.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
import type { LogtoConnector } from '#src/utils/connectors/types.js';
|
||||
|
@ -16,6 +17,8 @@ import { createRequester } from '#src/utils/test-utils.js';
|
|||
|
||||
const { jest } = import.meta;
|
||||
|
||||
const removeUnavailableSocialConnectorTargets = jest.fn();
|
||||
|
||||
const getLogtoConnectors: jest.MockedFunction<() => Promise<LogtoConnector[]>> = jest.fn();
|
||||
const getLogtoConnectorById: jest.MockedFunction<(connectorId: string) => Promise<LogtoConnector>> =
|
||||
jest.fn(async (connectorId: string) => {
|
||||
|
@ -38,27 +41,30 @@ const getLogtoConnectorById: jest.MockedFunction<(connectorId: string) => Promis
|
|||
});
|
||||
|
||||
const sendMessagePlaceHolder = jest.fn();
|
||||
const updateConnector = jest.fn();
|
||||
|
||||
const connectorQueries = {
|
||||
findConnectorById: jest.fn(),
|
||||
deleteConnectorById: jest.fn(),
|
||||
updateConnector: jest.fn(),
|
||||
} satisfies Partial<Queries['connectors']>;
|
||||
const { updateConnector } = connectorQueries;
|
||||
|
||||
const tenantContext = new MockTenant(
|
||||
undefined,
|
||||
{ connectors: { updateConnector } },
|
||||
{ connectors: connectorQueries },
|
||||
{
|
||||
getLogtoConnectors,
|
||||
getLogtoConnectorById,
|
||||
},
|
||||
{
|
||||
signInExperiences: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
removeUnavailableSocialConnectorTargets: async () => {},
|
||||
},
|
||||
signInExperiences: { removeUnavailableSocialConnectorTargets },
|
||||
}
|
||||
);
|
||||
|
||||
const connectorRoutes = await pickDefault(import('./connector.js'));
|
||||
const connectorDataRoutes = await pickDefault(import('./index.js'));
|
||||
|
||||
describe('connector PATCH routes', () => {
|
||||
const connectorRequest = createRequester({ authedRoutes: connectorRoutes, tenantContext });
|
||||
describe('connector data routes', () => {
|
||||
const connectorRequest = createRequester({ authedRoutes: connectorDataRoutes, tenantContext });
|
||||
|
||||
describe('PATCH /connectors/:id', () => {
|
||||
afterEach(() => {
|
||||
|
@ -131,6 +137,20 @@ describe('connector PATCH routes', () => {
|
|||
expect(response).toHaveProperty('statusCode', 400);
|
||||
});
|
||||
|
||||
it('throws when set syncProfile to `true` and with non-social connector', async () => {
|
||||
getLogtoConnectors.mockResolvedValueOnce([
|
||||
{
|
||||
dbEntry: mockConnector,
|
||||
metadata: mockMetadata,
|
||||
type: ConnectorType.Sms,
|
||||
...mockLogtoConnector,
|
||||
},
|
||||
]);
|
||||
const response = await connectorRequest.patch('/connectors/id').send({ syncProfile: true });
|
||||
expect(response).toHaveProperty('statusCode', 422);
|
||||
expect(updateConnector).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('successfully updates connector config', async () => {
|
||||
getLogtoConnectors.mockResolvedValue([
|
||||
{
|
||||
|
@ -235,20 +255,6 @@ describe('connector PATCH routes', () => {
|
|||
expect(response).toHaveProperty('statusCode', 200);
|
||||
});
|
||||
|
||||
it('throws when set syncProfile to `true` and with non-social connector', async () => {
|
||||
getLogtoConnectors.mockResolvedValueOnce([
|
||||
{
|
||||
dbEntry: mockConnector,
|
||||
metadata: mockMetadata,
|
||||
type: ConnectorType.Sms,
|
||||
...mockLogtoConnector,
|
||||
},
|
||||
]);
|
||||
const response = await connectorRequest.patch('/connectors/id').send({ syncProfile: true });
|
||||
expect(response).toHaveProperty('statusCode', 422);
|
||||
expect(updateConnector).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('successfully set syncProfile to `true` and with social connector', async () => {
|
||||
getLogtoConnectors.mockResolvedValue([
|
||||
{
|
|
@ -1,7 +1,4 @@
|
|||
/* eslint-disable max-lines */
|
||||
import type { ConnectorFactory } from '@logto/cli/lib/connector/index.js';
|
||||
import { ConnectorPlatform, VerificationCodeType } from '@logto/connector-kit';
|
||||
import type { EmailConnector, SmsConnector } from '@logto/connector-kit';
|
||||
import { ConnectorPlatform } from '@logto/connector-kit';
|
||||
import type { Connector } from '@logto/schemas';
|
||||
import { ConnectorType } from '@logto/schemas';
|
||||
import { pickDefault, createMockUtils } from '@logto/shared/esm';
|
||||
|
@ -9,13 +6,8 @@ import { any } from 'zod';
|
|||
|
||||
import {
|
||||
mockMetadata,
|
||||
mockMetadata0,
|
||||
mockMetadata1,
|
||||
mockMetadata2,
|
||||
mockMetadata3,
|
||||
mockConnector,
|
||||
mockConnectorFactory,
|
||||
mockLogtoConnectorList,
|
||||
mockLogtoConnector,
|
||||
} from '#src/__mocks__/index.js';
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
|
@ -26,28 +18,14 @@ import type { LogtoConnector } from '#src/utils/connectors/types.js';
|
|||
import { createRequester } from '#src/utils/test-utils.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
const { mockEsm, mockEsmWithActual } = createMockUtils(jest);
|
||||
|
||||
mockEsm('#src/utils/connectors/platform.js', () => ({
|
||||
checkSocialConnectorTargetAndPlatformUniqueness: jest.fn(),
|
||||
}));
|
||||
|
||||
const removeUnavailableSocialConnectorTargets = jest.fn();
|
||||
const { mockEsmWithActual } = createMockUtils(jest);
|
||||
|
||||
const connectorQueries = {
|
||||
findConnectorById: jest.fn(),
|
||||
countConnectorByConnectorId: jest.fn(),
|
||||
deleteConnectorById: jest.fn(),
|
||||
deleteConnectorByIds: jest.fn(),
|
||||
insertConnector: jest.fn(async (body) => body as Connector),
|
||||
} satisfies Partial<Queries['connectors']>;
|
||||
const {
|
||||
findConnectorById,
|
||||
countConnectorByConnectorId,
|
||||
deleteConnectorById,
|
||||
deleteConnectorByIds,
|
||||
insertConnector,
|
||||
} = connectorQueries;
|
||||
const { countConnectorByConnectorId, deleteConnectorByIds, insertConnector } = connectorQueries;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
const getLogtoConnectors = jest.fn<Promise<LogtoConnector[]>, []>();
|
||||
|
@ -87,86 +65,13 @@ const tenantContext = new MockTenant(
|
|||
return connector;
|
||||
},
|
||||
},
|
||||
{
|
||||
signInExperiences: { removeUnavailableSocialConnectorTargets },
|
||||
}
|
||||
{}
|
||||
);
|
||||
|
||||
const connectorRoutes = await pickDefault(import('./connector.js'));
|
||||
const connectorDataRoutes = await pickDefault(import('./index.js'));
|
||||
|
||||
describe('connector route', () => {
|
||||
const connectorRequest = createRequester({ authedRoutes: connectorRoutes, tenantContext });
|
||||
|
||||
describe('GET /connectors', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('throws if more than one email connector exists', async () => {
|
||||
getLogtoConnectors.mockResolvedValueOnce(mockLogtoConnectorList);
|
||||
const response = await connectorRequest.get('/connectors').send({});
|
||||
expect(response).toHaveProperty('statusCode', 400);
|
||||
});
|
||||
|
||||
it('throws if more than one SMS connector exists', async () => {
|
||||
getLogtoConnectors.mockResolvedValueOnce(
|
||||
mockLogtoConnectorList.filter((connector) => connector.type !== ConnectorType.Email)
|
||||
);
|
||||
const response = await connectorRequest.get('/connectors').send({});
|
||||
expect(response).toHaveProperty('statusCode', 400);
|
||||
});
|
||||
|
||||
it('shows all connectors', async () => {
|
||||
getLogtoConnectors.mockResolvedValueOnce(
|
||||
mockLogtoConnectorList.filter((connector) => connector.type === ConnectorType.Social)
|
||||
);
|
||||
const response = await connectorRequest.get('/connectors').send({});
|
||||
expect(response).toHaveProperty('statusCode', 200);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /connector-factories', () => {
|
||||
it('show all connector factories', async () => {
|
||||
loadConnectorFactories.mockResolvedValueOnce([
|
||||
{ ...mockConnectorFactory, metadata: mockMetadata0, type: ConnectorType.Sms },
|
||||
{ ...mockConnectorFactory, metadata: mockMetadata1, type: ConnectorType.Social },
|
||||
{ ...mockConnectorFactory, metadata: mockMetadata2, type: ConnectorType.Email },
|
||||
{ ...mockConnectorFactory, metadata: mockMetadata3, type: ConnectorType.Social },
|
||||
]);
|
||||
const response = await connectorRequest.get('/connector-factories').send({});
|
||||
expect(response.body).toMatchObject([
|
||||
{ ...mockMetadata0, type: ConnectorType.Sms },
|
||||
{ ...mockMetadata1, type: ConnectorType.Social },
|
||||
{ ...mockMetadata2, type: ConnectorType.Email },
|
||||
{ ...mockMetadata3, type: ConnectorType.Social },
|
||||
]);
|
||||
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 () => {
|
||||
getLogtoConnectors.mockResolvedValueOnce(mockLogtoConnectorList.slice(2));
|
||||
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 () => {
|
||||
getLogtoConnectors.mockResolvedValueOnce([]);
|
||||
const response = await connectorRequest.get('/connectors/id0').send({});
|
||||
expect(response).toHaveProperty('statusCode', 404);
|
||||
});
|
||||
|
||||
it('shows found connector information', async () => {
|
||||
getLogtoConnectors.mockResolvedValueOnce(mockLogtoConnectorList);
|
||||
const response = await connectorRequest.get('/connectors/id0').send({});
|
||||
expect(response).toHaveProperty('statusCode', 200);
|
||||
});
|
||||
});
|
||||
describe('connector data route', () => {
|
||||
const connectorRequest = createRequester({ authedRoutes: connectorDataRoutes, tenantContext });
|
||||
|
||||
describe('POST /connectors', () => {
|
||||
afterEach(() => {
|
||||
|
@ -414,129 +319,4 @@ describe('connector route', () => {
|
|||
expect(response).toHaveProperty('statusCode', 422);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /connectors/:id/test', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should get SMS connector and send test message', async () => {
|
||||
const sendMessage = jest.fn();
|
||||
const mockedSmsConnectorFactory: ConnectorFactory<SmsConnector> = {
|
||||
...mockConnectorFactory,
|
||||
metadata: mockMetadata,
|
||||
type: ConnectorType.Sms,
|
||||
createConnector: jest.fn(),
|
||||
};
|
||||
loadConnectorFactories.mockResolvedValueOnce([mockedSmsConnectorFactory]);
|
||||
buildRawConnector.mockResolvedValueOnce({ rawConnector: { sendMessage } });
|
||||
const response = await connectorRequest
|
||||
.post('/connectors/id/test')
|
||||
.send({ phone: '12345678901', config: { test: 123 } });
|
||||
expect(sendMessage).toHaveBeenCalledTimes(1);
|
||||
expect(sendMessage).toHaveBeenCalledWith(
|
||||
{
|
||||
to: '12345678901',
|
||||
type: VerificationCodeType.Generic,
|
||||
payload: {
|
||||
code: '000000',
|
||||
},
|
||||
},
|
||||
{ test: 123 }
|
||||
);
|
||||
expect(response).toHaveProperty('statusCode', 204);
|
||||
});
|
||||
|
||||
it('should get email connector and send test message', async () => {
|
||||
const sendMessage = jest.fn();
|
||||
const mockedEmailConnectorFactory: ConnectorFactory<EmailConnector> = {
|
||||
...mockConnectorFactory,
|
||||
metadata: mockMetadata,
|
||||
type: ConnectorType.Email,
|
||||
createConnector: jest.fn(),
|
||||
};
|
||||
loadConnectorFactories.mockResolvedValueOnce([mockedEmailConnectorFactory]);
|
||||
buildRawConnector.mockResolvedValueOnce({ rawConnector: { sendMessage } });
|
||||
const response = await connectorRequest
|
||||
.post('/connectors/id/test')
|
||||
.send({ email: 'test@email.com', config: { test: 123 } });
|
||||
expect(sendMessage).toHaveBeenCalledTimes(1);
|
||||
expect(sendMessage).toHaveBeenCalledWith(
|
||||
{
|
||||
to: 'test@email.com',
|
||||
type: VerificationCodeType.Generic,
|
||||
payload: {
|
||||
code: '000000',
|
||||
},
|
||||
},
|
||||
{ test: 123 }
|
||||
);
|
||||
expect(response).toHaveProperty('statusCode', 204);
|
||||
});
|
||||
|
||||
it('should throw when neither phone nor email is provided', async () => {
|
||||
const response = await connectorRequest.post('/connectors/id/test').send({});
|
||||
expect(response).toHaveProperty('statusCode', 400);
|
||||
});
|
||||
|
||||
it('should throw when sms connector is not found', async () => {
|
||||
getLogtoConnectors.mockResolvedValueOnce([]);
|
||||
const response = await connectorRequest
|
||||
.post('/connectors/id/test')
|
||||
.send({ phone: '12345678901' });
|
||||
expect(response).toHaveProperty('statusCode', 400);
|
||||
});
|
||||
|
||||
it('should throw when email connector is not found', async () => {
|
||||
getLogtoConnectors.mockResolvedValueOnce([]);
|
||||
const response = await connectorRequest
|
||||
.post('/connectors/id/test')
|
||||
.send({ email: 'test@email.com' });
|
||||
expect(response).toHaveProperty('statusCode', 400);
|
||||
});
|
||||
});
|
||||
|
||||
describe('DELETE /connectors/:id', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('delete connector instance and remove unavailable social connector targets', async () => {
|
||||
findConnectorById.mockResolvedValueOnce(mockConnector);
|
||||
loadConnectorFactories.mockResolvedValueOnce([mockConnectorFactory]);
|
||||
await connectorRequest.delete('/connectors/id').send({});
|
||||
expect(deleteConnectorById).toHaveBeenCalledTimes(1);
|
||||
expect(removeUnavailableSocialConnectorTargets).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('delete connector instance (connector factory is not social type)', async () => {
|
||||
findConnectorById.mockResolvedValueOnce(mockConnector);
|
||||
loadConnectorFactories.mockResolvedValueOnce([
|
||||
{ ...mockConnectorFactory, type: ConnectorType.Sms },
|
||||
]);
|
||||
await connectorRequest.delete('/connectors/id').send({});
|
||||
expect(deleteConnectorById).toHaveBeenCalledTimes(1);
|
||||
expect(removeUnavailableSocialConnectorTargets).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('delete connector instance (connector factory is not found)', async () => {
|
||||
findConnectorById.mockResolvedValueOnce(mockConnector);
|
||||
loadConnectorFactories.mockResolvedValueOnce([]);
|
||||
await connectorRequest.delete('/connectors/id').send({});
|
||||
expect(deleteConnectorById).toHaveBeenCalledTimes(1);
|
||||
expect(removeUnavailableSocialConnectorTargets).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('throws when connector not exists with `id`', async () => {
|
||||
// eslint-disable-next-line unicorn/no-useless-undefined
|
||||
findConnectorById.mockResolvedValueOnce(undefined);
|
||||
const response = await connectorRequest.delete('/connectors/id').send({});
|
||||
expect(response).toHaveProperty('statusCode', 500);
|
||||
});
|
||||
});
|
||||
});
|
||||
/* eslint-enable max-lines */
|
|
@ -1,15 +1,6 @@
|
|||
/* eslint-disable max-lines */
|
||||
import { buildRawConnector } from '@logto/cli/lib/connector/index.js';
|
||||
import type { ConnectorFactory } from '@logto/cli/lib/connector/index.js';
|
||||
import {
|
||||
type SmsConnector,
|
||||
type EmailConnector,
|
||||
demoConnectorIds,
|
||||
VerificationCodeType,
|
||||
validateConfig,
|
||||
} from '@logto/connector-kit';
|
||||
import { phoneRegEx, emailRegEx } from '@logto/core-kit';
|
||||
import { arbitraryObjectGuard, Connectors, ConnectorType } from '@logto/schemas';
|
||||
import { demoConnectorIds, validateConfig } from '@logto/connector-kit';
|
||||
import { Connectors, ConnectorType } from '@logto/schemas';
|
||||
import { buildIdGenerator } from '@logto/shared';
|
||||
import cleanDeep from 'clean-deep';
|
||||
import { string, object } from 'zod';
|
||||
|
@ -24,12 +15,14 @@ import {
|
|||
} from '#src/utils/connectors/index.js';
|
||||
import { checkSocialConnectorTargetAndPlatformUniqueness } from '#src/utils/connectors/platform.js';
|
||||
|
||||
import type { AuthedRouter, RouterInitArgs } from './types.js';
|
||||
import type { AuthedRouter, RouterInitArgs } from '../types.js';
|
||||
|
||||
import connectorConfigTestingRoutes from './config-testing.js';
|
||||
|
||||
const generateConnectorId = buildIdGenerator(12);
|
||||
|
||||
export default function connectorRoutes<T extends AuthedRouter>(
|
||||
...[router, { queries, connectors, libraries }]: RouterInitArgs<T>
|
||||
...[router, tenant]: RouterInitArgs<T>
|
||||
) {
|
||||
const {
|
||||
findConnectorById,
|
||||
|
@ -38,88 +31,11 @@ export default function connectorRoutes<T extends AuthedRouter>(
|
|||
deleteConnectorByIds,
|
||||
insertConnector,
|
||||
updateConnector,
|
||||
} = queries.connectors;
|
||||
const { getLogtoConnectorById, getLogtoConnectors } = connectors;
|
||||
} = tenant.queries.connectors;
|
||||
const { getLogtoConnectorById, getLogtoConnectors } = tenant.connectors;
|
||||
const {
|
||||
signInExperiences: { removeUnavailableSocialConnectorTargets },
|
||||
} = libraries;
|
||||
|
||||
router.get(
|
||||
'/connectors',
|
||||
koaGuard({
|
||||
query: object({
|
||||
target: string().optional(),
|
||||
}),
|
||||
}),
|
||||
async (ctx, next) => {
|
||||
const { target: filterTarget } = ctx.query;
|
||||
const connectors = await getLogtoConnectors();
|
||||
|
||||
checkSocialConnectorTargetAndPlatformUniqueness(connectors);
|
||||
|
||||
assertThat(
|
||||
connectors.filter((connector) => connector.type === ConnectorType.Email).length <= 1,
|
||||
'connector.more_than_one_email'
|
||||
);
|
||||
assertThat(
|
||||
connectors.filter((connector) => connector.type === ConnectorType.Sms).length <= 1,
|
||||
'connector.more_than_one_sms'
|
||||
);
|
||||
|
||||
const filteredConnectors = filterTarget
|
||||
? connectors.filter(({ metadata: { target } }) => target === filterTarget)
|
||||
: connectors;
|
||||
|
||||
ctx.body = filteredConnectors.map((connector) => transpileLogtoConnector(connector));
|
||||
|
||||
return next();
|
||||
}
|
||||
);
|
||||
|
||||
router.get('/connector-factories', async (ctx, next) => {
|
||||
const connectorFactories = await loadConnectorFactories();
|
||||
ctx.body = connectorFactories.map((connectorFactory) =>
|
||||
transpileConnectorFactory(connectorFactory)
|
||||
);
|
||||
|
||||
return next();
|
||||
});
|
||||
|
||||
router.get(
|
||||
'/connector-factories/:id',
|
||||
koaGuard({ params: object({ id: string().min(1) }) }),
|
||||
async (ctx, next) => {
|
||||
const {
|
||||
params: { id },
|
||||
} = ctx.guard;
|
||||
const connectorFactories = await loadConnectorFactories();
|
||||
|
||||
const connectorFactory = connectorFactories.find((factory) => factory.metadata.id === id);
|
||||
assertThat(connectorFactory, 'entity.not_found');
|
||||
|
||||
ctx.body = transpileConnectorFactory(connectorFactory);
|
||||
|
||||
return next();
|
||||
}
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/connectors/:id',
|
||||
koaGuard({ params: object({ id: string().min(1) }) }),
|
||||
async (ctx, next) => {
|
||||
const {
|
||||
params: { id },
|
||||
} = ctx.guard;
|
||||
const connector = await getLogtoConnectorById(id);
|
||||
|
||||
// Hide demo connector
|
||||
assertThat(!demoConnectorIds.includes(connector.metadata.id), 'connector.not_found');
|
||||
|
||||
ctx.body = transpileLogtoConnector(connector);
|
||||
|
||||
return next();
|
||||
}
|
||||
);
|
||||
} = tenant.libraries;
|
||||
|
||||
router.post(
|
||||
'/connectors',
|
||||
|
@ -234,6 +150,89 @@ export default function connectorRoutes<T extends AuthedRouter>(
|
|||
}
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/connectors',
|
||||
koaGuard({
|
||||
query: object({
|
||||
target: string().optional(),
|
||||
}),
|
||||
}),
|
||||
async (ctx, next) => {
|
||||
const { target: filterTarget } = ctx.query;
|
||||
const connectors = await getLogtoConnectors();
|
||||
|
||||
checkSocialConnectorTargetAndPlatformUniqueness(connectors);
|
||||
|
||||
assertThat(
|
||||
connectors.filter((connector) => connector.type === ConnectorType.Email).length <= 1,
|
||||
'connector.more_than_one_email'
|
||||
);
|
||||
assertThat(
|
||||
connectors.filter((connector) => connector.type === ConnectorType.Sms).length <= 1,
|
||||
'connector.more_than_one_sms'
|
||||
);
|
||||
|
||||
const filteredConnectors = filterTarget
|
||||
? connectors.filter(({ metadata: { target } }) => target === filterTarget)
|
||||
: connectors;
|
||||
|
||||
ctx.body = filteredConnectors.map((connector) => transpileLogtoConnector(connector));
|
||||
|
||||
return next();
|
||||
}
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/connectors/:id',
|
||||
koaGuard({ params: object({ id: string().min(1) }) }),
|
||||
async (ctx, next) => {
|
||||
const {
|
||||
params: { id },
|
||||
} = ctx.guard;
|
||||
const connector = await getLogtoConnectorById(id);
|
||||
|
||||
// Hide demo connector
|
||||
assertThat(!demoConnectorIds.includes(connector.metadata.id), 'connector.not_found');
|
||||
|
||||
ctx.body = transpileLogtoConnector(connector);
|
||||
|
||||
return next();
|
||||
}
|
||||
);
|
||||
|
||||
router.get('/connector-factories', async (ctx, next) => {
|
||||
const connectorFactories = await loadConnectorFactories();
|
||||
ctx.body = connectorFactories.map((connectorFactory) =>
|
||||
transpileConnectorFactory(connectorFactory)
|
||||
);
|
||||
|
||||
return next();
|
||||
});
|
||||
|
||||
router.get(
|
||||
'/connector-factories/:id',
|
||||
koaGuard({ params: object({ id: string().min(1) }) }),
|
||||
async (ctx, next) => {
|
||||
const {
|
||||
params: { id },
|
||||
} = ctx.guard;
|
||||
const connectorFactories = await loadConnectorFactories();
|
||||
|
||||
const connectorFactory = connectorFactories.find((factory) => factory.metadata.id === id);
|
||||
assertThat(
|
||||
connectorFactory,
|
||||
new RequestError({
|
||||
code: 'entity.not_found',
|
||||
status: 404,
|
||||
})
|
||||
);
|
||||
|
||||
ctx.body = transpileConnectorFactory(connectorFactory);
|
||||
|
||||
return next();
|
||||
}
|
||||
);
|
||||
|
||||
router.patch(
|
||||
'/connectors/:id',
|
||||
koaGuard({
|
||||
|
@ -288,67 +287,6 @@ export default function connectorRoutes<T extends AuthedRouter>(
|
|||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/connectors/:factoryId/test',
|
||||
koaGuard({
|
||||
params: object({ factoryId: string().min(1) }),
|
||||
body: object({
|
||||
phone: string().regex(phoneRegEx).optional(),
|
||||
email: string().regex(emailRegEx).optional(),
|
||||
config: arbitraryObjectGuard,
|
||||
}),
|
||||
}),
|
||||
async (ctx, next) => {
|
||||
const {
|
||||
params: { factoryId },
|
||||
body,
|
||||
} = ctx.guard;
|
||||
const { phone, email, config } = body;
|
||||
|
||||
const subject = phone ?? email;
|
||||
assertThat(subject, new RequestError({ code: 'guard.invalid_input' }));
|
||||
|
||||
const connectorFactories = await loadConnectorFactories();
|
||||
const connectorFactory = connectorFactories
|
||||
.filter(
|
||||
(factory): factory is ConnectorFactory<SmsConnector> | ConnectorFactory<EmailConnector> =>
|
||||
factory.type === ConnectorType.Email || factory.type === ConnectorType.Sms
|
||||
)
|
||||
.find(({ metadata: { id } }) => id === factoryId && !demoConnectorIds.includes(id));
|
||||
const expectType = phone ? ConnectorType.Sms : ConnectorType.Email;
|
||||
|
||||
assertThat(
|
||||
connectorFactory,
|
||||
new RequestError({
|
||||
code: 'connector.not_found',
|
||||
type: expectType,
|
||||
factoryId,
|
||||
})
|
||||
);
|
||||
|
||||
assertThat(connectorFactory.type === expectType, 'connector.unexpected_type');
|
||||
|
||||
const {
|
||||
rawConnector: { sendMessage },
|
||||
} = await buildRawConnector<SmsConnector | EmailConnector>(connectorFactory);
|
||||
|
||||
await sendMessage(
|
||||
{
|
||||
to: subject,
|
||||
type: VerificationCodeType.Generic,
|
||||
payload: {
|
||||
code: '000000',
|
||||
},
|
||||
},
|
||||
config
|
||||
);
|
||||
|
||||
ctx.status = 204;
|
||||
|
||||
return next();
|
||||
}
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/connectors/:id',
|
||||
koaGuard({ params: object({ id: string().min(1) }) }),
|
||||
|
@ -375,5 +313,6 @@ export default function connectorRoutes<T extends AuthedRouter>(
|
|||
return next();
|
||||
}
|
||||
);
|
||||
|
||||
connectorConfigTestingRoutes(router, tenant);
|
||||
}
|
||||
/* eslint-enable max-lines */
|
|
@ -13,7 +13,7 @@ import adminUserRoleRoutes from './admin-user-role.js';
|
|||
import adminUserRoutes from './admin-user.js';
|
||||
import applicationRoutes from './application.js';
|
||||
import authnRoutes from './authn.js';
|
||||
import connectorRoutes from './connector.js';
|
||||
import connectorRoutes from './connector/index.js';
|
||||
import customPhraseRoutes from './custom-phrase.js';
|
||||
import dashboardRoutes from './dashboard.js';
|
||||
import hookRoutes from './hook.js';
|
||||
|
|
Loading…
Add table
Reference in a new issue