From 5875d4cb3b250e03ca10d5d5dfd3668baa375773 Mon Sep 17 00:00:00 2001 From: wangsijie Date: Fri, 5 May 2023 21:20:58 +0800 Subject: [PATCH] test(core): add api response guard and error case tests to connector api (#3806) --- packages/core/src/routes/connector/index.ts | 23 ++++++-- .../src/tests/api/connector.test.ts | 52 +++++++++++++++---- packages/schemas/src/types/connector.ts | 26 +++++++--- 3 files changed, 79 insertions(+), 22 deletions(-) diff --git a/packages/core/src/routes/connector/index.ts b/packages/core/src/routes/connector/index.ts index 0ec6b65c1..e7c55dd66 100644 --- a/packages/core/src/routes/connector/index.ts +++ b/packages/core/src/routes/connector/index.ts @@ -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( 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( 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( 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( 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( 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( 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 }, diff --git a/packages/integration-tests/src/tests/api/connector.test.ts b/packages/integration-tests/src/tests/api/connector.test.ts index 646ac105c..a37b7d4cd 100644 --- a/packages/integration-tests/src/tests/api/connector.test.ts +++ b/packages/integration-tests/src/tests/api/connector.test.ts @@ -21,6 +21,7 @@ import { listConnectorFactories, getConnectorFactory, } from '#src/api/connector.js'; +import { createResponseWithCode } from '#src/helpers/admin-tenant.js'; const connectorIdMap = new Map(); @@ -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( diff --git a/packages/schemas/src/types/connector.ts b/packages/schemas/src/types/connector.ts index aa4b6624d..46bdf4f2e 100644 --- a/packages/schemas/src/types/connector.ts +++ b/packages/schemas/src/types/connector.ts @@ -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, '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; export const connectorFactoryResponseGuard = z .object({