kibana/src/plugins/content_management/server/rpc/procedures/get.test.ts
2023-04-04 08:20:11 -07:00

251 lines
7.4 KiB
TypeScript

/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { omit } from 'lodash';
import { schema } from '@kbn/config-schema';
import { ContentManagementServiceDefinitionVersioned } from '@kbn/object-versioning';
import { validate } from '../../utils';
import { ContentRegistry } from '../../core/registry';
import { createMockedStorage } from '../../core/mocks';
import { EventBus } from '../../core/event_bus';
import { getServiceObjectTransformFactory } from '../services_transforms_factory';
import { get } from './get';
const { fn, schemas } = get;
const inputSchema = schemas?.in;
const outputSchema = schemas?.out;
if (!inputSchema) {
throw new Error(`Input schema missing for [get] procedure.`);
}
if (!outputSchema) {
throw new Error(`Output schema missing for [get] procedure.`);
}
const FOO_CONTENT_ID = 'foo';
describe('RPC -> get()', () => {
describe('Input/Output validation', () => {
const validInput = { contentTypeId: 'foo', id: '123', version: 1 };
test('should validate that a contentTypeId and an id is passed', () => {
[
{ input: validInput },
{
input: omit(validInput, 'contentTypeId'),
expectedError: '[contentTypeId]: expected value of type [string] but got [undefined]',
},
{
input: { ...validInput, unknown: 'foo' },
expectedError: '[unknown]: definition for this key is missing',
},
{
input: { ...validInput, id: '' }, // id must have min 1 char
expectedError: '[id]: value has length [0] but it must have a minimum length of [1].',
},
{
input: omit(validInput, 'version'),
expectedError: '[version]: expected value of type [number] but got [undefined]',
},
{
input: { ...validInput, version: '1' }, // string number is OK
},
].forEach(({ input, expectedError }) => {
const error = validate(input, inputSchema);
if (!expectedError) {
try {
expect(error).toBe(null);
} catch (e) {
throw new Error(`Expected no error but got [{${error?.message}}].`);
}
} else {
expect(error?.message).toBe(expectedError);
}
});
});
test('should allow an options "object" to be passed', () => {
let error = validate(
{
contentTypeId: 'foo',
id: '123',
version: 1,
options: { any: 'object' },
},
inputSchema
);
expect(error).toBe(null);
error = validate(
{
contentTypeId: 'foo',
id: '123',
version: 1,
options: 123, // Not an object
},
inputSchema
);
expect(error?.message).toBe(
'[options]: expected a plain object value, but found [number] instead.'
);
});
test('should validate that the response is an object', () => {
let error = validate(
{
contentTypeId: 'foo',
result: {
item: {
any: 'object',
},
meta: {
foo: 'bar',
},
},
},
outputSchema
);
expect(error).toBe(null);
error = validate({ contentTypeId: '123', result: 123 }, outputSchema);
expect(error?.message).toBe(
'[result]: expected a plain object value, but found [number] instead.'
);
});
});
describe('procedure', () => {
const setup = () => {
const contentRegistry = new ContentRegistry(new EventBus());
const storage = createMockedStorage();
contentRegistry.register({
id: FOO_CONTENT_ID,
storage,
version: {
latest: 2,
},
});
const requestHandlerContext = 'mockedRequestHandlerContext';
const ctx: any = {
contentRegistry,
requestHandlerContext,
getTransformsFactory: getServiceObjectTransformFactory,
};
return { ctx, storage };
};
test('should return the storage get() result', async () => {
const { ctx, storage } = setup();
const expected = {
item: 'GetResult',
};
storage.get.mockResolvedValueOnce(expected);
const result = await fn(ctx, { contentTypeId: FOO_CONTENT_ID, id: '1234', version: 1 });
expect(result).toEqual({
contentTypeId: FOO_CONTENT_ID,
item: expected,
});
expect(storage.get).toHaveBeenCalledWith(
{
requestHandlerContext: ctx.requestHandlerContext,
version: {
request: 1,
latest: 2, // from the registry
},
utils: {
getTransforms: expect.any(Function),
},
},
'1234',
undefined
);
});
describe('validation', () => {
test('should validate that content type definition exist', () => {
const { ctx } = setup();
expect(() => fn(ctx, { contentTypeId: 'unknown', id: '1234' })).rejects.toEqual(
new Error('Content [unknown] is not registered.')
);
});
test('should throw if the request version is higher than the registered version', () => {
const { ctx } = setup();
expect(() =>
fn(ctx, {
contentTypeId: FOO_CONTENT_ID,
id: '1234',
version: 7,
})
).rejects.toEqual(new Error('Invalid version. Latest version is [2].'));
});
});
describe('object versioning', () => {
test('should expose a utility to transform and validate services objects', () => {
const { ctx, storage } = setup();
fn(ctx, { contentTypeId: FOO_CONTENT_ID, id: '1234', version: 1 });
const [[storageContext]] = storage.get.mock.calls;
// getTransforms() utils should be available from context
const { getTransforms } = storageContext.utils ?? {};
expect(getTransforms).not.toBeUndefined();
const definitions: ContentManagementServiceDefinitionVersioned = {
1: {
get: {
in: {
options: {
schema: schema.object({
version1: schema.string(),
}),
up: (pre: object) => ({ ...pre, version2: 'added' }),
},
},
},
},
2: {},
};
const transforms = getTransforms(definitions, 1);
// Some smoke tests for the getTransforms() utils. Complete test suite is inside
// the package @kbn/object-versioning
expect(transforms.get.in.options.up({ version1: 'foo' }).value).toEqual({
version1: 'foo',
version2: 'added',
});
const optionsUpTransform = transforms.get.in.options.up({ version1: 123 });
expect(optionsUpTransform.value).toBe(null);
expect(optionsUpTransform.error?.message).toBe(
'[version1]: expected value of type [string] but got [number]'
);
expect(transforms.get.in.options.validate({ version1: 123 })?.message).toBe(
'[version1]: expected value of type [string] but got [number]'
);
});
});
});
});