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

Message verification support #11

Closed
solarmosaic-kflorence opened this issue May 28, 2021 · 6 comments
Closed

Message verification support #11

solarmosaic-kflorence opened this issue May 28, 2021 · 6 comments

Comments

@solarmosaic-kflorence
Copy link

solarmosaic-kflorence commented May 28, 2021

Just wanted to start a discussion here about the best way to implement message verification support in Scala. First, a quick overview of current state:

  • there is a PactVerifier trait which works without any modification for request/response pacts

  • the trait uses ProviderVerifier().runVerificationForConsumer which supports both message and interaction (request/response) pacts

  • currently this method always defaults to request/response verification because ProviderInfo doesn't have an explicit verificationType -- it is easy to add support for this to the ProviderInfoBuilder:

    final case class ProviderInfoBuilder(
        name: String,
        protocol: String,
        host: String,
        port: Int,
        path: String,
        verificationType: PactVerification,
        pactSource: PactSource
    ) {
      private[pact4s] def toProviderInfo: ProviderInfo = {
        val p = new ProviderInfo(name, protocol, host, port, path)
        p.setVerificationType(verificationType)
        pactSource match {
          case broker: PactBroker => applyBrokerSourceToProvider(p, broker)
          case FileSource(consumer, file) =>
            p.hasPactWith(
              consumer,
              { consumer =>
                consumer.setPactSource(new PactJVMFileSource(file))
                kotlin.Unit.INSTANCE
              }
            )
            p
        }
      }
      ...
    }
  • however, this will result in verification via annotation (@PactVerifyProvider("message description")):

    @PactVerifyProvider("Resource with name 'example'")
    def exampleResource(): MessageAndMetadata = {
      val headers = Map("header-name" -> "header-value")
      val payload = """{"hello":"world"}""".getBytes
      new MessageAndMetadata(payload, headers.asJava)
    }

Full example:

class ProviderPactMessageVerificationSpec extends PactVerifier with DecorateAsJava {
  def provider: ProviderInfoBuilder = ProviderInfoBuilder(
    "provider",
    "http",
    "localhost",
    3456,
    "/",
    PactVerification.ANNOTATED_METHOD,
    FileSource("consumer", new File(getClass.getClassLoader.getResource("consumer_provider.json").getPath))
  )

  @PactVerifyProvider("Resource with name 'example'")
  def exampleResource(): MessageAndMetadata = {
    val headers = Map("header-name" -> "header-value")
    val payload = """{"hello":"world"}""".getBytes
    new MessageAndMetadata(payload, headers.asJava)
  }

  verifyPacts()
}

There are a few things awkward about this flow:

  • tying messages defined in the pact to generators using annotations feels very java-y, definitely not a normal test flow in Scala
  • the ProviderInfoBuilder should probably make defining the provider HTTP information optional -- it could be awkward for message verification where no provider states are needed (otherwise the HTTP information would be used to make requests against the provider application for states)

So what kind of test DSL do we want for linking of pact messages to message generators? And how would we integrate that with pact-jvm? Under the hood, the ProviderVerifier eventually calls verifyMessage -- we could invoke this directly ourselves. I think in general we should try not to drift too far from the pact-jvm implementation.

Another option I have seen is requesting of messages via an HTTP proxy: https://github.com/pact-foundation/pact-message-demo (the gist: you add endpoints to the provider to support returning messages based on description and provider state) -- however, pact-jvm does not support this natively.

@solarmosaic-kflorence
Copy link
Author

solarmosaic-kflorence commented May 28, 2021

Also need to consider provider-state support: https://docs.pact.io/implementation_guides/jvm/provider#provider-state -- it looks like currently this works by:

  • checking for providerStates on the Message
  • replays these against a provider via HTTP request
  • then invoke the message generation method

@solarmosaic-kflorence
Copy link
Author

solarmosaic-kflorence commented May 28, 2021

It doesn't seem like there is any easy alternative to having test-only HTTP endpoints on the provider application for provider states.

@solarmosaic-kflorence
Copy link
Author

After playing around a bit, I think it would be possible to not use annotations, but it would probably require some refactoring of the pact-jvm library. We can pretty easily extend the ProviderVerifier and override methods in it, but it's not easy to even pass a scala method into the verifyMessagePact function (as a java.lang.reflect.Method) without some hackery.

kflorence added a commit to kflorence/pact4s that referenced this issue May 28, 2021
kflorence added a commit to kflorence/pact4s that referenced this issue May 28, 2021
@solarmosaic-kflorence
Copy link
Author

Well this is a bummer pact-foundation/pact-jvm#610 -- I was wondering what would happen if I re-used the same producer/consumer names for both request/response and message pacts... that's the answer

[info] pact4s.scalatest.RequestResponsePactForgerScalaTestSuite *** ABORTED ***
[info]   au.com.dius.pact.core.model.InvalidPactException: Cannot merge pacts as they are not compatible
[info]   at au.com.dius.pact.core.model.DefaultPactWriter.writePact(PactWriter.kt:98)
[info]   at au.com.dius.pact.consumer.BaseMockServer.verifyResultAndWritePact(MockHttpServer.kt:160)
[info]   at pact4s.scalatest.RequestResponsePactForger.run(RequestResponsePactForger.scala:53)
[info]   at pact4s.scalatest.RequestResponsePactForger.run$(RequestResponsePactForger.scala:30)
[info]   at pact4s.scalatest.RequestResponsePactForgerScalaTestSuite.run(RequestResponsePactForgerScalaTestSuite.scala:19)
[info]   at org.scalatest.tools.Framework.org$scalatest$tools$Framework$$runSuite(Framework.scala:318)
[info]   at org.scalatest.tools.Framework$ScalaTestTask.execute(Framework.scala:513)
[info]   at sbt.TestRunner.runTest$1(TestFramework.scala:140)
[info]   at sbt.TestRunner.run(TestFramework.scala:155)
[info]   at sbt.TestFramework$$anon$3$$anonfun$$lessinit$greater$1.$anonfun$apply$1(TestFramework.scala:318)
[info]   ...

jbwheatley added a commit that referenced this issue Jun 3, 2021
Issue #11: allow verification by annotated method
@solarmosaic-kflorence
Copy link
Author

@jbwheatley I've opened pact-foundation/pact-jvm#1379 in pact-jvm to provide us with a better interface for method verification. This would allow us to do something like:

class ProviderPactMessageVerificationSpec extends PactVerifier with DecorateAsJava {
  def provider: ProviderInfoBuilder = ProviderInfoBuilder(
    "provider",
    "http",
    "localhost",
    3456,
    "/",
    PactVerification.FUNCTION,
    FileSource("consumer", new File(getClass.getClassLoader.getResource("consumer_provider.json").getPath))
  )

  def messages: String => MessageAndMetadata = {
    case "Resource with name 'example'" =>
      val headers = Map("header-name" -> "header-value")
      val payload = """{"hello":"world"}""".getBytes
      new MessageAndMetadata(payload, headers.asJava)
  }  

  verifyPacts()
}

@kflorence
Copy link
Contributor

pact-foundation/pact-jvm#1411

kflorence added a commit to kflorence/pact4s that referenced this issue Aug 22, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants