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([]) }) }) }) })