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

test(core): add api response guard and error case tests to connector api (#3806)

This commit is contained in:
wangsijie 2023-05-05 21:20:58 +08:00 committed by GitHub
parent 260f39f72d
commit 5875d4cb3b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 79 additions and 22 deletions

View file

@ -1,6 +1,11 @@
import { buildRawConnector } from '@logto/cli/lib/connector/index.js';
import { demoConnectorIds, validateConfig } from '@logto/connector-kit';
import { connectorFactoryResponseGuard, Connectors, ConnectorType } from '@logto/schemas';
import {
connectorFactoryResponseGuard,
Connectors,
ConnectorType,
connectorResponseGuard,
} from '@logto/schemas';
import { buildIdGenerator } from '@logto/shared';
import cleanDeep from 'clean-deep';
import { string, object } from 'zod';
@ -48,6 +53,8 @@ export default function connectorRoutes<T extends AuthedRouter>(
syncProfile: true,
})
.merge(Connectors.createGuard.pick({ id: true }).partial()), // `id` is optional
response: connectorResponseGuard,
status: [200, 422],
}),
async (ctx, next) => {
const {
@ -156,6 +163,8 @@ export default function connectorRoutes<T extends AuthedRouter>(
query: object({
target: string().optional(),
}),
response: connectorResponseGuard.array(),
status: [200, 400],
}),
async (ctx, next) => {
const { target: filterTarget } = ctx.query;
@ -184,7 +193,11 @@ export default function connectorRoutes<T extends AuthedRouter>(
router.get(
'/connectors/:id',
koaGuard({ params: object({ id: string().min(1) }) }),
koaGuard({
params: object({ id: string().min(1) }),
response: connectorResponseGuard,
status: [200, 404],
}),
async (ctx, next) => {
const {
params: { id },
@ -221,7 +234,7 @@ export default function connectorRoutes<T extends AuthedRouter>(
koaGuard({
params: object({ id: string().min(1) }),
response: connectorFactoryResponseGuard,
status: [200],
status: [200, 404],
}),
async (ctx, next) => {
const {
@ -251,6 +264,8 @@ export default function connectorRoutes<T extends AuthedRouter>(
body: Connectors.createGuard
.pick({ config: true, metadata: true, syncProfile: true })
.partial(),
response: connectorResponseGuard,
status: [200, 400, 404, 422],
}),
async (ctx, next) => {
const {
@ -300,7 +315,7 @@ export default function connectorRoutes<T extends AuthedRouter>(
router.delete(
'/connectors/:id',
koaGuard({ params: object({ id: string().min(1) }) }),
koaGuard({ params: object({ id: string().min(1) }), status: [204, 404] }),
async (ctx, next) => {
const {
params: { id },

View file

@ -21,6 +21,7 @@ import {
listConnectorFactories,
getConnectorFactory,
} from '#src/api/connector.js';
import { createResponseWithCode } from '#src/helpers/admin-tenant.js';
const connectorIdMap = new Map<string, string>();
@ -30,6 +31,16 @@ const mockConnectorSetups = [
{ connectorId: mockSocialConnectorId, config: mockSocialConnectorConfig },
];
const cleanUpConnectorTable = async () => {
const connectors = await listConnectors();
await Promise.all(
connectors.map(async ({ id }) => {
await deleteConnectorById(id);
})
);
connectorIdMap.clear();
};
/*
* We'd better only use mock connectors in integration tests.
* Since we will refactor connectors soon, keep using some real connectors
@ -52,16 +63,7 @@ test('connector set-up flow', async () => {
})
);
/**
* Clean up `connector` table
*/
const connectors = await listConnectors();
await Promise.all(
connectors.map(async ({ id }) => {
await deleteConnectorById(id);
})
);
connectorIdMap.clear();
await cleanUpConnectorTable();
/*
* Set up social/SMS/email connectors
@ -141,6 +143,36 @@ test('connector set-up flow', async () => {
);
});
test('create connector with non-exist connectorId', async () => {
await cleanUpConnectorTable();
await expect(postConnector({ connectorId: 'non-exist-id' })).rejects.toMatchObject(
createResponseWithCode(422)
);
});
test('create non standard social connector with target', async () => {
await cleanUpConnectorTable();
await expect(
postConnector({ connectorId: mockSocialConnectorId, metadata: { target: 'target' } })
).rejects.toMatchObject(createResponseWithCode(400));
});
test('create duplicated social connector', async () => {
await cleanUpConnectorTable();
await postConnector({ connectorId: mockSocialConnectorId });
await expect(postConnector({ connectorId: mockSocialConnectorId })).rejects.toMatchObject(
createResponseWithCode(422)
);
});
test('override metadata for non-standard social connector', async () => {
await cleanUpConnectorTable();
const { id } = await postConnector({ connectorId: mockSocialConnectorId });
await expect(updateConnectorConfig(id, {}, { target: 'target' })).rejects.toMatchObject(
createResponseWithCode(400)
);
});
test('send SMS/email test message', async () => {
const connectors = await listConnectors();
await Promise.all(

View file

@ -1,18 +1,28 @@
import type { BaseConnector, ConnectorMetadata } from '@logto/connector-kit';
import { ConnectorType, connectorMetadataGuard } from '@logto/connector-kit';
import { z } from 'zod';
import type { Connector } from '../db-entries/index.js';
import { Connectors } from '../db-entries/index.js';
export type { ConnectorMetadata } from '@logto/connector-kit';
export { ConnectorType, ConnectorPlatform } from '@logto/connector-kit';
export type ConnectorResponse = Pick<
Connector,
'id' | 'syncProfile' | 'config' | 'metadata' | 'connectorId'
> &
Omit<BaseConnector<ConnectorType>, 'configGuard' | 'metadata'> &
ConnectorMetadata & { isDemo?: boolean };
export const connectorResponseGuard = Connectors.guard
.pick({
id: true,
syncProfile: true,
config: true,
metadata: true,
connectorId: true,
})
.merge(connectorMetadataGuard)
.merge(
z.object({
type: z.nativeEnum(ConnectorType),
isDemo: z.boolean().optional(),
})
);
export type ConnectorResponse = z.infer<typeof connectorResponseGuard>;
export const connectorFactoryResponseGuard = z
.object({