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] The passed Env is not the good one #3066

Open
guizmaii opened this issue Aug 30, 2024 · 12 comments
Open

[Bug] The passed Env is not the good one #3066

guizmaii opened this issue Aug 30, 2024 · 12 comments
Labels
💎 Bounty bug Something isn't working

Comments

@guizmaii
Copy link
Member

guizmaii commented Aug 30, 2024

Reproducer:

import zio.http.*
import zio.http.endpoint.{AuthType, Endpoint}
import zio.http.netty.NettyConfig
import zio.http.netty.server.NettyDriver
import zio.test.TestAspect.shrinks
import zio.test.{Gen, Spec, TestEnvironment, ZIOSpecDefault, assertTrue, check}
import zio.{Scope, UIO, ULayer, ZIO, ZLayer, ZNothing}

object MyApiSpec extends ZIOSpecDefault {

  trait MyService  {
    def code: UIO[Int]
  }
  object MyService {
    def live(code: Int): ULayer[MyService] = ZLayer.succeed(new MyServiceLive(code))
  }
  final class MyServiceLive(_code: Int) extends MyService {
    def code: UIO[Int] = ZIO.succeed(_code)
  }

  val endpoint: Endpoint[Unit, String, ZNothing, Int, AuthType.None] =
    Endpoint(RoutePattern.POST / "api").in[String].out[Int]

  val api = endpoint.implement(_ => ZIO.serviceWithZIO[MyService](_.code))

  override def spec: Spec[TestEnvironment & Scope, Any] =
    test("test") {
      check(Gen.fromIterable(List(1, 2, 3, 4, 5))) { code =>
        (
          for {
            client <- ZIO.service[Client]
            port   <- ZIO.serviceWithZIO[Server](_.port)
            url     = URL.root.port(port) / "api"
            request = Request
                        .post(url = url, body = Body.json("this is some input"))
                        .addHeader(Header.Accept(MediaType.application.json))
            _      <- TestServer.addRoutes(api.toRoutes)
            result <- client.batched(request)
            output <- result.body.asString
          } yield assertTrue(output == s"$code")
        ).provideSome[TestServer & Client](
          ZLayer.succeed(new MyServiceLive(code))
        )
      }.provide(
        ZLayer.succeed(Server.Config.default.onAnyOpenPort),
        TestServer.layer,
        Client.default,
        NettyDriver.customized,
        ZLayer.succeed(NettyConfig.defaultWithFastShutdown),
      )
    } @@ shrinks(0)
}

The test will fail on the second iteration of the check with the following assertion error:

Assertion failed:
Expected :"2"
Actual   :"1"

The Env of the api handler is never updated. It always contains the instance of MyService returning 1

This is quite a nasty bug.

Cc @jdegoes @987Nabil

@guizmaii guizmaii added the bug Something isn't working label Aug 30, 2024
@kyri-petrou
Copy link
Collaborator

Do you happen to know if this an issue only of the TestServer or does it also affect the "real" server implementation?

@guizmaii
Copy link
Member Author

Do you happen to know if this an issue only of the TestServer or does it also affect the "real" server implementation?

I don't know. I can try to reproduce it in my app 🤔

@kyri-petrou
Copy link
Collaborator

I might be wrong but I'm thinking that in an app the usage pattern would be a bit different, probably it'd require a middleware to provide the env? Unless you're adding apps to the server dynamically that's it

@jdegoes
Copy link
Member

jdegoes commented Sep 18, 2024

/bounty $250

Copy link

algora-pbc bot commented Sep 18, 2024

💎 $250 bounty • ZIO

Steps to solve:

  1. Start working: Comment /attempt #3066 with your implementation plan
  2. Submit work: Create a pull request including /claim #3066 in the PR body to claim the bounty
  3. Receive payment: 100% of the bounty is received 2-5 days post-reward. Make sure you are eligible for payouts

Thank you for contributing to zio/zio-http!

Add a bountyShare on socials

Attempt Started (GMT+0) Solution
🟢 @exi Sep 20, 2024, 7:21:09 AM #3160

@exi
Copy link

exi commented Sep 20, 2024

/attempt #3066

I'd like to take a look at this

Algora profile Completed bounties Tech Active attempts Options
@exi 2 bounties from 2 projects
JavaScript, Nix,
Clojure & more
Cancel attempt

@exi
Copy link

exi commented Sep 22, 2024

This Bug is not technically a bug but an unfortunate behaviour of the addRoutes call.
During the TestServer.addRoutes call, the routes are bound to the current environment here:

provided: Routes[Any, Response] = routes.provideEnvironment(r)

Which in itself is not wrong, because the second call to addRoutes actually contains the correct MyServer service.
However it then goes and merges the old routes with the new routes, giving precedence to the old routes with the old environment:

val updatedApp = (oldApp ++ newApp).asInstanceOf[Routes[Any, Response]]

If we flip this here to say (newApp ++ oldApp) instead of (oldApp ++ newApp), the behaviour is as expected.

@jdegoes I'd be happy to submit a tiny PR to flip this and make it work as expected.

@guizmaii
Copy link
Member Author

@exi Go for it :)

Copy link

algora-pbc bot commented Sep 23, 2024

💡 @exi submitted a pull request that claims the bounty. You can visit your bounty board to reward.

exi added a commit to exi/zio-http that referenced this issue Sep 23, 2024
In case of duplicate routes, the old logic gives precedence to older routes, which breaks cases where the routes are updated with new environments and re-added.

This gives precedence to the newer ones.

/claim zio#3066
@exi
Copy link

exi commented Sep 24, 2024

I just found this comment:

/**
* Combines this HTTP application with the specified HTTP application. In case
* of route conflicts, the routes in this HTTP application take precedence
* over the routes in the specified HTTP application.
*/
def ++[Env1 <: Env, Err1 >: Err](that: Routes[Env1, Err1]): Routes[Env1, Err1] =
copy(routes = routes ++ that.routes)

It seems like it's the intended behaviour to have old routes take precendence. So this test case here is (sadly) working as intended as long as you are not replacing the TestServer for each test run. If you pull the whole environment including the TestServer into the inner environment, it works as expected.

@exi
Copy link

exi commented Oct 4, 2024

Gentle ping.
I'm not quite sure what else there is to do after I figured out why this behaviour is like it is and there has been no meaningful feedback for 2 weeks now.
I'd be happy to implement whatever change is best, but as it stands, this is working as intended.

@exi
Copy link

exi commented Nov 29, 2024

@guizmaii do you have any idea what's going on with this bounty? It has not been responded to for months.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
💎 Bounty bug Something isn't working
Projects
None yet
Development

No branches or pull requests

4 participants