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:
commit
f60a6a0f47
5 changed files with 78 additions and 97 deletions
|
@ -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();
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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')}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in a new issue