From bbe851fc0a201c60dce59316d4c385ccb30aa87c Mon Sep 17 00:00:00 2001 From: mdrezuanislamridoy Date: Thu, 17 Sep 2026 16:19:27 +0600 Subject: [PATCH] Add support for HTTP QUERY method with request snippets and tests --- cypress.config.js | 2 + docs/usage/configuration.md | 2 +- docs/usage/cors.md | 5 + docs/usage/http-query-method.md | 264 ++++++++++++++++++ src/core/plugins/request-snippets/fn.js | 2 +- .../e2e/features/query-method.cy.js | 54 ++++ .../documents/features/query-method.yaml | 59 ++++ .../helpers/query-method-server/index.js | 70 +++++ .../core/plugins/request-snippets/fn.test.js | 119 ++++++++ test/unit/core/plugins/spec/selectors.js | 64 +++++ 10 files changed, 639 insertions(+), 2 deletions(-) create mode 100644 docs/usage/http-query-method.md create mode 100644 test/e2e-cypress/e2e/features/query-method.cy.js create mode 100644 test/e2e-cypress/static/documents/features/query-method.yaml create mode 100644 test/e2e-cypress/support/helpers/query-method-server/index.js create mode 100644 test/unit/core/plugins/request-snippets/fn.test.js diff --git a/cypress.config.js b/cypress.config.js index a948ea0f97b..e4638c7a832 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -1,6 +1,7 @@ const { defineConfig } = require("cypress") const startOAuthServer = require("./test/e2e-cypress/support/helpers/oauth2-server") +const startQueryMethodServer = require("./test/e2e-cypress/support/helpers/query-method-server") module.exports = defineConfig({ fileServerFolder: "test/e2e-cypress/static", @@ -14,6 +15,7 @@ module.exports = defineConfig({ specPattern: "test/e2e-cypress/e2e/**/*.cy.{js,jsx}", setupNodeEvents: () => { startOAuthServer() + startQueryMethodServer() }, }, }) diff --git a/docs/usage/configuration.md b/docs/usage/configuration.md index c0ec34eea47..ed614c29831 100644 --- a/docs/usage/configuration.md +++ b/docs/usage/configuration.md @@ -287,7 +287,7 @@ Parameter name | Docker variable | Description `request.curlOptions` | _Unavailable_ | `Array`. If set, MUST be an array of command line options available to the `curl` command. This can be set on the mutated request in the `requestInterceptor` function. For example `request.curlOptions = ["-g", "--limit-rate 20k"]` `responseInterceptor` | _Unavailable_ | `Function=(a => a)`. MUST be a function. Function to intercept remote definition, "Try it out", and OAuth 2.0 responses. Accepts one argument responseInterceptor(response) and must return the modified response, or a Promise that resolves to the modified response. `showMutatedRequest` | `SHOW_MUTATED_REQUEST` | `Boolean=true`. If set to `true`, uses the mutated request returned from a requestInterceptor to produce the curl command in the UI, otherwise the request before the requestInterceptor was applied is used. -`supportedSubmitMethods` | `SUPPORTED_SUBMIT_METHODS` | `Array=["get", "put", "post", "delete", "options", "head", "patch", "trace", "query"]`. List of HTTP methods that have the "Try it out" feature enabled. An empty array disables "Try it out" for all operations. This does not filter the operations from the display. +`supportedSubmitMethods` | `SUPPORTED_SUBMIT_METHODS` | `Array=["get", "put", "post", "delete", "options", "head", "patch", "trace", "query"]`. List of HTTP methods that have the "Try it out" feature enabled. An empty array disables "Try it out" for all operations. This does not filter the operations from the display. See [The HTTP QUERY Method](./http-query-method.md) for details on the `query` entry. `validatorUrl` | `VALIDATOR_URL` | `String="https://validator.swagger.io/validator" OR null`. By default, Swagger UI attempts to validate specs against swagger.io's online validator. You can use this parameter to set a different validator URL, for example for locally deployed validators ([Validator Badge](https://github.com/swagger-api/validator-badge)). Setting it to either `none`, `127.0.0.1` or `localhost` will disable validation. `withCredentials` | `WITH_CREDENTIALS` | `Boolean=false` If set to `true`, enables passing credentials, [as defined in the Fetch standard](https://fetch.spec.whatwg.org/#credentials), in CORS requests that are sent by the browser. Note that Swagger UI cannot currently set cookies cross-domain (see [swagger-client#1163](https://github.com/swagger-api/swagger-client/issues/1163)) - as a result, you will have to rely on browser-supplied cookies (which this setting enables sending) that Swagger UI cannot control. diff --git a/docs/usage/cors.md b/docs/usage/cors.md index 844c8822585..16ed6e5371b 100644 --- a/docs/usage/cors.md +++ b/docs/usage/cors.md @@ -31,6 +31,11 @@ Content-Type: application/json Content-Length: 0 ``` +If your API definition includes [`QUERY` operations](./http-query-method.md) +(OpenAPI 3.2+), add `QUERY` to `Access-Control-Allow-Methods` as well — +browsers preflight `QUERY` requests the same way they preflight `PUT` or +`PATCH` requests. + This tells us that the petstore resource listing supports OPTIONS, and the following headers: `Content-Type`, `api_key`, `Authorization`. - Try Swagger UI from your file system and look at the debug console. If CORS is not enabled, you'll see something like this: diff --git a/docs/usage/http-query-method.md b/docs/usage/http-query-method.md new file mode 100644 index 00000000000..0e86cb61c7f --- /dev/null +++ b/docs/usage/http-query-method.md @@ -0,0 +1,264 @@ +# The HTTP QUERY Method + +Swagger UI has support for the HTTP `QUERY` method, as defined by +[RFC 10008](https://www.rfc-editor.org/rfc/rfc10008.html) and represented in +API definitions using +[OpenAPI Specification (OAS) 3.2.0's](https://spec.openapis.org/oas/v3.2.0.html#path-item-object) +native `query` Path Item field. + +This document explains what `QUERY` is, how to describe a `QUERY` operation +in an API definition, and how Swagger UI renders, executes, and generates +code for it. + +## What is `QUERY`? + +`QUERY` is an HTTP request method, on equal footing with `GET`, `POST`, +`PUT`, `DELETE`, `PATCH`, `HEAD`, `OPTIONS`, and `TRACE`. Per RFC 10008: + +- **`QUERY` is safe.** It does not create, modify, or delete state on the + server. +- **`QUERY` is idempotent.** Sending the same `QUERY` request multiple times + has the same effect as sending it once. +- **`QUERY` is intended for server-side querying.** It's for cases where a + query is too large, structured, or expressive to fit in a URI, but where a + `GET`'s safety/idempotency semantics are still desired. +- **`QUERY` may carry request content.** Unlike `GET`, a `QUERY` request can + have a body. The request content, together with its media type, *is* the + query. +- **The target resource (the request URI) determines the scope of the + query.** The URI itself does not need to change based on query content. + +### `QUERY` is not `GET`, and it is not `POST` + +It's tempting to think of `QUERY` as "`GET` with a body" or "`POST` but +safe". Neither framing is correct, and Swagger UI does not treat `QUERY` as +either method internally: + +| | `GET` | `POST` | `QUERY` | +| ------------------------ | ------ | ------ | ------- | +| Safe | Yes | No | Yes | +| Idempotent | Yes | No | Yes | +| Can carry request content | No¹ | Yes | Yes | + +¹ `GET` requests are not defined to carry meaningful request content. + +A `QUERY` request that carries content **must** declare an appropriate +`Content-Type`, exactly like `POST` or `PUT` would. A `QUERY` request with no +content sends no body and no `Content-Type`, exactly like a bodyless `GET` +would. + +### `QUERY` the method vs. `?query=` the URI syntax + +Do not confuse the HTTP `QUERY` **method** with URI **query-string +parameters** (the part of a URL after `?`). They're unrelated concepts and +Swagger UI supports both simultaneously — a `QUERY` operation can have path +parameters, URI query-string parameters, headers, *and* a request body, all +at once: + +``` +QUERY /search?tenant=abc HTTP/1.1 +Host: example.com +Content-Type: application/json +Accept: application/json + +{ + "filter": { + "status": "active" + } +} +``` + +## Defining a `QUERY` operation + +`QUERY` is a **native, standard Path Item field in OpenAPI 3.2.0 and +later** — it is not a vendor extension. It sits alongside `get`, `put`, +`post`, `delete`, `options`, `head`, `patch`, and `trace` as a first-class +Path Item Object field, and it is described exactly like those: + +> **OpenAPI compatibility note:** `query` is only valid for documents +> declaring `openapi: 3.2.x` or later. Swagger UI does not currently offer a +> vendor-extension fallback (e.g. `x-http-method: QUERY`) for OpenAPI 2.0, +> 3.0.x, or 3.1.x documents — if your document declares an older `openapi`/ +> `swagger` version, a `query` key under a path item is not a recognized +> standard operation for that version and should not be relied upon. Declare +> `openapi: 3.2.0` (or later) to use `QUERY` operations. + +### YAML example + +```yaml +openapi: 3.2.0 +info: + title: Example API + version: "1.0.0" +paths: + /search: + query: + summary: Search resources + description: Execute a safe, idempotent query using request content. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + filter: + type: object + properties: + status: + type: string + sort: + type: array + items: + type: string + page: + type: integer + limit: + type: integer + example: + filter: + status: active + sort: + - -createdAt + page: 1 + limit: 50 + responses: + "200": + description: Search results + content: + application/json: + schema: + type: object +``` + +### JSON example + +```json +{ + "openapi": "3.2.0", + "info": { "title": "Example API", "version": "1.0.0" }, + "paths": { + "/search": { + "query": { + "summary": "Search resources", + "description": "Execute a safe, idempotent query using request content.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "filter": { "type": "object" }, + "sort": { "type": "array", "items": { "type": "string" } }, + "page": { "type": "integer" }, + "limit": { "type": "integer" } + } + } + } + } + }, + "responses": { + "200": { + "description": "Search results", + "content": { + "application/json": { "schema": { "type": "object" } } + } + } + } + } + } + } +} +``` + +A `QUERY` operation supports the same features as any other OAS3 operation: +path parameters, URI query-string parameters (`in: query` parameters — +distinct from the HTTP `QUERY` method, see above), headers, security +requirements, request bodies of any media type (JSON, text, or custom), and +response definitions. + +## Rendering in the UI + +`QUERY` operations render like any other method: a colored `opblock` with a +`QUERY` method badge, expandable to show parameters, the request body +editor, responses, and a "Try it out" button. The method is always shown as +literal text ("QUERY"), never conveyed by color alone, so it remains +distinguishable without relying on color perception. + +## "Try it out" and request execution + +`QUERY` is included in the default +[`supportedSubmitMethods`](./configuration.md) configuration, so "Try it +out" is enabled for `QUERY` operations out of the box. As with any method, +you can remove `"query"` from a custom `supportedSubmitMethods` array to +disable "Try it out" for `QUERY` operations specifically. + +When you execute a `QUERY` operation, Swagger UI sends a real HTTP request +whose method is the literal token `QUERY` — never `POST`, never `GET`, and +never a `POST`/`GET` request with a method-override header. Path parameters, +query-string parameters, headers, authentication, and the request body all +work exactly as they do for `POST`/`PUT`/`PATCH` operations. `requestInterceptor` +and `responseInterceptor` see and can inspect/mutate the real `QUERY` +request and its response, the same as for any other method. + +## `curl` generation + +Swagger UI's generated `curl` command reflects the true request: + +```bash +curl -X QUERY \ + 'https://example.com/search' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d '{"filter":{"status":"active"}}' +``` + +The method shown is always `QUERY`, matching the request's declared or +inferred `Content-Type` — Swagger UI does not hardcode `application/json` +for `QUERY` requests; it uses whichever media type is selected for the +request body (JSON, `multipart/form-data`, `application/x-www-form-urlencoded`, +plain text, or any other declared media type). + +## CORS and browser considerations + +Because `QUERY` combined with a `Content-Type` header (or other +non-"simple" headers such as `Authorization`) does not qualify as a +["simple request"](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests), +browsers issue a CORS preflight (`OPTIONS`) request before sending it, +exactly as they do for `PUT`, `PATCH`, or a `POST` with a non-form +`Content-Type`. If your API and Swagger UI are served from different +origins, your server must respond to that preflight with: + +``` +Access-Control-Allow-Origin: +Access-Control-Allow-Methods: QUERY, GET, POST, PUT, PATCH, DELETE, OPTIONS +Access-Control-Allow-Headers: Content-Type, Authorization, +``` + +See [CORS](./cors.md) for general CORS guidance — the same guidance applies +to `QUERY` operations, with `QUERY` added to the allowed methods list. + +Swagger UI does not modify your server's CORS configuration; you must +configure `Access-Control-Allow-Methods: QUERY` (and any other required +headers) on your API server yourself. + +`QUERY` support in the underlying transport (the browser's `fetch()` +implementation and Swagger UI's HTTP client, via `swagger-client`) requires +a browser/runtime that permits `QUERY` as a request method. `QUERY` is not +one of the methods forbidden by the Fetch standard (`CONNECT`, `TRACE`, +`TRACK`), so modern browsers permit it as an ordinary custom method token. + +## Summary of the request lifecycle + +``` +OAS 3.2 `query` operation + -> recognized by Swagger UI's spec parser as a valid operation + -> rendered in the UI with a QUERY badge/color + -> "Try it out" builds a request with method "QUERY" + -> swagger-client preserves method: "QUERY" (case-preserved per the OAS document) + -> the browser's fetch() sends an HTTP request with method QUERY + -> your server receives QUERY + -> the response is displayed in Swagger UI + -> the equivalent curl command uses -X QUERY +``` diff --git a/src/core/plugins/request-snippets/fn.js b/src/core/plugins/request-snippets/fn.js index aea7c215657..6194af9452e 100644 --- a/src/core/plugins/request-snippets/fn.js +++ b/src/core/plugins/request-snippets/fn.js @@ -106,7 +106,7 @@ const curlify = (request, escape, newLine, ext = "") => { const body = request.get("body") if (body) { - if (isMultipartFormDataRequest && ["POST", "PUT", "PATCH"].includes(request.get("method"))) { + if (isMultipartFormDataRequest && ["POST", "PUT", "PATCH", "QUERY"].includes(request.get("method"))) { for (let [k, v] of body.entrySeq()) { let extractedKey = extractKey(k) addNewLine() diff --git a/test/e2e-cypress/e2e/features/query-method.cy.js b/test/e2e-cypress/e2e/features/query-method.cy.js new file mode 100644 index 00000000000..97e860f2238 --- /dev/null +++ b/test/e2e-cypress/e2e/features/query-method.cy.js @@ -0,0 +1,54 @@ +// Verifies the HTTP QUERY method (RFC 10008) end-to-end: +// API definition -> UI rendering -> Try it out -> real HTTP request -> +// real local server -> response displayed back in the UI -> curl generation. +// +// The local server at test/e2e-cypress/support/helpers/query-method-server +// echoes back the *actual* `req.method` it received on the wire, so this +// test proves the browser really sent QUERY, not merely that the UI +// displays the word "QUERY". +describe("HTTP QUERY method (RFC 10008)", () => { + it("renders QUERY as a first-class method and never as GET or POST", () => { + cy.visit("/?url=/documents/features/query-method.yaml") + .get("#operations-default-query_query_test") + .should("have.class", "opblock-query") + .find(".opblock-summary-method") + .should("have.text", "QUERY") + .should("not.have.text", "POST") + .should("not.have.text", "GET") + }) + + it("executes Try it out and the server actually receives an HTTP QUERY request", () => { + cy.visit("/?url=/documents/features/query-method.yaml") + .get("#operations-default-query_query_test .opblock-summary") + .click() + .get(".try-out__btn") + .click() + .get(".execute.opblock-control__btn") + .click() + .wait(500) + .get(".responses-wrapper .response-col_description .microlight") + .should("contains.text", "\"method\": \"QUERY\"") + .get(".responses-wrapper .response-col_description .microlight") + .should("contains.text", "\"received\": true") + .get(".responses-wrapper .response-col_description .microlight") + .should("contains.text", "\"contentType\": \"application/json\"") + .get(".responses-wrapper .response-col_description .microlight") + .should("contains.text", "\"status\": \"active\"") + }) + + it("generates a curl command with -X QUERY, never -X POST or -X GET", () => { + cy.visit("/?url=/documents/features/query-method.yaml") + .get("#operations-default-query_query_test .opblock-summary") + .click() + .get(".try-out__btn") + .click() + .get(".execute.opblock-control__btn") + .click() + .wait(500) + .get(".responses-wrapper .curl-command") + .should("contains.text", "QUERY") + .find("span") + .should("not.contains.text", "-X POST") + .should("not.contains.text", "-X GET") + }) +}) diff --git a/test/e2e-cypress/static/documents/features/query-method.yaml b/test/e2e-cypress/static/documents/features/query-method.yaml new file mode 100644 index 00000000000..14cef9782ea --- /dev/null +++ b/test/e2e-cypress/static/documents/features/query-method.yaml @@ -0,0 +1,59 @@ +openapi: 3.2.0 +info: + title: HTTP QUERY Method E2E Test + version: "1.0.0" + description: > + Exercises the HTTP QUERY method (RFC 10008) end-to-end against a real + local server, via `test/e2e-cypress/support/helpers/query-method-server`. +servers: + - url: http://localhost:3232 +paths: + /query-test: + query: + summary: Execute a safe, idempotent query using request content + description: > + QUERY is safe and idempotent, and its request content (together with + its media type) defines the query. This is distinct from the ?query + string parameters on the URI, which are supported independently. + requestBody: + required: false + content: + application/json: + schema: + type: object + properties: + filter: + type: object + properties: + status: + type: string + sort: + type: array + items: + type: string + limit: + type: integer + example: + filter: + status: active + sort: + - -createdAt + limit: 50 + responses: + "200": + description: The server's raw view of the request it received. + content: + application/json: + schema: + type: object + properties: + method: + type: string + received: + type: boolean + url: + type: string + contentType: + type: string + body: + type: object diff --git a/test/e2e-cypress/support/helpers/query-method-server/index.js b/test/e2e-cypress/support/helpers/query-method-server/index.js new file mode 100644 index 00000000000..aa4388af7e0 --- /dev/null +++ b/test/e2e-cypress/support/helpers/query-method-server/index.js @@ -0,0 +1,70 @@ +// A minimal, dependency-free HTTP server used to prove that Swagger UI's +// "Try it out" flow sends a *real* HTTP QUERY request (RFC 10008), rather +// than a POST/GET request labeled "QUERY" in the UI. +// +// Deliberately implemented with the raw `http` module (not Express) so that +// nothing in the stack normalizes or rejects the QUERY method token before +// we can observe `req.method`. +const http = require("http") + +const PORT = 3232 + +function startServer() { + const server = http.createServer((req, res) => { + res.setHeader("Access-Control-Allow-Origin", "*") + res.setHeader("Access-Control-Allow-Methods", "QUERY, GET, POST, OPTIONS") + res.setHeader( + "Access-Control-Allow-Headers", + "Content-Type, Authorization" + ) + + if (req.method === "OPTIONS") { + // CORS preflight + res.writeHead(204) + res.end() + return + } + + const chunks = [] + req.on("data", (chunk) => chunks.push(chunk)) + req.on("end", () => { + const rawBody = Buffer.concat(chunks).toString("utf8") + let parsedBody = null + + if (rawBody) { + try { + parsedBody = JSON.parse(rawBody) + } catch (e) { + parsedBody = rawBody + } + } + + res.setHeader("Content-Type", "application/json") + res.writeHead(200) + res.end( + JSON.stringify({ + // This is the actual method the server received on the wire, + // not something the UI merely displayed. + method: req.method, + received: true, + url: req.url, + contentType: req.headers["content-type"] || null, + body: parsedBody, + }) + ) + }) + }) + + server.listen(PORT) + + return function stopServer() { + server.close() + } +} + +module.exports = startServer + +if (require.main === module) { + // for debugging + startServer() +} diff --git a/test/unit/core/plugins/request-snippets/fn.test.js b/test/unit/core/plugins/request-snippets/fn.test.js new file mode 100644 index 00000000000..fb0a2fe12e6 --- /dev/null +++ b/test/unit/core/plugins/request-snippets/fn.test.js @@ -0,0 +1,119 @@ + +import { Map, fromJS } from "immutable" +import { requestSnippetGenerator_curl_bash } from "core/plugins/request-snippets/fn" + +describe("curl generation for the HTTP QUERY method (RFC 10008)", () => { + it("generates `-X 'QUERY'` and omits `-d`/Content-Type when there is no body", () => { + const request = fromJS({ + method: "QUERY", + url: "https://example.com/search", + headers: {}, + }) + + const curl = requestSnippetGenerator_curl_bash(request) + + expect(curl).toContain("-X 'QUERY'") + expect(curl).not.toContain("-X 'POST'") + expect(curl).not.toContain("-X 'GET'") + expect(curl).not.toContain("-d") + expect(curl).not.toContain("Content-Type") + }) + + it("preserves QUERY as the method and includes the JSON body/content-type/headers", () => { + const request = fromJS({ + method: "QUERY", + url: "https://example.com/search", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + Authorization: "Bearer token123", + }, + body: JSON.stringify({ filter: { status: "active" }, limit: 50 }), + }) + + const curl = requestSnippetGenerator_curl_bash(request) + + expect(curl).toContain("-X 'QUERY'") + expect(curl).toContain("-H 'Content-Type: application/json'") + expect(curl).toContain("-H 'Accept: application/json'") + expect(curl).toContain("-H 'Authorization: Bearer token123'") + expect(curl).toContain( + `-d '${JSON.stringify({ filter: { status: "active" }, limit: 50 })}'` + ) + expect(curl).not.toContain("-X 'POST'") + expect(curl).not.toContain("-X 'GET'") + }) + + it("preserves the query string (URI query parameters) separately from the QUERY method", () => { + const request = fromJS({ + method: "QUERY", + url: "https://example.com/search?tenant=abc", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ filter: { status: "active" } }), + }) + + const curl = requestSnippetGenerator_curl_bash(request) + + expect(curl).toContain("-X 'QUERY'") + expect(curl).toContain("https://example.com/search?tenant=abc") + }) + + it("uses -F multipart flags for a multipart/form-data QUERY request body", () => { + const body = Map({ + filter: "active", + }) + + const request = Map({ + method: "QUERY", + url: "https://example.com/search", + headers: Map({ "Content-Type": "multipart/form-data" }), + body, + }) + + const curl = requestSnippetGenerator_curl_bash(request) + + expect(curl).toContain("-X 'QUERY'") + expect(curl).toContain("-F") + expect(curl).toContain("filter=active") + }) + + it("does not add the POST-specific empty-body `-d ''` fallback to QUERY", () => { + const request = fromJS({ + method: "QUERY", + url: "https://example.com/search", + headers: {}, + }) + + const curl = requestSnippetGenerator_curl_bash(request) + + expect(curl).not.toContain("-d ''") + }) + + describe("regression: existing methods are unaffected", () => { + it("still adds `-d ''` for a bodyless POST", () => { + const request = fromJS({ + method: "POST", + url: "https://example.com/thing", + headers: {}, + }) + + const curl = requestSnippetGenerator_curl_bash(request) + + expect(curl).toContain("-X 'POST'") + expect(curl).toContain("-d ''") + }) + + it("still generates a correct GET request", () => { + const request = fromJS({ + method: "GET", + url: "https://example.com/thing", + headers: {}, + }) + + const curl = requestSnippetGenerator_curl_bash(request) + + expect(curl).toContain("-X 'GET'") + expect(curl).not.toContain("-d") + }) + }) +}) diff --git a/test/unit/core/plugins/spec/selectors.js b/test/unit/core/plugins/spec/selectors.js index 672c87baca0..cbb15e17554 100644 --- a/test/unit/core/plugins/spec/selectors.js +++ b/test/unit/core/plugins/spec/selectors.js @@ -1240,6 +1240,70 @@ describe("taggedOperations", function () { expect(result.toJS()).toEqual([]) }) }) +describe("operations", function () { + it("should recognize a `query` path item as an operation (HTTP QUERY method, RFC 10008)", function () { + const state = fromJS({ + json: { + paths: { + "/search": { + query: { + operationId: "searchThings", + summary: "Search things", + }, + get: { + operationId: "listThings", + }, + }, + }, + }, + }) + + const result = operations(state) + + expect(result.toJS()).toEqual([ + { + path: "/search", + method: "query", + operation: { + operationId: "searchThings", + summary: "Search things", + }, + id: "query-/search", + specPath: ["paths", "/search", "query"], + }, + { + path: "/search", + method: "get", + operation: { + operationId: "listThings", + }, + id: "get-/search", + specPath: ["paths", "/search", "get"], + }, + ]) + }) + + it("should not misclassify a `query` operation as GET or POST", function () { + const state = fromJS({ + json: { + paths: { + "/search": { + query: { + operationId: "searchThings", + }, + }, + }, + }, + }) + + const result = operations(state) + + expect(result.size).toEqual(1) + expect(result.first().get("method")).toEqual("query") + expect(result.first().get("method")).not.toEqual("get") + expect(result.first().get("method")).not.toEqual("post") + }) +}) describe("getOAS3RequiredRequestBodyContentType", () => { const pathMethod = ["/test", "post"]