diff --git a/ledger-service/http-json-oracle/BUILD.bazel b/ledger-service/http-json-oracle/BUILD.bazel index 87a30403b6..39729b1a76 100644 --- a/ledger-service/http-json-oracle/BUILD.bazel +++ b/ledger-service/http-json-oracle/BUILD.bazel @@ -51,6 +51,7 @@ da_scala_test( "//daml-lf/transaction", "//language-support/scala/bindings-akka", "//ledger-api/rs-grpc-bridge", + "//ledger-api/testing-utils", "//ledger-service/db-backend", "//ledger-service/http-json:http-json-ee", "//ledger-service/http-json:integration-tests-lib-ee", @@ -58,6 +59,9 @@ da_scala_test( "//ledger-service/http-json-testing:ee", "//ledger-service/jwt", "//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/oracle-testing", "//libs-scala/ports", diff --git a/ledger-service/http-json-testing/BUILD.bazel b/ledger-service/http-json-testing/BUILD.bazel index 0d73e28250..e09947aeae 100644 --- a/ledger-service/http-json-testing/BUILD.bazel +++ b/ledger-service/http-json-testing/BUILD.bazel @@ -43,6 +43,7 @@ hj_scalacopts = lf_scalacopts + [ "//bazel_tools/runfiles:scala_runfiles", "//language-support/scala/bindings-akka", "//ledger-api/rs-grpc-bridge", + "//ledger-api/testing-utils", "@maven//:org_scalatest_scalatest_compatible", "//ledger-service/http-json:http-json-{}".format(edition), "//ledger-service/http-json-cli:{}".format(edition), @@ -53,13 +54,17 @@ hj_scalacopts = lf_scalacopts + [ "//ledger/ledger-api-auth", "//ledger/ledger-api-common", "//ledger/ledger-configuration", + "//ledger/ledger-resources", "//ledger/metrics", "//ledger/participant-integration-api", - "//ledger/sandbox-classic", + "//ledger/sandbox", + "//ledger/sandbox:sandbox-scala-tests-lib", "//ledger/sandbox-common", + "//ledger/sandbox-common:sandbox-common-scala-tests-lib", "//libs-scala/contextualized-logging", "//libs-scala/db-utils", "//libs-scala/ports", + "//libs-scala/resources", "@maven//:io_dropwizard_metrics_metrics_core", ], ) diff --git a/ledger-service/http-json-testing/src/main/scala/com/daml/http/HttpServiceTestFixture.scala b/ledger-service/http-json-testing/src/main/scala/com/daml/http/HttpServiceTestFixture.scala index b0c5df39a7..fbda5a0929 100644 --- a/ledger-service/http-json-testing/src/main/scala/com/daml/http/HttpServiceTestFixture.scala +++ b/ledger-service/http-json-testing/src/main/scala/com/daml/http/HttpServiceTestFixture.scala @@ -37,13 +37,14 @@ import com.daml.ledger.client.configuration.{ LedgerIdRequirement, } import com.daml.ledger.client.withoutledgerid.{LedgerClient => DamlLedgerClient} +import com.daml.ledger.resources.ResourceContext import com.daml.logging.LoggingContextOf import com.daml.metrics.Metrics import com.daml.platform.apiserver.SeedService.Seeding import com.daml.platform.common.LedgerIdMode -import com.daml.platform.sandbox -import com.daml.platform.sandbox.SandboxServer +import com.daml.platform.sandbox.SandboxBackend import com.daml.platform.sandbox.config.SandboxConfig +import com.daml.platform.sandboxnext.Runner import com.daml.platform.services.time.TimeProviderType import com.daml.ports.Port import com.typesafe.scalalogging.LazyLogging @@ -150,19 +151,35 @@ object HttpServiceTestFixture extends LazyLogging with Assertions with Inside { useTls: UseTls = UseTls.NoTls, authService: Option[AuthService] = None, )(testFn: (Port, DamlLedgerClient, LedgerId) => Future[A])(implicit - mat: Materializer, aesf: ExecutionSequencerFactory, ec: ExecutionContext, ): Future[A] = { val ledgerId = LedgerId(testName) val applicationId = ApplicationId(testName) + implicit val resourceContext: ResourceContext = ResourceContext(ec) val ledgerF = for { - ledger <- Future( - new SandboxServer(ledgerConfig(Port.Dynamic, dars, ledgerId, authService, useTls), mat) + urlResource <- Future( + 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) val clientF: Future[DamlLedgerClient] = for { @@ -179,11 +196,12 @@ object HttpServiceTestFixture extends LazyLogging with Assertions with Inside { a <- testFn(ledgerPort, client, ledgerId) } yield a - fa.onComplete { _ => - ledgerF.foreach(_._1.close()) + fa.transformWith { ta => + ledgerF + .flatMap(_._1.release()) + .fallbackTo(Future.unit) + .transform(_ => ta) } - - fa } private def ledgerConfig( @@ -192,12 +210,15 @@ object HttpServiceTestFixture extends LazyLogging with Assertions with Inside { ledgerId: LedgerId, authService: Option[AuthService], useTls: UseTls, + jdbcUrl: Option[String], ): SandboxConfig = - sandbox.DefaultConfig.copy( + SandboxConfig.defaultConfig.copy( port = ledgerPort, damlPackages = dars, + jdbcUrl = jdbcUrl, timeProviderType = Some(TimeProviderType.WallClock), tlsConfig = if (useTls) Some(serverTlsConfig) else None, + engineMode = SandboxConfig.EngineMode.Dev, ledgerIdMode = LedgerIdMode.Static(ledgerId), authService = authService, 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) - private val clientTlsConfig = TlsConfiguration(enabled = true, clientCrt, clientPem, caCrt) + final val serverTlsConfig = TlsConfiguration(enabled = true, serverCrt, serverPem, caCrt) + final val clientTlsConfig = TlsConfiguration(enabled = true, clientCrt, clientPem, caCrt) private val noTlsConfig = TlsConfiguration(enabled = false, None, None, None) def jwtForParties(actAs: List[String], readAs: List[String], ledgerId: String) = { diff --git a/ledger-service/http-json-testing/src/main/scala/com/daml/http/util/SandboxTestLedger.scala b/ledger-service/http-json-testing/src/main/scala/com/daml/http/util/SandboxTestLedger.scala new file mode 100644 index 0000000000..233db39383 --- /dev/null +++ b/ledger-service/http-json-testing/src/main/scala/com/daml/http/util/SandboxTestLedger.scala @@ -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 + } +} diff --git a/ledger-service/http-json/BUILD.bazel b/ledger-service/http-json/BUILD.bazel index 96ba57516f..f00657f4d6 100644 --- a/ledger-service/http-json/BUILD.bazel +++ b/ledger-service/http-json/BUILD.bazel @@ -12,7 +12,7 @@ load( ) load("//rules_daml:daml.bzl", "daml_compile") 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 + [ "-P:wartremover:traverser:org.wartremover.warts.NonUnitStatements", @@ -283,7 +283,12 @@ alias( "//daml-lf/transaction-test-lib", "//language-support/scala/bindings-akka", "//ledger-api/rs-grpc-bridge", + "//ledger-api/testing-utils", + "//ledger/ledger-resources", "//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-testing:{}".format(edition), "//ledger-service/db-backend", @@ -326,6 +331,10 @@ alias( "src/it/scala/**/*.scala", "src/it/edition/{}/**/*.scala".format(edition), ]), + args = [ + "-l", + "skip_scala_2_12", + ] if scala_major_version == "2.12" else [], data = [ ":Account.dar", "//docs:quickstart-model.dar", @@ -369,17 +378,23 @@ alias( "//daml-lf/transaction-test-lib", "//language-support/scala/bindings-akka", "//ledger-api/rs-grpc-bridge", + "//ledger-api/testing-utils", "//ledger-service/db-backend", + "//ledger/ledger-api-auth", + "//ledger/ledger-resources", "//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-testing:{}".format(edition), "//ledger-service/jwt", "//ledger-service/utils", - "//ledger/ledger-api-auth", "//libs-scala/contextualized-logging", "//libs-scala/db-utils", "//libs-scala/ports", "//libs-scala/postgresql-testing", + "//libs-scala/resources", "//libs-scala/scala-utils", "//runtime-components/non-repudiation", "//runtime-components/non-repudiation-postgresql", diff --git a/ledger-service/http-json/src/it/scala/http/AuthorizationTest.scala b/ledger-service/http-json/src/it/scala/http/AuthorizationTest.scala index 1976232734..544b0b7a47 100644 --- a/ledger-service/http-json/src/it/scala/http/AuthorizationTest.scala +++ b/ledger-service/http-json/src/it/scala/http/AuthorizationTest.scala @@ -8,11 +8,14 @@ import akka.actor.ActorSystem import akka.stream.Materializer import com.daml.bazeltools.BazelRunfiles.rlocation 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.Logging.instanceUUIDLogCtx +import com.daml.http.util.SandboxTestLedger import com.daml.jwt.domain.Jwt import com.daml.ledger.api.auth.{AuthServiceStatic, Claim, ClaimPublic, ClaimSet} import com.daml.ledger.api.domain.LedgerId +import com.daml.ledger.api.testing.utils.SuiteResourceManagementAroundAll import com.daml.ledger.client.withoutledgerid.{LedgerClient => DamlLedgerClient} import org.scalatest.BeforeAndAfterAll import org.scalatest.flatspec.AsyncFlatSpec @@ -22,12 +25,18 @@ import org.slf4j.LoggerFactory import scala.concurrent.{ExecutionContext, Future} 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")) .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 mat: Materializer = Materializer(asys) @@ -43,6 +52,9 @@ final class AuthorizationTest extends AsyncFlatSpec with BeforeAndAfterAll with private val accessTokenFile = Files.createTempFile("Extractor", "AuthSpec") + override def authService = mockedAuthService + override def packageFiles = List(dar) + override protected def afterAll(): Unit = { super.afterAll() try { @@ -55,11 +67,11 @@ final class AuthorizationTest extends AsyncFlatSpec with BeforeAndAfterAll with } } - protected def withLedger[A](testFn: DamlLedgerClient => LedgerId => Future[A]): Future[A] = - HttpServiceTestFixture - .withLedger[A](List(dar), testId, Option(publicToken), authService = mockedAuthService) { - case (_, client, ledgerId) => testFn(client)(ledgerId) - } + protected def withLedger[A](testFn: DamlLedgerClient => LedgerId => Future[A]): Future[A] = { + usingLedger[A](testId, Some(publicToken)) { case (_, client, ledgerId) => + testFn(client)(ledgerId) + } + } private def packageService(client: DamlLedgerClient): PackageService = new PackageService(HttpService.doLoad(client.packageClient)) diff --git a/ledger-service/http-json/src/it/scala/http/HttpServiceWithPostgresIntTest.scala b/ledger-service/http-json/src/it/scala/http/HttpServiceWithPostgresIntTest.scala index dc4dfb9eaf..229db66a1b 100644 --- a/ledger-service/http-json/src/it/scala/http/HttpServiceWithPostgresIntTest.scala +++ b/ledger-service/http-json/src/it/scala/http/HttpServiceWithPostgresIntTest.scala @@ -20,11 +20,14 @@ class HttpServiceWithPostgresIntTest override def wsConfig: Option[WebsocketConfig] = None "query persists all active contracts" in withHttpService { (uri, encoder, _, _) => + val (party, headers) = getUniquePartyAndAuthHeaders("Alice") + val searchDataSet = genSearchDataSet(party) searchExpectOk( searchDataSet, jsObject("""{"templateIds": ["Iou:Iou"], "query": {"currency": "EUR"}}"""), uri, encoder, + headers, ).flatMap { searchResult: List[domain.ActiveContract[JsValue]] => discard { searchResult should have size 2 } discard { searchResult.map(getField("currency")) shouldBe List.fill(2)(JsString("EUR")) } diff --git a/ledger-service/http-json/src/it/scala/http/WebsocketServiceOffsetTickIntTest.scala b/ledger-service/http-json/src/it/scala/http/WebsocketServiceOffsetTickIntTest.scala index 7b24a78ada..d9e67ff626 100644 --- a/ledger-service/http-json/src/it/scala/http/WebsocketServiceOffsetTickIntTest.scala +++ b/ledger-service/http-json/src/it/scala/http/WebsocketServiceOffsetTickIntTest.scala @@ -3,13 +3,14 @@ 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.typesafe.scalalogging.StrictLogging import org.scalatest._ import org.scalatest.freespec.AsyncFreeSpec import org.scalatest.matchers.should.Matchers import scalaz.\/- +import scalaz.syntax.tag._ 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 { (uri, _, _, _) => + val (party, headers) = getUniquePartyAndAuthHeaders("Alice") for { - _ <- initialIouCreate(uri) - + _ <- initialIouCreate(uri, party, headers) + jwt = jwtForParties(List(party.unwrap), List(), testId) msgs <- singleClientQueryStream(jwt, uri, """{"templateIds": ["Iou:Iou"]}""") .take(10) .runWith(collectResultsAsTextMessage) diff --git a/ledger-service/http-json/src/itlib/scala/http/AbstractHttpServiceIntegrationTest.scala b/ledger-service/http-json/src/itlib/scala/http/AbstractHttpServiceIntegrationTest.scala index 55e46d872a..c886e00650 100644 --- a/ledger-service/http-json/src/itlib/scala/http/AbstractHttpServiceIntegrationTest.scala +++ b/ledger-service/http-json/src/itlib/scala/http/AbstractHttpServiceIntegrationTest.scala @@ -22,17 +22,18 @@ import com.daml.http.json.SprayJson.{decode, decode1, objectField} import com.daml.http.json._ import com.daml.http.util.ClientUtil.{boxedRecord, uniqueId} 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.domain.{DecodedJwt, Jwt} import com.daml.ledger.api.refinements.{ApiTypes => lar} import com.daml.ledger.api.v1.{value => v} import com.daml.ledger.client.withoutledgerid.{LedgerClient => DamlLedgerClient} 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.http.util.Logging.instanceUUIDLogCtx import com.daml.ledger.api.domain.LedgerId +import com.daml.ledger.api.testing.utils.SuiteResourceManagementAroundAll import com.typesafe.scalalogging.StrictLogging import org.scalatest._ import org.scalatest.freespec.AsyncFreeSpec @@ -51,13 +52,16 @@ import scala.concurrent.{ExecutionContext, Future} import scala.util.{Success, Try} import com.daml.ledger.api.{domain => LedgerApiDomain} import com.daml.ports.Port +import org.scalatest.Tag + +object SkipScala212 extends Tag("skip_scala_2_12") 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 dar3 = requiredResource(ModelTestDar.path) + private[http] val dar3 = requiredResource(SemanticTestDar.path) def sha256(source: Source[ByteString, Any])(implicit mat: Materializer): Try[String] = Try { import java.security.MessageDigest @@ -77,7 +81,10 @@ object AbstractHttpServiceIntegrationTestFuns { } @SuppressWarnings(Array("org.wartremover.warts.NonUnitStatements")) -trait AbstractHttpServiceIntegrationTestFuns extends StrictLogging { +trait AbstractHttpServiceIntegrationTestFuns + extends StrictLogging + with SandboxTestLedger + with SuiteResourceManagementAroundAll { this: AsyncTestSuite with Matchers with Inside => import AbstractHttpServiceIntegrationTestFuns._ import json.JsonProtocol._ @@ -116,36 +123,40 @@ trait AbstractHttpServiceIntegrationTestFuns extends StrictLogging { import tag.@@ // used for subtyping to make `AHS ec` beat executionContext 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]( testFn: (Uri, DomainJsonEncoder, DomainJsonDecoder, DamlLedgerClient, LedgerId) => Future[A] - ): Future[A] = - HttpServiceTestFixture.withLedger[A](List(dar1, dar2), testId, None, useTls) { - case (ledgerPort, _, ledgerId) => - HttpServiceTestFixture.withHttpService[A]( - testId, - ledgerPort, - jdbcConfig, - staticContentConfig, - useTls = useTls, - wsConfig = wsConfig, - )(testFn(_, _, _, _, ledgerId)) - } + ): Future[A] = usingLedger[A](testId) { case (ledgerPort, _, ledgerId) => + HttpServiceTestFixture.withHttpService[A]( + testId, + ledgerPort, + jdbcConfig, + staticContentConfig, + useTls = useTls, + wsConfig = wsConfig, + )(testFn(_, _, _, _, ledgerId)) + } protected def withHttpServiceAndClient[A](maxInboundMessageSize: Int)( testFn: (Uri, DomainJsonEncoder, DomainJsonDecoder, DamlLedgerClient, LedgerId) => Future[A] - ): Future[A] = - HttpServiceTestFixture.withLedger[A](List(dar1, dar2), testId, None, useTls) { - case (ledgerPort, _, ledgerId) => - HttpServiceTestFixture.withHttpService[A]( - testId, - ledgerPort, - jdbcConfig, - staticContentConfig, - useTls = useTls, - wsConfig = wsConfig, - maxInboundMessageSize = maxInboundMessageSize, - )(testFn(_, _, _, _, ledgerId)) - } + ): Future[A] = usingLedger[A](testId) { case (ledgerPort, _, ledgerId) => + HttpServiceTestFixture.withHttpService[A]( + testId, + ledgerPort, + jdbcConfig, + staticContentConfig, + useTls = useTls, + wsConfig = wsConfig, + maxInboundMessageSize = maxInboundMessageSize, + )(testFn(_, _, _, _, ledgerId)) + } protected def withHttpService[A]( f: (Uri, DomainJsonEncoder, DomainJsonDecoder, LedgerId) => Future[A] @@ -165,12 +176,12 @@ trait AbstractHttpServiceIntegrationTestFuns extends StrictLogging { )((uri, encoder, decoder, _) => f(uri, encoder, decoder)) 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) } 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) @@ -224,8 +235,9 @@ trait AbstractHttpServiceIntegrationTestFuns extends StrictLogging { create: domain.CreateCommand[v.Record, OptionalPkg], encoder: DomainJsonEncoder, uri: Uri, + headers: List[HttpHeader] = headersWithAuth, ): Future[Assertion] = - postContractsLookup(contractLocator, uri).flatMap { case (status, output) => + postContractsLookup(contractLocator, uri, headers).flatMap { case (status, output) => status shouldBe StatusCodes.OK assertStatus(output, StatusCodes.OK) val result = getResult(output) @@ -283,15 +295,16 @@ trait AbstractHttpServiceIntegrationTestFuns extends StrictLogging { } protected def iouCreateCommand( + partyName: String, amount: String = "999.9900000000", currency: String = "USD", ): domain.CreateCommand[v.Record, OptionalPkg] = { val templateId: OptionalPkg = domain.TemplateId(None, "Iou", "Iou") - val alice = Ref.Party assertFromString "Alice" + val party = Ref.Party assertFromString partyName val arg = argToApi(iouVA)( ShRecord( - issuer = alice, - owner = alice, + issuer = party, + owner = party, currency = currency, amount = LfNumeric assertFromString amount, observers = Vector.empty, @@ -314,15 +327,16 @@ trait AbstractHttpServiceIntegrationTestFuns extends StrictLogging { } protected def iouCreateAndExerciseTransferCommand( + partyName: String, amount: String = "999.9900000000", currency: String = "USD", ): domain.CreateAndExerciseCommand[v.Record, v.Value, OptionalPkg] = { val templateId: OptionalPkg = domain.TemplateId(None, "Iou", "Iou") - val alice = Ref.Party assertFromString "Alice" + val party = Ref.Party assertFromString partyName val payload = argToApi(iouVA)( ShRecord( - issuer = alice, - owner = alice, + issuer = party, + owner = party, currency = currency, amount = LfNumeric assertFromString amount, observers = Vector.empty, @@ -412,10 +426,11 @@ trait AbstractHttpServiceIntegrationTestFuns extends StrictLogging { protected def postContractsLookup( cmd: domain.ContractLocator[JsValue], uri: Uri, + headers: List[HttpHeader] = headersWithAuth, ): Future[(StatusCode, JsValue)] = for { 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 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)] = { - val payload = TestUtil.readFile("it/iouCreateCommand.json") + protected def initialIouCreate( + 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( serviceUri.withPath(Uri.Path("/v1/create")), payload, - headersWithAuth, + headers, ) } protected def initialAccountCreate( serviceUri: Uri, encoder: DomainJsonEncoder, + owner: domain.Party, + headers: List[HttpHeader], ): Future[(StatusCode, JsValue)] = { - val command = accountCreateCommand(domain.Party("Alice"), "abc123") - postCreateCommand(command, encoder, serviceUri) + val command = accountCreateCommand(owner, "abc123") + postCreateCommand(command, encoder, serviceUri, headers) } protected def jsObject(s: String): JsObject = { @@ -617,25 +651,31 @@ abstract class AbstractHttpServiceIntegrationTest override final def useTls = UseTls.NoTls "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 } - } - 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( - iouCreateCommand(amount = "111.11", currency = "EUR"), - iouCreateCommand(amount = "222.22", currency = "EUR"), - iouCreateCommand(amount = "333.33", currency = "GBP"), - iouCreateCommand(amount = "444.44", currency = "BTC"), + iouCreateCommand(amount = "111.11", currency = "EUR", partyName = partyName), + iouCreateCommand(amount = "222.22", currency = "EUR", partyName = partyName), + iouCreateCommand(amount = "333.33", currency = "GBP", partyName = partyName), + iouCreateCommand(amount = "444.44", currency = "BTC", partyName = partyName), ) + } "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) - getRequest(uri = uri.withPath(Uri.Path("/v1/query"))) + getRequest(uri = uri.withPath(Uri.Path("/v1/query")), headers) .flatMap { case (status, output) => status shouldBe StatusCodes.OK assertStatus(output, StatusCodes.OK) @@ -649,54 +689,59 @@ abstract class AbstractHttpServiceIntegrationTest } "multi-party query GET" in withHttpService { (uri, encoder, _, _) => + val (alice, aliceHeaders) = getUniquePartyAndAuthHeaders("Alice") + val (bob, bobHeaders) = getUniquePartyAndAuthHeaders("Alice") for { _ <- postCreateCommand( - accountCreateCommand(owner = domain.Party("Alice"), number = "42"), + accountCreateCommand(owner = alice, number = "42"), encoder, uri, + headers = aliceHeaders, ).map(r => r._1 shouldBe StatusCodes.OK) _ <- postCreateCommand( - accountCreateCommand(owner = domain.Party("Bob"), number = "23"), + accountCreateCommand(owner = bob, number = "23"), encoder, uri, - headers = headersWithPartyAuth(List("Bob")), + headers = bobHeaders, ).map(r => r._1 shouldBe StatusCodes.OK) - _ <- searchAllExpectOk(uri, headersWithPartyAuth(List("Alice"))).map(cs => - cs should have size 1 - ) - _ <- searchAllExpectOk(uri, headersWithPartyAuth(List("Bob"))).map(cs => - cs should have size 1 - ) - _ <- searchAllExpectOk(uri, headersWithPartyAuth(List("Alice", "Bob"))).map(cs => + _ <- searchAllExpectOk(uri, aliceHeaders).map(cs => 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 => cs should have size 2 ) } yield succeed } "query POST with empty query" in withHttpService { (uri, encoder, _, _) => + val (alice, headers) = getUniquePartyAndAuthHeaders("Alice") + val searchDataSet = genSearchDataSet(alice) searchExpectOk( searchDataSet, jsObject("""{"templateIds": ["Iou:Iou"]}"""), uri, encoder, + headers, ).map { acl: List[domain.ActiveContract[JsValue]] => acl.size shouldBe searchDataSet.size } } "multi-party query POST with empty query" in withHttpService { (uri, encoder, _, _) => + val (alice, aliceHeaders) = getUniquePartyAndAuthHeaders("Alice") + val (bob, bobHeaders) = getUniquePartyAndAuthHeaders("Alice") for { aliceAccountResp <- postCreateCommand( - accountCreateCommand(owner = domain.Party("Alice"), number = "42"), + accountCreateCommand(owner = alice, number = "42"), encoder, uri, + aliceHeaders, ) _ = aliceAccountResp._1 shouldBe StatusCodes.OK bobAccountResp <- postCreateCommand( - accountCreateCommand(owner = domain.Party("Bob"), number = "23"), + accountCreateCommand(owner = bob, number = "23"), encoder, uri, - headers = headersWithPartyAuth(List("Bob")), + bobHeaders, ) _ = bobAccountResp._1 shouldBe StatusCodes.OK _ <- searchExpectOk( @@ -704,7 +749,7 @@ abstract class AbstractHttpServiceIntegrationTest jsObject("""{"templateIds": ["Account:Account"]}"""), uri, encoder, - headers = headersWithPartyAuth(List("Alice")), + aliceHeaders, ) .map(acl => acl.size shouldBe 1) _ <- searchExpectOk( @@ -712,7 +757,7 @@ abstract class AbstractHttpServiceIntegrationTest jsObject("""{"templateIds": ["Account:Account"]}"""), uri, encoder, - headers = headersWithPartyAuth(List("Bob")), + bobHeaders, ) .map(acl => acl.size shouldBe 1) _ <- searchExpectOk( @@ -720,7 +765,7 @@ abstract class AbstractHttpServiceIntegrationTest jsObject("""{"templateIds": ["Account:Account"]}"""), uri, encoder, - headers = headersWithPartyAuth(List("Alice", "Bob")), + headers = headersWithPartyAuth(List(alice.unwrap, bob.unwrap)), ) .map(acl => acl.size shouldBe 2) } yield { @@ -729,11 +774,14 @@ abstract class AbstractHttpServiceIntegrationTest } "query with query, one field" in withHttpService { (uri, encoder, _, _) => + val (alice, headers) = getUniquePartyAndAuthHeaders("Alice") + val searchDataSet = genSearchDataSet(alice) searchExpectOk( searchDataSet, jsObject("""{"templateIds": ["Iou:Iou"], "query": {"currency": "EUR"}}"""), uri, encoder, + headers, ).map { acl: List[domain.ActiveContract[JsValue]] => acl.size shouldBe 2 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"}}""" ) - search(List(), query, uri, encoder).map { response => - inside(response) { case domain.OkResponse(acl, warnings, StatusCodes.OK) => - acl.size shouldBe 0 - warnings shouldBe Some( - domain.UnknownTemplateIds(List(domain.TemplateId(None, "UnknownModule", "UnknownEntity"))) - ) - } + search(List(), query, uri, encoder, headersWithPartyAuth(List("UnknownParty"))).map { + response => + inside(response) { case domain.OkResponse(acl, warnings, StatusCodes.OK) => + acl.size shouldBe 0 + warnings shouldBe Some( + domain.UnknownTemplateIds( + List(domain.TemplateId(None, "UnknownModule", "UnknownEntity")) + ) + ) + } } } "query returns unknown Template IDs as warnings and error" in withHttpService { (uri, encoder, _, _) => + val (alice, headers) = getUniquePartyAndAuthHeaders("Alice") search( - searchDataSet, + genSearchDataSet(alice), jsObject("""{"templateIds": ["AAA:BBB", "XXX:YYY"]}"""), uri, encoder, + headers, ).map { response => inside(response) { case domain.ErrorResponse(errors, warnings, StatusCodes.BadRequest) => errors shouldBe List(ErrorMessages.cannotResolveAnyTemplateId) @@ -780,7 +833,9 @@ abstract class AbstractHttpServiceIntegrationTest (uri, encoder, _, _) => 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.map(_._1) shouldBe List.fill(searchDataSet.size)(StatusCodes.OK) @@ -791,8 +846,8 @@ abstract class AbstractHttpServiceIntegrationTest val queryAmountAsNumber = queryAmountAs("111.11") List( - postJsonRequest(uri.withPath(Uri.Path("/v1/query")), queryAmountAsString), - postJsonRequest(uri.withPath(Uri.Path("/v1/query")), queryAmountAsNumber), + postJsonRequest(uri.withPath(Uri.Path("/v1/query")), queryAmountAsString, headers), + postJsonRequest(uri.withPath(Uri.Path("/v1/query")), queryAmountAsNumber, headers), ).sequence.flatMap { rs: List[(StatusCode, JsValue)] => rs.map(_._1) shouldBe List.fill(2)(StatusCodes.OK) inside(rs.map(_._2)) { case List(jsVal1, jsVal2) => @@ -826,13 +881,18 @@ abstract class AbstractHttpServiceIntegrationTest ).foreach { case (testLbl, testCurrency) => s"query record contains handles '$testLbl' strings properly" in withHttpService { (uri, encoder, _, _) => + val (alice, headers) = getUniquePartyAndAuthHeaders("Alice") searchExpectOk( - searchDataSet :+ iouCreateCommand(currency = testCurrency), + genSearchDataSet(alice) :+ iouCreateCommand( + currency = testCurrency, + partyName = alice.unwrap, + ), jsObject( s"""{"templateIds": ["Iou:Iou"], "query": {"currency": ${testCurrency.toJson}}}""" ), uri, encoder, + headers, ).map(inside(_) { case Seq(domain.ActiveContract(_, _, _, JsObject(fields), _, _, _)) => fields.get("currency") should ===(Some(JsString(testCurrency))) }) @@ -840,6 +900,8 @@ abstract class AbstractHttpServiceIntegrationTest } "query with query, two fields" in withHttpService { (uri, encoder, _, _) => + val (alice, headers) = getUniquePartyAndAuthHeaders("Alice") + val searchDataSet = genSearchDataSet(alice) searchExpectOk( searchDataSet, jsObject( @@ -847,6 +909,7 @@ abstract class AbstractHttpServiceIntegrationTest ), uri, encoder, + headers, ).map { acl: List[domain.ActiveContract[JsValue]] => acl.size shouldBe 1 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, _, _) => + val (alice, headers) = getUniquePartyAndAuthHeaders("Alice") + val searchDataSet = genSearchDataSet(alice) searchExpectOk( searchDataSet, jsObject( @@ -862,6 +927,7 @@ abstract class AbstractHttpServiceIntegrationTest ), uri, encoder, + headers, ).map { acl: List[domain.ActiveContract[JsValue]] => acl.size shouldBe 0 } @@ -894,9 +960,10 @@ abstract class AbstractHttpServiceIntegrationTest } "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 assertStatus(output, StatusCodes.OK) val activeContract = getResult(output) @@ -906,8 +973,8 @@ abstract class AbstractHttpServiceIntegrationTest "create IOU should fail if authorization header is missing" 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)) 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, _, _) => - 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)) postJsonRequest( uri.withPath(Uri.Path("/v1/create")), input, - headers = headersWithPartyAuth(actAs = List("Alice"), readAs = List("Bob")), + headers = headersWithPartyAuth(actAs = List(alice.unwrap), readAs = List("Bob")), ).flatMap { case (status, output) => status shouldBe StatusCodes.OK assertStatus(output, StatusCodes.OK) @@ -938,11 +1006,12 @@ abstract class AbstractHttpServiceIntegrationTest "create IOU with unsupported templateId should return proper error" in withHttpService { (uri, encoder, _, _) => + val (alice, headers) = getUniquePartyAndAuthHeaders("Alice") 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)) - postJsonRequest(uri.withPath(Uri.Path("/v1/create")), input).flatMap { + postJsonRequest(uri.withPath(Uri.Path("/v1/create")), input, headers).flatMap { case (status, output) => status shouldBe StatusCodes.BadRequest assertStatus(output, StatusCodes.BadRequest) @@ -955,8 +1024,9 @@ abstract class AbstractHttpServiceIntegrationTest } "exercise IOU_Transfer" in withHttpService { (uri, encoder, decoder, ledgerId) => - val create: domain.CreateCommand[v.Record, OptionalPkg] = iouCreateCommand() - postCreateCommand(create, encoder, uri) + val (alice, headers) = getUniquePartyAndAuthHeaders("Alice") + val create: domain.CreateCommand[v.Record, OptionalPkg] = iouCreateCommand(alice.unwrap) + postCreateCommand(create, encoder, uri, headers) .flatMap { case (createStatus, createOutput) => createStatus shouldBe StatusCodes.OK assertStatus(createOutput, StatusCodes.OK) @@ -966,7 +1036,7 @@ abstract class AbstractHttpServiceIntegrationTest iouExerciseTransferCommand(contractId) 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) => exerciseStatus shouldBe StatusCodes.OK assertStatus(exerciseOutput, StatusCodes.OK) @@ -977,18 +1047,20 @@ abstract class AbstractHttpServiceIntegrationTest decoder, uri, ledgerId, + headers, ) } }: Future[Assertion] } "create-and-exercise IOU_Transfer" in withHttpService { (uri, encoder, _, _) => + val (alice, headers) = getUniquePartyAndAuthHeaders("Alice") val cmd: domain.CreateAndExerciseCommand[v.Record, v.Value, OptionalPkg] = - iouCreateAndExerciseTransferCommand() + iouCreateAndExerciseTransferCommand(alice.unwrap) 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) => status shouldBe StatusCodes.OK inside( @@ -1019,6 +1091,7 @@ abstract class AbstractHttpServiceIntegrationTest decoder: DomainJsonDecoder, uri: Uri, ledgerId: LedgerId, + headers: List[HttpHeader], ): Future[Assertion] = { inside(SprayJson.decode[domain.ExerciseResponse[JsValue]](exerciseResponse)) { case \/-(domain.ExerciseResponse(JsString(exerciseResult), List(contract1, contract2))) => @@ -1038,7 +1111,7 @@ abstract class AbstractHttpServiceIntegrationTest Some(domain.TemplateId(None, "Iou", "IouTransfer")), domain.ContractId(exerciseResult), ) - postContractsLookup(newContractLocator, uri).flatMap { case (status, output) => + postContractsLookup(newContractLocator, uri, headers).flatMap { case (status, output) => status shouldBe StatusCodes.OK assertStatus(output, StatusCodes.OK) 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 { (uri, encoder, _, _) => - val contractId = lar.ContractId("#NonExistentContractId") + val contractIdString = "0" * 66 + val contractId = lar.ContractId(contractIdString) val exerciseJson: JsValue = encodeExercise(encoder)(iouExerciseTransferCommand(contractId)) postJsonRequest(uri.withPath(Uri.Path("/v1/exercise")), exerciseJson) .flatMap { case (status, output) => status shouldBe StatusCodes.InternalServerError assertStatus(output, StatusCodes.InternalServerError) 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] } "exercise Archive" in withHttpService { (uri, encoder, _, _) => - val create: domain.CreateCommand[v.Record, OptionalPkg] = iouCreateCommand() - postCreateCommand(create, encoder, uri) + val (alice, headers) = getUniquePartyAndAuthHeaders("Alice") + val create: domain.CreateCommand[v.Record, OptionalPkg] = iouCreateCommand(alice.unwrap) + postCreateCommand(create, encoder, uri, headers) .flatMap { case (createStatus, createOutput) => createStatus shouldBe StatusCodes.OK assertStatus(createOutput, StatusCodes.OK) @@ -1074,7 +1149,7 @@ abstract class AbstractHttpServiceIntegrationTest val exercise = archiveCommand(reference) 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) => exerciseStatus shouldBe StatusCodes.OK assertStatus(exerciseOutput, StatusCodes.OK) @@ -1085,17 +1160,7 @@ abstract class AbstractHttpServiceIntegrationTest } "should support multi-party command submissions" in withHttpService { (uri, encoder, _, _) => - val newDar = AbstractHttpServiceIntegrationTestFuns.dar3 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 cid <- postCreateCommand( multiPartyCreateCommand(List("Alice", "Bob"), ""), @@ -1174,7 +1239,7 @@ abstract class AbstractHttpServiceIntegrationTest import json.JsonProtocol._ 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] val x: F[Assertion] = for { @@ -1214,7 +1279,7 @@ abstract class AbstractHttpServiceIntegrationTest "parties endpoint should return all known parties" in withHttpServiceAndClient { (uri, _, _, client, _) => import scalaz.std.vector._ - val partyIds = Vector("Alice", "Bob", "Charlie", "Dave") + val partyIds = Vector("P1", "P2", "P3", "P4") val partyManagement = client.partyManagementClient partyIds @@ -1230,8 +1295,8 @@ abstract class AbstractHttpServiceIntegrationTest response.status shouldBe StatusCodes.OK response.warnings shouldBe empty val actualIds: Set[domain.Party] = response.result.view.map(_.identifier).toSet - actualIds shouldBe domain.Party.subst(partyIds.toSet) - response.result.toSet shouldBe + actualIds should contain allElementsOf domain.Party.subst(partyIds.toSet) + response.result.toSet should contain allElementsOf allocatedParties.toSet.map(domain.PartyDetails.fromLedgerApi) } } @@ -1242,9 +1307,9 @@ abstract class AbstractHttpServiceIntegrationTest (uri, _, _, client, _) => import scalaz.std.vector._ - val charlie = domain.Party("Charlie") - val knownParties = domain.Party.subst(Vector("Alice", "Bob", "Dave")) :+ charlie - val erin = domain.Party("Erin") + val charlie = getUniqueParty("Charlie") + val knownParties = Vector(getUniqueParty("Alice"), getUniqueParty("Bob")) :+ charlie + val erin = getUniqueParty("Erin") val requestedPartyIds: Vector[domain.Party] = knownParties.filterNot(_ == charlie) :+ erin 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 { (uri, _, _, _, _) => val requestedPartyIds: Vector[domain.Party] = - domain.Party.subst(Vector("Alice", "Bob", "Dave")) - + Vector(getUniqueParty("Alice"), getUniqueParty("Bob")) postJsonRequest( uri = uri.withPath(Uri.Path("/v1/parties")), JsArray(requestedPartyIds.map(x => JsString(x.unwrap))), @@ -1402,26 +1466,27 @@ abstract class AbstractHttpServiceIntegrationTest } "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 assertStatus(output, StatusCodes.OK) val contractId: ContractId = getContractId(getResult(output)) val locator = domain.EnrichedContractId(None, contractId) - lookupContractAndAssert(locator)(contractId, command, encoder, uri) + lookupContractAndAssert(locator)(contractId, command, encoder, uri, headers) }: Future[Assertion] } "fetch returns {status:200, result:null} when contract is not found" in withHttpService { (uri, _, _, _) => - val owner = domain.Party("Alice") + val (alice, headers) = getUniquePartyAndAuthHeaders("Alice") val accountNumber = "abc123" val locator = domain.EnrichedContractKey( 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) => status shouldBe StatusCodes.OK assertStatus(output, StatusCodes.OK) @@ -1433,32 +1498,32 @@ abstract class AbstractHttpServiceIntegrationTest } "fetch by key" in withHttpService { (uri, encoder, _, _) => - val owner = domain.Party("Alice") + val (alice, headers) = getUniquePartyAndAuthHeaders("Alice") val accountNumber = "abc123" 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 assertStatus(output, StatusCodes.OK) val contractId: ContractId = getContractId(getResult(output)) val locator = domain.EnrichedContractKey( 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] } "commands/exercise Archive by key" in withHttpService { (uri, encoder, _, _) => - val owner = domain.Party("Alice") + val (alice, headers) = getUniquePartyAndAuthHeaders("Alice") val accountNumber = "abc123" val create: domain.CreateCommand[v.Record, OptionalPkg] = - accountCreateCommand(owner, accountNumber) + accountCreateCommand(alice, accountNumber) val keyRecord = v.Record( 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)))), ) ) @@ -1470,11 +1535,11 @@ abstract class AbstractHttpServiceIntegrationTest archiveCommand(locator) 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 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) => exerciseStatus shouldBe 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 { (uri, _, _, _) => + val (alice, headers) = getUniquePartyAndAuthHeaders("Alice") testFetchByCompositeKey( uri, - jsObject("""{ + jsObject(s"""{ "templateId": "Account:KeyedByVariantAndRecord", "key": [ - "Alice", + "$alice", {"tag": "Bar", "value": 42}, {"baz": "another baz value"} ] }"""), + alice, + headers, ) } "fetch by key containing variant and record, encoded as record with string num" in withHttpService { (uri, _, _, _) => + val (alice, headers) = getUniquePartyAndAuthHeaders("Alice") testFetchByCompositeKey( uri, - jsObject("""{ + jsObject(s"""{ "templateId": "Account:KeyedByVariantAndRecord", "key": { - "_1": "Alice", + "_1": "$alice", "_2": {"tag": "Bar", "value": "42"}, "_3": {"baz": "another baz value"} } }"""), + alice, + headers, ) } - private def testFetchByCompositeKey(uri: Uri, request: JsObject) = { - val createCommand = jsObject("""{ + private def testFetchByCompositeKey( + uri: Uri, + request: JsObject, + party: domain.Party, + headers: List[HttpHeader], + ) = { + val createCommand = jsObject(s"""{ "templateId": "Account:KeyedByVariantAndRecord", "payload": { "name": "ABC DEF", - "party": "Alice", + "party": "${party.unwrap}", "age": 123, "fooVariant": {"tag": "Bar", "value": 42}, "bazRecord": {"baz": "another baz value"} } }""") - - postJsonRequest(uri.withPath(Uri.Path("/v1/create")), createCommand).flatMap { + postJsonRequest(uri.withPath(Uri.Path("/v1/create")), createCommand, headers).flatMap { case (status, output) => status shouldBe StatusCodes.OK assertStatus(output, StatusCodes.OK) 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) => status shouldBe StatusCodes.OK assertStatus(output, StatusCodes.OK) @@ -1540,12 +1615,12 @@ abstract class AbstractHttpServiceIntegrationTest } "query by a variant field" in withHttpService { (uri, encoder, _, _) => - val owner = domain.Party("Alice") + val (alice, headers) = getUniquePartyAndAuthHeaders("Alice") val accountNumber = "abc123" val now = TimestampConversion.instantToMicros(Instant.now) val nowStr = TimestampConversion.microsToInstant(now).toString val command: domain.CreateCommand[v.Record, OptionalPkg] = - accountCreateCommand(owner, accountNumber, now) + accountCreateCommand(alice, accountNumber, now) val packageId: Ref.PackageId = MetadataReader .templateByName(metadata2)(Ref.QualifiedName.assertFromString("Account:Account")) @@ -1553,7 +1628,7 @@ abstract class AbstractHttpServiceIntegrationTest .map(_._1) .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 assertStatus(output, StatusCodes.OK) 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) => searchStatus shouldBe 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] { (ledgerPort: Port, _, _) => + val (alice, headers) = getUniquePartyAndAuthHeaders("Alice") for { _ <- 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) } } _ <- withHttpServiceOnly(ledgerPort) { (uri, _, _) => - getRequest(uri = uri.withPath(Uri.Path("/v1/query"))) + getRequest(uri = uri.withPath(Uri.Path("/v1/query")), headers) .flatMap { case (status, output) => status shouldBe StatusCodes.OK assertStatus(output, StatusCodes.OK) @@ -1654,13 +1731,15 @@ abstract class AbstractHttpServiceIntegrationTest } 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 ) { (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 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] = domain.CreateAndExerciseCommand( @@ -1700,6 +1779,7 @@ abstract class AbstractHttpServiceIntegrationTest def queryN(n: Long): Future[Assertion] = postJsonRequest( uri.withPath(Uri.Path("/v1/query")), jsObject("""{"templateIds": ["Account:Account"]}"""), + headers, ).flatMap { case (status, output) => status shouldBe StatusCodes.OK assertStatus(output, StatusCodes.OK) @@ -1709,7 +1789,11 @@ abstract class AbstractHttpServiceIntegrationTest } 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 shouldBe StatusCodes.OK @@ -1723,6 +1807,7 @@ abstract class AbstractHttpServiceIntegrationTest status <- postJsonRequest( uri.withPath(Uri.Path("/v1/create-and-exercise")), encode(archiveCmd(created)), + headers, ).map(_._1) _ = { status shouldBe StatusCodes.OK diff --git a/ledger-service/http-json/src/itlib/scala/http/AbstractNonRepudiationTest.scala b/ledger-service/http-json/src/itlib/scala/http/AbstractNonRepudiationTest.scala index ffff1dce4c..8a64f5210f 100644 --- a/ledger-service/http-json/src/itlib/scala/http/AbstractNonRepudiationTest.scala +++ b/ledger-service/http-json/src/itlib/scala/http/AbstractNonRepudiationTest.scala @@ -9,7 +9,6 @@ import java.time.Clock import akka.http.scaladsl.model.Uri import com.daml.doobie.logging.Slf4jLogHandler -import com.daml.http.AbstractHttpServiceIntegrationTestFuns.{dar1, dar2} import com.daml.http.dbbackend.JdbcConfig import com.daml.http.json.{DomainJsonDecoder, DomainJsonEncoder} import com.daml.ledger.api.v1.command_service.CommandServiceGrpc @@ -85,7 +84,7 @@ abstract class AbstractNonRepudiationTest } private def withParticipant[A] = - HttpServiceTestFixture.withLedger[A](List(dar1, dar2), testId, None, useTls) _ + usingLedger[A](testId) _ private def withJsonApi[A](participantPort: Port) = HttpServiceTestFixture.withHttpService[A]( diff --git a/ledger-service/http-json/src/itlib/scala/http/AbstractWebsocketServiceIntegrationTest.scala b/ledger-service/http-json/src/itlib/scala/http/AbstractWebsocketServiceIntegrationTest.scala index 039e1a8d4c..31381bc333 100644 --- a/ledger-service/http-json/src/itlib/scala/http/AbstractWebsocketServiceIntegrationTest.scala +++ b/ledger-service/http-json/src/itlib/scala/http/AbstractWebsocketServiceIntegrationTest.scala @@ -6,7 +6,7 @@ package com.daml.http import akka.NotUsed import akka.http.scaladsl.Http 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.scaladsl.{Keep, Sink, Source} 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 { (uri, _, _, _) => + val (alice, headers) = getUniquePartyAndAuthHeaders("Alice") for { - _ <- initialIouCreate(uri) - + _ <- initialIouCreate(uri, alice, headers) + jwt = jwtForParties(List(alice.unwrap), List(), testId) clientMsg <- singleClientQueryStream( jwt, uri, @@ -169,7 +170,7 @@ abstract class AbstractWebsocketServiceIntegrationTest ).take(2) .runWith(collectResultsAsTextMessage) } yield inside(clientMsg) { case result +: heartbeats => - result should include(""""issuer":"Alice"""") + result should include(s""""issuer":"$alice"""") result should include(""""amount":"999.99"""") Inspectors.forAll(heartbeats)(assertHeartbeat) } @@ -177,14 +178,16 @@ abstract class AbstractWebsocketServiceIntegrationTest "fetch endpoint should publish transactions when command create is completed" in withHttpService { (uri, encoder, _, _) => + val (alice, headers) = getUniquePartyAndAuthHeaders("Alice") 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) .take(2) .runWith(collectResultsAsTextMessage) } yield inside(clientMsg) { case result +: heartbeats => - result should include(""""owner":"Alice"""") + result should include(s""""owner":"$alice"""") result should include(""""number":"abc123"""") result should not include (""""offset":"""") Inspectors.forAll(heartbeats)(assertHeartbeat) @@ -192,35 +195,37 @@ abstract class AbstractWebsocketServiceIntegrationTest } "query endpoint should warn on unknown template IDs" in withHttpService { (uri, _, _, _) => + val (alice, headers) = getUniquePartyAndAuthHeaders("Alice") for { - _ <- initialIouCreate(uri) + _ <- initialIouCreate(uri, alice, headers) clientMsg <- singleClientQueryStream( - jwt, + jwtForParties(List(alice.unwrap), List(), testId), uri, """{"templateIds": ["Iou:Iou", "Unknown:Template"]}""", ).take(3) .runWith(collectResultsAsTextMessage) } yield inside(clientMsg) { case warning +: result +: heartbeats => warning should include("\"warnings\":{\"unknownTemplateIds\":[\"Unk") - result should include("\"issuer\":\"Alice\"") + result should include(s""""issuer":"$alice"""") Inspectors.forAll(heartbeats)(assertHeartbeat) } } "fetch endpoint should warn on unknown template IDs" in withHttpService { (uri, encoder, _, _) => + val (alice, headers) = getUniquePartyAndAuthHeaders("Alice") for { - _ <- initialAccountCreate(uri, encoder) + _ <- initialAccountCreate(uri, encoder, alice, headers) clientMsg <- singleClientFetchStream( - jwt, + jwtForParties(List(alice.unwrap), List(), testId), 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) .runWith(collectResultsAsTextMessage) } yield inside(clientMsg) { case warning +: result +: heartbeats => warning should include("""{"warnings":{"unknownTemplateIds":["Unk""") - result should include(""""owner":"Alice"""") + result should include(s""""owner":"$alice"""") result should include(""""number":"abc123"""") Inspectors.forAll(heartbeats)(assertHeartbeat) } @@ -267,7 +272,8 @@ abstract class AbstractWebsocketServiceIntegrationTest (uri, _, _, _) => import spray.json._ - val initialCreate = initialIouCreate(uri) + val (party, headers) = getUniquePartyAndAuthHeaders("Alice") + val initialCreate = initialIouCreate(uri, party, headers) val query = """[ @@ -292,7 +298,7 @@ abstract class AbstractWebsocketServiceIntegrationTest postJsonRequest( uri.withPath(Uri.Path("/v1/exercise")), exercisePayload(domain.ContractId(ctid)), - headersWithAuth, + headers, ) map { case (statusCode, _) => statusCode.isSuccess shouldBe true } @@ -348,6 +354,7 @@ abstract class AbstractWebsocketServiceIntegrationTest creation <- initialCreate _ = creation._1 shouldBe a[StatusCodes.Success] iouCid = getContractId(getResult(creation._2)) + jwt = jwtForParties(List(party.unwrap), List(), testId) (kill, source) = singleClientQueryStream(jwt, uri, query) .viaMat(KillSwitches.single)(Keep.right) .preMaterialize() @@ -368,19 +375,21 @@ abstract class AbstractWebsocketServiceIntegrationTest (uri, encoder, _, _) => import spray.json._ + val (alice, aliceAuthHeaders) = getUniquePartyAndAuthHeaders("Alice") + val (bob, bobAuthHeaders) = getUniquePartyAndAuthHeaders("Bob") val f1 = postCreateCommand( - accountCreateCommand(domain.Party("Alice"), "abc123"), + accountCreateCommand(alice, "abc123"), encoder, uri, - headers = headersWithPartyAuth(List("Alice")), + headers = aliceAuthHeaders, ) val f2 = postCreateCommand( - accountCreateCommand(domain.Party("Bob"), "def456"), + accountCreateCommand(bob, "def456"), encoder, uri, - headers = headersWithPartyAuth(List("Bob")), + headers = bobAuthHeaders, ) val query = @@ -402,33 +411,33 @@ abstract class AbstractWebsocketServiceIntegrationTest ContractDelta(Vector(), _, Some(liveStartOffset)) <- readOne _ <- liftF( postCreateCommand( - accountCreateCommand(domain.Party("Alice"), "abc234"), + accountCreateCommand(alice, "abc234"), encoder, uri, - headers = headersWithPartyAuth(List("Alice")), + headers = aliceAuthHeaders, ) ) ContractDelta(Vector((_, aliceAccount)), _, Some(_)) <- readOne _ = inside(aliceAccount) { case JsObject(obj) => inside((obj get "owner", obj get "number")) { case (Some(JsString(owner)), Some(JsString(number))) => - owner shouldBe "Alice" + owner shouldBe alice.unwrap number shouldBe "abc234" } } _ <- liftF( postCreateCommand( - accountCreateCommand(domain.Party("Bob"), "def567"), + accountCreateCommand(bob, "def567"), encoder, uri, - headers = headersWithPartyAuth(List("Bob")), + headers = bobAuthHeaders, ) ) ContractDelta(Vector((_, bobAccount)), _, Some(lastSeenOffset)) <- readOne _ = inside(bobAccount) { case JsObject(obj) => inside((obj get "owner", obj get "number")) { case (Some(JsString(owner)), Some(JsString(number))) => - owner shouldBe "Bob" + owner shouldBe bob.unwrap number shouldBe "def567" } } @@ -456,7 +465,7 @@ abstract class AbstractWebsocketServiceIntegrationTest cid2 = getContractId(getResult(r2._2)) (kill, source) = singleClientQueryStream( - jwtForParties(List("Alice", "Bob"), List(), testId), + jwtForParties(List(alice.unwrap, bob.unwrap), List(), testId), uri, query, ).viaMat(KillSwitches.single)(Keep.right).preMaterialize() @@ -465,7 +474,12 @@ abstract class AbstractWebsocketServiceIntegrationTest lastSeen.unwrap should be > liveStart.unwrap 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 } yield inside(rescan) { case (Vector(_), _, Some(_)) => succeed @@ -474,20 +488,34 @@ abstract class AbstractWebsocketServiceIntegrationTest "fetch should receive deltas as contracts are archived/created, filtering out phantom archives" in withHttpService { (uri, encoder, _, _) => + val (alice, headers) = getUniquePartyAndAuthHeaders("Alice") val templateId = domain.TemplateId(None, "Account", "Account") def fetchRequest(contractIdAtOffset: Option[Option[domain.ContractId]] = None) = { import spray.json._, json.JsonProtocol._ List( - Map("templateId" -> "Account:Account".toJson, "key" -> List("Alice", "abc123").toJson) + Map( + "templateId" -> "Account:Account".toJson, + "key" -> List(alice.unwrap, "abc123").toJson, + ) ++ contractIdAtOffset .map(ocid => contractIdAtOffsetKey -> ocid.toJson) .toList ).toJson.compactPrint } val f1 = - postCreateCommand(accountCreateCommand(domain.Party("Alice"), "abc123"), encoder, uri) + postCreateCommand( + accountCreateCommand(alice, "abc123"), + encoder, + uri, + headers, + ) val f2 = - postCreateCommand(accountCreateCommand(domain.Party("Alice"), "def456"), encoder, uri) + postCreateCommand( + accountCreateCommand(alice, "def456"), + encoder, + uri, + headers, + ) def resp( cid1: domain.ContractId, @@ -500,12 +528,13 @@ abstract class AbstractWebsocketServiceIntegrationTest for { ContractDelta(Vector((cid, c)), Vector(), None) <- readOne _ = (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, _) => statusCode.isSuccess shouldBe true - postArchiveCommand(templateId, cid1, encoder, uri).map { case (statusCode, _) => - statusCode.isSuccess shouldBe true - cid + postArchiveCommand(templateId, cid1, encoder, uri, headers).map { + case (statusCode, _) => + statusCode.isSuccess shouldBe true + cid } }) @@ -541,7 +570,7 @@ abstract class AbstractWebsocketServiceIntegrationTest r2 <- f2 _ = r2._1 shouldBe a[StatusCodes.Success] cid2 = getContractId(getResult(r2._2)) - + jwt = jwtForParties(List(alice.unwrap), List(), testId) (kill, source) = singleClientFetchStream(jwt, uri, fetchRequest()) .viaMat(KillSwitches.single)(Keep.right) .preMaterialize() @@ -576,16 +605,29 @@ abstract class AbstractWebsocketServiceIntegrationTest } "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] = for { - r <- postCreateCommand(accountCreateCommand(domain.Party("Alice"), account), encoder, uri) + r <- postCreateCommand( + accountCreateCommand(alice, account), + encoder, + uri, + headers, + ) } yield { assert(r._1.isSuccess) getContractId(getResult(r._2)) } def archive(id: domain.ContractId): Future[Assertion] = for { - r <- postArchiveCommand(domain.TemplateId(None, "Account", "Account"), id, encoder, uri) + r <- postArchiveCommand( + domain.TemplateId(None, "Account", "Account"), + id, + encoder, + uri, + headers, + ) } yield { assert(r._1.isSuccess) } @@ -620,9 +662,9 @@ abstract class AbstractWebsocketServiceIntegrationTest ) } val req = - """ - |[{"templateId": "Account:Account", "key": ["Alice", "abc123"]}, - | {"templateId": "Account:Account", "key": ["Alice", "def456"]}] + s""" + |[{"templateId": "Account:Account", "key": ["$alice", "abc123"]}, + | {"templateId": "Account:Account", "key": ["$alice", "def456"]}] |""".stripMargin val (kill, source) = singleClientFetchStream(jwt, uri, req) @@ -637,26 +679,28 @@ abstract class AbstractWebsocketServiceIntegrationTest import spray.json._ val templateId = domain.TemplateId(None, "Account", "Account") + val (alice, aliceAuthHeaders) = getUniquePartyAndAuthHeaders("Alice") + val (bob, bobAuthHeaders) = getUniquePartyAndAuthHeaders("Bob") val f1 = postCreateCommand( - accountCreateCommand(domain.Party("Alice"), "abc123"), + accountCreateCommand(alice, "abc123"), encoder, uri, - headers = headersWithPartyAuth(List("Alice")), + headers = aliceAuthHeaders, ) val f2 = postCreateCommand( - accountCreateCommand(domain.Party("Bob"), "def456"), + accountCreateCommand(bob, "def456"), encoder, uri, - headers = headersWithPartyAuth(List("Bob")), + headers = bobAuthHeaders, ) val query = - """[ - {"templateId": "Account:Account", "key": ["Alice", "abc123"]}, - {"templateId": "Account:Account", "key": ["Bob", "def456"]} + s"""[ + {"templateId": "Account:Account", "key": ["$alice", "abc123"]}, + {"templateId": "Account:Account", "key": ["$bob", "def456"]} ]""" def resp( @@ -671,7 +715,7 @@ abstract class AbstractWebsocketServiceIntegrationTest Vector((account1, _), (account2, _)) <- readAcsN(2) _ = Seq(account1, account2) should contain theSameElementsAs Seq(cid1, cid2) ContractDelta(Vector(), _, Some(liveStartOffset)) <- readOne - _ <- liftF(postArchiveCommand(templateId, cid1, encoder, uri)) + _ <- liftF(postArchiveCommand(templateId, cid1, encoder, uri, aliceAuthHeaders)) ContractDelta(Vector(), Vector(archivedCid1), Some(_)) <- readOne _ = archivedCid1.contractId shouldBe cid1 _ <- liftF( @@ -680,7 +724,7 @@ abstract class AbstractWebsocketServiceIntegrationTest cid2, encoder, uri, - headers = headersWithPartyAuth(List("Bob")), + headers = bobAuthHeaders, ) ) ContractDelta(Vector(), Vector(archivedCid2), Some(lastSeenOffset)) <- readOne @@ -708,8 +752,9 @@ abstract class AbstractWebsocketServiceIntegrationTest _ = r2._1 shouldBe a[StatusCodes.Success] cid2 = getContractId(getResult(r2._2)) + jwt = jwtForParties(List(alice.unwrap, bob.unwrap), List(), testId) (kill, source) = singleClientFetchStream( - jwtForParties(List("Alice", "Bob"), List(), testId), + jwt, uri, query, ).viaMat(KillSwitches.single)(Keep.right).preMaterialize() @@ -719,7 +764,7 @@ abstract class AbstractWebsocketServiceIntegrationTest liveStart } rescan <- (singleClientFetchStream( - jwtForParties(List("Alice", "Bob"), List(), testId), + jwt, uri, query, Some(liveOffset), @@ -791,25 +836,29 @@ abstract class AbstractWebsocketServiceIntegrationTest "query on a bunch of random splits should yield consistent results" in withHttpService { (uri, _, _, _) => + val (alice, headers) = getUniquePartyAndAuthHeaders("Alice") val splitSample = SplitSeq.gen.map(_ map (BigDecimal(_))).sample.get val query = """[ {"templateIds": ["Iou:Iou"]} ]""" - val (kill, source) = singleClientQueryStream(jwt, uri, query) - .viaMat(KillSwitches.single)(Keep.right) - .preMaterialize() + val (kill, source) = + singleClientQueryStream(jwtForParties(List(alice.unwrap), List(), testId), uri, query) + .viaMat(KillSwitches.single)(Keep.right) + .preMaterialize() source .via(parseResp) .map(iouSplitResult) .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( serviceUri: Uri, ss: SplitSeq[BigDecimal], kill: UniqueKillSwitch, + partyName: String, + headers: List[HttpHeader], ): Consume.FCC[IouSplitResult, Assertion] = { val dslSyntax = Consume.syntax[IouSplitResult] import SplitSeq._ @@ -826,7 +875,7 @@ abstract class AbstractWebsocketServiceIntegrationTest postJsonRequest( serviceUri.withPath(Uri.Path("/v1/exercise")), exercisePayload(createdCid, l.x), - headersWithAuth, + headers, ) ) @@ -848,10 +897,10 @@ abstract class AbstractWebsocketServiceIntegrationTest "templateId" -> "Iou:Iou".toJson, "payload" -> Map( "observers" -> List[String]().toJson, - "issuer" -> "Alice".toJson, + "issuer" -> partyName.toJson, "amount" -> ss.x.toJson, "currency" -> "USD".toJson, - "owner" -> "Alice".toJson, + "owner" -> partyName.toJson, ).toJson, ).toJson } @@ -860,7 +909,7 @@ abstract class AbstractWebsocketServiceIntegrationTest postJsonRequest( serviceUri.withPath(Uri.Path("/v1/create")), initialPayload, - headersWithAuth, + headers, ) ) \/-((Vector((genesisCid, amt)), Vector())) <- readOne @@ -947,15 +996,17 @@ abstract class AbstractWebsocketServiceIntegrationTest val dslSyntax = Consume.syntax[JsValue] import dslSyntax._ import spray.json._ + val (alice, headers) = getUniquePartyAndAuthHeaders("Alice") + val jwt = jwtForParties(List(alice.unwrap), List(), testId) def createIouCommand(currency: String): String = s"""{ | "templateId": "Iou:Iou", | "payload": { | "observers": [], - | "issuer": "Alice", + | "issuer": "$alice", | "amount": "999.99", | "currency": "$currency", - | "owner": "Alice" + | "owner": "$alice" | } |}""".stripMargin def createIou(currency: String): Future[Assertion] = @@ -963,7 +1014,7 @@ abstract class AbstractWebsocketServiceIntegrationTest .postJsonStringRequest( uri.withPath(Uri.Path("/v1/create")), createIouCommand(currency), - headersWithAuth, + headers, ) .map(_._1 shouldBe a[StatusCodes.Success]) def contractsQuery(currency: String): String =