use more domain models for json-api response tests (#14101)

CHANGELOG_BEGIN
CHANGELOG_END
This commit is contained in:
Stephen Compall 2022-06-07 08:57:17 -04:00 committed by GitHub
parent 2ac54ce1f4
commit 7c23420876
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 579 additions and 698 deletions

View File

@ -533,21 +533,4 @@ object HttpServiceTestFixture extends LazyLogging with Assertions with Inside {
domain.CreateCommand(templateId, arg, None)
}
def getContractId(result: JsValue): domain.ContractId =
inside(result.asJsObject.fields.get("contractId")) { case Some(JsString(contractId)) =>
domain.ContractId(contractId)
}
def getResult(output: JsValue): JsValue = getChild(output, "result")
def getWarnings(output: JsValue): JsValue = getChild(output, "warnings")
def getChild(output: JsValue, field: String): JsValue = {
def errorMsg = s"Expected JsObject with '$field' field, got: $output"
output
.asJsObject(errorMsg)
.fields
.getOrElse(field, fail(errorMsg))
}
}

View File

@ -418,6 +418,21 @@ abstract class FailureTests
}
private[this] def getResult(output: JsValue): JsValue = getChild(output, "result")
private[this] def getChild(output: JsValue, field: String): JsValue = {
def errorMsg = s"Expected JsObject with '$field' field, got: $output"
output
.asJsObject(errorMsg)
.fields
.getOrElse(field, fail(errorMsg))
}
def getContractId(result: JsValue): domain.ContractId =
inside(result.asJsObject.fields.get("contractId")) { case Some(JsString(contractId)) =>
domain.ContractId(contractId)
}
// TEST_EVIDENCE: Semantics: fromStartupMode should not succeed for any input when the db connection is broken
"fromStartupMode should not succeed for any input when the connection to the db is broken" in {
import cats.effect.IO

View File

@ -88,20 +88,21 @@ abstract class HttpServiceIntegrationTest
fixture,
aliceHeaders,
)
testIIouID = {
discard { createTest._1 should ===(StatusCodes.OK) }
createTest._2.convertTo[domain.OkResponse[domain.ActiveContract[JsValue]]].result.contractId
testIIouID = inside(createTest) { case (StatusCodes.OK, domain.OkResponse(result, _, _)) =>
result.contractId
}
bobH <- fixture.getUniquePartyAndAuthHeaders("Bob")
(bob, _) = bobH
exerciseTest <- fixture.postJsonRequest(
Uri.Path("/v1/exercise"),
encodeExercise(encoder)(
iouTransfer(domain.EnrichedContractId(Some(exerciseBy), testIIouID), bob)
),
aliceHeaders,
)
} yield inside((exerciseTest._1, exerciseTest._2.convertTo[domain.SyncResponse[JsValue]])) {
exerciseTest <- fixture
.postJsonRequest(
Uri.Path("/v1/exercise"),
encodeExercise(encoder)(
iouTransfer(domain.EnrichedContractId(Some(exerciseBy), testIIouID), bob)
),
aliceHeaders,
)
.parseResponse[JsValue]
} yield inside(exerciseTest) {
case (StatusCodes.OK, domain.OkResponse(_, None, StatusCodes.OK)) => succeed
}
@ -139,26 +140,27 @@ abstract class HttpServiceIntegrationTest
_ = createTest._1 should ===(StatusCodes.OK)
bobH <- fixture.getUniquePartyAndAuthHeaders("Bob")
(bob, _) = bobH
exerciseTest <- fixture.postJsonRequest(
Uri.Path("/v1/exercise"),
encodeExercise(encoder)(
iouTransfer(
domain.EnrichedContractKey(
TpId.IIou.IIou,
v.Value(v.Value.Sum.Party(domain.Party unwrap alice)),
),
bob,
)
),
aliceHeaders,
)
} yield {
val Status = StatusCodes.BadRequest
discard { exerciseTest._1 should ===(Status) }
inside(exerciseTest._2.convertTo[domain.ErrorResponse]) {
case domain.ErrorResponse(Seq(lookup), None, Status, _) =>
lookup should include regex raw"Cannot resolve Template Key type, given: TemplateId\([0-9a-f]{64},IIou,IIou\)"
}
exerciseTest <- fixture
.postJsonRequest(
Uri.Path("/v1/exercise"),
encodeExercise(encoder)(
iouTransfer(
domain.EnrichedContractKey(
TpId.IIou.IIou,
v.Value(v.Value.Sum.Party(domain.Party unwrap alice)),
),
bob,
)
),
aliceHeaders,
)
.parseResponse[JsValue]
} yield inside(exerciseTest) {
case (
StatusCodes.BadRequest,
domain.ErrorResponse(Seq(lookup), None, StatusCodes.BadRequest, _),
) =>
lookup should include regex raw"Cannot resolve Template Key type, given: TemplateId\([0-9a-f]{64},IIou,IIou\)"
}
}

View File

@ -6,7 +6,7 @@ package com.daml.http
import akka.http.scaladsl.model.headers.Authorization
import akka.http.scaladsl.model.{StatusCodes, Uri}
import com.daml.fetchcontracts.domain.TemplateId.OptionalPkg
import com.daml.http.HttpServiceTestFixture.{UseTls, authorizationHeader, getResult, postRequest}
import com.daml.http.HttpServiceTestFixture.{UseTls, authorizationHeader, postRequest}
import com.daml.ledger.client.withoutledgerid.{LedgerClient => DamlLedgerClient}
import com.daml.http.dbbackend.JdbcConfig
import com.daml.http.domain.UserDetails
@ -91,18 +91,17 @@ class HttpServiceIntegrationTestUserManagementNoAuth
CanActAs(Ref.Party.assertFromString(alice.unwrap))
),
)
(status, output) <- fixture.postJsonRequest(
Uri.Path("/v1/create"),
input,
headers = headersWithUserAuth(user.id),
)
assertion <- {
status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK)
val activeContract = getResult(output)
response <- fixture
.postJsonRequest(
Uri.Path("/v1/create"),
input,
headers = headersWithUserAuth(user.id),
)
.parseResponse[domain.ActiveContract[JsValue]]
} yield inside(response) {
case (StatusCodes.OK, domain.OkResponse(activeContract, _, StatusCodes.OK)) =>
assertActiveContract(activeContract)(command, encoder)
}
} yield assertion
}
}
// TEST_EVIDENCE: Authorization: create IOU should fail if user has no permission
@ -119,16 +118,17 @@ class HttpServiceIntegrationTestUserManagementNoAuth
CanActAs(Ref.Party.assertFromString(bob.unwrap))
),
)
(status, output) <- fixture.postJsonRequest(
Uri.Path("/v1/create"),
input,
headers = headersWithUserAuth(user.id),
)
assertion <- {
status shouldBe StatusCodes.BadRequest
assertStatus(output, StatusCodes.BadRequest)
}
} yield assertion
response <- fixture
.postJsonRequest(
Uri.Path("/v1/create"),
input,
headers = headersWithUserAuth(user.id),
)
.parseResponse[JsValue]
} yield inside(response) {
case (StatusCodes.BadRequest, domain.ErrorResponse(_, _, StatusCodes.BadRequest, _)) =>
succeed
}
}
// TEST_EVIDENCE: Authorization: create IOU should fail if overwritten actAs & readAs result in missing permission even if the user would have the rights
@ -149,16 +149,17 @@ class HttpServiceIntegrationTestUserManagementNoAuth
CanActAs(Ref.Party.assertFromString(bob.unwrap)),
),
)
(status, output) <- fixture.postJsonRequest(
Uri.Path("/v1/create"),
input,
headers = headersWithUserAuth(user.id),
)
assertion <- {
status shouldBe StatusCodes.BadRequest
assertStatus(output, StatusCodes.BadRequest)
}
} yield assertion
response <- fixture
.postJsonRequest(
Uri.Path("/v1/create"),
input,
headers = headersWithUserAuth(user.id),
)
.parseResponse[JsValue]
} yield inside(response) {
case (StatusCodes.BadRequest, domain.ErrorResponse(_, _, StatusCodes.BadRequest, _)) =>
succeed
}
}
"requesting the user id should be possible via the user endpoint" in withHttpService { fixture =>
@ -169,17 +170,20 @@ class HttpServiceIntegrationTestUserManagementNoAuth
Ref.UserId.assertFromString(getUniqueUserName("nice.user")),
initialRights = List.empty,
)
(status, output) <- fixture.getRequest(
Uri.Path("/v1/user"),
headers = headersWithUserAuth(user.id),
)
(status, output) <- fixture
.getRequest(
Uri.Path("/v1/user"),
headers = headersWithUserAuth(user.id),
)
.parseResponse[UserDetails]
assertion <- {
status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK)
getResult(output).convertTo[UserDetails] shouldEqual UserDetails(
user.id,
user.primaryParty: Option[String],
)
inside(output) { case domain.OkResponse(result, _, StatusCodes.OK) =>
result shouldEqual UserDetails(
user.id,
user.primaryParty: Option[String],
)
}
}
} yield assertion
}
@ -197,21 +201,19 @@ class HttpServiceIntegrationTestUserManagementNoAuth
CanActAs(Ref.Party.assertFromString(bob.unwrap)),
),
)
(status, output) <- postRequest(
response <- postRequest(
uri.withPath(Uri.Path("/v1/user/rights")),
domain.ListUserRightsRequest(user.id).toJson,
headers = headersWithAdminAuth,
)
assertion <- {
status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK)
getResult(output).convertTo[List[domain.UserRight]] should contain theSameElementsAs
).parseResponse[List[domain.UserRight]]
} yield inside(response) {
case (StatusCodes.OK, domain.OkResponse(result, _, StatusCodes.OK)) =>
result should contain theSameElementsAs
List[domain.UserRight](
domain.CanActAs(alice),
domain.CanActAs(bob),
)
}
} yield assertion
}
}
"requesting the user rights for the current user should be possible via a GET to the user/rights endpoint" in withHttpService {
@ -227,18 +229,21 @@ class HttpServiceIntegrationTestUserManagementNoAuth
CanActAs(Ref.Party.assertFromString(bob.unwrap)),
),
)
(status, output) <- fixture.getRequest(
Uri.Path("/v1/user/rights"),
headers = headersWithUserAuth(user.id),
)
(status, output) <- fixture
.getRequest(
Uri.Path("/v1/user/rights"),
headers = headersWithUserAuth(user.id),
)
.parseResponse[List[domain.UserRight]]
assertion <- {
status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK)
getResult(output).convertTo[List[domain.UserRight]] should contain theSameElementsAs
List[domain.UserRight](
domain.CanActAs(alice),
domain.CanActAs(bob),
)
inside(output) { case domain.OkResponse(result, _, StatusCodes.OK) =>
result should contain theSameElementsAs
List[domain.UserRight](
domain.CanActAs(alice),
domain.CanActAs(bob),
)
}
}
} yield assertion
}
@ -258,14 +263,13 @@ class HttpServiceIntegrationTestUserManagementNoAuth
),
)
for {
(status, output) <- postRequest(
response <- postRequest(
uri.withPath(Uri.Path("/v1/user/create")),
createUserRequest.toJson,
headers = headersWithAdminAuth,
)
} yield {
status shouldBe StatusCodes.OK
getResult(output) shouldBe JsObject()
).parseResponse[JsValue]
} yield inside(response) { case (StatusCodes.OK, domain.OkResponse(r, _, _)) =>
r shouldBe JsObject()
}
}
@ -274,27 +278,28 @@ class HttpServiceIntegrationTestUserManagementNoAuth
import spray.json._
val username = getUniqueUserName("nice.user")
for {
(status, output) <- postRequest(
(status, _) <- postRequest(
uri.withPath(Uri.Path("/v1/user/create")),
JsObject("userId" -> JsString(username)),
headers = headersWithAdminAuth,
)
_ <- status shouldBe StatusCodes.OK
(status2, output2) <- postRequest(
response2 <- postRequest(
uri.withPath(Uri.Path("/v1/user")),
domain.GetUserRequest(username).toJson,
headers = headersWithAdminAuth,
)
_ <- status2 shouldBe StatusCodes.OK
_ <- getResult(output2).convertTo[UserDetails] shouldEqual UserDetails(username, None)
(status3, output3) <- postRequest(
).parseResponse[UserDetails]
_ <- inside(response2) { case (StatusCodes.OK, domain.OkResponse(ud, _, _)) =>
ud shouldEqual UserDetails(username, None)
}
response3 <- postRequest(
uri.withPath(Uri.Path("/v1/user/rights")),
domain.ListUserRightsRequest(username).toJson,
headers = headersWithAdminAuth,
)
_ <- status3 shouldBe StatusCodes.OK
} yield getResult(output3)
.convertTo[List[domain.UserRight]] shouldEqual List.empty
).parseResponse[List[domain.UserRight]]
} yield inside(response3) { case (StatusCodes.OK, domain.OkResponse(List(), _, _)) =>
succeed
}
}
"getting all users should be possible via the users endpoint" in withHttpService { fixture =>
@ -328,13 +333,15 @@ class HttpServiceIntegrationTestUserManagementNoAuth
_ = status shouldBe StatusCodes.OK
} yield ()
)
(status, output) <- fixture.getRequest(
Uri.Path("/v1/users"),
headers = headersWithAdminAuth,
)
_ = status shouldBe StatusCodes.OK
users = getResult(output).convertTo[List[UserDetails]]
} yield users.map(_.userId) should contain allElementsOf usernames
(status, result) <- fixture
.getRequest(
Uri.Path("/v1/users"),
headers = headersWithAdminAuth,
)
.parseResponse[List[UserDetails]]
} yield inside((status, result)) { case (StatusCodes.OK, domain.OkResponse(users, _, _)) =>
users.map(_.userId) should contain allElementsOf usernames
}
}
"getting information about a specific user should be possible via the user endpoint" in withHttpServiceAndClient {
@ -352,23 +359,21 @@ class HttpServiceIntegrationTestUserManagementNoAuth
),
)
for {
(status1, output1) <- postRequest(
response1 <- postRequest(
uri.withPath(Uri.Path("/v1/user/create")),
createUserRequest.toJson,
headers = headersWithAdminAuth,
)
_ <- {
status1 shouldBe StatusCodes.OK
getResult(output1) shouldBe JsObject()
).parseResponse[JsValue]
_ <- inside(response1) { case (StatusCodes.OK, domain.OkResponse(r, _, _)) =>
r shouldBe JsObject()
}
(status2, output2) <- postRequest(
response2 <- postRequest(
uri.withPath(Uri.Path(s"/v1/user")),
domain.GetUserRequest(createUserRequest.userId).toJson,
headers = headersWithAdminAuth,
)
} yield {
status2 shouldBe StatusCodes.OK
getResult(output2).convertTo[UserDetails] shouldBe UserDetails(
).parseResponse[UserDetails]
} yield inside(response2) { case (StatusCodes.OK, domain.OkResponse(ud, _, _)) =>
ud shouldBe UserDetails(
createUserRequest.userId,
createUserRequest.primaryParty,
)
@ -391,22 +396,22 @@ class HttpServiceIntegrationTestUserManagementNoAuth
),
)
for {
(status1, output1) <- postRequest(
response1 <- postRequest(
uri.withPath(Uri.Path("/v1/user/create")),
createUserRequest.toJson,
headers = headersWithAdminAuth,
)
_ <- {
status1 shouldBe StatusCodes.OK
getResult(output1) shouldBe JsObject()
).parseResponse[JsValue]
_ <- inside(response1) { case (StatusCodes.OK, domain.OkResponse(r, _, _)) =>
r shouldBe JsObject()
}
(status2, output2) <- fixture.getRequest(
Uri.Path(s"/v1/user"),
headers = headersWithUserAuth(createUserRequest.userId),
)
} yield {
status2 shouldBe StatusCodes.OK
getResult(output2).convertTo[UserDetails] shouldBe UserDetails(
response2 <- fixture
.getRequest(
Uri.Path(s"/v1/user"),
headers = headersWithUserAuth(createUserRequest.userId),
)
.parseResponse[UserDetails]
} yield inside(response2) { case (StatusCodes.OK, domain.OkResponse(userDetails, _, _)) =>
userDetails shouldBe UserDetails(
createUserRequest.userId,
createUserRequest.primaryParty,
)
@ -430,14 +435,13 @@ class HttpServiceIntegrationTestUserManagementNoAuth
),
)
for {
(status1, output1) <- postRequest(
response1 <- postRequest(
uri.withPath(Uri.Path("/v1/user/create")),
createUserRequest.toJson,
headers = headersWithAdminAuth,
)
_ <- {
status1 shouldBe StatusCodes.OK
getResult(output1) shouldBe JsObject()
).parseResponse[JsValue]
_ <- inside(response1) { case (StatusCodes.OK, domain.OkResponse(r, _, _)) =>
r shouldBe JsObject()
}
(status2, _) <- postRequest(
uri.withPath(Uri.Path(s"/v1/user/delete")),
@ -445,13 +449,14 @@ class HttpServiceIntegrationTestUserManagementNoAuth
headers = headersWithAdminAuth,
)
_ = status2 shouldBe StatusCodes.OK
(status3, output3) <- fixture.getRequest(
Uri.Path("/v1/users"),
headers = headersWithAdminAuth,
)
} yield {
status3 shouldBe StatusCodes.OK
getResult(output3).convertTo[List[UserDetails]] should not contain createUserRequest.userId
response3 <- fixture
.getRequest(
Uri.Path("/v1/users"),
headers = headersWithAdminAuth,
)
.parseResponse[List[UserDetails]]
} yield inside(response3) { case (StatusCodes.OK, domain.OkResponse(users, _, _)) =>
users should not contain createUserRequest.userId
}
}
@ -467,7 +472,7 @@ class HttpServiceIntegrationTestUserManagementNoAuth
CanActAs(Ref.Party.assertFromString(alice.unwrap))
),
)
(status, output) <- postRequest(
response <- postRequest(
uri.withPath(Uri.Path("/v1/user/rights/grant")),
domain
.GrantUserRightsRequest(
@ -480,28 +485,25 @@ class HttpServiceIntegrationTestUserManagementNoAuth
)
.toJson,
headers = headersWithAdminAuth,
)
_ <- {
status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK)
getResult(output).convertTo[List[domain.UserRight]] should contain theSameElementsAs List[
).parseResponse[List[domain.UserRight]]
_ <- inside(response) { case (StatusCodes.OK, domain.OkResponse(urs, _, StatusCodes.OK)) =>
urs should contain theSameElementsAs List[
domain.UserRight
](domain.CanActAs(bob), domain.ParticipantAdmin)
}
(status2, output2) <- postRequest(
response2 <- postRequest(
uri.withPath(Uri.Path("/v1/user/rights")),
domain.ListUserRightsRequest(user.id).toJson,
headers = headersWithAdminAuth,
)
assertion <- {
status2 shouldBe StatusCodes.OK
assertStatus(output2, StatusCodes.OK)
getResult(output2).convertTo[List[domain.UserRight]] should contain theSameElementsAs
List[domain.UserRight](
domain.CanActAs(alice),
domain.CanActAs(bob),
domain.ParticipantAdmin,
)
).parseResponse[List[domain.UserRight]]
assertion <- inside(response2) {
case (StatusCodes.OK, domain.OkResponse(urs, _, StatusCodes.OK)) =>
urs should contain theSameElementsAs
List[domain.UserRight](
domain.CanActAs(alice),
domain.CanActAs(bob),
domain.ParticipantAdmin,
)
}
} yield assertion
}
@ -521,7 +523,7 @@ class HttpServiceIntegrationTestUserManagementNoAuth
ParticipantAdmin,
),
)
(status, output) <- postRequest(
response <- postRequest(
uri.withPath(Uri.Path("/v1/user/rights/revoke")),
domain
.RevokeUserRightsRequest(
@ -534,32 +536,22 @@ class HttpServiceIntegrationTestUserManagementNoAuth
)
.toJson,
headers = headersWithAdminAuth,
)
_ <- {
status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK)
getResult(output)
.convertTo[List[domain.UserRight]] should contain theSameElementsAs List[
domain.UserRight
](
).parseResponse[List[domain.UserRight]]
_ <- inside(response) { case (StatusCodes.OK, domain.OkResponse(urs, _, StatusCodes.OK)) =>
urs should contain theSameElementsAs List[domain.UserRight](
domain.CanActAs(bob),
domain.ParticipantAdmin,
)
}
(status2, output2) <- postRequest(
response2 <- postRequest(
uri.withPath(Uri.Path("/v1/user/rights")),
domain.ListUserRightsRequest(user.id).toJson,
headers = headersWithAdminAuth,
)
assertion <- {
status2 shouldBe StatusCodes.OK
assertStatus(output2, StatusCodes.OK)
getResult(output2).convertTo[List[domain.UserRight]] should contain theSameElementsAs
List[domain.UserRight](
domain.CanActAs(alice)
)
}
} yield assertion
).parseResponse[List[domain.UserRight]]
} yield inside(response2) {
case (StatusCodes.OK, domain.OkResponse(urs, _, StatusCodes.OK)) =>
urs should contain theSameElementsAs List[domain.UserRight](domain.CanActAs(alice))
}
}
// TEST_EVIDENCE: Performance: creating and listing 20K users should be possible
@ -615,13 +607,14 @@ class HttpServiceIntegrationTestUserManagementNoAuth
for {
_ <- createUsers(createUserRequests)
(status, output) <- fixture.getRequest(
Uri.Path("/v1/users"),
headers = headersWithAdminAuth,
)
} yield {
status shouldBe StatusCodes.OK
val userIds = getResult(output).convertTo[List[UserDetails]].map(_.userId)
response <- fixture
.getRequest(
Uri.Path("/v1/users"),
headers = headersWithAdminAuth,
)
.parseResponse[List[UserDetails]]
} yield inside(response) { case (StatusCodes.OK, domain.OkResponse(users, _, _)) =>
val userIds = users.map(_.userId)
val expectedUserIds = "participant_admin" :: createUserRequests.map(_.userId)
userIds should contain allElementsOf expectedUserIds
}

View File

@ -15,10 +15,9 @@ import com.daml.http.endpoints.MeteringReportEndpoint.{
MeteringReportDateRequest,
MeteringReportRequest,
}
import com.daml.http.json.SprayJson.{decode, decode1, objectField}
import com.daml.http.json.SprayJson.objectField
import com.daml.http.json._
import com.daml.http.util.ClientUtil.{boxedRecord, uniqueId}
import com.daml.http.util.FutureUtil
import com.daml.jwt.domain.Jwt
import com.daml.ledger.api.refinements.{ApiTypes => lar}
import com.daml.ledger.api.v1.{value => v}
@ -83,19 +82,13 @@ trait AbstractHttpServiceIntegrationTestFunsCustomToken
Uri.Path("/v1/parties"),
headersWithPartyAuthLegacyFormat(List()),
)
.flatMap { case (status, output) =>
status shouldBe StatusCodes.OK
inside(
decode1[domain.OkResponse, List[domain.PartyDetails]](output)
) { case \/-(response) =>
response.status shouldBe StatusCodes.OK
response.warnings shouldBe empty
val actualIds: Set[domain.Party] = response.result.view.map(_.identifier).toSet
actualIds should contain allElementsOf domain.Party.subst(partyIds.toSet)
response.result.toSet should contain allElementsOf
allocatedParties.toSet.map(domain.PartyDetails.fromLedgerApi)
}
}
.parseResponse[List[domain.PartyDetails]]
.map(inside(_) { case (StatusCodes.OK, domain.OkResponse(result, None, StatusCodes.OK)) =>
val actualIds: Set[domain.Party] = result.view.map(_.identifier).toSet
actualIds should contain allElementsOf domain.Party.subst(partyIds.toSet)
result.toSet should contain allElementsOf
allocatedParties.toSet.map(domain.PartyDetails.fromLedgerApi)
})
}: Future[Assertion]
}
@ -115,15 +108,14 @@ trait AbstractHttpServiceIntegrationTestFunsCustomToken
input,
headers,
)
.flatMap { case (status, output) =>
status shouldBe StatusCodes.Unauthorized
assertStatus(output, StatusCodes.Unauthorized)
HttpServiceTestFixture.getChild(
output,
"errors",
) shouldBe JsArray(JsString("ledgerId missing in access token"))
}: Future[Assertion]
.parseResponse[JsValue]
.map(inside(_) {
case (
StatusCodes.Unauthorized,
domain.ErrorResponse(Seq(error), _, StatusCodes.Unauthorized, _),
) =>
error shouldBe "ledgerId missing in access token"
}): Future[Assertion]
}
"metering-report endpoint should return metering report" in withHttpService { fixture =>
@ -165,21 +157,14 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
UriFixture,
HttpServiceTestFixtureData,
}
import HttpServiceTestFixture.{
UseTls,
accountCreateCommand,
getResult,
getContractId,
archiveCommand,
getChild,
}
import HttpServiceTestFixture.{UseTls, accountCreateCommand, archiveCommand}
import json.JsonProtocol._
override def useTls = UseTls.NoTls
"query GET empty results" in withHttpService { fixture =>
fixture.getUniquePartyAndAuthHeaders("Alice").flatMap { case (_, headers) =>
fixture.searchAllExpectOk(headers).flatMap { case vector =>
fixture.searchAllExpectOk(headers).map { vector =>
vector should have size 0L
}
}
@ -205,15 +190,10 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
fixture
.getRequest(Uri.Path("/v1/query"), headers)
.flatMap { case (status, output) =>
status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK)
inside(output) { case JsObject(fields) =>
inside(fields.get("result")) { case Some(JsArray(vector)) =>
vector should have size searchDataSet.size.toLong
}
}
}: Future[Assertion]
.parseResponse[Vector[JsValue]]
.map(inside(_) { case (StatusCodes.OK, domain.OkResponse(vector, None, StatusCodes.OK)) =>
vector should have size searchDataSet.size.toLong
}): Future[Assertion]
}
}
}
@ -370,7 +350,7 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
fixture.getUniquePartyAndAuthHeaders("Alice").flatMap { case (alice, headers) =>
val searchDataSet = genSearchDataSet(alice)
searchDataSet.traverse(c => postCreateCommand(c, fixture, headers)).flatMap {
rs: List[(StatusCode, JsValue)] =>
rs: List[(StatusCode, _)] =>
rs.map(_._1) shouldBe List.fill(searchDataSet.size)(StatusCodes.OK)
def queryAmountAs(s: String) =
@ -379,21 +359,22 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
val queryAmountAsString = queryAmountAs("\"111.11\"")
val queryAmountAsNumber = queryAmountAs("111.11")
List(
fixture.postJsonRequest(Uri.Path("/v1/query"), queryAmountAsString, headers),
fixture.postJsonRequest(Uri.Path("/v1/query"), queryAmountAsNumber, headers),
).sequence.flatMap { rs: List[(StatusCode, JsValue)] =>
rs.map(_._1) shouldBe List.fill(2)(StatusCodes.OK)
inside(rs.map(_._2)) { case List(jsVal1, jsVal2) =>
jsVal1 shouldBe jsVal2
val acl1: List[domain.ActiveContract[JsValue]] = activeContractList(jsVal1)
val acl2: List[domain.ActiveContract[JsValue]] = activeContractList(jsVal2)
acl1 shouldBe acl2
inside(acl1) { case List(ac) =>
List(queryAmountAsString, queryAmountAsNumber)
.map(q =>
fixture
.postJsonRequest(Uri.Path("/v1/query"), q, headers)
.parseResponse[List[domain.ActiveContract[JsValue]]]
)
.sequence
.map(inside(_) {
case Seq(
(StatusCodes.OK, jsVal1 @ domain.OkResponse(acl1 @ List(ac), _, _)),
(StatusCodes.OK, jsVal2 @ domain.OkResponse(acl2, _, _)),
) =>
jsVal1 shouldBe jsVal2
acl1 shouldBe acl2
objectField(ac.payload, "amount") shouldBe Some(JsString("111.11"))
}
}
}
})
}: Future[Assertion]
}
}
@ -470,10 +451,11 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
"query with invalid JSON query should return error" in withHttpService { fixture =>
fixture
.postJsonStringRequest(Uri.Path("/v1/query"), "{NOT A VALID JSON OBJECT")
.flatMap { case (status, output) =>
status shouldBe StatusCodes.BadRequest
assertStatus(output, StatusCodes.BadRequest)
}: Future[Assertion]
.parseResponse[JsValue]
.map(inside(_) {
case (StatusCodes.BadRequest, domain.ErrorResponse(_, _, StatusCodes.BadRequest, _)) =>
succeed
}): Future[Assertion]
}
"fail to query by interface ID" in withHttpService { fixture =>
@ -514,15 +496,11 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
def searchAll(
headers: List[HttpHeader]
): Future[domain.SyncResponse[List[domain.ActiveContract[JsValue]]]] = {
): Future[domain.SyncResponse[List[domain.ActiveContract[JsValue]]]] =
fixture
.getRequest(Uri.Path("/v1/query"), headers)
.flatMap { case (_, output) =>
FutureUtil.toFuture(
decode1[domain.SyncResponse, List[domain.ActiveContract[JsValue]]](output)
)
}
}
.parseResponse[List[domain.ActiveContract[JsValue]]]
.map { case (_, output) => output }
}
@ -531,12 +509,11 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
fixture.getUniquePartyAndAuthHeaders("Alice").flatMap { case (alice, headers) =>
val command: domain.CreateCommand[v.Record, OptionalPkg] = iouCreateCommand(alice.unwrap)
postCreateCommand(command, fixture, headers).flatMap { case (status, output) =>
status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK)
val activeContract = getResult(output)
assertActiveContract(activeContract)(command, encoder)
}: Future[Assertion]
postCreateCommand(command, fixture, headers)
.map(inside(_) {
case (StatusCodes.OK, domain.OkResponse(activeContract, _, StatusCodes.OK)) =>
assertActiveContract(activeContract)(command, encoder)
}): Future[Assertion]
}
}
@ -547,14 +524,18 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
val command: domain.CreateCommand[v.Record, OptionalPkg] = iouCreateCommand(alice.unwrap)
val input: JsValue = encoder.encodeCreateCommand(command).valueOr(e => fail(e.shows))
fixture.postJsonRequest(Uri.Path("/v1/create"), input, List()).flatMap {
case (status, output) =>
status shouldBe StatusCodes.Unauthorized
assertStatus(output, StatusCodes.Unauthorized)
expectedOneErrorMessage(output) should include(
"missing Authorization header with OAuth 2.0 Bearer Token"
)
}: Future[Assertion]
fixture
.postJsonRequest(Uri.Path("/v1/create"), input, List())
.parseResponse[JsValue]
.map(inside(_) {
case (
StatusCodes.Unauthorized,
domain.ErrorResponse(Seq(error), _, StatusCodes.Unauthorized, _),
) =>
error should include(
"missing Authorization header with OAuth 2.0 Bearer Token"
)
}): Future[Assertion]
}
"create IOU should support extra readAs parties" in withHttpService { fixture =>
@ -566,18 +547,18 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
fixture
.headersWithPartyAuth(actAs = List(alice.unwrap), readAs = List("Bob"))
.flatMap(
fixture.postJsonRequest(
Uri.Path("/v1/create"),
input,
_,
)
fixture
.postJsonRequest(
Uri.Path("/v1/create"),
input,
_,
)
.parseResponse[domain.ActiveContract[JsValue]]
)
.flatMap { case (status, output) =>
status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK)
val activeContract = getResult(output)
assertActiveContract(activeContract)(command, encoder)
}: Future[Assertion]
.map(inside(_) {
case (StatusCodes.OK, domain.OkResponse(activeContract, _, StatusCodes.OK)) =>
assertActiveContract(activeContract)(command, encoder)
}): Future[Assertion]
}
"create IOU with unsupported templateId should return proper error" in withHttpService {
@ -588,16 +569,21 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
iouCreateCommand(alice.unwrap).copy(templateId = domain.TemplateId(None, "Iou", "Dummy"))
val input: JsValue = encoder.encodeCreateCommand(command).valueOr(e => fail(e.shows))
fixture.postJsonRequest(Uri.Path("/v1/create"), input, headers).flatMap {
case (status, output) =>
status shouldBe StatusCodes.BadRequest
assertStatus(output, StatusCodes.BadRequest)
val unknownTemplateId: OptionalPkg =
domain.TemplateId(None, command.templateId.moduleName, command.templateId.entityName)
expectedOneErrorMessage(output) should include(
s"Cannot resolve template ID, given: ${unknownTemplateId: OptionalPkg}"
)
}: Future[Assertion]
fixture
.postJsonRequest(Uri.Path("/v1/create"), input, headers)
.parseResponse[JsValue]
.map(inside(_) {
case (
StatusCodes.BadRequest,
domain.ErrorResponse(Seq(error), _, StatusCodes.BadRequest, _),
) =>
val unknownTemplateId: OptionalPkg =
domain
.TemplateId(None, command.templateId.moduleName, command.templateId.entityName)
error should include(
s"Cannot resolve template ID, given: ${unknownTemplateId: OptionalPkg}"
)
}): Future[Assertion]
}
}
@ -606,29 +592,26 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
fixture.getUniquePartyAndAuthHeaders("Alice").flatMap { case (alice, headers) =>
val create: domain.CreateCommand[v.Record, OptionalPkg] = iouCreateCommand(alice.unwrap)
postCreateCommand(create, fixture, headers)
.flatMap { case (createStatus, createOutput) =>
createStatus shouldBe StatusCodes.OK
assertStatus(createOutput, StatusCodes.OK)
.flatMap(inside(_) {
case (StatusCodes.OK, domain.OkResponse(createResult, _, StatusCodes.OK)) =>
val exercise: domain.ExerciseCommand[v.Value, domain.EnrichedContractId] =
iouExerciseTransferCommand(createResult.contractId)
val exerciseJson: JsValue = encodeExercise(encoder)(exercise)
val contractId = getContractId(getResult(createOutput))
val exercise: domain.ExerciseCommand[v.Value, domain.EnrichedContractId] =
iouExerciseTransferCommand(contractId)
val exerciseJson: JsValue = encodeExercise(encoder)(exercise)
fixture
.postJsonRequest(Uri.Path("/v1/exercise"), exerciseJson, headers)
.flatMap { case (exerciseStatus, exerciseOutput) =>
exerciseStatus shouldBe StatusCodes.OK
assertStatus(exerciseOutput, StatusCodes.OK)
assertExerciseResponseNewActiveContract(
getResult(exerciseOutput),
create,
exercise,
fixture,
headers,
)
}
}: Future[Assertion]
fixture
.postJsonRequest(Uri.Path("/v1/exercise"), exerciseJson, headers)
.parseResponse[domain.ExerciseResponse[JsValue]]
.flatMap(inside(_) {
case (StatusCodes.OK, domain.OkResponse(result, _, StatusCodes.OK)) =>
assertExerciseResponseNewActiveContract(
result,
create,
exercise,
fixture,
headers,
)
})
}): Future[Assertion]
}
}
@ -642,14 +625,10 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
fixture
.postJsonRequest(Uri.Path("/v1/create-and-exercise"), json, headers)
.flatMap { case (status, output) =>
status shouldBe StatusCodes.OK
inside(
decode1[domain.OkResponse, domain.ExerciseResponse[JsValue]](output)
) { case \/-(response) =>
response.status shouldBe StatusCodes.OK
response.warnings shouldBe empty
inside(response.result.events) {
.parseResponse[domain.ExerciseResponse[JsValue]]
.flatMap(inside(_) {
case (StatusCodes.OK, domain.OkResponse(result, None, StatusCodes.OK)) =>
inside(result.events) {
case List(
domain.Contract(\/-(created0)),
domain.Contract(-\/(archived0)),
@ -659,23 +638,22 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
assertTemplateId(archived0.templateId, cmd.templateId)
archived0.contractId shouldBe created0.contractId
assertTemplateId(created1.templateId, TpId.Iou.IouTransfer)
asContractId(response.result.exerciseResult) shouldBe created1.contractId
asContractId(result.exerciseResult) shouldBe created1.contractId
}
}
}: Future[Assertion]
}
})
}: Future[Assertion]
}
private def assertExerciseResponseNewActiveContract(
exerciseResponse: JsValue,
exerciseResponse: domain.ExerciseResponse[JsValue],
createCmd: domain.CreateCommand[v.Record, OptionalPkg],
exerciseCmd: domain.ExerciseCommand[v.Value, domain.EnrichedContractId],
fixture: HttpServiceTestFixtureData,
headers: List[HttpHeader],
): Future[Assertion] = {
import fixture.{uri, decoder}
inside(SprayJson.decode[domain.ExerciseResponse[JsValue]](exerciseResponse)) {
case \/-(domain.ExerciseResponse(JsString(exerciseResult), List(contract1, contract2))) =>
inside(exerciseResponse) {
case domain.ExerciseResponse(JsString(exerciseResult), List(contract1, contract2)) =>
// checking contracts
inside(contract1) { case domain.Contract(-\/(archivedContract)) =>
Future {
@ -692,11 +670,10 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
Some(TpId.Iou.IouTransfer),
domain.ContractId(exerciseResult),
)
postContractsLookup(newContractLocator, uri, headers).flatMap { case (status, output) =>
status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK)
getContractId(getResult(output)) shouldBe newContractLocator.contractId
}: Future[Assertion]
postContractsLookup(newContractLocator, uri, headers).map(inside(_) {
case (StatusCodes.OK, domain.OkResponse(Some(contract), _, StatusCodes.OK)) =>
contract.contractId shouldBe newContractLocator.contractId
}): Future[Assertion]
}
}
}
@ -746,24 +723,20 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
fixture.getUniquePartyAndAuthHeaders("Alice").flatMap { case (alice, headers) =>
val create: domain.CreateCommand[v.Record, OptionalPkg] = iouCreateCommand(alice.unwrap)
postCreateCommand(create, fixture, headers)
.flatMap { case (createStatus, createOutput) =>
createStatus shouldBe StatusCodes.OK
assertStatus(createOutput, StatusCodes.OK)
.flatMap(inside(_) {
case (StatusCodes.OK, domain.OkResponse(createResult, _, StatusCodes.OK)) =>
val reference = domain.EnrichedContractId(Some(TpId.Iou.Iou), createResult.contractId)
val exercise = archiveCommand(reference)
val exerciseJson: JsValue = encodeExercise(encoder)(exercise)
val contractId = getContractId(getResult(createOutput))
val reference = domain.EnrichedContractId(Some(TpId.Iou.Iou), contractId)
val exercise = archiveCommand(reference)
val exerciseJson: JsValue = encodeExercise(encoder)(exercise)
fixture
.postJsonRequest(Uri.Path("/v1/exercise"), exerciseJson, headers)
.flatMap { case (exerciseStatus, exerciseOutput) =>
exerciseStatus shouldBe StatusCodes.OK
assertStatus(exerciseOutput, StatusCodes.OK)
val exercisedResponse: JsObject = getResult(exerciseOutput).asJsObject
assertExerciseResponseArchivedContract(exercisedResponse, exercise)
}
}: Future[Assertion]
fixture
.postJsonRequest(Uri.Path("/v1/exercise"), exerciseJson, headers)
.parseResponse[domain.ExerciseResponse[JsValue]]
.flatMap(inside(_) {
case (StatusCodes.OK, domain.OkResponse(exercisedResponse, _, StatusCodes.OK)) =>
assertExerciseResponseArchivedContract(exercisedResponse, exercise)
})
}): Future[Assertion]
}
}
@ -776,10 +749,9 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
.flatMap(
postCreateCommand(multiPartyCreateCommand(List("Alice", "Bob"), ""), fixture, _)
)
.map { case (status, output) =>
status shouldBe StatusCodes.OK
getContractId(getResult(output))
}
.map(inside(_) { case (StatusCodes.OK, domain.OkResponse(result, _, _)) =>
result.contractId
})
// multi-party actAs on exercise
cidMulti <- fixture
.headersWithPartyAuth(List("Alice", "Bob", "Charlie", "David"))
@ -790,12 +762,11 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
_,
)
)
.map { case (status, output) =>
status shouldBe StatusCodes.OK
inside(getChild(getResult(output), "exerciseResult")) { case JsString(c) =>
.parseResponse[domain.ExerciseResponse[JsValue]]
.map(inside(_) {
case (StatusCodes.OK, domain.OkResponse(domain.ExerciseResponse(JsString(c), _), _, _)) =>
lar.ContractId(c)
}
}
})
// create a contract only visible to Alice
cid <- fixture
.headersWithPartyAuth(List("Alice"))
@ -806,10 +777,9 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
_,
)
)
.map { case (status, output) =>
status shouldBe StatusCodes.OK
getContractId(getResult(output))
}
.map(inside(_) { case (StatusCodes.OK, domain.OkResponse(result, _, _)) =>
result.contractId
})
_ <- fixture
.headersWithPartyAuth(List("Charlie"), readAs = List("Alice"))
.flatMap(
@ -826,19 +796,15 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
}
private def assertExerciseResponseArchivedContract(
exerciseResponse: JsValue,
exerciseResponse: domain.ExerciseResponse[JsValue],
exercise: domain.ExerciseCommand[v.Value, domain.EnrichedContractId],
): Assertion = {
inside(exerciseResponse) { case result @ JsObject(_) =>
inside(SprayJson.decode[domain.ExerciseResponse[JsValue]](result)) {
case \/-(domain.ExerciseResponse(exerciseResult, List(contract1))) =>
exerciseResult shouldBe JsObject()
inside(contract1) { case domain.Contract(-\/(archivedContract)) =>
(archivedContract.contractId.unwrap: String) shouldBe (exercise.reference.contractId.unwrap: String)
}
): Assertion =
inside(exerciseResponse) { case domain.ExerciseResponse(exerciseResult, List(contract1)) =>
exerciseResult shouldBe JsObject()
inside(contract1) { case domain.Contract(-\/(archivedContract)) =>
(archivedContract.contractId.unwrap: String) shouldBe (exercise.reference.contractId.unwrap: String)
}
}
}
"should be able to serialize and deserialize domain commands" in withHttpService { fixture =>
(testCreateCommandEncodingDecoding(fixture) *>
@ -906,19 +872,13 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
Uri.Path("/v1/parties"),
headers = headersWithAdminAuth,
)
.flatMap { case (status, output) =>
status shouldBe StatusCodes.OK
inside(
decode1[domain.OkResponse, List[domain.PartyDetails]](output)
) { case \/-(response) =>
response.status shouldBe StatusCodes.OK
response.warnings shouldBe empty
val actualIds: Set[domain.Party] = response.result.view.map(_.identifier).toSet
actualIds should contain allElementsOf domain.Party.subst(partyIds.toSet)
response.result.toSet should contain allElementsOf
allocatedParties.toSet.map(domain.PartyDetails.fromLedgerApi)
}
}
.parseResponse[List[domain.PartyDetails]]
.map(inside(_) { case (StatusCodes.OK, domain.OkResponse(result, None, StatusCodes.OK)) =>
val actualIds: Set[domain.Party] = result.view.map(_.identifier).toSet
actualIds should contain allElementsOf domain.Party.subst(partyIds.toSet)
result.toSet should contain allElementsOf
allocatedParties.toSet.map(domain.PartyDetails.fromLedgerApi)
})
}: Future[Assertion]
}
@ -943,21 +903,17 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
JsArray(requestedPartyIds.map(x => JsString(x.unwrap))),
headersWithAdminAuth,
)
.flatMap { case (status, output) =>
status shouldBe StatusCodes.OK
inside(
decode1[domain.OkResponse, List[domain.PartyDetails]](output)
) { case \/-(response) =>
response.status shouldBe StatusCodes.OK
response.warnings shouldBe Some(domain.UnknownParties(List(erin)))
val actualIds: Set[domain.Party] = response.result.view.map(_.identifier).toSet
.parseResponse[List[domain.PartyDetails]]
.flatMap(inside(_) {
case (StatusCodes.OK, domain.OkResponse(result, Some(warnings), StatusCodes.OK)) =>
warnings shouldBe domain.UnknownParties(List(erin))
val actualIds: Set[domain.Party] = result.view.map(_.identifier).toSet
actualIds shouldBe requestedPartyIds.toSet - erin // Erin is not known
val expected: Set[domain.PartyDetails] = allocatedParties.toSet
.map(domain.PartyDetails.fromLedgerApi)
.filterNot(_.identifier == charlie)
response.result.toSet shouldBe expected
}
}
result.toSet shouldBe expected
})
}: Future[Assertion]
}
@ -1005,15 +961,15 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
JsArray(requestedPartyIds.map(x => JsString(x.unwrap))),
headers = headersWithAdminAuth,
)
.flatMap { case (status, output) =>
status shouldBe StatusCodes.OK
inside(decode1[domain.SyncResponse, List[domain.PartyDetails]](output)) {
case \/-(domain.OkResponse(List(), Some(warnings), StatusCodes.OK)) =>
inside(warnings) { case domain.UnknownParties(unknownParties) =>
unknownParties.toSet shouldBe requestedPartyIds.toSet
}
}
}: Future[Assertion]
.parseResponse[List[domain.PartyDetails]]
.map(inside(_) {
case (
StatusCodes.OK,
domain
.OkResponse(List(), Some(domain.UnknownParties(unknownParties)), StatusCodes.OK),
) =>
unknownParties.toSet shouldBe requestedPartyIds.toSet
}): Future[Assertion]
}
"parties/allocate should allocate a new party" in withHttpService { fixture =>
@ -1028,29 +984,21 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
json = json,
headers = headersWithAdminAuth,
)
.flatMap { case (status, output) =>
status shouldBe StatusCodes.OK
inside(decode1[domain.OkResponse, domain.PartyDetails](output)) { case \/-(response) =>
response.status shouldBe StatusCodes.OK
val newParty = response.result
Some(newParty.identifier) shouldBe request.identifierHint
newParty.displayName shouldBe request.displayName
newParty.isLocal shouldBe true
fixture
.getRequest(
Uri.Path("/v1/parties"),
headersWithAdminAuth,
)
.flatMap { case (status, output) =>
status shouldBe StatusCodes.OK
inside(decode1[domain.OkResponse, List[domain.PartyDetails]](output)) {
case \/-(response) =>
response.status shouldBe StatusCodes.OK
response.result should contain(newParty)
}
}
}
}: Future[Assertion]
.parseResponse[domain.PartyDetails]
.flatMap(inside(_) { case (StatusCodes.OK, domain.OkResponse(newParty, _, StatusCodes.OK)) =>
Some(newParty.identifier) shouldBe request.identifierHint
newParty.displayName shouldBe request.displayName
newParty.isLocal shouldBe true
fixture
.getRequest(
Uri.Path("/v1/parties"),
headersWithAdminAuth,
)
.parseResponse[List[domain.PartyDetails]]
.map(inside(_) { case (StatusCodes.OK, domain.OkResponse(result, _, StatusCodes.OK)) =>
result should contain(newParty)
})
}): Future[Assertion]
}
"parties/allocate should allocate a new party without any hints" in withHttpService { fixture =>
@ -1060,27 +1008,22 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
json = JsObject(),
headers = headersWithAdminAuth,
)
.flatMap { case (status, output) =>
status shouldBe StatusCodes.OK
inside(decode1[domain.OkResponse, domain.PartyDetails](output)) { case \/-(response) =>
response.status shouldBe StatusCodes.OK
val newParty = response.result
newParty.identifier.unwrap.length should be > 0
newParty.displayName shouldBe None
newParty.isLocal shouldBe true
.parseResponse[domain.PartyDetails]
.flatMap(inside(_) { case (StatusCodes.OK, domain.OkResponse(newParty, _, StatusCodes.OK)) =>
newParty.identifier.unwrap.length should be > 0
newParty.displayName shouldBe None
newParty.isLocal shouldBe true
fixture
.getRequest(Uri.Path("/v1/parties"), headers = headersWithAdminAuth)
.flatMap { case (status, output) =>
status shouldBe StatusCodes.OK
inside(decode1[domain.OkResponse, List[domain.PartyDetails]](output)) {
case \/-(response) =>
response.status shouldBe StatusCodes.OK
response.result should contain(newParty)
}
}
}
}: Future[Assertion]
fixture
.getRequest(
Uri.Path("/v1/parties"),
headers = headersWithAdminAuth,
)
.parseResponse[List[domain.PartyDetails]]
.map(inside(_) { case (StatusCodes.OK, domain.OkResponse(result, _, StatusCodes.OK)) =>
result should contain(newParty)
})
}): Future[Assertion]
}
// TEST_EVIDENCE: Authorization: badly-authorized create is rejected
@ -1098,27 +1041,26 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
json = json,
headers = headersWithAdminAuth,
)
.flatMap { case (status, output) =>
status shouldBe StatusCodes.BadRequest
inside(decode[domain.ErrorResponse](output)) { case \/-(response) =>
response.status shouldBe StatusCodes.BadRequest
response.warnings shouldBe empty
response.errors.length shouldBe 1
}
}
.parseResponse[JsValue]
.map(inside(_) {
case (
StatusCodes.BadRequest,
domain.ErrorResponse(errors, None, StatusCodes.BadRequest, _),
) =>
errors.length shouldBe 1
})
}
"fetch by contractId" in withHttpService { fixture =>
fixture.getUniquePartyAndAuthHeaders("Alice").flatMap { case (alice, headers) =>
val command: domain.CreateCommand[v.Record, OptionalPkg] = iouCreateCommand(alice.unwrap)
postCreateCommand(command, fixture, headers).flatMap { case (status, output) =>
status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK)
val contractId: ContractId = getContractId(getResult(output))
val locator = domain.EnrichedContractId(None, contractId)
lookupContractAndAssert(locator, contractId, command, fixture, headers)
}: Future[Assertion]
postCreateCommand(command, fixture, headers).flatMap(inside(_) {
case (StatusCodes.OK, domain.OkResponse(result, _, StatusCodes.OK)) =>
val contractId: ContractId = result.contractId
val locator = domain.EnrichedContractId(None, contractId)
lookupContractAndAssert(locator, contractId, command, fixture, headers)
}): Future[Assertion]
}
}
@ -1131,15 +1073,10 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
TpId.Account.Account,
JsArray(JsString(alice.unwrap), JsString(accountNumber)),
)
postContractsLookup(locator, uri.withPath(Uri.Path("/v1/fetch")), headers).flatMap {
case (status, output) =>
status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK)
output
.asJsObject(s"expected JsObject, got: $output")
.fields
.get("result") shouldBe Some(JsNull)
}: Future[Assertion]
postContractsLookup(locator, uri.withPath(Uri.Path("/v1/fetch")), headers).map(inside(_) {
case (StatusCodes.OK, domain.OkResponse(None, _, StatusCodes.OK)) =>
succeed
}): Future[Assertion]
}
}
@ -1152,11 +1089,9 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
(alice, aliceHeaders) = res
command = iouCreateCommand(alice.unwrap)
createStatusOutput <- postCreateCommand(command, fixture, aliceHeaders)
contractId = {
val (status, output) = createStatusOutput
status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK)
getContractId(getResult(output))
contractId = inside(createStatusOutput) {
case (StatusCodes.OK, domain.OkResponse(result, _, StatusCodes.OK)) =>
result.contractId
}
locator = domain.EnrichedContractId(None, contractId)
// will cache if DB configured
@ -1168,16 +1103,13 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
aliceHeaders,
readAs = Some(List(charlie)),
)
_ = {
val (status, output) = badLookup
status shouldBe StatusCodes.Unauthorized
assertStatus(output, StatusCodes.Unauthorized)
output
.asJsObject(s"expected JsObject, got: $output")
.fields
.keySet should ===(Set("errors", "status"))
}
} yield succeed
} yield inside(badLookup) {
case (
StatusCodes.Unauthorized,
domain.ErrorResponse(_, None, StatusCodes.Unauthorized, None),
) =>
succeed
}
}
"fetch by key" in withHttpService { fixture =>
@ -1186,16 +1118,15 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
val command: domain.CreateCommand[v.Record, OptionalPkg] =
accountCreateCommand(alice, accountNumber)
postCreateCommand(command, fixture, headers).flatMap { case (status, output) =>
status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK)
val contractId: ContractId = getContractId(getResult(output))
val locator = domain.EnrichedContractKey(
TpId.Account.Account,
JsArray(JsString(alice.unwrap), JsString(accountNumber)),
)
lookupContractAndAssert(locator, contractId, command, fixture, headers)
}: Future[Assertion]
postCreateCommand(command, fixture, headers).flatMap(inside(_) {
case (StatusCodes.OK, domain.OkResponse(result, _, StatusCodes.OK)) =>
val contractId: ContractId = result.contractId
val locator = domain.EnrichedContractKey(
TpId.Account.Account,
JsArray(JsString(alice.unwrap), JsString(accountNumber)),
)
lookupContractAndAssert(locator, contractId, command, fixture, headers)
}): Future[Assertion]
}
}
@ -1220,16 +1151,15 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
archiveCommand(locator)
val archiveJson: JsValue = encodeExercise(encoder)(archive)
postCreateCommand(create, fixture, headers).flatMap { case (status, output) =>
status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK)
fixture.postJsonRequest(Uri.Path("/v1/exercise"), archiveJson, headers).flatMap {
case (exerciseStatus, exerciseOutput) =>
exerciseStatus shouldBe StatusCodes.OK
assertStatus(exerciseOutput, StatusCodes.OK)
}
}: Future[Assertion]
postCreateCommand(create, fixture, headers).flatMap(inside(_) {
case (StatusCodes.OK, domain.OkResponse(_, _, StatusCodes.OK)) =>
fixture
.postJsonRequest(Uri.Path("/v1/exercise"), archiveJson, headers)
.parseResponse[JsValue]
.map(inside(_) { case (StatusCodes.OK, domain.OkResponse(_, _, StatusCodes.OK)) =>
succeed
})
}): Future[Assertion]
}
}
@ -1287,19 +1217,19 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
"bazRecord": {"baz": "another baz value"}
}
}""")
fixture.postJsonRequest(Uri.Path("/v1/create"), createCommand, headers).flatMap {
case (status, output) =>
status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK)
val contractId: ContractId = getContractId(getResult(output))
fixture
.postJsonRequest(Uri.Path("/v1/create"), createCommand, headers)
.parseResponse[domain.ActiveContract[JsValue]]
.flatMap(inside(_) { case (StatusCodes.OK, domain.OkResponse(c, _, StatusCodes.OK)) =>
val contractId: ContractId = c.contractId
fixture.postJsonRequest(Uri.Path("/v1/fetch"), request, headers).flatMap {
case (status, output) =>
status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK)
activeContract(output).contractId shouldBe contractId
}
}: Future[Assertion]
fixture
.postJsonRequest(Uri.Path("/v1/fetch"), request, headers)
.parseResponse[domain.ActiveContract[JsValue]]
.flatMap(inside(_) { case (StatusCodes.OK, domain.OkResponse(c, _, StatusCodes.OK)) =>
c.contractId shouldBe contractId
})
}): Future[Assertion]
}
"query by a variant field" in withHttpService { fixture =>
@ -1316,12 +1246,11 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
.map(_._1)
.getOrElse(fail(s"Cannot retrieve packageId"))
postCreateCommand(command, fixture, headers).flatMap { case (status, output) =>
status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK)
val contractId: ContractId = getContractId(getResult(output))
postCreateCommand(command, fixture, headers).flatMap(inside(_) {
case (StatusCodes.OK, domain.OkResponse(result, _, StatusCodes.OK)) =>
val contractId: ContractId = result.contractId
val query = jsObject(s"""{
val query = jsObject(s"""{
"templateIds": ["$packageId:Account:Account"],
"query": {
"number" : "abc123",
@ -1329,15 +1258,14 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
}
}""")
fixture.postJsonRequest(Uri.Path("/v1/query"), query, headers).map {
case (searchStatus, searchOutput) =>
searchStatus shouldBe StatusCodes.OK
assertStatus(searchOutput, StatusCodes.OK)
inside(activeContractList(searchOutput)) { case List(ac) =>
ac.contractId shouldBe contractId
}
}
}: Future[Assertion]
fixture
.postJsonRequest(Uri.Path("/v1/query"), query, headers)
.parseResponse[List[domain.ActiveContract[JsValue]]]
.map(inside(_) {
case (StatusCodes.OK, domain.OkResponse(List(ac), _, StatusCodes.OK)) =>
ac.contractId shouldBe contractId
})
}): Future[Assertion]
}
}
@ -1417,13 +1345,11 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
_ <- withHttpServiceOnly(ledgerPort) { innerFixture =>
innerFixture
.getRequest(Uri.Path("/v1/query"), headers)
.flatMap { case (status, output) =>
status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK)
inside(getResult(output)) { case JsArray(result) =>
.parseResponse[Vector[JsValue]]
.map(inside(_) {
case (StatusCodes.OK, domain.OkResponse(result, _, StatusCodes.OK)) =>
result should have length 4
}
}: Future[Assertion]
}): Future[Assertion]
}
} yield succeed
}
@ -1477,37 +1403,33 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
jsObject("""{"templateIds": ["Account:Account"]}"""),
headers,
)
.flatMap { case (status, output) =>
status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK)
inside(getResult(output)) { case JsArray(result) =>
result should have length n
}
}
.parseResponse[Vector[JsValue]]
.map(inside(_) { case (StatusCodes.OK, domain.OkResponse(result, _, StatusCodes.OK)) =>
result should have length n
})
for {
resp <- fixture
.postJsonRequest(Uri.Path("/v1/create-and-exercise"), encode(createCmd), headers)
(status, output) = resp
_ = {
status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK)
.parseResponse[domain.ExerciseResponse[JsValue]]
result = inside(resp) {
case (StatusCodes.OK, domain.OkResponse(result, _, StatusCodes.OK)) =>
result
}
created = getChild(getResult(output), "exerciseResult").convertTo[List[String]]
created = result.exerciseResult.convertTo[List[String]]
_ = created should have length numContracts
_ <- queryN(numContracts)
status <- fixture
archiveResponse <- fixture
.postJsonRequest(
Uri.Path("/v1/create-and-exercise"),
encode(archiveCmd(created)),
headers,
)
.map(_._1)
_ = {
status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK)
.parseResponse[JsValue]
_ = inside(archiveResponse) {
case (StatusCodes.OK, domain.OkResponse(_, _, StatusCodes.OK)) =>
}
_ <- queryN(0)
@ -1565,11 +1487,9 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
.flatMap(headers =>
fixture.postJsonRequest(Uri.Path("/v1/exercise"), exerciseJson, headers)
)
.map { case (exerciseStatus, exerciseOutput) =>
exerciseStatus shouldBe StatusCodes.OK
assertStatus(exerciseOutput, StatusCodes.OK)
()
}
.parseResponse[JsValue]
.map(inside(_) { case (StatusCodes.OK, domain.OkResponse(_, _, StatusCodes.OK)) =>
})
}
@ -1582,10 +1502,9 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
fixture
.headersWithPartyAuth(actAs = List(fromPerspectiveOfParty.unwrap))
.flatMap(headers => fixture.postJsonRequest(Uri.Path("/v1/query"), query, headers))
.map { case (searchStatus, searchOutput) =>
searchStatus shouldBe StatusCodes.OK
assertStatus(searchOutput, StatusCodes.OK)
}
.parseResponse[JsValue]
.map(inside(_) { case (StatusCodes.OK, domain.OkResponse(_, _, StatusCodes.OK)) =>
})
}
val commands = partyIds.map { p =>
@ -1603,11 +1522,9 @@ abstract class AbstractHttpServiceIntegrationTestTokenIndependent
headers,
)
)
.map { case (status, output) =>
status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK)
getContractId(getResult(output))
}: Future[ContractId]
.map(inside(_) { case (StatusCodes.OK, domain.OkResponse(result, _, StatusCodes.OK)) =>
result.contractId
}): Future[ContractId]
fut.map(cid => (party, cid))
}
(alice, aliceUserId) = users(0)

View File

@ -262,30 +262,43 @@ trait AbstractHttpServiceIntegrationTestFuns
): Future[(StatusCode, domain.SyncResponse[Result])] =
headersWithAuth
.flatMap(postJsonRequest(path, json, _))
.map(_ map (decode1[domain.SyncResponse, Result](_).fold(e => fail(e.shows), identity)))
.parseResponse[Result]
def getRequest(path: Uri.Path, headers: List[HttpHeader]): Future[(StatusCode, JsValue)] =
HttpServiceTestFixture.getRequest(uri withPath path, headers)
def getRequest(
path: Uri.Path,
headers: List[HttpHeader],
): Future[(StatusCode, JsValue)] =
HttpServiceTestFixture
.getRequest(uri withPath path, headers)
def getRequestWithMinimumAuth[Result: JsonReader](
path: Uri.Path
): Future[(StatusCode, domain.SyncResponse[Result])] =
headersWithAuth
.flatMap(getRequest(path, _))
.map(_ map (decode1[domain.SyncResponse, Result](_).fold(e => fail(e.shows), identity)))
.parseResponse[Result]
}
implicit protected final class `Future JsValue functions`[A](
private val self: Future[(A, JsValue)]
) {
def parseResponse[Result: JsonReader]: Future[(A, domain.SyncResponse[Result])] =
self.map(_ map (decode1[domain.SyncResponse, Result](_).fold(e => fail(e.shows), identity)))
}
protected def postCreateCommand(
cmd: domain.CreateCommand[v.Record, OptionalPkg],
fixture: UriFixture with EncoderFixture,
headers: List[HttpHeader],
): Future[(StatusCode, JsValue)] =
HttpServiceTestFixture.postCreateCommand(cmd, fixture.encoder, fixture.uri, headers)
): Future[(StatusCode, domain.SyncResponse[domain.ActiveContract[JsValue]])] =
HttpServiceTestFixture
.postCreateCommand(cmd, fixture.encoder, fixture.uri, headers)
.parseResponse[domain.ActiveContract[JsValue]]
protected def postCreateCommand(
cmd: domain.CreateCommand[v.Record, OptionalPkg],
fixture: UriFixture with EncoderFixture,
): Future[(StatusCode, JsValue)] =
): Future[(StatusCode, domain.SyncResponse[domain.ActiveContract[JsValue]])] =
fixture.headersWithAuth.flatMap(postCreateCommand(cmd, fixture, _))
protected def postArchiveCommand(
@ -318,13 +331,11 @@ trait AbstractHttpServiceIntegrationTestFuns
fixture: UriFixture with EncoderFixture,
headers: List[HttpHeader],
): Future[Assertion] =
postContractsLookup(contractLocator, fixture.uri, headers).flatMap { case (status, output) =>
status shouldBe StatusCodes.OK
assertStatus(output, StatusCodes.OK)
val result = getResult(output)
contractId shouldBe getContractId(result)
assertActiveContract(result)(create, fixture.encoder)
}
postContractsLookup(contractLocator, fixture.uri, headers).map(inside(_) {
case (StatusCodes.OK, domain.OkResponse(Some(resultContract), _, StatusCodes.OK)) =>
contractId shouldBe resultContract.contractId
assertActiveContract(resultContract)(create, fixture.encoder)
})
protected def removeRecordId(a: v.Value): v.Value = a match {
case v.Value(v.Value.Sum.Record(r)) if r.recordId.isDefined =>
@ -506,33 +517,12 @@ trait AbstractHttpServiceIntegrationTestFuns
)
}
protected def result(jsObj: JsValue): JsValue = {
inside(jsObj) { case JsObject(fields) =>
inside(fields.get("result")) { case Some(value: JsValue) => value }
}
}
protected def assertStatus(jsObj: JsValue, expectedStatus: StatusCode): Assertion = {
inside(jsObj) { case JsObject(fields) =>
inside(fields.get("status")) { case Some(JsNumber(status)) =>
status shouldBe BigDecimal(expectedStatus.intValue)
}
}
}
protected def expectedOneErrorMessage(output: JsValue): String =
inside(output) { case JsObject(fields) =>
inside(fields.get("errors")) { case Some(JsArray(Vector(JsString(errorMsg)))) =>
errorMsg
}
}
protected def postContractsLookup(
cmd: domain.ContractLocator[JsValue],
uri: Uri,
headers: List[HttpHeader],
readAs: Option[List[domain.Party]],
): Future[(StatusCode, JsValue)] =
): Future[(StatusCode, domain.SyncResponse[Option[domain.ActiveContract[JsValue]]])] =
for {
locjson <- toFuture(SprayJson.encode(cmd)): Future[JsValue]
json <- toFuture(
@ -545,27 +535,15 @@ trait AbstractHttpServiceIntegrationTestFuns
)
)
result <- postJsonRequest(uri.withPath(Uri.Path("/v1/fetch")), json, headers)
.parseResponse[Option[domain.ActiveContract[JsValue]]]
} yield result
protected def postContractsLookup(
cmd: domain.ContractLocator[JsValue],
uri: Uri,
headers: List[HttpHeader],
): Future[(StatusCode, JsValue)] = postContractsLookup(cmd, uri, headers, None)
protected def activeContractList(output: JsValue): List[domain.ActiveContract[JsValue]] = {
val result = getResult(output)
SprayJson
.decode[List[domain.ActiveContract[JsValue]]](result)
.valueOr(e => fail(e.shows))
}
protected def activeContract(output: JsValue): domain.ActiveContract[JsValue] = {
val result = getResult(output)
SprayJson
.decode[domain.ActiveContract[JsValue]](result)
.valueOr(e => fail(e.shows))
}
): Future[(StatusCode, domain.SyncResponse[Option[domain.ActiveContract[JsValue]]])] =
postContractsLookup(cmd, uri, headers, None)
protected def asContractId(a: JsValue): domain.ContractId = inside(a) { case JsString(x) =>
domain.ContractId(x)
@ -640,11 +618,11 @@ trait AbstractHttpServiceIntegrationTestFuns
}
protected def assertActiveContract(
jsVal: JsValue
activeContract: domain.ActiveContract[JsValue]
)(
command: domain.CreateCommand[v.Record, OptionalPkg],
encoder: DomainJsonEncoder,
): Future[Assertion] = {
): Assertion = {
import encoder.implicits._
@ -653,11 +631,7 @@ trait AbstractHttpServiceIntegrationTestFuns
.traversePayload(SprayJson.encode[v.Record](_))
.getOrElse(fail(s"Failed to encode command: $command"))
Future {
inside(SprayJson.decode[domain.ActiveContract[JsValue]](jsVal)) { case \/-(activeContract) =>
(activeContract.payload: JsValue) shouldBe (expected.payload: JsValue)
}
}
(activeContract.payload: JsValue) shouldBe (expected.payload: JsValue)
}
protected def assertTemplateId(
@ -695,7 +669,7 @@ trait AbstractHttpServiceIntegrationTestFuns
serviceUri: Uri,
party: domain.Party,
headers: List[HttpHeader],
): Future[(StatusCode, JsValue)] = {
): Future[(StatusCode, domain.SyncResponse[domain.ActiveContract[JsValue]])] = {
val partyJson = party.toJson.compactPrint
val payload =
s"""
@ -710,18 +684,20 @@ trait AbstractHttpServiceIntegrationTestFuns
| }
|}
|""".stripMargin
HttpServiceTestFixture.postJsonStringRequest(
serviceUri.withPath(Uri.Path("/v1/create")),
payload,
headers,
)
HttpServiceTestFixture
.postJsonStringRequest(
serviceUri.withPath(Uri.Path("/v1/create")),
payload,
headers,
)
.parseResponse[domain.ActiveContract[JsValue]]
}
protected def initialAccountCreate(
fixture: UriFixture with EncoderFixture,
owner: domain.Party,
headers: List[HttpHeader],
): Future[(StatusCode, JsValue)] = {
): Future[(StatusCode, domain.SyncResponse[domain.ActiveContract[JsValue]])] = {
val command = accountCreateCommand(owner, "abc123")
postCreateCommand(command, fixture, headers)
}

View File

@ -11,14 +11,12 @@ import akka.http.scaladsl.model.ws.{
TextMessage,
WebSocketRequest,
}
import akka.http.scaladsl.model.{HttpHeader, StatusCodes, Uri}
import akka.http.scaladsl.model.{HttpHeader, StatusCode, StatusCodes, Uri}
import akka.stream.{KillSwitches, UniqueKillSwitch}
import akka.stream.scaladsl.{Keep, Sink, Source}
import com.daml.http.HttpServiceTestFixture.{
UseTls,
accountCreateCommand,
getContractId,
getResult,
sharedAccountCreateCommand,
}
import com.daml.http.json.SprayJson
@ -28,7 +26,6 @@ import org.scalatest._
import org.scalatest.freespec.AsyncFreeSpec
import org.scalatest.matchers.should.Matchers
import scalaz.std.option._
import scalaz.std.tuple._
import scalaz.std.vector._
import scalaz.syntax.std.option._
import scalaz.syntax.tag._
@ -366,8 +363,9 @@ abstract class AbstractWebsocketServiceIntegrationTest
aliceHeaders <- fixture.getUniquePartyAndAuthHeaders("Alice")
(party, headers) = aliceHeaders
creation <- initialIouCreate(uri, party, headers)
_ = creation._1 shouldBe a[StatusCodes.Success]
iouCid = getContractId(getResult(creation._2))
iouCid = inside(creation) { case (_: StatusCodes.Success, domain.OkResponse(c, _, _)) =>
c.contractId
}
jwt <- jwtForParties(uri)(List(party.unwrap), List(), testId)
(kill, source) = singleClientQueryStream(jwt, uri, query)
.viaMat(KillSwitches.single)(Keep.right)
@ -475,8 +473,9 @@ abstract class AbstractWebsocketServiceIntegrationTest
aliceHeaders <- getAliceHeaders
(party, headers) = aliceHeaders
creation <- initialIouCreate(uri, party, headers)
_ = creation._1 shouldBe a[StatusCodes.Success]
iouCid = getContractId(getResult(creation._2))
iouCid = inside(creation) { case (_: StatusCodes.Success, domain.OkResponse(c, _, _)) =>
c.contractId
}
jwt <- jwtForParties(uri)(List(party.unwrap), List(), testId)
(kill, source) = singleClientQueryStream(jwt, uri, query)
.viaMat(KillSwitches.single)(Keep.right)
@ -494,6 +493,13 @@ abstract class AbstractWebsocketServiceIntegrationTest
}
}
private[this] def resultContractId(
r: (StatusCode, domain.SyncResponse[domain.ActiveContract[_]])
) =
inside(r) { case (_: StatusCodes.Success, domain.OkResponse(result, _, _)) =>
result.contractId
}
"multi-party query should receive deltas as contracts are archived/created" in withHttpService {
fixture =>
import fixture.uri
@ -576,12 +582,10 @@ abstract class AbstractWebsocketServiceIntegrationTest
}
r1 <- f1
_ = r1._1 shouldBe a[StatusCodes.Success]
cid1 = getContractId(getResult(r1._2))
cid1 = resultContractId(r1)
r2 <- f2
_ = r2._1 shouldBe a[StatusCodes.Success]
cid2 = getContractId(getResult(r2._2))
cid2 = resultContractId(r2)
jwt <- jwtForParties(uri)(List(alice.unwrap, bob.unwrap), List(), testId)
(kill, source) = singleClientQueryStream(
@ -685,12 +689,10 @@ abstract class AbstractWebsocketServiceIntegrationTest
)
}
r1 <- f1
_ = r1._1 shouldBe a[StatusCodes.Success]
cid1 = getContractId(getResult(r1._2))
cid1 = resultContractId(r1)
r2 <- f2
_ = r2._1 shouldBe a[StatusCodes.Success]
cid2 = getContractId(getResult(r2._2))
cid2 = resultContractId(r2)
jwt <- jwtForParties(uri)(List(alice.unwrap), List(), testId)
(kill, source) = singleClientFetchStream(jwt, uri, fetchRequest(None))
.viaMat(KillSwitches.single)(Keep.right)
@ -738,10 +740,7 @@ abstract class AbstractWebsocketServiceIntegrationTest
fixture,
headers,
)
} yield {
assert(r._1.isSuccess)
getContractId(getResult(r._2))
}
} yield resultContractId(r)
archive = (id: domain.ContractId) =>
for {
r <- postArchiveCommand(
@ -864,12 +863,10 @@ abstract class AbstractWebsocketServiceIntegrationTest
)
}
r1 <- f1
_ = r1._1 shouldBe a[StatusCodes.Success]
cid1 = getContractId(getResult(r1._2))
cid1 = resultContractId(r1)
r2 <- f2
_ = r2._1 shouldBe a[StatusCodes.Success]
cid2 = getContractId(getResult(r2._2))
cid2 = resultContractId(r2)
jwt <- jwtForParties(uri)(List(alice.unwrap, bob.unwrap), List(), testId)
(kill, source) = singleClientFetchStream(
@ -999,7 +996,7 @@ abstract class AbstractWebsocketServiceIntegrationTest
fixture: UriFixture with EncoderFixture,
headers: List[HttpHeader],
): Future[(domain.Offset, domain.Offset)] = {
import json.JsonProtocol._, fixture.uri
import fixture.uri
type In = JsValue // JsValue might not be the most convenient choice
val syntax = Consume.syntax[In]
import syntax._
@ -1013,10 +1010,9 @@ abstract class AbstractWebsocketServiceIntegrationTest
headers,
)
)
cid = inside(
create map (_.convertTo[domain.SyncResponse[domain.ActiveContract[JsValue]]])
) { case (StatusCodes.OK, domain.OkResponse(contract, _, StatusCodes.OK)) =>
contract.contractId
cid = inside(create) {
case (StatusCodes.OK, domain.OkResponse(contract, _, StatusCodes.OK)) =>
contract.contractId
}
// wait for the creation's offset
offsetAfter <- readUntil[In] {
@ -1205,7 +1201,7 @@ abstract class AbstractWebsocketServiceIntegrationTest
for {
jwtForAliceAndBob <-
jwtForParties(uri)(actAs = aliceAndBob, readAs = Nil, ledgerId = testId)
(status, value) <-
createResponse <-
fixture
.headersWithPartyAuth(aliceAndBob)
.flatMap(headers =>
@ -1215,8 +1211,7 @@ abstract class AbstractWebsocketServiceIntegrationTest
headers = headers,
)
)
_ = status shouldBe a[StatusCodes.Success]
expectedContractId = getContractId(getResult(value))
expectedContractId = resultContractId(createResponse)
(killSwitch, source) = singleClientQueryStream(
jwt = jwtForAliceAndBob,
serviceUri = uri,

View File

@ -5,7 +5,7 @@
- Updating the package service succeeds with sufficient authorization: [AuthorizationTest.scala](ledger-service/http-json/src/it/scala/http/AuthorizationTest.scala#L89)
- accept user tokens: [TestMiddleware.scala](triggers/service/auth/src/test/scala/com/daml/auth/middleware/oauth2/TestMiddleware.scala#L152)
- badly-authorized create is rejected: [AuthorizationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthorizationSpec.scala#L60)
- badly-authorized create is rejected: [AbstractHttpServiceIntegrationTest.scala](ledger-service/http-json/src/itlib/scala/http/AbstractHttpServiceIntegrationTest.scala#L1086)
- badly-authorized create is rejected: [AbstractHttpServiceIntegrationTest.scala](ledger-service/http-json/src/itlib/scala/http/AbstractHttpServiceIntegrationTest.scala#L1029)
- badly-authorized exercise is rejected: [AuthorizationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthorizationSpec.scala#L158)
- badly-authorized exercise/create (create is unauthorized) is rejected: [AuthPropagationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthPropagationSpec.scala#L275)
- badly-authorized exercise/create (exercise is unauthorized) is rejected: [AuthPropagationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthPropagationSpec.scala#L243)
@ -13,20 +13,20 @@
- badly-authorized fetch is rejected: [AuthorizationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthorizationSpec.scala#L95)
- badly-authorized lookup is rejected: [AuthorizationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthorizationSpec.scala#L117)
- create IOU should fail if overwritten actAs & readAs result in missing permission even if the user would have the rights: [HttpServiceIntegrationTestUserManagement.scala](ledger-service/http-json/src/it/scala/http/HttpServiceIntegrationTestUserManagement.scala#L134)
- create IOU should fail if user has no permission: [HttpServiceIntegrationTestUserManagement.scala](ledger-service/http-json/src/it/scala/http/HttpServiceIntegrationTestUserManagement.scala#L108)
- create IOU should fail if user has no permission: [HttpServiceIntegrationTestUserManagement.scala](ledger-service/http-json/src/it/scala/http/HttpServiceIntegrationTestUserManagement.scala#L107)
- create IOU should work with correct user rights: [HttpServiceIntegrationTestUserManagement.scala](ledger-service/http-json/src/it/scala/http/HttpServiceIntegrationTestUserManagement.scala#L81)
- create with no signatories is rejected: [AuthorizationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthorizationSpec.scala#L50)
- create with non-signatory maintainers is rejected: [AuthorizationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthorizationSpec.scala#L72)
- exercise with no controllers is rejected: [AuthorizationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthorizationSpec.scala#L148)
- fetch fails when readAs not authed, even if prior fetch succeeded: [AbstractHttpServiceIntegrationTest.scala](ledger-service/http-json/src/itlib/scala/http/AbstractHttpServiceIntegrationTest.scala#L1146)
- fetch fails when readAs not authed, even if prior fetch succeeded: [AbstractHttpServiceIntegrationTest.scala](ledger-service/http-json/src/itlib/scala/http/AbstractHttpServiceIntegrationTest.scala#L1083)
- forbid a non-authorized party to check the status of a trigger: [TriggerServiceTest.scala](triggers/service/src/test/scala/com/digitalasset/daml/lf/engine/trigger/TriggerServiceTest.scala#L669)
- forbid a non-authorized party to list triggers: [TriggerServiceTest.scala](triggers/service/src/test/scala/com/digitalasset/daml/lf/engine/trigger/TriggerServiceTest.scala#L659)
- forbid a non-authorized party to start a trigger: [TriggerServiceTest.scala](triggers/service/src/test/scala/com/digitalasset/daml/lf/engine/trigger/TriggerServiceTest.scala#L648)
- forbid a non-authorized party to stop a trigger: [TriggerServiceTest.scala](triggers/service/src/test/scala/com/digitalasset/daml/lf/engine/trigger/TriggerServiceTest.scala#L685)
- forbid a non-authorized user to upload a DAR: [TriggerServiceTest.scala](triggers/service/src/test/scala/com/digitalasset/daml/lf/engine/trigger/TriggerServiceTest.scala#L701)
- multiple websocket requests over the same WebSocket connection are NOT allowed: [AbstractWebsocketServiceIntegrationTest.scala](ledger-service/http-json/src/itlib/scala/http/AbstractWebsocketServiceIntegrationTest.scala#L118)
- multiple websocket requests over the same WebSocket connection are NOT allowed: [AbstractWebsocketServiceIntegrationTest.scala](ledger-service/http-json/src/itlib/scala/http/AbstractWebsocketServiceIntegrationTest.scala#L115)
- refresh a token after expiry on the server side: [TriggerServiceTest.scala](triggers/service/src/test/scala/com/digitalasset/daml/lf/engine/trigger/TriggerServiceTest.scala#L726)
- reject requests with missing auth header: [AbstractHttpServiceIntegrationTest.scala](ledger-service/http-json/src/itlib/scala/http/AbstractHttpServiceIntegrationTest.scala#L543)
- reject requests with missing auth header: [AbstractHttpServiceIntegrationTest.scala](ledger-service/http-json/src/itlib/scala/http/AbstractHttpServiceIntegrationTest.scala#L520)
- request a fresh token after expiry on user request: [TriggerServiceTest.scala](triggers/service/src/test/scala/com/digitalasset/daml/lf/engine/trigger/TriggerServiceTest.scala#L711)
- return the token from a cookie: [TestMiddleware.scala](triggers/service/auth/src/test/scala/com/daml/auth/middleware/oauth2/TestMiddleware.scala#L96)
- return unauthorized on an expired token: [TestMiddleware.scala](triggers/service/auth/src/test/scala/com/daml/auth/middleware/oauth2/TestMiddleware.scala#L139)
@ -35,9 +35,9 @@
- return unauthorized without cookie: [TestMiddleware.scala](triggers/service/auth/src/test/scala/com/daml/auth/middleware/oauth2/TestMiddleware.scala#L87)
- the /login endpoint with an oauth server checking claims should not authorize disallowed admin claims: [TestMiddleware.scala](triggers/service/auth/src/test/scala/com/daml/auth/middleware/oauth2/TestMiddleware.scala#L343)
- the /login endpoint with an oauth server checking claims should not authorize unauthorized parties: [TestMiddleware.scala](triggers/service/auth/src/test/scala/com/daml/auth/middleware/oauth2/TestMiddleware.scala#L336)
- websocket request with invalid protocol token should be denied: [AbstractWebsocketServiceIntegrationTest.scala](ledger-service/http-json/src/itlib/scala/http/AbstractWebsocketServiceIntegrationTest.scala#L98)
- websocket request with valid protocol token should allow client subscribe to stream: [AbstractWebsocketServiceIntegrationTest.scala](ledger-service/http-json/src/itlib/scala/http/AbstractWebsocketServiceIntegrationTest.scala#L86)
- websocket request without protocol token should be denied: [AbstractWebsocketServiceIntegrationTest.scala](ledger-service/http-json/src/itlib/scala/http/AbstractWebsocketServiceIntegrationTest.scala#L108)
- websocket request with invalid protocol token should be denied: [AbstractWebsocketServiceIntegrationTest.scala](ledger-service/http-json/src/itlib/scala/http/AbstractWebsocketServiceIntegrationTest.scala#L95)
- websocket request with valid protocol token should allow client subscribe to stream: [AbstractWebsocketServiceIntegrationTest.scala](ledger-service/http-json/src/itlib/scala/http/AbstractWebsocketServiceIntegrationTest.scala#L83)
- websocket request without protocol token should be denied: [AbstractWebsocketServiceIntegrationTest.scala](ledger-service/http-json/src/itlib/scala/http/AbstractWebsocketServiceIntegrationTest.scala#L105)
- well-authorized create is accepted: [AuthorizationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthorizationSpec.scala#L43)
- well-authorized exercise is accepted: [AuthorizationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthorizationSpec.scala#L141)
- well-authorized exercise/create is accepted: [AuthPropagationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthPropagationSpec.scala#L221)
@ -180,7 +180,7 @@
- contract keys should be evaluated after ensure clause: [ContractKeySpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/ContractKeySpec.scala#L188)
- contract keys should be evaluated only when executing create: [ContractKeySpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/ContractKeySpec.scala#L149)
- exercise_interface with a contract instance that does not implement the interface fails.: [EvaluationOrderTest.scala](daml-lf/interpreter/src/test/scala/com/digitalasset/daml/lf/speedy/EvaluationOrderTest.scala#L1615)
- fromStartupMode should not succeed for any input when the db connection is broken: [FailureTests.scala](ledger-service/http-json/src/failurelib/scala/http/FailureTests.scala#L421)
- fromStartupMode should not succeed for any input when the db connection is broken: [FailureTests.scala](ledger-service/http-json/src/failurelib/scala/http/FailureTests.scala#L436)
- redirect to the configured callback URI after login: [TriggerServiceTest.scala](triggers/service/src/test/scala/com/digitalasset/daml/lf/engine/trigger/TriggerServiceTest.scala#L630)
- restart trigger on initialization failure due to failed connection: [TriggerServiceTest.scala](triggers/service/src/test/scala/com/digitalasset/daml/lf/engine/trigger/TriggerServiceTest.scala#L446)
- restart trigger on run-time failure due to dropped connection: [TriggerServiceTest.scala](triggers/service/src/test/scala/com/digitalasset/daml/lf/engine/trigger/TriggerServiceTest.scala#L466)
@ -202,8 +202,8 @@
## Performance:
- Tail call optimization: Tail recursion does not blow the scala JVM stack.: [TailCallTest.scala](daml-lf/interpreter/src/test/scala/com/digitalasset/daml/lf/speedy/TailCallTest.scala#L16)
- archiving a large number of contracts should succeed: [AbstractHttpServiceIntegrationTest.scala](ledger-service/http-json/src/itlib/scala/http/AbstractHttpServiceIntegrationTest.scala#L1432)
- creating and listing 20K users should be possible: [HttpServiceIntegrationTestUserManagement.scala](ledger-service/http-json/src/it/scala/http/HttpServiceIntegrationTestUserManagement.scala#L565)
- archiving a large number of contracts should succeed: [AbstractHttpServiceIntegrationTest.scala](ledger-service/http-json/src/itlib/scala/http/AbstractHttpServiceIntegrationTest.scala#L1358)
- creating and listing 20K users should be possible: [HttpServiceIntegrationTestUserManagement.scala](ledger-service/http-json/src/it/scala/http/HttpServiceIntegrationTestUserManagement.scala#L557)
## Input Validation:
- TLS configuration is parsed correctly from the config file: [CliSpec.scala](ledger-service/http-json/src/test/scala/com/digitalasset/http/CliSpec.scala#L273)