0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-30 20:33:54 -05:00

Merge pull request #1219 from logto-io/charles-log-3188-refactor-email-sms-connector-test-api

refactor: email/sms connector test API
This commit is contained in:
Charles Zhao 2022-06-23 18:18:18 +08:00 committed by GitHub
commit f60a6a0f47
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 78 additions and 97 deletions

View file

@ -14,6 +14,7 @@ import useApi from '@/hooks/use-api';
import * as styles from './index.module.scss'; import * as styles from './index.module.scss';
type Props = { type Props = {
connectorId: string;
connectorType: Exclude<ConnectorType, ConnectorType.Social>; connectorType: Exclude<ConnectorType, ConnectorType.Social>;
config?: string; config?: string;
className?: string; className?: string;
@ -23,7 +24,7 @@ type FormData = {
sendTo: string; sendTo: string;
}; };
const SenderTester = ({ connectorType, config, className }: Props) => { const SenderTester = ({ connectorId, connectorType, config, className }: Props) => {
const buttonPosReference = useRef(null); const buttonPosReference = useRef(null);
const [showTooltip, setShowTooltip] = useState(false); const [showTooltip, setShowTooltip] = useState(false);
const [isSubmitting, seIsSubmitting] = useState(false); const [isSubmitting, seIsSubmitting] = useState(false);
@ -62,7 +63,7 @@ const SenderTester = ({ connectorType, config, className }: Props) => {
try { try {
await api await api
.post(`/api/connectors/test/${connectorType.toLowerCase()}`, { .post(`/api/connectors/${connectorId}/test`, {
json: data, json: data,
}) })
.json(); .json();

View file

@ -213,7 +213,7 @@ const ConnectorDetails = () => {
/> />
</FormField> </FormField>
{data.type !== ConnectorType.Social && ( {data.type !== ConnectorType.Social && (
<SenderTester connectorType={data.type} config={config} /> <SenderTester connectorId={data.id} connectorType={data.type} config={config} />
)} )}
{saveError && <div>{saveError}</div>} {saveError && <div>{saveError}</div>}
</div> </div>

View file

@ -120,6 +120,7 @@ const Guide = ({ connector, onClose }: Props) => {
{!isSocialConnector && ( {!isSocialConnector && (
<SenderTester <SenderTester
className={styles.tester} className={styles.tester}
connectorId={connectorId}
connectorType={connectorType} connectorType={connectorType}
config={watch('connectorConfigJson')} config={watch('connectorConfigJson')}
/> />

View file

@ -101,51 +101,7 @@ describe('connector route', () => {
}); });
}); });
describe('POST /connectors/test/email', () => { describe('POST /connectors/:id/test', () => {
afterEach(() => {
jest.clearAllMocks();
});
it('should get email connector and send message', async () => {
const mockedEmailConnector: EmailConnectorInstance = {
connector: mockConnector,
metadata: mockMetadata,
validateConfig: jest.fn(),
getConfig: jest.fn(),
sendMessage: async (
address: string,
type: keyof EmailMessageTypes,
_payload: EmailMessageTypes[typeof type]
// eslint-disable-next-line @typescript-eslint/no-empty-function
): Promise<any> => {},
};
getConnectorInstancesPlaceHolder.mockResolvedValueOnce([mockedEmailConnector]);
const sendMessageSpy = jest.spyOn(mockedEmailConnector, 'sendMessage');
const response = await connectorRequest
.post('/connectors/test/email')
.send({ email: 'test@email.com', config: { test: 123 } });
expect(sendMessageSpy).toHaveBeenCalledTimes(1);
expect(sendMessageSpy).toHaveBeenCalledWith(
'test@email.com',
'Test',
{
code: 'email-test',
},
{ test: 123 }
);
expect(response).toHaveProperty('statusCode', 204);
});
it('should throw when email connector is not found', async () => {
getConnectorInstancesPlaceHolder.mockResolvedValueOnce([]);
const response = await connectorRequest
.post('/connectors/test/email')
.send({ email: 'test@email.com' });
expect(response).toHaveProperty('statusCode', 400);
});
});
describe('POST /connectors/test/sms', () => {
afterEach(() => { afterEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
}); });
@ -170,7 +126,7 @@ describe('connector route', () => {
getConnectorInstancesPlaceHolder.mockResolvedValueOnce([mockedSmsConnectorInstance]); getConnectorInstancesPlaceHolder.mockResolvedValueOnce([mockedSmsConnectorInstance]);
const sendMessageSpy = jest.spyOn(mockedSmsConnectorInstance, 'sendMessage'); const sendMessageSpy = jest.spyOn(mockedSmsConnectorInstance, 'sendMessage');
const response = await connectorRequest const response = await connectorRequest
.post('/connectors/test/sms') .post('/connectors/id/test')
.send({ phone: '12345678901', config: { test: 123 } }); .send({ phone: '12345678901', config: { test: 123 } });
expect(sendMessageSpy).toHaveBeenCalledTimes(1); expect(sendMessageSpy).toHaveBeenCalledTimes(1);
expect(sendMessageSpy).toHaveBeenCalledWith( expect(sendMessageSpy).toHaveBeenCalledWith(
@ -184,12 +140,55 @@ describe('connector route', () => {
expect(response).toHaveProperty('statusCode', 204); expect(response).toHaveProperty('statusCode', 204);
}); });
it('should get email connector and send message', async () => {
const mockedEmailConnector: EmailConnectorInstance = {
connector: mockConnector,
metadata: mockMetadata,
validateConfig: jest.fn(),
getConfig: jest.fn(),
sendMessage: async (
address: string,
type: keyof EmailMessageTypes,
_payload: EmailMessageTypes[typeof type]
// eslint-disable-next-line @typescript-eslint/no-empty-function
): Promise<any> => {},
};
getConnectorInstancesPlaceHolder.mockResolvedValueOnce([mockedEmailConnector]);
const sendMessageSpy = jest.spyOn(mockedEmailConnector, 'sendMessage');
const response = await connectorRequest
.post('/connectors/id/test')
.send({ email: 'test@email.com', config: { test: 123 } });
expect(sendMessageSpy).toHaveBeenCalledTimes(1);
expect(sendMessageSpy).toHaveBeenCalledWith(
'test@email.com',
'Test',
{
code: 'email-test',
},
{ 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 () => { it('should throw when sms connector is not found', async () => {
getConnectorInstancesPlaceHolder.mockResolvedValueOnce([]); getConnectorInstancesPlaceHolder.mockResolvedValueOnce([]);
const response = await connectorRequest const response = await connectorRequest
.post('/connectors/test/sms') .post('/connectors/id/test')
.send({ phone: '12345678901' }); .send({ phone: '12345678901' });
expect(response).toHaveProperty('statusCode', 400); expect(response).toHaveProperty('statusCode', 400);
}); });
it('should throw when email connector is not found', async () => {
getConnectorInstancesPlaceHolder.mockResolvedValueOnce([]);
const response = await connectorRequest
.post('/connectors/id/test')
.send({ email: 'test@email.com' });
expect(response).toHaveProperty('statusCode', 400);
});
}); });
}); });

View file

@ -149,25 +149,42 @@ export default function connectorRoutes<T extends AuthedRouter>(router: T) {
); );
router.post( router.post(
'/connectors/test/email', '/connectors/:id/test',
koaGuard({ koaGuard({
params: object({ id: string().min(1) }),
body: object({ body: object({
email: string().regex(emailRegEx), phone: string().regex(phoneRegEx).optional(),
email: string().regex(emailRegEx).optional(),
config: arbitraryObjectGuard.optional(), config: arbitraryObjectGuard.optional(),
}), }),
}), }),
async (ctx, next) => { async (ctx, next) => {
const { email, config } = ctx.guard.body; const {
params: { id },
body,
} = ctx.guard;
const { phone, email, config } = body;
const connectorInstances = await getConnectorInstances(); const connectorInstances = await getConnectorInstances();
const connector = connectorInstances.find( const subject = phone ?? email;
(connector): connector is EmailConnectorInstance => assertThat(subject, new RequestError({ code: 'guard.invalid_input' }));
connector.metadata.type === ConnectorType.Email
); const connector: SmsConnectorInstance | EmailConnectorInstance | undefined = phone
? connectorInstances.find(
(connector): connector is SmsConnectorInstance =>
connector.metadata.id === id && connector.metadata.type === ConnectorType.SMS
)
: connectorInstances.find(
(connector): connector is EmailConnectorInstance =>
connector.metadata.id === id && connector.metadata.type === ConnectorType.Email
);
assertThat( assertThat(
connector, connector,
new RequestError({ code: 'connector.not_found', type: ConnectorType.Email }) new RequestError({
code: 'connector.not_found',
type: phone ? ConnectorType.SMS : ConnectorType.Email,
})
); );
if (config) { if (config) {
@ -175,47 +192,10 @@ export default function connectorRoutes<T extends AuthedRouter>(router: T) {
} }
await connector.sendMessage( await connector.sendMessage(
email, subject,
'Test', 'Test',
{ {
code: 'email-test', code: phone ? '123456' : 'email-test',
},
config
);
ctx.status = 204;
return next();
}
);
router.post(
'/connectors/test/sms',
koaGuard({
body: object({
phone: string().regex(phoneRegEx),
config: arbitraryObjectGuard.optional(),
}),
}),
async (ctx, next) => {
const { phone, config } = ctx.guard.body;
const connectorInstances = await getConnectorInstances();
const connector = connectorInstances.find(
(connector): connector is SmsConnectorInstance =>
connector.metadata.type === ConnectorType.SMS
);
assertThat(
connector,
new RequestError({ code: 'connector.not_found', type: ConnectorType.SMS })
);
await connector.sendMessage(
phone,
'Test',
{
code: '123456',
}, },
config config
); );