0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

fix(core,connector): fix patch connector api cannot reset config/metadata bug (#4166)

This commit is contained in:
Darcy Ye 2023-07-14 17:38:56 +08:00 committed by GitHub
parent 305f1409fd
commit e441c089d7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 67 additions and 16 deletions

View file

@ -26,7 +26,7 @@ const sendMessage =
const config = inputConfig ?? (await getConfig(defaultMetadata.id));
validateConfig<MockMailConfig>(config, mockMailConfigGuard);
const { templates } = config;
const template = templates.find((template) => template.usageType === type);
const template = templates?.find((template) => template.usageType === type);
assert(
template,

View file

@ -12,11 +12,13 @@ const templateGuard = z.object({
content: z.string(), // With variable {{code}}, support HTML
});
export const mockMailConfigGuard = z.object({
apiKey: z.string(),
fromEmail: z.string(),
fromName: z.string().optional(),
templates: z.array(templateGuard),
});
export const mockMailConfigGuard = z
.object({
apiKey: z.string(),
fromEmail: z.string(),
fromName: z.string().optional(),
templates: z.array(templateGuard),
})
.partial();
export type MockMailConfig = z.infer<typeof mockMailConfigGuard>;

View file

@ -69,12 +69,20 @@ describe('buildUpdateWhere()', () => {
).resolves.toStrictEqual({ id: 'foo', customClientMetadata: '{"idTokenTtl":3600}' });
});
it('throws an error when `undefined` found in values', async () => {
it('should skip the keys whose value is `undefined`', async () => {
const user: CreateUser = {
id: 'foo',
username: '456',
};
const pool = createTestPool(
'update "users"\nset "username"=$1\nwhere "id"=$2 and "username"=$3'
'update "users"\nset "username"=$1\nwhere "id"=$2 and "username"=$3\nreturning *',
(_, [username, id]) => ({
id: String(id),
username: String(username),
})
);
const updateWhere = buildUpdateWhereWithPool(pool)(Users);
const updateWhere = buildUpdateWhereWithPool(pool)(Users, true);
await expect(
updateWhere({
@ -82,7 +90,7 @@ describe('buildUpdateWhere()', () => {
where: { id: 'foo', username: '456' },
jsonbMode: 'merge',
})
).rejects.toMatchError(new Error(`Cannot convert id to primitive`));
).resolves.toStrictEqual({ ...user, username: '123' });
});
it('throws `entity.not_exists_with_id` error with `undefined` when `returning` is true', async () => {

View file

@ -32,7 +32,7 @@ export const buildUpdateWhereWithPool =
const connectKeyValueWithEqualSign = (data: Partial<Schema>, jsonbMode: 'replace' | 'merge') =>
Object.entries<SchemaValue>(data)
.map(([key, value]) => {
if (!isKeyOfSchema(key)) {
if (!isKeyOfSchema(key) || value === undefined) {
return;
}

View file

@ -179,6 +179,34 @@ describe('connector data routes', () => {
);
});
it('successfully reset connector config', async () => {
getLogtoConnectors.mockResolvedValue([
{
dbEntry: mockConnector,
metadata: { ...mockMetadata, isStandard: true },
type: ConnectorType.Social,
...mockLogtoConnector,
},
]);
updateConnector.mockResolvedValueOnce({
...mockConnector,
config: {},
});
const response = await connectorRequest.patch('/connectors/id').send({
config: {},
});
expect(response).toHaveProperty('statusCode', 200);
expect(updateConnector).toHaveBeenCalledWith(
expect.objectContaining({
where: { id: 'id' },
set: {
config: {},
},
jsonbMode: 'replace',
})
);
});
it('successfully updates connector config and metadata', async () => {
getLogtoConnectors.mockResolvedValue([
{
@ -235,9 +263,6 @@ describe('connector data routes', () => {
...mockConnector,
metadata: {
target: 'connector',
name: { en: '' },
logo: '',
logoDark: '',
},
});
const response = await connectorRequest.patch('/connectors/id').send({

View file

@ -5,8 +5,10 @@ import {
Connectors,
ConnectorType,
connectorResponseGuard,
type JsonObject,
} from '@logto/schemas';
import { buildIdGenerator } from '@logto/shared';
import { conditional } from '@silverhand/essentials';
import cleanDeep from 'clean-deep';
import { string, object } from 'zod';
@ -308,7 +310,17 @@ export default function connectorRoutes<T extends AuthedRouter>(
}
await updateConnector({
set: cleanDeep({ config, metadata, syncProfile }),
set: {
/**
* `JsonObject` has all non-undefined values, and `cleanDeep` method with default settings
* drops all keys with undefined values, the return type of `Partial<JsonObject>` is still `JsonObject`.
* The type inference failed to infer this, manually assign type `JsonObject`.
*/
// eslint-disable-next-line no-restricted-syntax
config: conditional(config && (cleanDeep(config) as JsonObject)),
metadata: conditional(metadata && cleanDeep(metadata)),
syncProfile,
},
where: { id },
jsonbMode: 'replace',
});

View file

@ -115,6 +115,10 @@ test('connector set-up flow', async () => {
currentConnectors.find((connector) => connector.connectorId === mockAlternativeEmailConnectorId)
?.config
).toEqual(mockAlternativeEmailConnectorConfig);
// Can reset the connector config to empty object `{}` (when it is valid).
await expect(updateConnectorConfig(id, { config: {} })).resolves.not.toThrow();
const alternativeEmailConnector = await getConnector(id);
expect(alternativeEmailConnector.config).toEqual({});
/*
* Delete (i.e. disable) a connector