From ec4a255b2d9975409c610808d3283372f1d18bdd Mon Sep 17 00:00:00 2001 From: Naresh Jain Date: Tue, 2 Jan 2024 14:19:44 +0530 Subject: [PATCH 1/6] added a failing test --- .../kotlin/in/specmatic/stub/HttpStubTest.kt | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/core/src/test/kotlin/in/specmatic/stub/HttpStubTest.kt b/core/src/test/kotlin/in/specmatic/stub/HttpStubTest.kt index d6d56d16a..74116de6d 100644 --- a/core/src/test/kotlin/in/specmatic/stub/HttpStubTest.kt +++ b/core/src/test/kotlin/in/specmatic/stub/HttpStubTest.kt @@ -883,6 +883,41 @@ paths: """.trim(), "" ).toFeature() + val featureWithPathParamExamples = OpenApiSpecification.fromYAML( + """ +openapi: 3.0.1 +info: + title: Data API + version: "1" +paths: + /{productId}: + get: + summary: Data + parameters: + - name: productId + schema: + type: string + in: path + required: true + examples: + QUERY_SUCCESS: + value: xyz123 + responses: + "200": + description: Data + content: + application/json: + schema: + type: array + items: + type: + string + examples: + QUERY_SUCCESS: + value: ["one", "two"] +""".trim(), "" + ).toFeature() + @Test fun `expectations for payload from examples`() { @@ -906,6 +941,17 @@ paths: } } + @Test + fun `expectations for path params from examples`() { + HttpStub(featureWithPathParamExamples).use { stub -> + stub.client.execute(HttpRequest("GET", "/xyz123")) + .let { response -> + assertThat(response.status).isEqualTo(200) + assertThat(response.body).isEqualTo(parsedJSONArray("""["one", "two"]""")) + } + } + } + @Test fun `expectations from examples should have less priority than file expectations`() { HttpStub( From b3f7cf7a8909130a432ddc1f7b61daef7bbcb6d0 Mon Sep 17 00:00:00 2001 From: Hari Krishnan Date: Tue, 2 Jan 2024 17:08:12 +0530 Subject: [PATCH 2/6] using path param examples as stub responses --- .../conversions/OpenApiSpecification.kt | 46 +++++++++++-------- .../kotlin/in/specmatic/stub/HttpStubTest.kt | 23 ++++++++-- 2 files changed, 47 insertions(+), 22 deletions(-) diff --git a/core/src/main/kotlin/in/specmatic/conversions/OpenApiSpecification.kt b/core/src/main/kotlin/in/specmatic/conversions/OpenApiSpecification.kt index de2c37e0c..35304d45c 100644 --- a/core/src/main/kotlin/in/specmatic/conversions/OpenApiSpecification.kt +++ b/core/src/main/kotlin/in/specmatic/conversions/OpenApiSpecification.kt @@ -667,27 +667,23 @@ class OpenApiSpecification(private val openApiFilePath: String, private val pars securitySchemes = operationSecuritySchemes(operation, securitySchemes) ) - val exampleQueryParams = - operation.parameters.orEmpty() - .filterIsInstance() - .fold(emptyMap>()) { - acc, queryParameter -> - - queryParameter - .examples.orEmpty() - .entries - .fold(acc) { acc, (exampleName, example) -> - val exampleValue = example.value?.toString() ?: "" - val exampleMap = acc[exampleName] ?: emptyMap() - acc.plus(exampleName to exampleMap.plus(queryParameter.name to exampleValue)) - } - } + val exampleQueryParams = namedExampleParams(operation, QueryParameter::class.java) + val examplePathParams = namedExampleParams(operation, PathParameter::class.java) + + val unionOfParameterKeys = exampleQueryParams.keys.union(examplePathParams.keys) return when (val requestBody = resolveRequestBody(operation)) { null -> { - val examples = exampleQueryParams.mapValues { - listOf(HttpRequest(method = httpMethod, path = urlMatcher.path, queryParams = it.value)) - } + val examples: Map> = unionOfParameterKeys.map { exampleName -> + val queryParams = exampleQueryParams[exampleName] ?: emptyMap() + val pathParams = examplePathParams[exampleName] ?: emptyMap() + + val path = pathParams.entries.fold(urlMatcher.toOpenApiPath()) { acc, (key, value) -> + acc.replace("{$key}", value) + } + + exampleName to listOf(HttpRequest(method = httpMethod, path = path, queryParams = queryParams)) + }.toMap() listOf( Pair(requestPattern, examples) @@ -764,6 +760,20 @@ class OpenApiSpecification(private val openApiFilePath: String, private val pars } } + private fun namedExampleParams(operation: Operation, parameterType: Class): Map> = operation.parameters.orEmpty() + .filterIsInstance(parameterType) + .fold(emptyMap>()) { acc, parameter -> + + parameter + .examples.orEmpty() + .entries + .fold(acc) { acc, (exampleName, example) -> + val exampleValue = example.value?.toString() ?: "" + val exampleMap = acc[exampleName] ?: emptyMap() + acc.plus(exampleName to exampleMap.plus(parameter.name to exampleValue)) + } + } + private fun resolveRequestBody(operation: Operation): RequestBody? = operation.requestBody?.`$ref`?.let { resolveReferenceToRequestBody(it).second diff --git a/core/src/test/kotlin/in/specmatic/stub/HttpStubTest.kt b/core/src/test/kotlin/in/specmatic/stub/HttpStubTest.kt index 74116de6d..d7629c09f 100644 --- a/core/src/test/kotlin/in/specmatic/stub/HttpStubTest.kt +++ b/core/src/test/kotlin/in/specmatic/stub/HttpStubTest.kt @@ -2,7 +2,6 @@ package `in`.specmatic.stub import `in`.specmatic.conversions.OpenApiSpecification import `in`.specmatic.core.* -import `in`.specmatic.core.HttpRequest import `in`.specmatic.core.pattern.* import `in`.specmatic.core.value.NumberValue import `in`.specmatic.core.value.StringValue @@ -883,7 +882,7 @@ paths: """.trim(), "" ).toFeature() - val featureWithPathParamExamples = OpenApiSpecification.fromYAML( + private val featureWithPathParamExamples = OpenApiSpecification.fromYAML( """ openapi: 3.0.1 info: @@ -900,8 +899,10 @@ paths: in: path required: true examples: - QUERY_SUCCESS: + PATH_SUCCESS: value: xyz123 + PATH_FAILURE: + value: abc123 responses: "200": description: Data @@ -913,8 +914,17 @@ paths: type: string examples: - QUERY_SUCCESS: + PATH_SUCCESS: value: ["one", "two"] + "404": + description: Data not found + content: + text/plain: + schema: + type: string + examples: + PATH_FAILURE: + value: "Could not find abc123" """.trim(), "" ).toFeature() @@ -949,6 +959,11 @@ paths: assertThat(response.status).isEqualTo(200) assertThat(response.body).isEqualTo(parsedJSONArray("""["one", "two"]""")) } + stub.client.execute(HttpRequest("GET", "/abc123")) + .let { response -> + assertThat(response.status).isEqualTo(404) + assertThat(response.body.toStringLiteral()).isEqualTo("Could not find abc123") + } } } From 6c447638959cb3a0956443917bb1dd99e3060888 Mon Sep 17 00:00:00 2001 From: Hari Krishnan Date: Tue, 2 Jan 2024 17:24:24 +0530 Subject: [PATCH 3/6] using header param examples as stub responses --- .../conversions/OpenApiSpecification.kt | 6 +- .../kotlin/in/specmatic/stub/HttpStubTest.kt | 57 +++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/core/src/main/kotlin/in/specmatic/conversions/OpenApiSpecification.kt b/core/src/main/kotlin/in/specmatic/conversions/OpenApiSpecification.kt index 35304d45c..92fa4fd6e 100644 --- a/core/src/main/kotlin/in/specmatic/conversions/OpenApiSpecification.kt +++ b/core/src/main/kotlin/in/specmatic/conversions/OpenApiSpecification.kt @@ -669,20 +669,22 @@ class OpenApiSpecification(private val openApiFilePath: String, private val pars val exampleQueryParams = namedExampleParams(operation, QueryParameter::class.java) val examplePathParams = namedExampleParams(operation, PathParameter::class.java) + val exampleHeaderParams = namedExampleParams(operation, HeaderParameter::class.java) - val unionOfParameterKeys = exampleQueryParams.keys.union(examplePathParams.keys) + val unionOfParameterKeys = (exampleQueryParams.keys + examplePathParams.keys + exampleHeaderParams.keys).distinct() return when (val requestBody = resolveRequestBody(operation)) { null -> { val examples: Map> = unionOfParameterKeys.map { exampleName -> val queryParams = exampleQueryParams[exampleName] ?: emptyMap() val pathParams = examplePathParams[exampleName] ?: emptyMap() + val headerParams = exampleHeaderParams[exampleName] ?: emptyMap() val path = pathParams.entries.fold(urlMatcher.toOpenApiPath()) { acc, (key, value) -> acc.replace("{$key}", value) } - exampleName to listOf(HttpRequest(method = httpMethod, path = path, queryParams = queryParams)) + exampleName to listOf(HttpRequest(method = httpMethod, path = path, queryParams = queryParams, headers = headerParams)) }.toMap() listOf( diff --git a/core/src/test/kotlin/in/specmatic/stub/HttpStubTest.kt b/core/src/test/kotlin/in/specmatic/stub/HttpStubTest.kt index d7629c09f..bfb04b174 100644 --- a/core/src/test/kotlin/in/specmatic/stub/HttpStubTest.kt +++ b/core/src/test/kotlin/in/specmatic/stub/HttpStubTest.kt @@ -928,6 +928,47 @@ paths: """.trim(), "" ).toFeature() + private val featureWithHeaderParamExamples = OpenApiSpecification.fromYAML( + """ +openapi: 3.0.1 +info: + title: Header Example API + version: "1" +paths: + /hello: + get: + parameters: + - name: userId + schema: + type: string + in: header + required: true + examples: + HEADER_SUCCESS: + value: John + HEADER_FAILURE: + value: Jane + responses: + "200": + description: Data + content: + text/plain: + schema: + type: string + examples: + HEADER_SUCCESS: + value: "Hello John" + "401": + description: Unauthorized + content: + text/plain: + schema: + type: string + examples: + HEADER_FAILURE: + value: "User Jane not authorized" +""".trim(), "" + ).toFeature() @Test fun `expectations for payload from examples`() { @@ -967,6 +1008,22 @@ paths: } } + @Test + fun `expectations for header params from examples`() { + HttpStub(featureWithHeaderParamExamples).use { stub -> + stub.client.execute(HttpRequest("GET", "/hello", headers = mapOf("userId" to "John"))) + .let { response -> + assertThat(response.status).isEqualTo(200) + assertThat(response.body.toStringLiteral()).isEqualTo("Hello John") + } + stub.client.execute(HttpRequest("GET", "/hello", headers = mapOf("userId" to "Jane"))) + .let { response -> + assertThat(response.status).isEqualTo(401) + assertThat(response.body.toStringLiteral()).isEqualTo("User Jane not authorized") + } + } + } + @Test fun `expectations from examples should have less priority than file expectations`() { HttpStub( From 8fe50bf0c863790f24308edac9598bc3b1a78c8e Mon Sep 17 00:00:00 2001 From: Hari Krishnan Date: Tue, 2 Jan 2024 17:36:11 +0530 Subject: [PATCH 4/6] accounting for (OMIT) in while using parameter examples as stub responses --- .../conversions/OpenApiSpecification.kt | 2 +- .../kotlin/in/specmatic/stub/HttpStubTest.kt | 54 ++++++++++++++++++- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/core/src/main/kotlin/in/specmatic/conversions/OpenApiSpecification.kt b/core/src/main/kotlin/in/specmatic/conversions/OpenApiSpecification.kt index 92fa4fd6e..eaf7cb66c 100644 --- a/core/src/main/kotlin/in/specmatic/conversions/OpenApiSpecification.kt +++ b/core/src/main/kotlin/in/specmatic/conversions/OpenApiSpecification.kt @@ -768,7 +768,7 @@ class OpenApiSpecification(private val openApiFilePath: String, private val pars parameter .examples.orEmpty() - .entries + .entries.filter { it.value.value?.toString().orEmpty() !in OMIT } .fold(acc) { acc, (exampleName, example) -> val exampleValue = example.value?.toString() ?: "" val exampleMap = acc[exampleName] ?: emptyMap() diff --git a/core/src/test/kotlin/in/specmatic/stub/HttpStubTest.kt b/core/src/test/kotlin/in/specmatic/stub/HttpStubTest.kt index bfb04b174..af480fdde 100644 --- a/core/src/test/kotlin/in/specmatic/stub/HttpStubTest.kt +++ b/core/src/test/kotlin/in/specmatic/stub/HttpStubTest.kt @@ -803,7 +803,7 @@ paths: @Nested inner class ExpectationPriorities { - val featureWithBodyExamples = OpenApiSpecification.fromYAML( + private val featureWithBodyExamples = OpenApiSpecification.fromYAML( """ openapi: 3.0.1 info: @@ -847,7 +847,7 @@ paths: """.trim(), "" ).toFeature() - val featureWithQueryParamExamples = OpenApiSpecification.fromYAML( + private val featureWithQueryParamExamples = OpenApiSpecification.fromYAML( """ openapi: 3.0.1 info: @@ -970,6 +970,45 @@ paths: """.trim(), "" ).toFeature() + private val featureWithOmittedParamExamples = OpenApiSpecification.fromYAML( + """ +openapi: 3.0.1 +info: + title: Header Example API + version: "1" +paths: + /hello: + get: + parameters: + - name: userId + schema: + type: string + in: header + required: true + examples: + HEADER_SUCCESS: + value: John + - name: role + schema: + type: string + in: header + required: false + examples: + HEADER_SUCCESS: + value: "(omit)" + responses: + "200": + description: Data + content: + text/plain: + schema: + type: string + examples: + HEADER_SUCCESS: + value: "Hello John" +""".trim(), "" + ).toFeature() + @Test fun `expectations for payload from examples`() { HttpStub(featureWithBodyExamples).use { stub -> @@ -1024,6 +1063,17 @@ paths: } } + @Test + fun `expectations from examples ignores omitted parameters while matching stub request`() { + HttpStub(featureWithOmittedParamExamples).use { stub -> + stub.client.execute(HttpRequest("GET", "/hello", headers = mapOf("userId" to "John"))) + .let { response -> + assertThat(response.status).isEqualTo(200) + assertThat(response.body.toStringLiteral()).isEqualTo("Hello John") + } + } + } + @Test fun `expectations from examples should have less priority than file expectations`() { HttpStub( From e4570532d7119251697b4c8260b9d9d760596fe3 Mon Sep 17 00:00:00 2001 From: Hari Krishnan Date: Tue, 2 Jan 2024 17:56:06 +0530 Subject: [PATCH 5/6] accounting for security schemes when using parameter examples as stub data --- .../conversions/OpenApiSpecification.kt | 9 +- .../specmatic/conversions/ExamplesAsStub.kt | 115 ++++++++++++++++++ 2 files changed, 123 insertions(+), 1 deletion(-) diff --git a/core/src/main/kotlin/in/specmatic/conversions/OpenApiSpecification.kt b/core/src/main/kotlin/in/specmatic/conversions/OpenApiSpecification.kt index eaf7cb66c..e88ebb2b4 100644 --- a/core/src/main/kotlin/in/specmatic/conversions/OpenApiSpecification.kt +++ b/core/src/main/kotlin/in/specmatic/conversions/OpenApiSpecification.kt @@ -684,7 +684,14 @@ class OpenApiSpecification(private val openApiFilePath: String, private val pars acc.replace("{$key}", value) } - exampleName to listOf(HttpRequest(method = httpMethod, path = path, queryParams = queryParams, headers = headerParams)) + val httpRequest = + HttpRequest(method = httpMethod, path = path, queryParams = queryParams, headers = headerParams) + + val requestsWithSecurityParams: List = securitySchemes.map { (_, securityScheme) -> + securityScheme.addTo(httpRequest) + } + + exampleName to requestsWithSecurityParams }.toMap() listOf( diff --git a/core/src/test/kotlin/in/specmatic/conversions/ExamplesAsStub.kt b/core/src/test/kotlin/in/specmatic/conversions/ExamplesAsStub.kt index 9af9b0e5b..4ca793e66 100644 --- a/core/src/test/kotlin/in/specmatic/conversions/ExamplesAsStub.kt +++ b/core/src/test/kotlin/in/specmatic/conversions/ExamplesAsStub.kt @@ -71,4 +71,119 @@ security: assertThat(response.body.toStringLiteral()).isEqualTo("Hello to you!") } } + + @Test + fun `examples as stub should work for request with body when there are no security schemes`() { + val feature = OpenApiSpecification.fromYAML(""" +openapi: 3.0.0 +info: + title: Sample API + description: Optional multiline or single-line description in [CommonMark](http://commonmark.org/help/) or HTML. + version: 0.1.9 +servers: + - url: http://api.example.com/v1 + description: Optional server description, e.g. Main (production) server + - url: http://staging-api.example.com + description: Optional server description, e.g. Internal staging server for testing +paths: + /hello: + post: + summary: hello world + description: Optional extended description in CommonMark or HTML. + requestBody: + content: + application/json: + schema: + type: object + required: + - message + properties: + message: + type: string + examples: + SUCCESS: + value: + message: Hello World! + responses: + '200': + description: Says hello + content: + application/json: + schema: + type: string + examples: + SUCCESS: + value: + Hello to you! + """.trimIndent(), "").toFeature() + + HttpStub(feature).use { + val response = it.client.execute(HttpRequest( + "POST", + "/hello", + body = parsedJSONObject("""{"message": "Hello World!"}""") + )) + + assertThat(response.body.toStringLiteral()).isEqualTo("Hello to you!") + } + } + + @Test + fun `any value should match the given security scheme when request body does not exist`() { + val feature = OpenApiSpecification.fromYAML(""" +openapi: 3.0.0 +info: + title: Sample API + description: Optional multiline or single-line description in [CommonMark](http://commonmark.org/help/) or HTML. + version: 0.1.9 +servers: + - url: http://api.example.com/v1 + description: Optional server description, e.g. Main (production) server + - url: http://staging-api.example.com + description: Optional server description, e.g. Internal staging server for testing +paths: + /hello: + get: + summary: hello world + description: Optional extended description in CommonMark or HTML. + parameters: + - name: greeting + schema: + type: string + in: query + examples: + SUCCESS: + value: "Hello" + responses: + '200': + description: Says hello + content: + application/json: + schema: + type: string + examples: + SUCCESS: + value: + Hello to you! +components: + securitySchemes: + BearerAuth: + type: http + scheme: bearer + +security: + - BearerAuth: [] + """.trimIndent(), "").toFeature() + + HttpStub(feature).use { + val response = it.client.execute(HttpRequest( + "GET", + "/hello", + mapOf("Authorization" to "Bearer 1234"), + queryParams = mapOf("greeting" to "Hello") + )) + + assertThat(response.body.toStringLiteral()).isEqualTo("Hello to you!") + } + } } \ No newline at end of file From b55abc504324b0d28463c189ed34e2ad6bd91297 Mon Sep 17 00:00:00 2001 From: Hari Krishnan Date: Tue, 2 Jan 2024 18:01:59 +0530 Subject: [PATCH 6/6] moving all exammples as stub data related tests to ExamplesStubStubTest --- .../specmatic/conversions/ExamplesAsStub.kt | 189 -------- .../conversions/ExamplesAsStubTest.kt | 406 ++++++++++++++++++ .../kotlin/in/specmatic/stub/HttpStubTest.kt | 216 ---------- 3 files changed, 406 insertions(+), 405 deletions(-) delete mode 100644 core/src/test/kotlin/in/specmatic/conversions/ExamplesAsStub.kt create mode 100644 core/src/test/kotlin/in/specmatic/conversions/ExamplesAsStubTest.kt diff --git a/core/src/test/kotlin/in/specmatic/conversions/ExamplesAsStub.kt b/core/src/test/kotlin/in/specmatic/conversions/ExamplesAsStub.kt deleted file mode 100644 index 4ca793e66..000000000 --- a/core/src/test/kotlin/in/specmatic/conversions/ExamplesAsStub.kt +++ /dev/null @@ -1,189 +0,0 @@ -package `in`.specmatic.conversions - -import `in`.specmatic.core.HttpRequest -import `in`.specmatic.core.pattern.parsedJSONObject -import `in`.specmatic.stub.HttpStub -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Test - -class ExamplesAsStub { - @Test - fun `any value should match the given security scheme`() { - val feature = OpenApiSpecification.fromYAML(""" -openapi: 3.0.0 -info: - title: Sample API - description: Optional multiline or single-line description in [CommonMark](http://commonmark.org/help/) or HTML. - version: 0.1.9 -servers: - - url: http://api.example.com/v1 - description: Optional server description, e.g. Main (production) server - - url: http://staging-api.example.com - description: Optional server description, e.g. Internal staging server for testing -paths: - /hello: - post: - summary: hello world - description: Optional extended description in CommonMark or HTML. - requestBody: - content: - application/json: - schema: - type: object - required: - - message - properties: - message: - type: string - examples: - SUCCESS: - value: - message: Hello World! - responses: - '200': - description: Says hello - content: - application/json: - schema: - type: string - examples: - SUCCESS: - value: - Hello to you! -components: - securitySchemes: - BearerAuth: - type: http - scheme: bearer - -security: - - BearerAuth: [] - """.trimIndent(), "").toFeature() - - HttpStub(feature).use { - val response = it.client.execute(HttpRequest( - "POST", - "/hello", - mapOf("Authorization" to "Bearer 1234"), - parsedJSONObject("""{"message": "Hello World!"}""") - )) - - assertThat(response.body.toStringLiteral()).isEqualTo("Hello to you!") - } - } - - @Test - fun `examples as stub should work for request with body when there are no security schemes`() { - val feature = OpenApiSpecification.fromYAML(""" -openapi: 3.0.0 -info: - title: Sample API - description: Optional multiline or single-line description in [CommonMark](http://commonmark.org/help/) or HTML. - version: 0.1.9 -servers: - - url: http://api.example.com/v1 - description: Optional server description, e.g. Main (production) server - - url: http://staging-api.example.com - description: Optional server description, e.g. Internal staging server for testing -paths: - /hello: - post: - summary: hello world - description: Optional extended description in CommonMark or HTML. - requestBody: - content: - application/json: - schema: - type: object - required: - - message - properties: - message: - type: string - examples: - SUCCESS: - value: - message: Hello World! - responses: - '200': - description: Says hello - content: - application/json: - schema: - type: string - examples: - SUCCESS: - value: - Hello to you! - """.trimIndent(), "").toFeature() - - HttpStub(feature).use { - val response = it.client.execute(HttpRequest( - "POST", - "/hello", - body = parsedJSONObject("""{"message": "Hello World!"}""") - )) - - assertThat(response.body.toStringLiteral()).isEqualTo("Hello to you!") - } - } - - @Test - fun `any value should match the given security scheme when request body does not exist`() { - val feature = OpenApiSpecification.fromYAML(""" -openapi: 3.0.0 -info: - title: Sample API - description: Optional multiline or single-line description in [CommonMark](http://commonmark.org/help/) or HTML. - version: 0.1.9 -servers: - - url: http://api.example.com/v1 - description: Optional server description, e.g. Main (production) server - - url: http://staging-api.example.com - description: Optional server description, e.g. Internal staging server for testing -paths: - /hello: - get: - summary: hello world - description: Optional extended description in CommonMark or HTML. - parameters: - - name: greeting - schema: - type: string - in: query - examples: - SUCCESS: - value: "Hello" - responses: - '200': - description: Says hello - content: - application/json: - schema: - type: string - examples: - SUCCESS: - value: - Hello to you! -components: - securitySchemes: - BearerAuth: - type: http - scheme: bearer - -security: - - BearerAuth: [] - """.trimIndent(), "").toFeature() - - HttpStub(feature).use { - val response = it.client.execute(HttpRequest( - "GET", - "/hello", - mapOf("Authorization" to "Bearer 1234"), - queryParams = mapOf("greeting" to "Hello") - )) - - assertThat(response.body.toStringLiteral()).isEqualTo("Hello to you!") - } - } -} \ No newline at end of file diff --git a/core/src/test/kotlin/in/specmatic/conversions/ExamplesAsStubTest.kt b/core/src/test/kotlin/in/specmatic/conversions/ExamplesAsStubTest.kt new file mode 100644 index 000000000..7f569beff --- /dev/null +++ b/core/src/test/kotlin/in/specmatic/conversions/ExamplesAsStubTest.kt @@ -0,0 +1,406 @@ +package `in`.specmatic.conversions + +import `in`.specmatic.core.HttpRequest +import `in`.specmatic.core.pattern.parsedJSONArray +import `in`.specmatic.core.pattern.parsedJSONObject +import `in`.specmatic.stub.HttpStub +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +class ExamplesAsStubTest { + private val featureWithQueryParamExamples = OpenApiSpecification.fromYAML( + """ +openapi: 3.0.1 +info: + title: Data API + version: "1" +paths: + /: + get: + summary: Data + parameters: + - name: type + schema: + type: string + in: query + required: true + examples: + QUERY_SUCCESS: + value: data + responses: + "200": + description: Data + content: + application/json: + schema: + type: array + items: + type: + string + examples: + QUERY_SUCCESS: + value: ["one", "two"] +""".trim(), "" + ).toFeature() + + private val featureWithPathParamExamples = OpenApiSpecification.fromYAML( + """ +openapi: 3.0.1 +info: + title: Data API + version: "1" +paths: + /{productId}: + get: + summary: Data + parameters: + - name: productId + schema: + type: string + in: path + required: true + examples: + PATH_SUCCESS: + value: xyz123 + PATH_FAILURE: + value: abc123 + responses: + "200": + description: Data + content: + application/json: + schema: + type: array + items: + type: + string + examples: + PATH_SUCCESS: + value: ["one", "two"] + "404": + description: Data not found + content: + text/plain: + schema: + type: string + examples: + PATH_FAILURE: + value: "Could not find abc123" +""".trim(), "" + ).toFeature() + + private val featureWithHeaderParamExamples = OpenApiSpecification.fromYAML( + """ +openapi: 3.0.1 +info: + title: Header Example API + version: "1" +paths: + /hello: + get: + parameters: + - name: userId + schema: + type: string + in: header + required: true + examples: + HEADER_SUCCESS: + value: John + HEADER_FAILURE: + value: Jane + responses: + "200": + description: Data + content: + text/plain: + schema: + type: string + examples: + HEADER_SUCCESS: + value: "Hello John" + "401": + description: Unauthorized + content: + text/plain: + schema: + type: string + examples: + HEADER_FAILURE: + value: "User Jane not authorized" +""".trim(), "" + ).toFeature() + + private val featureWithOmittedParamExamples = OpenApiSpecification.fromYAML( + """ +openapi: 3.0.1 +info: + title: Header Example API + version: "1" +paths: + /hello: + get: + parameters: + - name: userId + schema: + type: string + in: header + required: true + examples: + HEADER_SUCCESS: + value: John + - name: role + schema: + type: string + in: header + required: false + examples: + HEADER_SUCCESS: + value: "(omit)" + responses: + "200": + description: Data + content: + text/plain: + schema: + type: string + examples: + HEADER_SUCCESS: + value: "Hello John" +""".trim(), "" + ).toFeature() + + @Test + fun `any value should match the given security scheme`() { + val feature = OpenApiSpecification.fromYAML(""" +openapi: 3.0.0 +info: + title: Sample API + description: Optional multiline or single-line description in [CommonMark](http://commonmark.org/help/) or HTML. + version: 0.1.9 +servers: + - url: http://api.example.com/v1 + description: Optional server description, e.g. Main (production) server + - url: http://staging-api.example.com + description: Optional server description, e.g. Internal staging server for testing +paths: + /hello: + post: + summary: hello world + description: Optional extended description in CommonMark or HTML. + requestBody: + content: + application/json: + schema: + type: object + required: + - message + properties: + message: + type: string + examples: + SUCCESS: + value: + message: Hello World! + responses: + '200': + description: Says hello + content: + application/json: + schema: + type: string + examples: + SUCCESS: + value: + Hello to you! +components: + securitySchemes: + BearerAuth: + type: http + scheme: bearer + +security: + - BearerAuth: [] + """.trimIndent(), "").toFeature() + + HttpStub(feature).use { + val response = it.client.execute(HttpRequest( + "POST", + "/hello", + mapOf("Authorization" to "Bearer 1234"), + parsedJSONObject("""{"message": "Hello World!"}""") + )) + + assertThat(response.body.toStringLiteral()).isEqualTo("Hello to you!") + } + } + + @Test + fun `examples as stub should work for request with body when there are no security schemes`() { + val feature = OpenApiSpecification.fromYAML(""" +openapi: 3.0.0 +info: + title: Sample API + description: Optional multiline or single-line description in [CommonMark](http://commonmark.org/help/) or HTML. + version: 0.1.9 +servers: + - url: http://api.example.com/v1 + description: Optional server description, e.g. Main (production) server + - url: http://staging-api.example.com + description: Optional server description, e.g. Internal staging server for testing +paths: + /hello: + post: + summary: hello world + description: Optional extended description in CommonMark or HTML. + requestBody: + content: + application/json: + schema: + type: object + required: + - message + properties: + message: + type: string + examples: + SUCCESS: + value: + message: Hello World! + responses: + '200': + description: Says hello + content: + application/json: + schema: + type: string + examples: + SUCCESS: + value: + Hello to you! + """.trimIndent(), "").toFeature() + + HttpStub(feature).use { + val response = it.client.execute(HttpRequest( + "POST", + "/hello", + body = parsedJSONObject("""{"message": "Hello World!"}""") + )) + + assertThat(response.body.toStringLiteral()).isEqualTo("Hello to you!") + } + } + + @Test + fun `any value should match the given security scheme when request body does not exist`() { + val feature = OpenApiSpecification.fromYAML(""" +openapi: 3.0.0 +info: + title: Sample API + description: Optional multiline or single-line description in [CommonMark](http://commonmark.org/help/) or HTML. + version: 0.1.9 +servers: + - url: http://api.example.com/v1 + description: Optional server description, e.g. Main (production) server + - url: http://staging-api.example.com + description: Optional server description, e.g. Internal staging server for testing +paths: + /hello: + get: + summary: hello world + description: Optional extended description in CommonMark or HTML. + parameters: + - name: greeting + schema: + type: string + in: query + examples: + SUCCESS: + value: "Hello" + responses: + '200': + description: Says hello + content: + application/json: + schema: + type: string + examples: + SUCCESS: + value: + Hello to you! +components: + securitySchemes: + BearerAuth: + type: http + scheme: bearer + +security: + - BearerAuth: [] + """.trimIndent(), "").toFeature() + + HttpStub(feature).use { + val response = it.client.execute(HttpRequest( + "GET", + "/hello", + mapOf("Authorization" to "Bearer 1234"), + queryParams = mapOf("greeting" to "Hello") + )) + + assertThat(response.body.toStringLiteral()).isEqualTo("Hello to you!") + } + } + + @Test + fun `expectations for query params from examples`() { + HttpStub(featureWithQueryParamExamples).use { stub -> + stub.client.execute(HttpRequest("GET", "/?type=data")) + .let { response -> + assertThat(response.status).isEqualTo(200) + assertThat(response.body).isEqualTo(parsedJSONArray("""["one", "two"]""")) + } + } + } + + @Test + fun `expectations for path params from examples`() { + HttpStub(featureWithPathParamExamples).use { stub -> + stub.client.execute(HttpRequest("GET", "/xyz123")) + .let { response -> + assertThat(response.status).isEqualTo(200) + assertThat(response.body).isEqualTo(parsedJSONArray("""["one", "two"]""")) + } + stub.client.execute(HttpRequest("GET", "/abc123")) + .let { response -> + assertThat(response.status).isEqualTo(404) + assertThat(response.body.toStringLiteral()).isEqualTo("Could not find abc123") + } + } + } + + @Test + fun `expectations for header params from examples`() { + HttpStub(featureWithHeaderParamExamples).use { stub -> + stub.client.execute(HttpRequest("GET", "/hello", headers = mapOf("userId" to "John"))) + .let { response -> + assertThat(response.status).isEqualTo(200) + assertThat(response.body.toStringLiteral()).isEqualTo("Hello John") + } + stub.client.execute(HttpRequest("GET", "/hello", headers = mapOf("userId" to "Jane"))) + .let { response -> + assertThat(response.status).isEqualTo(401) + assertThat(response.body.toStringLiteral()).isEqualTo("User Jane not authorized") + } + } + } + + @Test + fun `expectations from examples ignores omitted parameters while matching stub request`() { + HttpStub(featureWithOmittedParamExamples).use { stub -> + stub.client.execute(HttpRequest("GET", "/hello", headers = mapOf("userId" to "John"))) + .let { response -> + assertThat(response.status).isEqualTo(200) + assertThat(response.body.toStringLiteral()).isEqualTo("Hello John") + } + } + } +} \ No newline at end of file diff --git a/core/src/test/kotlin/in/specmatic/stub/HttpStubTest.kt b/core/src/test/kotlin/in/specmatic/stub/HttpStubTest.kt index af480fdde..e374ae05f 100644 --- a/core/src/test/kotlin/in/specmatic/stub/HttpStubTest.kt +++ b/core/src/test/kotlin/in/specmatic/stub/HttpStubTest.kt @@ -847,168 +847,6 @@ paths: """.trim(), "" ).toFeature() - private val featureWithQueryParamExamples = OpenApiSpecification.fromYAML( - """ -openapi: 3.0.1 -info: - title: Data API - version: "1" -paths: - /: - get: - summary: Data - parameters: - - name: type - schema: - type: string - in: query - required: true - examples: - QUERY_SUCCESS: - value: data - responses: - "200": - description: Data - content: - application/json: - schema: - type: array - items: - type: - string - examples: - QUERY_SUCCESS: - value: ["one", "two"] -""".trim(), "" - ).toFeature() - - private val featureWithPathParamExamples = OpenApiSpecification.fromYAML( - """ -openapi: 3.0.1 -info: - title: Data API - version: "1" -paths: - /{productId}: - get: - summary: Data - parameters: - - name: productId - schema: - type: string - in: path - required: true - examples: - PATH_SUCCESS: - value: xyz123 - PATH_FAILURE: - value: abc123 - responses: - "200": - description: Data - content: - application/json: - schema: - type: array - items: - type: - string - examples: - PATH_SUCCESS: - value: ["one", "two"] - "404": - description: Data not found - content: - text/plain: - schema: - type: string - examples: - PATH_FAILURE: - value: "Could not find abc123" -""".trim(), "" - ).toFeature() - - private val featureWithHeaderParamExamples = OpenApiSpecification.fromYAML( - """ -openapi: 3.0.1 -info: - title: Header Example API - version: "1" -paths: - /hello: - get: - parameters: - - name: userId - schema: - type: string - in: header - required: true - examples: - HEADER_SUCCESS: - value: John - HEADER_FAILURE: - value: Jane - responses: - "200": - description: Data - content: - text/plain: - schema: - type: string - examples: - HEADER_SUCCESS: - value: "Hello John" - "401": - description: Unauthorized - content: - text/plain: - schema: - type: string - examples: - HEADER_FAILURE: - value: "User Jane not authorized" -""".trim(), "" - ).toFeature() - - private val featureWithOmittedParamExamples = OpenApiSpecification.fromYAML( - """ -openapi: 3.0.1 -info: - title: Header Example API - version: "1" -paths: - /hello: - get: - parameters: - - name: userId - schema: - type: string - in: header - required: true - examples: - HEADER_SUCCESS: - value: John - - name: role - schema: - type: string - in: header - required: false - examples: - HEADER_SUCCESS: - value: "(omit)" - responses: - "200": - description: Data - content: - text/plain: - schema: - type: string - examples: - HEADER_SUCCESS: - value: "Hello John" -""".trim(), "" - ).toFeature() - @Test fun `expectations for payload from examples`() { HttpStub(featureWithBodyExamples).use { stub -> @@ -1020,60 +858,6 @@ paths: } } - @Test - fun `expectations for query params from examples`() { - HttpStub(featureWithQueryParamExamples).use { stub -> - stub.client.execute(HttpRequest("GET", "/?type=data")) - .let { response -> - assertThat(response.status).isEqualTo(200) - assertThat(response.body).isEqualTo(parsedJSONArray("""["one", "two"]""")) - } - } - } - - @Test - fun `expectations for path params from examples`() { - HttpStub(featureWithPathParamExamples).use { stub -> - stub.client.execute(HttpRequest("GET", "/xyz123")) - .let { response -> - assertThat(response.status).isEqualTo(200) - assertThat(response.body).isEqualTo(parsedJSONArray("""["one", "two"]""")) - } - stub.client.execute(HttpRequest("GET", "/abc123")) - .let { response -> - assertThat(response.status).isEqualTo(404) - assertThat(response.body.toStringLiteral()).isEqualTo("Could not find abc123") - } - } - } - - @Test - fun `expectations for header params from examples`() { - HttpStub(featureWithHeaderParamExamples).use { stub -> - stub.client.execute(HttpRequest("GET", "/hello", headers = mapOf("userId" to "John"))) - .let { response -> - assertThat(response.status).isEqualTo(200) - assertThat(response.body.toStringLiteral()).isEqualTo("Hello John") - } - stub.client.execute(HttpRequest("GET", "/hello", headers = mapOf("userId" to "Jane"))) - .let { response -> - assertThat(response.status).isEqualTo(401) - assertThat(response.body.toStringLiteral()).isEqualTo("User Jane not authorized") - } - } - } - - @Test - fun `expectations from examples ignores omitted parameters while matching stub request`() { - HttpStub(featureWithOmittedParamExamples).use { stub -> - stub.client.execute(HttpRequest("GET", "/hello", headers = mapOf("userId" to "John"))) - .let { response -> - assertThat(response.status).isEqualTo(200) - assertThat(response.body.toStringLiteral()).isEqualTo("Hello John") - } - } - } - @Test fun `expectations from examples should have less priority than file expectations`() { HttpStub(