From e6e1bd9b8780735cf459d450a96c0c16c2ec5e8f Mon Sep 17 00:00:00 2001 From: Sufiyan Date: Sat, 23 Nov 2024 16:27:03 +0530 Subject: [PATCH] Modifications to response assertions storage - Eliminate storage and reliance on responseValueValidation. - Introduce a new field in the Row to store responses that can be parsed for future assertions. - Rename the field for response value validation in the Row. - Modify test result matching post contract test to override the unexpectedKeyCheck if the request is attribute selected. --- .../io/specmatic/conversions/ExampleFromFile.kt | 3 ++- .../io/specmatic/conversions/OpenApiSpecification.kt | 2 +- core/src/main/kotlin/io/specmatic/core/Feature.kt | 2 +- .../kotlin/io/specmatic/core/HttpResponsePattern.kt | 2 +- core/src/main/kotlin/io/specmatic/core/Scenario.kt | 10 +++++++--- core/src/main/kotlin/io/specmatic/core/pattern/Row.kt | 3 ++- .../kotlin/io/specmatic/test/ExamplePostValidator.kt | 2 +- .../kotlin/io/specmatic/test/ExamplePreProcessor.kt | 2 +- .../main/kotlin/io/specmatic/test/ScenarioAsTest.kt | 11 ++--------- .../specmatic/conversions/OpenApiSpecificationTest.kt | 4 ++-- 10 files changed, 20 insertions(+), 21 deletions(-) diff --git a/core/src/main/kotlin/io/specmatic/conversions/ExampleFromFile.kt b/core/src/main/kotlin/io/specmatic/conversions/ExampleFromFile.kt index 2878de7fd..941273585 100644 --- a/core/src/main/kotlin/io/specmatic/conversions/ExampleFromFile.kt +++ b/core/src/main/kotlin/io/specmatic/conversions/ExampleFromFile.kt @@ -39,7 +39,8 @@ class ExampleFromFile(val json: JSONObjectValue, val file: File) { values, name = testName, fileSource = this.file.canonicalPath, - responseExampleForValidation = responseExample, + exactResponseExample = responseExample.takeUnless { this.isPartial() }, + responseExampleForAssertion = response, requestExample = scenarioStub.getRequestWithAdditionalParamsIfAny(specmaticConfig.additionalExampleParamsFilePath), responseExample = response.takeUnless { this.isPartial() }, isPartial = scenarioStub.partial != null diff --git a/core/src/main/kotlin/io/specmatic/conversions/OpenApiSpecification.kt b/core/src/main/kotlin/io/specmatic/conversions/OpenApiSpecification.kt index b70b11ed3..47009b374 100644 --- a/core/src/main/kotlin/io/specmatic/conversions/OpenApiSpecification.kt +++ b/core/src/main/kotlin/io/specmatic/conversions/OpenApiSpecification.kt @@ -728,7 +728,7 @@ class OpenApiSpecification( } else valueString }, name = exampleName, - responseExampleForValidation = if(resolvedResponseExample != null && responseExample.isNotEmpty()) resolvedResponseExample else null, + exactResponseExample = if(resolvedResponseExample != null && responseExample.isNotEmpty()) resolvedResponseExample else null, requestExample = requestExampleAsHttpRequests[exampleName]?.first(), responseExample = responseExample ) diff --git a/core/src/main/kotlin/io/specmatic/core/Feature.kt b/core/src/main/kotlin/io/specmatic/core/Feature.kt index fc230599d..5021a9cef 100644 --- a/core/src/main/kotlin/io/specmatic/core/Feature.kt +++ b/core/src/main/kotlin/io/specmatic/core/Feature.kt @@ -514,7 +514,7 @@ data class Feature( val scenarioStub = ScenarioStub.readFromFile(File(filePath)) val originalScenario = scenarios.firstOrNull { scenario -> - scenario.matches(scenarioStub.request, scenarioStub.response) is Result.Success + scenario.matches(scenarioStub.request, scenarioStub.response, DefaultMismatchMessages, flagsBased) is Result.Success } ?: return HasFailure(Result.Failure("Could not find an API matching example $filePath")) val concreteTestScenario = Scenario( diff --git a/core/src/main/kotlin/io/specmatic/core/HttpResponsePattern.kt b/core/src/main/kotlin/io/specmatic/core/HttpResponsePattern.kt index 862916a1d..f573ce0bc 100644 --- a/core/src/main/kotlin/io/specmatic/core/HttpResponsePattern.kt +++ b/core/src/main/kotlin/io/specmatic/core/HttpResponsePattern.kt @@ -76,7 +76,7 @@ data class HttpResponsePattern( fun withResponseExampleValue(row: Row, resolver: Resolver): HttpResponsePattern = attempt(breadCrumb = "RESPONSE") { - val responseExample: ResponseExample = row.responseExampleForValidation.takeIf { !row.isPartial } ?: return@attempt this + val responseExample: ResponseExample = row.exactResponseExample ?: return@attempt this val responseExampleMatchResult = matches(responseExample.responseExample, resolver) diff --git a/core/src/main/kotlin/io/specmatic/core/Scenario.kt b/core/src/main/kotlin/io/specmatic/core/Scenario.kt index e1a54edf8..c1dbbacce 100644 --- a/core/src/main/kotlin/io/specmatic/core/Scenario.kt +++ b/core/src/main/kotlin/io/specmatic/core/Scenario.kt @@ -297,10 +297,14 @@ data class Scenario( } } - fun matches(httpRequest: HttpRequest, httpResponse: HttpResponse, mismatchMessages: MismatchMessages = DefaultMismatchMessages, unexpectedKeyCheck: UnexpectedKeyCheck? = null): Result { - val resolver = updatedResolver(mismatchMessages, unexpectedKeyCheck).copy(context = RequestContext(httpRequest)) + fun matchesResponse(httpRequest: HttpRequest, httpResponse: HttpResponse, mismatchMessages: MismatchMessages = DefaultMismatchMessages, unexpectedKeyCheck: UnexpectedKeyCheck? = null): Result { + val updatedUnexpectedKeyCheck = if (isRequestAttributeSelected(httpRequest)) { + ValidateUnexpectedKeys + } else unexpectedKeyCheck - return matches(httpResponse, mismatchMessages, unexpectedKeyCheck, resolver) + val resolver = updatedResolver(mismatchMessages, updatedUnexpectedKeyCheck).copy(context = RequestContext(httpRequest)) + + return matches(httpResponse, mismatchMessages, updatedUnexpectedKeyCheck, resolver) } fun matches(httpRequest: HttpRequest, httpResponse: HttpResponse, mismatchMessages: MismatchMessages, flagsBased: FlagsBased): Result { diff --git a/core/src/main/kotlin/io/specmatic/core/pattern/Row.kt b/core/src/main/kotlin/io/specmatic/core/pattern/Row.kt index 44f661396..15153049c 100644 --- a/core/src/main/kotlin/io/specmatic/core/pattern/Row.kt +++ b/core/src/main/kotlin/io/specmatic/core/pattern/Row.kt @@ -16,7 +16,8 @@ data class Row( val name: String = "", val fileSource: String? = null, val requestBodyJSONExample: JSONExample? = null, - val responseExampleForValidation: ResponseExample? = null, + val responseExampleForAssertion: HttpResponse? = null, + val exactResponseExample: ResponseExample? = null, val requestExample: HttpRequest? = null, val responseExample: HttpResponse? = null, val isPartial: Boolean = false diff --git a/core/src/main/kotlin/io/specmatic/test/ExamplePostValidator.kt b/core/src/main/kotlin/io/specmatic/test/ExamplePostValidator.kt index d9cc2db58..a571ccaa9 100644 --- a/core/src/main/kotlin/io/specmatic/test/ExamplePostValidator.kt +++ b/core/src/main/kotlin/io/specmatic/test/ExamplePostValidator.kt @@ -21,7 +21,7 @@ object ExamplePostValidator: ResponseValidator { } private fun Row.toAsserts(): List { - val responseExampleBody = this.responseExampleForValidation?.responseExample ?: return emptyList() + val responseExampleBody = this.responseExampleForAssertion ?: return emptyList() val headerAsserts = responseExampleBody.headers.map { parsedAssert("RESPONSE.HEADERS", it.key, StringValue(it.value)) diff --git a/core/src/main/kotlin/io/specmatic/test/ExamplePreProcessor.kt b/core/src/main/kotlin/io/specmatic/test/ExamplePreProcessor.kt index 89ceeeb2d..2eb9ce186 100644 --- a/core/src/main/kotlin/io/specmatic/test/ExamplePreProcessor.kt +++ b/core/src/main/kotlin/io/specmatic/test/ExamplePreProcessor.kt @@ -155,7 +155,7 @@ object ExampleProcessor { return } - val bodyToCheck = exampleRow.responseExample?.body ?: exampleRow.responseExampleForValidation?.responseExample?.body + val bodyToCheck = exampleRow.responseExampleForAssertion?.body bodyToCheck?.ifContainsStoreToken { type -> runningEntity = when (type) { StoreType.REPLACE -> httpResponse.body.toFactStore(prefix = "ENTITY") diff --git a/core/src/main/kotlin/io/specmatic/test/ScenarioAsTest.kt b/core/src/main/kotlin/io/specmatic/test/ScenarioAsTest.kt index 149f1b5da..f083df636 100644 --- a/core/src/main/kotlin/io/specmatic/test/ScenarioAsTest.kt +++ b/core/src/main/kotlin/io/specmatic/test/ScenarioAsTest.kt @@ -124,16 +124,10 @@ data class ScenarioAsTest( } } - private fun testResult( - request: HttpRequest, - response: HttpResponse, - testScenario: Scenario, - flagsBased: FlagsBased? = null - ): Result { - + private fun testResult(request: HttpRequest, response: HttpResponse, testScenario: Scenario, flagsBased: FlagsBased): Result { val result = when { response.specmaticResultHeaderValue() == "failure" -> Result.Failure(response.body.toStringLiteral()).updateScenario(testScenario) - else -> testScenario.matches(request, response, ContractAndResponseMismatch, flagsBased?.unexpectedKeyCheck ?: ValidateUnexpectedKeys) + else -> testScenario.matchesResponse(request, response, ContractAndResponseMismatch, flagsBased.unexpectedKeyCheck ?: ValidateUnexpectedKeys) } if (result is Result.Success && result.isPartialSuccess()) { @@ -143,7 +137,6 @@ data class ScenarioAsTest( return result } - } private fun LogMessage.withComment(comment: String?): LogMessage { diff --git a/core/src/test/kotlin/io/specmatic/conversions/OpenApiSpecificationTest.kt b/core/src/test/kotlin/io/specmatic/conversions/OpenApiSpecificationTest.kt index 6896ed630..5204b3a27 100644 --- a/core/src/test/kotlin/io/specmatic/conversions/OpenApiSpecificationTest.kt +++ b/core/src/test/kotlin/io/specmatic/conversions/OpenApiSpecificationTest.kt @@ -350,7 +350,7 @@ Pet: val (scenarioInfos, _) = openApiSpecification.toScenarioInfos() val examples = scenarioInfos.first().examples.flatMap { - it.rows.map { row -> row.responseExampleForValidation } + it.rows.map { row -> row.exactResponseExample } } examples.forEach { assertThat(it).isInstanceOf(ResponseValueExample::class.java) @@ -364,7 +364,7 @@ Pet: val (scenarioInfos, _) = openApiSpecification.toScenarioInfos() val examples = scenarioInfos.first().examples.flatMap { - it.rows.map { row -> row.responseExampleForValidation } + it.rows.map { row -> row.exactResponseExample } } examples.forEach { assertThat(it).isNull()