Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] Cannot use SttpClientInterpreter with SttpBackendStub #4075

Closed
chessman opened this issue Oct 2, 2024 · 2 comments · Fixed by #4085
Closed

[BUG] Cannot use SttpClientInterpreter with SttpBackendStub #4075

chessman opened this issue Oct 2, 2024 · 2 comments · Fixed by #4085

Comments

@chessman
Copy link

chessman commented Oct 2, 2024

Tapir version: 1.11.5

Scala version: 3.5.1

Describe the bug

Documentation mentions that a stub server can be used with an endpoint interpreted as a client:

You can define the sttp requests by hand, to see how an arbitrary request will be handled by your server endpoints. Or, you can interpret an endpoint as a client, to test both how the client & server interpreters interact with your endpoints.

I tried to adapt the example but it throws an error:

❯ scala-cli test tapir.scala
MySpec:
- should work *** FAILED ***
  java.lang.RuntimeException: Endpoint GET /api {header Authorization} -> {body as text/plain (UTF-8)}/{body as text/plain (UTF-8)} returned error: , for security inputs: password, inputs: ().
  at sttp.tapir.client.sttp.SttpClientInterpreter.toSecureRequestThrowErrors$$anonfun$1$$anonfun$1$$anonfun$2(SttpClientInterpreter.scala:203)
  at sttp.client3.ResponseAs.map$$anonfun$1(ResponseAs.scala:30)
  at sttp.client3.MappedResponseAs.mapWithMetadata$$anonfun$1(ResponseAs.scala:92)
  at sttp.client3.testing.SttpBackendStub$.tryAdjustResponseBody$$anonfun$2$$anonfun$2$$anonfun$1(SttpBackendStub.scala:236)
  at scala.concurrent.Future$.$anonfun$apply$1(Future.scala:687)
  at scala.concurrent.impl.Promise$Transformation.run(Promise.scala:467)
  at java.base/java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1395)
  at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
  at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182)
  at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655)

How to reproduce?

//> using scala 3.5.1
//> using dep com.softwaremill.sttp.tapir::tapir-sttp-client:1.11.5
//> using dep com.softwaremill.sttp.tapir::tapir-sttp-stub-server:1.11.5
//> using dep org.scalatest::scalatest:3.2.19

import org.scalatest.flatspec.AsyncFlatSpec
import org.scalatest.matchers.should.Matchers
import sttp.client3.*
import sttp.client3.testing.SttpBackendStub
import sttp.tapir.server.stub.TapirStubInterpreter
import sttp.tapir.client.sttp.SttpClientInterpreter

import sttp.tapir.*
import sttp.tapir.server.ServerEndpoint
import scala.concurrent.Future

class MySpec extends AsyncFlatSpec with Matchers:

  val someEndpoint: Endpoint[String, Unit, String, String, Any] = endpoint.get
    .in("api")
    .securityIn(auth.bearer[String]())
    .out(stringBody)
    .errorOut(stringBody)
    
  val someServerEndpoint: ServerEndpoint[Any, Future] = someEndpoint
    .serverSecurityLogic(token =>
      Future.successful {
        if (token == "password") Right("user123") else Left("unauthorized")
      }
    )
    .serverLogic(user => _ => Future.successful(Right(s"hello $user"))) 

  it should "work" in {
    // given
    val backendStub: SttpBackend[Future, Any] = TapirStubInterpreter(SttpBackendStub.asynchronousFuture)
      .whenServerEndpoint(someServerEndpoint)
      .thenRunLogic()
      .backend()
      
    val client = SttpClientInterpreter().toSecureClientThrowErrors(
      someEndpoint,
      Some(uri"http://localhost/api"),
      backendStub
    )

    val response = client("password")(())
      
    // then
    response.map(_ shouldBe "hello user123")  
  }
@adamw
Copy link
Member

adamw commented Oct 7, 2024

The reason for this is that the server is returning a 404 for the request. This in turn is caused by the URI that is being constructed. You provide http://localhost/api as the base URI; to that URI, the client appends the path of the endpoint that should be called, so you end up with http://localhost/api/api. And the server doesn't know how to handle this request.

But definitely the error reporting here should be better, I'll see if there are some places for improvement :)

@adamw
Copy link
Member

adamw commented Oct 7, 2024

With #4085, the exception message will be a bit more helpful:

Exception in thread "main" java.lang.RuntimeException: Endpoint GET /api {header Authorization} -> {body as text/plain (UTF-8)}/{body as text/plain (UTF-8)} returned error: , for security inputs: password, inputs: (). Request: GET http://localhost/api/api.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants