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:
parent
260f39f72d
commit
5875d4cb3b
3 changed files with 79 additions and 22 deletions
|
@ -1,6 +1,11 @@
|
||||||
import { buildRawConnector } from '@logto/cli/lib/connector/index.js';
|
import { buildRawConnector } from '@logto/cli/lib/connector/index.js';
|
||||||
import { demoConnectorIds, validateConfig } from '@logto/connector-kit';
|
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 { buildIdGenerator } from '@logto/shared';
|
||||||
import cleanDeep from 'clean-deep';
|
import cleanDeep from 'clean-deep';
|
||||||
import { string, object } from 'zod';
|
import { string, object } from 'zod';
|
||||||
|
@ -48,6 +53,8 @@ export default function connectorRoutes<T extends AuthedRouter>(
|
||||||
syncProfile: true,
|
syncProfile: true,
|
||||||
})
|
})
|
||||||
.merge(Connectors.createGuard.pick({ id: true }).partial()), // `id` is optional
|
.merge(Connectors.createGuard.pick({ id: true }).partial()), // `id` is optional
|
||||||
|
response: connectorResponseGuard,
|
||||||
|
status: [200, 422],
|
||||||
}),
|
}),
|
||||||
async (ctx, next) => {
|
async (ctx, next) => {
|
||||||
const {
|
const {
|
||||||
|
@ -156,6 +163,8 @@ export default function connectorRoutes<T extends AuthedRouter>(
|
||||||
query: object({
|
query: object({
|
||||||
target: string().optional(),
|
target: string().optional(),
|
||||||
}),
|
}),
|
||||||
|
response: connectorResponseGuard.array(),
|
||||||
|
status: [200, 400],
|
||||||
}),
|
}),
|
||||||
async (ctx, next) => {
|
async (ctx, next) => {
|
||||||
const { target: filterTarget } = ctx.query;
|
const { target: filterTarget } = ctx.query;
|
||||||
|
@ -184,7 +193,11 @@ export default function connectorRoutes<T extends AuthedRouter>(
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/connectors/:id',
|
'/connectors/:id',
|
||||||
koaGuard({ params: object({ id: string().min(1) }) }),
|
koaGuard({
|
||||||
|
params: object({ id: string().min(1) }),
|
||||||
|
response: connectorResponseGuard,
|
||||||
|
status: [200, 404],
|
||||||
|
}),
|
||||||
async (ctx, next) => {
|
async (ctx, next) => {
|
||||||
const {
|
const {
|
||||||
params: { id },
|
params: { id },
|
||||||
|
@ -221,7 +234,7 @@ export default function connectorRoutes<T extends AuthedRouter>(
|
||||||
koaGuard({
|
koaGuard({
|
||||||
params: object({ id: string().min(1) }),
|
params: object({ id: string().min(1) }),
|
||||||
response: connectorFactoryResponseGuard,
|
response: connectorFactoryResponseGuard,
|
||||||
status: [200],
|
status: [200, 404],
|
||||||
}),
|
}),
|
||||||
async (ctx, next) => {
|
async (ctx, next) => {
|
||||||
const {
|
const {
|
||||||
|
@ -251,6 +264,8 @@ export default function connectorRoutes<T extends AuthedRouter>(
|
||||||
body: Connectors.createGuard
|
body: Connectors.createGuard
|
||||||
.pick({ config: true, metadata: true, syncProfile: true })
|
.pick({ config: true, metadata: true, syncProfile: true })
|
||||||
.partial(),
|
.partial(),
|
||||||
|
response: connectorResponseGuard,
|
||||||
|
status: [200, 400, 404, 422],
|
||||||
}),
|
}),
|
||||||
async (ctx, next) => {
|
async (ctx, next) => {
|
||||||
const {
|
const {
|
||||||
|
@ -300,7 +315,7 @@ export default function connectorRoutes<T extends AuthedRouter>(
|
||||||
|
|
||||||
router.delete(
|
router.delete(
|
||||||
'/connectors/:id',
|
'/connectors/:id',
|
||||||
koaGuard({ params: object({ id: string().min(1) }) }),
|
koaGuard({ params: object({ id: string().min(1) }), status: [204, 404] }),
|
||||||
async (ctx, next) => {
|
async (ctx, next) => {
|
||||||
const {
|
const {
|
||||||
params: { id },
|
params: { id },
|
||||||
|
|
|
@ -21,6 +21,7 @@ import {
|
||||||
listConnectorFactories,
|
listConnectorFactories,
|
||||||
getConnectorFactory,
|
getConnectorFactory,
|
||||||
} from '#src/api/connector.js';
|
} from '#src/api/connector.js';
|
||||||
|
import { createResponseWithCode } from '#src/helpers/admin-tenant.js';
|
||||||
|
|
||||||
const connectorIdMap = new Map<string, string>();
|
const connectorIdMap = new Map<string, string>();
|
||||||
|
|
||||||
|
@ -30,6 +31,16 @@ const mockConnectorSetups = [
|
||||||
{ connectorId: mockSocialConnectorId, config: mockSocialConnectorConfig },
|
{ 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.
|
* We'd better only use mock connectors in integration tests.
|
||||||
* Since we will refactor connectors soon, keep using some real connectors
|
* Since we will refactor connectors soon, keep using some real connectors
|
||||||
|
@ -52,16 +63,7 @@ test('connector set-up flow', async () => {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
await cleanUpConnectorTable();
|
||||||
* Clean up `connector` table
|
|
||||||
*/
|
|
||||||
const connectors = await listConnectors();
|
|
||||||
await Promise.all(
|
|
||||||
connectors.map(async ({ id }) => {
|
|
||||||
await deleteConnectorById(id);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
connectorIdMap.clear();
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Set up social/SMS/email connectors
|
* 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 () => {
|
test('send SMS/email test message', async () => {
|
||||||
const connectors = await listConnectors();
|
const connectors = await listConnectors();
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
|
|
|
@ -1,18 +1,28 @@
|
||||||
import type { BaseConnector, ConnectorMetadata } from '@logto/connector-kit';
|
|
||||||
import { ConnectorType, connectorMetadataGuard } from '@logto/connector-kit';
|
import { ConnectorType, connectorMetadataGuard } from '@logto/connector-kit';
|
||||||
import { z } from 'zod';
|
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 type { ConnectorMetadata } from '@logto/connector-kit';
|
||||||
export { ConnectorType, ConnectorPlatform } from '@logto/connector-kit';
|
export { ConnectorType, ConnectorPlatform } from '@logto/connector-kit';
|
||||||
|
|
||||||
export type ConnectorResponse = Pick<
|
export const connectorResponseGuard = Connectors.guard
|
||||||
Connector,
|
.pick({
|
||||||
'id' | 'syncProfile' | 'config' | 'metadata' | 'connectorId'
|
id: true,
|
||||||
> &
|
syncProfile: true,
|
||||||
Omit<BaseConnector<ConnectorType>, 'configGuard' | 'metadata'> &
|
config: true,
|
||||||
ConnectorMetadata & { isDemo?: boolean };
|
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
|
export const connectorFactoryResponseGuard = z
|
||||||
.object({
|
.object({
|
||||||
|
|
Loading…
Reference in a new issue