diff --git a/docs/api/saved-objects/find.asciidoc b/docs/api/saved-objects/find.asciidoc index e42f32149657..26c6187f9b99 100644 --- a/docs/api/saved-objects/find.asciidoc +++ b/docs/api/saved-objects/find.asciidoc @@ -11,13 +11,12 @@ saved objects by various conditions. `GET /api/saved_objects/_find` ==== Query Parameters - +`type` (required):: + (array|string) The saved object type(s) that the response should be limited to `per_page` (optional):: (number) The number of objects to return per page `page` (optional):: (number) The page of objects to return -`type` (optional):: - (array|string) The saved object type(s) that the response should be limited to `search` (optional):: (string) A {ref}/query-dsl-simple-query-string-query.html[simple_query_string] Elasticsearch query to filter the objects in the response `search_fields` (optional):: diff --git a/src/server/saved_objects/routes/find.js b/src/server/saved_objects/routes/find.js index 047117520bda..35185f197268 100644 --- a/src/server/saved_objects/routes/find.js +++ b/src/server/saved_objects/routes/find.js @@ -29,7 +29,7 @@ export const createFindRoute = (prereqs) => ({ query: Joi.object().keys({ per_page: Joi.number().min(0).default(20), page: Joi.number().min(0).default(1), - type: Joi.array().items(Joi.string()).single(), + type: Joi.array().items(Joi.string()).single().required(), search: Joi.string().allow('').optional(), search_fields: Joi.array().items(Joi.string()).single(), sort_field: Joi.array().items(Joi.string()).single(), diff --git a/src/server/saved_objects/routes/find.test.js b/src/server/saved_objects/routes/find.test.js index 7437d1823e1f..7bb599be6cfd 100644 --- a/src/server/saved_objects/routes/find.test.js +++ b/src/server/saved_objects/routes/find.test.js @@ -44,12 +44,28 @@ describe('GET /api/saved_objects/_find', () => { savedObjectsClient.find.resetHistory(); }); - it('formats successful response', async () => { + it('returns with status 400 when type is missing', async () => { const request = { method: 'GET', url: '/api/saved_objects/_find' }; + const { payload, statusCode } = await server.inject(request); + + expect(statusCode).toEqual(400); + expect(JSON.parse(payload)).toMatchObject({ + statusCode: 400, + error: 'Bad Request', + message: 'child "type" fails because ["type" is required]', + }); + }); + + it('formats successful response', async () => { + const request = { + method: 'GET', + url: '/api/saved_objects/_find?type=index-pattern' + }; + const clientResponse = { total: 2, data: [ @@ -81,7 +97,7 @@ describe('GET /api/saved_objects/_find', () => { it('calls upon savedObjectClient.find with defaults', async () => { const request = { method: 'GET', - url: '/api/saved_objects/_find' + url: '/api/saved_objects/_find?type=foo&type=bar' }; await server.inject(request); @@ -89,13 +105,13 @@ describe('GET /api/saved_objects/_find', () => { expect(savedObjectsClient.find.calledOnce).toBe(true); const options = savedObjectsClient.find.getCall(0).args[0]; - expect(options).toEqual({ perPage: 20, page: 1 }); + expect(options).toEqual({ perPage: 20, page: 1, type: ['foo', 'bar'] }); }); it('accepts the query parameter page/per_page', async () => { const request = { method: 'GET', - url: '/api/saved_objects/_find?per_page=10&page=50' + url: '/api/saved_objects/_find?type=foo&per_page=10&page=50' }; await server.inject(request); @@ -103,13 +119,13 @@ describe('GET /api/saved_objects/_find', () => { expect(savedObjectsClient.find.calledOnce).toBe(true); const options = savedObjectsClient.find.getCall(0).args[0]; - expect(options).toEqual({ perPage: 10, page: 50 }); + expect(options).toEqual({ perPage: 10, page: 50, type: ['foo'] }); }); it('accepts the query parameter search_fields', async () => { const request = { method: 'GET', - url: '/api/saved_objects/_find?search_fields=title' + url: '/api/saved_objects/_find?type=foo&search_fields=title' }; await server.inject(request); @@ -117,13 +133,13 @@ describe('GET /api/saved_objects/_find', () => { expect(savedObjectsClient.find.calledOnce).toBe(true); const options = savedObjectsClient.find.getCall(0).args[0]; - expect(options).toEqual({ perPage: 20, page: 1, searchFields: ['title'] }); + expect(options).toEqual({ perPage: 20, page: 1, searchFields: ['title'], type: ['foo'] }); }); it('accepts the query parameter fields as a string', async () => { const request = { method: 'GET', - url: '/api/saved_objects/_find?fields=title' + url: '/api/saved_objects/_find?type=foo&fields=title' }; await server.inject(request); @@ -131,13 +147,13 @@ describe('GET /api/saved_objects/_find', () => { expect(savedObjectsClient.find.calledOnce).toBe(true); const options = savedObjectsClient.find.getCall(0).args[0]; - expect(options).toEqual({ perPage: 20, page: 1, fields: ['title'] }); + expect(options).toEqual({ perPage: 20, page: 1, fields: ['title'], type: ['foo'] }); }); it('accepts the query parameter fields as an array', async () => { const request = { method: 'GET', - url: '/api/saved_objects/_find?fields=title&fields=description' + url: '/api/saved_objects/_find?type=foo&fields=title&fields=description' }; await server.inject(request); @@ -146,7 +162,7 @@ describe('GET /api/saved_objects/_find', () => { const options = savedObjectsClient.find.getCall(0).args[0]; expect(options).toEqual({ - perPage: 20, page: 1, fields: ['title', 'description'] + perPage: 20, page: 1, fields: ['title', 'description'], type: ['foo'] }); }); @@ -161,7 +177,7 @@ describe('GET /api/saved_objects/_find', () => { expect(savedObjectsClient.find.calledOnce).toBe(true); const options = savedObjectsClient.find.getCall(0).args[0]; - expect(options).toEqual({ perPage: 20, page: 1, type: [ 'index-pattern' ] }); + expect(options).toEqual({ perPage: 20, page: 1, type: ['index-pattern'] }); }); it('accepts the query parameter type as an array', async () => { diff --git a/src/server/saved_objects/service/lib/repository.js b/src/server/saved_objects/service/lib/repository.js index 93371b7d1b74..c03cb97e3e86 100644 --- a/src/server/saved_objects/service/lib/repository.js +++ b/src/server/saved_objects/service/lib/repository.js @@ -251,6 +251,10 @@ export class SavedObjectsRepository { fields, } = options; + if (!type) { + throw new TypeError(`options.type must be a string or an array of strings`); + } + if (searchFields && !Array.isArray(searchFields)) { throw new TypeError('options.searchFields must be an array'); } diff --git a/src/server/saved_objects/service/lib/repository.test.js b/src/server/saved_objects/service/lib/repository.test.js index 9330f0e0f029..388f905aac2f 100644 --- a/src/server/saved_objects/service/lib/repository.test.js +++ b/src/server/saved_objects/service/lib/repository.test.js @@ -385,7 +385,7 @@ describe('SavedObjectsRepository', () => { }); describe('#delete', () => { - it('throws notFound when ES is unable to find the document', async () => { + it('throws notFound when ES is unable to find the document', async () => { expect.assertions(1); callAdminCluster.returns(Promise.resolve({ @@ -394,7 +394,7 @@ describe('SavedObjectsRepository', () => { try { await savedObjectsRepository.delete('index-pattern', 'logstash-*'); - } catch(e) { + } catch (e) { expect(e.output.statusCode).toEqual(404); } }); @@ -423,9 +423,15 @@ describe('SavedObjectsRepository', () => { callAdminCluster.returns(searchResults); }); + it('requires type to be defined', async () => { + await expect(savedObjectsRepository.find({})).rejects.toThrow(/options\.type must be/); + sinon.assert.notCalled(callAdminCluster); + sinon.assert.notCalled(onBeforeWrite); + }); + it('requires searchFields be an array if defined', async () => { try { - await savedObjectsRepository.find({ searchFields: 'string' }); + await savedObjectsRepository.find({ type: 'foo', searchFields: 'string' }); throw new Error('expected find() to reject'); } catch (error) { sinon.assert.notCalled(callAdminCluster); @@ -436,7 +442,7 @@ describe('SavedObjectsRepository', () => { it('requires fields be an array if defined', async () => { try { - await savedObjectsRepository.find({ fields: 'string' }); + await savedObjectsRepository.find({ type: 'foo', fields: 'string' }); throw new Error('expected find() to reject'); } catch (error) { sinon.assert.notCalled(callAdminCluster); @@ -461,7 +467,7 @@ describe('SavedObjectsRepository', () => { it('merges output of getSearchDsl into es request body', async () => { getSearchDsl.returns({ query: 1, aggregations: 2 }); - await savedObjectsRepository.find(); + await savedObjectsRepository.find({ type: 'foo' }); sinon.assert.calledOnce(callAdminCluster); sinon.assert.notCalled(onBeforeWrite); sinon.assert.calledWithExactly(callAdminCluster, 'search', sinon.match({ @@ -475,7 +481,7 @@ describe('SavedObjectsRepository', () => { it('formats Elasticsearch response', async () => { const count = searchResults.hits.hits.length; - const response = await savedObjectsRepository.find(); + const response = await savedObjectsRepository.find({ type: 'foo' }); expect(response.total).toBe(count); expect(response.saved_objects).toHaveLength(count); @@ -492,7 +498,7 @@ describe('SavedObjectsRepository', () => { }); it('accepts per_page/page', async () => { - await savedObjectsRepository.find({ perPage: 10, page: 6 }); + await savedObjectsRepository.find({ type: 'foo', perPage: 10, page: 6 }); sinon.assert.calledOnce(callAdminCluster); sinon.assert.calledWithExactly(callAdminCluster, sinon.match.string, sinon.match({ @@ -504,12 +510,12 @@ describe('SavedObjectsRepository', () => { }); it('can filter by fields', async () => { - await savedObjectsRepository.find({ fields: ['title'] }); + await savedObjectsRepository.find({ type: 'foo', fields: ['title'] }); sinon.assert.calledOnce(callAdminCluster); sinon.assert.calledWithExactly(callAdminCluster, sinon.match.string, sinon.match({ _source: [ - '*.title', 'type', 'title' + 'foo.title', 'type', 'title' ] })); diff --git a/src/server/saved_objects/service/lib/search_dsl/search_dsl.js b/src/server/saved_objects/service/lib/search_dsl/search_dsl.js index ea34c127e985..1197a332f341 100644 --- a/src/server/saved_objects/service/lib/search_dsl/search_dsl.js +++ b/src/server/saved_objects/service/lib/search_dsl/search_dsl.js @@ -31,8 +31,8 @@ export function getSearchDsl(mappings, options = {}) { sortOrder } = options; - if (!type && sortField) { - throw Boom.notAcceptable('Cannot sort without filtering by type'); + if (!type) { + throw Boom.notAcceptable('type must be specified'); } if (sortOrder && !sortField) { diff --git a/src/server/saved_objects/service/lib/search_dsl/search_dsl.test.js b/src/server/saved_objects/service/lib/search_dsl/search_dsl.test.js index 85302b5e2572..82796b5638cc 100644 --- a/src/server/saved_objects/service/lib/search_dsl/search_dsl.test.js +++ b/src/server/saved_objects/service/lib/search_dsl/search_dsl.test.js @@ -27,13 +27,13 @@ describe('getSearchDsl', () => { afterEach(() => sandbox.restore()); describe('validation', () => { - it('throws when sortField is passed without type', () => { + it('throws when type is not specified', () => { expect(() => { getSearchDsl({}, { type: undefined, sortField: 'title' }); - }).toThrowError(/sort without .+ type/); + }).toThrowError(/type must be specified/); }); it('throws when sortOrder without sortField', () => { expect(() => { @@ -89,7 +89,7 @@ describe('getSearchDsl', () => { it('returns combination of getQueryParams and getSortingParams', () => { sandbox.stub(queryParamsNS, 'getQueryParams').returns({ a: 'a' }); sandbox.stub(sortParamsNS, 'getSortingParams').returns({ b: 'b' }); - expect(getSearchDsl({})).toEqual({ a: 'a', b: 'b' }); + expect(getSearchDsl(null, { type: 'foo' })).toEqual({ a: 'a', b: 'b' }); }); }); }); diff --git a/src/server/saved_objects/service/saved_objects_client.test.js b/src/server/saved_objects/service/saved_objects_client.test.js index a7189f5a3989..930ccb3922c2 100644 --- a/src/server/saved_objects/service/saved_objects_client.test.js +++ b/src/server/saved_objects/service/saved_objects_client.test.js @@ -72,7 +72,7 @@ test(`#find`, async () => { }; const client = new SavedObjectsClient(mockRepository); - const options = {}; + const options = { type: 'foo' }; const result = await client.find(options); expect(mockRepository.find).toHaveBeenCalledWith(options); diff --git a/test/api_integration/apis/saved_objects/find.js b/test/api_integration/apis/saved_objects/find.js index 1b8096ae048c..c9b1e9fc73f4 100644 --- a/test/api_integration/apis/saved_objects/find.js +++ b/test/api_integration/apis/saved_objects/find.js @@ -140,6 +140,25 @@ export default function ({ getService }) { )); }); + describe('missing type', () => { + it('should return 400', async () => ( + await supertest + .get('/api/saved_objects/_find') + .expect(400) + .then(resp => { + expect(resp.body).to.eql({ + error: 'Bad Request', + message: 'child "type" fails because ["type" is required]', + statusCode: 400, + validation: { + keys: ['type'], + source: 'query' + } + }); + }) + )); + }); + describe('page beyond total', () => { it('should return 200 with empty response', async () => ( await supertest diff --git a/x-pack/plugins/security/index.js b/x-pack/plugins/security/index.js index d49844a21454..2abd107758da 100644 --- a/x-pack/plugins/security/index.js +++ b/x-pack/plugins/security/index.js @@ -139,7 +139,6 @@ export const security = (kibana) => new kibana.Plugin({ errors: savedObjects.SavedObjectsClient.errors, checkPrivileges, auditLogger, - savedObjectTypes: savedObjects.types, actions: authorization.actions, }); }); diff --git a/x-pack/plugins/security/server/lib/saved_objects_client/secure_saved_objects_client.js b/x-pack/plugins/security/server/lib/saved_objects_client/secure_saved_objects_client.js index eb19f59c2969..ca66742370ae 100644 --- a/x-pack/plugins/security/server/lib/saved_objects_client/secure_saved_objects_client.js +++ b/x-pack/plugins/security/server/lib/saved_objects_client/secure_saved_objects_client.js @@ -15,7 +15,6 @@ export class SecureSavedObjectsClient { callWithRequestRepository, checkPrivileges, auditLogger, - savedObjectTypes, actions, } = options; @@ -24,7 +23,6 @@ export class SecureSavedObjectsClient { this._callWithRequestRepository = callWithRequestRepository; this._checkPrivileges = checkPrivileges; this._auditLogger = auditLogger; - this._savedObjectTypes = savedObjectTypes; this._actions = actions; } @@ -57,11 +55,12 @@ export class SecureSavedObjectsClient { } async find(options = {}) { - if (options.type) { - return await this._findWithTypes(options); - } - - return await this._findAcrossAllTypes(options); + return await this._execute( + options.type, + 'find', + { options }, + repository => repository.find(options) + ); } async bulkGet(objects = []) { @@ -95,7 +94,7 @@ export class SecureSavedObjectsClient { async _checkSavedObjectPrivileges(actions) { try { return await this._checkPrivileges(actions); - } catch(error) { + } catch (error) { const { reason } = get(error, 'body.error', {}); throw this.errors.decorateGeneralError(error, reason); } @@ -120,48 +119,4 @@ export class SecureSavedObjectsClient { throw new Error('Unexpected result from hasPrivileges'); } } - - async _findAcrossAllTypes(options) { - const action = 'find'; - - // we have to filter for only their authorized types - const types = this._savedObjectTypes; - const typesToPrivilegesMap = new Map(types.map(type => [type, this._actions.getSavedObjectAction(type, action)])); - const { result, username, missing } = await this._checkSavedObjectPrivileges(Array.from(typesToPrivilegesMap.values())); - - if (result === CHECK_PRIVILEGES_RESULT.LEGACY) { - return await this._callWithRequestRepository.find(options); - } - - const authorizedTypes = Array.from(typesToPrivilegesMap.entries()) - .filter(([ , privilege]) => !missing.includes(privilege)) - .map(([type]) => type); - - if (authorizedTypes.length === 0) { - this._auditLogger.savedObjectsAuthorizationFailure( - username, - action, - types, - missing, - { options } - ); - throw this.errors.decorateForbiddenError(new Error(`Not authorized to find saved_object`)); - } - - this._auditLogger.savedObjectsAuthorizationSuccess(username, action, authorizedTypes, { options }); - - return await this._internalRepository.find({ - ...options, - type: authorizedTypes - }); - } - - async _findWithTypes(options) { - return await this._execute( - options.type, - 'find', - { options }, - repository => repository.find(options) - ); - } } diff --git a/x-pack/plugins/security/server/lib/saved_objects_client/secure_saved_objects_client.test.js b/x-pack/plugins/security/server/lib/saved_objects_client/secure_saved_objects_client.test.js index 99656772c0df..61a0c0a50bcd 100644 --- a/x-pack/plugins/security/server/lib/saved_objects_client/secure_saved_objects_client.test.js +++ b/x-pack/plugins/security/server/lib/saved_objects_client/secure_saved_objects_client.test.js @@ -516,7 +516,7 @@ describe('#find', () => { auditLogger: mockAuditLogger, actions: mockActions, }); - const options = { type: [ type1, type2 ] }; + const options = { type: [type1, type2] }; await expect(client.find(options)).rejects.toThrowError(mockErrors.forbiddenError); @@ -598,9 +598,7 @@ describe('#find', () => { }); describe('no type', () => { - test(`throws decorated GeneralError when hasPrivileges rejects promise`, async () => { - const type1 = 'foo'; - const type2 = 'bar'; + test(`throws error`, async () => { const mockRepository = {}; const mockErrors = createMockErrors(); const mockCheckPrivileges = jest.fn().mockImplementation(async () => { @@ -613,161 +611,18 @@ describe('#find', () => { repository: mockRepository, checkPrivileges: mockCheckPrivileges, auditLogger: mockAuditLogger, - savedObjectTypes: [type1, type2], actions: mockActions, }); await expect(client.find()).rejects.toThrowError(mockErrors.generalError); expect(mockCheckPrivileges).toHaveBeenCalledWith([ - mockActions.getSavedObjectAction(type1, 'find'), - mockActions.getSavedObjectAction(type2, 'find'), + mockActions.getSavedObjectAction(undefined, 'find'), ]); expect(mockErrors.decorateGeneralError).toHaveBeenCalledTimes(1); expect(mockAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); }); - - test(`throws decorated ForbiddenError when unauthorized`, async () => { - const type = 'foo'; - const username = Symbol(); - const mockRepository = {}; - const mockErrors = createMockErrors(); - const mockCheckPrivileges = jest.fn().mockImplementation(async privileges => ({ - result: CHECK_PRIVILEGES_RESULT.UNAUTHORIZED, - username, - missing: [ - privileges[0] - ], - })); - const mockAuditLogger = createMockAuditLogger(); - const mockActions = createMockActions(); - const client = new SecureSavedObjectsClient({ - errors: mockErrors, - repository: mockRepository, - checkPrivileges: mockCheckPrivileges, - auditLogger: mockAuditLogger, - savedObjectTypes: [type], - actions: mockActions, - }); - const options = Symbol(); - - await expect(client.find(options)).rejects.toThrowError(mockErrors.forbiddenError); - - expect(mockCheckPrivileges).toHaveBeenCalledWith([mockActions.getSavedObjectAction(type, 'find')]); - expect(mockErrors.decorateForbiddenError).toHaveBeenCalledTimes(1); - expect(mockAuditLogger.savedObjectsAuthorizationFailure).toHaveBeenCalledWith( - username, - 'find', - [type], - [mockActions.getSavedObjectAction(type, 'find')], - { - options - } - ); - expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); - }); - - test(`returns result of callWithRequestRepository.find when legacy`, async () => { - const type = 'foo'; - const username = Symbol(); - const returnValue = Symbol(); - const mockRepository = { - find: jest.fn().mockReturnValue(returnValue) - }; - const mockErrors = createMockErrors(); - const mockCheckPrivileges = jest.fn().mockImplementation(async privileges => ({ - result: CHECK_PRIVILEGES_RESULT.LEGACY, - username, - missing: privileges, - })); - const mockAuditLogger = createMockAuditLogger(); - const mockActions = createMockActions(); - const client = new SecureSavedObjectsClient({ - errors: mockErrors, - callWithRequestRepository: mockRepository, - checkPrivileges: mockCheckPrivileges, - auditLogger: mockAuditLogger, - savedObjectTypes: [type], - actions: mockActions, - }); - const options = Symbol(); - - const result = await client.find(options); - - expect(result).toBe(returnValue); - expect(mockCheckPrivileges).toHaveBeenCalledWith([mockActions.getSavedObjectAction(type, 'find')]); - expect(mockRepository.find).toHaveBeenCalledWith(options); - expect(mockAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); - expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); - }); - - test(`specifies authorized types when calling repository.find()`, async () => { - const type1 = 'foo'; - const type2 = 'bar'; - const mockRepository = { - find: jest.fn(), - }; - const mockErrors = createMockErrors(); - const mockCheckPrivileges = jest.fn().mockImplementation(async privileges => ({ - result: CHECK_PRIVILEGES_RESULT.UNAUTHORIZED, - missing: [ - privileges[0] - ] - })); - const mockAuditLogger = createMockAuditLogger(); - const mockActions = createMockActions(); - const client = new SecureSavedObjectsClient({ - errors: mockErrors, - internalRepository: mockRepository, - checkPrivileges: mockCheckPrivileges, - auditLogger: mockAuditLogger, - savedObjectTypes: [type1, type2], - actions: mockActions, - }); - - await client.find(); - - expect(mockCheckPrivileges).toHaveBeenCalledWith([ - mockActions.getSavedObjectAction(type1, 'find'), - mockActions.getSavedObjectAction(type2, 'find'), - ]); - expect(mockRepository.find).toHaveBeenCalledWith(expect.objectContaining({ - type: [type2] - })); - }); - - test(`returns result of repository.find`, async () => { - const type = 'foo'; - const username = Symbol(); - const returnValue = Symbol(); - const mockRepository = { - find: jest.fn().mockReturnValue(returnValue) - }; - const mockCheckPrivileges = jest.fn().mockImplementation(async () => ({ - result: CHECK_PRIVILEGES_RESULT.AUTHORIZED, - username, - missing: [], - })); - const mockAuditLogger = createMockAuditLogger(); - const client = new SecureSavedObjectsClient({ - internalRepository: mockRepository, - checkPrivileges: mockCheckPrivileges, - auditLogger: mockAuditLogger, - savedObjectTypes: [type], - actions: createMockActions(), - }); - const options = Symbol(); - - const result = await client.find(options); - - expect(result).toBe(returnValue); - expect(mockRepository.find).toHaveBeenCalledWith({ type: [type] }); - expect(mockAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); - expect(mockAuditLogger.savedObjectsAuthorizationSuccess).toHaveBeenCalledWith(username, 'find', [type], { - options, - }); - }); }); }); diff --git a/x-pack/test/rbac_api_integration/apis/saved_objects/find.js b/x-pack/test/rbac_api_integration/apis/saved_objects/find.js index 5bb42acacd39..9f2c4fde3358 100644 --- a/x-pack/test/rbac_api_integration/apis/saved_objects/find.js +++ b/x-pack/test/rbac_api_integration/apis/saved_objects/find.js @@ -31,85 +31,15 @@ export default function ({ getService }) { }); }; - const expectResultsWithValidTypes = (resp) => { + const expectBadRequest = (resp) => { expect(resp.body).to.eql({ - page: 1, - per_page: 20, - total: 4, - saved_objects: [ - { - id: '91200a00-9efd-11e7-acb3-3dab96693fab', - type: 'index-pattern', - updated_at: '2017-09-21T18:49:16.270Z', - version: 1, - attributes: resp.body.saved_objects[0].attributes - }, - { - id: '7.0.0-alpha1', - type: 'config', - updated_at: '2017-09-21T18:49:16.302Z', - version: 1, - attributes: resp.body.saved_objects[1].attributes - }, - { - id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', - type: 'visualization', - updated_at: '2017-09-21T18:51:23.794Z', - version: 1, - attributes: resp.body.saved_objects[2].attributes - }, - { - id: 'be3733a0-9efe-11e7-acb3-3dab96693fab', - type: 'dashboard', - updated_at: '2017-09-21T18:57:40.826Z', - version: 1, - attributes: resp.body.saved_objects[3].attributes - }, - ] - }); - }; - - const expectAllResultsIncludingInvalidTypes = (resp) => { - expect(resp.body).to.eql({ - page: 1, - per_page: 20, - total: 5, - saved_objects: [ - { - id: '91200a00-9efd-11e7-acb3-3dab96693fab', - type: 'index-pattern', - updated_at: '2017-09-21T18:49:16.270Z', - version: 1, - attributes: resp.body.saved_objects[0].attributes - }, - { - id: '7.0.0-alpha1', - type: 'config', - updated_at: '2017-09-21T18:49:16.302Z', - version: 1, - attributes: resp.body.saved_objects[1].attributes - }, - { - id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', - type: 'visualization', - updated_at: '2017-09-21T18:51:23.794Z', - version: 1, - attributes: resp.body.saved_objects[2].attributes - }, - { - id: 'be3733a0-9efe-11e7-acb3-3dab96693fab', - type: 'dashboard', - updated_at: '2017-09-21T18:57:40.826Z', - version: 1, - attributes: resp.body.saved_objects[3].attributes - }, - { - id: 'visualization:dd7caf20-9efd-11e7-acb3-3dab96693faa', - type: 'not-a-visualization', - updated_at: '2017-09-21T18:51:23.794Z', - version: 1 - }, - ] + error: 'Bad Request', + message: 'child "type" fails because ["type" is required]', + statusCode: 400, + validation: { + keys: ['type'], + source: 'query' + } }); }; @@ -130,14 +60,6 @@ export default function ({ getService }) { }); }; - const expectForbiddenCantFindAnyTypes = resp => { - expect(resp.body).to.eql({ - statusCode: 403, - error: 'Forbidden', - message: `Not authorized to find saved_object` - }); - }; - const findTest = (description, { auth, tests }) => { describe(description, () => { before(() => esArchiver.load('saved_objects/basic')); @@ -221,8 +143,8 @@ export default function ({ getService }) { }, noType: { description: `forbidded can't find any types`, - statusCode: 403, - response: expectForbiddenCantFindAnyTypes, + statusCode: 400, + response: expectBadRequest, } } }); @@ -255,8 +177,8 @@ export default function ({ getService }) { }, noType: { description: 'all objects', - statusCode: 200, - response: expectResultsWithValidTypes, + statusCode: 400, + response: expectBadRequest, }, }, }); @@ -289,8 +211,8 @@ export default function ({ getService }) { }, noType: { description: 'all objects', - statusCode: 200, - response: expectAllResultsIncludingInvalidTypes, + statusCode: 400, + response: expectBadRequest, }, }, }); @@ -323,8 +245,8 @@ export default function ({ getService }) { }, noType: { description: 'all objects', - statusCode: 200, - response: expectAllResultsIncludingInvalidTypes, + statusCode: 400, + response: expectBadRequest, }, } }); @@ -357,8 +279,8 @@ export default function ({ getService }) { }, noType: { description: 'all objects', - statusCode: 200, - response: expectResultsWithValidTypes, + statusCode: 400, + response: expectBadRequest, }, }, }); @@ -391,8 +313,8 @@ export default function ({ getService }) { }, noType: { description: 'all objects', - statusCode: 200, - response: expectResultsWithValidTypes, + statusCode: 400, + response: expectBadRequest, }, } }); @@ -425,8 +347,8 @@ export default function ({ getService }) { }, noType: { description: 'all objects', - statusCode: 200, - response: expectResultsWithValidTypes, + statusCode: 400, + response: expectBadRequest, }, }, }); @@ -459,8 +381,8 @@ export default function ({ getService }) { }, noType: { description: 'all objects', - statusCode: 200, - response: expectResultsWithValidTypes, + statusCode: 400, + response: expectBadRequest, }, } });