[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",
"//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",

View File

@ -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",
],
)

View File

@ -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) = {

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("@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",

View File

@ -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))

View File

@ -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")) }

View File

@ -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)

View File

@ -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

View File

@ -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](

View File

@ -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 =