From 2380053aef58a633317adbc0e90902f608787b78 Mon Sep 17 00:00:00 2001 From: kciesielski Date: Mon, 7 Aug 2023 11:36:32 +0200 Subject: [PATCH 01/10] Fail when status = NoContent but there's a body --- .../sttp/tapir/server/interpreter/ServerInterpreter.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/server/core/src/main/scala/sttp/tapir/server/interpreter/ServerInterpreter.scala b/server/core/src/main/scala/sttp/tapir/server/interpreter/ServerInterpreter.scala index ba9b0a0579..a78179ed2d 100644 --- a/server/core/src/main/scala/sttp/tapir/server/interpreter/ServerInterpreter.scala +++ b/server/core/src/main/scala/sttp/tapir/server/interpreter/ServerInterpreter.scala @@ -220,9 +220,10 @@ class ServerInterpreter[R, F[_], B, S]( val statusCode = outputValues.statusCode.getOrElse(defaultStatusCode) val headers = outputValues.headers - outputValues.body match { - case Some(bodyFromHeaders) => ServerResponse(statusCode, headers, Some(bodyFromHeaders(Headers(headers))), Some(output)).unit - case None => ServerResponse(statusCode, headers, None: Option[B], Some(output)).unit + (statusCode, outputValues.body) match { + case (StatusCode.NoContent, Some(_)) => monad.error(new IllegalStateException("Unexpected response body when status code == NoContent (204)")) + case (_, Some(bodyFromHeaders)) => ServerResponse(statusCode, headers, Some(bodyFromHeaders(Headers(headers))), Some(output)).unit + case (_, None) => ServerResponse(statusCode, headers, None: Option[B], Some(output)).unit } } } From ab6d4a919f26eeecd9288720314a7e1dffe46212 Mon Sep 17 00:00:00 2001 From: kciesielski Date: Mon, 7 Aug 2023 11:47:43 +0200 Subject: [PATCH 02/10] Add a test case --- .../scala/sttp/tapir/server/tests/ServerBasicTests.scala | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala b/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala index e82f8aa2dd..086d9b4a6f 100644 --- a/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala +++ b/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala @@ -769,6 +769,14 @@ class ServerBasicTests[F[_], OPTIONS, ROUTE]( r.code shouldBe StatusCode.InternalServerError r.body shouldBe Symbol("left") } + }, + testServer( + "fail when code = NoContent but there's a body", + NonEmptyList.of( + route(endpoint.out(jsonBody[Unit]).out(statusCode(StatusCode.NoContent)).serverLogicSuccess(_ => pureResult(()))) + ) + ) { (backend, baseUri) => + basicRequest.get(uri"$baseUri").send(backend).map(_.code shouldBe StatusCode.InternalServerError) } ) From 42dda45367a477b0a69eedd0614efb6d27c7febd Mon Sep 17 00:00:00 2001 From: kciesielski Date: Mon, 7 Aug 2023 12:09:48 +0200 Subject: [PATCH 03/10] Refactor test --- .../scala/sttp/tapir/server/tests/ServerBasicTests.scala | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala b/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala index 086d9b4a6f..9ec6ed8d9b 100644 --- a/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala +++ b/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala @@ -771,13 +771,11 @@ class ServerBasicTests[F[_], OPTIONS, ROUTE]( } }, testServer( - "fail when code = NoContent but there's a body", + "fail when status is 204 NoContent, but there's a body", NonEmptyList.of( route(endpoint.out(jsonBody[Unit]).out(statusCode(StatusCode.NoContent)).serverLogicSuccess(_ => pureResult(()))) ) - ) { (backend, baseUri) => - basicRequest.get(uri"$baseUri").send(backend).map(_.code shouldBe StatusCode.InternalServerError) - } + ) { (backend, baseUri) => basicRequest.get(baseUri).send(backend).map(_.code shouldBe StatusCode.InternalServerError) } ) def throwFruits(name: String): F[String] = From a4e479007946fd73573ff617d48f39d2e484241f Mon Sep 17 00:00:00 2001 From: kciesielski Date: Thu, 17 Aug 2023 10:46:42 +0200 Subject: [PATCH 04/10] Add 304 to status codes which shouldn't have body --- .../tapir/server/interpreter/ServerInterpreter.scala | 2 +- .../sttp/tapir/server/tests/ServerBasicTests.scala | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/server/core/src/main/scala/sttp/tapir/server/interpreter/ServerInterpreter.scala b/server/core/src/main/scala/sttp/tapir/server/interpreter/ServerInterpreter.scala index a78179ed2d..93bef28c6d 100644 --- a/server/core/src/main/scala/sttp/tapir/server/interpreter/ServerInterpreter.scala +++ b/server/core/src/main/scala/sttp/tapir/server/interpreter/ServerInterpreter.scala @@ -221,7 +221,7 @@ class ServerInterpreter[R, F[_], B, S]( val headers = outputValues.headers (statusCode, outputValues.body) match { - case (StatusCode.NoContent, Some(_)) => monad.error(new IllegalStateException("Unexpected response body when status code == NoContent (204)")) + case (StatusCode.NoContent | StatusCode.NotModified, Some(_)) => monad.error(new IllegalStateException(s"Unexpected response body when status code == $statusCode")) case (_, Some(bodyFromHeaders)) => ServerResponse(statusCode, headers, Some(bodyFromHeaders(Headers(headers))), Some(output)).unit case (_, None) => ServerResponse(statusCode, headers, None: Option[B], Some(output)).unit } diff --git a/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala b/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala index 9ec6ed8d9b..72e18e7b5c 100644 --- a/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala +++ b/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala @@ -771,11 +771,15 @@ class ServerBasicTests[F[_], OPTIONS, ROUTE]( } }, testServer( - "fail when status is 204 NoContent, but there's a body", + "fail when status is 204 or 304, but there's a body", NonEmptyList.of( - route(endpoint.out(jsonBody[Unit]).out(statusCode(StatusCode.NoContent)).serverLogicSuccess(_ => pureResult(()))) + route(endpoint.in("no_content").out(jsonBody[Unit]).out(statusCode(StatusCode.NoContent)).serverLogicSuccess(_ => pureResult(()))), + route(endpoint.in("not_modified").out(jsonBody[Unit]).out(statusCode(StatusCode.NotModified)).serverLogicSuccess(_ => pureResult(()))) ) - ) { (backend, baseUri) => basicRequest.get(baseUri).send(backend).map(_.code shouldBe StatusCode.InternalServerError) } + ) { (backend, baseUri) => + basicRequest.get(uri"$baseUri/no_content").send(backend).map(_.code shouldBe StatusCode.InternalServerError) >> + basicRequest.get(uri"$baseUri/not_modified").send(backend).map(_.code shouldBe StatusCode.InternalServerError) + } ) def throwFruits(name: String): F[String] = From 0b518f61e81cd05234ca23fc7bd507a0a94529e3 Mon Sep 17 00:00:00 2001 From: kciesielski Date: Mon, 21 Aug 2023 11:43:09 +0200 Subject: [PATCH 05/10] Add verification to EndpointVerifier, test error outputs --- .../tapir/server/tests/ServerBasicTests.scala | 31 +++++++++++++++++-- .../testing/EndpointVerificationError.scala | 15 +++++++++ .../sttp/tapir/testing/EndpointVerifier.scala | 30 ++++++++++++++++-- .../tapir/testing/EndpointVerifierTest.scala | 30 ++++++++++++++++++ 4 files changed, 101 insertions(+), 5 deletions(-) diff --git a/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala b/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala index 72e18e7b5c..3eb958f726 100644 --- a/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala +++ b/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala @@ -774,11 +774,32 @@ class ServerBasicTests[F[_], OPTIONS, ROUTE]( "fail when status is 204 or 304, but there's a body", NonEmptyList.of( route(endpoint.in("no_content").out(jsonBody[Unit]).out(statusCode(StatusCode.NoContent)).serverLogicSuccess(_ => pureResult(()))), - route(endpoint.in("not_modified").out(jsonBody[Unit]).out(statusCode(StatusCode.NotModified)).serverLogicSuccess(_ => pureResult(()))) + route( + endpoint.in("not_modified").out(jsonBody[Unit]).out(statusCode(StatusCode.NotModified)).serverLogicSuccess(_ => pureResult(())) + ), + route( + endpoint + .in("one_of") + .in(query[String]("select_err")) + .errorOut( + sttp.tapir.oneOf[ErrorInfo]( + oneOfVariant(statusCode(StatusCode.NotFound).and(jsonBody[NotFound])), + oneOfVariant(statusCode(StatusCode.NoContent).and(jsonBody[NoContentData])), + ) + ) + .serverLogic(selectErr => + if (selectErr == "no_content") + pureResult(Left(NoContentData("error"))) + else + pureResult(Left(NotFound("error"))) + ) + ) ) - ) { (backend, baseUri) => + ) { (backend, baseUri) => basicRequest.get(uri"$baseUri/no_content").send(backend).map(_.code shouldBe StatusCode.InternalServerError) >> - basicRequest.get(uri"$baseUri/not_modified").send(backend).map(_.code shouldBe StatusCode.InternalServerError) + basicRequest.get(uri"$baseUri/not_modified").send(backend).map(_.code shouldBe StatusCode.InternalServerError) >> + basicRequest.get(uri"$baseUri/one_of?select_err=no_content").send(backend).map(_.code shouldBe StatusCode.InternalServerError) + basicRequest.get(uri"$baseUri/one_of?select_err=not_found").send(backend).map(_.code shouldBe StatusCode.NotFound) } ) @@ -797,3 +818,7 @@ object Animal extends Enum[Animal] with TapirCodecEnumeratum { override def values = findValues } + +sealed trait ErrorInfo +case class NotFound(what: String) extends ErrorInfo +case class NoContentData(msg: String) extends ErrorInfo diff --git a/testing/src/main/scala/sttp/tapir/testing/EndpointVerificationError.scala b/testing/src/main/scala/sttp/tapir/testing/EndpointVerificationError.scala index a054891de6..1065ba118e 100644 --- a/testing/src/main/scala/sttp/tapir/testing/EndpointVerificationError.scala +++ b/testing/src/main/scala/sttp/tapir/testing/EndpointVerificationError.scala @@ -2,6 +2,7 @@ package sttp.tapir.testing import sttp.model.Method import sttp.tapir.AnyEndpoint +import sttp.model.StatusCode sealed trait EndpointVerificationError @@ -52,3 +53,17 @@ case class IncorrectPathsError(e: AnyEndpoint, at: Int) extends EndpointVerifica case class DuplicatedMethodDefinitionError(e: AnyEndpoint, methods: List[Method]) extends EndpointVerificationError { override def toString: String = s"An endpoint ${e.show} have multiple method definitions: $methods" } + +/** + * Endpoint `e` defines outputs where status code indicates no body, but at the same time a body output is specified. For status codes 204 and 304 it's forbidden by specification. + * + * Example of incorrectly defined endpoint: + * + * {{{ + * endpoint.get.in("x").out(jsonBody[Unit]).out(statusCode(StatusCode.NoContent)) + * }}} + * + */ +case class UnexpectedBodyError(e: AnyEndpoint, statusCode: StatusCode) extends EndpointVerificationError { + override def toString: String = s"An endpoint ${e.show} may return status code ${statusCode} with body, which is not allowed by specificiation." +} diff --git a/testing/src/main/scala/sttp/tapir/testing/EndpointVerifier.scala b/testing/src/main/scala/sttp/tapir/testing/EndpointVerifier.scala index 7de8b6ef1a..ceb0c4b265 100644 --- a/testing/src/main/scala/sttp/tapir/testing/EndpointVerifier.scala +++ b/testing/src/main/scala/sttp/tapir/testing/EndpointVerifier.scala @@ -1,16 +1,23 @@ package sttp.tapir.testing import sttp.model.Method -import sttp.tapir.internal.{RichEndpointInput, UrlencodedData} +import sttp.model.StatusCode.{NoContent, NotModified} +import sttp.tapir.internal.{RichEndpointInput, RichEndpointOutput, UrlencodedData} import sttp.tapir.{AnyEndpoint, EndpointInput, testing} import scala.annotation.tailrec +import sttp.tapir.AnyEndpoint +import sttp.tapir.EndpointOutput +import sttp.tapir.EndpointOutput.StatusCode +import sttp.tapir.EndpointOutput.FixedStatusCode +import sttp.tapir.EndpointIO object EndpointVerifier { def apply(endpoints: List[AnyEndpoint]): Set[EndpointVerificationError] = { findShadowedEndpoints(endpoints, List()).groupBy(_.e).map(_._2.head).toSet ++ findIncorrectPaths(endpoints).toSet ++ - findDuplicatedMethodDefinitions(endpoints).toSet + findDuplicatedMethodDefinitions(endpoints).toSet ++ + findForbiddenStatusAndBody(endpoints).toSet } private def findIncorrectPaths(endpoints: List[AnyEndpoint]): List[IncorrectPathsError] = { @@ -35,6 +42,9 @@ object EndpointVerifier { in.filter(e => checkIfShadows(endpoint, e)).map(e => testing.ShadowedEndpointError(e, endpoint)) } + private def findForbiddenStatusAndBody(endpoints: List[AnyEndpoint]): List[UnexpectedBodyError] = + endpoints.flatMap(hasStatusWithoutBody) + private def checkIfShadows(e1: AnyEndpoint, e2: AnyEndpoint): Boolean = checkMethods(e1, e2) && checkPaths(e1, e2) @@ -65,6 +75,22 @@ object EndpointVerifier { ) } + private def hasStatusWithoutBody(endpoint: AnyEndpoint): Option[UnexpectedBodyError] = { + (endpoint.output.asBasicOutputsList ++ endpoint.errorOutput.asBasicOutputsList) + .collect { list => + list + .collectFirst { case _: EndpointIO.Body[_, _] => list } + .flatMap(_.collectFirst { + case EndpointOutput.FixedStatusCode(NoContent, _, _) => NoContent + case EndpointOutput.FixedStatusCode(NotModified, _, _) => NotModified + }) + .map(UnexpectedBodyError(endpoint, _)) + + } + .flatMap(_.toList) + .headOption + } + private def inputPathSegments(input: EndpointInput[_]): Vector[PathComponent] = { input .traverseInputs({ diff --git a/testing/src/test/scala/sttp/tapir/testing/EndpointVerifierTest.scala b/testing/src/test/scala/sttp/tapir/testing/EndpointVerifierTest.scala index 6a7968c895..9ea8e51391 100644 --- a/testing/src/test/scala/sttp/tapir/testing/EndpointVerifierTest.scala +++ b/testing/src/test/scala/sttp/tapir/testing/EndpointVerifierTest.scala @@ -4,6 +4,10 @@ import org.scalatest.flatspec.AnyFlatSpecLike import org.scalatest.matchers.should.Matchers import sttp.model.Method import sttp.tapir._ +import sttp.model.StatusCode +import sttp.tapir.generic.auto._ +import sttp.tapir.json.circe._ +import io.circe.generic.auto._ class EndpointVerifierTest extends AnyFlatSpecLike with Matchers { @@ -286,4 +290,30 @@ class EndpointVerifierTest extends AnyFlatSpecLike with Matchers { result shouldBe Set() } + + it should "detect endpoints with body where status code doesn't allow a body" in { + + val e1 = endpoint.in("endpoint1_Err").out(stringBody).out(statusCode(StatusCode.NoContent)) + val e2 = endpoint.in("endpoint2_Ok").out(stringBody).out(statusCode(StatusCode.BadRequest)) + val e3 = endpoint.in("endpoint3_Err").out(stringBody).out(statusCode(StatusCode.NotModified)) + val e4 = endpoint.in("endpoint4_ok").out(emptyOutputAs(NoContent)).out(statusCode(StatusCode.NoContent)) + val e5 = endpoint + .in("endpoint5_err") + .out(stringBody) + .errorOut( + sttp.tapir.oneOf[ErrorInfo]( + oneOfVariant(statusCode(StatusCode.NotFound).and(jsonBody[NotFound])), + oneOfVariant(statusCode(StatusCode.NoContent).and(jsonBody[NoContentData])) + ) + ) + + val result = EndpointVerifier(List(e1, e2, e3, e4, e5)) + + result shouldBe Set(UnexpectedBodyError(e1, StatusCode.NoContent), UnexpectedBodyError(e3, StatusCode.NotModified), UnexpectedBodyError(e5, StatusCode.NoContent)) + } } + +sealed trait ErrorInfo +case class NotFound(what: String) extends ErrorInfo +case object NoContent extends ErrorInfo +case class NoContentData(msg: String) extends ErrorInfo From 6732ab25971ef74d0ebf4208ce0668ef8f36d188 Mon Sep 17 00:00:00 2001 From: kciesielski Date: Mon, 21 Aug 2023 12:11:36 +0200 Subject: [PATCH 06/10] Refactor for clarity --- .../sttp/tapir/testing/EndpointVerifier.scala | 40 +++++++------------ .../tapir/testing/EndpointVerifierTest.scala | 5 +-- 2 files changed, 17 insertions(+), 28 deletions(-) diff --git a/testing/src/main/scala/sttp/tapir/testing/EndpointVerifier.scala b/testing/src/main/scala/sttp/tapir/testing/EndpointVerifier.scala index ceb0c4b265..668f1de54b 100644 --- a/testing/src/main/scala/sttp/tapir/testing/EndpointVerifier.scala +++ b/testing/src/main/scala/sttp/tapir/testing/EndpointVerifier.scala @@ -2,22 +2,18 @@ package sttp.tapir.testing import sttp.model.Method import sttp.model.StatusCode.{NoContent, NotModified} +import sttp.tapir.EndpointOutput.FixedStatusCode import sttp.tapir.internal.{RichEndpointInput, RichEndpointOutput, UrlencodedData} -import sttp.tapir.{AnyEndpoint, EndpointInput, testing} +import sttp.tapir.{AnyEndpoint, EndpointIO, EndpointInput, EndpointOutput, testing} import scala.annotation.tailrec -import sttp.tapir.AnyEndpoint -import sttp.tapir.EndpointOutput -import sttp.tapir.EndpointOutput.StatusCode -import sttp.tapir.EndpointOutput.FixedStatusCode -import sttp.tapir.EndpointIO object EndpointVerifier { def apply(endpoints: List[AnyEndpoint]): Set[EndpointVerificationError] = { findShadowedEndpoints(endpoints, List()).groupBy(_.e).map(_._2.head).toSet ++ findIncorrectPaths(endpoints).toSet ++ findDuplicatedMethodDefinitions(endpoints).toSet ++ - findForbiddenStatusAndBody(endpoints).toSet + findIncorrectStatusWithBody(endpoints).toSet } private def findIncorrectPaths(endpoints: List[AnyEndpoint]): List[IncorrectPathsError] = { @@ -42,8 +38,18 @@ object EndpointVerifier { in.filter(e => checkIfShadows(endpoint, e)).map(e => testing.ShadowedEndpointError(e, endpoint)) } - private def findForbiddenStatusAndBody(endpoints: List[AnyEndpoint]): List[UnexpectedBodyError] = - endpoints.flatMap(hasStatusWithoutBody) + private def findIncorrectStatusWithBody(endpoints: List[AnyEndpoint]): List[UnexpectedBodyError] = + endpoints.flatMap { e => + val outputs = (e.output.asBasicOutputsList ++ e.errorOutput.asBasicOutputsList) + outputs.flatMap { outputElems => + val hasBody = outputElems.collectFirst { case b: EndpointIO.Body[_, _] => b }.isDefined + val noBodyStatusCodes = outputElems.collect { + case EndpointOutput.FixedStatusCode(NoContent, _, _) => NoContent + case EndpointOutput.FixedStatusCode(NotModified, _, _) => NotModified + } + if (hasBody) noBodyStatusCodes.map(UnexpectedBodyError(e, _)) else Nil + } + } private def checkIfShadows(e1: AnyEndpoint, e2: AnyEndpoint): Boolean = checkMethods(e1, e2) && checkPaths(e1, e2) @@ -75,22 +81,6 @@ object EndpointVerifier { ) } - private def hasStatusWithoutBody(endpoint: AnyEndpoint): Option[UnexpectedBodyError] = { - (endpoint.output.asBasicOutputsList ++ endpoint.errorOutput.asBasicOutputsList) - .collect { list => - list - .collectFirst { case _: EndpointIO.Body[_, _] => list } - .flatMap(_.collectFirst { - case EndpointOutput.FixedStatusCode(NoContent, _, _) => NoContent - case EndpointOutput.FixedStatusCode(NotModified, _, _) => NotModified - }) - .map(UnexpectedBodyError(endpoint, _)) - - } - .flatMap(_.toList) - .headOption - } - private def inputPathSegments(input: EndpointInput[_]): Vector[PathComponent] = { input .traverseInputs({ diff --git a/testing/src/test/scala/sttp/tapir/testing/EndpointVerifierTest.scala b/testing/src/test/scala/sttp/tapir/testing/EndpointVerifierTest.scala index 9ea8e51391..74fcc3ecd2 100644 --- a/testing/src/test/scala/sttp/tapir/testing/EndpointVerifierTest.scala +++ b/testing/src/test/scala/sttp/tapir/testing/EndpointVerifierTest.scala @@ -1,13 +1,12 @@ package sttp.tapir.testing +import io.circe.generic.auto._ import org.scalatest.flatspec.AnyFlatSpecLike import org.scalatest.matchers.should.Matchers -import sttp.model.Method +import sttp.model.{Method, StatusCode} import sttp.tapir._ -import sttp.model.StatusCode import sttp.tapir.generic.auto._ import sttp.tapir.json.circe._ -import io.circe.generic.auto._ class EndpointVerifierTest extends AnyFlatSpecLike with Matchers { From 032e2d5f4dacbcd30fcc0a73f9862413a5346be8 Mon Sep 17 00:00:00 2001 From: kciesielski Date: Mon, 21 Aug 2023 12:27:20 +0200 Subject: [PATCH 07/10] Add type hints for Scala 2, organize imports --- .../scala/sttp/tapir/server/tests/ServerBasicTests.scala | 8 ++++---- .../main/scala/sttp/tapir/testing/EndpointVerifier.scala | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala b/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala index 3eb958f726..62e529d9ac 100644 --- a/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala +++ b/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala @@ -787,12 +787,12 @@ class ServerBasicTests[F[_], OPTIONS, ROUTE]( oneOfVariant(statusCode(StatusCode.NoContent).and(jsonBody[NoContentData])), ) ) - .serverLogic(selectErr => + .serverLogic { selectErr => if (selectErr == "no_content") - pureResult(Left(NoContentData("error"))) + pureResult[F, Either[ErrorInfo, Unit]](Left(NoContentData("error"))) else - pureResult(Left(NotFound("error"))) - ) + pureResult[F, Either[ErrorInfo, Unit]](Left(NotFound("error"))) + } ) ) ) { (backend, baseUri) => diff --git a/testing/src/main/scala/sttp/tapir/testing/EndpointVerifier.scala b/testing/src/main/scala/sttp/tapir/testing/EndpointVerifier.scala index 668f1de54b..745ae98e41 100644 --- a/testing/src/main/scala/sttp/tapir/testing/EndpointVerifier.scala +++ b/testing/src/main/scala/sttp/tapir/testing/EndpointVerifier.scala @@ -2,7 +2,6 @@ package sttp.tapir.testing import sttp.model.Method import sttp.model.StatusCode.{NoContent, NotModified} -import sttp.tapir.EndpointOutput.FixedStatusCode import sttp.tapir.internal.{RichEndpointInput, RichEndpointOutput, UrlencodedData} import sttp.tapir.{AnyEndpoint, EndpointIO, EndpointInput, EndpointOutput, testing} From 0ab844c4aaf50f30de3aee19e5e47ed71b51d6e0 Mon Sep 17 00:00:00 2001 From: kciesielski Date: Mon, 21 Aug 2023 12:40:03 +0200 Subject: [PATCH 08/10] Add missing dependency --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index c126c86c77..1cb70420c1 100644 --- a/build.sbt +++ b/build.sbt @@ -457,7 +457,7 @@ lazy val testing: ProjectMatrix = (projectMatrix in file("testing")) .jvmPlatform(scalaVersions = scala2And3Versions) .jsPlatform(scalaVersions = scala2And3Versions, settings = commonJsSettings) .nativePlatform(scalaVersions = List(scala3), settings = commonNativeSettings) - .dependsOn(core) + .dependsOn(core, circeJson % Test) lazy val tests: ProjectMatrix = (projectMatrix in file("tests")) .settings(commonSettings) From f3fe4a1694436d8fa654b77eb649e7b8b5db4a91 Mon Sep 17 00:00:00 2001 From: kciesielski Date: Mon, 21 Aug 2023 15:36:41 +0200 Subject: [PATCH 09/10] Adjust tests to avoid complains from Play --- .../tapir/server/tests/ServerBasicTests.scala | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala b/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala index 62e529d9ac..a5f501eb27 100644 --- a/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala +++ b/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala @@ -773,11 +773,9 @@ class ServerBasicTests[F[_], OPTIONS, ROUTE]( testServer( "fail when status is 204 or 304, but there's a body", NonEmptyList.of( - route(endpoint.in("no_content").out(jsonBody[Unit]).out(statusCode(StatusCode.NoContent)).serverLogicSuccess(_ => pureResult(()))), - route( - endpoint.in("not_modified").out(jsonBody[Unit]).out(statusCode(StatusCode.NotModified)).serverLogicSuccess(_ => pureResult(())) - ), - route( + route(List( + endpoint.in("no_content").out(jsonBody[Unit]).out(statusCode(StatusCode.NoContent)).serverLogicSuccess[F](_ => pureResult(())), + endpoint.in("not_modified").out(jsonBody[Unit]).out(statusCode(StatusCode.NotModified)).serverLogicSuccess[F](_ => pureResult(())), endpoint .in("one_of") .in(query[String]("select_err")) @@ -787,18 +785,17 @@ class ServerBasicTests[F[_], OPTIONS, ROUTE]( oneOfVariant(statusCode(StatusCode.NoContent).and(jsonBody[NoContentData])), ) ) - .serverLogic { selectErr => + .serverLogic[F] { selectErr => if (selectErr == "no_content") pureResult[F, Either[ErrorInfo, Unit]](Left(NoContentData("error"))) else pureResult[F, Either[ErrorInfo, Unit]](Left(NotFound("error"))) } - ) - ) + ))) ) { (backend, baseUri) => basicRequest.get(uri"$baseUri/no_content").send(backend).map(_.code shouldBe StatusCode.InternalServerError) >> basicRequest.get(uri"$baseUri/not_modified").send(backend).map(_.code shouldBe StatusCode.InternalServerError) >> - basicRequest.get(uri"$baseUri/one_of?select_err=no_content").send(backend).map(_.code shouldBe StatusCode.InternalServerError) + basicRequest.get(uri"$baseUri/one_of?select_err=no_content").send(backend).map(_.code shouldBe StatusCode.InternalServerError) >> basicRequest.get(uri"$baseUri/one_of?select_err=not_found").send(backend).map(_.code shouldBe StatusCode.NotFound) } ) From 54bb7a9b5120707a2af13d42c74a3f91ff943bfa Mon Sep 17 00:00:00 2001 From: kciesielski Date: Wed, 23 Aug 2023 12:17:18 +0200 Subject: [PATCH 10/10] Add one more test case for EndpointVerifier --- .../tapir/testing/EndpointVerifierTest.scala | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/testing/src/test/scala/sttp/tapir/testing/EndpointVerifierTest.scala b/testing/src/test/scala/sttp/tapir/testing/EndpointVerifierTest.scala index 74fcc3ecd2..da3fd801ed 100644 --- a/testing/src/test/scala/sttp/tapir/testing/EndpointVerifierTest.scala +++ b/testing/src/test/scala/sttp/tapir/testing/EndpointVerifierTest.scala @@ -305,10 +305,22 @@ class EndpointVerifierTest extends AnyFlatSpecLike with Matchers { oneOfVariant(statusCode(StatusCode.NoContent).and(jsonBody[NoContentData])) ) ) + val e6 = endpoint + .in("endpoint6_ok") + .errorOut( + sttp.tapir.oneOf[ErrorInfo]( + oneOfVariant(statusCode(StatusCode.NotFound).and(jsonBody[NotFound])), + oneOfVariant(statusCode(StatusCode.NoContent).and(emptyOutputAs(NoContent))) + ) + ) - val result = EndpointVerifier(List(e1, e2, e3, e4, e5)) + val result = EndpointVerifier(List(e1, e2, e3, e4, e5, e6)) - result shouldBe Set(UnexpectedBodyError(e1, StatusCode.NoContent), UnexpectedBodyError(e3, StatusCode.NotModified), UnexpectedBodyError(e5, StatusCode.NoContent)) + result shouldBe Set( + UnexpectedBodyError(e1, StatusCode.NoContent), + UnexpectedBodyError(e3, StatusCode.NotModified), + UnexpectedBodyError(e5, StatusCode.NoContent) + ) } }