/* 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)
      })
    })
})
