mirror of
https://github.com/digital-asset/daml.git
synced 2024-09-20 09:17:43 +03:00
Migrate LedgerClient test from to Canton (#16795)
This commit is contained in:
parent
99cbff4eff
commit
90c023e98e
@ -44,6 +44,7 @@ da_scala_library(
|
||||
"//libs-scala/resources",
|
||||
"//libs-scala/scala-utils",
|
||||
"//libs-scala/timer-utils",
|
||||
"//test-common",
|
||||
"@maven//:org_scalatest_scalatest_compatible",
|
||||
],
|
||||
)
|
||||
|
@ -22,7 +22,8 @@ import org.scalatest.Suite
|
||||
import scala.concurrent.duration.DurationInt
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
|
||||
import java.nio.file.{Files, Path, Paths}
|
||||
import java.nio.file.{Path, Paths, Files}
|
||||
import java.util.UUID
|
||||
|
||||
@scala.annotation.nowarn("msg=match may not be exhaustive")
|
||||
object CantonFixture {
|
||||
@ -46,16 +47,14 @@ object CantonFixture {
|
||||
Paths.get(rlocation("test-common/test-certificates/" + src))
|
||||
}
|
||||
|
||||
private val counter = new java.util.concurrent.atomic.AtomicLong()
|
||||
|
||||
def freshLong() = counter.getAndIncrement()
|
||||
|
||||
def freshName(prefix: String): String = {
|
||||
assert(!prefix.contains('_'))
|
||||
prefix + "__" + freshLong().toString
|
||||
prefix + "__" + UUID.randomUUID()
|
||||
}
|
||||
|
||||
def freshUserId() = Ref.UserId.assertFromString(freshName("user"))
|
||||
def freshUserId(): Ref.UserId = Ref.UserId.assertFromString(freshName("user"))
|
||||
|
||||
def freshParty(): Ref.Party = Ref.Party.assertFromString(freshName("Party"))
|
||||
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,192 @@
|
||||
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.daml.lf
|
||||
package integrationtest
|
||||
|
||||
import java.nio.file.Path
|
||||
import java.util
|
||||
|
||||
import com.daml.ledger.api.domain
|
||||
import com.daml.ledger.api.testing.utils.{MockMessages => M}
|
||||
import com.daml.ledger.api.v1.command_service.SubmitAndWaitRequest
|
||||
import com.daml.ledger.api.v1.command_submission_service.SubmitRequest
|
||||
import com.daml.ledger.api.v1.commands.Command.Command.{Create, Exercise}
|
||||
import com.daml.ledger.api.v1.commands.{Command, Commands, CreateCommand, ExerciseCommand}
|
||||
import com.daml.ledger.api.v1.value.Value.Sum
|
||||
import com.daml.ledger.api.v1.value.Value.Sum.{Bool, Party, Text, Timestamp}
|
||||
import com.daml.ledger.api.v1.value.{Identifier, Record, RecordField, Value, Variant}
|
||||
import com.daml.lf.archive.DarReader
|
||||
import com.daml.lf.data.Ref
|
||||
import com.daml.platform.participant.util.ValueConversions._
|
||||
import com.daml.platform.testing.TestTemplateIdentifiers
|
||||
import scalaz.syntax.tag._
|
||||
|
||||
trait TestCommands {
|
||||
|
||||
import TestCommands.SubmitRequestEnhancer
|
||||
|
||||
protected def darFile: Path
|
||||
|
||||
protected lazy val packageId: Ref.PackageId =
|
||||
DarReader.assertReadArchiveFromFile(darFile.toFile).main.pkgId
|
||||
|
||||
protected lazy val templateIds = new TestTemplateIdentifiers(packageId)
|
||||
|
||||
protected def buildRequest(
|
||||
ledgerId: domain.LedgerId,
|
||||
commandId: String,
|
||||
commands: Seq[Command],
|
||||
applicationId: String,
|
||||
party: String,
|
||||
): SubmitRequest =
|
||||
M.submitRequest.update(
|
||||
_.commands.commandId := commandId,
|
||||
_.commands.ledgerId := ledgerId.unwrap,
|
||||
_.commands.applicationId := applicationId,
|
||||
_.commands.party := party,
|
||||
_.commands.commands := commands,
|
||||
)
|
||||
|
||||
protected def dummyCommands(
|
||||
ledgerId: domain.LedgerId,
|
||||
commandId: String,
|
||||
applicationId: String,
|
||||
party: String,
|
||||
): SubmitRequest =
|
||||
buildRequest(
|
||||
ledgerId = ledgerId,
|
||||
commandId = commandId,
|
||||
commands = List(
|
||||
createWithOperator(templateIds.dummy, party),
|
||||
createWithOperator(templateIds.dummyWithParam, party),
|
||||
createWithOperator(templateIds.dummyFactory, party),
|
||||
),
|
||||
applicationId = applicationId,
|
||||
party = party,
|
||||
)
|
||||
|
||||
protected def dummyMultiPartyCommands(
|
||||
ledgerId: domain.LedgerId,
|
||||
commandId: String,
|
||||
applicationId: String,
|
||||
party: String,
|
||||
actAs: Seq[String],
|
||||
readAs: Seq[String],
|
||||
): SubmitRequest = {
|
||||
// This method returns a multi-party submission, however the Daml contract uses a single party.
|
||||
// Pick a random party for the Daml contract (it needs to be one of the submitters).
|
||||
val operator = actAs.headOption.getOrElse(party)
|
||||
dummyCommands(ledgerId, commandId, applicationId, operator)
|
||||
.update(
|
||||
_.commands.party := party,
|
||||
_.commands.actAs := actAs,
|
||||
_.commands.readAs := readAs,
|
||||
)
|
||||
}
|
||||
|
||||
protected def createWithOperator(templateId: Identifier, party: String): Command =
|
||||
Command(
|
||||
Create(
|
||||
CreateCommand(
|
||||
Some(templateId),
|
||||
Some(Record(Some(templateId), List(RecordField("operator", Some(Value(Party(party))))))),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
private def oneKilobyteString: String = {
|
||||
val numChars = 500 // each char takes 2 bytes for now in Java 8
|
||||
val array = new Array[Char](numChars)
|
||||
util.Arrays.fill(array, 'a')
|
||||
new String(array)
|
||||
}
|
||||
|
||||
protected def oneKbCommand(templateId: Identifier): Command =
|
||||
Command(
|
||||
Create(
|
||||
CreateCommand(
|
||||
Some(templateId),
|
||||
Some(
|
||||
Record(
|
||||
Some(templateId),
|
||||
List(
|
||||
RecordField("operator", Some(Value(Party("party")))),
|
||||
RecordField("text", Some(Value(Text(oneKilobyteString)))),
|
||||
),
|
||||
)
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
protected def paramShowcaseArgs: Record = {
|
||||
val variant = Value(Value.Sum.Variant(Variant(None, "SomeInteger", 1.asInt64)))
|
||||
val nestedVariant = Vector("value" -> variant).asRecordValue
|
||||
val integerList = Vector(1, 2).map(_.toLong.asInt64).asList
|
||||
Record(
|
||||
Some(templateIds.parameterShowcase),
|
||||
Vector(
|
||||
RecordField("operator", "Alice".asParty),
|
||||
RecordField("integer", 1.asInt64),
|
||||
RecordField("decimal", "1.1".asNumeric),
|
||||
RecordField("text", Value(Text("text"))),
|
||||
RecordField("bool", Value(Bool(true))),
|
||||
RecordField("time", Value(Timestamp(0))),
|
||||
RecordField(
|
||||
"relTime",
|
||||
42.asInt64,
|
||||
), // RelTime gets now compiled to Integer with the new primitive types
|
||||
RecordField("nestedOptionalInteger", nestedVariant),
|
||||
RecordField("integerList", integerList),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
protected def paramShowcase: Commands = Commands(
|
||||
"ledgerId",
|
||||
"workflowId",
|
||||
"appId",
|
||||
"cmd",
|
||||
"Alice",
|
||||
Seq(
|
||||
Command(
|
||||
Command.Command.Create(
|
||||
CreateCommand(Some(templateIds.parameterShowcase), Option(paramShowcaseArgs))
|
||||
)
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
protected def oneKbCommandRequest(
|
||||
ledgerId: domain.LedgerId,
|
||||
commandId: String,
|
||||
applicationId: String,
|
||||
party: String,
|
||||
): SubmitRequest =
|
||||
buildRequest(
|
||||
ledgerId = ledgerId,
|
||||
commandId = commandId,
|
||||
commands = List(oneKbCommand(templateIds.textContainer)),
|
||||
applicationId = applicationId,
|
||||
party = party,
|
||||
)
|
||||
|
||||
protected def exerciseWithUnit(
|
||||
templateId: Identifier,
|
||||
contractId: String,
|
||||
choice: String,
|
||||
args: Option[Value] = Some(Value(Sum.Record(Record.defaultInstance))),
|
||||
): Command =
|
||||
Command(Exercise(ExerciseCommand(Some(templateId), contractId, choice, args)))
|
||||
|
||||
import scala.language.implicitConversions
|
||||
implicit def SubmitRequestEnhancer(request: SubmitRequest): SubmitRequestEnhancer =
|
||||
new SubmitRequestEnhancer(request)
|
||||
}
|
||||
|
||||
object TestCommands {
|
||||
implicit final class SubmitRequestEnhancer(private val request: SubmitRequest) extends AnyVal {
|
||||
def toSync: SubmitAndWaitRequest = SubmitAndWaitRequest(request.commands)
|
||||
}
|
||||
}
|
@ -86,7 +86,7 @@ da_scala_test_suite(
|
||||
name = "ledger-api-client-integration-tests",
|
||||
srcs = glob(["src/it/**/*.scala"]),
|
||||
data = [
|
||||
"//test-common:dar-files",
|
||||
"//test-common:dar-files-default",
|
||||
],
|
||||
resources = [
|
||||
"src/it/resources/logback-test.xml",
|
||||
@ -95,9 +95,12 @@ da_scala_test_suite(
|
||||
"@maven//:com_typesafe_akka_akka_actor",
|
||||
"@maven//:com_typesafe_akka_akka_stream",
|
||||
],
|
||||
tags = ["cpu:4"],
|
||||
deps = [
|
||||
":ledger-api-client",
|
||||
"//bazel_tools/runfiles:scala_runfiles",
|
||||
"//daml-lf/engine",
|
||||
"//daml-lf/integration-test-lib",
|
||||
"//daml-lf/transaction",
|
||||
"//language-support/scala/bindings",
|
||||
"//ledger-api/rs-grpc-bridge",
|
||||
@ -117,6 +120,7 @@ da_scala_test_suite(
|
||||
"//libs-scala/concurrent",
|
||||
"//libs-scala/contextualized-logging",
|
||||
"//libs-scala/grpc-utils",
|
||||
"//libs-scala/jwt",
|
||||
"//libs-scala/ledger-resources",
|
||||
"//libs-scala/logging-entries",
|
||||
"//libs-scala/ports",
|
||||
@ -124,6 +128,7 @@ da_scala_test_suite(
|
||||
"//observability/metrics",
|
||||
"//observability/tracing",
|
||||
"//test-common",
|
||||
"//test-common:dar-files-default-lib",
|
||||
"@maven//:ch_qos_logback_logback_classic",
|
||||
"@maven//:io_netty_netty_handler",
|
||||
],
|
||||
|
@ -8,12 +8,9 @@ import java.util.concurrent.TimeUnit
|
||||
import akka.NotUsed
|
||||
import akka.stream.scaladsl.{Sink, Source}
|
||||
import com.daml.api.util.TimeProvider
|
||||
import com.daml.bazeltools.BazelRunfiles
|
||||
import com.daml.ledger.api.domain
|
||||
import com.daml.ledger.api.testing.utils.{
|
||||
IsStatusException,
|
||||
MockMessages,
|
||||
SuiteResourceManagementAroundAll,
|
||||
}
|
||||
import com.daml.ledger.api.testing.utils.{IsStatusException, SuiteResourceManagementAroundAll}
|
||||
import com.daml.ledger.api.v1.command_completion_service.CommandCompletionServiceGrpc
|
||||
import com.daml.ledger.api.v1.command_submission_service.{
|
||||
CommandSubmissionServiceGrpc,
|
||||
@ -37,12 +34,12 @@ import com.daml.ledger.client.services.commands.{
|
||||
CompletionStreamElement,
|
||||
}
|
||||
import com.daml.ledger.client.services.testing.time.StaticTime
|
||||
import com.daml.ledger.runner.common.Config
|
||||
import com.daml.lf.crypto.Hash
|
||||
import com.daml.ledger.test.ModelTestDar
|
||||
import com.daml.lf.crypto
|
||||
import com.daml.lf.data.{Bytes, Ref}
|
||||
import com.daml.lf.integrationtest.{CantonFixture, TestCommands}
|
||||
import com.daml.lf.value.Value.ContractId
|
||||
import com.daml.platform.participant.util.ValueConversions._
|
||||
import com.daml.platform.sandbox.fixture.SandboxFixture
|
||||
import com.daml.platform.sandbox.services.TestCommands
|
||||
import com.daml.util.Ctx
|
||||
import com.google.rpc.code.Code
|
||||
import io.grpc.{Status, StatusRuntimeException}
|
||||
@ -58,15 +55,21 @@ import scala.concurrent.duration.FiniteDuration
|
||||
import scala.util.Success
|
||||
import scala.util.control.NonFatal
|
||||
|
||||
import java.nio.file.Paths
|
||||
|
||||
final class CommandClientIT
|
||||
extends AsyncWordSpec
|
||||
with TestCommands
|
||||
with SandboxFixture
|
||||
with CantonFixture
|
||||
with Matchers
|
||||
with SuiteResourceManagementAroundAll
|
||||
with TryValues
|
||||
with Inside {
|
||||
|
||||
protected def darFile = Paths.get(BazelRunfiles.rlocation(ModelTestDar.path))
|
||||
|
||||
override protected lazy val darFiles: List[java.nio.file.Path] = List(darFile)
|
||||
|
||||
private val defaultCommandClientConfiguration =
|
||||
CommandClientConfiguration(
|
||||
maxCommandsInFlight = 1,
|
||||
@ -74,19 +77,28 @@ final class CommandClientIT
|
||||
defaultDeduplicationTime = Duration.ofSeconds(30),
|
||||
)
|
||||
|
||||
private val testLedgerId = domain.LedgerId("ledgerId")
|
||||
private val testNotLedgerId = domain.LedgerId("hotdog")
|
||||
private val testLedgerId =
|
||||
domain.LedgerId(config.ledgerIds.head)
|
||||
private val testNotLedgerId =
|
||||
domain.LedgerId(CantonFixture.freshName("hotdog"))
|
||||
|
||||
private lazy val channel = config.channel(suiteResource.value.head)
|
||||
private lazy val defaultClient = defaultLedgerClient()
|
||||
private def freshParty() = for {
|
||||
client <- defaultClient
|
||||
details <- client.partyManagementClient.allocateParty(Some(CantonFixture.freshParty()), None)
|
||||
} yield details.party
|
||||
|
||||
private def commandClientWithoutTime(
|
||||
ledgerId: domain.LedgerId,
|
||||
applicationId: String = MockMessages.applicationId,
|
||||
appId: String = applicationId.unwrap,
|
||||
configuration: CommandClientConfiguration = defaultCommandClientConfiguration,
|
||||
): CommandClient =
|
||||
new CommandClient(
|
||||
CommandSubmissionServiceGrpc.stub(channel),
|
||||
CommandCompletionServiceGrpc.stub(channel),
|
||||
ledgerId,
|
||||
applicationId,
|
||||
appId,
|
||||
configuration,
|
||||
)
|
||||
|
||||
@ -100,21 +112,30 @@ final class CommandClientIT
|
||||
|
||||
private def commandClient(
|
||||
ledgerId: domain.LedgerId = testLedgerId,
|
||||
applicationId: String = MockMessages.applicationId,
|
||||
appId: String = applicationId.unwrap,
|
||||
configuration: CommandClientConfiguration = defaultCommandClientConfiguration,
|
||||
): Future[CommandClient] =
|
||||
timeProvider(ledgerId)
|
||||
.map(_ => commandClientWithoutTime(ledgerId, applicationId, configuration))
|
||||
.map(_ =>
|
||||
commandClientWithoutTime(appId = appId, ledgerId = ledgerId, configuration = configuration)
|
||||
)
|
||||
|
||||
override protected def config: Config = super.config.copy(ledgerId = testLedgerId.unwrap)
|
||||
|
||||
private val submittingPartyList = List(MockMessages.party)
|
||||
private val LedgerBegin = LedgerOffset(Boundary(LEDGER_BEGIN))
|
||||
|
||||
private def submitRequest(commandId: String, individualCommands: Seq[Command]): SubmitRequest =
|
||||
buildRequest(testLedgerId, commandId, individualCommands)
|
||||
private def submitRequest(
|
||||
commandId: String,
|
||||
individualCommands: Seq[Command],
|
||||
party: Ref.Party,
|
||||
): SubmitRequest =
|
||||
buildRequest(
|
||||
ledgerId = testLedgerId,
|
||||
commandId = commandId,
|
||||
commands = individualCommands,
|
||||
applicationId = applicationId.unwrap,
|
||||
party = party,
|
||||
)
|
||||
|
||||
private def submitRequestWithId(commandId: String): SubmitRequest =
|
||||
private def submitRequestWithId(commandId: String, party: Ref.Party) =
|
||||
submitRequest(
|
||||
commandId,
|
||||
List(
|
||||
@ -123,15 +144,16 @@ final class CommandClientIT
|
||||
Some(
|
||||
Record(
|
||||
Some(templateIds.dummy),
|
||||
Seq(RecordField("operator", Option(MockMessages.party.asParty))),
|
||||
Seq(RecordField("operator", Some(party.asParty))),
|
||||
)
|
||||
),
|
||||
).wrap
|
||||
),
|
||||
party,
|
||||
)
|
||||
|
||||
private def commandSubmissionWithId(commandId: String): CommandSubmission =
|
||||
CommandSubmission(submitRequestWithId(commandId).getCommands)
|
||||
private def commandSubmissionWithId(commandId: String, party: Ref.Party): CommandSubmission =
|
||||
CommandSubmission(submitRequestWithId(commandId, party).getCommands)
|
||||
|
||||
// Commands and completions can be read out of order. Since we use GRPC monocalls to send,
|
||||
// they can even be sent out of order.
|
||||
@ -177,12 +199,13 @@ final class CommandClientIT
|
||||
*/
|
||||
private def readExpectedCommandIds(
|
||||
client: CommandClient,
|
||||
party: Ref.Party,
|
||||
checkpoint: LedgerOffset,
|
||||
expected: Set[String],
|
||||
timeLimit: Span = 6.seconds,
|
||||
): Future[(Set[String], Set[String])] =
|
||||
readExpectedElements(
|
||||
client.completionSource(submittingPartyList, checkpoint).collect {
|
||||
client.completionSource(List(party), checkpoint).collect {
|
||||
case CompletionStreamElement.CompletionElement(c, _) => c.commandId
|
||||
},
|
||||
expected,
|
||||
@ -221,7 +244,7 @@ final class CommandClientIT
|
||||
}
|
||||
|
||||
"fail with the expected status on a ledger Id mismatch" in {
|
||||
commandClientWithoutTime(testNotLedgerId)
|
||||
commandClientWithoutTime(ledgerId = testNotLedgerId)
|
||||
.getCompletionEnd()
|
||||
.failed map IsStatusException(Status.NOT_FOUND)
|
||||
}
|
||||
@ -233,8 +256,9 @@ final class CommandClientIT
|
||||
val contexts = 1 to 10
|
||||
|
||||
for {
|
||||
party <- freshParty()
|
||||
client <- commandClient()
|
||||
result <- Source(contexts.map(i => Ctx(i, commandSubmissionWithId(i.toString))))
|
||||
result <- Source(contexts.map(i => Ctx(i, commandSubmissionWithId(i.toString, party))))
|
||||
.via(client.submissionFlow())
|
||||
.map(_.map(_.isSuccess))
|
||||
.runWith(Sink.seq)
|
||||
@ -244,21 +268,26 @@ final class CommandClientIT
|
||||
}
|
||||
|
||||
"fail with the expected status on a ledger Id mismatch" in {
|
||||
val aSubmission = commandSubmissionWithId("1")
|
||||
val submission = aSubmission.copy(
|
||||
commands = aSubmission.commands.update(_.ledgerId := testNotLedgerId.unwrap)
|
||||
)
|
||||
Source
|
||||
.single(Ctx(1, submission))
|
||||
.via(commandClientWithoutTime(testNotLedgerId).submissionFlow())
|
||||
.runWith(Sink.head)
|
||||
.map(err => IsStatusException(Status.NOT_FOUND)(err.value.failure.exception))
|
||||
|
||||
for {
|
||||
party <- freshParty()
|
||||
aSubmission = commandSubmissionWithId("1", party)
|
||||
submission = aSubmission.copy(
|
||||
commands = aSubmission.commands.update(_.ledgerId := testNotLedgerId.unwrap)
|
||||
)
|
||||
err <- Source
|
||||
.single(Ctx(1, submission))
|
||||
.via(commandClientWithoutTime(ledgerId = testNotLedgerId).submissionFlow())
|
||||
.runWith(Sink.head)
|
||||
} yield IsStatusException(Status.NOT_FOUND)(err.value.failure.exception)
|
||||
|
||||
}
|
||||
|
||||
"fail with INVALID REQUEST for empty application ids" in {
|
||||
val request = submitRequestWithId("7000").update(_.commands.applicationId := "")
|
||||
val resF = for {
|
||||
client <- commandClient(applicationId = "")
|
||||
party <- freshParty()
|
||||
request = submitRequestWithId("7000", party).update(_.commands.applicationId := "")
|
||||
client <- commandClient(appId = "")
|
||||
res <- client.submitSingleCommand(request)
|
||||
} yield res
|
||||
|
||||
@ -275,8 +304,9 @@ final class CommandClientIT
|
||||
|
||||
"fail with INVALID REQUEST for empty application ids" in {
|
||||
val completionsF = for {
|
||||
client <- commandClient(applicationId = "")
|
||||
completionsSource = client.completionSource(submittingPartyList, LedgerBegin)
|
||||
party <- freshParty()
|
||||
client <- commandClient(appId = "")
|
||||
completionsSource = client.completionSource(List(party), LedgerBegin)
|
||||
completions <- completionsSource.takeWithin(5.seconds).runWith(Sink.seq)
|
||||
} yield completions
|
||||
|
||||
@ -289,10 +319,13 @@ final class CommandClientIT
|
||||
}
|
||||
|
||||
"fail with the expected status on a ledger Id mismatch" in {
|
||||
commandClientWithoutTime(testNotLedgerId)
|
||||
.completionSource(submittingPartyList, LedgerBegin)
|
||||
.runWith(Sink.head)
|
||||
.failed map IsStatusException(Status.NOT_FOUND)
|
||||
for {
|
||||
party <- freshParty()
|
||||
err <- commandClientWithoutTime(ledgerId = testNotLedgerId)
|
||||
.completionSource(List(party), LedgerBegin)
|
||||
.runWith(Sink.head)
|
||||
.failed
|
||||
} yield IsStatusException(Status.NOT_FOUND)(err)
|
||||
}
|
||||
|
||||
"return completions of commands submitted before subscription if they are after the offset" in {
|
||||
@ -304,10 +337,11 @@ final class CommandClientIT
|
||||
|
||||
// val for type inference
|
||||
val resultF = for {
|
||||
party <- freshParty()
|
||||
client <- commandClient()
|
||||
checkpoint <- client.getCompletionEnd()
|
||||
submissionResults <- Source(
|
||||
commandIds.map(i => Ctx(i, commandSubmissionWithId(i.toString)))
|
||||
commandIds.map(i => Ctx(i, commandSubmissionWithId(i.toString, party)))
|
||||
)
|
||||
.flatMapMerge(10, randomDelay)
|
||||
.via(client.submissionFlow())
|
||||
@ -315,10 +349,8 @@ final class CommandClientIT
|
||||
.runWith(Sink.seq)
|
||||
_ = submissionResults.foreach(v => v shouldBe a[Success[_]])
|
||||
|
||||
result <- readExpectedCommandIds(client, checkpoint.getOffset, commandIdStrings)
|
||||
} yield {
|
||||
result
|
||||
}
|
||||
result <- readExpectedCommandIds(client, party, checkpoint.getOffset, commandIdStrings)
|
||||
} yield result
|
||||
|
||||
resultF map { case (seenCommandIds, remainingCommandIds) =>
|
||||
// N.B.: completions may include already-seen elements, and may be out of order
|
||||
@ -336,15 +368,17 @@ final class CommandClientIT
|
||||
val commandIdStrings = Set(commandIds.map(_.toString): _*)
|
||||
|
||||
for {
|
||||
party <- freshParty()
|
||||
client <- commandClient()
|
||||
checkpoint <- client.getCompletionEnd()
|
||||
_ <- Source(commandIds.map(i => Ctx(i, commandSubmissionWithId(i.toString))))
|
||||
_ <- Source(commandIds.map(i => Ctx(i, commandSubmissionWithId(i.toString, party))))
|
||||
.flatMapMerge(10, randomDelay)
|
||||
.via(client.submissionFlow())
|
||||
.map(_.context)
|
||||
.runWith(Sink.ignore)
|
||||
(seenCommandIds, remainingCommandIds) <- readExpectedCommandIds(
|
||||
client,
|
||||
party,
|
||||
checkpoint.getOffset,
|
||||
commandIdStrings,
|
||||
)
|
||||
@ -359,149 +393,164 @@ final class CommandClientIT
|
||||
|
||||
"return the contexts for commands as they are completed" in {
|
||||
val contexts = 6001.to(6010)
|
||||
|
||||
for {
|
||||
party <- freshParty()
|
||||
client <- commandClient()
|
||||
tracker <- client.trackCommands[Int](submittingPartyList)
|
||||
result <- Source(contexts.map(i => Ctx(i, commandSubmissionWithId(i.toString))))
|
||||
tracker <- client.trackCommands[Int](List(party))
|
||||
result <- Source(contexts.map(i => Ctx(i, commandSubmissionWithId(i.toString, party))))
|
||||
.via(tracker)
|
||||
.map(_.context)
|
||||
.runWith(Sink.seq)
|
||||
} yield {
|
||||
result should contain theSameElementsAs contexts
|
||||
}
|
||||
} yield result should contain theSameElementsAs contexts
|
||||
}
|
||||
|
||||
"complete the stream when there's nothing to track" in {
|
||||
for {
|
||||
party <- freshParty()
|
||||
client <- commandClient()
|
||||
tracker <- client.trackCommands[Int](submittingPartyList)
|
||||
tracker <- client.trackCommands[Int](List(party))
|
||||
_ <- Source.empty[Ctx[Int, CommandSubmission]].via(tracker).runWith(Sink.ignore)
|
||||
} yield {
|
||||
succeed
|
||||
}
|
||||
} yield succeed
|
||||
}
|
||||
|
||||
"not accept commands with missing args, return INVALID_ARGUMENT" in {
|
||||
val expectedMessageSubstring =
|
||||
"Expecting 1 field for record"
|
||||
val commandWithInvalidArgs =
|
||||
submitRequest(
|
||||
"Creating_contracts_for_invalid_arg_test",
|
||||
List(CreateCommand(Some(templateIds.dummy), Some(Record())).wrap),
|
||||
val expectedMessageSubstring = "Expecting 1 field for record"
|
||||
for {
|
||||
party <- freshParty()
|
||||
commandWithInvalidArgs =
|
||||
submitRequest(
|
||||
"Creating_contracts_for_invalid_arg_test",
|
||||
List(CreateCommand(Some(templateIds.dummy), Some(Record())).wrap),
|
||||
party,
|
||||
)
|
||||
a <- assertCommandFailsWithCode(
|
||||
commandWithInvalidArgs,
|
||||
Code.INVALID_ARGUMENT,
|
||||
expectedMessageSubstring,
|
||||
)
|
||||
|
||||
assertCommandFailsWithCode(
|
||||
commandWithInvalidArgs,
|
||||
Code.INVALID_ARGUMENT,
|
||||
expectedMessageSubstring,
|
||||
)
|
||||
} yield a
|
||||
}
|
||||
|
||||
"not accept commands with args of the wrong type, return INVALID_ARGUMENT" in {
|
||||
val expectedMessageSubstring =
|
||||
"mismatching type"
|
||||
val command =
|
||||
submitRequest(
|
||||
"Boolean_param_with_wrong_type",
|
||||
List(
|
||||
CreateCommand(
|
||||
Some(templateIds.dummy),
|
||||
Some(
|
||||
List("operator" -> true.asBoolean)
|
||||
.asRecordOf(templateIds.dummy)
|
||||
),
|
||||
).wrap
|
||||
),
|
||||
val expectedMessageSubstring = "mismatching type"
|
||||
for {
|
||||
party <- freshParty()
|
||||
command =
|
||||
submitRequest(
|
||||
"Boolean_param_with_wrong_type",
|
||||
List(
|
||||
CreateCommand(
|
||||
Some(templateIds.dummy),
|
||||
Some(
|
||||
List("operator" -> true.asBoolean)
|
||||
.asRecordOf(templateIds.dummy)
|
||||
),
|
||||
).wrap
|
||||
),
|
||||
party,
|
||||
)
|
||||
a <- assertCommandFailsWithCode(
|
||||
command,
|
||||
Code.INVALID_ARGUMENT,
|
||||
expectedMessageSubstring,
|
||||
)
|
||||
|
||||
assertCommandFailsWithCode(
|
||||
command,
|
||||
Code.INVALID_ARGUMENT,
|
||||
expectedMessageSubstring,
|
||||
)
|
||||
} yield a
|
||||
}
|
||||
|
||||
"not accept commands with unknown args, return INVALID_ARGUMENT" in {
|
||||
val expectedMessageSubstring =
|
||||
"Missing record field"
|
||||
val command =
|
||||
submitRequest(
|
||||
"Param_with_wrong_name",
|
||||
List(
|
||||
CreateCommand(
|
||||
Some(templateIds.dummy),
|
||||
Some(
|
||||
List("hotdog" -> true.asBoolean)
|
||||
.asRecordOf(templateIds.dummy)
|
||||
),
|
||||
).wrap
|
||||
),
|
||||
val expectedMessageSubstring = "Missing record field"
|
||||
for {
|
||||
party <- freshParty()
|
||||
command =
|
||||
submitRequest(
|
||||
"Param_with_wrong_name",
|
||||
List(
|
||||
CreateCommand(
|
||||
Some(templateIds.dummy),
|
||||
Some(
|
||||
List("hotdog" -> true.asBoolean)
|
||||
.asRecordOf(templateIds.dummy)
|
||||
),
|
||||
).wrap
|
||||
),
|
||||
party,
|
||||
)
|
||||
a <- assertCommandFailsWithCode(
|
||||
command,
|
||||
Code.INVALID_ARGUMENT,
|
||||
expectedMessageSubstring,
|
||||
)
|
||||
|
||||
assertCommandFailsWithCode(
|
||||
command,
|
||||
Code.INVALID_ARGUMENT,
|
||||
expectedMessageSubstring,
|
||||
)
|
||||
} yield a
|
||||
}
|
||||
|
||||
"not accept commands with malformed decimals, return INVALID_ARGUMENT" in {
|
||||
import com.daml.ledger.api.v1.value._
|
||||
|
||||
val commandId = "Malformed_decimal"
|
||||
val expectedMessageSubString =
|
||||
"""Could not read Numeric string "1E-19""""
|
||||
val expectedMessageSubString = """Could not read Numeric string "1E-19""""
|
||||
|
||||
val command = submitRequest(
|
||||
commandId,
|
||||
List(
|
||||
CreateCommand(
|
||||
Some(templateIds.parameterShowcase),
|
||||
Some(recordWithArgument(paramShowcaseArgs, RecordField("decimal", "1E-19".asNumeric))),
|
||||
).wrap
|
||||
),
|
||||
)
|
||||
|
||||
assertCommandFailsWithCode(command, Code.INVALID_ARGUMENT, expectedMessageSubString)
|
||||
}
|
||||
|
||||
"not accept commands with bad obligables, return INVALID_ARGUMENT" in {
|
||||
val command =
|
||||
submitRequest(
|
||||
"Obligable_error",
|
||||
List(
|
||||
for {
|
||||
party <- freshParty()
|
||||
command = submitRequest(
|
||||
commandId,
|
||||
Seq(
|
||||
CreateCommand(
|
||||
Some(templateIds.dummy),
|
||||
Some(templateIds.parameterShowcase),
|
||||
Some(
|
||||
List("operator" -> ("not" + MockMessages.party).asParty)
|
||||
.asRecordOf(templateIds.dummy)
|
||||
recordWithArgument(paramShowcaseArgs, RecordField("decimal", "1E-19".asNumeric))
|
||||
),
|
||||
).wrap
|
||||
),
|
||||
party,
|
||||
)
|
||||
a <- assertCommandFailsWithCode(command, Code.INVALID_ARGUMENT, expectedMessageSubString)
|
||||
} yield a
|
||||
}
|
||||
|
||||
"not accept commands with bad obligables, return INVALID_ARGUMENT" in {
|
||||
for {
|
||||
party <- freshParty()
|
||||
command =
|
||||
submitRequest(
|
||||
"Obligable_error",
|
||||
List(
|
||||
CreateCommand(
|
||||
Some(templateIds.dummy),
|
||||
Some(
|
||||
List("operator" -> ("not" + party).asParty)
|
||||
.asRecordOf(templateIds.dummy)
|
||||
),
|
||||
).wrap
|
||||
),
|
||||
party,
|
||||
)
|
||||
a <- assertCommandFailsWithCode(command, Code.INVALID_ARGUMENT, "requires authorizers")
|
||||
} yield a
|
||||
|
||||
assertCommandFailsWithCode(command, Code.INVALID_ARGUMENT, "requires authorizers")
|
||||
}
|
||||
|
||||
"not accept exercises with bad contract IDs, return ABORTED" in {
|
||||
val contractId = ContractId.V1(
|
||||
Hash.hashPrivateKey(
|
||||
"#deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef-123"
|
||||
)
|
||||
)
|
||||
val command =
|
||||
submitRequest(
|
||||
"Exercise_contract_not_found",
|
||||
List(
|
||||
ExerciseCommand(
|
||||
Some(templateIds.dummy),
|
||||
contractId.coid,
|
||||
"DummyChoice1",
|
||||
Some(unit),
|
||||
).wrap
|
||||
),
|
||||
)
|
||||
val dummySuffix: Bytes = Bytes.assertFromString("00")
|
||||
val contractId =
|
||||
ContractId.V1.assertBuild(crypto.Hash.hashPrivateKey("secret"), dummySuffix)
|
||||
for {
|
||||
party <- freshParty()
|
||||
command =
|
||||
submitRequest(
|
||||
"Exercise_contract_not_found",
|
||||
List(
|
||||
ExerciseCommand(
|
||||
Some(templateIds.dummy),
|
||||
contractId.coid,
|
||||
"DummyChoice1",
|
||||
Some(unit),
|
||||
).wrap
|
||||
),
|
||||
party,
|
||||
)
|
||||
a <- assertCommandFailsWithCode(command, Code.NOT_FOUND, "CONTRACT_NOT_FOUND")
|
||||
} yield a
|
||||
|
||||
assertCommandFailsWithCode(command, Code.NOT_FOUND, "CONTRACT_NOT_FOUND")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,32 +4,25 @@
|
||||
package com.daml.ledger.client
|
||||
|
||||
import com.daml.grpc.GrpcException
|
||||
import com.daml.ledger.api.domain
|
||||
import com.daml.ledger.api.testing.utils.{AkkaBeforeAndAfterAll, SuiteResourceManagementAroundEach}
|
||||
import com.daml.jwt.JwtSigner
|
||||
import com.daml.jwt.domain.DecodedJwt
|
||||
import com.daml.ledger.api.auth.AuthServiceJWTCodec
|
||||
import com.daml.ledger.api.auth.CustomDamlJWTPayload
|
||||
import com.daml.ledger.client.configuration.{
|
||||
CommandClientConfiguration,
|
||||
LedgerClientConfiguration,
|
||||
LedgerIdRequirement,
|
||||
CommandClientConfiguration,
|
||||
}
|
||||
import com.daml.ledger.runner.common.Config
|
||||
import com.daml.platform.sandbox.SandboxRequiringAuthorization
|
||||
import com.daml.platform.sandbox.fixture.SandboxFixture
|
||||
import com.daml.lf.integrationtest.CantonFixture
|
||||
import org.scalatest.Inside
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
import org.scalatest.wordspec.AsyncWordSpec
|
||||
import scalaz.syntax.tag._
|
||||
|
||||
final class LedgerClientAuthIT
|
||||
extends AsyncWordSpec
|
||||
with Matchers
|
||||
with Inside
|
||||
with AkkaBeforeAndAfterAll
|
||||
with SuiteResourceManagementAroundEach
|
||||
with SandboxFixture
|
||||
with SandboxRequiringAuthorization {
|
||||
final class LedgerClientAuthIT extends AsyncWordSpec with Matchers with Inside with CantonFixture {
|
||||
|
||||
private val LedgerId =
|
||||
domain.LedgerId(s"${classOf[LedgerClientAuthIT].getSimpleName.toLowerCase}-ledger-id")
|
||||
protected val jwtSecret: String = java.util.UUID.randomUUID.toString
|
||||
|
||||
override protected lazy val authSecret = Some(jwtSecret)
|
||||
|
||||
private val ClientConfigurationWithoutToken = LedgerClientConfiguration(
|
||||
applicationId = classOf[LedgerClientAuthIT].getSimpleName,
|
||||
@ -38,11 +31,32 @@ final class LedgerClientAuthIT
|
||||
token = None,
|
||||
)
|
||||
|
||||
private val ClientConfiguration = ClientConfigurationWithoutToken.copy(
|
||||
token = Some(toHeader(readOnlyToken("Read-only party")))
|
||||
private val emptyToken = CustomDamlJWTPayload(
|
||||
ledgerId = None,
|
||||
participantId = None,
|
||||
applicationId = None,
|
||||
exp = None,
|
||||
admin = false,
|
||||
actAs = Nil,
|
||||
readAs = Nil,
|
||||
)
|
||||
|
||||
override protected def config: Config = super.config.copy(ledgerId = LedgerId.unwrap)
|
||||
private val ClientConfiguration = ClientConfigurationWithoutToken.copy(
|
||||
token = Some(
|
||||
JwtSigner.HMAC256
|
||||
.sign(
|
||||
DecodedJwt(
|
||||
"""{"alg": "HS256", "typ": "JWT"}""",
|
||||
AuthServiceJWTCodec.compactPrint(emptyToken.copy(readAs = List("Alice")), false),
|
||||
),
|
||||
jwtSecret,
|
||||
)
|
||||
.getOrElse(sys.error("Failed to generate token"))
|
||||
.value
|
||||
)
|
||||
)
|
||||
|
||||
lazy val channel = config.channel(suiteResource.value.head)
|
||||
|
||||
"the ledger client" when {
|
||||
"it has a read-only token" should {
|
||||
@ -50,7 +64,7 @@ final class LedgerClientAuthIT
|
||||
for {
|
||||
client <- LedgerClient(channel, ClientConfiguration)
|
||||
} yield {
|
||||
client.ledgerId should be(LedgerId)
|
||||
client.ledgerId should be(config.ledgerIds.head)
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,7 +89,7 @@ final class LedgerClientAuthIT
|
||||
.allocateParty(
|
||||
hint = Some(partyName),
|
||||
displayName = Some(partyName),
|
||||
token = Some(toHeader(adminToken)),
|
||||
token = config.adminToken,
|
||||
)
|
||||
} yield {
|
||||
allocatedParty.displayName should be(Some(partyName))
|
||||
|
@ -4,16 +4,14 @@
|
||||
package com.daml.ledger.client
|
||||
|
||||
import com.daml.ledger.api.domain
|
||||
import com.daml.ledger.api.domain.{IdentityProviderConfig, IdentityProviderId, JwksUrl}
|
||||
import com.daml.ledger.api.testing.utils.{AkkaBeforeAndAfterAll, SuiteResourceManagementAroundEach}
|
||||
import com.daml.ledger.api.domain.{IdentityProviderId}
|
||||
import com.daml.ledger.client.configuration.{
|
||||
CommandClientConfiguration,
|
||||
LedgerClientConfiguration,
|
||||
LedgerIdRequirement,
|
||||
}
|
||||
import com.daml.ledger.runner.common.Config
|
||||
import com.daml.lf.integrationtest.CantonFixture
|
||||
import com.daml.lf.data.Ref
|
||||
import com.daml.platform.sandbox.fixture.SandboxFixture
|
||||
import com.google.protobuf.field_mask.FieldMask
|
||||
import io.grpc.ManagedChannel
|
||||
import org.scalatest.Inside
|
||||
@ -22,26 +20,19 @@ import org.scalatest.wordspec.AsyncWordSpec
|
||||
import scalaz.OneAnd
|
||||
import scalaz.syntax.tag._
|
||||
|
||||
final class LedgerClientIT
|
||||
extends AsyncWordSpec
|
||||
with Matchers
|
||||
with Inside
|
||||
with AkkaBeforeAndAfterAll
|
||||
with SuiteResourceManagementAroundEach
|
||||
with SandboxFixture {
|
||||
final class LedgerClientIT extends AsyncWordSpec with Matchers with Inside with CantonFixture {
|
||||
|
||||
private val LedgerId =
|
||||
domain.LedgerId(s"${classOf[LedgerClientIT].getSimpleName.toLowerCase}-ledger-id")
|
||||
private val LedgerId = domain.LedgerId(config.ledgerIds.head)
|
||||
|
||||
lazy val channel = config.channel(suiteResource.value.head)
|
||||
|
||||
private val ClientConfiguration = LedgerClientConfiguration(
|
||||
applicationId = classOf[LedgerClientIT].getSimpleName,
|
||||
applicationId = applicationId.unwrap,
|
||||
ledgerIdRequirement = LedgerIdRequirement.none,
|
||||
commandClient = CommandClientConfiguration.default,
|
||||
token = None,
|
||||
)
|
||||
|
||||
override protected def config: Config = super.config.copy(ledgerId = LedgerId.unwrap)
|
||||
|
||||
"the ledger client" should {
|
||||
"retrieve the ledger ID" in {
|
||||
for {
|
||||
@ -52,14 +43,14 @@ final class LedgerClientIT
|
||||
}
|
||||
|
||||
"make some requests" in {
|
||||
val partyName = "Alice"
|
||||
val partyName = CantonFixture.freshName("Alice")
|
||||
for {
|
||||
client <- LedgerClient(channel, ClientConfiguration)
|
||||
// The request type is irrelevant here; the point is that we can make some.
|
||||
allocatedParty <- client.partyManagementClient
|
||||
.allocateParty(hint = Some(partyName), displayName = None)
|
||||
retrievedParties <- client.partyManagementClient
|
||||
.getParties(OneAnd(Ref.Party.assertFromString(partyName), Set.empty))
|
||||
.getParties(OneAnd(Ref.Party.assertFromString(allocatedParty.party), Set.empty))
|
||||
} yield {
|
||||
retrievedParties should be(List(allocatedParty))
|
||||
}
|
||||
@ -78,22 +69,25 @@ final class LedgerClientIT
|
||||
}
|
||||
|
||||
"identity provider config" should {
|
||||
val config = IdentityProviderConfig(
|
||||
IdentityProviderId.Id(Ref.LedgerString.assertFromString("abcd")),
|
||||
def freshConfig(): domain.IdentityProviderConfig = domain.IdentityProviderConfig(
|
||||
domain.IdentityProviderId.Id(
|
||||
Ref.LedgerString.assertFromString(CantonFixture.freshName("abcd"))
|
||||
),
|
||||
isDeactivated = false,
|
||||
JwksUrl.assertFromString("http://jwks.some.domain:9999/jwks"),
|
||||
"SomeUser",
|
||||
Some("SomeAudience"),
|
||||
domain.JwksUrl.assertFromString("http://jwks.some.domain:9999/jwks"),
|
||||
CantonFixture.freshName("SomeUser"),
|
||||
Some(CantonFixture.freshName("SomeAudience")),
|
||||
)
|
||||
|
||||
val updatedConfig = config.copy(
|
||||
def updateConfig(config: domain.IdentityProviderConfig) = config.copy(
|
||||
isDeactivated = true,
|
||||
jwksUrl = JwksUrl("http://someotherurl"),
|
||||
issuer = "ANewIssuer",
|
||||
audience = Some("ChangedAudience"),
|
||||
jwksUrl = domain.JwksUrl("http://someotherurl"),
|
||||
issuer = CantonFixture.freshName("ANewIssuer"),
|
||||
audience = Some(CantonFixture.freshName("ChangedAudience")),
|
||||
)
|
||||
|
||||
"create an identity provider" in {
|
||||
val config = freshConfig()
|
||||
for {
|
||||
client <- LedgerClient(channel, ClientConfiguration)
|
||||
createdConfig <- client.identityProviderConfigClient.createIdentityProviderConfig(
|
||||
@ -105,6 +99,7 @@ final class LedgerClientIT
|
||||
}
|
||||
}
|
||||
"get an identity provider" in {
|
||||
val config = freshConfig()
|
||||
for {
|
||||
client <- LedgerClient(channel, ClientConfiguration)
|
||||
_ <- client.identityProviderConfigClient.createIdentityProviderConfig(config, None)
|
||||
@ -117,6 +112,8 @@ final class LedgerClientIT
|
||||
}
|
||||
}
|
||||
"update an identity provider" in {
|
||||
val config = freshConfig()
|
||||
val updatedConfig = updateConfig(config)
|
||||
for {
|
||||
client <- LedgerClient(channel, ClientConfiguration)
|
||||
_ <- client.identityProviderConfigClient.createIdentityProviderConfig(config, None)
|
||||
@ -136,8 +133,11 @@ final class LedgerClientIT
|
||||
}
|
||||
|
||||
"list identity providers" in {
|
||||
val config = freshConfig()
|
||||
val updatedConfig = updateConfig(config)
|
||||
for {
|
||||
client <- LedgerClient(channel, ClientConfiguration)
|
||||
before <- client.identityProviderConfigClient.listIdentityProviderConfigs(None)
|
||||
config1 <- client.identityProviderConfigClient.createIdentityProviderConfig(config, None)
|
||||
config2 <- client.identityProviderConfigClient.createIdentityProviderConfig(
|
||||
updatedConfig.copy(identityProviderId =
|
||||
@ -145,23 +145,25 @@ final class LedgerClientIT
|
||||
),
|
||||
None,
|
||||
)
|
||||
respConfig <- client.identityProviderConfigClient.listIdentityProviderConfigs(None)
|
||||
after <- client.identityProviderConfigClient.listIdentityProviderConfigs(None)
|
||||
} yield {
|
||||
respConfig.toSet should contain theSameElementsAs (Set(config2, config1))
|
||||
(after.toSet -- before) should contain theSameElementsAs Set(config2, config1)
|
||||
}
|
||||
}
|
||||
|
||||
"delete identity provider" in {
|
||||
val config = freshConfig()
|
||||
for {
|
||||
client <- LedgerClient(channel, ClientConfiguration)
|
||||
before <- client.identityProviderConfigClient.listIdentityProviderConfigs(None)
|
||||
config1 <- client.identityProviderConfigClient.createIdentityProviderConfig(config, None)
|
||||
_ <- client.identityProviderConfigClient.deleteIdentityProviderConfig(
|
||||
config1.identityProviderId,
|
||||
None,
|
||||
)
|
||||
respConfig <- client.identityProviderConfigClient.listIdentityProviderConfigs(None)
|
||||
after <- client.identityProviderConfigClient.listIdentityProviderConfigs(None)
|
||||
} yield {
|
||||
respConfig.toSet should be(Set.empty)
|
||||
before.toSet should be(after.toSet)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user