-
Notifications
You must be signed in to change notification settings - Fork 8.6k
[Scout] Migrate Data Views API tests from FTR - Part5 #264088
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a3a9c97
89a6e62
eb57e93
736f9b2
2b5a1c8
d9ed7f1
4fdf739
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,133 @@ | ||
| /* | ||
| * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public | ||
| * License v3.0 only", or the "Server Side Public License, v 1". | ||
| */ | ||
|
|
||
| import Boom from '@hapi/boom'; | ||
| import { errors as esErrors } from '@elastic/elasticsearch'; | ||
|
|
||
| import { | ||
| isEsIndexNotFoundError, | ||
| createNoMatchingIndicesError, | ||
| isNoMatchingIndicesError, | ||
| convertEsError, | ||
| } from './errors'; | ||
|
|
||
| // Mirrors the live ES "index_not_found_exception" payload shape so the | ||
| // behaviour stays in sync with what `@elastic/elasticsearch` throws. | ||
| const buildIndexNotFoundError = () => | ||
| new esErrors.ResponseError({ | ||
| statusCode: 404, | ||
| body: { | ||
| error: { | ||
| type: 'index_not_found_exception', | ||
| reason: 'no such index [SHOULD NOT EXIST]', | ||
| index: 'SHOULD NOT EXIST', | ||
| 'resource.id': 'SHOULD NOT EXIST', | ||
| 'resource.type': 'index_or_alias', | ||
| }, | ||
| }, | ||
| } as ConstructorParameters<typeof esErrors.ResponseError>[0]); | ||
|
|
||
| const buildDocNotFoundError = () => | ||
| new esErrors.ResponseError({ | ||
| statusCode: 404, | ||
| body: { | ||
| _index: 'basic_index', | ||
| _id: '1234', | ||
| found: false, | ||
| }, | ||
| } as ConstructorParameters<typeof esErrors.ResponseError>[0]); | ||
|
|
||
| describe('index_patterns/* error handler', () => { | ||
| describe('isEsIndexNotFoundError()', () => { | ||
| it('identifies index not found errors', () => { | ||
| expect(isEsIndexNotFoundError(buildIndexNotFoundError())).toBe(true); | ||
| }); | ||
|
|
||
| it('rejects doc not found errors', () => { | ||
| expect(isEsIndexNotFoundError(buildDocNotFoundError())).toBe(false); | ||
| }); | ||
| }); | ||
|
|
||
| describe('createNoMatchingIndicesError()', () => { | ||
| it('returns a boom error', () => { | ||
| const error = createNoMatchingIndicesError('foo*') as Boom.Boom; | ||
| expect(error.isBoom).toBe(true); | ||
| }); | ||
|
|
||
| it('sets output code to "no_matching_indices"', () => { | ||
| const error = createNoMatchingIndicesError('foo*') as Boom.Boom; | ||
| expect(error.output.payload).toHaveProperty('code', 'no_matching_indices'); | ||
| }); | ||
| }); | ||
|
|
||
| describe('isNoMatchingIndicesError()', () => { | ||
| it('returns true for errors from createNoMatchingIndicesError()', () => { | ||
| expect(isNoMatchingIndicesError(createNoMatchingIndicesError('foo*'))).toBe(true); | ||
| }); | ||
|
|
||
| it('returns false for indexNotFoundError', () => { | ||
| expect(isNoMatchingIndicesError(buildIndexNotFoundError())).toBe(false); | ||
| }); | ||
|
|
||
| it('returns false for docNotFoundError', () => { | ||
| expect(isNoMatchingIndicesError(buildDocNotFoundError())).toBe(false); | ||
| }); | ||
| }); | ||
|
|
||
| describe('convertEsError()', () => { | ||
| const indices = ['foo', 'bar']; | ||
|
|
||
| it('converts indexNotFoundErrors into NoMatchingIndices errors', () => { | ||
| const converted = convertEsError(indices, buildIndexNotFoundError()); | ||
| expect(isNoMatchingIndicesError(converted)).toBe(true); | ||
| }); | ||
|
|
||
| it('wraps other errors in Boom', () => { | ||
| const error = new esErrors.ResponseError({ | ||
| statusCode: 403, | ||
| body: { | ||
| root_cause: [ | ||
| { | ||
| type: 'security_exception', | ||
| reason: 'action [indices:data/read/field_caps] is unauthorized for user [standard]', | ||
| }, | ||
| ], | ||
| type: 'security_exception', | ||
| reason: 'action [indices:data/read/field_caps] is unauthorized for user [standard]', | ||
| }, | ||
| } as ConstructorParameters<typeof esErrors.ResponseError>[0]); | ||
|
|
||
| expect(error).not.toHaveProperty('isBoom'); | ||
| const converted = convertEsError(indices, error) as Boom.Boom; | ||
| expect(converted).toHaveProperty('isBoom'); | ||
| expect(converted.output.statusCode).toBe(403); | ||
| }); | ||
|
|
||
| it('handles errors that are already Boom errors', () => { | ||
| const error = new Error() as Error & { statusCode?: number }; | ||
| error.statusCode = 401; | ||
| const boomError = Boom.boomify(error, { statusCode: error.statusCode }); | ||
|
|
||
| const converted = convertEsError(indices, boomError) as Boom.Boom; | ||
|
|
||
| expect(converted.output.statusCode).toBe(401); | ||
| }); | ||
|
|
||
| it('preserves headers from Boom errors', () => { | ||
| const error = new Error() as Error & { statusCode?: number }; | ||
| error.statusCode = 401; | ||
| const boomError = Boom.boomify(error, { statusCode: error.statusCode }); | ||
| const wwwAuthenticate = 'Basic realm="Authorization Required"'; | ||
| boomError.output.headers['WWW-Authenticate'] = wwwAuthenticate; | ||
| const converted = convertEsError(indices, boomError) as Boom.Boom; | ||
|
|
||
| expect(converted.output.headers['WWW-Authenticate']).toBe(wwwAuthenticate); | ||
| }); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,12 +21,23 @@ export const COMMON_HEADERS = { | |
| export const ES_ARCHIVE_BASIC_INDEX = | ||
| 'src/platform/test/api_integration/fixtures/es_archiver/index_patterns/basic_index'; | ||
|
|
||
| export const ES_ARCHIVE_CONFLICTS = | ||
| 'src/platform/test/api_integration/fixtures/es_archiver/index_patterns/conflicts'; | ||
|
|
||
| export const KBN_ARCHIVE_SAVED_OBJECTS_BASIC = | ||
| 'src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json'; | ||
|
|
||
| export const KBN_ARCHIVE_SAVED_OBJECTS_RELATIONSHIPS = | ||
| 'src/platform/test/api_integration/fixtures/kbn_archiver/management/saved_objects/relationships.json'; | ||
|
|
||
| export const DATA_VIEW_PATH_LEGACY = 'api/index_patterns/index_pattern'; | ||
| export const DATA_VIEW_PATH = 'api/data_views/data_view'; | ||
| export const SERVICE_PATH_LEGACY = 'api/index_patterns'; | ||
| export const SERVICE_PATH = 'api/data_views'; | ||
| export const SERVICE_KEY_LEGACY = 'index_pattern'; | ||
| export const SERVICE_KEY = 'data_view'; | ||
| export const HAS_USER_DATA_VIEW_PATH = 'api/data_views/has_user_data_view'; | ||
| export const HAS_USER_INDEX_PATTERN_PATH = 'api/index_patterns/has_user_index_pattern'; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 Minor — Use constants for shared test values / Dead code
Posted via Macroscope — Scout Test Review |
||
|
|
||
| export const ID_OVER_MAX_LENGTH = 'x'.repeat(1759); | ||
|
|
||
|
|
@@ -36,4 +47,9 @@ export const INTERNAL_COMMON_HEADERS = { | |
| [ELASTIC_HTTP_VERSION_HEADER]: '1', | ||
| }; | ||
|
|
||
| export const EXISTING_INDICES_PATH = 'internal/data_views/_existing_indices'; | ||
| export const FIELDS_FOR_WILDCARD_PATH = 'internal/data_views/_fields_for_wildcard'; | ||
| export const FIELDS_ROUTE_PATH = 'internal/data_views/fields'; | ||
| export const RESOLVE_INDEX_PATH = 'internal/index-pattern-management/resolve_index'; | ||
| export const SWAP_REFERENCES_PATH = `${SERVICE_PATH}/swap_references`; | ||
| export const SWAP_REFERENCES_PREVIEW_PATH = `${SERVICE_PATH}/swap_references/_preview`; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,162 @@ | ||
| /* | ||
| * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public | ||
| * License v3.0 only", or the "Server Side Public License, v 1". | ||
| */ | ||
|
|
||
| import { apiTest, tags, type RoleApiCredentials } from '@kbn/scout'; | ||
| import { expect } from '@kbn/scout/api'; | ||
| import { COMMON_HEADERS, DATA_VIEW_PATH, SERVICE_KEY } from '../../fixtures/constants'; | ||
|
|
||
| apiTest.describe( | ||
| `POST ${DATA_VIEW_PATH}/{id}/fields - errors (data view api)`, | ||
| { tag: tags.deploymentAgnostic }, | ||
| () => { | ||
| let adminApiCredentials: RoleApiCredentials; | ||
| let createdIds: string[] = []; | ||
|
|
||
| apiTest.beforeAll(async ({ requestAuth }) => { | ||
| adminApiCredentials = await requestAuth.getApiKey('admin'); | ||
| }); | ||
|
|
||
| apiTest.afterEach(async ({ apiServices }) => { | ||
| for (const id of createdIds) { | ||
| await apiServices.dataViews.delete(id); | ||
| } | ||
| createdIds = []; | ||
| }); | ||
|
|
||
| apiTest('returns 404 error on non-existing data view', async ({ apiClient }) => { | ||
| const id = `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx-${Date.now()}`; | ||
| const response = await apiClient.post(`${DATA_VIEW_PATH}/${id}/fields`, { | ||
| headers: { | ||
| ...COMMON_HEADERS, | ||
| ...adminApiCredentials.apiKeyHeader, | ||
| }, | ||
| responseType: 'json', | ||
| body: { | ||
| fields: { | ||
| foo: {}, | ||
| }, | ||
| }, | ||
| }); | ||
|
|
||
| expect(response).toHaveStatusCode(404); | ||
| }); | ||
|
|
||
| apiTest('returns error when "fields" payload attribute is invalid', async ({ apiClient }) => { | ||
| const title = `foo-${Date.now()}-${Math.random()}*`; | ||
|
|
||
| const createResponse = await apiClient.post(DATA_VIEW_PATH, { | ||
| headers: { | ||
| ...COMMON_HEADERS, | ||
| ...adminApiCredentials.apiKeyHeader, | ||
| }, | ||
| responseType: 'json', | ||
| body: { | ||
| [SERVICE_KEY]: { title }, | ||
| }, | ||
| }); | ||
|
|
||
| expect(createResponse).toHaveStatusCode(200); | ||
| const id = createResponse.body[SERVICE_KEY].id; | ||
| createdIds.push(id); | ||
|
|
||
| const response = await apiClient.post(`${DATA_VIEW_PATH}/${id}/fields`, { | ||
| headers: { | ||
| ...COMMON_HEADERS, | ||
| ...adminApiCredentials.apiKeyHeader, | ||
| }, | ||
| responseType: 'json', | ||
| body: { | ||
| fields: 123, | ||
| }, | ||
| }); | ||
|
|
||
| expect(response).toHaveStatusCode(400); | ||
| expect(response.body.statusCode).toBe(400); | ||
| expect(response.body.message).toBe( | ||
| '[request body.fields]: expected value of type [object] but got [number]' | ||
| ); | ||
| }); | ||
|
|
||
| apiTest('returns error if no changes are specified', async ({ apiClient }) => { | ||
| const title = `foo-${Date.now()}-${Math.random()}*`; | ||
|
|
||
| const createResponse = await apiClient.post(DATA_VIEW_PATH, { | ||
| headers: { | ||
| ...COMMON_HEADERS, | ||
| ...adminApiCredentials.apiKeyHeader, | ||
| }, | ||
| responseType: 'json', | ||
| body: { | ||
| [SERVICE_KEY]: { title }, | ||
| }, | ||
| }); | ||
|
|
||
| expect(createResponse).toHaveStatusCode(200); | ||
| const id = createResponse.body[SERVICE_KEY].id; | ||
| createdIds.push(id); | ||
|
|
||
| const response = await apiClient.post(`${DATA_VIEW_PATH}/${id}/fields`, { | ||
| headers: { | ||
| ...COMMON_HEADERS, | ||
| ...adminApiCredentials.apiKeyHeader, | ||
| }, | ||
| responseType: 'json', | ||
| body: { | ||
| fields: { | ||
| foo: {}, | ||
| bar: {}, | ||
| baz: {}, | ||
| }, | ||
| }, | ||
| }); | ||
|
|
||
| expect(response).toHaveStatusCode(400); | ||
| expect(response.body.statusCode).toBe(400); | ||
| expect(response.body.message).toBe('Change set is empty.'); | ||
| }); | ||
|
|
||
| apiTest('returns validation error for too long customDescription', async ({ apiClient }) => { | ||
| const title = `foo-${Date.now()}-${Math.random()}*`; | ||
|
|
||
| const createResponse = await apiClient.post(DATA_VIEW_PATH, { | ||
| headers: { | ||
| ...COMMON_HEADERS, | ||
| ...adminApiCredentials.apiKeyHeader, | ||
| }, | ||
| responseType: 'json', | ||
| body: { | ||
| [SERVICE_KEY]: { title }, | ||
| }, | ||
| }); | ||
|
|
||
| expect(createResponse).toHaveStatusCode(200); | ||
| const id = createResponse.body[SERVICE_KEY].id; | ||
| createdIds.push(id); | ||
|
|
||
| const response = await apiClient.post(`${DATA_VIEW_PATH}/${id}/fields`, { | ||
| headers: { | ||
| ...COMMON_HEADERS, | ||
| ...adminApiCredentials.apiKeyHeader, | ||
| }, | ||
| responseType: 'json', | ||
| body: { | ||
| fields: { | ||
| foo: { | ||
| customDescription: 'too long value'.repeat(50), | ||
| }, | ||
| }, | ||
| }, | ||
| }); | ||
|
|
||
| expect(response).toHaveStatusCode(400); | ||
| expect(response.body.statusCode).toBe(400); | ||
| expect(response.body.message).toContain('it must have a maximum length'); | ||
| }); | ||
| } | ||
| ); |
Uh oh!
There was an error while loading. Please reload this page.