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