Browse Source

fix: path-item $ref produces/consumes inheritance (via #5049)

* implement a selector for consumes options

* fix incorrect comment, test names

* add `consumesOptionsFor` selector

* use `consumesOptionsFor` and drop `operationConsumes`
bubble
kyle 5 years ago
committed by GitHub
parent
commit
971c6f7536
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 400 additions and 39 deletions
  1. +1
    -2
      src/core/components/operation.jsx
  2. +1
    -1
      src/core/components/parameter-row.jsx
  3. +42
    -6
      src/core/plugins/spec/selectors.js
  4. +263
    -30
      test/core/plugins/spec/selectors.js
  5. +17
    -0
      test/e2e-cypress/static/documents/bugs/5043/status.yaml
  6. +44
    -0
      test/e2e-cypress/static/documents/bugs/5043/swagger.yaml
  7. +32
    -0
      test/e2e-cypress/tests/bugs/5043.js

+ 1
- 2
src/core/components/operation.jsx View File

@@ -83,7 +83,6 @@ export default class Operation extends PureComponent {

let operation = operationProps.getIn(["op"])
let responses = operation.get("responses")
let produces = operation.get("produces")
let parameters = getList(operation, ["parameters"])
let operationScheme = specSelectors.operationScheme(path, method)
let isShownKey = ["operations", tag, operationId]
@@ -216,7 +215,7 @@ export default class Operation extends PureComponent {
specSelectors={ specSelectors }
oas3Actions={oas3Actions}
specActions={ specActions }
produces={ produces }
produces={specSelectors.producesOptionsFor([path, method]) }
producesValue={ specSelectors.currentProducesFor([path, method]) }
specPath={specPath.push("responses")}
path={ path }


+ 1
- 1
src/core/components/parameter-row.jsx View File

@@ -129,7 +129,7 @@ export default class ParameterRow extends Component {
: <ParamBody getComponent={getComponent}
fn={fn}
param={param}
consumes={ specSelectors.operationConsumes(pathMethod) }
consumes={ specSelectors.consumesOptionsFor(pathMethod) }
consumesValue={ specSelectors.contentTypeValues(pathMethod).get("requestContentType") }
onChange={this.onChangeWrapper}
onChangeConsumes={onChangeConsumes}


+ 42
- 6
src/core/plugins/spec/selectors.js View File

@@ -401,12 +401,6 @@ export function contentTypeValues(state, pathMethod) {
})
}

// Get the consumes/produces by path
export function operationConsumes(state, pathMethod) {
pathMethod = pathMethod || []
return specJsonWithResolvedSubtrees(state).getIn(["paths", ...pathMethod, "consumes"], fromJS({}))
}

// Get the currently selected produces value for an operation
export function currentProducesFor(state, pathMethod) {
pathMethod = pathMethod || []
@@ -425,6 +419,48 @@ export function currentProducesFor(state, pathMethod) {

}

// Get the produces options for an operation
export function producesOptionsFor(state, pathMethod) {
pathMethod = pathMethod || []

const spec = specJsonWithResolvedSubtrees(state)
const operation = spec.getIn([ "paths", ...pathMethod], null)

if(operation === null) {
// return nothing if the operation does not exist
return
}

const [path] = pathMethod

const operationProduces = operation.get("produces", null)
const pathItemProduces = spec.getIn(["paths", path, "produces"], null)
const globalProduces = spec.getIn(["produces"], null)

return operationProduces || pathItemProduces || globalProduces
}

// Get the consumes options for an operation
export function consumesOptionsFor(state, pathMethod) {
pathMethod = pathMethod || []

const spec = specJsonWithResolvedSubtrees(state)
const operation = spec.getIn(["paths", ...pathMethod], null)

if (operation === null) {
// return nothing if the operation does not exist
return
}

const [path] = pathMethod

const operationConsumes = operation.get("consumes", null)
const pathItemConsumes = spec.getIn(["paths", path, "consumes"], null)
const globalConsumes = spec.getIn(["consumes"], null)

return operationConsumes || pathItemConsumes || globalConsumes
}

export const operationScheme = ( state, path, method ) => {
let url = state.get("url")
let matchResult = url.match(/^([a-z][a-z0-9+\-.]*):/)


+ 263
- 30
test/core/plugins/spec/selectors.js View File

@@ -7,7 +7,7 @@ import {
contentTypeValues,
operationScheme,
specJsonWithResolvedSubtrees,
operationConsumes
producesOptionsFor,
} from "corePlugins/spec/selectors"

import Petstore from "./assets/petstore.json"
@@ -15,7 +15,8 @@ import {
operationWithMeta,
parameterWithMeta,
parameterWithMetaByIdentity,
parameterInclusionSettingFor
parameterInclusionSettingFor,
consumesOptionsFor
} from "../../../../src/core/plugins/spec/selectors"

describe("spec plugin - selectors", function(){
@@ -253,34 +254,6 @@ describe("spec plugin - selectors", function(){

})

describe("operationConsumes", function(){
it("should return the operationConsumes for an operation", function(){
// Given
let state = fromJS({
json: {
paths: {
"/one": {
get: {
consumes: [
"application/xml",
"application/something-else"
]
}
}
}
}
})

// When
let contentTypes = operationConsumes(state, [ "/one", "get" ])
// Then
expect(contentTypes.toJS()).toEqual([
"application/xml",
"application/something-else"
])
})
})

describe("operationScheme", function(){

it("should return the correct scheme for a remote spec that doesn't specify a scheme", function(){
@@ -751,4 +724,264 @@ describe("spec plugin - selectors", function(){
expect(result).toEqual(true)
})
})
describe("producesOptionsFor", function() {
it("should return an operation produces value", function () {
const state = fromJS({
json: {
paths: {
"/": {
"get": {
description: "my operation",
produces: [
"operation/one",
"operation/two",
]
}
}
}
}
})

const result = producesOptionsFor(state, ["/", "get"])

expect(result.toJS()).toEqual([
"operation/one",
"operation/two",
])
})
it("should return a path item produces value", function () {
const state = fromJS({
json: {
paths: {
"/": {
"get": {
description: "my operation",
produces: [
"path-item/one",
"path-item/two",
]
}
}
}
}
})

const result = producesOptionsFor(state, ["/", "get"])

expect(result.toJS()).toEqual([
"path-item/one",
"path-item/two",
])
})
it("should return a global produces value", function () {
const state = fromJS({
json: {
produces: [
"global/one",
"global/two",
],
paths: {
"/": {
"get": {
description: "my operation"
}
}
}
}
})

const result = producesOptionsFor(state, ["/", "get"])

expect(result.toJS()).toEqual([
"global/one",
"global/two",
])
})
it("should favor an operation produces value over a path-item value", function () {
const state = fromJS({
json: {
paths: {
"/": {
produces: [
"path-item/one",
"path-item/two",
],
"get": {
description: "my operation",
produces: [
"operation/one",
"operation/two",
]
}
}
}
}
})

const result = producesOptionsFor(state, ["/", "get"])

expect(result.toJS()).toEqual([
"operation/one",
"operation/two",
])
})
it("should favor a path-item produces value over a global value", function () {
const state = fromJS({
json: {
produces: [
"global/one",
"global/two",
],
paths: {
"/": {
produces: [
"path-item/one",
"path-item/two",
],
"get": {
description: "my operation"
}
}
}
}
})

const result = producesOptionsFor(state, ["/", "get"])

expect(result.toJS()).toEqual([
"path-item/one",
"path-item/two",
])
})
})
describe("consumesOptionsFor", function() {
it("should return an operation consumes value", function () {
const state = fromJS({
json: {
paths: {
"/": {
"get": {
description: "my operation",
consumes: [
"operation/one",
"operation/two",
]
}
}
}
}
})

const result = consumesOptionsFor(state, ["/", "get"])

expect(result.toJS()).toEqual([
"operation/one",
"operation/two",
])
})
it("should return a path item consumes value", function () {
const state = fromJS({
json: {
paths: {
"/": {
"get": {
description: "my operation",
consumes: [
"path-item/one",
"path-item/two",
]
}
}
}
}
})

const result = consumesOptionsFor(state, ["/", "get"])

expect(result.toJS()).toEqual([
"path-item/one",
"path-item/two",
])
})
it("should return a global consumes value", function () {
const state = fromJS({
json: {
consumes: [
"global/one",
"global/two",
],
paths: {
"/": {
"get": {
description: "my operation"
}
}
}
}
})

const result = consumesOptionsFor(state, ["/", "get"])

expect(result.toJS()).toEqual([
"global/one",
"global/two",
])
})
it("should favor an operation consumes value over a path-item value", function () {
const state = fromJS({
json: {
paths: {
"/": {
consumes: [
"path-item/one",
"path-item/two",
],
"get": {
description: "my operation",
consumes: [
"operation/one",
"operation/two",
]
}
}
}
}
})

const result = consumesOptionsFor(state, ["/", "get"])

expect(result.toJS()).toEqual([
"operation/one",
"operation/two",
])
})
it("should favor a path-item consumes value over a global value", function () {
const state = fromJS({
json: {
consumes: [
"global/one",
"global/two",
],
paths: {
"/": {
consumes: [
"path-item/one",
"path-item/two",
],
"get": {
description: "my operation"
}
}
}
}
})

const result = consumesOptionsFor(state, ["/", "get"])

expect(result.toJS()).toEqual([
"path-item/one",
"path-item/two",
])
})
})
})

+ 17
- 0
test/e2e-cypress/static/documents/bugs/5043/status.yaml View File

@@ -0,0 +1,17 @@
---
paths:
findByStatus:
get:
tags:
- "pet"
summary: "Finds Pets by status"
description: "Multiple status values can be provided with comma separated strings"
operationId: "findPetsByStatus"
parameters:
- name: "status"
in: "body"
schema:
type: string
responses:
200:
description: ok

+ 44
- 0
test/e2e-cypress/static/documents/bugs/5043/swagger.yaml View File

@@ -0,0 +1,44 @@
swagger: "2.0"
info:
description: "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters."
version: "1.0.0"
title: "Swagger Petstore"
termsOfService: "http://swagger.io/terms/"
contact:
email: "apiteam@swagger.io"
license:
name: "Apache 2.0"
url: "http://www.apache.org/licenses/LICENSE-2.0.html"
host: "petstore.swagger.io"
basePath: "/v2"
produces:
- application/json
- application/xml
- text/csv
consumes:
- application/json
- application/xml
- text/csv
schemes:
- "https"
- "http"
paths:
/pet:
post:
tags:
- "pet"
summary: "Add a new pet to the store"
description: ""
operationId: "addPet"
parameters:
- in: "body"
name: "body"
description: "Pet object that needs to be added to the store"
required: true
schema:
$ref: "#/definitions/Pet"
responses:
405:
description: "Invalid input"
/pet/findByStatus:
$ref: "status.yaml#/paths/findByStatus"

+ 32
- 0
test/e2e-cypress/tests/bugs/5043.js View File

@@ -0,0 +1,32 @@
import repeat from "lodash/repeat"

describe("#5043: path-level $ref path items should inherit global consumes/produces", () => {
it("should render consumes options correctly", () => {
cy
.visit("/?url=/documents/bugs/5043/swagger.yaml")
.get("#operations-pet-findPetsByStatus")
.click()
.get(".try-out__btn")
.click()
.get(".content-type")
.contains("application/json")
.get(".content-type")
.contains("application/xml")
.get(".content-type")
.contains("text/csv")
})
it("should render produces options correctly", () => {
cy
.visit("/?url=/documents/bugs/5043/swagger.yaml")
.get("#operations-pet-findPetsByStatus")
.click()
.get(".try-out__btn")
.click()
.get(".body-param-content-type select")
.contains("application/json")
.get(".body-param-content-type select")
.contains("application/xml")
.get(".body-param-content-type select")
.contains("text/csv")
})
})

Loading…
Cancel
Save