diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c684781b..4c7f88647 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Added support for `application/yaml` responses ([#363](https://github.com/opensearch-project/opensearch-api-specification/pull/363)) - Added test for search with seq_no_primary_term ([#367](https://github.com/opensearch-project/opensearch-api-specification/pull/367)) - Added a linter for parameter sorting ([#369](https://github.com/opensearch-project/opensearch-api-specification/pull/369)) +- Added AjvErrorsParser to print more informative error messages ([#364](https://github.com/opensearch-project/opensearch-api-specification/issues/364)) - Added support for `application/cbor` responses ([#371](https://github.com/opensearch-project/opensearch-api-specification/pull/371)) - Added support for `application/smile` responses ([#386](https://github.com/opensearch-project/opensearch-api-specification/pull/386)) diff --git a/json_schemas/_info.schema.yaml b/json_schemas/_info.schema.yaml index 77a2257b7..6b1ce5383 100644 --- a/json_schemas/_info.schema.yaml +++ b/json_schemas/_info.schema.yaml @@ -40,5 +40,4 @@ properties: type: string required: - title - - version - - $schema \ No newline at end of file + - version \ No newline at end of file diff --git a/json_schemas/_superseded_operations.schema.yaml b/json_schemas/_superseded_operations.schema.yaml index a49f6671e..6f11df33f 100644 --- a/json_schemas/_superseded_operations.schema.yaml +++ b/json_schemas/_superseded_operations.schema.yaml @@ -1,9 +1,10 @@ $schema: http://json-schema.org/draft-07/schema# type: object -patternProperties: - ^\$schema$: +properties: + $schema: type: string +patternProperties: ^/: type: object properties: @@ -17,5 +18,4 @@ patternProperties: enum: [GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH] required: [superseded_by, operations] additionalProperties: false -required: [$schema] additionalProperties: false \ No newline at end of file diff --git a/json_schemas/test_story.schema.yaml b/json_schemas/test_story.schema.yaml index a4cddf37e..3c3e28f53 100644 --- a/json_schemas/test_story.schema.yaml +++ b/json_schemas/test_story.schema.yaml @@ -18,7 +18,7 @@ properties: type: array items: $ref: '#/definitions/Chapter' -required: [$schema, description, chapters] +required: [description, chapters] additionalProperties: false definitions: diff --git a/tools/src/_utils/AjvErrorsParser.ts b/tools/src/_utils/AjvErrorsParser.ts new file mode 100644 index 000000000..1d93ed18e --- /dev/null +++ b/tools/src/_utils/AjvErrorsParser.ts @@ -0,0 +1,98 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +*/ + +import { Ajv2019 as AJV, ErrorsTextOptions, ErrorObject } from 'ajv/dist/2019' +import _ from 'lodash' + +export default class AjvErrorsParser { + private readonly ajv: AJV + private readonly options: ErrorsTextOptions + + constructor(ajv: AJV, options: ErrorsTextOptions = {}) { + this.ajv = ajv + this.options = { separator: ' --- ', ...options } + } + + parse(errors: ErrorObject[] | undefined | null): string { + const error_groups = this.#group_errors(errors ?? []) + const parsed_errors = [ + this.#prohibited_property_error(error_groups.prohibited), + this.#required_property_error(error_groups.required), + this.#enum_error(error_groups.enum), + ...error_groups.others + ].filter(e => e != null) as ErrorObject[] + return this.ajv.errorsText(parsed_errors, this.options) + } + + #group_errors(errors: ErrorObject[]): { required: ErrorObject[], prohibited: ErrorObject[], enum: ErrorObject[], others: ErrorObject[] } { + const categories = { + required: [] as ErrorObject[], + prohibited: [] as ErrorObject[], + enum: [] as ErrorObject[], + others: [] as ErrorObject[] + } + _.values(_.groupBy(errors, 'instancePath')).forEach((path_errors) => { + for (const error of path_errors) { + switch (error.keyword) { + case 'required': + categories.required.push(error) + break + case 'unevaluatedProperties': + case 'additionalProperties': + categories.prohibited.push(error) + break + case 'enum': + categories.enum.push(error) + break + default: + categories.others.push(error) + } + } + }) + return categories + } + + #prohibited_property_error(errors: ErrorObject[]): ErrorObject | undefined { + if (errors.length === 0) return + const properties = errors.map((error) => error.params.additionalProperty ?? error.params.unevaluatedProperty).join(', ') + return { + keyword: 'prohibited', + instancePath: errors[0].instancePath, + schemaPath: errors[0].schemaPath, + params: {}, + message: `contains unsupported properties: ${properties}` + } + } + + #required_property_error(errors: ErrorObject[]): ErrorObject | undefined { + if (errors.length === 0) return + const properties = errors.map((error) => error.params.missingProperty).join(', ') + return { + keyword: 'required', + instancePath: errors[0].instancePath, + schemaPath: errors[0].schemaPath, + params: {}, + message: `MUST contain the missing properties: ${properties}` + } + } + + #enum_error(errors: ErrorObject[]): ErrorObject | undefined { + if (errors.length === 0) return + const allowed_values = errors[0].params.allowedValues.join(', ') + return { + keyword: 'enum', + instancePath: errors[0].instancePath, + schemaPath: errors[0].schemaPath, + params: {}, + message: `MUST be equal to one of the allowed values: ${allowed_values}` + } + } + + +} \ No newline at end of file diff --git a/tools/src/_utils/JsonSchemaValidator.ts b/tools/src/_utils/JsonSchemaValidator.ts new file mode 100644 index 000000000..3ece32d15 --- /dev/null +++ b/tools/src/_utils/JsonSchemaValidator.ts @@ -0,0 +1,61 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +*/ + +import { Ajv2019 as AJV, ErrorsTextOptions, Options as AjvOptions, ValidateFunction } from 'ajv/dist/2019' +import ajv_errors, { ErrorMessageOptions } from 'ajv-errors' +import addFormats from 'ajv-formats'; +import AjvErrorsParser from './AjvErrorsParser'; + +interface JsonSchemaValidatorOpts { + ajv_opts?: AjvOptions, + ajv_errors_opts?: ErrorMessageOptions, + errors_text_opts?: ErrorsTextOptions + additional_keywords?: string[], + reference_schemas?: Record> +} + +const DEFAULT_AJV_OPTS = { + strict: true, + allErrors: true +} + +// Wrapper for AJV +export default class JsonSchemaValidator { + private readonly ajv: AJV + private readonly errors_parser: AjvErrorsParser + private readonly _validate: ValidateFunction | undefined + message: string | undefined + + constructor(default_schema?: Record, options: JsonSchemaValidatorOpts = {}) { + this.ajv = new AJV({ ...DEFAULT_AJV_OPTS, ...options.ajv_opts }) + addFormats(this.ajv); + if (options.ajv_errors_opts != null) ajv_errors(this.ajv, options.ajv_errors_opts) + for (const keyword of options.additional_keywords ?? []) this.ajv.addKeyword(keyword) + Object.entries(options.reference_schemas ?? {}).forEach(([key, schema]) => this.ajv.addSchema(schema, key)) + this.errors_parser = new AjvErrorsParser(this.ajv, options.errors_text_opts) + if (default_schema) this._validate = this.ajv.compile(default_schema) + } + + validate_data(data: any, schema?: Record): string | undefined { + if (schema) return this.#validate(this.ajv.compile(schema), data) + if (this._validate) return this.#validate(this._validate, data) + throw new Error('No schema provided') + } + + validate_schema(schema: Record): string | undefined { + const validate_func = this.ajv.validateSchema.bind(this.ajv) as ValidateFunction + return this.#validate(validate_func, schema, true) + } + + #validate(validate_func: ValidateFunction, data: any, is_schema: boolean = false): string | undefined { + const valid = validate_func(data) as boolean + const errors = is_schema ? this.ajv.errors : validate_func.errors + return valid ? undefined : this.errors_parser.parse(errors) + } +} \ No newline at end of file diff --git a/tools/src/linter/SchemasValidator.ts b/tools/src/linter/SchemasValidator.ts index 9390f09a1..6e40027aa 100644 --- a/tools/src/linter/SchemasValidator.ts +++ b/tools/src/linter/SchemasValidator.ts @@ -7,16 +7,10 @@ * compatible open source license. */ -import AJV from 'ajv' -import addFormats from 'ajv-formats' import OpenApiMerger from '../merger/OpenApiMerger' import { type ValidationError } from '../types' -import { type Logger } from '../Logger' - -const IGNORED_ERROR_PREFIXES = [ - 'can\'t resolve reference', // errors in referenced schemas will also cause reference errors - 'discriminator: oneOf subschemas' // known bug in ajv: https://github.com/ajv-validator/ajv/issues/2281 -] +import { Logger, LogLevel } from '../Logger' +import JsonSchemaValidator from "../_utils/JsonSchemaValidator"; const ADDITIONAL_KEYWORDS = [ 'x-version-added', @@ -29,18 +23,16 @@ export default class SchemasValidator { logger: Logger root_folder: string spec: Record = {} - ajv: AJV + json_validator: JsonSchemaValidator constructor (root_folder: string, logger: Logger) { this.logger = logger this.root_folder = root_folder - this.ajv = new AJV({ strict: true, discriminator: true }) - addFormats(this.ajv) - for (const keyword of ADDITIONAL_KEYWORDS) this.ajv.addKeyword(keyword) + this.json_validator = new JsonSchemaValidator(undefined, { additional_keywords: ADDITIONAL_KEYWORDS }) } validate (): ValidationError[] { - this.spec = new OpenApiMerger(this.root_folder, this.logger).merge().components as Record + this.spec = new OpenApiMerger(this.root_folder, new Logger(LogLevel.error)).merge().components as Record const named_schemas_errors = this.validate_named_schemas() if (named_schemas_errors.length > 0) return named_schemas_errors return [ @@ -53,7 +45,7 @@ export default class SchemasValidator { validate_named_schemas (): ValidationError[] { return Object.entries(this.spec.schemas as Record).map(([key, _schema]) => { const schema = _schema as Record - const error = this.validate_schema(schema, `#/components/schemas/${key}`) + const error = this.validate_schema(schema) if (error == null) return const file = `schemas/${key.split(':')[0]}.yaml` @@ -100,16 +92,10 @@ export default class SchemasValidator { }).filter(e => e != null) as ValidationError[] } - validate_schema (schema: Record, key: string | undefined = undefined): Error | undefined { + validate_schema (schema: Record): string | undefined { if (schema == null || schema.$ref != null) return - try { - if (key != null) this.ajv.addSchema(schema, key) - this.ajv.compile(schema) - } catch (_e: any) { - const error = _e as Error - for (const prefix of IGNORED_ERROR_PREFIXES) if (error.message.startsWith(prefix)) return - return error - } + const message = this.json_validator.validate_schema(schema) + if (message != null) return message } group_to_namespace (group: string): string { @@ -118,7 +104,7 @@ export default class SchemasValidator { return namespace ?? '_core' } - error (file: string, location: string, error: Error): ValidationError { - return { file, location, message: error.message } + error (file: string, location: string, message: string): ValidationError { + return { file, location, message } } } diff --git a/tools/src/linter/components/base/FileValidator.ts b/tools/src/linter/components/base/FileValidator.ts index a6f9da82e..d5f144dba 100644 --- a/tools/src/linter/components/base/FileValidator.ts +++ b/tools/src/linter/components/base/FileValidator.ts @@ -10,9 +10,8 @@ import ValidatorBase from './ValidatorBase' import { type ValidationError } from 'types' import { type OpenAPIV3 } from 'openapi-types' -import { read_yaml, to_json } from '../../../helpers' -import AJV from 'ajv' -import addFormats from 'ajv-formats' +import { read_yaml } from '../../../helpers' +import JsonSchemaValidator from "../../../_utils/JsonSchemaValidator"; export default class FileValidator extends ValidatorBase { file_path: string @@ -61,11 +60,10 @@ export default class FileValidator extends ValidatorBase { const json_schema_path: string = (this.spec() as any).$schema ?? '' if (json_schema_path === '') return this.error('JSON Schema is required but not found in this file.', '$schema') const schema = read_yaml(json_schema_path) - const ajv = new AJV({ schemaId: 'id' }) - addFormats(ajv) - const validator = ajv.compile(schema) - if (!validator(this.spec())) { - return this.error(`File content does not match JSON schema found in '${json_schema_path}':\n ${to_json(validator.errors)}`) - } + delete schema.$schema + const validator = new JsonSchemaValidator() + const message = validator.validate_data(this.spec(), schema) + if (message != null) + return this.error(`File content does not match JSON schema found in '${json_schema_path}': ${message}`) } } diff --git a/tools/src/tester/SchemaValidator.ts b/tools/src/tester/SchemaValidator.ts index eddf54a09..1dc2898d1 100644 --- a/tools/src/tester/SchemaValidator.ts +++ b/tools/src/tester/SchemaValidator.ts @@ -7,13 +7,12 @@ * compatible open source license. */ -import AJV from 'ajv' -import ajv_errors from 'ajv-errors' -import addFormats from 'ajv-formats' +import JsonSchemaValidator from "../_utils/JsonSchemaValidator"; import { type OpenAPIV3 } from 'openapi-types' import { type Evaluation, Result } from './types/eval.types' import { Logger } from 'Logger' import { to_json } from '../helpers' +import _ from 'lodash' const ADDITIONAL_KEYWORDS = [ 'discriminator', @@ -24,36 +23,24 @@ const ADDITIONAL_KEYWORDS = [ ] export default class SchemaValidator { - private readonly ajv: AJV + private readonly json_validator: JsonSchemaValidator private readonly logger: Logger constructor (spec: OpenAPIV3.Document, logger: Logger) { this.logger = logger - this.ajv = new AJV({ allErrors: true, strict: true }) - addFormats(this.ajv) - for (const keyword of ADDITIONAL_KEYWORDS) this.ajv.addKeyword(keyword) - ajv_errors(this.ajv, { singleError: true }) - const schemas = spec.components?.schemas ?? {} - for (const key in schemas) this.ajv.addSchema(schemas[key], `#/components/schemas/${key}`) + const component_schemas = spec.components?.schemas ?? {} + const reference_schemas = _.mapKeys(component_schemas, (_, name) => `#/components/schemas/${name}`) + this.json_validator = new JsonSchemaValidator(undefined, { reference_schemas, additional_keywords: ADDITIONAL_KEYWORDS, ajv_errors_opts: { singleError: true } }) } validate (schema: OpenAPIV3.SchemaObject, data: any): Evaluation { - const validate = this.ajv.compile(schema) - const valid = validate(data) - if (!valid) { + const message = this.json_validator.validate_data(data as Record, schema) + if (message != null) { this.logger.info(`# ${to_json(schema)}`) this.logger.info(`* ${to_json(data)}`) - this.logger.info(`& ${to_json(validate.errors)}`) + this.logger.info(`& ${to_json(message)}`) + return { result: Result.FAILED, message } } - - var result: Evaluation = { - result: valid ? Result.PASSED : Result.FAILED, - } - - if (!valid) { - result.message = this.ajv.errorsText(validate.errors) - } - - return result + return { result: Result.PASSED } } } diff --git a/tools/src/tester/StoryValidator.ts b/tools/src/tester/StoryValidator.ts index 6b15228d5..e9a2ef097 100644 --- a/tools/src/tester/StoryValidator.ts +++ b/tools/src/tester/StoryValidator.ts @@ -9,20 +9,16 @@ import { Result, StoryEvaluation, StoryFile } from "./types/eval.types"; import * as path from "path"; -import { Ajv2019, ValidateFunction } from 'ajv/dist/2019' -import addFormats from 'ajv-formats' import { read_yaml } from "../helpers"; +import JsonSchemaValidator from "../_utils/JsonSchemaValidator"; export default class StoryValidator { private static readonly SCHEMA_FILE = path.resolve("./json_schemas/test_story.schema.yaml") - private readonly ajv: Ajv2019 - private readonly validate_schema: ValidateFunction + private readonly json_validator: JsonSchemaValidator constructor() { - this.ajv = new Ajv2019({ allErrors: true, strict: false }) - addFormats(this.ajv) const schema = read_yaml(StoryValidator.SCHEMA_FILE) - this.validate_schema = this.ajv.compile(schema) + this.json_validator = new JsonSchemaValidator(schema, { ajv_opts: { strictTypes: false } }) } validate(story_file: StoryFile): StoryEvaluation | undefined { @@ -39,8 +35,8 @@ export default class StoryValidator { } #validate_schema(story_file: StoryFile): StoryEvaluation | undefined { - const valid = this.validate_schema(story_file.story) - if (!valid) return this.#invalid(story_file, this.ajv.errorsText(this.validate_schema.errors)) + const message = this.json_validator.validate_data(story_file.story) + if (message != null) return this.#invalid(story_file, message) } #invalid({ story, display_path, full_path }: StoryFile, message: string): StoryEvaluation { diff --git a/tools/tests/_utils/AjvErrorsParser.test.ts b/tools/tests/_utils/AjvErrorsParser.test.ts new file mode 100644 index 000000000..60b16bb89 --- /dev/null +++ b/tools/tests/_utils/AjvErrorsParser.test.ts @@ -0,0 +1,62 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +*/ + +import { Ajv2019 } from "ajv/dist/2019"; +import AjvErrorsParser from "../../src/_utils/AjvErrorsParser"; + +describe('AjvErrorsParser', () => { + const ajv = new Ajv2019({ allErrors: true, strict: false }) + const parser = new AjvErrorsParser(ajv, { separator: ' | ' , dataVar: 'Obj' }) + const schema = { + type: 'object', + additionalProperties: false, + required: [ 'a_boolean', 'a_number', 'an_array', 'an_enum', 'a_compound_object'], + properties: { + a_boolean: { type: 'boolean' }, + a_number: { type: 'number' }, + a_nullable_string: { type: ['string', 'null'] }, + a_non_nullable_string: { type: 'string' }, + an_array: { type: 'array', items: { type: 'string' }, minItems: 1 }, + an_enum: { type: 'string', enum: ['a', 'b', 'c'] }, + a_compound_object: { + unevaluatedProperties: false, + allOf: [ + { type: 'object', properties: { a: { type: 'string' } } }, + { type: 'object', properties: { b: { type: 'number' } } }, + ], + } + } + } + + const data = { + an_array: [], + an_enum: 'd', + a_compound_object: { stranger: 'danger', hello: 'world' }, + space_odyssey: 42, + thirteen_sentinels: 426 + } + + const func = ajv.compile(schema) + func(data) + + it('can parse multiple errors', () => { + expect(parser.parse(func.errors)).toEqual( + 'Obj contains unsupported properties: space_odyssey, thirteen_sentinels, stranger, hello | ' + + 'Obj MUST contain the missing properties: a_boolean, a_number | ' + + 'Obj/an_enum MUST be equal to one of the allowed values: a, b, c | ' + + 'Obj/an_array must NOT have fewer than 1 items' + ) + }); + + it('can parse empty errors', () => { + const empty_func = ajv.compile({ type: 'object' }) + empty_func({}) + expect(parser.parse(empty_func.errors)).toEqual(ajv.errorsText(undefined)) + }); +}); \ No newline at end of file diff --git a/tools/tests/linter/SchemasValidator.test.ts b/tools/tests/linter/SchemasValidator.test.ts index c2d48dc4f..c0929b6ec 100644 --- a/tools/tests/linter/SchemasValidator.test.ts +++ b/tools/tests/linter/SchemasValidator.test.ts @@ -16,12 +16,12 @@ test('validate() - named_schemas', () => { { file: 'schemas/actions.yaml', location: '#/components/schemas/Bark', - message: 'schema is invalid: data/type must be equal to one of the allowed values, data/type must be array, data/type must match a schema in anyOf' + message: 'data/type MUST be equal to one of the allowed values: array, boolean, integer, null, number, object, string --- data/type must be array --- data/type must match a schema in anyOf' }, { file: 'schemas/animals.yaml', location: '#/components/schemas/Dog', - message: 'schema is invalid: data/type must be equal to one of the allowed values, data/type must be array, data/type must match a schema in anyOf' + message: 'data/type MUST be equal to one of the allowed values: array, boolean, integer, null, number, object, string --- data/type must be array --- data/type must match a schema in anyOf' } ]) }) @@ -32,22 +32,22 @@ test('validate() - anonymous_schemas', () => { { file: '_global_parameters.yaml', location: 'human', - message: 'schema is invalid: data/type must be equal to one of the allowed values, data/type must be array, data/type must match a schema in anyOf' + message: 'data/type MUST be equal to one of the allowed values: array, boolean, integer, null, number, object, string --- data/type must be array --- data/type must match a schema in anyOf' }, { file: 'namespaces/_core.yaml', location: '#/components/parameters/adopt::path.docket', - message: 'schema is invalid: data/type must be equal to one of the allowed values, data/type must be array, data/type must match a schema in anyOf' + message: 'data/type MUST be equal to one of the allowed values: array, boolean, integer, null, number, object, string --- data/type must be array --- data/type must match a schema in anyOf' }, { file: 'namespaces/adopt.yaml', location: '#/components/requestBodies/adopt/content/application/json', - message: 'schema is invalid: data/type must be equal to one of the allowed values, data/type must be array, data/type must match a schema in anyOf' + message: 'data/type MUST be equal to one of the allowed values: array, boolean, integer, null, number, object, string --- data/type must be array --- data/type must match a schema in anyOf' }, { file: 'namespaces/_core.yaml', location: '#/components/responses/adopt@200/content/application/json', - message: 'schema is invalid: data/type must be equal to one of the allowed values, data/type must be array, data/type must match a schema in anyOf' + message: 'data/type MUST be equal to one of the allowed values: array, boolean, integer, null, number, object, string --- data/type must be array --- data/type must match a schema in anyOf' } ]) }) diff --git a/tools/tests/linter/SupersededOperationsFile.test.ts b/tools/tests/linter/SupersededOperationsFile.test.ts index 423b68520..db68fdc1b 100644 --- a/tools/tests/linter/SupersededOperationsFile.test.ts +++ b/tools/tests/linter/SupersededOperationsFile.test.ts @@ -15,26 +15,7 @@ describe('validate()', () => { expect(validator.validate()).toEqual([ { file: 'superseded_operations/invalid_schema.yaml', - message: "File content does not match JSON schema found in './json_schemas/_superseded_operations.schema.yaml':\n " + - JSON.stringify([ - { - "instancePath": "/~1hello~1world/operations/1", - "schemaPath": "#/patternProperties/%5E~1/properties/operations/items/enum", - "keyword": "enum", - "params": { - "allowedValues": [ - "GET", - "POST", - "PUT", - "DELETE", - "HEAD", - "OPTIONS", - "PATCH" - ] - }, - "message": "must be equal to one of the allowed values" - } - ], null, 2), + message: "File content does not match JSON schema found in './json_schemas/_superseded_operations.schema.yaml': data/~1hello~1world/operations/1 MUST be equal to one of the allowed values: GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH" }, ]) }) diff --git a/tools/tests/tester/StoryValidator.test.ts b/tools/tests/tester/StoryValidator.test.ts index c3a42d372..4a755b7a2 100644 --- a/tools/tests/tester/StoryValidator.test.ts +++ b/tools/tests/tester/StoryValidator.test.ts @@ -29,7 +29,10 @@ describe('StoryValidator', () => { test('invalid story', () => { const evaluation = validate('tools/tests/tester/fixtures/invalid_story.yaml') expect(evaluation?.result).toBe('ERROR') - expect(evaluation?.message).toBe("Invalid Story: data/epilogues/0 must NOT have unevaluated properties, data/chapters/0 must have required property 'method', data/chapters/1/method must be equal to one of the allowed values") + expect(evaluation?.message).toBe("Invalid Story: " + + "data/epilogues/0 contains unsupported properties: response --- " + + "data/chapters/0 MUST contain the missing properties: method --- " + + "data/chapters/1/method MUST be equal to one of the allowed values: GET, PUT, POST, DELETE, PATCH, HEAD, OPTIONS") }) test('valid story', () => { diff --git a/tools/tests/tester/fixtures/evals/failed/invalid_data.yaml b/tools/tests/tester/fixtures/evals/failed/invalid_data.yaml index 65a65f681..0e5816933 100644 --- a/tools/tests/tester/fixtures/evals/failed/invalid_data.yaml +++ b/tools/tests/tester/fixtures/evals/failed/invalid_data.yaml @@ -33,7 +33,7 @@ chapters: result: PASSED request_body: result: FAILED - message: data must NOT have additional properties + message: 'data contains unsupported properties: aliases' response: status: result: PASSED @@ -58,7 +58,7 @@ chapters: message: expected acknowledged='false', got 'true', missing shards_acknowledged='true' payload_schema: result: FAILED - message: data must NOT have additional properties + message: 'data contains unsupported properties: acknowledged' - title: This chapter should fail because the response status does not match. overall: result: ERROR diff --git a/tools/tests/tester/test.test.ts b/tools/tests/tester/test.test.ts index e2b2ea330..b82e2abd7 100644 --- a/tools/tests/tester/test.test.ts +++ b/tools/tests/tester/test.test.ts @@ -41,7 +41,7 @@ test('displays story filename', () => { test('invalid story', () => { expect(spec(['--tests', 'tools/tests/tester/fixtures/invalid_story.yaml']).stdout).toContain( - `${ansi.gray("(Invalid Story: data/epilogues/0 must NOT have unevaluated properties, ...)")}` + `\x1b[90m(Invalid Story:` ) })