Skip to content

Commit

Permalink
Fail on missing properties.
Browse files Browse the repository at this point in the history
Signed-off-by: dblock <[email protected]>
  • Loading branch information
dblock committed Jun 18, 2024
1 parent 49cd814 commit b650c9d
Show file tree
Hide file tree
Showing 14 changed files with 155 additions and 20 deletions.
27 changes: 15 additions & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"@eslint/js": "^9.1.1",
"@types/jest": "^29.5.12",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"ajv-errors": "^3.0.0",
"eslint": "^8.57.0",
"eslint-config-standard-with-typescript": "^43.0.1",
"eslint-plugin-eslint-comments": "^3.2.0",
Expand Down
2 changes: 1 addition & 1 deletion tools/src/linter/SchemaRefsValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export default class SchemaRefsValidator {

#find_refs_in_namespaces_folder (): void {
const search = (obj: any): void => {
const ref: string = obj.$ref ?? ''
const ref: string = obj?.$ref ?? ''
if (ref !== '') {
const file = ref.split('#')[0].replace('../', '')
const name = ref.split('/').pop() ?? ''
Expand Down
6 changes: 4 additions & 2 deletions tools/src/merger/OpenApiMerger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,9 @@ export default class OpenApiMerger {
const spec = read_yaml(`${folder}/${file}`)
const category = file.split('.yaml')[0]
this.redirect_refs_in_schema(category, spec)
this.schemas[category] = spec.components.schemas as Record<string, OpenAPIV3.SchemaObject>
if (spec.components?.schemas !== undefined) {
this.schemas[category] = spec.components?.schemas
}
})

Object.entries(this.schemas).forEach(([category, schemas]) => {
Expand All @@ -97,7 +99,7 @@ export default class OpenApiMerger {

// Redirect schema references in schema files to local references in single-file spec.
redirect_refs_in_schema (category: string, obj: any): void {
const ref: string = obj.$ref ?? ''
const ref: string = obj?.$ref ?? ''
if (ref !== '') {
if (ref.startsWith('#/components/schemas')) { obj.$ref = `#/components/schemas/${category}:${ref.split('/').pop()}` } else {
const other_category = ref.match(/(.*)\.yaml/)?.[1] ?? ''
Expand Down
49 changes: 49 additions & 0 deletions tools/src/tester/MergedOpenApiSpec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* 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 { type OpenAPIV3 } from 'openapi-types'
import { Logger } from '../Logger'
import { SpecificationContext } from '../linter/utils';
import { SchemaVisitor } from '../linter/utils/SpecificationVisitor';
import OpenApiMerger from '../merger/OpenApiMerger';

// An augmented spec with additionalProperties: false.
export default class MergedOpenApiSpec {
logger: Logger
file_path: string
protected _spec: OpenAPIV3.Document | undefined

constructor (spec_path: string, logger: Logger = new Logger()) {
this.logger = logger
this.file_path = spec_path
}

spec (): OpenAPIV3.Document {
if (this._spec) return this._spec
const spec = (new OpenApiMerger(this.file_path, this.logger)).merge()
const ctx = new SpecificationContext(this.file_path)
this.inject_additional_properties(ctx, spec)
this._spec = spec
return this._spec
}

private inject_additional_properties(ctx: SpecificationContext, spec: OpenAPIV3.Document): void {
const visitor = new SchemaVisitor((_ctx, schema) => {
if ('properties' in schema && schema.additionalProperties === undefined) {
// causes any undeclared field in the response to produce an error
(schema as any).additionalProperties = {
not: true,
errorMessage: "property is not defined in the spec"
}
}
});

visitor.visit_specification(ctx, spec)
}
}
2 changes: 2 additions & 0 deletions tools/src/tester/SchemaValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/

import AJV from 'ajv'
import ajv_errors from 'ajv-errors'
import addFormats from 'ajv-formats'
import { type OpenAPIV3 } from 'openapi-types'
import { type Evaluation, Result } from './types/eval.types'
Expand All @@ -17,6 +18,7 @@ export default class SchemaValidator {
constructor (spec: OpenAPIV3.Document) {
this.ajv = new AJV({ allErrors: true, strict: true })
addFormats(this.ajv)
ajv_errors(this.ajv, { singleError: true })
this.ajv.addKeyword('discriminator')
const schemas = spec.components?.schemas ?? {}
for (const key in schemas) this.ajv.addSchema(schemas[key], `#/components/schemas/${key}`)
Expand Down
5 changes: 2 additions & 3 deletions tools/src/tester/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
* compatible open source license.
*/

import OpenApiMerger from '../merger/OpenApiMerger'
import { LogLevel, Logger } from '../Logger'
import TestRunner from './TestRunner'
import { Command, Option } from '@commander-js/extra-typings'
Expand All @@ -26,6 +25,7 @@ import StoryEvaluator from './StoryEvaluator'
import { ConsoleResultLogger } from './ResultLogger'
import * as process from 'node:process'
import SupplementalChapterEvaluator from './SupplementalChapterEvaluator'
import MergedOpenApiSpec from './MergedOpenApiSpec'

const command = new Command()
.description('Run test stories against the OpenSearch spec.')
Expand All @@ -48,8 +48,7 @@ const command = new Command()
const opts = command.opts()
const logger = new Logger(opts.verbose ? LogLevel.info : LogLevel.warn)

const spec = (new OpenApiMerger(opts.specPath, logger)).merge()

const spec = (new MergedOpenApiSpec(opts.specPath, logger)).spec()
const http_client = new OpenSearchHttpClient(get_opensearch_opts_from_cli(opts))
const chapter_reader = new ChapterReader(http_client)
const chapter_evaluator = new ChapterEvaluator(new OperationLocator(spec), chapter_reader, new SchemaValidator(spec))
Expand Down
25 changes: 25 additions & 0 deletions tools/tests/tester/MergedOpenApiSpec.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* 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 { Logger } from 'Logger'
import MergedOpenApiSpec from "tester/MergedOpenApiSpec"

const spec = new MergedOpenApiSpec('tools/tests/tester/fixtures/specs/complete', new Logger())

test('adds additionalProperties when not present', () => {
const responses: any = spec.spec().components?.responses
const schema = responses['info@200'].content['application/json'].schema
expect(schema.additionalProperties).toEqual({ not: true, errorMessage: 'property is not defined in the spec' })
})

test('does not add additionalProperties when present', () => {
const responses: any = spec.spec().components?.responses
const schema = responses['info@201'].content['application/json'].schema
expect(schema.additionalProperties).toEqual(true)
})
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ test('resolve_value', () => {
},
g: 123
}
expect(story_outputs.resolve_value(value)).toEqual(
expect(story_outputs.resolve_value(value)).toEqual(
{
a: 1,
b: [1, 2, 3],
Expand All @@ -55,7 +55,7 @@ test('resolve_params', () => {
c: 3,
d: 'str'
}
expect(story_outputs.resolve_params(parameters)).toEqual({
expect(story_outputs.resolve_params(parameters)).toEqual({
a: 1,
b: 2,
c: 3,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
openapi: 3.1.0
info:
title: ''
version: ''
5 changes: 5 additions & 0 deletions tools/tests/tester/fixtures/specs/complete/_info.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
$schema: should-be-ignored

title: OpenSearch API
description: OpenSearch API
version: 1.0.0
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
$schema: should-be-ignored
39 changes: 39 additions & 0 deletions tools/tests/tester/fixtures/specs/complete/namespaces/index.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
openapi: 3.1.0
info:
title: OpenSearch API
description: OpenSearch API
version: 1.0.0
paths:
'/index':
get:
operationId: get.0
responses:
'200':
$ref: '#/components/responses/info@200'
'201':
$ref: '#/components/responses/info@201'
components:
responses:
info@200:
description: ''
content:
application/json:
schema:
type: object
properties:
tagline:
type: string
required:
- tagline
info@201:
description: ''
content:
application/json:
schema:
type: object
properties:
tagline:
type: string
required:
- tagline
additionalProperties: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
openapi: 3.1.0
info:
title: OpenSearch API
description: OpenSearch API
version: 1.0.0

0 comments on commit b650c9d

Please sign in to comment.