Added Swagger

This commit is contained in:
2020-06-10 08:25:21 +02:00
parent 5c6f37eaf7
commit af76cbca87
257 changed files with 48861 additions and 12 deletions
@@ -0,0 +1,94 @@
import expect from "expect"
import validateHelper, { expectNoErrors } from "../validate-helper.js"
describe("validation plugin - semantic - 2and3 operations", () => {
describe("Operations must have unique operationIds", () => {
describe("OpenAPI 3.0", () => {
it("should return an error when operationId collisions exist", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/": {
get: {
operationId: "myId"
},
post: {
operationId: "myId"
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.message).toEqual(`Operations must have unique operationIds.`)
expect(firstError.path).toEqual(["paths", "/", "post", "operationId"])
})
})
it("should not return an error when operationId collisions don't exist", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/": {
get: {
operationId: "myId1"
},
post: {
operationId: "myId2"
}
}
}
}
return expectNoErrors(spec)
})
})
describe("Swagger 2.0", () => {
it("should return an error when operationId collisions exist", () => {
const spec = {
swagger: "2.0",
paths: {
"/": {
get: {
operationId: "myId"
},
post: {
operationId: "myId"
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.message).toEqual(`Operations must have unique operationIds.`)
expect(firstError.path).toEqual(["paths", "/", "post", "operationId"])
})
})
it("should not return an error when operationId collisions don't exist", () => {
const spec = {
swagger: "2.0",
paths: {
"/": {
get: {
operationId: "myId1"
},
post: {
operationId: "myId2"
}
}
}
}
return expectNoErrors(spec)
})
})
})
})
@@ -0,0 +1,997 @@
import expect from "expect"
import validateHelper from "../validate-helper.js"
describe(`validation plugin - semantic - 2and3 parameters`, () => {
describe(`parameters must have unique name + in values`, () => {
describe(`direct siblings`, () => {
it("should return an error for an invalid Swagger 2 definition", () => {
const spec = {
swagger: "2.0",
"paths": {
"/pets": {
"parameters": [
{
"name": "pathLevel",
"in": "query",
"description": "tags to filter by",
"type": "string"
},
{
"name": "pathLevel",
"in": "query",
"description": "tags to filter by",
"type": "string"
},
],
"get": {
"parameters": [
{
"name": "opLevel",
"in": "query",
"description": "tags to filter by",
"type": "string"
},
{
"name": "opLevel",
"in": "query",
"description": "tags to filter by",
"type": "string"
},
]
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
const secondError = allErrors[1]
expect(allErrors.length).toEqual(2)
expect(firstError.path).toEqual(["paths", "/pets", "parameters", "1"])
expect(firstError.message).toEqual("Sibling parameters must have unique name + in values")
expect(secondError.path).toEqual(["paths", "/pets", "get", "parameters", "1"])
expect(secondError.message).toEqual("Sibling parameters must have unique name + in values")
})
})
it("should return an error for an invalid OpenAPI 3 definition", () => {
const spec = {
openapi: "3.0.0",
"paths": {
"/pets": {
"parameters": [
{
"name": "pathLevel",
"in": "query",
"description": "tags to filter by",
"schema": {
"type": "string"
}
},
{
"name": "pathLevel",
"in": "query",
"description": "tags to filter by",
"schema": {
"type": "string"
}
},
],
"get": {
"parameters": [
{
"name": "opLevel",
"in": "query",
"description": "tags to filter by",
"schema": {
"type": "string"
}
},
{
"name": "opLevel",
"in": "query",
"description": "tags to filter by",
"schema": {
"type": "string"
}
},
]
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
const secondError = allErrors[1]
expect(allErrors.length).toEqual(2)
expect(firstError.path).toEqual(["paths", "/pets", "parameters", "1"])
expect(firstError.message).toEqual("Sibling parameters must have unique name + in values")
expect(secondError.path).toEqual(["paths", "/pets", "get", "parameters", "1"])
expect(secondError.message).toEqual("Sibling parameters must have unique name + in values")
})
})
it("should return no errors for a valid Swagger 2 definition", () => {
const spec = {
swagger: "2.0",
"paths": {
"/pets": {
"get": {
"parameters": [
{
"name": "wags",
"in": "query",
"description": "wags to filter by",
"type": "string"
},
{
"name": "tags",
"in": "query",
"description": "tags to filter by",
"type": "string"
},
]
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(0)
})
})
it("should return no errors for a valid OpenAPI 3 definition", () => {
const spec = {
openapi: "3.0.0",
"paths": {
"/pets": {
"get": {
"parameters": [
{
"name": "wags",
"in": "query",
"description": "wags to filter by",
"type": "string"
},
{
"name": "tags",
"in": "query",
"description": "tags to filter by",
"type": "string"
},
]
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(0)
})
})
})
describe(`inherited siblings`, () => {
it("should return no errors for a valid Swagger 2 definition due to inheritance", () => {
const spec = {
swagger: "2.0",
parameters: {
MyParam: {
name: "one",
in: "query"
}
},
"paths": {
"/pets": {
"parameters": [
{
name: "one",
in: "query"
},
{
name: "two",
in: "query"
}
],
"get": {
"parameters": [
{
name: "two",
in: "query"
},
{
name: "three",
in: "query"
}
]
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(0)
})
})
it("should return no errors for a valid OpenAPI 3 definition due to inheritance", () => {
const spec = {
openapi: "3.0.0",
parameters: {
MyParam: {
name: "one",
in: "query"
}
},
"paths": {
"/pets": {
"parameters": [
{
name: "one",
in: "query"
},
{
name: "two",
in: "query"
}
],
"get": {
"parameters": [
{
name: "two",
in: "query"
},
{
name: "three",
in: "query"
}
]
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(0)
})
})
it("should not return an error for root parameters in Swagger 2", () => {
const spec = {
swagger: "2.0",
parameters: {
MyParam: {
name: "one",
in: "query"
}
},
"paths": {
"/pets": {
"parameters": [
{
name: "otherParam",
in: "query"
}
],
"get": {
"parameters": [
{
name: "one",
in: "query"
}
]
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(0)
})
})
it("should not return an error for root parameters in OpenAPI 3", () => {
const spec = {
openapi: "3.0.0",
parameters: {
MyParam: {
name: "one",
in: "query"
}
},
"paths": {
"/pets": {
"parameters": [
{
name: "otherParam",
in: "query"
}
],
"get": {
"parameters": [
{
name: "one",
in: "query"
}
]
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(0)
})
})
it("should return no errors for a valid Swagger 2 definition", () => {
const spec = {
swagger: "2.0",
parameters: {
MyParamOne: {
name: "one",
in: "query"
},
MyParamTwo: {
name: "anotherParam1",
in: "query"
},
},
"paths": {
"/pets/{one}/{two}": {
"parameters": [
{
name: "one",
in: "path",
required: true
},
{
name: "two",
in: "query"
},
{
name: "anotherParam2",
in: "query"
},
],
"get": {
"parameters": [
{
name: "two",
in: "path",
required: true
},
{
name: "three",
in: "query"
},
{
name: "anotherParam3",
in: "query"
},
]
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors).toEqual([])
})
})
it("should return no errors for a valid OpenAPI 3 definition", () => {
const spec = {
openapi: "3.0.0",
parameters: {
MyParamOne: {
name: "one",
in: "query"
},
MyParamTwo: {
name: "anotherParam1",
in: "query"
},
},
"paths": {
"/pets/{one}/{two}": {
"parameters": [
{
name: "one",
in: "path",
required: true
},
{
name: "two",
in: "query"
},
{
name: "anotherParam2",
in: "query"
},
],
"get": {
"parameters": [
{
name: "two",
in: "path",
required: true
},
{
name: "three",
in: "query"
},
{
name: "anotherParam3",
in: "query"
},
]
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(0)
})
})
})
})
describe(`parameter defaults must be present in enums`, () => {
it("should return an error for an invalid Swagger 2 definition", () => {
const spec = {
swagger: "2.0",
"paths": {
"/pets": {
"get": {
"parameters": [
{
"name": "num",
"in": "query",
"type": "number",
enum: [1, 2, 3],
default: 0
},
]
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.path).toEqual(["paths", "/pets", "get", "parameters", "0", "default"])
expect(firstError.message).toEqual("Default values must be present in `enum`")
})
})
it("should return an error for an invalid OpenAPI 3 definition", () => {
const spec = {
openapi: "3.0.0",
"paths": {
"/pets": {
"get": {
"parameters": [
{
"name": "num",
"in": "query",
"type": "number",
schema: {
enum: [1, 2, 3],
default: 0
}
},
]
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.path).toEqual(["paths", "/pets", "get", "parameters", "0", "schema", "default"])
expect(firstError.message).toEqual("Default values must be present in `enum`")
})
})
it("should return an error for an invalid OpenAPI 3 definition", () => {
const spec = {
openapi: "3.0.0",
components: {
parameters: {
MyParam: {
"name": "num",
"in": "query",
"type": "number",
schema: {
enum: [1, 2, 3],
default: 0
}
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.path).toEqual(["components", "parameters", "MyParam", "schema", "default"])
expect(firstError.message).toEqual("Default values must be present in `enum`")
})
})
it("should return no errors for a Swagger 2 definition without default set", () => {
const spec = {
swagger: "2.0",
"paths": {
"/pets": {
"get": {
"parameters": [
{
"name": "num",
"in": "query",
"type": "number",
enum: [1, 2, 3]
},
]
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(0)
})
})
it("should return no errors for an OpenAPI 3 definition without default set", () => {
const spec = {
openapi: "3.0.0",
"paths": {
"/pets": {
"get": {
"parameters": [
{
"name": "num",
"in": "query",
"type": "number",
schema: {
enum: [1, 2, 3]
}
},
]
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(0)
})
})
it("should return no errors for a Swagger 2 definition without enum set", () => {
const spec = {
swagger: "2.0",
"paths": {
"/pets": {
"get": {
"parameters": [
{
"name": "num",
"in": "query",
"type": "number",
default: 0
},
]
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(0)
})
})
it("should return no errors for an OpenAPI 3 definition without enum set", () => {
const spec = {
openapi: "3.0.0",
"paths": {
"/pets": {
"get": {
"parameters": [
{
"name": "num",
"in": "query",
"type": "number",
schema: {
default: 0
}
},
]
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(0)
})
})
})
describe(`path parameters must be in path definition`, () => {
it("should return no errors for a valid Swagger 2 definition", () => {
const spec = {
swagger: "2.0",
info: {
"title": "Correct path parameters in path",
"version": "1.0.0"
},
"paths": {
"/foo/{param1}/{param2}/{param3}": {
"parameters": [
{
name: "param1",
in: "path",
required: true,
type: "string"
},
{
$ref: "#/parameters/param3"
}
],
"get": {
"parameters": [
{
name: "param2",
in: "path",
required: true,
type: "string"
}
],
responses: {
"200": {
"description": "ok"
}
}
}
}
},
parameters: {
param3: {
name: "param3",
in: "path",
required: true,
type: "string"
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(0)
})
})
it("should return 2 errors for each path parameter that isn't in the path in spec 2", () => {
const spec = {
swagger: "2.0",
info: {
"title": "Unused path parameters in path",
"version": "1.0.0"
},
"paths": {
"/foo": {
"parameters": [
{
name: "param1",
in: "path",
required: true,
type: "string"
}
],
"get": {
"parameters": [
{
name: "param2",
in: "path",
required: true,
type: "string"
}
],
responses: {
"200": {
"description": "ok"
}
}
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(2)
const firstError = allErrors[0]
expect(firstError.path).toEqual(["paths", "/foo", "parameters", "0","name"])
expect(firstError.message).toEqual(`Path parameter "param1" must have the corresponding {param1} segment in the "/foo" path`)
const secondError = allErrors[1]
expect(secondError.path).toEqual(["paths", "/foo", "get", "parameters", "0","name"])
expect(secondError.message).toEqual(`Path parameter "param2" must have the corresponding {param2} segment in the "/foo" path`)
})
})
it("should return 2 errors for each referenced path parameter that isn't in the path for 2 spec", () => {
const spec = {
swagger: "2.0",
info: {
"title": "Unused path parameters in path",
"version": "1.0.0"
},
"paths": {
"/foo/{param1}/{param2}": {
"parameters": [
{
name: "param1",
in: "path",
required: true,
type: "string"
},
{
$ref: "#/parameters/param3"
}
],
"get": {
"parameters": [
{
name: "param2",
in: "path",
required: true,
type: "string"
},
{
$ref: "#/parameters/param4"
}
],
responses: {
"200": {
"description": "ok"
}
}
}
}
},
parameters: {
param3: {
name: "param3",
in: "path",
required: true,
type: "string"
},
param4: {
name: "param4",
in: "path",
required: true,
type: "string"
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(2)
const firstError = allErrors[0]
expect(firstError.path).toEqual(["paths", "/foo/{param1}/{param2}", "parameters", "1","name"])
expect(firstError.message).toEqual(`Path parameter "param3" must have the corresponding {param3} segment in the "/foo/{param1}/{param2}" path`)
const secondError = allErrors[1]
expect(secondError.path).toEqual(["paths", "/foo/{param1}/{param2}","get", "parameters", "1","name"])
expect(secondError.message).toEqual(`Path parameter "param4" must have the corresponding {param4} segment in the "/foo/{param1}/{param2}" path`)
})
})
it("should return no errors for a valid Swagger 3 definition", () => {
const spec = {
swagger: "3.0",
info: {
"title": "Correct path parameters in path",
"version": "1.0.0"
},
"paths": {
"/foo/{param1}/{param2}/{param3}": {
"parameters": [
{
name: "param1",
in: "path",
required: true,
type: "string"
},
{
$ref: "#/parameters/param3"
}
],
"get": {
"parameters": [
{
name: "param2",
in: "path",
required: true,
type: "string"
}
],
responses: {
"200": {
"description": "ok"
}
}
}
}
},
components: {
parameters: {
param3: {
name: "param3",
in: "path",
required: true,
type: "string"
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(0)
})
})
it("should return 2 errors for each path parameter that isn't in the path in spec 3", () => {
const spec = {
openapi: "3.0.2",
info: {
"title": "Unused path parameters in path",
"version": "1.0.0"
},
"paths": {
"/foo": {
"parameters": [
{
name: "param1",
in: "path",
required: true,
type: "string"
}
],
"get": {
"parameters": [
{
name: "param2",
in: "path",
required: true,
type: "string"
}
],
responses: {
"200": {
"description": "ok"
}
}
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(2)
const firstError = allErrors[0]
expect(firstError.path).toEqual(["paths", "/foo", "parameters", "0","name"])
expect(firstError.message).toEqual(`Path parameter "param1" must have the corresponding {param1} segment in the "/foo" path`)
const secondError = allErrors[1]
expect(secondError.path).toEqual(["paths", "/foo", "get", "parameters", "0","name"])
expect(secondError.message).toEqual(`Path parameter "param2" must have the corresponding {param2} segment in the "/foo" path`)
})
})
it("should return 2 errors for each referenced path parameter that isn't in the path for 3 spec", () => {
const spec = {
openapi: "3.0.2",
info: {
"title": "Unused path parameters in path",
"version": "1.0.0"
},
"paths": {
"/foo/{param1}/{param2}": {
"parameters": [
{
name: "param1",
in: "path",
required: true,
type: "string"
},
{
$ref: "#/components/parameters/param3"
}
],
"get": {
"parameters": [
{
name: "param2",
in: "path",
required: true,
type: "string"
},
{
$ref: "#/components/parameters/param4"
}
],
responses: {
"200": {
"description": "ok"
}
}
}
}
},
components: {
parameters: {
param3: {
name: "param3",
in: "path",
required: true,
type: "string"
},
param4: {
name: "param4",
in: "path",
required: true,
type: "string"
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(2)
const firstError = allErrors[0]
expect(firstError.path).toEqual(["paths", "/foo/{param1}/{param2}", "parameters", "1","name"])
expect(firstError.message).toEqual(`Path parameter "param3" must have the corresponding {param3} segment in the "/foo/{param1}/{param2}" path`)
const secondError = allErrors[1]
expect(secondError.path).toEqual(["paths", "/foo/{param1}/{param2}","get", "parameters", "1","name"])
expect(secondError.message).toEqual(`Path parameter "param4" must have the corresponding {param4} segment in the "/foo/{param1}/{param2}" path`)
})
})
})
})
@@ -0,0 +1,484 @@
/* eslint-env mocha */
import expect from "expect"
import validateHelper, { expectNoErrors } from "../validate-helper.js"
describe("validation plugin - semantic - 2and3 paths", () => {
describe("Paths cannot have query strings in them", () => {
it("should return one problem for an stray '?' in a Swagger 2 path string", function(){
const spec = {
swagger: "2.0",
paths: {
"/report?asdf=123": {
}
}
}
return validateHelper(spec)
.then( system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(1)
const firstError = allErrors[0]
expect(firstError.message).toEqual("Query strings in paths are not allowed.")
expect(firstError.path).toEqual(["paths", "/report?asdf=123"])
})
})
it("should return one problem for an stray '?' in an OpenAPI 3 path string", function(){
const spec = {
openapi: "3.0.0",
paths: {
"/report?asdf=123": {
}
}
}
return validateHelper(spec)
.then( system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(1)
const firstError = allErrors[0]
expect(firstError.message).toEqual("Query strings in paths are not allowed.")
expect(firstError.path).toEqual(["paths", "/report?asdf=123"])
})
})
it("should return no problems for a correct Swagger 2 path template", function(){
const spec = {
swagger: "2.0",
paths: {
"/CoolPath/{id}": {
parameters: [{
name: "id",
in: "path",
required: true
}]
}
}
}
return expectNoErrors(spec)
})
it("should return no problems for a correct OpenAPI 3 path template", function(){
const spec = {
openapi: "3.0.0",
paths: {
"/CoolPath/{id}": {
parameters: [{
name: "id",
in: "path",
required: true
}]
}
}
}
return expectNoErrors(spec)
})
})
describe("Path parameter definitions need matching paramater declarations", function(){
describe("OpenAPI 3", () => {
it("should not return problems for a valid path-level definiton/declaration pair", function(){
const spec = {
openapi: "3.0.0",
paths: {
"/CoolPath/{id}": {
parameters: [{
name: "id",
in: "path",
description: "An id",
required: true
}]
}
}
}
return expectNoErrors(spec)
})
it("should not return problems for a valid path-level definiton/declaration pair using a $ref", function(){
const spec = {
openapi: "3.0.0",
paths: {
"/CoolPath/{id}": {
parameters: [
{ $ref: "#/parameters/id" }
]
}
},
parameters: {
id: {
name: "id",
in: "path",
description: "An id",
required: true
}
}
}
return expectNoErrors(spec)
})
it("should not return problems for a valid operation-level definiton/declaration pair", function(){
const spec = {
openapi: "3.0.0",
paths: {
"/CoolPath/{id}": {
get: {
parameters: [{
name: "id",
in: "path",
description: "An id",
required: true
}]
}
}
}
}
return expectNoErrors(spec)
})
it("should return one problem for a path parameter defined at the operation level that is not present within every operation on the path", function(){
const spec = {
openapi: "3.0.0",
paths: {
"/CoolPath/{id}": {
get: {
parameters: [{
name: "id",
in: "path",
description: "An id",
required: true
}]
},
post: {
description: "the path parameter definition is missing here"
},
delete: {
description: "the path parameter definition is missing here"
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.message).toEqual( `Declared path parameter \"id\" needs to be defined within every operation in the path (missing in "post", "delete"), or moved to the path-level parameters object`)
expect(firstError.path).toEqual(["paths", "/CoolPath/{id}"])
})
})
it("should return one problem when the definition is completely absent", function(){
const spec = {
openapi: "3.0.0",
paths: {
"/CoolPath/{id}": {
parameters: []
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.message).toEqual( "Declared path parameter \"id\" needs to be defined as a path parameter at either the path or operation level")
expect(firstError.path).toEqual(["paths", "/CoolPath/{id}"])
})
})
it("should return one error when no parameters are defined", function(){
const spec = {
openapi: "3.0.0",
paths: {
"/CoolPath/{id}": {}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.message).toEqual( "Declared path parameter \"id\" needs to be defined as a path parameter at either the path or operation level")
expect(firstError.path).toEqual(["paths", "/CoolPath/{id}"])
})
})
it("should return one problem for a missed 'in' value", function(){
const spec = {
openapi: "3.0.0",
paths: {
"/CoolPath/{id}": {
parameters: [{
name: "id",
// in: "path",
description: "An id"
}]
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.message).toEqual( "Declared path parameter \"id\" needs to be defined as a path parameter at either the path or operation level")
expect(firstError.path).toEqual(["paths", "/CoolPath/{id}"])
})
})
})
describe("Swagger 2", () => {
it("should not return problems for a valid path-level definiton/declaration pair", function(){
const spec = {
swagger: "2.0",
paths: {
"/CoolPath/{id}": {
parameters: [{
name: "id",
in: "path",
description: "An id",
required: true
}]
}
}
}
return expectNoErrors(spec)
})
it("should not return problems for a valid path-level definiton/declaration pair using a $ref", function(){
const spec = {
swagger: "2.0",
paths: {
"/CoolPath/{id}": {
parameters: [
{ $ref: "#/parameters/id" }
]
}
},
parameters: {
id: {
name: "id",
in: "path",
description: "An id",
required: true
}
}
}
return expectNoErrors(spec)
})
it("should not return problems for a valid operation-level definiton/declaration pair", function(){
const spec = {
swagger: "2.0",
paths: {
"/CoolPath/{id}": {
get: {
parameters: [{
name: "id",
in: "path",
description: "An id",
required: true
}]
}
}
}
}
return expectNoErrors(spec)
})
it("should return one problem for a path parameter defined at the operation level that is not present within every operation on the path", function(){
const spec = {
swagger: "2.0",
paths: {
"/CoolPath/{id}": {
get: {
parameters: [{
name: "id",
in: "path",
description: "An id",
required: true
}]
},
post: {
description: "the path parameter definition is missing here"
},
delete: {
description: "the path parameter definition is missing here"
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.message).toEqual( `Declared path parameter \"id\" needs to be defined within every operation in the path (missing in "post", "delete"), or moved to the path-level parameters object`)
expect(firstError.path).toEqual(["paths", "/CoolPath/{id}"])
})
})
it("should return one problem when the definition is completely absent", function(){
const spec = {
swagger: "2.0",
paths: {
"/CoolPath/{id}": {
parameters: []
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.message).toEqual( "Declared path parameter \"id\" needs to be defined as a path parameter at either the path or operation level")
expect(firstError.path).toEqual(["paths", "/CoolPath/{id}"])
})
})
it("should return one error when no parameters are defined", function(){
const spec = {
swagger: "2.0",
info: {
title: "test",
version: "1.0.0"
},
paths: {
"/{12345instanceABCDE_instance_12345}": {
get: {
responses: {
"200": {
description: "ok"
}
}
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.message).toEqual( "Declared path parameter \"12345instanceABCDE_instance_12345\" needs to be defined as a path parameter at either the path or operation level")
expect(firstError.path).toEqual(["paths", "/{12345instanceABCDE_instance_12345}"])
})
})
it("should return a well-formed error when ", function(){
const spec = {
swagger: "2.0",
paths: {
"/CoolPath/{id}": {}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.message).toEqual( "Declared path parameter \"id\" needs to be defined as a path parameter at either the path or operation level")
expect(firstError.path).toEqual(["paths", "/CoolPath/{id}"])
})
})
it.skip("should return one problem for a missed 'in' value", function(){
const spec = {
swagger: "2.0",
paths: {
"/CoolPath/{id}": {
parameters: [{
name: "id",
// in: "path",
description: "An id"
}]
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.message).toEqual( "Declared path parameter \"id\" needs to be defined as a path parameter at either the path or operation level")
expect(firstError.path).toEqual(["paths", "/CoolPath/{id}"])
})
})
it("should return a specific error for parameters with different casing characters OpenAPI 3 definition", () => {
const spec = {
openapi: "3.0.1",
"paths": {
"/{myParam}": {
"get": {
"parameters": [
{
"name": "myparam",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
]
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.message).toEqual("Parameter names are case-sensitive. The parameter named \"myparam\" does not match the case used in the path \"/{myParam}\".")
})
})
})
it("should return no errors for parameters with same characters in path and parameters definition", () => {
const spec = {
openapi: "3.0.1",
"paths": {
"/{myParam}": {
"get": {
"parameters": [
{
"name": "myParam",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
]
}
}
}
}
return expectNoErrors(spec)
})
})
})
@@ -0,0 +1,698 @@
import expect from "expect"
import validateHelper, { expectNoErrorsOrWarnings } from "../validate-helper.js"
describe("validation plugin - semantic - 2and3 refs", function() {
this.timeout(10 * 1000)
describe("Ref siblings", () => {
it("should return a warning when another property is a sibling of a $ref in OpenAPI 3", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/CoolPath": {
get: {
$ref: "#/components/schemas/abc",
description: "My very cool get"
}
}
},
components: {
schemas: {
abc: {}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(1)
const firstError = allErrors[0]
expect(firstError.message).toMatch("Sibling values alongside $refs are ignored.\nTo add properties to a $ref, wrap the $ref into allOf, or move the extra properties into the referenced definition (if applicable).")
expect(firstError.level).toEqual("warning")
expect(firstError.path).toEqual(["paths", "/CoolPath", "get", "description"])
})
})
it("should return a warning when another property is a sibling of a $ref in Swagger 2", () => {
const spec = {
swagger: "2.0",
paths: {
"/CoolPath": {
get: {
$ref: "#/definitions/abc",
description: "My very cool get"
}
}
},
definitions: {
abc: {}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(1)
const firstError = allErrors[0]
expect(firstError.message).toMatch("Sibling values alongside $refs are ignored.\nTo add properties to a $ref, wrap the $ref into allOf, or move the extra properties into the referenced definition (if applicable).")
expect(firstError.level).toEqual("warning")
expect(firstError.path).toEqual(["paths", "/CoolPath", "get", "description"])
})
})
it("should return no warnings when a $ref has no siblings in OpenAPI 3", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/CoolPath": {
get: {
$ref: "#/components/schemas/abc"
}
}
},
components: {
schemas: {
abc: {}
}
}
}
return expectNoErrorsOrWarnings(spec)
})
it("should return no warnings when a $ref has no siblings in Swagger 2", () => {
const spec = {
swagger: "2.0",
paths: {
"/CoolPath": {
get: {
$ref: "#/definitions/abc"
}
}
},
definitions: {
abc: {}
}
}
return expectNoErrorsOrWarnings(spec)
})
it("should return no warnings when a path item $ref has siblings in OpenAPI 3", () => {
const spec = {
openapi: "3.0.0",
paths: {
$ref: "#/components/schemas/abc",
"/CoolPath": {
get: {
$ref: "#/components/schemas/abc"
}
}
},
components: {
schemas: {
abc: {}
}
}
}
return expectNoErrorsOrWarnings(spec)
})
it("should return no warnings when a path item $ref has siblings in Swagger 2", () => {
const spec = {
swagger: "2.0",
paths: {
$ref: "#/definitions/abc",
"/CoolPath": {
get: {
$ref: "#/definitions/abc"
}
}
},
definitions: {
abc: {}
}
}
return expectNoErrorsOrWarnings(spec)
})
})
describe("Unused definitions", () => {
it("should return a warning when a definition is declared but not used in OpenAPI 3", () => {
const spec = {
openapi: "3.0.0",
components: {
schemas: {
"x-Foo": {
type: "object"
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(1)
const firstError = allErrors[0]
expect(firstError.message).toMatch("Definition was declared but never used in document")
expect(firstError.level).toEqual("warning")
expect(firstError.path).toEqual(["components", "schemas", "x-Foo"])
})
})
it("should not return a warning when a definition with special characters is properly referenced in OpenAPI 3", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/CoolPath": {
get: {
responses: {
200: {
content: {
"application/json": {
schema: {
$ref: "#/components/schemas/x~1Foo"
}
}
}
},
400: {
content: {
"application/json": {
schema: {
$ref: "#/components/schemas/x~0Bar"
}
}
}
}
}
}
}
},
components: {
schemas: {
"x/Foo": {
type: "object"
},
"x~Bar": {
type: "object"
}
}
}
}
return validateHelper(spec)
.then(system => {
// We want warnings only, without errors about invalid component names
const allWarnings = system.errSelectors.allErrors().toJS()
.filter(err => err.level === "warning")
expect(allWarnings.length).toEqual(0)
})
})
it("should return a warning when a definition is declared but not used in Swagger 2", () => {
const spec = {
swagger: "2.0",
paths: {
"/CoolPath": {}
},
definitions: {
abc: {
type: "string"
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(1)
const firstError = allErrors[0]
expect(firstError.message).toMatch("Definition was declared but never used in document")
expect(firstError.level).toEqual("warning")
expect(firstError.path).toEqual(["definitions", "abc"])
})
})
it("should not return a warning when a definition with special character is declared and used in Swagger 2", () => {
const spec = {
swagger: "2.0",
paths: {
"/CoolPath": {
get: {
responses: {
200: {
schema: {
$ref: "#/definitions/x~1Foo"
}
},
400: {
schema: {
$ref: "#/definitions/x~0Bar"
}
}
}
}
}
},
definitions: {
"x/Foo": {
type: "object"
},
"x~Bar": {
type: "object"
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(0)
})
})
})
describe("Malformed $ref values", () => {
it("should return an error when a JSON pointer lacks a leading `#/` in Swagger 2", () => {
const spec = {
swagger: "2.0",
paths: {
"/CoolPath": {
$ref: "#myObj/abc"
}
},
myObj: {
abc: {
type: "string"
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(1)
const firstError = allErrors[0]
expect(firstError.message).toMatch("$ref paths must begin with `#/`")
expect(firstError.level).toEqual("error")
expect(firstError.path).toEqual(["paths", "/CoolPath", "$ref"])
})
})
it("should return an error when a JSON pointer lacks a leading `#/` in OpenAPI 3", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/CoolPath": {
$ref: "#myObj/abc"
}
},
myObj: {
abc: {
type: "string"
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(1)
const firstError = allErrors[0]
expect(firstError.message).toMatch("$ref paths must begin with `#/`")
expect(firstError.level).toEqual("error")
expect(firstError.path).toEqual(["paths", "/CoolPath", "$ref"])
})
})
it("should return no errors when a JSON pointer is a well-formed remote reference in Swagger 2", () => {
const spec = {
swagger: "2.0",
paths: {
"/CoolPath": {
$ref: "http://google.com#/myObj/abc"
},
},
myObj: {
abc: {
type: "string",
properties: {
$ref: "http://google.com/MyRegularURLReference"
}
}
}
}
return validateHelper(spec)
.then(system => {
const allSemanticErrors = system.errSelectors.allErrors().toJS()
.filter(err => err.source !== "resolver")
expect(allSemanticErrors).toEqual([])
})
})
it("should return no errors when a JSON pointer is a well-formed remote reference in OpenAPI 3", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/CoolPath": {
$ref: "http://google.com#/myObj/abc"
},
},
myObj: {
abc: {
type: "string",
properties: {
$ref: "http://google.com/MyRegularURLReference"
}
}
}
}
return validateHelper(spec)
.then(system => {
const allSemanticErrors = system.errSelectors.allErrors().toJS()
.filter(err => err.source !== "resolver")
expect(allSemanticErrors).toEqual([])
})
})
it("should return an error when a JSON pointer uses incorrect percent-encoding in Swagger 2", () => {
const spec = {
"swagger": "2.0",
"paths": {
"/foo": {
"get": {
"responses": {
"200": {
"description": "Success",
"schema": {
"$ref": "#/definitions/foo bar"
}
}
}
}
}
},
"definitions": {
"foo bar": {
"type": "string"
}
}
}
return validateHelper(spec)
.then(system => {
const allSemanticErrors = system.errSelectors.allErrors().toJS()
.filter(err => err.source !== "resolver")
expect(allSemanticErrors[0]).toInclude({
level: "warning",
message: "Definition was declared but never used in document",
path: ["definitions", "foo bar"]
})
expect(allSemanticErrors.length).toEqual(2)
expect(allSemanticErrors[1]).toInclude({
level: "error",
message: "$ref values must be RFC3986-compliant percent-encoded URIs",
path: ["paths", "/foo", "get", "responses", "200", "schema", "$ref"]
})
})
})
it("should return an error when a JSON pointer uses incorrect percent-encoding in OpenAPI 3", () => {
const spec = {
"openapi": "3.0.0",
"paths": {
"/foo": {
"get": {
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/foo bar"
}
}
}
}
}
}
}
},
components: {
schemas: {
"foo bar": {
"type": "string"
}
}
}
}
return validateHelper(spec)
.then(system => {
const allSemanticErrors = system.errSelectors.allErrors().toJS()
.filter(err => err.source !== "resolver")
expect(allSemanticErrors.length).toEqual(3)
expect(allSemanticErrors[0]).toInclude({
level: "warning",
message: "Definition was declared but never used in document",
path: ["components", "schemas", "foo bar"]
})
expect(allSemanticErrors[1]).toInclude({
level: "error",
message: "$ref values must be RFC3986-compliant percent-encoded URIs",
path: ["paths", "/foo", "get", "responses", "200", "content", "application/json", "schema", "$ref"]
}),
expect(allSemanticErrors[2]).toInclude({
level: "error",
message: "Component names can only contain the characters A-Z a-z 0-9 - . _",
path: ["components", "schemas", "foo bar"]
})
})
})
it("should return no errors when a JSON pointer uses correct percent-encoding in Swagger 2", () => {
const spec = {
"swagger": "2.0",
"paths": {
"/foo": {
"get": {
"responses": {
"200": {
"description": "Success",
"schema": {
"$ref": "#/definitions/foo%20bar"
}
}
}
}
}
},
"definitions": {
"foo bar": {
"type": "string"
}
}
}
return validateHelper(spec)
.then(system => {
const allSemanticErrors = system.errSelectors.allErrors().toJS()
.filter(err => err.source !== "resolver")
expect(allSemanticErrors).toEqual([])
})
})
it("should return no errors when a JSON pointer uses correct percent-encoding in OpenAPI 3", () => {
const spec = {
"openapi": "3.0.0",
"paths": {
"/foo": {
"get": {
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/foo%20bar"
}
}
}
}
}
}
}
},
components: {
schemas: {
"foo bar": {
"type": "string"
}
}
}
}
return validateHelper(spec)
.then(system => {
const allSemanticErrors = system.errSelectors.allErrors().toJS()
.filter(err => err.source !== "resolver")
expect(allSemanticErrors.length).toEqual(1)
expect(allSemanticErrors[0]).toInclude({
level: "error",
message: "Component names can only contain the characters A-Z a-z 0-9 - . _",
path: ["components", "schemas", "foo bar"]
})
})
})
})
describe("Nonexistent $ref pointers", () => {
it("should return an error when a local JSON pointer does not exist in Swagger 2", () => {
const spec = {
swagger: "2.0",
paths: {
"/CoolPath": {
$ref: "#/myObj/DoesNotExist"
}
},
myObj: {
abc: {
type: "string",
properties: {
$ref: "http://google.com/MyRegularURLReference"
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
.filter(err => err.source !== "resolver")
expect(allErrors.length).toEqual(1)
const firstError = allErrors[0]
expect(firstError.message).toMatch("$refs must reference a valid location in the document")
expect(firstError.level).toEqual("error")
expect(firstError.path).toEqual(["paths", "/CoolPath", "$ref"])
})
})
it("should return an error when a local JSON pointer does not exist in OpenAPI 3", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/CoolPath": {
$ref: "#/myObj/DoesNotExist"
}
},
myObj: {
abc: {
type: "string",
properties: {
$ref: "http://google.com/MyRegularURLReference"
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
.filter(err => err.source !== "resolver")
.filter((el, i, arr) => arr.indexOf(el) === i)
expect(allErrors.length).toEqual(1)
const firstError = allErrors[0]
expect(firstError.message).toMatch("$refs must reference a valid location in the document")
expect(firstError.level).toEqual("error")
expect(firstError.path).toEqual(["paths", "/CoolPath", "$ref"])
})
})
it("should return no errors when a JSON pointer exists in Swagger 2", () => {
const spec = {
swagger: "2.0",
paths: {
"/CoolPath": {
$ref: "#/myObj/abc"
},
},
myObj: {
abc: {
type: "string"
}
}
}
return validateHelper(spec)
.then(system => {
const allSemanticErrors = system.errSelectors.allErrors().toJS()
.filter(err => err.source !== "resolver")
expect(allSemanticErrors).toEqual([])
})
})
it("should return no errors when a JSON pointer exists in OpenAPI 3", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/CoolPath": {
$ref: "#/myObj/abc"
},
},
myObj: {
abc: {
type: "string",
properties: {
$ref: "http://google.com/MyRegularURLReference"
}
}
}
}
return validateHelper(spec)
.then(system => {
const allSemanticErrors = system.errSelectors.allErrors().toJS()
.filter(err => err.source !== "resolver")
expect(allSemanticErrors).toEqual([])
})
})
it("should return no errors when a JSON pointer is a remote reference in Swagger 2", () => {
const spec = {
swagger: "2.0",
paths: {
"/CoolPath": {
$ref: "http://google.com#/myObj/abc"
},
},
myObj: {
abc: {
type: "string"
}
}
}
return validateHelper(spec)
.then(system => {
const allSemanticErrors = system.errSelectors.allErrors().toJS()
.filter(err => err.source !== "resolver")
expect(allSemanticErrors).toEqual([])
})
})
it("should return no errors when a JSON pointer is a remote reference in OpenAPI 3", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/CoolPath": {
$ref: "http://google.com#/myObj/abc"
},
},
myObj: {
abc: {
type: "string"
}
}
}
return validateHelper(spec)
.then(system => {
const allSemanticErrors = system.errSelectors.allErrors().toJS()
.filter(err => err.source !== "resolver")
expect(allSemanticErrors).toEqual([])
})
})
})
})
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,217 @@
import expect from "expect"
import validateHelper, { expectNoErrorsOrWarnings } from "../validate-helper.js"
describe("validation plugin - semantic - 2and3 security", () => {
it("should return an error when top-level security references a non-existing security scheme", () => {
const spec = {
swagger: "2.0",
security: [
{
fictional_security_definition: [
"write:pets"
]
}
]
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(1)
const firstError = allErrors[0]
expect(firstError.path).toEqual(["security", "0"])
expect(firstError.message).toMatch("Security requirements must match a security definition")
})
})
it("should return an error when an operation references a non-existing security scheme", () => {
const spec = {
swagger: "2.0",
paths: {
"/": {
get: {
security: [
{
fictional_security_definition: [
"write:pets"
]
}
]
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(1)
const firstError = allErrors[0]
expect(firstError.path).toEqual(["paths", "/", "get", "security", "0"])
expect(firstError.message).toMatch("Security requirements must match a security definition")
})
})
it("should return a warning when a security scheme is defined but not used in OpenAPI 2.0", () => {
const spec = {
swagger: "2.0",
securityDefinitions: {
auth: {
type: "basic"
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.level).toEqual("warning")
expect(firstError.message).toMatch("Security scheme was defined but never used.")
expect(firstError.path).toEqual(["securityDefinitions", "auth"])
})
})
it("should return a warning when a security scheme is defined but not used in OpenAPI 3.0", () => {
const spec = {
openapi: "3.0.0",
components: {
securitySchemes: {
auth: {
type: "http"
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.level).toEqual("warning")
expect(firstError.message).toMatch("Security scheme was defined but never used.")
expect(firstError.path).toEqual(["components", "securitySchemes", "auth"])
})
})
it("should return no errors when a security scheme is defined and referenced globally in OpenAPI 2.0", () => {
const spec = {
swagger: "2.0",
security: [
{ auth: [] }
],
securityDefinitions: {
auth: {
type: "basic"
}
}
}
return expectNoErrorsOrWarnings(spec)
})
it("should return no errors when a security scheme is defined and used in an operation in OpenAPI 2.0", () => {
const spec = {
swagger: "2.0",
paths: {
"/": {
get: {
security: [
{ auth: [] }
]
}
}
},
securityDefinitions: {
auth: {
type: "basic"
}
}
}
return expectNoErrorsOrWarnings(spec)
})
it("should return no errors when a security scheme is defined and referenced globally in OpenAPI 3.0", () => {
const spec = {
openapi: "3.0.0",
security: [
{ auth: [] }
],
components: {
securitySchemes: {
auth: {
type: "http"
}
}
}
}
return expectNoErrorsOrWarnings(spec)
})
it("should return no errors when a security scheme is defined and used in an operation in OpenAPI 3.0", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/": {
get: {
security: [
{ auth: [] }
]
}
}
},
components: {
securitySchemes: {
auth: {
type: "http"
}
}
}
}
return expectNoErrorsOrWarnings(spec)
})
it("should return no errrors when `security` contains multiple requirements combined using logical OR", () => {
const spec = {
swagger: "2.0",
security: [
{},
{ auth: [] }
],
securityDefinitions: {
auth: {
type: "basic"
}
}
}
return expectNoErrorsOrWarnings(spec)
})
it("should return no errors when security schemes are combined using logical AND", () => {
const spec = {
swagger: "2.0",
security: [
{
auth1: [],
auth2: []
}
],
securityDefinitions: {
auth1: {
type: "apiKey"
},
auth2: {
type: "apiKey"
}
}
}
return expectNoErrorsOrWarnings(spec)
})
})
@@ -0,0 +1,132 @@
/**
* @prettier
*/
import expect from "expect"
import validateHelper, { expectNoErrors } from "../validate-helper.js"
describe("validation plugin - semantic - 2and3 tags", () => {
describe("global `tags` array", () => {
describe("OpenAPI 3.0", () => {
it("should return an error when two tag objects are equivalent", () => {
const spec = {
openapi: "3.0.0",
tags: [
{
name: "pet",
},
{
name: "pet",
},
],
}
return validateHelper(spec).then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.message).toEqual("Tag Objects must have unique `name` field values.")
expect(firstError.path).toEqual(["tags", "1"])
})
})
it("should return an error when two tag objects have the same name", () => {
const spec = {
openapi: "3.0.0",
tags: [
{
name: "pet",
},
{
name: "pet",
description: "Everything about your pets",
},
],
}
return validateHelper(spec).then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.message).toEqual("Tag Objects must have unique `name` field values.")
expect(firstError.path).toEqual(["tags", "1"])
})
})
it("should not return an error when two tags have unique names", () => {
const spec = {
openapi: "3.0.0",
tags: [
{
name: "pet",
},
{
name: "store",
},
],
}
return expectNoErrors(spec)
})
})
describe("Swagger 2.0", () => {
it("should return an error when two tag objects are equivalent", () => {
const spec = {
swagger: "2.0",
tags: [
{
name: "pet",
},
{
name: "pet",
},
],
}
return validateHelper(spec).then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.message).toEqual("Tag Objects must have unique `name` field values.")
expect(firstError.path).toEqual(["tags", "1"])
})
})
it("should return an error when two tag objects have the same name", () => {
const spec = {
swagger: "2.0",
tags: [
{
name: "pet",
},
{
name: "pet",
description: "Everything about your pets",
},
],
}
return validateHelper(spec).then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.message).toEqual("Tag Objects must have unique `name` field values.")
expect(firstError.path).toEqual(["tags", "1"])
})
})
it("should not return an error when two tags have unique names", () => {
const spec = {
swagger: "2.0",
tags: [
{
name: "pet",
},
{
name: "store",
},
],
}
return expectNoErrors(spec)
})
})
})
})
@@ -0,0 +1,50 @@
import { expectNoErrors } from "../validate-helper.js"
describe("editor bug #1817 - path parameter semantic error with TRACE", function() {
it("should return no problems for a path parameter defined in a Swagger 2 TRACE operation", function () {
const spec = {
swagger: "2.0",
paths: {
"/CoolPath/{id}": {
trace: {
parameters: [
{
name: "id",
in: "path",
required: true,
schema: {
type: "string"
}
}
]
}
}
}
}
return expectNoErrors(spec)
})
it("should return no problems for a path parameter defined in an OpenAPI 3 TRACE operation", function () {
const spec = {
openapi: "3.0.0",
paths: {
"/CoolPath/{id}": {
trace: {
parameters: [
{
name: "id",
in: "path",
required: true,
schema: {
type: "string"
}
}
]
}
}
}
}
return expectNoErrors(spec)
})
})
@@ -0,0 +1,532 @@
/* eslint-env mocha */
import expect from "expect"
import validateHelper, { expectNoErrors } from "./validate-helper.js"
describe("validation plugin - semantic - form data", function(){
this.timeout(10 * 1000)
describe("/parameters/...", function(){
describe("typo in formdata", function(){
it("should warn about formdata ( typo )", function(){
const spec = {
swagger: "2.0",
parameters: {
CoolParam: [
{ in: "formdata" },
]
},
paths: {
"/some": {
post: {
parameters: [
{ in: "formdata" },
]
}
}
}
}
return validateHelper(spec)
.then( system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.message).toEqual(`Parameter "in: formdata" is invalid, did you mean "in: formData"?`)
expect(firstError.path).toEqual(["paths", "/some", "post", "parameters", "0"])
})
})
})
})
describe("missing consumes", function(){
it("should complain if 'type:file` and no 'in: formData", function(){
const spec = {
swagger: "2.0",
paths: {
"/some": {
post: {
consumes: ["multipart/form-data"],
parameters: [
{
type: "file",
},
]
}
}
}
}
return validateHelper(spec)
.then( system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.message).toEqual(`Parameters with "type: file" must have "in: formData"`)
expect(firstError.path).toEqual(["paths", "/some", "post", "parameters", "0"])
})
})
it("should complain if 'type:file` and no consumes - 'multipart/form-data'", function(){
const spec = {
swagger: "2.0",
paths: {
"/some": {
post: {
parameters: [
{
in: "formData",
type: "file",
},
]
}
}
}
}
return validateHelper(spec)
.then( system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.message).toEqual(`Operations with parameters of "type: file" must include "multipart/form-data" in their "consumes" property`)
expect(firstError.path).toEqual(["paths", "/some", "post"])
})
})
it("should complain if 'in:formData` and no consumes - 'multipart/form-data' or 'application/x-www-form-urlencoded'", function(){
const spec = {
swagger: "2.0",
paths: {
"/some": {
post: {
parameters: [
{
in: "formData",
},
]
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.message).toEqual(`Operations with parameters of "in: formData" must include "application/x-www-form-urlencoded" or "multipart/form-data" in their "consumes" property`)
expect(firstError.path).toEqual(["paths", "/some", "post"])
})
})
it("should not complain if 'in:formData` and consumes is set globally", function(){
const spec = {
swagger: "2.0",
consumes: [
"multipart/form-data"
],
paths: {
"/some": {
post: {
parameters: [
{
in: "formData",
},
]
}
}
}
}
return expectNoErrors(spec)
})
})
describe("/pathitems/...", function(){
it("should complain about having both in the same parameter", function(){
const spec = {
swagger: "2.0",
paths: {
"/": {
consumes: ["multipart/form-data"],
parameters: [
{ in: "formData" },
{ in: "body" },
]
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.message).toEqual(`Parameters cannot have both a "in: body" and "in: formData", as "formData" _will_ be the body`)
expect(firstError.path).toEqual(["paths", "/", "parameters"])
})
})
it("should complain if 'type:file` and no 'in: formData", function(){
const spec = {
swagger: "2.0",
paths: {
"/": {
consumes: ["multipart/form-data"],
parameters: [
{ type: "file" }
]
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.message).toEqual(`Parameters with "type: file" must have "in: formData"`)
expect(firstError.path).toEqual(["paths", "/", "parameters", "0"])
})
})
describe("Path-level form parameters and operation-level consumes", function(){
describe("`in: formData` + `type: file`", function() {
it("should report an error for missing consumes with a path-level parameter", function(){
const spec = {
swagger: "2.0",
paths: {
"/foo": {
parameters: [
{
name: "param",
in: "formData",
required: true,
type: "file"
}
],
post: {
responses: {
"200": {
description: "ok"
}
}
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(1)
const firstError = allErrors[0]
expect(firstError.message).toEqual(`Operations with parameters of "type: file" must include "multipart/form-data" in their "consumes" property`)
expect(firstError.path).toEqual(["paths", "/foo", "post"])
})
})
it("should report an error for incorrect global consumes with a path-level parameter", function(){
const spec = {
swagger: "2.0",
consumes: ["application/json"],
paths: {
"/foo": {
parameters: [
{
name: "param",
in: "formData",
required: true,
type: "file"
}
],
post: {
responses: {
"200": {
description: "ok"
}
}
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(1)
const firstError = allErrors[0]
expect(firstError.message).toEqual(`Operations with parameters of "type: file" must include "multipart/form-data" in their "consumes" property`)
expect(firstError.path).toEqual(["paths", "/foo", "post"])
})
})
it("should report an error for incorrect operation-level consumes with a path-level parameter", function(){
const spec = {
swagger: "2.0",
paths: {
"/foo": {
parameters: [
{
name: "param",
in: "formData",
required: true,
type: "file"
}
],
post: {
consumes: ["application/json"],
responses: {
"200": {
description: "ok"
}
}
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(1)
const firstError = allErrors[0]
expect(firstError.message).toEqual(`Operations with parameters of "type: file" must include "multipart/form-data" in their "consumes" property`)
expect(firstError.path).toEqual(["paths", "/foo", "post"])
})
})
it("should not report an error for correct global consumes with a path-level parameter", function(){
const spec = {
swagger: "2.0",
consumes: ["multipart/form-data"],
paths: {
"/foo": {
parameters: [
{
name: "param",
in: "formData",
required: true,
type: "file"
}
],
post: {
responses: {
"200": {
description: "ok"
}
}
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(0)
})
})
it("should not report an error for correct operation consumes with a path-level parameter", function(){
const spec = {
swagger: "2.0",
paths: {
"/foo": {
parameters: [
{
name: "param",
in: "formData",
required: true,
type: "file"
}
],
post: {
consumes: ["multipart/form-data"],
responses: {
"200": {
description: "ok"
}
}
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(0)
})
})
})
describe("`in: formData`", function() {
it("should report an error for missing consumes with a path-level formData parameter", function(){
const spec = {
swagger: "2.0",
paths: {
"/foo": {
parameters: [
{
name: "param",
in: "formData",
required: true,
type: "string"
}
],
post: {
responses: {
"200": {
description: "ok"
}
}
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(1)
const firstError = allErrors[0]
expect(firstError.message).toEqual(`Operations with parameters of "in: formData" must include "application/x-www-form-urlencoded" or "multipart/form-data" in their "consumes" property`)
expect(firstError.path).toEqual(["paths", "/foo", "post"])
})
})
it("should report an error for incorrect global consumes with a path-level formData parameter", function(){
const spec = {
swagger: "2.0",
consumes: ["application/json"],
paths: {
"/foo": {
parameters: [
{
name: "param",
in: "formData",
required: true,
type: "string"
}
],
post: {
responses: {
"200": {
description: "ok"
}
}
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(1)
const firstError = allErrors[0]
expect(firstError.message).toEqual(`Operations with parameters of "in: formData" must include "application/x-www-form-urlencoded" or "multipart/form-data" in their "consumes" property`)
expect(firstError.path).toEqual(["paths", "/foo", "post"])
})
})
it("should report an error for incorrect operation-level consumes with a path-level formData parameter", function(){
const spec = {
swagger: "2.0",
paths: {
"/foo": {
parameters: [
{
name: "param",
in: "formData",
required: true,
type: "string"
}
],
post: {
consumes: ["application/json"],
responses: {
"200": {
description: "ok"
}
}
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(1)
const firstError = allErrors[0]
expect(firstError.message).toEqual(`Operations with parameters of "in: formData" must include "application/x-www-form-urlencoded" or "multipart/form-data" in their "consumes" property`)
expect(firstError.path).toEqual(["paths", "/foo", "post"])
})
})
it("should not report an error for correct global consumes with a path-level parameter", function(){
const spec = {
swagger: "2.0",
consumes: ["multipart/form-data"],
paths: {
"/foo": {
parameters: [
{
name: "param",
in: "formData",
required: true,
type: "string"
}
],
post: {
responses: {
"200": {
description: "ok"
}
}
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(0)
})
})
it("should not report an error for correct operation consumes with a path-level parameter", function(){
const spec = {
swagger: "2.0",
paths: {
"/foo": {
parameters: [
{
name: "param",
in: "formData",
required: true,
type: "string"
}
],
post: {
consumes: ["multipart/form-data"],
responses: {
"200": {
description: "ok"
}
}
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(0)
})
})
})
})
})
})
@@ -0,0 +1,228 @@
import expect from "expect"
import validateHelper, { expectNoErrors } from "../validate-helper.js"
describe("validation plugin - semantic - oas3 components", () => {
describe("OAS3 component names must consist of allowed characters", () => {
it("should return an error when OAS3 component names contain forbidden characters", () => {
const spec = {
openapi: "3.0.0",
components: {
schemas: {
"Foo«Bar»": {}
},
parameters: {
"Foo«Bar»": {
in: "query",
name: "foo",
schema: {
type: "string"
}
}
},
responses: {
"Foo«Bar»": {
description: "ok"
}
},
examples: {
"Foo«Bar»": {
value: 1
}
},
requestBodies: {
"Foo«Bar»": {
content: {
"text/plain": {}
}
}
},
headers: {
"Foo«Bar»": {
schema: {
type: "string"
}
}
},
securitySchemes: {
"Foo«Bar»": {
type: "http",
scheme: "basic"
}
},
callbacks: {
"Foo«Bar»": {
"{$request.body#/callbackUrl}": {}
}
},
links: {
"Foo«Bar»": {
operationId: "getUser"
}
}
}
}
return validateHelper(spec)
.then(system => {
// We want errors only, without "unused definition" warnings
const allErrors = system.errSelectors.allErrors().toJS()
.filter(err => err.level === "error")
const errorMessage = "Component names can only contain the characters A-Z a-z 0-9 - . _"
expect(allErrors.length).toEqual(9)
expect(allErrors[0]).toInclude({
level: "error",
message: errorMessage,
path: ["components", "schemas", "Foo«Bar»"]
})
expect(allErrors[1]).toInclude({
level: "error",
message: errorMessage,
path: ["components", "parameters", "Foo«Bar»"]
})
expect(allErrors[2]).toInclude({
level: "error",
message: errorMessage,
path: ["components", "responses", "Foo«Bar»"]
})
expect(allErrors[3]).toInclude({
level: "error",
message: errorMessage,
path: ["components", "examples", "Foo«Bar»"]
})
expect(allErrors[4]).toInclude({
level: "error",
message: errorMessage,
path: ["components", "requestBodies", "Foo«Bar»"]
})
expect(allErrors[5]).toInclude({
level: "error",
message: errorMessage,
path: ["components", "headers", "Foo«Bar»"]
})
expect(allErrors[6]).toInclude({
level: "error",
message: errorMessage,
path: ["components", "securitySchemes", "Foo«Bar»"]
})
expect(allErrors[7]).toInclude({
level: "error",
message: errorMessage,
path: ["components", "callbacks", "Foo«Bar»"]
})
expect(allErrors[8]).toInclude({
level: "error",
message: errorMessage,
path: ["components", "links", "Foo«Bar»"]
})
})
})
it("should not return errors when OAS3 component names consist of allowed characters", () => {
const spec = {
openapi: "3.0.0",
components: {
schemas: {
"A-a.0_": {}
},
parameters: {
"B-b.1_": {
in: "query",
name: "foo",
schema: {
type: "string"
}
}
},
responses: {
"C-c.3_": {
description: "ok"
}
},
examples: {
"D-d.4_": {
value: 1
}
},
requestBodies: {
"E-e.5_": {
content: {
"test/plain": {}
}
}
},
headers: {
"F-f.6_": {
schema: {
type: "string"
}
}
},
securitySchemes: {
"G-g.7_": {
type: "http",
scheme: "basic"
}
},
callbacks: {
"H-h.8_": {
"{$request.body#/callbackUrl}": {}
}
},
links: {
"I-i.9_": {
operationId: "getUser"
}
}
}
}
return expectNoErrors(spec)
})
it("should not return errors when an x- extension key contains special characters", () => {
const spec = {
openapi: "3.0.0",
components: {
"x-foo«bar»": {
"key with spaces": 42
}
}
}
return expectNoErrors(spec)
})
it("should not return errors when OAS2 component names contain special characters", () => {
const spec = {
swagger: "2.0",
definitions: {
"Foo«Bar»": {
type: "object"
}
},
parameters: {
"Foo«Bar»": {
in: "query",
name: "foo",
type: "string"
}
},
responses: {
"Foo«Bar»": {
description: "ok"
}
},
securityDefinitions: {
"Foo«Bar»": {
type: "basic"
}
}
}
return expectNoErrors(spec)
})
})
})
@@ -0,0 +1,91 @@
import expect from "expect"
import validateHelper, { expectNoErrors } from "../validate-helper.js"
describe("validation plugin - semantic - oas3 operations", () => {
describe("GET and DELETE operations may not have a requestBody", () => {
it("should return an error when a requestBody exists in a GET operation", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/": {
get: {
operationId: "myId",
requestBody: {
content: {
"application/json": {
schema: {
type: "string"
}
}
}
}
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.message).toEqual(`GET operations cannot have a requestBody.`)
expect(firstError.path).toEqual(["paths", "/", "get", "requestBody"])
})
})
it("should return an error when a requestBody exists in a DELETE operation", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/": {
delete: {
operationId: "myId",
requestBody: {
content: {
"application/json": {
schema: {
type: "string"
}
}
}
}
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.message).toEqual(`DELETE operations cannot have a requestBody.`)
expect(firstError.path).toEqual(["paths", "/", "delete", "requestBody"])
})
})
it("should not return an error when other methods contain a requestBody", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/": {
post: {
operationId: "myId",
requestBody: {
content: {
"application/json": {
schema: {
type: "string"
}
}
}
}
}
}
}
}
return expectNoErrors(spec)
})
})
})
@@ -0,0 +1,165 @@
import expect from "expect"
import validateHelper, { expectNoErrorsOrWarnings } from "../validate-helper.js"
describe("validation plugin - semantic - oas3 parameters", () => {
describe("Header parameters should not be named Authorization, Content-Type, or Accept", () => {
it("should return a warning when a header parameter named Authorization is defined in an operation", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/": {
get: {
parameters: [
{
in: "header",
name: "authorization"
}
]
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.message).toMatch(`Header parameters named "Authorization" are ignored.`)
expect(firstError.path).toEqual(["paths", "/", "get", "parameters", "0", "name"])
})
})
it("should return a warning when a header parameter named Content-Type is defined in an operation", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/": {
get: {
parameters: [
{
in: "header",
name: "content-type"
}
]
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.message).toMatch(`Header parameters named "Content-Type" are ignored.`)
expect(firstError.path).toEqual(["paths", "/", "get", "parameters", "0", "name"])
})
})
it("should return a warning when a header parameter named Accept is defined in an operation", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/": {
get: {
parameters: [
{
in: "header",
name: "accept"
}
]
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.message).toMatch(`Header parameters named "Accept" are ignored.`)
expect(firstError.path).toEqual(["paths", "/", "get", "parameters", "0", "name"])
})
})
it("should return a warning when a header parameter named Authorization is defined in components", () => {
const spec = {
openapi: "3.0.0",
components: {
parameters: {
auth: {
in: "header",
name: "authorization"
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.message).toMatch(`Header parameters named "Authorization" are ignored.`)
expect(firstError.path).toEqual(["components", "parameters", "auth", "name"])
})
})
it("should return no warnings when a non-header parameter is named Authorization", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/": {
get: {
parameters: [
{
in: "query",
name: "authorization"
}
]
}
}
}
}
return expectNoErrorsOrWarnings(spec)
})
it("should return no warnings when a header parameter name contains 'Authorization' as a substring", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/": {
get: {
parameters: [
{
in: "header",
name: "X-Authorization"
}
]
}
}
}
}
return expectNoErrorsOrWarnings(spec)
})
it("should return no warnings when a header parameter is named 'Authorization' in OpenAPI 2.0", () => {
const spec = {
swagger: "2.0",
paths: {
"/": {
get: {
parameters: [
{
in: "header",
name: "authorization"
}
]
}
}
}
}
return expectNoErrorsOrWarnings(spec)
})
})
})
@@ -0,0 +1,706 @@
import expect from "expect"
import validateHelper, { expectNoErrorsOrWarnings } from "../validate-helper.js"
describe("validation plugin - semantic - oas3 refs", () => {
describe("$refs for request bodies must reference a request body by position", () => {
it("should return an error when a requestBody incorrectly references a local component schema", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/": {
post: {
operationId: "myId",
requestBody: {
$ref: "#/components/schemas/MySchema"
}
}
}
},
components: {
schemas: {
MySchema: {
type: "string"
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.message).toEqual(`requestBody $refs cannot point to '#/components/schemas/…', they must point to '#/components/requestBodies/…'`)
expect(firstError.path).toEqual(["paths", "/", "post", "requestBody", "$ref"])
})
})
it("should not return an error when a requestBody references a remote component schema", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/": {
post: {
operationId: "myId",
requestBody: {
$ref: "http://google.com/#/components/schemas/MySchema"
}
}
}
}
}
return expectNoErrorsOrWarnings(spec)
})
it("should return an error when a requestBody in a callback incorrectly references a local component schema", () => {
const spec = {
openapi: "3.0.0",
info: null,
version: "1.0.0-oas3",
title: "example",
paths: {
"/api/callbacks": {
post: {
responses: {
"200": {
description: "OK"
}
},
callbacks: {
callback: {
"/callback": {
post: {
requestBody: {
$ref: "#/components/schemas/callbackRequest"
},
responses: {
"200": {
description: "OK"
}
}
}
}
}
}
}
}
},
components: {
schemas: {
callbackRequest: {
type: "object",
properties: {
property1: {
type: "integer",
example: 10000
}
}
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.message).toEqual(`requestBody $refs cannot point to '#/components/schemas/…', they must point to '#/components/requestBodies/…'`)
expect(firstError.path).toEqual(["paths", "/api/callbacks", "post", "callbacks",
"callback", "/callback", "post", "requestBody", "$ref"])
})
})
it("should return no errors when a requestBody correctly references a local component request body", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/": {
post: {
operationId: "myId",
requestBody: {
$ref: "#/components/requestBodies/MyBody"
}
}
}
},
components: {
requestBodies: {
MyBody: {
content: {
"application/json": {
schema: {
type: "string"
}
}
}
}
}
}
}
return expectNoErrorsOrWarnings(spec)
})
it("should return no errors when a requestBody correctly references a local operation request body", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/": {
post: {
operationId: "myId",
requestBody: {
$ref: "#/paths/~1/put/requestBody"
}
},
put: {
requestBody: {
content: {
"application/json": {
schema: {
type: "string"
}
}
}
}
}
}
}
}
return expectNoErrorsOrWarnings(spec)
})
it("should return no errors when a requestBody correctly references a remote component request body", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/": {
post: {
operationId: "myId",
requestBody: {
$ref: "http://google.com/#/components/requestBodies/MyBody"
}
}
}
},
components: {
requestBodies: {
MyBody: {
content: {
"application/json": {
schema: {
type: "string"
}
}
}
}
}
}
}
return expectNoErrorsOrWarnings(spec)
})
it("should return no errors when a requestBody correctly references a remote https component request body", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/": {
post: {
operationId: "myId",
requestBody: {
$ref: "https://example.com/file.yaml#/components/requestBodies/group1/addPetBody"
}
}
}
},
components: {
requestBodies: {
MyBody: {
content: {
"application/json": {
schema: {
type: "string"
}
}
}
}
}
}
}
return expectNoErrorsOrWarnings(spec)
})
it("should return no errors when a requestBody correctly references an external yaml file", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/": {
post: {
operationId: "myId",
requestBody: {
$ref: "addPetBody.yaml"
}
}
}
},
components: {
requestBodies: {
MyBody: {
content: {
"application/json": {
schema: {
type: "string"
}
}
}
}
}
}
}
return expectNoErrorsOrWarnings(spec)
})
it("should return no errors when a requestBody correctly references an external yaml pointing some node", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/": {
post: {
operationId: "myId",
requestBody: {
$ref: "./components.yaml#/path/to/some/node"
}
}
}
},
components: {
requestBodies: {
MyBody: {
content: {
"application/json": {
schema: {
type: "string"
}
}
}
}
}
}
}
return expectNoErrorsOrWarnings(spec)
})
})
describe("$refs for requestbody schemas must reference a schema by position", () => {
it("should return an error when a requestBody schema incorrectly references a local component requestBody", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/foo": {
post: {
requestBody: {
content: {
"application/json": {
schema: {
$ref: "#/components/requestBodies/Foo"
}
}
}
}
}
}
},
components: {
requestBodies: {
Foo: {
type: "string"
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const allSemanticErrors = allErrors.filter(err => err.source === "semantic")
expect(allSemanticErrors.length).toEqual(1)
const firstError = allSemanticErrors[0]
expect(firstError.message).toEqual(`requestBody schema $refs must point to a position where a Schema Object can be legally placed`)
expect(firstError.path).toEqual(["paths", "/foo", "post", "requestBody", "content", "application/json", "schema", "$ref"])
})
})
it("should not return an error when a requestBody schema references a local component schema", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/foo": {
post: {
requestBody: {
content: {
"application/json": {
schema: {
$ref: "#/components/schemas/Foo"
}
}
}
}
}
}
},
components: {
schemas: {
Foo: {
type: "string"
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(0)
})
})
it("should not return an error when a requestBody schema references remote document paths", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/foo": {
post: {
requestBody: {
content: {
"application/json": {
schema: {
$ref: "http://google.com#/Foo"
}
}
}
}
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(0)
})
})
it("should not return an error when a requestBody schema references entire remote documents", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/foo": {
post: {
requestBody: {
content: {
"application/json": {
schema: {
$ref: "addPetBody.yaml"
}
}
}
}
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(0)
})
})
it("should not return an error when a requestBody schema references local operation requestBody schemas", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/foo": {
post: {
responses: {
"200": {
description: "OK"
}
},
requestBody: {
content: {
"application/json": {
schema: {
type: "string"
}
}
}
}
},
put: {
requestBody: {
responses: {
"200": {
description: "OK"
}
},
content: {
"application/json": {
schema: {
$ref: "#/paths/~1foo/post/requestBody/content/application~1json/schema"
}
}
}
}
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(0)
})
})
})
describe("response header $refs should not point to parameters", () => {
it("should return an error when a response header incorrectly references a local parameter component", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/foo": {
get: {
responses: {
"200": {
description: "OK",
headers: {
"X-MyHeader": {
$ref: "#/components/parameters/MyHeader"
}
}
}
}
}
}
},
components: {
headers: {
MyHeader: {
$ref: "#/components/parameters/MyHeader"
}
},
parameters: {
MyHeader: {}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.message).toEqual(`OAS3 header $refs should point to #/components/headers/... and not #/components/parameters/...`)
expect(firstError.path).toEqual(["paths", "/foo", "get","responses","200", "headers", "X-MyHeader", "$ref"])
})
})
it("should return no errors when a response header correctly references a local header component", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/foo": {
get: {
responses: {
"200": {
description: "OK",
headers: {
"X-MyHeader": {
$ref: "#/components/headers/MyHeader"
}
}
}
}
}
}
},
components: {
headers: {
MyHeader: {
$ref: "#/components/headers/MyHeader"
}
}
}
}
return expectNoErrorsOrWarnings(spec)
})
it("should return no errors for external $refs in response headers", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/foo": {
get: {
responses: {
"200": {
description: "OK",
headers: {
"X-MyHeader": {
$ref: "https://www.google.com/#/components/parameter/MyHeader"
}
}
}
}
}
}
},
components: {
headers: {
MyHeader: {
$ref: "#/components/headers/MyHeader"
}
}
}
}
return expectNoErrorsOrWarnings(spec)
})
})
describe("parameter $refs should not point to header components", () => {
it("should return an error when a parameter incorrectly references a response header component", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/foo": {
parameters: [
{
$ref: "#/components/headers/foo"
}
],
get: {
parameters: [
{
$ref: "#/components/headers/foo"
}
],
responses: {
"200": {
description: "OK"
}
}
}
}
},
components: {
parameters: {
myParam: {
$ref: "#/components/headers/foo"
}
},
headers: {
foo: {}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(3)
const firstError = allErrors[0]
expect(firstError.message).toEqual(`OAS3 parameter $refs should point to #/components/parameters/... and not #/components/headers/...`)
expect(firstError.path).toEqual(["paths","/foo","parameters", "0", "$ref"])
const secondError = allErrors[1]
expect(secondError.message).toEqual(`OAS3 parameter $refs should point to #/components/parameters/... and not #/components/headers/...`)
expect(secondError.path).toEqual(["paths","/foo","get","parameters", "0", "$ref"])
const thirdError = allErrors[2]
expect(thirdError.message).toEqual(`OAS3 parameter $refs should point to #/components/parameters/... and not #/components/headers/...`)
expect(thirdError.path).toEqual(["components","parameters", "myParam", "$ref"])
})
})
it("should return no errors when a parameter correctly references a parameter component", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/foo": {
parameters: [
{
$ref: "#/components/parameters/foo"
}
],
get: {
parameters: [
{
$ref: "#/components/parameters/foo"
}
],
responses: {
"200": {
description: "OK"
}
}
}
}
},
components: {
parameters: {
foo: {
$ref: "#/components/parameters/foo"
}
}
}
}
return expectNoErrorsOrWarnings(spec)
})
it("should return no errors for external parameter $refs", () => {
const spec = {
openapi: "3.0.0",
paths: {
"/foo": {
parameters: [
{
$ref: "http://www.google.com/#/components/parameters/foo"
}
],
get: {
parameters: [
{
$ref: "http://www.google.com/#/components/parameters/foo"
}
],
responses: {
"200": {
description: "OK"
}
}
}
}
},
components: {
parameters: {
foo: {
$ref: "http://www.google.com/#/components/parameters/foo"
}
}
}
}
return expectNoErrorsOrWarnings(spec)
})
})
})
@@ -0,0 +1,142 @@
import expect from "expect"
import validateHelper, { expectNoErrors } from "./validate-helper.js"
describe("validation plugin - semantic - parameters", function() {
this.timeout(10 * 1000 ) // For swagger-ui startup
it("should return an error when an array type parameter omits an `items` property", () => {
const spec = {
swagger: "2.0",
"paths": {
"/pets": {
"get": {
"parameters": [
{
"name": "tags",
"in": "query",
"description": "tags to filter by",
"type": "array"
}
]
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.path).toEqual(["paths", "/pets", "get", "parameters", "0"])
expect(firstError.message).toMatch(/.*type.*array.*require.*items/)
})
})
describe("Operations cannot have both a 'body' parameter and a 'formData' parameter", () => {
it("should complain about having both in the same operation", function(){
const spec = {
swagger: "2.0",
paths: {
"/": {
get: {
consumes: ["multipart/form-data"],
parameters: [
{ in: "formData" },
{ in: "body" },
]
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.message).toEqual(`Parameters cannot have both a "in: body" and "in: formData", as "formData" _will_ be the body`)
expect(firstError.path).toEqual(["paths", "/", "get", "parameters"])
})
})
it("should not complain about having only a body parameter in the same operation", function(){
const spec = {
swagger: "2.0",
paths: {
"/": {
get: {
consumes: ["multipart/form-data"],
parameters: [
{ in: "body" },
]
}
}
}
}
return expectNoErrors(spec)
})
it("should not complain about having only a formData parameter in the same operation", function(){
const spec = {
swagger: "2.0",
paths: {
"/": {
get: {
consumes: ["multipart/form-data"],
parameters: [
{ in: "formData" },
]
}
}
}
}
return expectNoErrors(spec)
})
})
describe("Operations must have only one body parameter", () => {
it("should complain about having two body parameters in the same operation", function(){
const spec = {
swagger: "2.0",
paths: {
"/": {
get: {
consumes: ["multipart/form-data"],
parameters: [
{ in: "body" },
{ in: "body" },
]
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.message).toEqual(`Multiple body parameters are not allowed.`)
expect(firstError.path).toEqual(["paths", "/", "get", "parameters"])
})
})
it("should not complain about having one body parameter in the same operation", function(){
const spec = {
swagger: "2.0",
paths: {
"/": {
get: {
consumes: ["multipart/form-data"],
parameters: [
{ in: "body" }
]
}
}
}
}
return expectNoErrors(spec)
})
})
})
@@ -0,0 +1,275 @@
/* eslint-env mocha */
import expect from "expect"
import validateHelper, { expectNoErrors } from "./validate-helper.js"
describe("validation plugin - semantic - paths", function(){
this.timeout(10 * 1000)
describe("Empty path templates are not allowed", () => {
it("should return one problem for an empty path template", function(){
const spec = {
swagger: "2.0",
paths: {
"/CoolPath/{}": {}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.message).toEqual( "Empty path parameter declarations are not valid")
expect(firstError.path).toEqual(["paths", "/CoolPath/{}"])
})
})
})
describe("Path parameters declared in the path string need matching definitions", () => {
it("should return one problem for an undefined declared path parameter", function(){
const spec = {
swagger: "2.0",
paths: {
"/CoolPath/{id}": {}
}
}
return validateHelper(spec)
.then( system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(1)
const firstError = allErrors[0]
expect(firstError.message).toEqual( "Declared path parameter \"id\" needs to be defined as a path parameter at either the path or operation level")
expect(firstError.path).toEqual(["paths", "/CoolPath/{id}"])
})
})
it("should return one problem for an path parameter defined in another path", function(){
const spec = {
swagger: "2.0",
paths: {
"/CoolPath/{id}": {},
"/UncoolPath/{id}": {
parameters: [{
name: "id",
in: "path",
required: true
}]
}
}
}
return validateHelper(spec)
.then( system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(1)
const firstError = allErrors[0]
expect(firstError.message).toEqual("Declared path parameter \"id\" needs to be defined as a path parameter at either the path or operation level")
expect(firstError.path).toEqual(["paths", "/CoolPath/{id}"])
})
})
it("should return no problems for a path parameter defined in the path", function(){
const spec = {
swagger: "2.0",
paths: {
"/CoolPath/{id}": {
parameters: [{
name: "id",
in: "path",
required: true
}]
}
}
}
return expectNoErrors(spec)
})
it("should return no problems for a path parameter defined in an operation", function(){
const spec = {
swagger: "2.0",
paths: {
"/CoolPath/{id}": {
get: {
parameters: [{
name: "id",
in: "path",
required: true
}]
}
}
}
}
return expectNoErrors(spec)
})
})
describe("Path strings must be equivalently different", () => {
it("should return one problem for an equivalent templated path strings", function(){
const spec = {
swagger: "2.0",
paths: {
"/CoolPath/{id}": {
parameters: [{
name: "id",
in: "path",
required: true
}]
},
"/CoolPath/{count}": {
parameters: [{
name: "count",
in: "path",
required: true
}]
}
}
}
return validateHelper(spec)
.then( system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(1)
const firstError = allErrors[0]
expect(firstError.message).toEqual("Equivalent paths are not allowed.")
expect(firstError.path).toEqual(["paths", "/CoolPath/{count}"])
})
})
it("should return no problems for a templated and untemplated pair of path strings", function(){
const spec = {
swagger: "2.0",
paths: {
"/CoolPath/": {},
"/CoolPath/{count}": {
parameters: [{
name: "count",
in: "path",
required: true
}]
}
}
}
return expectNoErrors(spec)
})
it("should return no problems for a templated and double-templated set of path strings", function(){
const spec = {
swagger: "2.0",
paths: {
"/CoolPath/{group_id1}/all": {
parameters: [{
name: "group_id1",
in: "path",
required: true
}]
},
"/CoolPath/{group_id2}/{user_id2}": {
parameters: [
{
name: "group_id2",
in: "path",
required: true
},
{
name: "user_id2",
in: "path",
required: true
},
]
},
}
}
return expectNoErrors(spec)
})
})
describe("Paths must have unique name + in parameters", () => {
it("should return no problems for a name collision only", function(){
const spec = {
swagger: "2.0",
paths: {
"/CoolPath/{id}": {
parameters: [
{
name: "id",
in: "path",
required: true
},
{
name: "id",
in: "query"
}
]
}
}
}
return expectNoErrors(spec)
})
it("should return no problems when 'in' is not defined", function(){
const spec = {
swagger: "2.0",
paths: {
"/CoolPath/{id}": {
parameters: [
{
name: "id",
in: "path",
required: true
},
{
name: "id",
// in: "path"
}
]
}
}
}
return expectNoErrors(spec)
})
})
describe("Integrations", () => {
it.skip("should return two problems for an equivalent path string missing a parameter definition", function(){
// const spec = {
// paths: {
// "/CoolPath/{id}": {
// parameters: [{
// name: "id",
// in: "path"
// }]
// },
// "/CoolPath/{count}": {}
// }
// }
//
// let res = validate({ resolvedSpec: spec })
// expect(res.errors).toEqual([
// {
// message: "Equivalent paths are not allowed.",
// path: "paths./CoolPath/{count}"
// },
// {
// message: "Declared path parameter \"count\" needs to be defined as a path parameter at either the path or operation level",
// path: "paths./CoolPath/{count}"
// }
// ])
// expect(res.warnings).toEqual([])
})
})
})
@@ -0,0 +1,227 @@
import expect from "expect"
import validateHelper, { expectNoErrorsOrWarnings } from "./validate-helper.js"
describe("validation plugin - semantic - refs", function() {
this.timeout(10 * 1000)
describe.skip("Refs are restricted in specific areas of a spec", () => {
describe("Response $refs", () => {
it("should return a problem for a parameters $ref in a response position", function(){
const spec = {
swagger: "2.0",
paths: {
"/CoolPath": {
responses: {
"200": {
$ref: "#/parameters/abc"
}
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(1)
const firstError = allErrors[0]
expect(firstError.path).toEqual(["paths", "/CoolPath", "responses", "200", "$ref"])
// expect(firstError.message).toMatch("")
})
})
// FIXME: This poses a problem for our newer validation PR, as it only iterates over the resolved spec.
// We can look for $$refs, but that may be too fragile.
// PS: We have a flag in mapSpec, that adds $$refs known as metaPatches
it("should return a problem for a definitions $ref in a response position", function(){
const spec = {
swagger: "2.0",
paths: {
"/CoolPath": {
schema: {
$ref: "#/responses/abc"
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
const firstError = allErrors[0]
expect(allErrors.length).toEqual(1)
expect(firstError.path).toEqual(["paths", "/CoolPath", "responses", "200", "$ref"])
expect(firstError.message).toEqual(`Response references are not allowed within schemas`)
})
})
it("should not return a problem for a responses $ref in a response position", function(){
const spec = {
swagger: "2.0",
paths: {
"/CoolPath": {
responses: {
"200": {
$ref: "#/responses/abc"
}
}
}
}
}
return expectNoErrorsOrWarnings(spec)
})
})
describe("Schema $refs", () => {
// See note on resolved vs raw spec
it("should return a problem for a parameters $ref in a schema position", function(){
// const spec = {
// paths: {
// "/CoolPath": {
// schema: {
// $ref: "#/parameters/abc"
// }
// }
// }
// }
// let res = validate({ jsSpec: spec })
// expect(res.errors.length).toEqual(1)
// expect(res.errors[0].path).toEqual(["paths", "/CoolPath", "schema", "$ref"])
// expect(res.warnings.length).toEqual(0)
})
it("should return a problem for a responses $ref in a schema position", function(){
// const spec = {
// paths: {
// "/CoolPath": {
// schema: {
// $ref: "#/responses/abc"
// }
// }
// }
// }
//
// let res = validate({ jsSpec: spec })
// expect(res.errors.length).toEqual(1)
// expect(res.errors[0].path).toEqual(["paths", "/CoolPath", "schema", "$ref"])
// expect(res.warnings.length).toEqual(0)
})
it("should not return a problem for a definition $ref in a schema position", function(){
const spec = {
swagger: "2.0",
paths: {
"/CoolPath": {
schema: {
$ref: "#/definition/abc"
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(0)
})
})
it("should not return a problem for a schema property named 'properties'", function(){
// #492 regression
const spec = {
"swagger": "2.0",
"definitions": {
"ServicePlan": {
"description": "New Plan to be added to a service.",
"properties": {
"plan_id": {
"type": "string",
"description": "ID of the new plan from the catalog."
},
"parameters": {
"$ref": "#/definitions/Parameter"
},
"previous_values": {
"$ref": "#/definitions/PreviousValues"
}
}
}
}
}
return validateHelper(spec)
.then(system => {
let allErrors = system.errSelectors.allErrors().toJS()
allErrors = allErrors.filter(a => a.level != "warning")
expect(allErrors.length).toEqual(0)
})
})
})
describe("Parameter $refs", () => {
it("should return a problem for a definition $ref in a parameter position", function(){
const spec = {
swagger: "2.0",
paths: {
"/CoolPath": {
parameters: [{
$ref: "#/definitions/abc"
}]
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(1)
const firstError = allErrors[0]
expect(firstError.path).toEqual(["paths", "/CoolPath", "parameters", "0", "$ref"])
expect(firstError.message).toMatch("")
})
})
it("should return a problem for a responses $ref in a parameter position", function(){
const spec = {
swagger: "2.0",
paths: {
"/CoolPath": {
parameters: [{
$ref: "#/responses/abc"
}]
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(1)
const firstError = allErrors[0]
expect(firstError.path).toEqual(["paths", "/CoolPath", "parameters", "0", "$ref"])
expect(firstError.message).toMatch("")
})
})
it("should not return a problem for a parameter $ref in a parameter position", function(){
const spec = {
swagger: "2.0",
paths: {
"/CoolPath": {
parameters: [{
$ref: "#/parameters/abc"
}]
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(0)
})
})
})
})
})
@@ -0,0 +1,544 @@
import expect from "expect"
import validateHelper, {
expectNoErrors,
expectNoErrorsOrWarnings
} from "./validate-helper.js"
describe("validation plugin - semantic - schema", function() {
this.timeout(10 * 1000)
it("should return an error when a definition's property is readOnly and required by the schema", () => {
const spec = {
swagger: "2.0",
blah: {
$ref: "#/definitions/CoolModel"
},
definitions: {
CoolModel: {
required: ["BadProperty"],
properties: {
BadProperty: {
readOnly: true
}
}
}
}
}
return validateHelper(spec)
.then( system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(1)
const firstError = allErrors[0]
expect(firstError.message).toEqual("Read only properties cannot be marked as required by a schema.")
expect(firstError.path).toEqual(["definitions", "CoolModel", "required", "0"])
})
})
it("should not return an error when a definition's property is not readOnly and required by the schema", () => {
const spec = {
swagger: "2.0",
definitions: {
CoolModel: {
required: ["BadProperty"],
properties: {
BadProperty: {
type: "string"
}
}
}
}
}
return validateHelper(spec)
.then( system => {
const allErrors = system.errSelectors
.allErrors()
.filter(a => a.get("level") === "error") // We have an incidental "warning"
.toJS()
expect(allErrors).toEqual(0)
})
})
it("should return an error when a response schema's property is readOnly and required by the schema", () => {
const spec = {
swagger: "2.0",
paths: {
"/CoolPath": {
get: {
responses: {
200: {
schema: {
required: ["BadProperty"],
properties: {
BadProperty: {
readOnly: true
}
}
}
}
}
}
}
}
}
return validateHelper(spec)
.then( system => {
const allErrors = system.errSelectors
.allErrors()
.toJS()
expect(allErrors.length).toEqual(1)
const firstError = allErrors[0]
expect(firstError.message).toEqual("Read only properties cannot be marked as required by a schema.")
expect(firstError.path).toEqual(["paths", "/CoolPath", "get", "responses", "200", "schema", "required", "0"])
})
})
it("should not return an error when a response schema's property is not readOnly and required by the schema", () => {
const spec = {
swagger: "2.0",
paths: {
"/CoolPath": {
get: {
responses: {
200: {
schema: {
required: ["BadProperty"],
properties: {
BadProperty: {
type: "string"
}
}
}
}
}
}
}
}
}
return expectNoErrorsOrWarnings(spec)
})
it("should return an error when a parameter schema's property is readOnly and required by the schema", () => {
const spec = {
swagger: "2.0",
paths: {
"/CoolPath": {
get: {
parameters: [{
name: "BadParameter",
in: "body",
schema: {
required: ["BadProperty"],
properties: {
BadProperty: {
readOnly: true
}
}
}
}]
}
}
}
}
return validateHelper(spec)
.then( system => {
const allErrors = system.errSelectors
.allErrors()
.toJS()
expect(allErrors.length).toEqual(1)
const firstError = allErrors[0]
expect(firstError.message).toEqual("Read only properties cannot be marked as required by a schema.")
expect(firstError.path).toEqual(["paths", "/CoolPath", "get", "parameters", "0", "schema", "required", "0"])
})
})
it("should not return an error when a parameter schema's property is not readOnly and required by the schema", () => {
const spec = {
swagger: "2.0",
paths: {
"/CoolPath": {
get: {
parameters: [{
name: "BadParameter",
in: "body",
schema: {
required: ["BadProperty"],
properties: {
BadProperty: {
type: "string"
}
}
}
}]
}
}
}
}
return expectNoErrorsOrWarnings(spec)
})
describe("Type key", () => {
it("should return an error when \"type\" is an array", () => {
const spec = {
swagger: "2.0",
paths: {
"/CoolPath": {
get: {
responses: {
"200": {
schema: {
type: ["number", "string"]
}
}
}
}
}
}
}
return validateHelper(spec)
.then( system => {
const allErrors = system.errSelectors
.allErrors()
.toJS()
expect(allErrors.length).toEqual(1)
const firstError = allErrors[0]
expect(firstError.message).toEqual(`Schema "type" key must be a string`)
expect(firstError.path).toEqual(["paths", "/CoolPath", "get", "responses", "200", "schema", "type"])
})
})
it("should not return an error when \"type\" is a property name", () => {
const spec = {
"swagger": "2.0",
"definitions": {
"ApiResponse": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"type": {
"type": "string"
},
"message": {
"type": "string"
}
}
}
}
}
return validateHelper(spec)
.then(system => {
let allErrors = system.errSelectors.allErrors().toJS()
allErrors = allErrors.filter(a => a.level != "warning") // ignore warnings
expect(allErrors.length).toEqual(0)
})
})
it("should not return an error when \"type\" is a property name inside additionalProperties", () => {
const spec = {
"swagger": "2.0",
"definitions": {
"ApiResponse": {
"type": "object",
"additionalProperties": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"type": {
"type": "string"
},
"message": {
"type": "string"
}
}
}
}
}
}
return validateHelper(spec)
.then(system => {
let allErrors = system.errSelectors.allErrors().toJS()
allErrors = allErrors.filter(a => a.level != "warning") // ignore warnings
expect(allErrors.length).toEqual(0)
})
})
it("should not return an error when \"type\" is a model name", () => {
const spec = {
"swagger": "2.0",
"definitions": {
"type": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"message": {
"type": "string"
}
}
}
}
}
return expectNoErrors(spec)
})
})
describe(`"type: array" requires "items"`, () => {
describe("header objects", function() {
// It takes a while to start up swagger-ui, for some reason
it("should return an error when an array header object omits an `items` property", () => {
// Given
const spec = {
"swagger": "2.0",
"info": {
"version": "1.0.0",
"title": "Swagger Petstore"
},
"paths": {
"/pets": {
"get": {
"description": "Returns all pets from the system that the user has access to",
"responses": {
"200": {
"description": "pet response",
"headers": {
"X-MyHeader": {
"type": "array"
}
}
},
"default": {
"description": "unexpected error"
}
}
}
}
}
}
// When
return validateHelper(spec)
.then(system => {
// Then
expect(system.errSelectors.allErrors().count()).toEqual(1)
const firstError = system.errSelectors.allErrors().first().toJS()
expect(firstError.message).toMatch(/.*type.*array.*require.*items/)
expect(firstError.path).toEqual(["paths", "/pets", "get", "responses", "200", "headers", "X-MyHeader"])
})
})
it("should not return an error when an array header object has an `items` property", () => {
const spec = {
"swagger": "2.0",
"info": {
"version": "1.0.0",
"title": "Swagger Petstore"
},
"paths": {
"/pets": {
"get": {
"description": "Returns all pets from the system that the user has access to",
"responses": {
"200": {
"description": "pet response",
"headers": {
"X-MyHeader": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"default": {
"description": "unexpected error"
}
}
}
}
}
}
return expectNoErrorsOrWarnings(spec)
})
})
describe("definitions", function() {
// It takes a while to start up swagger-ui, for some reason
it("should return an error when an array definition omits an `items` property", () => {
// Given
const spec = {
swagger: "2.0",
paths: {
$ref: "#/definitions/asdf"
},
"definitions": {
"asdf": {
type: "array"
}
}
}
// When
return validateHelper(spec)
.then(system => {
// Then
expect(system.errSelectors.allErrors().count()).toEqual(1)
const firstError = system.errSelectors.allErrors().first().toJS()
expect(firstError.message).toMatch(/.*type.*array.*require.*items/)
expect(firstError.path).toEqual(["definitions", "asdf"])
})
})
it("should not return an error when an array definition has an `items` property", () => {
const spec = {
"swagger": "2.0",
"info": {
"version": "1.0.0",
"title": "Swagger Petstore"
},
"paths": {
"/pets": {
"get": {
"description": "Returns all pets from the system that the user has access to",
"responses": {
"200": {
"description": "pet response",
"headers": {
"X-MyHeader": {
$ref: "#/definitions/array"
}
}
},
"default": {
"description": "unexpected error"
}
}
}
}
},
"definitions": {
"array": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
return expectNoErrorsOrWarnings(spec)
})
})
describe("'pattern' Z anchors", function() {
it("should return an error when a schema has a Z anchor in its pattern", () => {
// Given
const spec = {
swagger: "2.0",
paths: {
$ref: "#/definitions/asdf"
},
"definitions": {
"asdf": {
type: "string",
pattern: "^[-a-zA-Z0-9_]+\\Z"
}
}
}
// When
return validateHelper(spec)
.then(system => {
// Then
expect(system.errSelectors.allErrors().count()).toEqual(1)
const firstError = system.errSelectors.allErrors().first().toJS()
expect(firstError.message).toEqual(`"\\Z" anchors are not allowed in regular expression patterns`)
expect(firstError.path).toEqual(["definitions", "asdf", "pattern"])
})
})
it("should return an error when a subschema has a Z anchor in its pattern", () => {
// Given
const spec = {
swagger: "2.0",
paths: {
"/": {
get: {
responses: {
"200": {
schema: {
type: "object",
properties: {
slug: {
type: "string",
pattern: "^[-a-zA-Z0-9_]+\\Z"
}
}
}
}
}
}
}
},
}
// When
return validateHelper(spec)
.then(system => {
// Then
expect(system.errSelectors.allErrors().count()).toEqual(1)
const firstError = system.errSelectors.allErrors().first().toJS()
expect(firstError.message).toEqual(`"\\Z" anchors are not allowed in regular expression patterns`)
expect(firstError.path).toEqual(["paths", "/", "get", "responses", "200", "schema", "properties", "slug", "pattern"])
})
})
it("should not return an error when a regex pattern doesn't use a Z anchor", () => {
const spec = {
swagger: "2.0",
paths: {
$ref: "#/definitions/asdf"
},
"definitions": {
"asdf": {
type: "string",
pattern: "^[-a-zA-Z0-9_]+"
}
}
}
return expectNoErrorsOrWarnings(spec)
})
})
})
})
@@ -0,0 +1,146 @@
import expect from "expect"
import validateHelper, { expectNoErrorsOrWarnings } from "./validate-helper.js"
describe("validation plugin - semantic - security scopes", function() {
this.timeout(10 * 1000) // For the slow validateHelper startup ( via swagger-ui )
it("should return an error when an operation references a non-existing security scope", () => {
const spec = {
"swagger": "2.0",
"securityDefinitions": {
"api_key": {
"type": "apiKey",
"name": "apikey",
"in": "query",
"scopes": {
"asdf": "blah blah"
}
}
},
"paths": {
"/": {
"get": {
"description": "asdf",
"security": [
{
"api_key": [
"write:pets"
]
}
]
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(1)
const firstError = allErrors[0]
expect(firstError.path).toEqual(["paths", "/", "get", "security", "0", "0"])
expect(firstError.message).toEqual("Security scope definition write:pets could not be resolved")
})
})
it("should return an error when an operation references a security definition with no scopes", () => {
const spec = {
"swagger": "2.0",
"securityDefinitions": {
"api_key": {
"type": "apiKey",
"name": "apikey",
"in": "query"
}
},
"paths": {
"/": {
"get": {
"description": "asdf",
"security": [
{
"api_key": [
"write:pets"
]
}
]
}
}
}
}
return validateHelper(spec)
.then(system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors.length).toEqual(1)
const firstError = allErrors[0]
expect(firstError.path).toEqual(["paths", "/", "get", "security", "0", "0"])
expect(firstError.message).toMatch("Security scope definition write:pets could not be resolved")
})
})
it("should not return an error when an operation references an existing security scope", () => {
const spec = {
"swagger": "2.0",
"securityDefinitions": {
"api_key": {
"type": "apiKey",
"name": "apikey",
"in": "query",
"scopes": {
"write:pets": "write to pets"
}
}
},
"paths": {
"/": {
"get": {
"description": "asdf",
"security": [
{
"api_key": [
"write:pets"
]
}
]
}
}
}
}
return expectNoErrorsOrWarnings(spec)
})
it("should not return an error when an top-level security references an existing security scope", () => {
const spec = {
"swagger": "2.0",
"securityDefinitions": {
"api_key": {
"type": "apiKey",
"name": "apikey",
"in": "query",
"scopes": {
"write:pets": ""
}
}
},
"security": [
{
"api_key": [
"write:pets"
]
}
],
"paths": {
"/": {
"get": {
"description": "asdf"
}
}
}
}
return expectNoErrorsOrWarnings(spec)
})
})
@@ -0,0 +1,72 @@
import expect from "expect"
import SwaggerUi from "swagger-ui"
import debounce from "lodash/debounce"
import ValidateBasePlugin from "plugins/validate-base"
import ValidateSemanticPlugin from "plugins/validate-semantic"
import ASTPlugin from "plugins/ast"
const envDelay = process.env.ASYNC_TEST_DELAY
const DELAY_MS = (typeof envDelay === "string" ? parseInt(envDelay) : envDelay) || 50
export default function validateHelper(spec) {
return new Promise((resolve) => {
const system = SwaggerUi({
spec,
domNode: null,
presets: [
SwaggerUi.plugins.SpecIndex,
SwaggerUi.plugins.ErrIndex,
SwaggerUi.plugins.DownloadUrl,
SwaggerUi.plugins.SwaggerJsIndex,
SwaggerUi.plugins.Oas3Index
],
initialState: {
layout: undefined
},
plugins: [
ASTPlugin,
ValidateBasePlugin,
ValidateSemanticPlugin,
() => ({
statePlugins: {
configs: {
actions: {
loaded: () => {
return {
type: "noop"
}
}
}
}
}
})
]
})
system.validateActions.all()
const registerActivity = debounce(() => resolve(system), DELAY_MS)
system.getStore().subscribe(registerActivity)
})
}
export function expectNoErrorsOrWarnings(spec) {
return validateHelper(spec)
.then( system => {
const allErrors = system.errSelectors.allErrors().toJS()
expect(allErrors).toEqual([])
})
}
export function expectNoErrors(spec) {
return validateHelper(spec)
.then(system => {
let allErrors = system.errSelectors.allErrors().toJS()
allErrors = allErrors.filter(a => a.level === "error") // ignore warnings
expect(allErrors).toEqual([])
})
}