[JSON-API] Migrating tests to use sandbox next (#11034)

* Changes to use sandbox next for our integration tests

CHANGELOG_BEGIN
CHANGELOG_END

* remove sandbox classic dependency for HttpServiceTestFixture and perf tests

* rely on sandbox next fixture test class

* add missing dependencies for http-json-oracle

* changes based on code review comments

* Add tag to skip test case for scala_2_12 and also add a jdbc backend for sandbox spun up for perf tests

* Reduce size of contracts for archiving test
This commit is contained in:
akshayshirahatti-da 2021-09-30 17:29:37 +01:00 committed by GitHub
parent ffc8d6894f
commit d3e6f16c61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 522 additions and 247 deletions

View File

@ -51,6 +51,7 @@ da_scala_test(
"//daml-lf/transaction", "//daml-lf/transaction",
"//language-support/scala/bindings-akka", "//language-support/scala/bindings-akka",
"//ledger-api/rs-grpc-bridge", "//ledger-api/rs-grpc-bridge",
"//ledger-api/testing-utils",
"//ledger-service/db-backend", "//ledger-service/db-backend",
"//ledger-service/http-json:http-json-ee", "//ledger-service/http-json:http-json-ee",
"//ledger-service/http-json:integration-tests-lib-ee", "//ledger-service/http-json:integration-tests-lib-ee",
@ -58,6 +59,9 @@ da_scala_test(
"//ledger-service/http-json-testing:ee", "//ledger-service/http-json-testing:ee",
"//ledger-service/jwt", "//ledger-service/jwt",
"//ledger-service/utils", "//ledger-service/utils",
"//ledger/sandbox:sandbox-scala-tests-lib",
"//ledger/sandbox-common",
"//ledger/sandbox-common:sandbox-common-scala-tests-lib",
"//libs-scala/db-utils", "//libs-scala/db-utils",
"//libs-scala/oracle-testing", "//libs-scala/oracle-testing",
"//libs-scala/ports", "//libs-scala/ports",

View File

@ -43,6 +43,7 @@ hj_scalacopts = lf_scalacopts + [
"//bazel_tools/runfiles:scala_runfiles", "//bazel_tools/runfiles:scala_runfiles",
"//language-support/scala/bindings-akka", "//language-support/scala/bindings-akka",
"//ledger-api/rs-grpc-bridge", "//ledger-api/rs-grpc-bridge",
"//ledger-api/testing-utils",
"@maven//:org_scalatest_scalatest_compatible", "@maven//:org_scalatest_scalatest_compatible",
"//ledger-service/http-json:http-json-{}".format(edition), "//ledger-service/http-json:http-json-{}".format(edition),
"//ledger-service/http-json-cli:{}".format(edition), "//ledger-service/http-json-cli:{}".format(edition),
@ -53,13 +54,17 @@ hj_scalacopts = lf_scalacopts + [
"//ledger/ledger-api-auth", "//ledger/ledger-api-auth",
"//ledger/ledger-api-common", "//ledger/ledger-api-common",
"//ledger/ledger-configuration", "//ledger/ledger-configuration",
"//ledger/ledger-resources",
"//ledger/metrics", "//ledger/metrics",
"//ledger/participant-integration-api", "//ledger/participant-integration-api",
"//ledger/sandbox-classic", "//ledger/sandbox",
"//ledger/sandbox:sandbox-scala-tests-lib",
"//ledger/sandbox-common", "//ledger/sandbox-common",
"//ledger/sandbox-common:sandbox-common-scala-tests-lib",
"//libs-scala/contextualized-logging", "//libs-scala/contextualized-logging",
"//libs-scala/db-utils", "//libs-scala/db-utils",
"//libs-scala/ports", "//libs-scala/ports",
"//libs-scala/resources",
"@maven//:io_dropwizard_metrics_metrics_core", "@maven//:io_dropwizard_metrics_metrics_core",
], ],
) )

View File

@ -37,13 +37,14 @@ import com.daml.ledger.client.configuration.{
LedgerIdRequirement, LedgerIdRequirement,
} }
import com.daml.ledger.client.withoutledgerid.{LedgerClient => DamlLedgerClient} import com.daml.ledger.client.withoutledgerid.{LedgerClient => DamlLedgerClient}
import com.daml.ledger.resources.ResourceContext
import com.daml.logging.LoggingContextOf import com.daml.logging.LoggingContextOf
import com.daml.metrics.Metrics import com.daml.metrics.Metrics
import com.daml.platform.apiserver.SeedService.Seeding import com.daml.platform.apiserver.SeedService.Seeding
import com.daml.platform.common.LedgerIdMode import com.daml.platform.common.LedgerIdMode
import com.daml.platform.sandbox import com.daml.platform.sandbox.SandboxBackend
import com.daml.platform.sandbox.SandboxServer
import com.daml.platform.sandbox.config.SandboxConfig import com.daml.platform.sandbox.config.SandboxConfig
import com.daml.platform.sandboxnext.Runner
import com.daml.platform.services.time.TimeProviderType import com.daml.platform.services.time.TimeProviderType
import com.daml.ports.Port import com.daml.ports.Port
import com.typesafe.scalalogging.LazyLogging import com.typesafe.scalalogging.LazyLogging
@ -150,19 +151,35 @@ object HttpServiceTestFixture extends LazyLogging with Assertions with Inside {
useTls: UseTls = UseTls.NoTls, useTls: UseTls = UseTls.NoTls,
authService: Option[AuthService] = None, authService: Option[AuthService] = None,
)(testFn: (Port, DamlLedgerClient, LedgerId) => Future[A])(implicit )(testFn: (Port, DamlLedgerClient, LedgerId) => Future[A])(implicit
mat: Materializer,
aesf: ExecutionSequencerFactory, aesf: ExecutionSequencerFactory,
ec: ExecutionContext, ec: ExecutionContext,
): Future[A] = { ): Future[A] = {
val ledgerId = LedgerId(testName) val ledgerId = LedgerId(testName)
val applicationId = ApplicationId(testName) val applicationId = ApplicationId(testName)
implicit val resourceContext: ResourceContext = ResourceContext(ec)
val ledgerF = for { val ledgerF = for {
ledger <- Future( urlResource <- Future(
new SandboxServer(ledgerConfig(Port.Dynamic, dars, ledgerId, authService, useTls), mat) SandboxBackend.H2Database.owner
.map(info => Some(info.jdbcUrl))
.acquire()
) )
port <- ledger.portF jdbcUrl <- urlResource.asFuture
ledger <- Future(
new Runner(
ledgerConfig(
Port.Dynamic,
dars,
ledgerId,
useTls = useTls,
authService = authService,
jdbcUrl = jdbcUrl,
)
)
.acquire()
)
port <- ledger.asFuture
} yield (ledger, port) } yield (ledger, port)
val clientF: Future[DamlLedgerClient] = for { val clientF: Future[DamlLedgerClient] = for {
@ -179,11 +196,12 @@ object HttpServiceTestFixture extends LazyLogging with Assertions with Inside {
a <- testFn(ledgerPort, client, ledgerId) a <- testFn(ledgerPort, client, ledgerId)
} yield a } yield a
fa.onComplete { _ => fa.transformWith { ta =>
ledgerF.foreach(_._1.close()) ledgerF
.flatMap(_._1.release())
.fallbackTo(Future.unit)
.transform(_ => ta)
} }
fa
} }
private def ledgerConfig( private def ledgerConfig(
@ -192,12 +210,15 @@ object HttpServiceTestFixture extends LazyLogging with Assertions with Inside {
ledgerId: LedgerId, ledgerId: LedgerId,
authService: Option[AuthService], authService: Option[AuthService],
useTls: UseTls, useTls: UseTls,
jdbcUrl: Option[String],
): SandboxConfig = ): SandboxConfig =
sandbox.DefaultConfig.copy( SandboxConfig.defaultConfig.copy(
port = ledgerPort, port = ledgerPort,
damlPackages = dars, damlPackages = dars,
jdbcUrl = jdbcUrl,
timeProviderType = Some(TimeProviderType.WallClock), timeProviderType = Some(TimeProviderType.WallClock),
tlsConfig = if (useTls) Some(serverTlsConfig) else None, tlsConfig = if (useTls) Some(serverTlsConfig) else None,
engineMode = SandboxConfig.EngineMode.Dev,
ledgerIdMode = LedgerIdMode.Static(ledgerId), ledgerIdMode = LedgerIdMode.Static(ledgerId),
authService = authService, authService = authService,
seeding = Some(Seeding.Weak), seeding = Some(Seeding.Weak),
@ -271,8 +292,8 @@ object HttpServiceTestFixture extends LazyLogging with Assertions with Inside {
} }
} }
private val serverTlsConfig = TlsConfiguration(enabled = true, serverCrt, serverPem, caCrt) final val serverTlsConfig = TlsConfiguration(enabled = true, serverCrt, serverPem, caCrt)
private val clientTlsConfig = TlsConfiguration(enabled = true, clientCrt, clientPem, caCrt) final val clientTlsConfig = TlsConfiguration(enabled = true, clientCrt, clientPem, caCrt)
private val noTlsConfig = TlsConfiguration(enabled = false, None, None, None) private val noTlsConfig = TlsConfiguration(enabled = false, None, None, None)
def jwtForParties(actAs: List[String], readAs: List[String], ledgerId: String) = { def jwtForParties(actAs: List[String], readAs: List[String], ledgerId: String) = {

View File

@ -0,0 +1,78 @@
// Copyright (c) 2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.http.util
import com.daml.grpc.adapter.ExecutionSequencerFactory
import com.daml.http.HttpServiceTestFixture.{UseTls, clientTlsConfig, serverTlsConfig}
import com.daml.ledger.api.domain.LedgerId
import com.daml.ledger.client.configuration.{
CommandClientConfiguration,
LedgerClientConfiguration,
LedgerIdRequirement,
}
import com.daml.ports.Port
import com.daml.ledger.client.withoutledgerid.{LedgerClient => DamlLedgerClient}
import com.daml.platform.apiserver.SeedService.Seeding
import com.daml.platform.common.LedgerIdMode
import com.daml.platform.sandbox.config.SandboxConfig
import com.daml.platform.sandboxnext.SandboxNextFixture
import com.daml.platform.services.time.TimeProviderType
import org.scalatest.Suite
import scala.concurrent.{ExecutionContext, Future}
trait SandboxTestLedger extends SandboxNextFixture {
self: Suite =>
protected def testId: String
def useTls: UseTls
def ledgerId = LedgerId(testId)
override protected def config: SandboxConfig = SandboxConfig.defaultConfig.copy(
port = Port.Dynamic,
damlPackages = packageFiles,
timeProviderType = Some(TimeProviderType.WallClock),
tlsConfig = if (useTls) Some(serverTlsConfig) else None,
ledgerIdMode = LedgerIdMode.Static(ledgerId),
authService = authService,
scenario = scenario,
engineMode = SandboxConfig.EngineMode.Dev,
seeding = Some(Seeding.Weak),
)
def clientCfg(token: Option[String], testName: String): LedgerClientConfiguration =
LedgerClientConfiguration(
applicationId = testName,
ledgerIdRequirement = LedgerIdRequirement.none,
commandClient = CommandClientConfiguration.default,
sslContext = if (useTls) clientTlsConfig.client() else None,
token = token,
)
def usingLedger[A](testName: String, token: Option[String] = None)(
testFn: (Port, DamlLedgerClient, LedgerId) => Future[A]
)(implicit
esf: ExecutionSequencerFactory,
ec: ExecutionContext,
): Future[A] = {
val clientF: Future[DamlLedgerClient] = for {
ledgerPort <- Future(serverPort)
} yield DamlLedgerClient.singleHost(
"localhost",
ledgerPort.value,
clientCfg(token, testName),
)(ec, esf)
val fa: Future[A] = for {
ledgerPort <- Future(serverPort)
client <- clientF
a <- testFn(ledgerPort, client, ledgerId)
} yield a
fa
}
}

View File

@ -12,7 +12,7 @@ load(
) )
load("//rules_daml:daml.bzl", "daml_compile") load("//rules_daml:daml.bzl", "daml_compile")
load("@os_info//:os_info.bzl", "is_windows") load("@os_info//:os_info.bzl", "is_windows")
load("@scala_version//:index.bzl", "scala_version_suffix") load("@scala_version//:index.bzl", "scala_major_version", "scala_version_suffix")
hj_scalacopts = lf_scalacopts + [ hj_scalacopts = lf_scalacopts + [
"-P:wartremover:traverser:org.wartremover.warts.NonUnitStatements", "-P:wartremover:traverser:org.wartremover.warts.NonUnitStatements",
@ -283,7 +283,12 @@ alias(
"//daml-lf/transaction-test-lib", "//daml-lf/transaction-test-lib",
"//language-support/scala/bindings-akka", "//language-support/scala/bindings-akka",
"//ledger-api/rs-grpc-bridge", "//ledger-api/rs-grpc-bridge",
"//ledger-api/testing-utils",
"//ledger/ledger-resources",
"//ledger/metrics", "//ledger/metrics",
"//ledger/sandbox:sandbox-scala-tests-lib",
"//ledger/sandbox-common",
"//ledger/sandbox-common:sandbox-common-scala-tests-lib",
"//ledger-service/http-json-cli:{}".format(edition), "//ledger-service/http-json-cli:{}".format(edition),
"//ledger-service/http-json-testing:{}".format(edition), "//ledger-service/http-json-testing:{}".format(edition),
"//ledger-service/db-backend", "//ledger-service/db-backend",
@ -326,6 +331,10 @@ alias(
"src/it/scala/**/*.scala", "src/it/scala/**/*.scala",
"src/it/edition/{}/**/*.scala".format(edition), "src/it/edition/{}/**/*.scala".format(edition),
]), ]),
args = [
"-l",
"skip_scala_2_12",
] if scala_major_version == "2.12" else [],
data = [ data = [
":Account.dar", ":Account.dar",
"//docs:quickstart-model.dar", "//docs:quickstart-model.dar",
@ -369,17 +378,23 @@ alias(
"//daml-lf/transaction-test-lib", "//daml-lf/transaction-test-lib",
"//language-support/scala/bindings-akka", "//language-support/scala/bindings-akka",
"//ledger-api/rs-grpc-bridge", "//ledger-api/rs-grpc-bridge",
"//ledger-api/testing-utils",
"//ledger-service/db-backend", "//ledger-service/db-backend",
"//ledger/ledger-api-auth",
"//ledger/ledger-resources",
"//ledger/metrics", "//ledger/metrics",
"//ledger/sandbox:sandbox-scala-tests-lib",
"//ledger/sandbox-common",
"//ledger/sandbox-common:sandbox-common-scala-tests-lib",
"//ledger-service/http-json-cli:{}".format(edition), "//ledger-service/http-json-cli:{}".format(edition),
"//ledger-service/http-json-testing:{}".format(edition), "//ledger-service/http-json-testing:{}".format(edition),
"//ledger-service/jwt", "//ledger-service/jwt",
"//ledger-service/utils", "//ledger-service/utils",
"//ledger/ledger-api-auth",
"//libs-scala/contextualized-logging", "//libs-scala/contextualized-logging",
"//libs-scala/db-utils", "//libs-scala/db-utils",
"//libs-scala/ports", "//libs-scala/ports",
"//libs-scala/postgresql-testing", "//libs-scala/postgresql-testing",
"//libs-scala/resources",
"//libs-scala/scala-utils", "//libs-scala/scala-utils",
"//runtime-components/non-repudiation", "//runtime-components/non-repudiation",
"//runtime-components/non-repudiation-postgresql", "//runtime-components/non-repudiation-postgresql",

View File

@ -8,11 +8,14 @@ import akka.actor.ActorSystem
import akka.stream.Materializer import akka.stream.Materializer
import com.daml.bazeltools.BazelRunfiles.rlocation import com.daml.bazeltools.BazelRunfiles.rlocation
import com.daml.grpc.adapter.{AkkaExecutionSequencerPool, ExecutionSequencerFactory} import com.daml.grpc.adapter.{AkkaExecutionSequencerPool, ExecutionSequencerFactory}
import com.daml.http.HttpServiceTestFixture.UseTls
import com.daml.http.util.TestUtil.requiredFile import com.daml.http.util.TestUtil.requiredFile
import com.daml.http.util.Logging.instanceUUIDLogCtx import com.daml.http.util.Logging.instanceUUIDLogCtx
import com.daml.http.util.SandboxTestLedger
import com.daml.jwt.domain.Jwt import com.daml.jwt.domain.Jwt
import com.daml.ledger.api.auth.{AuthServiceStatic, Claim, ClaimPublic, ClaimSet} import com.daml.ledger.api.auth.{AuthServiceStatic, Claim, ClaimPublic, ClaimSet}
import com.daml.ledger.api.domain.LedgerId import com.daml.ledger.api.domain.LedgerId
import com.daml.ledger.api.testing.utils.SuiteResourceManagementAroundAll
import com.daml.ledger.client.withoutledgerid.{LedgerClient => DamlLedgerClient} import com.daml.ledger.client.withoutledgerid.{LedgerClient => DamlLedgerClient}
import org.scalatest.BeforeAndAfterAll import org.scalatest.BeforeAndAfterAll
import org.scalatest.flatspec.AsyncFlatSpec import org.scalatest.flatspec.AsyncFlatSpec
@ -22,12 +25,18 @@ import org.slf4j.LoggerFactory
import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.{ExecutionContext, Future}
import scala.util.control.NonFatal import scala.util.control.NonFatal
final class AuthorizationTest extends AsyncFlatSpec with BeforeAndAfterAll with Matchers { final class AuthorizationTest
extends AsyncFlatSpec
with BeforeAndAfterAll
with Matchers
with SandboxTestLedger
with SuiteResourceManagementAroundAll {
private val dar = requiredFile(rlocation("docs/quickstart-model.dar")) private val dar = requiredFile(rlocation("docs/quickstart-model.dar"))
.fold(e => throw new IllegalStateException(e), identity) .fold(e => throw new IllegalStateException(e), identity)
private val testId: String = this.getClass.getSimpleName protected val testId: String = this.getClass.getSimpleName
override def useTls = UseTls.NoTls
implicit val asys: ActorSystem = ActorSystem(testId) implicit val asys: ActorSystem = ActorSystem(testId)
implicit val mat: Materializer = Materializer(asys) implicit val mat: Materializer = Materializer(asys)
@ -43,6 +52,9 @@ final class AuthorizationTest extends AsyncFlatSpec with BeforeAndAfterAll with
private val accessTokenFile = Files.createTempFile("Extractor", "AuthSpec") private val accessTokenFile = Files.createTempFile("Extractor", "AuthSpec")
override def authService = mockedAuthService
override def packageFiles = List(dar)
override protected def afterAll(): Unit = { override protected def afterAll(): Unit = {
super.afterAll() super.afterAll()
try { try {
@ -55,11 +67,11 @@ final class AuthorizationTest extends AsyncFlatSpec with BeforeAndAfterAll with
} }
} }
protected def withLedger[A](testFn: DamlLedgerClient => LedgerId => Future[A]): Future[A] = protected def withLedger[A](testFn: DamlLedgerClient => LedgerId => Future[A]): Future[A] = {
HttpServiceTestFixture usingLedger[A](testId, Some(publicToken)) { case (_, client, ledgerId) =>
.withLedger[A](List(dar), testId, Option(publicToken), authService = mockedAuthService) { testFn(client)(ledgerId)
case (_, client, ledgerId) => testFn(client)(ledgerId) }
} }
private def packageService(client: DamlLedgerClient): PackageService = private def packageService(client: DamlLedgerClient): PackageService =
new PackageService(HttpService.doLoad(client.packageClient)) new PackageService(HttpService.doLoad(client.packageClient))

View File

@ -20,11 +20,14 @@ class HttpServiceWithPostgresIntTest
override def wsConfig: Option[WebsocketConfig] = None override def wsConfig: Option[WebsocketConfig] = None
"query persists all active contracts" in withHttpService { (uri, encoder, _, _) => "query persists all active contracts" in withHttpService { (uri, encoder, _, _) =>
val (party, headers) = getUniquePartyAndAuthHeaders("Alice")
val searchDataSet = genSearchDataSet(party)
searchExpectOk( searchExpectOk(
searchDataSet, searchDataSet,
jsObject("""{"templateIds": ["Iou:Iou"], "query": {"currency": "EUR"}}"""), jsObject("""{"templateIds": ["Iou:Iou"], "query": {"currency": "EUR"}}"""),
uri, uri,
encoder, encoder,
headers,
).flatMap { searchResult: List[domain.ActiveContract[JsValue]] => ).flatMap { searchResult: List[domain.ActiveContract[JsValue]] =>
discard { searchResult should have size 2 } discard { searchResult should have size 2 }
discard { searchResult.map(getField("currency")) shouldBe List.fill(2)(JsString("EUR")) } discard { searchResult.map(getField("currency")) shouldBe List.fill(2)(JsString("EUR")) }

View File

@ -3,13 +3,14 @@
package com.daml.http package com.daml.http
import com.daml.http.HttpServiceTestFixture.UseTls import com.daml.http.HttpServiceTestFixture.{UseTls, jwtForParties}
import com.daml.http.dbbackend.JdbcConfig import com.daml.http.dbbackend.JdbcConfig
import com.typesafe.scalalogging.StrictLogging import com.typesafe.scalalogging.StrictLogging
import org.scalatest._ import org.scalatest._
import org.scalatest.freespec.AsyncFreeSpec import org.scalatest.freespec.AsyncFreeSpec
import org.scalatest.matchers.should.Matchers import org.scalatest.matchers.should.Matchers
import scalaz.\/- import scalaz.\/-
import scalaz.syntax.tag._
import scala.concurrent.duration._ import scala.concurrent.duration._
@ -49,9 +50,10 @@ class WebsocketServiceOffsetTickIntTest
"Given non-empty ACS, JSON API should emit ACS block and after it only absolute offset ticks" in withHttpService { "Given non-empty ACS, JSON API should emit ACS block and after it only absolute offset ticks" in withHttpService {
(uri, _, _, _) => (uri, _, _, _) =>
val (party, headers) = getUniquePartyAndAuthHeaders("Alice")
for { for {
_ <- initialIouCreate(uri) _ <- initialIouCreate(uri, party, headers)
jwt = jwtForParties(List(party.unwrap), List(), testId)
msgs <- singleClientQueryStream(jwt, uri, """{"templateIds": ["Iou:Iou"]}""") msgs <- singleClientQueryStream(jwt, uri, """{"templateIds": ["Iou:Iou"]}""")
.take(10) .take(10)
.runWith(collectResultsAsTextMessage) .runWith(collectResultsAsTextMessage)

View File

@ -22,17 +22,18 @@ import com.daml.http.json.SprayJson.{decode, decode1, objectField}
import com.daml.http.json._ import com.daml.http.json._
import com.daml.http.util.ClientUtil.{boxedRecord, uniqueId} import com.daml.http.util.ClientUtil.{boxedRecord, uniqueId}
import com.daml.http.util.FutureUtil.toFuture import com.daml.http.util.FutureUtil.toFuture
import com.daml.http.util.{FutureUtil, TestUtil} import com.daml.http.util.{FutureUtil, SandboxTestLedger}
import com.daml.jwt.JwtSigner import com.daml.jwt.JwtSigner
import com.daml.jwt.domain.{DecodedJwt, Jwt} import com.daml.jwt.domain.{DecodedJwt, Jwt}
import com.daml.ledger.api.refinements.{ApiTypes => lar} import com.daml.ledger.api.refinements.{ApiTypes => lar}
import com.daml.ledger.api.v1.{value => v} import com.daml.ledger.api.v1.{value => v}
import com.daml.ledger.client.withoutledgerid.{LedgerClient => DamlLedgerClient} import com.daml.ledger.client.withoutledgerid.{LedgerClient => DamlLedgerClient}
import com.daml.ledger.service.MetadataReader import com.daml.ledger.service.MetadataReader
import com.daml.ledger.test.ModelTestDar import com.daml.ledger.test.{ModelTestDar, SemanticTestDar}
import com.daml.platform.participant.util.LfEngineToApi.lfValueToApiValue import com.daml.platform.participant.util.LfEngineToApi.lfValueToApiValue
import com.daml.http.util.Logging.instanceUUIDLogCtx import com.daml.http.util.Logging.instanceUUIDLogCtx
import com.daml.ledger.api.domain.LedgerId import com.daml.ledger.api.domain.LedgerId
import com.daml.ledger.api.testing.utils.SuiteResourceManagementAroundAll
import com.typesafe.scalalogging.StrictLogging import com.typesafe.scalalogging.StrictLogging
import org.scalatest._ import org.scalatest._
import org.scalatest.freespec.AsyncFreeSpec import org.scalatest.freespec.AsyncFreeSpec
@ -51,13 +52,16 @@ import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Success, Try} import scala.util.{Success, Try}
import com.daml.ledger.api.{domain => LedgerApiDomain} import com.daml.ledger.api.{domain => LedgerApiDomain}
import com.daml.ports.Port import com.daml.ports.Port
import org.scalatest.Tag
object SkipScala212 extends Tag("skip_scala_2_12")
object AbstractHttpServiceIntegrationTestFuns { object AbstractHttpServiceIntegrationTestFuns {
private[http] val dar1 = requiredResource("docs/quickstart-model.dar") private[http] val dar1 = requiredResource(ModelTestDar.path)
private[http] val dar2 = requiredResource("ledger-service/http-json/Account.dar") private[http] val dar2 = requiredResource("ledger-service/http-json/Account.dar")
private[http] val dar3 = requiredResource(ModelTestDar.path) private[http] val dar3 = requiredResource(SemanticTestDar.path)
def sha256(source: Source[ByteString, Any])(implicit mat: Materializer): Try[String] = Try { def sha256(source: Source[ByteString, Any])(implicit mat: Materializer): Try[String] = Try {
import java.security.MessageDigest import java.security.MessageDigest
@ -77,7 +81,10 @@ object AbstractHttpServiceIntegrationTestFuns {
} }
@SuppressWarnings(Array("org.wartremover.warts.NonUnitStatements")) @SuppressWarnings(Array("org.wartremover.warts.NonUnitStatements"))
trait AbstractHttpServiceIntegrationTestFuns extends StrictLogging { trait AbstractHttpServiceIntegrationTestFuns
extends StrictLogging
with SandboxTestLedger
with SuiteResourceManagementAroundAll {
this: AsyncTestSuite with Matchers with Inside => this: AsyncTestSuite with Matchers with Inside =>
import AbstractHttpServiceIntegrationTestFuns._ import AbstractHttpServiceIntegrationTestFuns._
import json.JsonProtocol._ import json.JsonProtocol._
@ -116,36 +123,40 @@ trait AbstractHttpServiceIntegrationTestFuns extends StrictLogging {
import tag.@@ // used for subtyping to make `AHS ec` beat executionContext import tag.@@ // used for subtyping to make `AHS ec` beat executionContext
implicit val `AHS ec`: ExecutionContext @@ this.type = tag[this.type](`AHS asys`.dispatcher) implicit val `AHS ec`: ExecutionContext @@ this.type = tag[this.type](`AHS asys`.dispatcher)
override def packageFiles = List(dar1, dar2)
protected def getUniqueParty(name: String) = getUniquePartyAndAuthHeaders(name)._1
protected def getUniquePartyAndAuthHeaders(name: String): (domain.Party, List[HttpHeader]) = {
val partyName = s"${name}_${uniqueId()}"
(domain.Party(partyName), headersWithPartyAuth(List(partyName)))
}
protected def withHttpServiceAndClient[A]( protected def withHttpServiceAndClient[A](
testFn: (Uri, DomainJsonEncoder, DomainJsonDecoder, DamlLedgerClient, LedgerId) => Future[A] testFn: (Uri, DomainJsonEncoder, DomainJsonDecoder, DamlLedgerClient, LedgerId) => Future[A]
): Future[A] = ): Future[A] = usingLedger[A](testId) { case (ledgerPort, _, ledgerId) =>
HttpServiceTestFixture.withLedger[A](List(dar1, dar2), testId, None, useTls) { HttpServiceTestFixture.withHttpService[A](
case (ledgerPort, _, ledgerId) => testId,
HttpServiceTestFixture.withHttpService[A]( ledgerPort,
testId, jdbcConfig,
ledgerPort, staticContentConfig,
jdbcConfig, useTls = useTls,
staticContentConfig, wsConfig = wsConfig,
useTls = useTls, )(testFn(_, _, _, _, ledgerId))
wsConfig = wsConfig, }
)(testFn(_, _, _, _, ledgerId))
}
protected def withHttpServiceAndClient[A](maxInboundMessageSize: Int)( protected def withHttpServiceAndClient[A](maxInboundMessageSize: Int)(
testFn: (Uri, DomainJsonEncoder, DomainJsonDecoder, DamlLedgerClient, LedgerId) => Future[A] testFn: (Uri, DomainJsonEncoder, DomainJsonDecoder, DamlLedgerClient, LedgerId) => Future[A]
): Future[A] = ): Future[A] = usingLedger[A](testId) { case (ledgerPort, _, ledgerId) =>
HttpServiceTestFixture.withLedger[A](List(dar1, dar2), testId, None, useTls) { HttpServiceTestFixture.withHttpService[A](
case (ledgerPort, _, ledgerId) => testId,
HttpServiceTestFixture.withHttpService[A]( ledgerPort,
testId, jdbcConfig,
ledgerPort, staticContentConfig,
jdbcConfig, useTls = useTls,
staticContentConfig, wsConfig = wsConfig,
useTls = useTls, maxInboundMessageSize = maxInboundMessageSize,
wsConfig = wsConfig, )(testFn(_, _, _, _, ledgerId))
maxInboundMessageSize = maxInboundMessageSize, }
)(testFn(_, _, _, _, ledgerId))
}
protected def withHttpService[A]( protected def withHttpService[A](
f: (Uri, DomainJsonEncoder, DomainJsonDecoder, LedgerId) => Future[A] f: (Uri, DomainJsonEncoder, DomainJsonDecoder, LedgerId) => Future[A]
@ -165,12 +176,12 @@ trait AbstractHttpServiceIntegrationTestFuns extends StrictLogging {
)((uri, encoder, decoder, _) => f(uri, encoder, decoder)) )((uri, encoder, decoder, _) => f(uri, encoder, decoder))
protected def withLedger[A](testFn: (DamlLedgerClient, LedgerId) => Future[A]): Future[A] = protected def withLedger[A](testFn: (DamlLedgerClient, LedgerId) => Future[A]): Future[A] =
HttpServiceTestFixture.withLedger[A](List(dar1, dar2), testId) { case (_, client, ledgerId) => usingLedger[A](testId) { case (_, client, ledgerId) =>
testFn(client, ledgerId) testFn(client, ledgerId)
} }
protected def withLedger2[A](testFn: (Port, DamlLedgerClient, LedgerId) => Future[A]): Future[A] = protected def withLedger2[A](testFn: (Port, DamlLedgerClient, LedgerId) => Future[A]): Future[A] =
HttpServiceTestFixture.withLedger[A](List(dar1, dar2), testId)(testFn) usingLedger[A](testId)(testFn)
protected val headersWithAuth = authorizationHeader(jwt) protected val headersWithAuth = authorizationHeader(jwt)
@ -224,8 +235,9 @@ trait AbstractHttpServiceIntegrationTestFuns extends StrictLogging {
create: domain.CreateCommand[v.Record, OptionalPkg], create: domain.CreateCommand[v.Record, OptionalPkg],
encoder: DomainJsonEncoder, encoder: DomainJsonEncoder,
uri: Uri, uri: Uri,
headers: List[HttpHeader] = headersWithAuth,
): Future[Assertion] = ): Future[Assertion] =
postContractsLookup(contractLocator, uri).flatMap { case (status, output) => postContractsLookup(contractLocator, uri, headers).flatMap { case (status, output) =>
status shouldBe StatusCodes.OK status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK) assertStatus(output, StatusCodes.OK)
val result = getResult(output) val result = getResult(output)
@ -283,15 +295,16 @@ trait AbstractHttpServiceIntegrationTestFuns extends StrictLogging {
} }
protected def iouCreateCommand( protected def iouCreateCommand(
partyName: String,
amount: String = "999.9900000000", amount: String = "999.9900000000",
currency: String = "USD", currency: String = "USD",
): domain.CreateCommand[v.Record, OptionalPkg] = { ): domain.CreateCommand[v.Record, OptionalPkg] = {
val templateId: OptionalPkg = domain.TemplateId(None, "Iou", "Iou") val templateId: OptionalPkg = domain.TemplateId(None, "Iou", "Iou")
val alice = Ref.Party assertFromString "Alice" val party = Ref.Party assertFromString partyName
val arg = argToApi(iouVA)( val arg = argToApi(iouVA)(
ShRecord( ShRecord(
issuer = alice, issuer = party,
owner = alice, owner = party,
currency = currency, currency = currency,
amount = LfNumeric assertFromString amount, amount = LfNumeric assertFromString amount,
observers = Vector.empty, observers = Vector.empty,
@ -314,15 +327,16 @@ trait AbstractHttpServiceIntegrationTestFuns extends StrictLogging {
} }
protected def iouCreateAndExerciseTransferCommand( protected def iouCreateAndExerciseTransferCommand(
partyName: String,
amount: String = "999.9900000000", amount: String = "999.9900000000",
currency: String = "USD", currency: String = "USD",
): domain.CreateAndExerciseCommand[v.Record, v.Value, OptionalPkg] = { ): domain.CreateAndExerciseCommand[v.Record, v.Value, OptionalPkg] = {
val templateId: OptionalPkg = domain.TemplateId(None, "Iou", "Iou") val templateId: OptionalPkg = domain.TemplateId(None, "Iou", "Iou")
val alice = Ref.Party assertFromString "Alice" val party = Ref.Party assertFromString partyName
val payload = argToApi(iouVA)( val payload = argToApi(iouVA)(
ShRecord( ShRecord(
issuer = alice, issuer = party,
owner = alice, owner = party,
currency = currency, currency = currency,
amount = LfNumeric assertFromString amount, amount = LfNumeric assertFromString amount,
observers = Vector.empty, observers = Vector.empty,
@ -412,10 +426,11 @@ trait AbstractHttpServiceIntegrationTestFuns extends StrictLogging {
protected def postContractsLookup( protected def postContractsLookup(
cmd: domain.ContractLocator[JsValue], cmd: domain.ContractLocator[JsValue],
uri: Uri, uri: Uri,
headers: List[HttpHeader] = headersWithAuth,
): Future[(StatusCode, JsValue)] = ): Future[(StatusCode, JsValue)] =
for { for {
json <- toFuture(SprayJson.encode(cmd)): Future[JsValue] json <- toFuture(SprayJson.encode(cmd)): Future[JsValue]
result <- postJsonRequest(uri.withPath(Uri.Path("/v1/fetch")), json) result <- postJsonRequest(uri.withPath(Uri.Path("/v1/fetch")), json, headers)
} yield result } yield result
protected def activeContractList(output: JsValue): List[domain.ActiveContract[JsValue]] = { protected def activeContractList(output: JsValue): List[domain.ActiveContract[JsValue]] = {
@ -539,21 +554,40 @@ trait AbstractHttpServiceIntegrationTestFuns extends StrictLogging {
} }
} }
protected def initialIouCreate(serviceUri: Uri): Future[(StatusCode, JsValue)] = { protected def initialIouCreate(
val payload = TestUtil.readFile("it/iouCreateCommand.json") serviceUri: Uri,
party: domain.Party,
headers: List[HttpHeader],
): Future[(StatusCode, JsValue)] = {
val partyJson = party.unwrap
val payload =
s"""
|{
| "templateId": "Iou:Iou",
| "payload": {
| "observers": [],
| "issuer": "$partyJson",
| "amount": "999.99",
| "currency": "USD",
| "owner": "$partyJson"
| }
|}
|""".stripMargin
HttpServiceTestFixture.postJsonStringRequest( HttpServiceTestFixture.postJsonStringRequest(
serviceUri.withPath(Uri.Path("/v1/create")), serviceUri.withPath(Uri.Path("/v1/create")),
payload, payload,
headersWithAuth, headers,
) )
} }
protected def initialAccountCreate( protected def initialAccountCreate(
serviceUri: Uri, serviceUri: Uri,
encoder: DomainJsonEncoder, encoder: DomainJsonEncoder,
owner: domain.Party,
headers: List[HttpHeader],
): Future[(StatusCode, JsValue)] = { ): Future[(StatusCode, JsValue)] = {
val command = accountCreateCommand(domain.Party("Alice"), "abc123") val command = accountCreateCommand(owner, "abc123")
postCreateCommand(command, encoder, serviceUri) postCreateCommand(command, encoder, serviceUri, headers)
} }
protected def jsObject(s: String): JsObject = { protected def jsObject(s: String): JsObject = {
@ -617,25 +651,31 @@ abstract class AbstractHttpServiceIntegrationTest
override final def useTls = UseTls.NoTls override final def useTls = UseTls.NoTls
"query GET empty results" in withHttpService { (uri: Uri, _, _, _) => "query GET empty results" in withHttpService { (uri: Uri, _, _, _) =>
searchAllExpectOk(uri).flatMap { case vector => val (_, headers) = getUniquePartyAndAuthHeaders("Alice")
searchAllExpectOk(uri, headers).flatMap { case vector =>
vector should have size 0L vector should have size 0L
} }
} }
protected val searchDataSet: List[domain.CreateCommand[v.Record, OptionalPkg]] = protected def genSearchDataSet(
party: domain.Party
): List[domain.CreateCommand[v.Record, OptionalPkg]] = {
val partyName = party.unwrap
List( List(
iouCreateCommand(amount = "111.11", currency = "EUR"), iouCreateCommand(amount = "111.11", currency = "EUR", partyName = partyName),
iouCreateCommand(amount = "222.22", currency = "EUR"), iouCreateCommand(amount = "222.22", currency = "EUR", partyName = partyName),
iouCreateCommand(amount = "333.33", currency = "GBP"), iouCreateCommand(amount = "333.33", currency = "GBP", partyName = partyName),
iouCreateCommand(amount = "444.44", currency = "BTC"), iouCreateCommand(amount = "444.44", currency = "BTC", partyName = partyName),
) )
}
"query GET" in withHttpService { (uri: Uri, encoder, _, _) => "query GET" in withHttpService { (uri: Uri, encoder, _, _) =>
searchDataSet.traverse(c => postCreateCommand(c, encoder, uri)).flatMap { rs => val (alice, headers) = getUniquePartyAndAuthHeaders("Alice")
val searchDataSet = genSearchDataSet(alice)
searchDataSet.traverse(c => postCreateCommand(c, encoder, uri, headers)).flatMap { rs =>
rs.map(_._1) shouldBe List.fill(searchDataSet.size)(StatusCodes.OK) rs.map(_._1) shouldBe List.fill(searchDataSet.size)(StatusCodes.OK)
getRequest(uri = uri.withPath(Uri.Path("/v1/query"))) getRequest(uri = uri.withPath(Uri.Path("/v1/query")), headers)
.flatMap { case (status, output) => .flatMap { case (status, output) =>
status shouldBe StatusCodes.OK status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK) assertStatus(output, StatusCodes.OK)
@ -649,54 +689,59 @@ abstract class AbstractHttpServiceIntegrationTest
} }
"multi-party query GET" in withHttpService { (uri, encoder, _, _) => "multi-party query GET" in withHttpService { (uri, encoder, _, _) =>
val (alice, aliceHeaders) = getUniquePartyAndAuthHeaders("Alice")
val (bob, bobHeaders) = getUniquePartyAndAuthHeaders("Alice")
for { for {
_ <- postCreateCommand( _ <- postCreateCommand(
accountCreateCommand(owner = domain.Party("Alice"), number = "42"), accountCreateCommand(owner = alice, number = "42"),
encoder, encoder,
uri, uri,
headers = aliceHeaders,
).map(r => r._1 shouldBe StatusCodes.OK) ).map(r => r._1 shouldBe StatusCodes.OK)
_ <- postCreateCommand( _ <- postCreateCommand(
accountCreateCommand(owner = domain.Party("Bob"), number = "23"), accountCreateCommand(owner = bob, number = "23"),
encoder, encoder,
uri, uri,
headers = headersWithPartyAuth(List("Bob")), headers = bobHeaders,
).map(r => r._1 shouldBe StatusCodes.OK) ).map(r => r._1 shouldBe StatusCodes.OK)
_ <- searchAllExpectOk(uri, headersWithPartyAuth(List("Alice"))).map(cs => _ <- searchAllExpectOk(uri, aliceHeaders).map(cs => cs should have size 1)
cs should have size 1 _ <- searchAllExpectOk(uri, bobHeaders).map(cs => cs should have size 1)
) _ <- searchAllExpectOk(uri, headersWithPartyAuth(List(alice.unwrap, bob.unwrap))).map(cs =>
_ <- searchAllExpectOk(uri, headersWithPartyAuth(List("Bob"))).map(cs =>
cs should have size 1
)
_ <- searchAllExpectOk(uri, headersWithPartyAuth(List("Alice", "Bob"))).map(cs =>
cs should have size 2 cs should have size 2
) )
} yield succeed } yield succeed
} }
"query POST with empty query" in withHttpService { (uri, encoder, _, _) => "query POST with empty query" in withHttpService { (uri, encoder, _, _) =>
val (alice, headers) = getUniquePartyAndAuthHeaders("Alice")
val searchDataSet = genSearchDataSet(alice)
searchExpectOk( searchExpectOk(
searchDataSet, searchDataSet,
jsObject("""{"templateIds": ["Iou:Iou"]}"""), jsObject("""{"templateIds": ["Iou:Iou"]}"""),
uri, uri,
encoder, encoder,
headers,
).map { acl: List[domain.ActiveContract[JsValue]] => ).map { acl: List[domain.ActiveContract[JsValue]] =>
acl.size shouldBe searchDataSet.size acl.size shouldBe searchDataSet.size
} }
} }
"multi-party query POST with empty query" in withHttpService { (uri, encoder, _, _) => "multi-party query POST with empty query" in withHttpService { (uri, encoder, _, _) =>
val (alice, aliceHeaders) = getUniquePartyAndAuthHeaders("Alice")
val (bob, bobHeaders) = getUniquePartyAndAuthHeaders("Alice")
for { for {
aliceAccountResp <- postCreateCommand( aliceAccountResp <- postCreateCommand(
accountCreateCommand(owner = domain.Party("Alice"), number = "42"), accountCreateCommand(owner = alice, number = "42"),
encoder, encoder,
uri, uri,
aliceHeaders,
) )
_ = aliceAccountResp._1 shouldBe StatusCodes.OK _ = aliceAccountResp._1 shouldBe StatusCodes.OK
bobAccountResp <- postCreateCommand( bobAccountResp <- postCreateCommand(
accountCreateCommand(owner = domain.Party("Bob"), number = "23"), accountCreateCommand(owner = bob, number = "23"),
encoder, encoder,
uri, uri,
headers = headersWithPartyAuth(List("Bob")), bobHeaders,
) )
_ = bobAccountResp._1 shouldBe StatusCodes.OK _ = bobAccountResp._1 shouldBe StatusCodes.OK
_ <- searchExpectOk( _ <- searchExpectOk(
@ -704,7 +749,7 @@ abstract class AbstractHttpServiceIntegrationTest
jsObject("""{"templateIds": ["Account:Account"]}"""), jsObject("""{"templateIds": ["Account:Account"]}"""),
uri, uri,
encoder, encoder,
headers = headersWithPartyAuth(List("Alice")), aliceHeaders,
) )
.map(acl => acl.size shouldBe 1) .map(acl => acl.size shouldBe 1)
_ <- searchExpectOk( _ <- searchExpectOk(
@ -712,7 +757,7 @@ abstract class AbstractHttpServiceIntegrationTest
jsObject("""{"templateIds": ["Account:Account"]}"""), jsObject("""{"templateIds": ["Account:Account"]}"""),
uri, uri,
encoder, encoder,
headers = headersWithPartyAuth(List("Bob")), bobHeaders,
) )
.map(acl => acl.size shouldBe 1) .map(acl => acl.size shouldBe 1)
_ <- searchExpectOk( _ <- searchExpectOk(
@ -720,7 +765,7 @@ abstract class AbstractHttpServiceIntegrationTest
jsObject("""{"templateIds": ["Account:Account"]}"""), jsObject("""{"templateIds": ["Account:Account"]}"""),
uri, uri,
encoder, encoder,
headers = headersWithPartyAuth(List("Alice", "Bob")), headers = headersWithPartyAuth(List(alice.unwrap, bob.unwrap)),
) )
.map(acl => acl.size shouldBe 2) .map(acl => acl.size shouldBe 2)
} yield { } yield {
@ -729,11 +774,14 @@ abstract class AbstractHttpServiceIntegrationTest
} }
"query with query, one field" in withHttpService { (uri, encoder, _, _) => "query with query, one field" in withHttpService { (uri, encoder, _, _) =>
val (alice, headers) = getUniquePartyAndAuthHeaders("Alice")
val searchDataSet = genSearchDataSet(alice)
searchExpectOk( searchExpectOk(
searchDataSet, searchDataSet,
jsObject("""{"templateIds": ["Iou:Iou"], "query": {"currency": "EUR"}}"""), jsObject("""{"templateIds": ["Iou:Iou"], "query": {"currency": "EUR"}}"""),
uri, uri,
encoder, encoder,
headers,
).map { acl: List[domain.ActiveContract[JsValue]] => ).map { acl: List[domain.ActiveContract[JsValue]] =>
acl.size shouldBe 2 acl.size shouldBe 2
acl.map(a => objectField(a.payload, "currency")) shouldBe List.fill(2)(Some(JsString("EUR"))) acl.map(a => objectField(a.payload, "currency")) shouldBe List.fill(2)(Some(JsString("EUR")))
@ -746,23 +794,28 @@ abstract class AbstractHttpServiceIntegrationTest
"""{"templateIds": ["Iou:Iou", "UnknownModule:UnknownEntity"], "query": {"currency": "EUR"}}""" """{"templateIds": ["Iou:Iou", "UnknownModule:UnknownEntity"], "query": {"currency": "EUR"}}"""
) )
search(List(), query, uri, encoder).map { response => search(List(), query, uri, encoder, headersWithPartyAuth(List("UnknownParty"))).map {
inside(response) { case domain.OkResponse(acl, warnings, StatusCodes.OK) => response =>
acl.size shouldBe 0 inside(response) { case domain.OkResponse(acl, warnings, StatusCodes.OK) =>
warnings shouldBe Some( acl.size shouldBe 0
domain.UnknownTemplateIds(List(domain.TemplateId(None, "UnknownModule", "UnknownEntity"))) warnings shouldBe Some(
) domain.UnknownTemplateIds(
} List(domain.TemplateId(None, "UnknownModule", "UnknownEntity"))
)
)
}
} }
} }
"query returns unknown Template IDs as warnings and error" in withHttpService { "query returns unknown Template IDs as warnings and error" in withHttpService {
(uri, encoder, _, _) => (uri, encoder, _, _) =>
val (alice, headers) = getUniquePartyAndAuthHeaders("Alice")
search( search(
searchDataSet, genSearchDataSet(alice),
jsObject("""{"templateIds": ["AAA:BBB", "XXX:YYY"]}"""), jsObject("""{"templateIds": ["AAA:BBB", "XXX:YYY"]}"""),
uri, uri,
encoder, encoder,
headers,
).map { response => ).map { response =>
inside(response) { case domain.ErrorResponse(errors, warnings, StatusCodes.BadRequest) => inside(response) { case domain.ErrorResponse(errors, warnings, StatusCodes.BadRequest) =>
errors shouldBe List(ErrorMessages.cannotResolveAnyTemplateId) errors shouldBe List(ErrorMessages.cannotResolveAnyTemplateId)
@ -780,7 +833,9 @@ abstract class AbstractHttpServiceIntegrationTest
(uri, encoder, _, _) => (uri, encoder, _, _) =>
import scalaz.std.scalaFuture._ import scalaz.std.scalaFuture._
searchDataSet.traverse(c => postCreateCommand(c, encoder, uri)).flatMap { val (alice, headers) = getUniquePartyAndAuthHeaders("Alice")
val searchDataSet = genSearchDataSet(alice)
searchDataSet.traverse(c => postCreateCommand(c, encoder, uri, headers)).flatMap {
rs: List[(StatusCode, JsValue)] => rs: List[(StatusCode, JsValue)] =>
rs.map(_._1) shouldBe List.fill(searchDataSet.size)(StatusCodes.OK) rs.map(_._1) shouldBe List.fill(searchDataSet.size)(StatusCodes.OK)
@ -791,8 +846,8 @@ abstract class AbstractHttpServiceIntegrationTest
val queryAmountAsNumber = queryAmountAs("111.11") val queryAmountAsNumber = queryAmountAs("111.11")
List( List(
postJsonRequest(uri.withPath(Uri.Path("/v1/query")), queryAmountAsString), postJsonRequest(uri.withPath(Uri.Path("/v1/query")), queryAmountAsString, headers),
postJsonRequest(uri.withPath(Uri.Path("/v1/query")), queryAmountAsNumber), postJsonRequest(uri.withPath(Uri.Path("/v1/query")), queryAmountAsNumber, headers),
).sequence.flatMap { rs: List[(StatusCode, JsValue)] => ).sequence.flatMap { rs: List[(StatusCode, JsValue)] =>
rs.map(_._1) shouldBe List.fill(2)(StatusCodes.OK) rs.map(_._1) shouldBe List.fill(2)(StatusCodes.OK)
inside(rs.map(_._2)) { case List(jsVal1, jsVal2) => inside(rs.map(_._2)) { case List(jsVal1, jsVal2) =>
@ -826,13 +881,18 @@ abstract class AbstractHttpServiceIntegrationTest
).foreach { case (testLbl, testCurrency) => ).foreach { case (testLbl, testCurrency) =>
s"query record contains handles '$testLbl' strings properly" in withHttpService { s"query record contains handles '$testLbl' strings properly" in withHttpService {
(uri, encoder, _, _) => (uri, encoder, _, _) =>
val (alice, headers) = getUniquePartyAndAuthHeaders("Alice")
searchExpectOk( searchExpectOk(
searchDataSet :+ iouCreateCommand(currency = testCurrency), genSearchDataSet(alice) :+ iouCreateCommand(
currency = testCurrency,
partyName = alice.unwrap,
),
jsObject( jsObject(
s"""{"templateIds": ["Iou:Iou"], "query": {"currency": ${testCurrency.toJson}}}""" s"""{"templateIds": ["Iou:Iou"], "query": {"currency": ${testCurrency.toJson}}}"""
), ),
uri, uri,
encoder, encoder,
headers,
).map(inside(_) { case Seq(domain.ActiveContract(_, _, _, JsObject(fields), _, _, _)) => ).map(inside(_) { case Seq(domain.ActiveContract(_, _, _, JsObject(fields), _, _, _)) =>
fields.get("currency") should ===(Some(JsString(testCurrency))) fields.get("currency") should ===(Some(JsString(testCurrency)))
}) })
@ -840,6 +900,8 @@ abstract class AbstractHttpServiceIntegrationTest
} }
"query with query, two fields" in withHttpService { (uri, encoder, _, _) => "query with query, two fields" in withHttpService { (uri, encoder, _, _) =>
val (alice, headers) = getUniquePartyAndAuthHeaders("Alice")
val searchDataSet = genSearchDataSet(alice)
searchExpectOk( searchExpectOk(
searchDataSet, searchDataSet,
jsObject( jsObject(
@ -847,6 +909,7 @@ abstract class AbstractHttpServiceIntegrationTest
), ),
uri, uri,
encoder, encoder,
headers,
).map { acl: List[domain.ActiveContract[JsValue]] => ).map { acl: List[domain.ActiveContract[JsValue]] =>
acl.size shouldBe 1 acl.size shouldBe 1
acl.map(a => objectField(a.payload, "currency")) shouldBe List(Some(JsString("EUR"))) acl.map(a => objectField(a.payload, "currency")) shouldBe List(Some(JsString("EUR")))
@ -855,6 +918,8 @@ abstract class AbstractHttpServiceIntegrationTest
} }
"query with query, no results" in withHttpService { (uri, encoder, _, _) => "query with query, no results" in withHttpService { (uri, encoder, _, _) =>
val (alice, headers) = getUniquePartyAndAuthHeaders("Alice")
val searchDataSet = genSearchDataSet(alice)
searchExpectOk( searchExpectOk(
searchDataSet, searchDataSet,
jsObject( jsObject(
@ -862,6 +927,7 @@ abstract class AbstractHttpServiceIntegrationTest
), ),
uri, uri,
encoder, encoder,
headers,
).map { acl: List[domain.ActiveContract[JsValue]] => ).map { acl: List[domain.ActiveContract[JsValue]] =>
acl.size shouldBe 0 acl.size shouldBe 0
} }
@ -894,9 +960,10 @@ abstract class AbstractHttpServiceIntegrationTest
} }
"create IOU" in withHttpService { (uri, encoder, _, _) => "create IOU" in withHttpService { (uri, encoder, _, _) =>
val command: domain.CreateCommand[v.Record, OptionalPkg] = iouCreateCommand() val (alice, headers) = getUniquePartyAndAuthHeaders("Alice")
val command: domain.CreateCommand[v.Record, OptionalPkg] = iouCreateCommand(alice.unwrap)
postCreateCommand(command, encoder, uri).flatMap { case (status, output) => postCreateCommand(command, encoder, uri, headers).flatMap { case (status, output) =>
status shouldBe StatusCodes.OK status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK) assertStatus(output, StatusCodes.OK)
val activeContract = getResult(output) val activeContract = getResult(output)
@ -906,8 +973,8 @@ abstract class AbstractHttpServiceIntegrationTest
"create IOU should fail if authorization header is missing" in withHttpService { "create IOU should fail if authorization header is missing" in withHttpService {
(uri, encoder, _, _) => (uri, encoder, _, _) =>
val command: domain.CreateCommand[v.Record, OptionalPkg] = val alice = getUniqueParty("Alice")
iouCreateCommand() val command: domain.CreateCommand[v.Record, OptionalPkg] = iouCreateCommand(alice.unwrap)
val input: JsValue = encoder.encodeCreateCommand(command).valueOr(e => fail(e.shows)) val input: JsValue = encoder.encodeCreateCommand(command).valueOr(e => fail(e.shows))
postJsonRequest(uri.withPath(Uri.Path("/v1/create")), input, List()).flatMap { postJsonRequest(uri.withPath(Uri.Path("/v1/create")), input, List()).flatMap {
@ -921,13 +988,14 @@ abstract class AbstractHttpServiceIntegrationTest
} }
"create IOU should support extra readAs parties" in withHttpService { (uri, encoder, _, _) => "create IOU should support extra readAs parties" in withHttpService { (uri, encoder, _, _) =>
val command: domain.CreateCommand[v.Record, OptionalPkg] = iouCreateCommand() val alice = getUniqueParty("Alice")
val command: domain.CreateCommand[v.Record, OptionalPkg] = iouCreateCommand(alice.unwrap)
val input: JsValue = encoder.encodeCreateCommand(command).valueOr(e => fail(e.shows)) val input: JsValue = encoder.encodeCreateCommand(command).valueOr(e => fail(e.shows))
postJsonRequest( postJsonRequest(
uri.withPath(Uri.Path("/v1/create")), uri.withPath(Uri.Path("/v1/create")),
input, input,
headers = headersWithPartyAuth(actAs = List("Alice"), readAs = List("Bob")), headers = headersWithPartyAuth(actAs = List(alice.unwrap), readAs = List("Bob")),
).flatMap { case (status, output) => ).flatMap { case (status, output) =>
status shouldBe StatusCodes.OK status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK) assertStatus(output, StatusCodes.OK)
@ -938,11 +1006,12 @@ abstract class AbstractHttpServiceIntegrationTest
"create IOU with unsupported templateId should return proper error" in withHttpService { "create IOU with unsupported templateId should return proper error" in withHttpService {
(uri, encoder, _, _) => (uri, encoder, _, _) =>
val (alice, headers) = getUniquePartyAndAuthHeaders("Alice")
val command: domain.CreateCommand[v.Record, OptionalPkg] = val command: domain.CreateCommand[v.Record, OptionalPkg] =
iouCreateCommand().copy(templateId = domain.TemplateId(None, "Iou", "Dummy")) iouCreateCommand(alice.unwrap).copy(templateId = domain.TemplateId(None, "Iou", "Dummy"))
val input: JsValue = encoder.encodeCreateCommand(command).valueOr(e => fail(e.shows)) val input: JsValue = encoder.encodeCreateCommand(command).valueOr(e => fail(e.shows))
postJsonRequest(uri.withPath(Uri.Path("/v1/create")), input).flatMap { postJsonRequest(uri.withPath(Uri.Path("/v1/create")), input, headers).flatMap {
case (status, output) => case (status, output) =>
status shouldBe StatusCodes.BadRequest status shouldBe StatusCodes.BadRequest
assertStatus(output, StatusCodes.BadRequest) assertStatus(output, StatusCodes.BadRequest)
@ -955,8 +1024,9 @@ abstract class AbstractHttpServiceIntegrationTest
} }
"exercise IOU_Transfer" in withHttpService { (uri, encoder, decoder, ledgerId) => "exercise IOU_Transfer" in withHttpService { (uri, encoder, decoder, ledgerId) =>
val create: domain.CreateCommand[v.Record, OptionalPkg] = iouCreateCommand() val (alice, headers) = getUniquePartyAndAuthHeaders("Alice")
postCreateCommand(create, encoder, uri) val create: domain.CreateCommand[v.Record, OptionalPkg] = iouCreateCommand(alice.unwrap)
postCreateCommand(create, encoder, uri, headers)
.flatMap { case (createStatus, createOutput) => .flatMap { case (createStatus, createOutput) =>
createStatus shouldBe StatusCodes.OK createStatus shouldBe StatusCodes.OK
assertStatus(createOutput, StatusCodes.OK) assertStatus(createOutput, StatusCodes.OK)
@ -966,7 +1036,7 @@ abstract class AbstractHttpServiceIntegrationTest
iouExerciseTransferCommand(contractId) iouExerciseTransferCommand(contractId)
val exerciseJson: JsValue = encodeExercise(encoder)(exercise) val exerciseJson: JsValue = encodeExercise(encoder)(exercise)
postJsonRequest(uri.withPath(Uri.Path("/v1/exercise")), exerciseJson) postJsonRequest(uri.withPath(Uri.Path("/v1/exercise")), exerciseJson, headers)
.flatMap { case (exerciseStatus, exerciseOutput) => .flatMap { case (exerciseStatus, exerciseOutput) =>
exerciseStatus shouldBe StatusCodes.OK exerciseStatus shouldBe StatusCodes.OK
assertStatus(exerciseOutput, StatusCodes.OK) assertStatus(exerciseOutput, StatusCodes.OK)
@ -977,18 +1047,20 @@ abstract class AbstractHttpServiceIntegrationTest
decoder, decoder,
uri, uri,
ledgerId, ledgerId,
headers,
) )
} }
}: Future[Assertion] }: Future[Assertion]
} }
"create-and-exercise IOU_Transfer" in withHttpService { (uri, encoder, _, _) => "create-and-exercise IOU_Transfer" in withHttpService { (uri, encoder, _, _) =>
val (alice, headers) = getUniquePartyAndAuthHeaders("Alice")
val cmd: domain.CreateAndExerciseCommand[v.Record, v.Value, OptionalPkg] = val cmd: domain.CreateAndExerciseCommand[v.Record, v.Value, OptionalPkg] =
iouCreateAndExerciseTransferCommand() iouCreateAndExerciseTransferCommand(alice.unwrap)
val json: JsValue = encoder.encodeCreateAndExerciseCommand(cmd).valueOr(e => fail(e.shows)) val json: JsValue = encoder.encodeCreateAndExerciseCommand(cmd).valueOr(e => fail(e.shows))
postJsonRequest(uri.withPath(Uri.Path("/v1/create-and-exercise")), json) postJsonRequest(uri.withPath(Uri.Path("/v1/create-and-exercise")), json, headers)
.flatMap { case (status, output) => .flatMap { case (status, output) =>
status shouldBe StatusCodes.OK status shouldBe StatusCodes.OK
inside( inside(
@ -1019,6 +1091,7 @@ abstract class AbstractHttpServiceIntegrationTest
decoder: DomainJsonDecoder, decoder: DomainJsonDecoder,
uri: Uri, uri: Uri,
ledgerId: LedgerId, ledgerId: LedgerId,
headers: List[HttpHeader],
): Future[Assertion] = { ): Future[Assertion] = {
inside(SprayJson.decode[domain.ExerciseResponse[JsValue]](exerciseResponse)) { inside(SprayJson.decode[domain.ExerciseResponse[JsValue]](exerciseResponse)) {
case \/-(domain.ExerciseResponse(JsString(exerciseResult), List(contract1, contract2))) => case \/-(domain.ExerciseResponse(JsString(exerciseResult), List(contract1, contract2))) =>
@ -1038,7 +1111,7 @@ abstract class AbstractHttpServiceIntegrationTest
Some(domain.TemplateId(None, "Iou", "IouTransfer")), Some(domain.TemplateId(None, "Iou", "IouTransfer")),
domain.ContractId(exerciseResult), domain.ContractId(exerciseResult),
) )
postContractsLookup(newContractLocator, uri).flatMap { case (status, output) => postContractsLookup(newContractLocator, uri, headers).flatMap { case (status, output) =>
status shouldBe StatusCodes.OK status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK) assertStatus(output, StatusCodes.OK)
getContractId(getResult(output)) shouldBe newContractLocator.contractId getContractId(getResult(output)) shouldBe newContractLocator.contractId
@ -1049,21 +1122,23 @@ abstract class AbstractHttpServiceIntegrationTest
"exercise IOU_Transfer with unknown contractId should return proper error" in withHttpService { "exercise IOU_Transfer with unknown contractId should return proper error" in withHttpService {
(uri, encoder, _, _) => (uri, encoder, _, _) =>
val contractId = lar.ContractId("#NonExistentContractId") val contractIdString = "0" * 66
val contractId = lar.ContractId(contractIdString)
val exerciseJson: JsValue = encodeExercise(encoder)(iouExerciseTransferCommand(contractId)) val exerciseJson: JsValue = encodeExercise(encoder)(iouExerciseTransferCommand(contractId))
postJsonRequest(uri.withPath(Uri.Path("/v1/exercise")), exerciseJson) postJsonRequest(uri.withPath(Uri.Path("/v1/exercise")), exerciseJson)
.flatMap { case (status, output) => .flatMap { case (status, output) =>
status shouldBe StatusCodes.InternalServerError status shouldBe StatusCodes.InternalServerError
assertStatus(output, StatusCodes.InternalServerError) assertStatus(output, StatusCodes.InternalServerError)
expectedOneErrorMessage(output) should include( expectedOneErrorMessage(output) should include(
"Contract could not be found with id ContractId(#NonExistentContractId)" s"Contract could not be found with id ContractId($contractIdString)"
) )
}: Future[Assertion] }: Future[Assertion]
} }
"exercise Archive" in withHttpService { (uri, encoder, _, _) => "exercise Archive" in withHttpService { (uri, encoder, _, _) =>
val create: domain.CreateCommand[v.Record, OptionalPkg] = iouCreateCommand() val (alice, headers) = getUniquePartyAndAuthHeaders("Alice")
postCreateCommand(create, encoder, uri) val create: domain.CreateCommand[v.Record, OptionalPkg] = iouCreateCommand(alice.unwrap)
postCreateCommand(create, encoder, uri, headers)
.flatMap { case (createStatus, createOutput) => .flatMap { case (createStatus, createOutput) =>
createStatus shouldBe StatusCodes.OK createStatus shouldBe StatusCodes.OK
assertStatus(createOutput, StatusCodes.OK) assertStatus(createOutput, StatusCodes.OK)
@ -1074,7 +1149,7 @@ abstract class AbstractHttpServiceIntegrationTest
val exercise = archiveCommand(reference) val exercise = archiveCommand(reference)
val exerciseJson: JsValue = encodeExercise(encoder)(exercise) val exerciseJson: JsValue = encodeExercise(encoder)(exercise)
postJsonRequest(uri.withPath(Uri.Path("/v1/exercise")), exerciseJson) postJsonRequest(uri.withPath(Uri.Path("/v1/exercise")), exerciseJson, headers)
.flatMap { case (exerciseStatus, exerciseOutput) => .flatMap { case (exerciseStatus, exerciseOutput) =>
exerciseStatus shouldBe StatusCodes.OK exerciseStatus shouldBe StatusCodes.OK
assertStatus(exerciseOutput, StatusCodes.OK) assertStatus(exerciseOutput, StatusCodes.OK)
@ -1085,17 +1160,7 @@ abstract class AbstractHttpServiceIntegrationTest
} }
"should support multi-party command submissions" in withHttpService { (uri, encoder, _, _) => "should support multi-party command submissions" in withHttpService { (uri, encoder, _, _) =>
val newDar = AbstractHttpServiceIntegrationTestFuns.dar3
for { for {
_ <- Http()
.singleRequest(
HttpRequest(
method = HttpMethods.POST,
uri = uri.withPath(Uri.Path("/v1/packages")),
headers = authorizationHeader(jwtAdminNoParty),
entity = HttpEntity.fromFile(ContentTypes.`application/octet-stream`, newDar),
)
)
// multi-party actAs on create // multi-party actAs on create
cid <- postCreateCommand( cid <- postCreateCommand(
multiPartyCreateCommand(List("Alice", "Bob"), ""), multiPartyCreateCommand(List("Alice", "Bob"), ""),
@ -1174,7 +1239,7 @@ abstract class AbstractHttpServiceIntegrationTest
import json.JsonProtocol._ import json.JsonProtocol._
import util.ErrorOps._ import util.ErrorOps._
val command0: domain.CreateCommand[v.Record, OptionalPkg] = iouCreateCommand() val command0: domain.CreateCommand[v.Record, OptionalPkg] = iouCreateCommand("Alice")
type F[A] = EitherT[Future, JsonError, A] type F[A] = EitherT[Future, JsonError, A]
val x: F[Assertion] = for { val x: F[Assertion] = for {
@ -1214,7 +1279,7 @@ abstract class AbstractHttpServiceIntegrationTest
"parties endpoint should return all known parties" in withHttpServiceAndClient { "parties endpoint should return all known parties" in withHttpServiceAndClient {
(uri, _, _, client, _) => (uri, _, _, client, _) =>
import scalaz.std.vector._ import scalaz.std.vector._
val partyIds = Vector("Alice", "Bob", "Charlie", "Dave") val partyIds = Vector("P1", "P2", "P3", "P4")
val partyManagement = client.partyManagementClient val partyManagement = client.partyManagementClient
partyIds partyIds
@ -1230,8 +1295,8 @@ abstract class AbstractHttpServiceIntegrationTest
response.status shouldBe StatusCodes.OK response.status shouldBe StatusCodes.OK
response.warnings shouldBe empty response.warnings shouldBe empty
val actualIds: Set[domain.Party] = response.result.view.map(_.identifier).toSet val actualIds: Set[domain.Party] = response.result.view.map(_.identifier).toSet
actualIds shouldBe domain.Party.subst(partyIds.toSet) actualIds should contain allElementsOf domain.Party.subst(partyIds.toSet)
response.result.toSet shouldBe response.result.toSet should contain allElementsOf
allocatedParties.toSet.map(domain.PartyDetails.fromLedgerApi) allocatedParties.toSet.map(domain.PartyDetails.fromLedgerApi)
} }
} }
@ -1242,9 +1307,9 @@ abstract class AbstractHttpServiceIntegrationTest
(uri, _, _, client, _) => (uri, _, _, client, _) =>
import scalaz.std.vector._ import scalaz.std.vector._
val charlie = domain.Party("Charlie") val charlie = getUniqueParty("Charlie")
val knownParties = domain.Party.subst(Vector("Alice", "Bob", "Dave")) :+ charlie val knownParties = Vector(getUniqueParty("Alice"), getUniqueParty("Bob")) :+ charlie
val erin = domain.Party("Erin") val erin = getUniqueParty("Erin")
val requestedPartyIds: Vector[domain.Party] = knownParties.filterNot(_ == charlie) :+ erin val requestedPartyIds: Vector[domain.Party] = knownParties.filterNot(_ == charlie) :+ erin
val partyManagement = client.partyManagementClient val partyManagement = client.partyManagementClient
@ -1308,8 +1373,7 @@ abstract class AbstractHttpServiceIntegrationTest
"parties endpoint returns empty result with warnings and OK status if nothing found" in withHttpServiceAndClient { "parties endpoint returns empty result with warnings and OK status if nothing found" in withHttpServiceAndClient {
(uri, _, _, _, _) => (uri, _, _, _, _) =>
val requestedPartyIds: Vector[domain.Party] = val requestedPartyIds: Vector[domain.Party] =
domain.Party.subst(Vector("Alice", "Bob", "Dave")) Vector(getUniqueParty("Alice"), getUniqueParty("Bob"))
postJsonRequest( postJsonRequest(
uri = uri.withPath(Uri.Path("/v1/parties")), uri = uri.withPath(Uri.Path("/v1/parties")),
JsArray(requestedPartyIds.map(x => JsString(x.unwrap))), JsArray(requestedPartyIds.map(x => JsString(x.unwrap))),
@ -1402,26 +1466,27 @@ abstract class AbstractHttpServiceIntegrationTest
} }
"fetch by contractId" in withHttpService { (uri, encoder, _, _) => "fetch by contractId" in withHttpService { (uri, encoder, _, _) =>
val command: domain.CreateCommand[v.Record, OptionalPkg] = iouCreateCommand() val (alice, headers) = getUniquePartyAndAuthHeaders("Alice")
val command: domain.CreateCommand[v.Record, OptionalPkg] = iouCreateCommand(alice.unwrap)
postCreateCommand(command, encoder, uri).flatMap { case (status, output) => postCreateCommand(command, encoder, uri, headers).flatMap { case (status, output) =>
status shouldBe StatusCodes.OK status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK) assertStatus(output, StatusCodes.OK)
val contractId: ContractId = getContractId(getResult(output)) val contractId: ContractId = getContractId(getResult(output))
val locator = domain.EnrichedContractId(None, contractId) val locator = domain.EnrichedContractId(None, contractId)
lookupContractAndAssert(locator)(contractId, command, encoder, uri) lookupContractAndAssert(locator)(contractId, command, encoder, uri, headers)
}: Future[Assertion] }: Future[Assertion]
} }
"fetch returns {status:200, result:null} when contract is not found" in withHttpService { "fetch returns {status:200, result:null} when contract is not found" in withHttpService {
(uri, _, _, _) => (uri, _, _, _) =>
val owner = domain.Party("Alice") val (alice, headers) = getUniquePartyAndAuthHeaders("Alice")
val accountNumber = "abc123" val accountNumber = "abc123"
val locator = domain.EnrichedContractKey( val locator = domain.EnrichedContractKey(
domain.TemplateId(None, "Account", "Account"), domain.TemplateId(None, "Account", "Account"),
JsArray(JsString(owner.unwrap), JsString(accountNumber)), JsArray(JsString(alice.unwrap), JsString(accountNumber)),
) )
postContractsLookup(locator, uri.withPath(Uri.Path("/v1/fetch"))).flatMap { postContractsLookup(locator, uri.withPath(Uri.Path("/v1/fetch")), headers).flatMap {
case (status, output) => case (status, output) =>
status shouldBe StatusCodes.OK status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK) assertStatus(output, StatusCodes.OK)
@ -1433,32 +1498,32 @@ abstract class AbstractHttpServiceIntegrationTest
} }
"fetch by key" in withHttpService { (uri, encoder, _, _) => "fetch by key" in withHttpService { (uri, encoder, _, _) =>
val owner = domain.Party("Alice") val (alice, headers) = getUniquePartyAndAuthHeaders("Alice")
val accountNumber = "abc123" val accountNumber = "abc123"
val command: domain.CreateCommand[v.Record, OptionalPkg] = val command: domain.CreateCommand[v.Record, OptionalPkg] =
accountCreateCommand(owner, accountNumber) accountCreateCommand(alice, accountNumber)
postCreateCommand(command, encoder, uri).flatMap { case (status, output) => postCreateCommand(command, encoder, uri, headers).flatMap { case (status, output) =>
status shouldBe StatusCodes.OK status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK) assertStatus(output, StatusCodes.OK)
val contractId: ContractId = getContractId(getResult(output)) val contractId: ContractId = getContractId(getResult(output))
val locator = domain.EnrichedContractKey( val locator = domain.EnrichedContractKey(
domain.TemplateId(None, "Account", "Account"), domain.TemplateId(None, "Account", "Account"),
JsArray(JsString(owner.unwrap), JsString(accountNumber)), JsArray(JsString(alice.unwrap), JsString(accountNumber)),
) )
lookupContractAndAssert(locator)(contractId, command, encoder, uri) lookupContractAndAssert(locator)(contractId, command, encoder, uri, headers)
}: Future[Assertion] }: Future[Assertion]
} }
"commands/exercise Archive by key" in withHttpService { (uri, encoder, _, _) => "commands/exercise Archive by key" in withHttpService { (uri, encoder, _, _) =>
val owner = domain.Party("Alice") val (alice, headers) = getUniquePartyAndAuthHeaders("Alice")
val accountNumber = "abc123" val accountNumber = "abc123"
val create: domain.CreateCommand[v.Record, OptionalPkg] = val create: domain.CreateCommand[v.Record, OptionalPkg] =
accountCreateCommand(owner, accountNumber) accountCreateCommand(alice, accountNumber)
val keyRecord = v.Record( val keyRecord = v.Record(
fields = Seq( fields = Seq(
v.RecordField(value = Some(v.Value(v.Value.Sum.Party(owner.unwrap)))), v.RecordField(value = Some(v.Value(v.Value.Sum.Party(alice.unwrap)))),
v.RecordField(value = Some(v.Value(v.Value.Sum.Text(accountNumber)))), v.RecordField(value = Some(v.Value(v.Value.Sum.Text(accountNumber)))),
) )
) )
@ -1470,11 +1535,11 @@ abstract class AbstractHttpServiceIntegrationTest
archiveCommand(locator) archiveCommand(locator)
val archiveJson: JsValue = encodeExercise(encoder)(archive) val archiveJson: JsValue = encodeExercise(encoder)(archive)
postCreateCommand(create, encoder, uri).flatMap { case (status, output) => postCreateCommand(create, encoder, uri, headers).flatMap { case (status, output) =>
status shouldBe StatusCodes.OK status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK) assertStatus(output, StatusCodes.OK)
postJsonRequest(uri.withPath(Uri.Path("/v1/exercise")), archiveJson).flatMap { postJsonRequest(uri.withPath(Uri.Path("/v1/exercise")), archiveJson, headers).flatMap {
case (exerciseStatus, exerciseOutput) => case (exerciseStatus, exerciseOutput) =>
exerciseStatus shouldBe StatusCodes.OK exerciseStatus shouldBe StatusCodes.OK
assertStatus(exerciseOutput, StatusCodes.OK) assertStatus(exerciseOutput, StatusCodes.OK)
@ -1484,53 +1549,63 @@ abstract class AbstractHttpServiceIntegrationTest
"fetch by key containing variant and record, encoded as array with number num" in withHttpService { "fetch by key containing variant and record, encoded as array with number num" in withHttpService {
(uri, _, _, _) => (uri, _, _, _) =>
val (alice, headers) = getUniquePartyAndAuthHeaders("Alice")
testFetchByCompositeKey( testFetchByCompositeKey(
uri, uri,
jsObject("""{ jsObject(s"""{
"templateId": "Account:KeyedByVariantAndRecord", "templateId": "Account:KeyedByVariantAndRecord",
"key": [ "key": [
"Alice", "$alice",
{"tag": "Bar", "value": 42}, {"tag": "Bar", "value": 42},
{"baz": "another baz value"} {"baz": "another baz value"}
] ]
}"""), }"""),
alice,
headers,
) )
} }
"fetch by key containing variant and record, encoded as record with string num" in withHttpService { "fetch by key containing variant and record, encoded as record with string num" in withHttpService {
(uri, _, _, _) => (uri, _, _, _) =>
val (alice, headers) = getUniquePartyAndAuthHeaders("Alice")
testFetchByCompositeKey( testFetchByCompositeKey(
uri, uri,
jsObject("""{ jsObject(s"""{
"templateId": "Account:KeyedByVariantAndRecord", "templateId": "Account:KeyedByVariantAndRecord",
"key": { "key": {
"_1": "Alice", "_1": "$alice",
"_2": {"tag": "Bar", "value": "42"}, "_2": {"tag": "Bar", "value": "42"},
"_3": {"baz": "another baz value"} "_3": {"baz": "another baz value"}
} }
}"""), }"""),
alice,
headers,
) )
} }
private def testFetchByCompositeKey(uri: Uri, request: JsObject) = { private def testFetchByCompositeKey(
val createCommand = jsObject("""{ uri: Uri,
request: JsObject,
party: domain.Party,
headers: List[HttpHeader],
) = {
val createCommand = jsObject(s"""{
"templateId": "Account:KeyedByVariantAndRecord", "templateId": "Account:KeyedByVariantAndRecord",
"payload": { "payload": {
"name": "ABC DEF", "name": "ABC DEF",
"party": "Alice", "party": "${party.unwrap}",
"age": 123, "age": 123,
"fooVariant": {"tag": "Bar", "value": 42}, "fooVariant": {"tag": "Bar", "value": 42},
"bazRecord": {"baz": "another baz value"} "bazRecord": {"baz": "another baz value"}
} }
}""") }""")
postJsonRequest(uri.withPath(Uri.Path("/v1/create")), createCommand, headers).flatMap {
postJsonRequest(uri.withPath(Uri.Path("/v1/create")), createCommand).flatMap {
case (status, output) => case (status, output) =>
status shouldBe StatusCodes.OK status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK) assertStatus(output, StatusCodes.OK)
val contractId: ContractId = getContractId(getResult(output)) val contractId: ContractId = getContractId(getResult(output))
postJsonRequest(uri.withPath(Uri.Path("/v1/fetch")), request).flatMap { postJsonRequest(uri.withPath(Uri.Path("/v1/fetch")), request, headers).flatMap {
case (status, output) => case (status, output) =>
status shouldBe StatusCodes.OK status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK) assertStatus(output, StatusCodes.OK)
@ -1540,12 +1615,12 @@ abstract class AbstractHttpServiceIntegrationTest
} }
"query by a variant field" in withHttpService { (uri, encoder, _, _) => "query by a variant field" in withHttpService { (uri, encoder, _, _) =>
val owner = domain.Party("Alice") val (alice, headers) = getUniquePartyAndAuthHeaders("Alice")
val accountNumber = "abc123" val accountNumber = "abc123"
val now = TimestampConversion.instantToMicros(Instant.now) val now = TimestampConversion.instantToMicros(Instant.now)
val nowStr = TimestampConversion.microsToInstant(now).toString val nowStr = TimestampConversion.microsToInstant(now).toString
val command: domain.CreateCommand[v.Record, OptionalPkg] = val command: domain.CreateCommand[v.Record, OptionalPkg] =
accountCreateCommand(owner, accountNumber, now) accountCreateCommand(alice, accountNumber, now)
val packageId: Ref.PackageId = MetadataReader val packageId: Ref.PackageId = MetadataReader
.templateByName(metadata2)(Ref.QualifiedName.assertFromString("Account:Account")) .templateByName(metadata2)(Ref.QualifiedName.assertFromString("Account:Account"))
@ -1553,7 +1628,7 @@ abstract class AbstractHttpServiceIntegrationTest
.map(_._1) .map(_._1)
.getOrElse(fail(s"Cannot retrieve packageId")) .getOrElse(fail(s"Cannot retrieve packageId"))
postCreateCommand(command, encoder, uri).flatMap { case (status, output) => postCreateCommand(command, encoder, uri, headers).flatMap { case (status, output) =>
status shouldBe StatusCodes.OK status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK) assertStatus(output, StatusCodes.OK)
val contractId: ContractId = getContractId(getResult(output)) val contractId: ContractId = getContractId(getResult(output))
@ -1566,7 +1641,7 @@ abstract class AbstractHttpServiceIntegrationTest
} }
}""") }""")
postJsonRequest(uri.withPath(Uri.Path("/v1/query")), query).map { postJsonRequest(uri.withPath(Uri.Path("/v1/query")), query, headers).map {
case (searchStatus, searchOutput) => case (searchStatus, searchOutput) =>
searchStatus shouldBe StatusCodes.OK searchStatus shouldBe StatusCodes.OK
assertStatus(searchOutput, StatusCodes.OK) assertStatus(searchOutput, StatusCodes.OK)
@ -1635,14 +1710,16 @@ abstract class AbstractHttpServiceIntegrationTest
"package list is updated when a query request is made" in withLedger2[Assertion] { "package list is updated when a query request is made" in withLedger2[Assertion] {
(ledgerPort: Port, _, _) => (ledgerPort: Port, _, _) =>
val (alice, headers) = getUniquePartyAndAuthHeaders("Alice")
for { for {
_ <- withHttpServiceOnly(ledgerPort) { (uri, encoder, _) => _ <- withHttpServiceOnly(ledgerPort) { (uri, encoder, _) =>
searchDataSet.traverse(c => postCreateCommand(c, encoder, uri)).flatMap { rs => val searchDataSet = genSearchDataSet(alice)
searchDataSet.traverse(c => postCreateCommand(c, encoder, uri, headers)).flatMap { rs =>
rs.map(_._1) shouldBe List.fill(searchDataSet.size)(StatusCodes.OK) rs.map(_._1) shouldBe List.fill(searchDataSet.size)(StatusCodes.OK)
} }
} }
_ <- withHttpServiceOnly(ledgerPort) { (uri, _, _) => _ <- withHttpServiceOnly(ledgerPort) { (uri, _, _) =>
getRequest(uri = uri.withPath(Uri.Path("/v1/query"))) getRequest(uri = uri.withPath(Uri.Path("/v1/query")), headers)
.flatMap { case (status, output) => .flatMap { case (status, output) =>
status shouldBe StatusCodes.OK status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK) assertStatus(output, StatusCodes.OK)
@ -1654,13 +1731,15 @@ abstract class AbstractHttpServiceIntegrationTest
} yield succeed } yield succeed
} }
"archiving a large number of contracts should succeed" in withHttpServiceAndClient( "archiving a large number of contracts should succeed" taggedAs (SkipScala212) in withHttpServiceAndClient(
StartSettings.DefaultMaxInboundMessageSize * 10 StartSettings.DefaultMaxInboundMessageSize * 10
) { (uri, encoder, _, _, _) => ) { (uri, encoder, _, _, _) =>
val numContracts: Long = 10000 val (alice, headers) = getUniquePartyAndAuthHeaders("Alice")
//The numContracts size should test for https://github.com/digital-asset/daml/issues/10339
val numContracts: Long = 2000
val helperId = domain.TemplateId(None, "Account", "Helper") val helperId = domain.TemplateId(None, "Account", "Helper")
val payload = v.Record( val payload = v.Record(
fields = List(v.RecordField("owner", Some(v.Value(v.Value.Sum.Party("Alice"))))) fields = List(v.RecordField("owner", Some(v.Value(v.Value.Sum.Party(alice.unwrap)))))
) )
val createCmd: domain.CreateAndExerciseCommand[v.Record, v.Value, OptionalPkg] = val createCmd: domain.CreateAndExerciseCommand[v.Record, v.Value, OptionalPkg] =
domain.CreateAndExerciseCommand( domain.CreateAndExerciseCommand(
@ -1700,6 +1779,7 @@ abstract class AbstractHttpServiceIntegrationTest
def queryN(n: Long): Future[Assertion] = postJsonRequest( def queryN(n: Long): Future[Assertion] = postJsonRequest(
uri.withPath(Uri.Path("/v1/query")), uri.withPath(Uri.Path("/v1/query")),
jsObject("""{"templateIds": ["Account:Account"]}"""), jsObject("""{"templateIds": ["Account:Account"]}"""),
headers,
).flatMap { case (status, output) => ).flatMap { case (status, output) =>
status shouldBe StatusCodes.OK status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK) assertStatus(output, StatusCodes.OK)
@ -1709,7 +1789,11 @@ abstract class AbstractHttpServiceIntegrationTest
} }
for { for {
resp <- postJsonRequest(uri.withPath(Uri.Path("/v1/create-and-exercise")), encode(createCmd)) resp <- postJsonRequest(
uri.withPath(Uri.Path("/v1/create-and-exercise")),
encode(createCmd),
headers,
)
(status, output) = resp (status, output) = resp
_ = { _ = {
status shouldBe StatusCodes.OK status shouldBe StatusCodes.OK
@ -1723,6 +1807,7 @@ abstract class AbstractHttpServiceIntegrationTest
status <- postJsonRequest( status <- postJsonRequest(
uri.withPath(Uri.Path("/v1/create-and-exercise")), uri.withPath(Uri.Path("/v1/create-and-exercise")),
encode(archiveCmd(created)), encode(archiveCmd(created)),
headers,
).map(_._1) ).map(_._1)
_ = { _ = {
status shouldBe StatusCodes.OK status shouldBe StatusCodes.OK

View File

@ -9,7 +9,6 @@ import java.time.Clock
import akka.http.scaladsl.model.Uri import akka.http.scaladsl.model.Uri
import com.daml.doobie.logging.Slf4jLogHandler import com.daml.doobie.logging.Slf4jLogHandler
import com.daml.http.AbstractHttpServiceIntegrationTestFuns.{dar1, dar2}
import com.daml.http.dbbackend.JdbcConfig import com.daml.http.dbbackend.JdbcConfig
import com.daml.http.json.{DomainJsonDecoder, DomainJsonEncoder} import com.daml.http.json.{DomainJsonDecoder, DomainJsonEncoder}
import com.daml.ledger.api.v1.command_service.CommandServiceGrpc import com.daml.ledger.api.v1.command_service.CommandServiceGrpc
@ -85,7 +84,7 @@ abstract class AbstractNonRepudiationTest
} }
private def withParticipant[A] = private def withParticipant[A] =
HttpServiceTestFixture.withLedger[A](List(dar1, dar2), testId, None, useTls) _ usingLedger[A](testId) _
private def withJsonApi[A](participantPort: Port) = private def withJsonApi[A](participantPort: Port) =
HttpServiceTestFixture.withHttpService[A]( HttpServiceTestFixture.withHttpService[A](

View File

@ -6,7 +6,7 @@ package com.daml.http
import akka.NotUsed import akka.NotUsed
import akka.http.scaladsl.Http import akka.http.scaladsl.Http
import akka.http.scaladsl.model.ws.{Message, TextMessage, WebSocketRequest} import akka.http.scaladsl.model.ws.{Message, TextMessage, WebSocketRequest}
import akka.http.scaladsl.model.{StatusCodes, Uri} import akka.http.scaladsl.model.{HttpHeader, StatusCodes, Uri}
import akka.stream.{KillSwitches, UniqueKillSwitch} import akka.stream.{KillSwitches, UniqueKillSwitch}
import akka.stream.scaladsl.{Keep, Sink, Source} import akka.stream.scaladsl.{Keep, Sink, Source}
import com.daml.http.json.SprayJson import com.daml.http.json.SprayJson
@ -159,9 +159,10 @@ abstract class AbstractWebsocketServiceIntegrationTest
"query endpoint should publish transactions when command create is completed" in withHttpService { "query endpoint should publish transactions when command create is completed" in withHttpService {
(uri, _, _, _) => (uri, _, _, _) =>
val (alice, headers) = getUniquePartyAndAuthHeaders("Alice")
for { for {
_ <- initialIouCreate(uri) _ <- initialIouCreate(uri, alice, headers)
jwt = jwtForParties(List(alice.unwrap), List(), testId)
clientMsg <- singleClientQueryStream( clientMsg <- singleClientQueryStream(
jwt, jwt,
uri, uri,
@ -169,7 +170,7 @@ abstract class AbstractWebsocketServiceIntegrationTest
).take(2) ).take(2)
.runWith(collectResultsAsTextMessage) .runWith(collectResultsAsTextMessage)
} yield inside(clientMsg) { case result +: heartbeats => } yield inside(clientMsg) { case result +: heartbeats =>
result should include(""""issuer":"Alice"""") result should include(s""""issuer":"$alice"""")
result should include(""""amount":"999.99"""") result should include(""""amount":"999.99"""")
Inspectors.forAll(heartbeats)(assertHeartbeat) Inspectors.forAll(heartbeats)(assertHeartbeat)
} }
@ -177,14 +178,16 @@ abstract class AbstractWebsocketServiceIntegrationTest
"fetch endpoint should publish transactions when command create is completed" in withHttpService { "fetch endpoint should publish transactions when command create is completed" in withHttpService {
(uri, encoder, _, _) => (uri, encoder, _, _) =>
val (alice, headers) = getUniquePartyAndAuthHeaders("Alice")
for { for {
_ <- initialAccountCreate(uri, encoder) _ <- initialAccountCreate(uri, encoder, alice, headers)
jwt = jwtForParties(List(alice.unwrap), Nil, testId)
fetchRequest = s"""[{"templateId": "Account:Account", "key": ["$alice", "abc123"]}]"""
clientMsg <- singleClientFetchStream(jwt, uri, fetchRequest) clientMsg <- singleClientFetchStream(jwt, uri, fetchRequest)
.take(2) .take(2)
.runWith(collectResultsAsTextMessage) .runWith(collectResultsAsTextMessage)
} yield inside(clientMsg) { case result +: heartbeats => } yield inside(clientMsg) { case result +: heartbeats =>
result should include(""""owner":"Alice"""") result should include(s""""owner":"$alice"""")
result should include(""""number":"abc123"""") result should include(""""number":"abc123"""")
result should not include (""""offset":"""") result should not include (""""offset":"""")
Inspectors.forAll(heartbeats)(assertHeartbeat) Inspectors.forAll(heartbeats)(assertHeartbeat)
@ -192,35 +195,37 @@ abstract class AbstractWebsocketServiceIntegrationTest
} }
"query endpoint should warn on unknown template IDs" in withHttpService { (uri, _, _, _) => "query endpoint should warn on unknown template IDs" in withHttpService { (uri, _, _, _) =>
val (alice, headers) = getUniquePartyAndAuthHeaders("Alice")
for { for {
_ <- initialIouCreate(uri) _ <- initialIouCreate(uri, alice, headers)
clientMsg <- singleClientQueryStream( clientMsg <- singleClientQueryStream(
jwt, jwtForParties(List(alice.unwrap), List(), testId),
uri, uri,
"""{"templateIds": ["Iou:Iou", "Unknown:Template"]}""", """{"templateIds": ["Iou:Iou", "Unknown:Template"]}""",
).take(3) ).take(3)
.runWith(collectResultsAsTextMessage) .runWith(collectResultsAsTextMessage)
} yield inside(clientMsg) { case warning +: result +: heartbeats => } yield inside(clientMsg) { case warning +: result +: heartbeats =>
warning should include("\"warnings\":{\"unknownTemplateIds\":[\"Unk") warning should include("\"warnings\":{\"unknownTemplateIds\":[\"Unk")
result should include("\"issuer\":\"Alice\"") result should include(s""""issuer":"$alice"""")
Inspectors.forAll(heartbeats)(assertHeartbeat) Inspectors.forAll(heartbeats)(assertHeartbeat)
} }
} }
"fetch endpoint should warn on unknown template IDs" in withHttpService { (uri, encoder, _, _) => "fetch endpoint should warn on unknown template IDs" in withHttpService { (uri, encoder, _, _) =>
val (alice, headers) = getUniquePartyAndAuthHeaders("Alice")
for { for {
_ <- initialAccountCreate(uri, encoder) _ <- initialAccountCreate(uri, encoder, alice, headers)
clientMsg <- singleClientFetchStream( clientMsg <- singleClientFetchStream(
jwt, jwtForParties(List(alice.unwrap), List(), testId),
uri, uri,
"""[{"templateId": "Account:Account", "key": ["Alice", "abc123"]}, {"templateId": "Unknown:Template", "key": ["Alice", "abc123"]}]""", s"""[{"templateId": "Account:Account", "key": ["$alice", "abc123"]}, {"templateId": "Unknown:Template", "key": ["$alice", "abc123"]}]""",
).take(3) ).take(3)
.runWith(collectResultsAsTextMessage) .runWith(collectResultsAsTextMessage)
} yield inside(clientMsg) { case warning +: result +: heartbeats => } yield inside(clientMsg) { case warning +: result +: heartbeats =>
warning should include("""{"warnings":{"unknownTemplateIds":["Unk""") warning should include("""{"warnings":{"unknownTemplateIds":["Unk""")
result should include(""""owner":"Alice"""") result should include(s""""owner":"$alice"""")
result should include(""""number":"abc123"""") result should include(""""number":"abc123"""")
Inspectors.forAll(heartbeats)(assertHeartbeat) Inspectors.forAll(heartbeats)(assertHeartbeat)
} }
@ -267,7 +272,8 @@ abstract class AbstractWebsocketServiceIntegrationTest
(uri, _, _, _) => (uri, _, _, _) =>
import spray.json._ import spray.json._
val initialCreate = initialIouCreate(uri) val (party, headers) = getUniquePartyAndAuthHeaders("Alice")
val initialCreate = initialIouCreate(uri, party, headers)
val query = val query =
"""[ """[
@ -292,7 +298,7 @@ abstract class AbstractWebsocketServiceIntegrationTest
postJsonRequest( postJsonRequest(
uri.withPath(Uri.Path("/v1/exercise")), uri.withPath(Uri.Path("/v1/exercise")),
exercisePayload(domain.ContractId(ctid)), exercisePayload(domain.ContractId(ctid)),
headersWithAuth, headers,
) map { case (statusCode, _) => ) map { case (statusCode, _) =>
statusCode.isSuccess shouldBe true statusCode.isSuccess shouldBe true
} }
@ -348,6 +354,7 @@ abstract class AbstractWebsocketServiceIntegrationTest
creation <- initialCreate creation <- initialCreate
_ = creation._1 shouldBe a[StatusCodes.Success] _ = creation._1 shouldBe a[StatusCodes.Success]
iouCid = getContractId(getResult(creation._2)) iouCid = getContractId(getResult(creation._2))
jwt = jwtForParties(List(party.unwrap), List(), testId)
(kill, source) = singleClientQueryStream(jwt, uri, query) (kill, source) = singleClientQueryStream(jwt, uri, query)
.viaMat(KillSwitches.single)(Keep.right) .viaMat(KillSwitches.single)(Keep.right)
.preMaterialize() .preMaterialize()
@ -368,19 +375,21 @@ abstract class AbstractWebsocketServiceIntegrationTest
(uri, encoder, _, _) => (uri, encoder, _, _) =>
import spray.json._ import spray.json._
val (alice, aliceAuthHeaders) = getUniquePartyAndAuthHeaders("Alice")
val (bob, bobAuthHeaders) = getUniquePartyAndAuthHeaders("Bob")
val f1 = val f1 =
postCreateCommand( postCreateCommand(
accountCreateCommand(domain.Party("Alice"), "abc123"), accountCreateCommand(alice, "abc123"),
encoder, encoder,
uri, uri,
headers = headersWithPartyAuth(List("Alice")), headers = aliceAuthHeaders,
) )
val f2 = val f2 =
postCreateCommand( postCreateCommand(
accountCreateCommand(domain.Party("Bob"), "def456"), accountCreateCommand(bob, "def456"),
encoder, encoder,
uri, uri,
headers = headersWithPartyAuth(List("Bob")), headers = bobAuthHeaders,
) )
val query = val query =
@ -402,33 +411,33 @@ abstract class AbstractWebsocketServiceIntegrationTest
ContractDelta(Vector(), _, Some(liveStartOffset)) <- readOne ContractDelta(Vector(), _, Some(liveStartOffset)) <- readOne
_ <- liftF( _ <- liftF(
postCreateCommand( postCreateCommand(
accountCreateCommand(domain.Party("Alice"), "abc234"), accountCreateCommand(alice, "abc234"),
encoder, encoder,
uri, uri,
headers = headersWithPartyAuth(List("Alice")), headers = aliceAuthHeaders,
) )
) )
ContractDelta(Vector((_, aliceAccount)), _, Some(_)) <- readOne ContractDelta(Vector((_, aliceAccount)), _, Some(_)) <- readOne
_ = inside(aliceAccount) { case JsObject(obj) => _ = inside(aliceAccount) { case JsObject(obj) =>
inside((obj get "owner", obj get "number")) { inside((obj get "owner", obj get "number")) {
case (Some(JsString(owner)), Some(JsString(number))) => case (Some(JsString(owner)), Some(JsString(number))) =>
owner shouldBe "Alice" owner shouldBe alice.unwrap
number shouldBe "abc234" number shouldBe "abc234"
} }
} }
_ <- liftF( _ <- liftF(
postCreateCommand( postCreateCommand(
accountCreateCommand(domain.Party("Bob"), "def567"), accountCreateCommand(bob, "def567"),
encoder, encoder,
uri, uri,
headers = headersWithPartyAuth(List("Bob")), headers = bobAuthHeaders,
) )
) )
ContractDelta(Vector((_, bobAccount)), _, Some(lastSeenOffset)) <- readOne ContractDelta(Vector((_, bobAccount)), _, Some(lastSeenOffset)) <- readOne
_ = inside(bobAccount) { case JsObject(obj) => _ = inside(bobAccount) { case JsObject(obj) =>
inside((obj get "owner", obj get "number")) { inside((obj get "owner", obj get "number")) {
case (Some(JsString(owner)), Some(JsString(number))) => case (Some(JsString(owner)), Some(JsString(number))) =>
owner shouldBe "Bob" owner shouldBe bob.unwrap
number shouldBe "def567" number shouldBe "def567"
} }
} }
@ -456,7 +465,7 @@ abstract class AbstractWebsocketServiceIntegrationTest
cid2 = getContractId(getResult(r2._2)) cid2 = getContractId(getResult(r2._2))
(kill, source) = singleClientQueryStream( (kill, source) = singleClientQueryStream(
jwtForParties(List("Alice", "Bob"), List(), testId), jwtForParties(List(alice.unwrap, bob.unwrap), List(), testId),
uri, uri,
query, query,
).viaMat(KillSwitches.single)(Keep.right).preMaterialize() ).viaMat(KillSwitches.single)(Keep.right).preMaterialize()
@ -465,7 +474,12 @@ abstract class AbstractWebsocketServiceIntegrationTest
lastSeen.unwrap should be > liveStart.unwrap lastSeen.unwrap should be > liveStart.unwrap
liveStart liveStart
} }
rescan <- (singleClientQueryStream(jwt, uri, query, Some(liveOffset)) rescan <- (singleClientQueryStream(
jwtForParties(List(alice.unwrap), List(), testId),
uri,
query,
Some(liveOffset),
)
via parseResp).take(1) runWith remainingDeltas via parseResp).take(1) runWith remainingDeltas
} yield inside(rescan) { case (Vector(_), _, Some(_)) => } yield inside(rescan) { case (Vector(_), _, Some(_)) =>
succeed succeed
@ -474,20 +488,34 @@ abstract class AbstractWebsocketServiceIntegrationTest
"fetch should receive deltas as contracts are archived/created, filtering out phantom archives" in withHttpService { "fetch should receive deltas as contracts are archived/created, filtering out phantom archives" in withHttpService {
(uri, encoder, _, _) => (uri, encoder, _, _) =>
val (alice, headers) = getUniquePartyAndAuthHeaders("Alice")
val templateId = domain.TemplateId(None, "Account", "Account") val templateId = domain.TemplateId(None, "Account", "Account")
def fetchRequest(contractIdAtOffset: Option[Option[domain.ContractId]] = None) = { def fetchRequest(contractIdAtOffset: Option[Option[domain.ContractId]] = None) = {
import spray.json._, json.JsonProtocol._ import spray.json._, json.JsonProtocol._
List( List(
Map("templateId" -> "Account:Account".toJson, "key" -> List("Alice", "abc123").toJson) Map(
"templateId" -> "Account:Account".toJson,
"key" -> List(alice.unwrap, "abc123").toJson,
)
++ contractIdAtOffset ++ contractIdAtOffset
.map(ocid => contractIdAtOffsetKey -> ocid.toJson) .map(ocid => contractIdAtOffsetKey -> ocid.toJson)
.toList .toList
).toJson.compactPrint ).toJson.compactPrint
} }
val f1 = val f1 =
postCreateCommand(accountCreateCommand(domain.Party("Alice"), "abc123"), encoder, uri) postCreateCommand(
accountCreateCommand(alice, "abc123"),
encoder,
uri,
headers,
)
val f2 = val f2 =
postCreateCommand(accountCreateCommand(domain.Party("Alice"), "def456"), encoder, uri) postCreateCommand(
accountCreateCommand(alice, "def456"),
encoder,
uri,
headers,
)
def resp( def resp(
cid1: domain.ContractId, cid1: domain.ContractId,
@ -500,12 +528,13 @@ abstract class AbstractWebsocketServiceIntegrationTest
for { for {
ContractDelta(Vector((cid, c)), Vector(), None) <- readOne ContractDelta(Vector((cid, c)), Vector(), None) <- readOne
_ = (cid: String) shouldBe (cid1.unwrap: String) _ = (cid: String) shouldBe (cid1.unwrap: String)
ctid <- liftF(postArchiveCommand(templateId, cid2, encoder, uri).flatMap { ctid <- liftF(postArchiveCommand(templateId, cid2, encoder, uri, headers).flatMap {
case (statusCode, _) => case (statusCode, _) =>
statusCode.isSuccess shouldBe true statusCode.isSuccess shouldBe true
postArchiveCommand(templateId, cid1, encoder, uri).map { case (statusCode, _) => postArchiveCommand(templateId, cid1, encoder, uri, headers).map {
statusCode.isSuccess shouldBe true case (statusCode, _) =>
cid statusCode.isSuccess shouldBe true
cid
} }
}) })
@ -541,7 +570,7 @@ abstract class AbstractWebsocketServiceIntegrationTest
r2 <- f2 r2 <- f2
_ = r2._1 shouldBe a[StatusCodes.Success] _ = r2._1 shouldBe a[StatusCodes.Success]
cid2 = getContractId(getResult(r2._2)) cid2 = getContractId(getResult(r2._2))
jwt = jwtForParties(List(alice.unwrap), List(), testId)
(kill, source) = singleClientFetchStream(jwt, uri, fetchRequest()) (kill, source) = singleClientFetchStream(jwt, uri, fetchRequest())
.viaMat(KillSwitches.single)(Keep.right) .viaMat(KillSwitches.single)(Keep.right)
.preMaterialize() .preMaterialize()
@ -576,16 +605,29 @@ abstract class AbstractWebsocketServiceIntegrationTest
} }
"fetch multiple keys should work" in withHttpService { (uri, encoder, _, _) => "fetch multiple keys should work" in withHttpService { (uri, encoder, _, _) =>
val (alice, headers) = getUniquePartyAndAuthHeaders("Alice")
val jwt = jwtForParties(List(alice.unwrap), List(), testId)
def create(account: String): Future[domain.ContractId] = def create(account: String): Future[domain.ContractId] =
for { for {
r <- postCreateCommand(accountCreateCommand(domain.Party("Alice"), account), encoder, uri) r <- postCreateCommand(
accountCreateCommand(alice, account),
encoder,
uri,
headers,
)
} yield { } yield {
assert(r._1.isSuccess) assert(r._1.isSuccess)
getContractId(getResult(r._2)) getContractId(getResult(r._2))
} }
def archive(id: domain.ContractId): Future[Assertion] = def archive(id: domain.ContractId): Future[Assertion] =
for { for {
r <- postArchiveCommand(domain.TemplateId(None, "Account", "Account"), id, encoder, uri) r <- postArchiveCommand(
domain.TemplateId(None, "Account", "Account"),
id,
encoder,
uri,
headers,
)
} yield { } yield {
assert(r._1.isSuccess) assert(r._1.isSuccess)
} }
@ -620,9 +662,9 @@ abstract class AbstractWebsocketServiceIntegrationTest
) )
} }
val req = val req =
""" s"""
|[{"templateId": "Account:Account", "key": ["Alice", "abc123"]}, |[{"templateId": "Account:Account", "key": ["$alice", "abc123"]},
| {"templateId": "Account:Account", "key": ["Alice", "def456"]}] | {"templateId": "Account:Account", "key": ["$alice", "def456"]}]
|""".stripMargin |""".stripMargin
val (kill, source) = singleClientFetchStream(jwt, uri, req) val (kill, source) = singleClientFetchStream(jwt, uri, req)
@ -637,26 +679,28 @@ abstract class AbstractWebsocketServiceIntegrationTest
import spray.json._ import spray.json._
val templateId = domain.TemplateId(None, "Account", "Account") val templateId = domain.TemplateId(None, "Account", "Account")
val (alice, aliceAuthHeaders) = getUniquePartyAndAuthHeaders("Alice")
val (bob, bobAuthHeaders) = getUniquePartyAndAuthHeaders("Bob")
val f1 = val f1 =
postCreateCommand( postCreateCommand(
accountCreateCommand(domain.Party("Alice"), "abc123"), accountCreateCommand(alice, "abc123"),
encoder, encoder,
uri, uri,
headers = headersWithPartyAuth(List("Alice")), headers = aliceAuthHeaders,
) )
val f2 = val f2 =
postCreateCommand( postCreateCommand(
accountCreateCommand(domain.Party("Bob"), "def456"), accountCreateCommand(bob, "def456"),
encoder, encoder,
uri, uri,
headers = headersWithPartyAuth(List("Bob")), headers = bobAuthHeaders,
) )
val query = val query =
"""[ s"""[
{"templateId": "Account:Account", "key": ["Alice", "abc123"]}, {"templateId": "Account:Account", "key": ["$alice", "abc123"]},
{"templateId": "Account:Account", "key": ["Bob", "def456"]} {"templateId": "Account:Account", "key": ["$bob", "def456"]}
]""" ]"""
def resp( def resp(
@ -671,7 +715,7 @@ abstract class AbstractWebsocketServiceIntegrationTest
Vector((account1, _), (account2, _)) <- readAcsN(2) Vector((account1, _), (account2, _)) <- readAcsN(2)
_ = Seq(account1, account2) should contain theSameElementsAs Seq(cid1, cid2) _ = Seq(account1, account2) should contain theSameElementsAs Seq(cid1, cid2)
ContractDelta(Vector(), _, Some(liveStartOffset)) <- readOne ContractDelta(Vector(), _, Some(liveStartOffset)) <- readOne
_ <- liftF(postArchiveCommand(templateId, cid1, encoder, uri)) _ <- liftF(postArchiveCommand(templateId, cid1, encoder, uri, aliceAuthHeaders))
ContractDelta(Vector(), Vector(archivedCid1), Some(_)) <- readOne ContractDelta(Vector(), Vector(archivedCid1), Some(_)) <- readOne
_ = archivedCid1.contractId shouldBe cid1 _ = archivedCid1.contractId shouldBe cid1
_ <- liftF( _ <- liftF(
@ -680,7 +724,7 @@ abstract class AbstractWebsocketServiceIntegrationTest
cid2, cid2,
encoder, encoder,
uri, uri,
headers = headersWithPartyAuth(List("Bob")), headers = bobAuthHeaders,
) )
) )
ContractDelta(Vector(), Vector(archivedCid2), Some(lastSeenOffset)) <- readOne ContractDelta(Vector(), Vector(archivedCid2), Some(lastSeenOffset)) <- readOne
@ -708,8 +752,9 @@ abstract class AbstractWebsocketServiceIntegrationTest
_ = r2._1 shouldBe a[StatusCodes.Success] _ = r2._1 shouldBe a[StatusCodes.Success]
cid2 = getContractId(getResult(r2._2)) cid2 = getContractId(getResult(r2._2))
jwt = jwtForParties(List(alice.unwrap, bob.unwrap), List(), testId)
(kill, source) = singleClientFetchStream( (kill, source) = singleClientFetchStream(
jwtForParties(List("Alice", "Bob"), List(), testId), jwt,
uri, uri,
query, query,
).viaMat(KillSwitches.single)(Keep.right).preMaterialize() ).viaMat(KillSwitches.single)(Keep.right).preMaterialize()
@ -719,7 +764,7 @@ abstract class AbstractWebsocketServiceIntegrationTest
liveStart liveStart
} }
rescan <- (singleClientFetchStream( rescan <- (singleClientFetchStream(
jwtForParties(List("Alice", "Bob"), List(), testId), jwt,
uri, uri,
query, query,
Some(liveOffset), Some(liveOffset),
@ -791,25 +836,29 @@ abstract class AbstractWebsocketServiceIntegrationTest
"query on a bunch of random splits should yield consistent results" in withHttpService { "query on a bunch of random splits should yield consistent results" in withHttpService {
(uri, _, _, _) => (uri, _, _, _) =>
val (alice, headers) = getUniquePartyAndAuthHeaders("Alice")
val splitSample = SplitSeq.gen.map(_ map (BigDecimal(_))).sample.get val splitSample = SplitSeq.gen.map(_ map (BigDecimal(_))).sample.get
val query = val query =
"""[ """[
{"templateIds": ["Iou:Iou"]} {"templateIds": ["Iou:Iou"]}
]""" ]"""
val (kill, source) = singleClientQueryStream(jwt, uri, query) val (kill, source) =
.viaMat(KillSwitches.single)(Keep.right) singleClientQueryStream(jwtForParties(List(alice.unwrap), List(), testId), uri, query)
.preMaterialize() .viaMat(KillSwitches.single)(Keep.right)
.preMaterialize()
source source
.via(parseResp) .via(parseResp)
.map(iouSplitResult) .map(iouSplitResult)
.filterNot(_ == \/-((Vector(), Vector()))) // liveness marker/heartbeat .filterNot(_ == \/-((Vector(), Vector()))) // liveness marker/heartbeat
.runWith(Consume.interpret(trialSplitSeq(uri, splitSample, kill))) .runWith(Consume.interpret(trialSplitSeq(uri, splitSample, kill, alice.unwrap, headers)))
} }
private def trialSplitSeq( private def trialSplitSeq(
serviceUri: Uri, serviceUri: Uri,
ss: SplitSeq[BigDecimal], ss: SplitSeq[BigDecimal],
kill: UniqueKillSwitch, kill: UniqueKillSwitch,
partyName: String,
headers: List[HttpHeader],
): Consume.FCC[IouSplitResult, Assertion] = { ): Consume.FCC[IouSplitResult, Assertion] = {
val dslSyntax = Consume.syntax[IouSplitResult] val dslSyntax = Consume.syntax[IouSplitResult]
import SplitSeq._ import SplitSeq._
@ -826,7 +875,7 @@ abstract class AbstractWebsocketServiceIntegrationTest
postJsonRequest( postJsonRequest(
serviceUri.withPath(Uri.Path("/v1/exercise")), serviceUri.withPath(Uri.Path("/v1/exercise")),
exercisePayload(createdCid, l.x), exercisePayload(createdCid, l.x),
headersWithAuth, headers,
) )
) )
@ -848,10 +897,10 @@ abstract class AbstractWebsocketServiceIntegrationTest
"templateId" -> "Iou:Iou".toJson, "templateId" -> "Iou:Iou".toJson,
"payload" -> Map( "payload" -> Map(
"observers" -> List[String]().toJson, "observers" -> List[String]().toJson,
"issuer" -> "Alice".toJson, "issuer" -> partyName.toJson,
"amount" -> ss.x.toJson, "amount" -> ss.x.toJson,
"currency" -> "USD".toJson, "currency" -> "USD".toJson,
"owner" -> "Alice".toJson, "owner" -> partyName.toJson,
).toJson, ).toJson,
).toJson ).toJson
} }
@ -860,7 +909,7 @@ abstract class AbstractWebsocketServiceIntegrationTest
postJsonRequest( postJsonRequest(
serviceUri.withPath(Uri.Path("/v1/create")), serviceUri.withPath(Uri.Path("/v1/create")),
initialPayload, initialPayload,
headersWithAuth, headers,
) )
) )
\/-((Vector((genesisCid, amt)), Vector())) <- readOne \/-((Vector((genesisCid, amt)), Vector())) <- readOne
@ -947,15 +996,17 @@ abstract class AbstractWebsocketServiceIntegrationTest
val dslSyntax = Consume.syntax[JsValue] val dslSyntax = Consume.syntax[JsValue]
import dslSyntax._ import dslSyntax._
import spray.json._ import spray.json._
val (alice, headers) = getUniquePartyAndAuthHeaders("Alice")
val jwt = jwtForParties(List(alice.unwrap), List(), testId)
def createIouCommand(currency: String): String = def createIouCommand(currency: String): String =
s"""{ s"""{
| "templateId": "Iou:Iou", | "templateId": "Iou:Iou",
| "payload": { | "payload": {
| "observers": [], | "observers": [],
| "issuer": "Alice", | "issuer": "$alice",
| "amount": "999.99", | "amount": "999.99",
| "currency": "$currency", | "currency": "$currency",
| "owner": "Alice" | "owner": "$alice"
| } | }
|}""".stripMargin |}""".stripMargin
def createIou(currency: String): Future[Assertion] = def createIou(currency: String): Future[Assertion] =
@ -963,7 +1014,7 @@ abstract class AbstractWebsocketServiceIntegrationTest
.postJsonStringRequest( .postJsonStringRequest(
uri.withPath(Uri.Path("/v1/create")), uri.withPath(Uri.Path("/v1/create")),
createIouCommand(currency), createIouCommand(currency),
headersWithAuth, headers,
) )
.map(_._1 shouldBe a[StatusCodes.Success]) .map(_._1 shouldBe a[StatusCodes.Success])
def contractsQuery(currency: String): String = def contractsQuery(currency: String): String =