Migrate LedgerClient test from to Canton (#16795)

This commit is contained in:
Remy 2023-05-10 11:16:40 +02:00 committed by GitHub
parent 99cbff4eff
commit 90c023e98e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 478 additions and 216 deletions

View File

@ -44,6 +44,7 @@ da_scala_library(
"//libs-scala/resources", "//libs-scala/resources",
"//libs-scala/scala-utils", "//libs-scala/scala-utils",
"//libs-scala/timer-utils", "//libs-scala/timer-utils",
"//test-common",
"@maven//:org_scalatest_scalatest_compatible", "@maven//:org_scalatest_scalatest_compatible",
], ],
) )

View File

@ -22,7 +22,8 @@ import org.scalatest.Suite
import scala.concurrent.duration.DurationInt import scala.concurrent.duration.DurationInt
import scala.concurrent.{ExecutionContext, Future} 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") @scala.annotation.nowarn("msg=match may not be exhaustive")
object CantonFixture { object CantonFixture {
@ -46,16 +47,14 @@ object CantonFixture {
Paths.get(rlocation("test-common/test-certificates/" + src)) 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 = { def freshName(prefix: String): String = {
assert(!prefix.contains('_')) 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"))
} }

View File

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

View File

@ -86,7 +86,7 @@ da_scala_test_suite(
name = "ledger-api-client-integration-tests", name = "ledger-api-client-integration-tests",
srcs = glob(["src/it/**/*.scala"]), srcs = glob(["src/it/**/*.scala"]),
data = [ data = [
"//test-common:dar-files", "//test-common:dar-files-default",
], ],
resources = [ resources = [
"src/it/resources/logback-test.xml", "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_actor",
"@maven//:com_typesafe_akka_akka_stream", "@maven//:com_typesafe_akka_akka_stream",
], ],
tags = ["cpu:4"],
deps = [ deps = [
":ledger-api-client", ":ledger-api-client",
"//bazel_tools/runfiles:scala_runfiles",
"//daml-lf/engine", "//daml-lf/engine",
"//daml-lf/integration-test-lib",
"//daml-lf/transaction", "//daml-lf/transaction",
"//language-support/scala/bindings", "//language-support/scala/bindings",
"//ledger-api/rs-grpc-bridge", "//ledger-api/rs-grpc-bridge",
@ -117,6 +120,7 @@ da_scala_test_suite(
"//libs-scala/concurrent", "//libs-scala/concurrent",
"//libs-scala/contextualized-logging", "//libs-scala/contextualized-logging",
"//libs-scala/grpc-utils", "//libs-scala/grpc-utils",
"//libs-scala/jwt",
"//libs-scala/ledger-resources", "//libs-scala/ledger-resources",
"//libs-scala/logging-entries", "//libs-scala/logging-entries",
"//libs-scala/ports", "//libs-scala/ports",
@ -124,6 +128,7 @@ da_scala_test_suite(
"//observability/metrics", "//observability/metrics",
"//observability/tracing", "//observability/tracing",
"//test-common", "//test-common",
"//test-common:dar-files-default-lib",
"@maven//:ch_qos_logback_logback_classic", "@maven//:ch_qos_logback_logback_classic",
"@maven//:io_netty_netty_handler", "@maven//:io_netty_netty_handler",
], ],

View File

@ -8,12 +8,9 @@ import java.util.concurrent.TimeUnit
import akka.NotUsed import akka.NotUsed
import akka.stream.scaladsl.{Sink, Source} import akka.stream.scaladsl.{Sink, Source}
import com.daml.api.util.TimeProvider import com.daml.api.util.TimeProvider
import com.daml.bazeltools.BazelRunfiles
import com.daml.ledger.api.domain import com.daml.ledger.api.domain
import com.daml.ledger.api.testing.utils.{ import com.daml.ledger.api.testing.utils.{IsStatusException, SuiteResourceManagementAroundAll}
IsStatusException,
MockMessages,
SuiteResourceManagementAroundAll,
}
import com.daml.ledger.api.v1.command_completion_service.CommandCompletionServiceGrpc import com.daml.ledger.api.v1.command_completion_service.CommandCompletionServiceGrpc
import com.daml.ledger.api.v1.command_submission_service.{ import com.daml.ledger.api.v1.command_submission_service.{
CommandSubmissionServiceGrpc, CommandSubmissionServiceGrpc,
@ -37,12 +34,12 @@ import com.daml.ledger.client.services.commands.{
CompletionStreamElement, CompletionStreamElement,
} }
import com.daml.ledger.client.services.testing.time.StaticTime import com.daml.ledger.client.services.testing.time.StaticTime
import com.daml.ledger.runner.common.Config import com.daml.ledger.test.ModelTestDar
import com.daml.lf.crypto.Hash 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.lf.value.Value.ContractId
import com.daml.platform.participant.util.ValueConversions._ 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.daml.util.Ctx
import com.google.rpc.code.Code import com.google.rpc.code.Code
import io.grpc.{Status, StatusRuntimeException} import io.grpc.{Status, StatusRuntimeException}
@ -58,15 +55,21 @@ import scala.concurrent.duration.FiniteDuration
import scala.util.Success import scala.util.Success
import scala.util.control.NonFatal import scala.util.control.NonFatal
import java.nio.file.Paths
final class CommandClientIT final class CommandClientIT
extends AsyncWordSpec extends AsyncWordSpec
with TestCommands with TestCommands
with SandboxFixture with CantonFixture
with Matchers with Matchers
with SuiteResourceManagementAroundAll with SuiteResourceManagementAroundAll
with TryValues with TryValues
with Inside { 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 = private val defaultCommandClientConfiguration =
CommandClientConfiguration( CommandClientConfiguration(
maxCommandsInFlight = 1, maxCommandsInFlight = 1,
@ -74,19 +77,28 @@ final class CommandClientIT
defaultDeduplicationTime = Duration.ofSeconds(30), defaultDeduplicationTime = Duration.ofSeconds(30),
) )
private val testLedgerId = domain.LedgerId("ledgerId") private val testLedgerId =
private val testNotLedgerId = domain.LedgerId("hotdog") 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( private def commandClientWithoutTime(
ledgerId: domain.LedgerId, ledgerId: domain.LedgerId,
applicationId: String = MockMessages.applicationId, appId: String = applicationId.unwrap,
configuration: CommandClientConfiguration = defaultCommandClientConfiguration, configuration: CommandClientConfiguration = defaultCommandClientConfiguration,
): CommandClient = ): CommandClient =
new CommandClient( new CommandClient(
CommandSubmissionServiceGrpc.stub(channel), CommandSubmissionServiceGrpc.stub(channel),
CommandCompletionServiceGrpc.stub(channel), CommandCompletionServiceGrpc.stub(channel),
ledgerId, ledgerId,
applicationId, appId,
configuration, configuration,
) )
@ -100,21 +112,30 @@ final class CommandClientIT
private def commandClient( private def commandClient(
ledgerId: domain.LedgerId = testLedgerId, ledgerId: domain.LedgerId = testLedgerId,
applicationId: String = MockMessages.applicationId, appId: String = applicationId.unwrap,
configuration: CommandClientConfiguration = defaultCommandClientConfiguration, configuration: CommandClientConfiguration = defaultCommandClientConfiguration,
): Future[CommandClient] = ): Future[CommandClient] =
timeProvider(ledgerId) 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 val LedgerBegin = LedgerOffset(Boundary(LEDGER_BEGIN))
private def submitRequest(commandId: String, individualCommands: Seq[Command]): SubmitRequest = private def submitRequest(
buildRequest(testLedgerId, commandId, individualCommands) 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( submitRequest(
commandId, commandId,
List( List(
@ -123,15 +144,16 @@ final class CommandClientIT
Some( Some(
Record( Record(
Some(templateIds.dummy), Some(templateIds.dummy),
Seq(RecordField("operator", Option(MockMessages.party.asParty))), Seq(RecordField("operator", Some(party.asParty))),
) )
), ),
).wrap ).wrap
), ),
party,
) )
private def commandSubmissionWithId(commandId: String): CommandSubmission = private def commandSubmissionWithId(commandId: String, party: Ref.Party): CommandSubmission =
CommandSubmission(submitRequestWithId(commandId).getCommands) CommandSubmission(submitRequestWithId(commandId, party).getCommands)
// Commands and completions can be read out of order. Since we use GRPC monocalls to send, // Commands and completions can be read out of order. Since we use GRPC monocalls to send,
// they can even be sent out of order. // they can even be sent out of order.
@ -177,12 +199,13 @@ final class CommandClientIT
*/ */
private def readExpectedCommandIds( private def readExpectedCommandIds(
client: CommandClient, client: CommandClient,
party: Ref.Party,
checkpoint: LedgerOffset, checkpoint: LedgerOffset,
expected: Set[String], expected: Set[String],
timeLimit: Span = 6.seconds, timeLimit: Span = 6.seconds,
): Future[(Set[String], Set[String])] = ): Future[(Set[String], Set[String])] =
readExpectedElements( readExpectedElements(
client.completionSource(submittingPartyList, checkpoint).collect { client.completionSource(List(party), checkpoint).collect {
case CompletionStreamElement.CompletionElement(c, _) => c.commandId case CompletionStreamElement.CompletionElement(c, _) => c.commandId
}, },
expected, expected,
@ -221,7 +244,7 @@ final class CommandClientIT
} }
"fail with the expected status on a ledger Id mismatch" in { "fail with the expected status on a ledger Id mismatch" in {
commandClientWithoutTime(testNotLedgerId) commandClientWithoutTime(ledgerId = testNotLedgerId)
.getCompletionEnd() .getCompletionEnd()
.failed map IsStatusException(Status.NOT_FOUND) .failed map IsStatusException(Status.NOT_FOUND)
} }
@ -233,8 +256,9 @@ final class CommandClientIT
val contexts = 1 to 10 val contexts = 1 to 10
for { for {
party <- freshParty()
client <- commandClient() 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()) .via(client.submissionFlow())
.map(_.map(_.isSuccess)) .map(_.map(_.isSuccess))
.runWith(Sink.seq) .runWith(Sink.seq)
@ -244,21 +268,26 @@ final class CommandClientIT
} }
"fail with the expected status on a ledger Id mismatch" in { "fail with the expected status on a ledger Id mismatch" in {
val aSubmission = commandSubmissionWithId("1")
val submission = aSubmission.copy( for {
commands = aSubmission.commands.update(_.ledgerId := testNotLedgerId.unwrap) party <- freshParty()
) aSubmission = commandSubmissionWithId("1", party)
Source submission = aSubmission.copy(
.single(Ctx(1, submission)) commands = aSubmission.commands.update(_.ledgerId := testNotLedgerId.unwrap)
.via(commandClientWithoutTime(testNotLedgerId).submissionFlow()) )
.runWith(Sink.head) err <- Source
.map(err => IsStatusException(Status.NOT_FOUND)(err.value.failure.exception)) .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 { "fail with INVALID REQUEST for empty application ids" in {
val request = submitRequestWithId("7000").update(_.commands.applicationId := "")
val resF = for { val resF = for {
client <- commandClient(applicationId = "") party <- freshParty()
request = submitRequestWithId("7000", party).update(_.commands.applicationId := "")
client <- commandClient(appId = "")
res <- client.submitSingleCommand(request) res <- client.submitSingleCommand(request)
} yield res } yield res
@ -275,8 +304,9 @@ final class CommandClientIT
"fail with INVALID REQUEST for empty application ids" in { "fail with INVALID REQUEST for empty application ids" in {
val completionsF = for { val completionsF = for {
client <- commandClient(applicationId = "") party <- freshParty()
completionsSource = client.completionSource(submittingPartyList, LedgerBegin) client <- commandClient(appId = "")
completionsSource = client.completionSource(List(party), LedgerBegin)
completions <- completionsSource.takeWithin(5.seconds).runWith(Sink.seq) completions <- completionsSource.takeWithin(5.seconds).runWith(Sink.seq)
} yield completions } yield completions
@ -289,10 +319,13 @@ final class CommandClientIT
} }
"fail with the expected status on a ledger Id mismatch" in { "fail with the expected status on a ledger Id mismatch" in {
commandClientWithoutTime(testNotLedgerId) for {
.completionSource(submittingPartyList, LedgerBegin) party <- freshParty()
.runWith(Sink.head) err <- commandClientWithoutTime(ledgerId = testNotLedgerId)
.failed map IsStatusException(Status.NOT_FOUND) .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 { "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 for type inference
val resultF = for { val resultF = for {
party <- freshParty()
client <- commandClient() client <- commandClient()
checkpoint <- client.getCompletionEnd() checkpoint <- client.getCompletionEnd()
submissionResults <- Source( submissionResults <- Source(
commandIds.map(i => Ctx(i, commandSubmissionWithId(i.toString))) commandIds.map(i => Ctx(i, commandSubmissionWithId(i.toString, party)))
) )
.flatMapMerge(10, randomDelay) .flatMapMerge(10, randomDelay)
.via(client.submissionFlow()) .via(client.submissionFlow())
@ -315,10 +349,8 @@ final class CommandClientIT
.runWith(Sink.seq) .runWith(Sink.seq)
_ = submissionResults.foreach(v => v shouldBe a[Success[_]]) _ = submissionResults.foreach(v => v shouldBe a[Success[_]])
result <- readExpectedCommandIds(client, checkpoint.getOffset, commandIdStrings) result <- readExpectedCommandIds(client, party, checkpoint.getOffset, commandIdStrings)
} yield { } yield result
result
}
resultF map { case (seenCommandIds, remainingCommandIds) => resultF map { case (seenCommandIds, remainingCommandIds) =>
// N.B.: completions may include already-seen elements, and may be out of order // 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): _*) val commandIdStrings = Set(commandIds.map(_.toString): _*)
for { for {
party <- freshParty()
client <- commandClient() client <- commandClient()
checkpoint <- client.getCompletionEnd() 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) .flatMapMerge(10, randomDelay)
.via(client.submissionFlow()) .via(client.submissionFlow())
.map(_.context) .map(_.context)
.runWith(Sink.ignore) .runWith(Sink.ignore)
(seenCommandIds, remainingCommandIds) <- readExpectedCommandIds( (seenCommandIds, remainingCommandIds) <- readExpectedCommandIds(
client, client,
party,
checkpoint.getOffset, checkpoint.getOffset,
commandIdStrings, commandIdStrings,
) )
@ -359,149 +393,164 @@ final class CommandClientIT
"return the contexts for commands as they are completed" in { "return the contexts for commands as they are completed" in {
val contexts = 6001.to(6010) val contexts = 6001.to(6010)
for { for {
party <- freshParty()
client <- commandClient() client <- commandClient()
tracker <- client.trackCommands[Int](submittingPartyList) tracker <- client.trackCommands[Int](List(party))
result <- Source(contexts.map(i => Ctx(i, commandSubmissionWithId(i.toString)))) result <- Source(contexts.map(i => Ctx(i, commandSubmissionWithId(i.toString, party))))
.via(tracker) .via(tracker)
.map(_.context) .map(_.context)
.runWith(Sink.seq) .runWith(Sink.seq)
} yield { } yield result should contain theSameElementsAs contexts
result should contain theSameElementsAs contexts
}
} }
"complete the stream when there's nothing to track" in { "complete the stream when there's nothing to track" in {
for { for {
party <- freshParty()
client <- commandClient() client <- commandClient()
tracker <- client.trackCommands[Int](submittingPartyList) tracker <- client.trackCommands[Int](List(party))
_ <- Source.empty[Ctx[Int, CommandSubmission]].via(tracker).runWith(Sink.ignore) _ <- Source.empty[Ctx[Int, CommandSubmission]].via(tracker).runWith(Sink.ignore)
} yield { } yield succeed
succeed
}
} }
"not accept commands with missing args, return INVALID_ARGUMENT" in { "not accept commands with missing args, return INVALID_ARGUMENT" in {
val expectedMessageSubstring = val expectedMessageSubstring = "Expecting 1 field for record"
"Expecting 1 field for record" for {
val commandWithInvalidArgs = party <- freshParty()
submitRequest( commandWithInvalidArgs =
"Creating_contracts_for_invalid_arg_test", submitRequest(
List(CreateCommand(Some(templateIds.dummy), Some(Record())).wrap), "Creating_contracts_for_invalid_arg_test",
List(CreateCommand(Some(templateIds.dummy), Some(Record())).wrap),
party,
)
a <- assertCommandFailsWithCode(
commandWithInvalidArgs,
Code.INVALID_ARGUMENT,
expectedMessageSubstring,
) )
} yield a
assertCommandFailsWithCode(
commandWithInvalidArgs,
Code.INVALID_ARGUMENT,
expectedMessageSubstring,
)
} }
"not accept commands with args of the wrong type, return INVALID_ARGUMENT" in { "not accept commands with args of the wrong type, return INVALID_ARGUMENT" in {
val expectedMessageSubstring = val expectedMessageSubstring = "mismatching type"
"mismatching type" for {
val command = party <- freshParty()
submitRequest( command =
"Boolean_param_with_wrong_type", submitRequest(
List( "Boolean_param_with_wrong_type",
CreateCommand( List(
Some(templateIds.dummy), CreateCommand(
Some( Some(templateIds.dummy),
List("operator" -> true.asBoolean) Some(
.asRecordOf(templateIds.dummy) List("operator" -> true.asBoolean)
), .asRecordOf(templateIds.dummy)
).wrap ),
), ).wrap
),
party,
)
a <- assertCommandFailsWithCode(
command,
Code.INVALID_ARGUMENT,
expectedMessageSubstring,
) )
} yield a
assertCommandFailsWithCode(
command,
Code.INVALID_ARGUMENT,
expectedMessageSubstring,
)
} }
"not accept commands with unknown args, return INVALID_ARGUMENT" in { "not accept commands with unknown args, return INVALID_ARGUMENT" in {
val expectedMessageSubstring = val expectedMessageSubstring = "Missing record field"
"Missing record field" for {
val command = party <- freshParty()
submitRequest( command =
"Param_with_wrong_name", submitRequest(
List( "Param_with_wrong_name",
CreateCommand( List(
Some(templateIds.dummy), CreateCommand(
Some( Some(templateIds.dummy),
List("hotdog" -> true.asBoolean) Some(
.asRecordOf(templateIds.dummy) List("hotdog" -> true.asBoolean)
), .asRecordOf(templateIds.dummy)
).wrap ),
), ).wrap
),
party,
)
a <- assertCommandFailsWithCode(
command,
Code.INVALID_ARGUMENT,
expectedMessageSubstring,
) )
} yield a
assertCommandFailsWithCode(
command,
Code.INVALID_ARGUMENT,
expectedMessageSubstring,
)
} }
"not accept commands with malformed decimals, return INVALID_ARGUMENT" in { "not accept commands with malformed decimals, return INVALID_ARGUMENT" in {
import com.daml.ledger.api.v1.value._
val commandId = "Malformed_decimal" val commandId = "Malformed_decimal"
val expectedMessageSubString = val expectedMessageSubString = """Could not read Numeric string "1E-19""""
"""Could not read Numeric string "1E-19""""
val command = submitRequest( for {
commandId, party <- freshParty()
List( command = submitRequest(
CreateCommand( commandId,
Some(templateIds.parameterShowcase), Seq(
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(
CreateCommand( CreateCommand(
Some(templateIds.dummy), Some(templateIds.parameterShowcase),
Some( Some(
List("operator" -> ("not" + MockMessages.party).asParty) recordWithArgument(paramShowcaseArgs, RecordField("decimal", "1E-19".asNumeric))
.asRecordOf(templateIds.dummy)
), ),
).wrap ).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 { "not accept exercises with bad contract IDs, return ABORTED" in {
val contractId = ContractId.V1( val dummySuffix: Bytes = Bytes.assertFromString("00")
Hash.hashPrivateKey( val contractId =
"#deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef-123" ContractId.V1.assertBuild(crypto.Hash.hashPrivateKey("secret"), dummySuffix)
) for {
) party <- freshParty()
val command = command =
submitRequest( submitRequest(
"Exercise_contract_not_found", "Exercise_contract_not_found",
List( List(
ExerciseCommand( ExerciseCommand(
Some(templateIds.dummy), Some(templateIds.dummy),
contractId.coid, contractId.coid,
"DummyChoice1", "DummyChoice1",
Some(unit), Some(unit),
).wrap ).wrap
), ),
) party,
)
a <- assertCommandFailsWithCode(command, Code.NOT_FOUND, "CONTRACT_NOT_FOUND")
} yield a
assertCommandFailsWithCode(command, Code.NOT_FOUND, "CONTRACT_NOT_FOUND")
} }
} }
} }

View File

@ -4,32 +4,25 @@
package com.daml.ledger.client package com.daml.ledger.client
import com.daml.grpc.GrpcException import com.daml.grpc.GrpcException
import com.daml.ledger.api.domain import com.daml.jwt.JwtSigner
import com.daml.ledger.api.testing.utils.{AkkaBeforeAndAfterAll, SuiteResourceManagementAroundEach} 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.{ import com.daml.ledger.client.configuration.{
CommandClientConfiguration,
LedgerClientConfiguration, LedgerClientConfiguration,
LedgerIdRequirement, LedgerIdRequirement,
CommandClientConfiguration,
} }
import com.daml.ledger.runner.common.Config import com.daml.lf.integrationtest.CantonFixture
import com.daml.platform.sandbox.SandboxRequiringAuthorization
import com.daml.platform.sandbox.fixture.SandboxFixture
import org.scalatest.Inside import org.scalatest.Inside
import org.scalatest.matchers.should.Matchers import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AsyncWordSpec import org.scalatest.wordspec.AsyncWordSpec
import scalaz.syntax.tag._
final class LedgerClientAuthIT final class LedgerClientAuthIT extends AsyncWordSpec with Matchers with Inside with CantonFixture {
extends AsyncWordSpec
with Matchers
with Inside
with AkkaBeforeAndAfterAll
with SuiteResourceManagementAroundEach
with SandboxFixture
with SandboxRequiringAuthorization {
private val LedgerId = protected val jwtSecret: String = java.util.UUID.randomUUID.toString
domain.LedgerId(s"${classOf[LedgerClientAuthIT].getSimpleName.toLowerCase}-ledger-id")
override protected lazy val authSecret = Some(jwtSecret)
private val ClientConfigurationWithoutToken = LedgerClientConfiguration( private val ClientConfigurationWithoutToken = LedgerClientConfiguration(
applicationId = classOf[LedgerClientAuthIT].getSimpleName, applicationId = classOf[LedgerClientAuthIT].getSimpleName,
@ -38,11 +31,32 @@ final class LedgerClientAuthIT
token = None, token = None,
) )
private val ClientConfiguration = ClientConfigurationWithoutToken.copy( private val emptyToken = CustomDamlJWTPayload(
token = Some(toHeader(readOnlyToken("Read-only party"))) 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 { "the ledger client" when {
"it has a read-only token" should { "it has a read-only token" should {
@ -50,7 +64,7 @@ final class LedgerClientAuthIT
for { for {
client <- LedgerClient(channel, ClientConfiguration) client <- LedgerClient(channel, ClientConfiguration)
} yield { } yield {
client.ledgerId should be(LedgerId) client.ledgerId should be(config.ledgerIds.head)
} }
} }
@ -75,7 +89,7 @@ final class LedgerClientAuthIT
.allocateParty( .allocateParty(
hint = Some(partyName), hint = Some(partyName),
displayName = Some(partyName), displayName = Some(partyName),
token = Some(toHeader(adminToken)), token = config.adminToken,
) )
} yield { } yield {
allocatedParty.displayName should be(Some(partyName)) allocatedParty.displayName should be(Some(partyName))

View File

@ -4,16 +4,14 @@
package com.daml.ledger.client package com.daml.ledger.client
import com.daml.ledger.api.domain import com.daml.ledger.api.domain
import com.daml.ledger.api.domain.{IdentityProviderConfig, IdentityProviderId, JwksUrl} import com.daml.ledger.api.domain.{IdentityProviderId}
import com.daml.ledger.api.testing.utils.{AkkaBeforeAndAfterAll, SuiteResourceManagementAroundEach}
import com.daml.ledger.client.configuration.{ import com.daml.ledger.client.configuration.{
CommandClientConfiguration, CommandClientConfiguration,
LedgerClientConfiguration, LedgerClientConfiguration,
LedgerIdRequirement, LedgerIdRequirement,
} }
import com.daml.ledger.runner.common.Config import com.daml.lf.integrationtest.CantonFixture
import com.daml.lf.data.Ref import com.daml.lf.data.Ref
import com.daml.platform.sandbox.fixture.SandboxFixture
import com.google.protobuf.field_mask.FieldMask import com.google.protobuf.field_mask.FieldMask
import io.grpc.ManagedChannel import io.grpc.ManagedChannel
import org.scalatest.Inside import org.scalatest.Inside
@ -22,26 +20,19 @@ import org.scalatest.wordspec.AsyncWordSpec
import scalaz.OneAnd import scalaz.OneAnd
import scalaz.syntax.tag._ import scalaz.syntax.tag._
final class LedgerClientIT final class LedgerClientIT extends AsyncWordSpec with Matchers with Inside with CantonFixture {
extends AsyncWordSpec
with Matchers
with Inside
with AkkaBeforeAndAfterAll
with SuiteResourceManagementAroundEach
with SandboxFixture {
private val LedgerId = private val LedgerId = domain.LedgerId(config.ledgerIds.head)
domain.LedgerId(s"${classOf[LedgerClientIT].getSimpleName.toLowerCase}-ledger-id")
lazy val channel = config.channel(suiteResource.value.head)
private val ClientConfiguration = LedgerClientConfiguration( private val ClientConfiguration = LedgerClientConfiguration(
applicationId = classOf[LedgerClientIT].getSimpleName, applicationId = applicationId.unwrap,
ledgerIdRequirement = LedgerIdRequirement.none, ledgerIdRequirement = LedgerIdRequirement.none,
commandClient = CommandClientConfiguration.default, commandClient = CommandClientConfiguration.default,
token = None, token = None,
) )
override protected def config: Config = super.config.copy(ledgerId = LedgerId.unwrap)
"the ledger client" should { "the ledger client" should {
"retrieve the ledger ID" in { "retrieve the ledger ID" in {
for { for {
@ -52,14 +43,14 @@ final class LedgerClientIT
} }
"make some requests" in { "make some requests" in {
val partyName = "Alice" val partyName = CantonFixture.freshName("Alice")
for { for {
client <- LedgerClient(channel, ClientConfiguration) client <- LedgerClient(channel, ClientConfiguration)
// The request type is irrelevant here; the point is that we can make some. // The request type is irrelevant here; the point is that we can make some.
allocatedParty <- client.partyManagementClient allocatedParty <- client.partyManagementClient
.allocateParty(hint = Some(partyName), displayName = None) .allocateParty(hint = Some(partyName), displayName = None)
retrievedParties <- client.partyManagementClient retrievedParties <- client.partyManagementClient
.getParties(OneAnd(Ref.Party.assertFromString(partyName), Set.empty)) .getParties(OneAnd(Ref.Party.assertFromString(allocatedParty.party), Set.empty))
} yield { } yield {
retrievedParties should be(List(allocatedParty)) retrievedParties should be(List(allocatedParty))
} }
@ -78,22 +69,25 @@ final class LedgerClientIT
} }
"identity provider config" should { "identity provider config" should {
val config = IdentityProviderConfig( def freshConfig(): domain.IdentityProviderConfig = domain.IdentityProviderConfig(
IdentityProviderId.Id(Ref.LedgerString.assertFromString("abcd")), domain.IdentityProviderId.Id(
Ref.LedgerString.assertFromString(CantonFixture.freshName("abcd"))
),
isDeactivated = false, isDeactivated = false,
JwksUrl.assertFromString("http://jwks.some.domain:9999/jwks"), domain.JwksUrl.assertFromString("http://jwks.some.domain:9999/jwks"),
"SomeUser", CantonFixture.freshName("SomeUser"),
Some("SomeAudience"), Some(CantonFixture.freshName("SomeAudience")),
) )
val updatedConfig = config.copy( def updateConfig(config: domain.IdentityProviderConfig) = config.copy(
isDeactivated = true, isDeactivated = true,
jwksUrl = JwksUrl("http://someotherurl"), jwksUrl = domain.JwksUrl("http://someotherurl"),
issuer = "ANewIssuer", issuer = CantonFixture.freshName("ANewIssuer"),
audience = Some("ChangedAudience"), audience = Some(CantonFixture.freshName("ChangedAudience")),
) )
"create an identity provider" in { "create an identity provider" in {
val config = freshConfig()
for { for {
client <- LedgerClient(channel, ClientConfiguration) client <- LedgerClient(channel, ClientConfiguration)
createdConfig <- client.identityProviderConfigClient.createIdentityProviderConfig( createdConfig <- client.identityProviderConfigClient.createIdentityProviderConfig(
@ -105,6 +99,7 @@ final class LedgerClientIT
} }
} }
"get an identity provider" in { "get an identity provider" in {
val config = freshConfig()
for { for {
client <- LedgerClient(channel, ClientConfiguration) client <- LedgerClient(channel, ClientConfiguration)
_ <- client.identityProviderConfigClient.createIdentityProviderConfig(config, None) _ <- client.identityProviderConfigClient.createIdentityProviderConfig(config, None)
@ -117,6 +112,8 @@ final class LedgerClientIT
} }
} }
"update an identity provider" in { "update an identity provider" in {
val config = freshConfig()
val updatedConfig = updateConfig(config)
for { for {
client <- LedgerClient(channel, ClientConfiguration) client <- LedgerClient(channel, ClientConfiguration)
_ <- client.identityProviderConfigClient.createIdentityProviderConfig(config, None) _ <- client.identityProviderConfigClient.createIdentityProviderConfig(config, None)
@ -136,8 +133,11 @@ final class LedgerClientIT
} }
"list identity providers" in { "list identity providers" in {
val config = freshConfig()
val updatedConfig = updateConfig(config)
for { for {
client <- LedgerClient(channel, ClientConfiguration) client <- LedgerClient(channel, ClientConfiguration)
before <- client.identityProviderConfigClient.listIdentityProviderConfigs(None)
config1 <- client.identityProviderConfigClient.createIdentityProviderConfig(config, None) config1 <- client.identityProviderConfigClient.createIdentityProviderConfig(config, None)
config2 <- client.identityProviderConfigClient.createIdentityProviderConfig( config2 <- client.identityProviderConfigClient.createIdentityProviderConfig(
updatedConfig.copy(identityProviderId = updatedConfig.copy(identityProviderId =
@ -145,23 +145,25 @@ final class LedgerClientIT
), ),
None, None,
) )
respConfig <- client.identityProviderConfigClient.listIdentityProviderConfigs(None) after <- client.identityProviderConfigClient.listIdentityProviderConfigs(None)
} yield { } yield {
respConfig.toSet should contain theSameElementsAs (Set(config2, config1)) (after.toSet -- before) should contain theSameElementsAs Set(config2, config1)
} }
} }
"delete identity provider" in { "delete identity provider" in {
val config = freshConfig()
for { for {
client <- LedgerClient(channel, ClientConfiguration) client <- LedgerClient(channel, ClientConfiguration)
before <- client.identityProviderConfigClient.listIdentityProviderConfigs(None)
config1 <- client.identityProviderConfigClient.createIdentityProviderConfig(config, None) config1 <- client.identityProviderConfigClient.createIdentityProviderConfig(config, None)
_ <- client.identityProviderConfigClient.deleteIdentityProviderConfig( _ <- client.identityProviderConfigClient.deleteIdentityProviderConfig(
config1.identityProviderId, config1.identityProviderId,
None, None,
) )
respConfig <- client.identityProviderConfigClient.listIdentityProviderConfigs(None) after <- client.identityProviderConfigClient.listIdentityProviderConfigs(None)
} yield { } yield {
respConfig.toSet should be(Set.empty) before.toSet should be(after.toSet)
} }
} }