Change can* signature to be the same as their equivalent function, apply PR feedback

This commit is contained in:
Mike Cote 2019-05-01 13:10:28 -04:00
parent 6287a8cecf
commit b657ac8fc1
15 changed files with 410 additions and 130 deletions

View file

@ -36,15 +36,16 @@ describe('getSortedObjectsForExport()', () => {
beforeEach(() => {
jest.resetAllMocks();
savedObjectsClient.canBulkCreate.mockImplementation((types: string[]) =>
types.map(type => ({ type, can: true }))
savedObjectsClient.canBulkCreate.mockImplementation((objects: any, options: any) =>
objects.map((obj: any) => ({ type: obj.type, can: true }))
);
savedObjectsClient.canBulkGet.mockImplementation((types: string[]) =>
types.map(type => ({ type, can: true }))
);
savedObjectsClient.canFind.mockImplementation((types: string[]) =>
types.map(type => ({ type, can: true }))
savedObjectsClient.canBulkGet.mockImplementation((objects: any, options: any) =>
objects.map((obj: any) => ({ type: obj.type, can: true }))
);
savedObjectsClient.canFind.mockImplementation((options: any) => {
const types = Array.isArray(options.type) ? options.type : [options.type];
return types.map((type: string) => ({ type, can: true }));
});
});
test('exports selected types and sorts them', async () => {

View file

@ -74,7 +74,7 @@ async function fetchObjectsToExport({
'bulk_get',
objectTypes,
supportedTypes,
await savedObjectsClient.canBulkGet(objectTypes)
await savedObjectsClient.canBulkGet(objects)
);
if (objects.length > exportSizeLimit) {
throw Boom.badRequest(`Can't export more than ${exportSizeLimit} objects`);
@ -95,7 +95,12 @@ async function fetchObjectsToExport({
'find',
types || [],
supportedTypes,
await savedObjectsClient.canFind(types || [])
await savedObjectsClient.canFind({
type: types,
sortField: '_id',
sortOrder: 'asc',
perPage: exportSizeLimit,
})
);
const findResponse = await savedObjectsClient.find({
type: types,

View file

@ -72,15 +72,16 @@ describe('importSavedObjects()', () => {
beforeEach(() => {
jest.resetAllMocks();
savedObjectsClient.canBulkCreate.mockImplementation((types: string[]) =>
types.map(type => ({ type, can: true }))
savedObjectsClient.canBulkCreate.mockImplementation((objects: any, options: any) =>
objects.map((obj: any) => ({ type: obj.type, can: true }))
);
savedObjectsClient.canBulkGet.mockImplementation((types: string[]) =>
types.map(type => ({ type, can: true }))
);
savedObjectsClient.canFind.mockImplementation((types: string[]) =>
types.map(type => ({ type, can: true }))
savedObjectsClient.canBulkGet.mockImplementation((objects: any, options: any) =>
objects.map((obj: any) => ({ type: obj.type, can: true }))
);
savedObjectsClient.canFind.mockImplementation((options: any) => {
const types = Array.isArray(options.type) ? options.type : [options.type];
return types.map((type: string) => ({ type, can: true }));
});
});
test('returns early when no objects exist', async () => {

View file

@ -52,7 +52,7 @@ export async function importSavedObjects({
const objectsFromStream = await collectSavedObjects({ readStream, objectLimit });
const objectTypes = [...new Set(objectsFromStream.map(obj => obj.type))];
const authorizedTypes = await savedObjectsClient.canBulkCreate(objectTypes);
const authorizedTypes = await savedObjectsClient.canBulkCreate(objectsFromStream, { overwrite });
const invalidTypes = [
...new Set([
...objectTypes.filter(type => !supportedTypes.includes(type)),

View file

@ -78,15 +78,16 @@ describe('resolveImportErrors()', () => {
beforeEach(() => {
jest.resetAllMocks();
savedObjectsClient.canBulkCreate.mockImplementation((types: string[]) =>
types.map(type => ({ type, can: true }))
savedObjectsClient.canBulkCreate.mockImplementation((objects: any, options: any) =>
objects.map((obj: any) => ({ type: obj.type, can: true }))
);
savedObjectsClient.canBulkGet.mockImplementation((types: string[]) =>
types.map(type => ({ type, can: true }))
);
savedObjectsClient.canFind.mockImplementation((types: string[]) =>
types.map(type => ({ type, can: true }))
savedObjectsClient.canBulkGet.mockImplementation((objects: any, options: any) =>
objects.map((obj: any) => ({ type: obj.type, can: true }))
);
savedObjectsClient.canFind.mockImplementation((options: any) => {
const types = Array.isArray(options.type) ? options.type : [options.type];
return types.map((type: string) => ({ type, can: true }));
});
});
test('works with empty parameters', async () => {

View file

@ -19,7 +19,7 @@
import Boom from 'boom';
import { Readable } from 'stream';
import { SavedObjectsClient } from '../service';
import { SavedObjectsClient, SavedObject } from '../service';
import { collectSavedObjects } from './collect_saved_objects';
import { createObjectsFilter } from './create_objects_filter';
import { extractErrors } from './extract_errors';
@ -41,6 +41,36 @@ interface ImportResponse {
errors?: ImportError[];
}
async function getAuthorizedTypes(
savedObjectsClient: SavedObjectsClient,
objects: SavedObject[],
retries: Retry[]
) {
// Call canBulkCreate twice since the parameters have to be the same as if bulkCreate is called
const { objectsToOverwrite, objectsToNotOverwrite } = splitOverwrites(objects, retries);
const resultForObjectsToOverwrite = await savedObjectsClient.canBulkCreate(objectsToOverwrite, {
overwrite: true,
});
const resultForObjectsToNotOverwrite = await savedObjectsClient.canBulkCreate(
objectsToNotOverwrite
);
// Extract which types can be created in bulk
const resultMap: { [key: string]: boolean } = {};
for (const { type, can } of resultForObjectsToNotOverwrite) {
resultMap[type] = can;
}
for (const { type, can } of resultForObjectsToOverwrite) {
const canPreviously = resultMap[type] !== false;
resultMap[type] = canPreviously && can;
}
return Object.keys(resultMap).map(type => ({
type,
can: resultMap[type],
}));
}
export async function resolveImportErrors({
readStream,
objectLimit,
@ -60,7 +90,7 @@ export async function resolveImportErrors({
});
const objectTypes = [...new Set(objectsToResolve.map(obj => obj.type))];
const authorizedTypes = await savedObjectsClient.canBulkCreate(objectTypes);
const authorizedTypes = await getAuthorizedTypes(savedObjectsClient, objectsToResolve, retries);
const invalidTypes = [
...new Set([
...objectTypes.filter(type => !supportedTypes.includes(type)),

View file

@ -40,15 +40,16 @@ describe('POST /api/saved_objects/_import', () => {
beforeEach(() => {
server = createMockServer();
jest.resetAllMocks();
savedObjectsClient.canBulkCreate.mockImplementation((types: string[]) =>
types.map(type => ({ type, can: true }))
savedObjectsClient.canBulkCreate.mockImplementation((objects: any, options: any) =>
objects.map((obj: any) => ({ type: obj.type, can: true }))
);
savedObjectsClient.canBulkGet.mockImplementation((types: string[]) =>
types.map(type => ({ type, can: true }))
);
savedObjectsClient.canFind.mockImplementation((types: string[]) =>
types.map(type => ({ type, can: true }))
savedObjectsClient.canBulkGet.mockImplementation((objects: any, options: any) =>
objects.map((obj: any) => ({ type: obj.type, can: true }))
);
savedObjectsClient.canFind.mockImplementation((options: any) => {
const types = Array.isArray(options.type) ? options.type : [options.type];
return types.map((type: string) => ({ type, can: true }));
});
const prereqs = {
getSavedObjectsClient: {

View file

@ -40,15 +40,16 @@ describe('POST /api/saved_objects/_resolve_import_errors', () => {
beforeEach(() => {
server = createMockServer();
jest.resetAllMocks();
savedObjectsClient.canBulkCreate.mockImplementation((types: string[]) =>
types.map(type => ({ type, can: true }))
savedObjectsClient.canBulkCreate.mockImplementation((objects: any, options: any) =>
objects.map((obj: any) => ({ type: obj.type, can: true }))
);
savedObjectsClient.canBulkGet.mockImplementation((types: string[]) =>
types.map(type => ({ type, can: true }))
);
savedObjectsClient.canFind.mockImplementation((types: string[]) =>
types.map(type => ({ type, can: true }))
savedObjectsClient.canBulkGet.mockImplementation((objects: any, options: any) =>
objects.map((obj: any) => ({ type: obj.type, can: true }))
);
savedObjectsClient.canFind.mockImplementation((options: any) => {
const types = Array.isArray(options.type) ? options.type : [options.type];
return types.map((type: string) => ({ type, can: true }));
});
const prereqs = {
getSavedObjectsClient: {

View file

@ -127,8 +127,9 @@ export declare class SavedObjectsClient {
objects: Array<BulkCreateObject<T>>,
options?: CreateOptions
): Promise<BulkCreateResponse<T>>;
public canBulkCreate(
types: string[]
public canBulkCreate<T extends SavedObjectAttributes = any>(
objects: Array<BulkCreateObject<T>>,
options?: CreateOptions
): Promise<
Array<{
type: string;
@ -139,12 +140,17 @@ export declare class SavedObjectsClient {
public find<T extends SavedObjectAttributes = any>(
options: FindOptions
): Promise<FindResponse<T>>;
public canFind(types: string[]): Promise<Array<{ type: string; can: boolean }>>;
public canFind<T extends SavedObjectAttributes = any>(
options: FindOptions
): Promise<Array<{ type: string; can: boolean }>>;
public bulkGet<T extends SavedObjectAttributes = any>(
objects: BulkGetObjects,
options?: BaseOptions
): Promise<BulkGetResponse<T>>;
public canBulkGet(types: string[]): Promise<Array<{ type: string; can: boolean }>>;
public canBulkGet<T extends SavedObjectAttributes = any>(
objects: BulkGetObjects,
options?: BaseOptions
): Promise<Array<{ type: string; can: boolean }>>;
public get<T extends SavedObjectAttributes = any>(
type: string,
id: string,

View file

@ -127,11 +127,15 @@ export class SavedObjectsClient {
*
* This should only be used by import / export / resolve import errors.
*
* @param {Array<string>} types Types of saved objects
* @param {array} objects - [{ type, id, attributes }]
* @param {object} [options={}]
* @property {boolean} [options.overwrite=false] - overwrites existing documents
* @property {string} [options.namespace]
* @return [{ type, can }]
*/
async canBulkCreate(types) {
return types.map(type => ({ type, can: true }));
async canBulkCreate(objects) {
const types = new Set(objects.map(obj => obj.type));
return [...types].map(type => ({ type, can: true }));
}
/**
@ -172,17 +176,30 @@ export class SavedObjectsClient {
*
* This should only be used by import / export / resolve import errors.
*
* @param {Array<string>} types Types of saved objects
* @param {object} [options={}]
* @property {(string|Array<string>)} [options.type]
* @property {string} [options.search]
* @property {string} [options.defaultSearchOperator]
* @property {Array<string>} [options.searchFields] - see Elasticsearch Simple Query String
* Query field argument for more information
* @property {integer} [options.page=1]
* @property {integer} [options.perPage=20]
* @property {string} [options.sortField]
* @property {string} [options.sortOrder]
* @property {Array<string>} [options.fields]
* @property {string} [options.namespace]
* @property {object} [options.hasReference] - { type, id }
* @return [{ type, can }]
*/
async canFind(types) {
async canFind(options = {}) {
const types = Array.isArray(options.type) ? options.type : [options.type];
return types.map(type => ({ type, can: true }));
}
/**
* Returns an array of objects by id
*
* @param {array} objects - an array ids, or an array of objects containing id and optionally type
* @param {array} objects - an array of objects containing id and type
* @param {object} [options={}]
* @property {string} [options.namespace]
* @returns {promise} - { saved_objects: [{ id, type, version, attributes }] }
@ -202,11 +219,14 @@ export class SavedObjectsClient {
*
* This should only be used by import / export / resolve import errors.
*
* @param {Array<string>} types Types of saved objects
* @param {array} objects - an array of objects containing id and type
* @param {object} [options={}]
* @property {string} [options.namespace]
* @return [{ type, can }]
*/
async canBulkGet(types) {
return types.map(type => ({ type, can: true }));
async canBulkGet(objects = []) {
const types = new Set(objects.map(obj => obj.type));
return [...types].map(type => ({ type, can: true }));
}
/**

View file

@ -48,8 +48,9 @@ export class SecureSavedObjectsClientWrapper {
return await this._baseClient.bulkCreate(objects, options);
}
async canBulkCreate(types) {
return await this._checkAuthorizedTypes(types, 'bulk_create');
async canBulkCreate(objects, options = {}) {
const types = uniq(objects.map(o => o.type));
return await this._checkSavedObjectPrivileges(types, 'bulk_create', { objects, options });
}
async delete(type, id, options) {
@ -72,8 +73,8 @@ export class SecureSavedObjectsClientWrapper {
return this._baseClient.find(options);
}
async canFind(types) {
return await this._checkAuthorizedTypes(types, 'find');
async canFind(options = {}) {
return await this._checkSavedObjectPrivileges(options.type, 'find', { options });
}
async bulkGet(objects = [], options = {}) {
@ -87,8 +88,9 @@ export class SecureSavedObjectsClientWrapper {
return await this._baseClient.bulkGet(objects, options);
}
async canBulkGet(types) {
return await this._checkAuthorizedTypes(types, 'bulk_get');
async canBulkGet(objects = [], options = {}) {
const types = uniq(objects.map(o => o.type));
return await this._checkSavedObjectPrivileges(types, 'bulk_get', { objects, options });
}
async get(type, id, options = {}) {
@ -111,46 +113,48 @@ export class SecureSavedObjectsClientWrapper {
return await this._baseClient.update(type, id, attributes, options);
}
async _checkSavedObjectPrivileges(actions) {
async _checkSavedObjectPrivileges(typeOrTypes, action, args) {
const types = Array.isArray(typeOrTypes) ? typeOrTypes : [typeOrTypes];
const actionsToTypesMap = new Map(types.map(type => [this._actions.savedObject.get(type, action), type]));
const actions = Array.from(actionsToTypesMap.keys());
// Check privileges
let result;
try {
return await this._checkPrivileges(actions);
result = await this._checkPrivileges(actions);
} catch(error) {
const { reason } = get(error, 'body.error', {});
throw this.errors.decorateGeneralError(error, reason);
}
}
async _checkAuthorizedTypes(typeOrTypes, action) {
const types = Array.isArray(typeOrTypes) ? typeOrTypes : [typeOrTypes];
const actionsToTypesMap = new Map(types.map(type => [this._actions.savedObject.get(type, action), type]));
const actions = Array.from(actionsToTypesMap.keys());
const { privileges } = await this._checkSavedObjectPrivileges(actions);
const missingPrivileges = this._getMissingPrivileges(privileges);
const invalidTypes = missingPrivileges.map(privilege => actionsToTypesMap.get(privilege));
return types.map(type => ({
type,
can: !invalidTypes.includes(type),
}));
}
async _ensureAuthorized(typeOrTypes, action, args) {
const types = Array.isArray(typeOrTypes) ? typeOrTypes : [typeOrTypes];
const actionsToTypesMap = new Map(types.map(type => [this._actions.savedObject.get(type, action), type]));
const actions = Array.from(actionsToTypesMap.keys());
const { hasAllRequested, username, privileges } = await this._checkSavedObjectPrivileges(actions);
if (hasAllRequested) {
this._auditLogger.savedObjectsAuthorizationSuccess(username, action, types, args);
// Log to audit and return array of types that are authorized
let typesMissingPrivileges = [];
if (result.hasAllRequested) {
this._auditLogger.savedObjectsAuthorizationSuccess(result.username, action, types, args);
} else {
const missingPrivileges = this._getMissingPrivileges(privileges);
const missingPrivileges = this._getMissingPrivileges(result.privileges);
this._auditLogger.savedObjectsAuthorizationFailure(
username,
result.username,
action,
types,
missingPrivileges,
args
);
const msg = `Unable to ${action} ${missingPrivileges.map(privilege => actionsToTypesMap.get(privilege)).sort().join(',')}`;
typesMissingPrivileges = missingPrivileges.map(privilege => actionsToTypesMap.get(privilege));
}
return types.map(type => ({
type,
can: !typesMissingPrivileges.includes(type),
}));
}
async _ensureAuthorized(typeOrTypes, action, args) {
const result = await this._checkSavedObjectPrivileges(typeOrTypes, action, args);
const unauthorizedTypes = result
.filter(obj => obj.can === false)
.map(obj => obj.type);
if (unauthorizedTypes.length) {
const msg = `Unable to ${action} ${unauthorizedTypes.sort().join(',')}`;
throw this.errors.decorateForbiddenError(new Error(msg));
}
}

View file

@ -332,7 +332,7 @@ describe(`spaces disabled`, () => {
spaces: null,
});
await expect(client.canBulkCreate([type])).rejects.toThrowError(mockErrors.generalError);
await expect(client.canBulkCreate([{ type }])).rejects.toThrowError(mockErrors.generalError);
expect(mockCheckPrivilegesDynamicallyWithRequest).toHaveBeenCalledWith(mockRequest);
expect(mockCheckPrivileges).toHaveBeenCalledWith([mockActions.savedObject.get(type, 'bulk_create')]);
@ -368,9 +368,14 @@ describe(`spaces disabled`, () => {
savedObjectTypes: [],
spaces: null,
});
const types = [type1, type2 ];
const objects = [
{ type: type1 },
{ type: type1 },
{ type: type2 },
];
const options = Symbol();
const result = await client.canBulkCreate(types);
const result = await client.canBulkCreate(objects, options);
expect(result).toEqual([
{
type: type1,
@ -387,6 +392,16 @@ describe(`spaces disabled`, () => {
mockActions.savedObject.get(type1, 'bulk_create'),
mockActions.savedObject.get(type2, 'bulk_create'),
]);
expect(mockAuditLogger.savedObjectsAuthorizationFailure).toHaveBeenCalledWith(
username,
'bulk_create',
[type1, type2],
[mockActions.savedObject.get(type1, 'bulk_create')],
{
objects,
options,
}
);
});
});
@ -702,7 +717,7 @@ describe(`spaces disabled`, () => {
spaces: null,
});
await expect(client.canFind([type])).rejects.toThrowError(mockErrors.generalError);
await expect(client.canFind({ type })).rejects.toThrowError(mockErrors.generalError);
expect(mockCheckPrivilegesDynamicallyWithRequest).toHaveBeenCalledWith(mockRequest);
expect(mockCheckPrivileges).toHaveBeenCalledWith([mockActions.savedObject.get(type, 'find')]);
@ -739,9 +754,9 @@ describe(`spaces disabled`, () => {
savedObjectTypes: [],
spaces: null,
});
const types = [type1, type2];
const result = await client.canFind(types);
const options = { type: [type1, type2] };
const result = await client.canFind(options);
expect(result).toEqual([
{
type: type1,
@ -752,11 +767,22 @@ describe(`spaces disabled`, () => {
can: true,
},
]);
expect(mockCheckPrivilegesDynamicallyWithRequest).toHaveBeenCalledWith(mockRequest);
expect(mockCheckPrivileges).toHaveBeenCalledWith([
mockActions.savedObject.get(type1, 'find'),
mockActions.savedObject.get(type2, 'find')
]);
expect(mockAuditLogger.savedObjectsAuthorizationFailure).toHaveBeenCalledWith(
username,
'find',
[type1, type2],
[mockActions.savedObject.get(type1, 'find')],
{
options
}
);
expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled();
});
});
@ -921,7 +947,7 @@ describe(`spaces disabled`, () => {
spaces: null,
});
await expect(client.canBulkGet([type])).rejects.toThrowError(mockErrors.generalError);
await expect(client.canBulkGet([{ type }])).rejects.toThrowError(mockErrors.generalError);
expect(mockCheckPrivilegesDynamicallyWithRequest).toHaveBeenCalledWith(mockRequest);
expect(mockCheckPrivileges).toHaveBeenCalledWith([mockActions.savedObject.get(type, 'bulk_get')]);
@ -957,10 +983,14 @@ describe(`spaces disabled`, () => {
savedObjectTypes: [],
spaces: null,
});
const types = [type1, type2];
const result = await client.canBulkGet(types);
const objects = [
{ type: type1 },
{ type: type1 },
{ type: type2 },
];
const options = Symbol();
const result = await client.canBulkGet(objects, options);
expect(result).toEqual([
{
type: type1,
@ -971,11 +1001,23 @@ describe(`spaces disabled`, () => {
can: true,
},
]);
expect(mockCheckPrivilegesDynamicallyWithRequest).toHaveBeenCalledWith(mockRequest);
expect(mockCheckPrivileges).toHaveBeenCalledWith([
mockActions.savedObject.get(type1, 'bulk_get'),
mockActions.savedObject.get(type2, 'bulk_get'),
]);
expect(mockAuditLogger.savedObjectsAuthorizationFailure).toHaveBeenCalledWith(
username,
'bulk_get',
[type1, type2],
[mockActions.savedObject.get(type1, 'bulk_get')],
{
objects,
options,
}
);
expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled();
});
});

View file

@ -8,11 +8,21 @@ exports[`default space #bulkGet throws error if objects type is space 1`] = `"Sp
exports[`default space #bulkGet throws error if options.namespace is specified 1`] = `"Spaces currently determines the namespaces"`;
exports[`default space #canBulkCreate throws error if types contains space 1`] = `"Spaces can not be accessed using the SavedObjectsClient"`;
exports[`default space #canBulkCreate throws error if objects type is space 1`] = `"Spaces can not be accessed using the SavedObjectsClient"`;
exports[`default space #canBulkCreate throws error if options.namespace is specified 1`] = `"Spaces currently determines the namespaces"`;
exports[`default space #canBulkGet throws error if objects type is space 1`] = `"Spaces can not be accessed using the SavedObjectsClient"`;
exports[`default space #canFind throws error if types contains space 1`] = `"Spaces can not be accessed using the SavedObjectsClient"`;
exports[`default space #canBulkGet throws error if options.namespace is specified 1`] = `"Spaces currently determines the namespaces"`;
exports[`default space #canFind if options.type isn't provided specifies options.type based on the types excluding the space 1`] = `"Spaces can not be accessed using the SavedObjectsClient"`;
exports[`default space #canFind throws error if options.namespace is specified 1`] = `"Spaces currently determines the namespaces"`;
exports[`default space #canFind throws error if options.type is array containing space 1`] = `"Spaces can not be accessed using the SavedObjectsClient"`;
exports[`default space #canFind throws error if options.type is space 1`] = `"Spaces can not be accessed using the SavedObjectsClient"`;
exports[`default space #create throws error if options.namespace is specified 1`] = `"Spaces currently determines the namespaces"`;
@ -46,11 +56,21 @@ exports[`space_1 space #bulkGet throws error if objects type is space 1`] = `"Sp
exports[`space_1 space #bulkGet throws error if options.namespace is specified 1`] = `"Spaces currently determines the namespaces"`;
exports[`space_1 space #canBulkCreate throws error if types contains space 1`] = `"Spaces can not be accessed using the SavedObjectsClient"`;
exports[`space_1 space #canBulkCreate throws error if objects type is space 1`] = `"Spaces can not be accessed using the SavedObjectsClient"`;
exports[`space_1 space #canBulkCreate throws error if options.namespace is specified 1`] = `"Spaces currently determines the namespaces"`;
exports[`space_1 space #canBulkGet throws error if objects type is space 1`] = `"Spaces can not be accessed using the SavedObjectsClient"`;
exports[`space_1 space #canFind throws error if types contains space 1`] = `"Spaces can not be accessed using the SavedObjectsClient"`;
exports[`space_1 space #canBulkGet throws error if options.namespace is specified 1`] = `"Spaces currently determines the namespaces"`;
exports[`space_1 space #canFind if options.type isn't provided specifies options.type based on the types excluding the space 1`] = `"Spaces can not be accessed using the SavedObjectsClient"`;
exports[`space_1 space #canFind throws error if options.namespace is specified 1`] = `"Spaces currently determines the namespaces"`;
exports[`space_1 space #canFind throws error if options.type is array containing space 1`] = `"Spaces can not be accessed using the SavedObjectsClient"`;
exports[`space_1 space #canFind throws error if options.type is space 1`] = `"Spaces can not be accessed using the SavedObjectsClient"`;
exports[`space_1 space #create throws error if options.namespace is specified 1`] = `"Spaces currently determines the namespaces"`;

View file

@ -173,7 +173,7 @@ const createMockClient = () => {
});
describe('#canBulkGet', () => {
test('throws error if objects type is space', async () => {
test(`throws error if options.namespace is specified`, async () => {
const request = createMockRequest({ id: currentSpace.id });
const baseClient = createMockClient();
const spacesService = createSpacesService(server);
@ -185,10 +185,31 @@ const createMockClient = () => {
types,
});
await expect(client.canBulkGet(['foo', 'space'])).rejects.toThrowErrorMatchingSnapshot();
await expect(
client.canBulkGet([{ id: '', type: 'foo' }], { namespace: 'bar' })
).rejects.toThrowErrorMatchingSnapshot();
});
test('supplements options', async () => {
test(`throws error if objects type is space`, async () => {
const request = createMockRequest({ id: currentSpace.id });
const baseClient = createMockClient();
const spacesService = createSpacesService(server);
const client = new SpacesSavedObjectsClient({
request,
baseClient,
spacesService,
types,
});
await expect(
client.canBulkGet([{ id: '', type: 'foo' }, { id: '', type: 'space' }], {
namespace: 'bar',
})
).rejects.toThrowErrorMatchingSnapshot();
});
test(`supplements options with undefined namespace`, async () => {
const request = createMockRequest({ id: currentSpace.id });
const baseClient = createMockClient();
const expectedReturnValue = Symbol();
@ -202,12 +223,16 @@ const createMockClient = () => {
types,
});
const reqTypes = Object.freeze(['foo']);
const objects = [{ type: 'foo' }];
const options = Object.freeze({ foo: 'bar' });
// @ts-ignore
const actualReturnValue = await client.canBulkGet(reqTypes);
const actualReturnValue = await client.canBulkGet(objects, options);
expect(actualReturnValue).toBe(expectedReturnValue);
expect(baseClient.canBulkGet).toHaveBeenCalledWith(reqTypes);
expect(baseClient.canBulkGet).toHaveBeenCalledWith(objects, {
foo: 'bar',
namespace: currentSpace.expectedNamespace,
});
});
});
@ -332,11 +357,9 @@ const createMockClient = () => {
});
describe('#canFind', () => {
test('throws error if types contains space', async () => {
test(`throws error if options.namespace is specified`, async () => {
const request = createMockRequest({ id: currentSpace.id });
const baseClient = createMockClient();
const expectedReturnValue = Symbol();
baseClient.canFind.mockReturnValue(expectedReturnValue);
const spacesService = createSpacesService(server);
const client = new SpacesSavedObjectsClient({
@ -346,10 +369,10 @@ const createMockClient = () => {
types,
});
await expect(client.canFind(['space'])).rejects.toThrowErrorMatchingSnapshot();
await expect(client.canFind({ namespace: 'bar' })).rejects.toThrowErrorMatchingSnapshot();
});
test('supplements types', async () => {
test(`throws error if options.type is space`, async () => {
const request = createMockRequest({ id: currentSpace.id });
const baseClient = createMockClient();
const expectedReturnValue = Symbol();
@ -363,12 +386,93 @@ const createMockClient = () => {
types,
});
const reqTypes = Object.freeze(['foo', 'bar']);
// @ts-ignore
const actualReturnValue = await client.canFind(reqTypes);
await expect(client.canFind({ type: 'space' })).rejects.toThrowErrorMatchingSnapshot();
});
test(`passes options.type to baseClient if valid singular type specified`, async () => {
const request = createMockRequest({ id: currentSpace.id });
const baseClient = createMockClient();
const expectedReturnValue = Symbol();
baseClient.canFind.mockReturnValue(expectedReturnValue);
const spacesService = createSpacesService(server);
const client = new SpacesSavedObjectsClient({
request,
baseClient,
spacesService,
types,
});
const options = Object.freeze({ type: 'foo' });
const actualReturnValue = await client.canFind(options);
expect(actualReturnValue).toBe(expectedReturnValue);
expect(baseClient.canFind).toHaveBeenCalledWith(reqTypes);
expect(baseClient.canFind).toHaveBeenCalledWith({
type: ['foo'],
namespace: currentSpace.expectedNamespace,
});
});
test(`throws error if options.type is array containing space`, async () => {
const request = createMockRequest({ id: currentSpace.id });
const baseClient = createMockClient();
const expectedReturnValue = Symbol();
baseClient.canFind.mockReturnValue(expectedReturnValue);
const spacesService = createSpacesService(server);
const client = new SpacesSavedObjectsClient({
request,
baseClient,
spacesService,
types,
});
await expect(
client.canFind({ type: ['space', 'foo'] })
).rejects.toThrowErrorMatchingSnapshot();
});
test(`if options.type isn't provided specifies options.type based on the types excluding the space`, async () => {
const request = createMockRequest({ id: currentSpace.id });
const baseClient = createMockClient();
const expectedReturnValue = Symbol();
baseClient.canFind.mockReturnValue(expectedReturnValue);
const spacesService = createSpacesService(server);
const client = new SpacesSavedObjectsClient({
request,
baseClient,
spacesService,
types,
});
await expect(
client.canFind({ type: ['space', 'foo'] })
).rejects.toThrowErrorMatchingSnapshot();
});
test(`supplements options with undefined namespace`, async () => {
const request = createMockRequest({ id: currentSpace.id });
const baseClient = createMockClient();
const expectedReturnValue = Symbol();
baseClient.canFind.mockReturnValue(expectedReturnValue);
const spacesService = createSpacesService(server);
const client = new SpacesSavedObjectsClient({
request,
baseClient,
spacesService,
types,
});
const options = Object.freeze({ type: ['foo', 'bar'] });
const actualReturnValue = await client.canFind(options);
expect(actualReturnValue).toBe(expectedReturnValue);
expect(baseClient.canFind).toHaveBeenCalledWith({
type: ['foo', 'bar'],
namespace: currentSpace.expectedNamespace,
});
});
});
@ -499,7 +603,7 @@ const createMockClient = () => {
});
describe('#canBulkCreate', () => {
test('throws error if types contains space', async () => {
test(`throws error if options.namespace is specified`, async () => {
const request = createMockRequest({ id: currentSpace.id });
const baseClient = createMockClient();
const spacesService = createSpacesService(server);
@ -511,10 +615,32 @@ const createMockClient = () => {
types,
});
await expect(client.canBulkCreate(['foo', 'space'])).rejects.toThrowErrorMatchingSnapshot();
await expect(
client.canBulkCreate([{ id: '', type: 'foo', attributes: {} }], { namespace: 'bar' })
).rejects.toThrowErrorMatchingSnapshot();
});
test('supplements options', async () => {
test(`throws error if objects type is space`, async () => {
const request = createMockRequest({ id: currentSpace.id });
const baseClient = createMockClient();
const spacesService = createSpacesService(server);
const client = new SpacesSavedObjectsClient({
request,
baseClient,
spacesService,
types,
});
await expect(
client.canBulkCreate([
{ id: '', type: 'foo', attributes: {} },
{ id: '', type: 'space', attributes: {} },
])
).rejects.toThrowErrorMatchingSnapshot();
});
test(`supplements options with undefined namespace`, async () => {
const request = createMockRequest({ id: currentSpace.id });
const baseClient = createMockClient();
const expectedReturnValue = Symbol();
@ -528,12 +654,16 @@ const createMockClient = () => {
types,
});
const reqTypes = Object.freeze(['foo']);
const objects = [{ type: 'foo' }];
const options = Object.freeze({ foo: 'bar' });
// @ts-ignore
const actualReturnValue = await client.canBulkCreate(reqTypes);
const actualReturnValue = await client.canBulkCreate(objects, options);
expect(actualReturnValue).toBe(expectedReturnValue);
expect(baseClient.canBulkCreate).toHaveBeenCalledWith(reqTypes);
expect(baseClient.canBulkCreate).toHaveBeenCalledWith(objects, {
foo: 'bar',
namespace: currentSpace.expectedNamespace,
});
});
});

View file

@ -117,10 +117,14 @@ export class SpacesSavedObjectsClient implements SavedObjectsClient {
});
}
public async canBulkCreate(types: string[]) {
throwErrorIfTypesContainsSpace(types);
public async canBulkCreate(objects: BulkCreateObject[], options: BaseOptions = {}) {
throwErrorIfTypesContainsSpace(objects.map(object => object.type));
throwErrorIfNamespaceSpecified(options);
return await this.client.canBulkCreate(types);
return await this.client.canBulkCreate(objects, {
...options,
namespace: getNamespace(this.spaceId),
});
}
/**
@ -174,10 +178,20 @@ export class SpacesSavedObjectsClient implements SavedObjectsClient {
});
}
public async canFind(types: string[]) {
throwErrorIfTypesContainsSpace(types);
public async canFind(options: FindOptions = {}) {
if (options.type) {
throwErrorIfTypesContainsSpace(coerceToArray(options.type));
}
return await this.client.canFind(types);
throwErrorIfNamespaceSpecified(options);
return await this.client.canFind({
...options,
type: (options.type ? coerceToArray(options.type) : this.types).filter(
type => type !== 'space'
),
namespace: getNamespace(this.spaceId),
});
}
/**
@ -204,10 +218,14 @@ export class SpacesSavedObjectsClient implements SavedObjectsClient {
});
}
public async canBulkGet(types: string[]) {
throwErrorIfTypesContainsSpace(types);
public async canBulkGet(objects: BulkGetObjects = [], options: BaseOptions = {}) {
throwErrorIfTypesContainsSpace(objects.map(object => object.type));
throwErrorIfNamespaceSpecified(options);
return await this.client.canBulkGet(types);
return await this.client.canBulkGet(objects, {
...options,
namespace: getNamespace(this.spaceId),
});
}
/**