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/scala-utils",
"//libs-scala/timer-utils",
"//test-common",
"@maven//:org_scalatest_scalatest_compatible",
],
)

View File

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

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

View File

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

View File

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

View File

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