diff --git a/server/sttp-mock-server/src/main/scala/sttp/tapir/server/mockserver/SttpMockServerClient.scala b/server/sttp-mock-server/src/main/scala/sttp/tapir/server/mockserver/SttpMockServerClient.scala index 5341e57e9b..48aee0bdcc 100644 --- a/server/sttp-mock-server/src/main/scala/sttp/tapir/server/mockserver/SttpMockServerClient.scala +++ b/server/sttp-mock-server/src/main/scala/sttp/tapir/server/mockserver/SttpMockServerClient.scala @@ -4,7 +4,7 @@ import io.circe.{JsonObject, Printer} import io.circe.syntax._ import sttp.client3._ import sttp.client3.testing._ -import sttp.model.Uri.UriContext +import sttp.model.Uri.{QuerySegment, UriContext} import sttp.model.{ContentTypeRange, HasHeaders, Header, HeaderNames, Headers, MediaType, StatusCode, Uri} import sttp.tapir.internal.ParamsAsAny import sttp.tapir.{CodecFormat, DecodeResult, Endpoint, RawBodyType, WebSocketBodyOutput} @@ -134,7 +134,8 @@ object SttpMockServerClient { val request = SttpClientInterpreter().toSecureRequest(endpoint, None).apply(securityInput).apply(input) ExpectationRequestDefinition( method = request.method, - path = request.uri, + path = request.uri.copy(querySegments = Seq()), + queryStringParameters = querySegmentsToMultiMapOpt(request.uri.querySegments), body = toExpectationBody(request), headers = headersToMultiMapOpt(request.headers) ) @@ -206,6 +207,16 @@ object SttpMockServerClient { if (headers.isEmpty) None else Some(headers.groupBy(_.name).map { case (name, values) => name -> values.map(_.value).toList }) + private def querySegmentsToMultiMapOpt(querySegments: Seq[QuerySegment]): Option[Map[String, List[String]]] = + if (querySegments.isEmpty) None + else + Some( + querySegments + .collect { case kv: QuerySegment.KeyValue => kv } + .groupBy(x => x.k) + .map { case (key, values) => key -> values.map(_.v).toList } + ) + private val printer = Printer.noSpaces private def handleSimpleResponse(response: Response[Either[String, String]]): Either[Throwable, Unit] = { diff --git a/server/sttp-mock-server/src/main/scala/sttp/tapir/server/mockserver/model.scala b/server/sttp-mock-server/src/main/scala/sttp/tapir/server/mockserver/model.scala index ca35f7eaa1..9bae583f32 100644 --- a/server/sttp-mock-server/src/main/scala/sttp/tapir/server/mockserver/model.scala +++ b/server/sttp-mock-server/src/main/scala/sttp/tapir/server/mockserver/model.scala @@ -27,6 +27,7 @@ case class Expectation( case class ExpectationRequestDefinition( method: Method, path: Uri, + queryStringParameters: Option[Map[String, List[String]]], body: Option[ExpectationBodyDefinition], headers: Option[Map[String, List[String]]] ) diff --git a/server/sttp-mock-server/src/test/scala/sttp/tapir/server/mockserver/SttpMockServerClientSpec.scala b/server/sttp-mock-server/src/test/scala/sttp/tapir/server/mockserver/SttpMockServerClientSpec.scala index 60040add79..166b847110 100644 --- a/server/sttp-mock-server/src/test/scala/sttp/tapir/server/mockserver/SttpMockServerClientSpec.scala +++ b/server/sttp-mock-server/src/test/scala/sttp/tapir/server/mockserver/SttpMockServerClientSpec.scala @@ -46,6 +46,13 @@ class SttpMockServerClientSpec extends AnyFlatSpec with Matchers with BeforeAndA .errorOut(circe.jsonBody[ApiError]) .out(circe.jsonBody[PersonView]) + private val queryParameterEndpoint = endpoint + .in("api" / "v1" / "person") + .in(query[String]("name").and(query[Int]("age")).mapTo[CreatePersonCommand]) + .put + .errorOut(circe.jsonBody[ApiError]) + .out(circe.jsonBody[PersonView]) + it should "create plain text expectation correctly" in { val sampleIn = "Hello, world!" val sampleOut = "Hello to you!" @@ -67,6 +74,27 @@ class SttpMockServerClientSpec extends AnyFlatSpec with Matchers with BeforeAndA actual shouldEqual Success(Value(Right(sampleOut))) } + it should "create query parameters in expectation correctly" in { + val sampleIn = CreatePersonCommand("John", 23) + val sampleOut = PersonView(uuid(), "John", 23) + + val actual = for { + _ <- mockServerClient + .whenInputMatches(queryParameterEndpoint)((), sampleIn) + .thenSuccess(sampleOut) + + resp <- SttpClientInterpreter() + .toRequest(queryParameterEndpoint, baseUri = Some(baseUri)) + .apply(sampleIn) + .send(backend) + + _ <- mockServerClient + .verifyRequest(queryParameterEndpoint, VerificationTimes.exactlyOnce)((), sampleIn) + } yield resp.body + + actual shouldEqual Success(Value(Right(sampleOut))) + } + it should "create plain text error expectation correctly" in { val sampleIn = "Hello, world!" val sampleOut = "BOOOM!"