From 175f8e5e57eee2134caed33e7e444f6454cdd44f Mon Sep 17 00:00:00 2001 From: Pat Losoponkul Date: Tue, 31 Jan 2023 17:14:17 +0700 Subject: [PATCH] test(castor): add more validation tests --- .../core/util/DIDOperationValidator.scala | 21 ++- .../core/util/DIDOperationValidatorSpec.scala | 127 +++++++++++------- 2 files changed, 99 insertions(+), 49 deletions(-) diff --git a/castor/lib/core/src/main/scala/io/iohk/atala/castor/core/util/DIDOperationValidator.scala b/castor/lib/core/src/main/scala/io/iohk/atala/castor/core/util/DIDOperationValidator.scala index c4bf8737f8..418e7a5af0 100644 --- a/castor/lib/core/src/main/scala/io/iohk/atala/castor/core/util/DIDOperationValidator.scala +++ b/castor/lib/core/src/main/scala/io/iohk/atala/castor/core/util/DIDOperationValidator.scala @@ -60,12 +60,12 @@ private object CreateOperationValidator extends BaseOperationValidator { private def validateServiceNonEmptyEndpoints( operation: PrismDIDOperation.Create ): Either[DIDOperationError, Unit] = { - val serviceWithEmptyEndpoints = operation.services.filter(_.serviceEndpoint.isEmpty) + val serviceWithEmptyEndpoints = operation.services.filter(_.serviceEndpoint.isEmpty).map(_.id) if (serviceWithEmptyEndpoints.isEmpty) Right(()) else Left( DIDOperationError.InvalidArgument( - s"new service must not have empty serviceEndpoint: ${serviceWithEmptyEndpoints.mkString("[", ", ", "]")}" + s"service must not have empty serviceEndpoint: ${serviceWithEmptyEndpoints.mkString("[", ", ", "]")}" ) ) } @@ -90,6 +90,7 @@ private object UpdateOperationValidator extends BaseOperationValidator { _ <- validatePreviousOperationHash(operation, _.previousOperationHash) _ <- validateNonEmptyUpdateAction(operation) _ <- validateUpdateServiceNonEmpty(operation) + _ <- validateAddServiceNonEmptyEndpoint(operation) } yield () } @@ -113,6 +114,22 @@ private object UpdateOperationValidator extends BaseOperationValidator { ) } + private def validateAddServiceNonEmptyEndpoint( + operation: PrismDIDOperation.Update + ): Either[DIDOperationError, Unit] = { + val serviceWithEmptyEndpoints = operation.actions + .collect { case UpdateDIDAction.AddService(s) => s } + .filter(_.serviceEndpoint.isEmpty) + .map(_.id) + if (serviceWithEmptyEndpoints.isEmpty) Right(()) + else + Left( + DIDOperationError.InvalidArgument( + s"service must not have empty serviceEndpoint: ${serviceWithEmptyEndpoints.mkString("[", ", ", "]")}" + ) + ) + } + private def extractKeyIds(operation: PrismDIDOperation.Update): Seq[String] = operation.actions.flatMap { case UpdateDIDAction.AddKey(publicKey) => Some(publicKey.id) case UpdateDIDAction.AddInternalKey(publicKey) => Some(publicKey.id) diff --git a/castor/lib/core/src/test/scala/io/iohk/atala/castor/core/util/DIDOperationValidatorSpec.scala b/castor/lib/core/src/test/scala/io/iohk/atala/castor/core/util/DIDOperationValidatorSpec.scala index 3abd718956..6d6c4faf5c 100644 --- a/castor/lib/core/src/test/scala/io/iohk/atala/castor/core/util/DIDOperationValidatorSpec.scala +++ b/castor/lib/core/src/test/scala/io/iohk/atala/castor/core/util/DIDOperationValidatorSpec.scala @@ -34,6 +34,10 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { y = Base64UrlString.fromStringUnsafe("00") ) + private def invalidArgumentContainsString(text: String): Assertion[Either[Any, Any]] = isLeft( + isSubtype[DIDOperationError.InvalidArgument](hasField("msg", _.msg, containsString(text))) + ) + private val createOperationValidationSpec = { def createPrismDIDOperation( publicKeys: Seq[PublicKey] = Nil, @@ -90,11 +94,7 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { ) val op = createPrismDIDOperation(publicKeys = publicKeys, internalKeys = internalKeys) assert(DIDOperationValidator(Config(50, 50)).validate(op))( - isLeft( - isSubtype[DIDOperationError.InvalidArgument]( - hasField("msg", _.msg, containsString("id for public-keys is not unique")) - ) - ) + invalidArgumentContainsString("id for public-keys is not unique") ) }, test("reject CreateOperation on too many service access") { @@ -120,11 +120,7 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { ) val op = createPrismDIDOperation(services = services) assert(DIDOperationValidator(Config(15, 15)).validate(op))( - isLeft( - isSubtype[DIDOperationError.InvalidArgument]( - hasField("msg", _.msg, containsString("id for services is not unique")) - ) - ) + invalidArgumentContainsString("id for services is not unique") ) }, test("reject CreateOperation on invalid key-id") { @@ -137,11 +133,7 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { ) val op = createPrismDIDOperation(publicKeys = publicKeys) assert(DIDOperationValidator(Config(50, 50)).validate(op))( - isLeft( - isSubtype[DIDOperationError.InvalidArgument]( - hasField("msg", _.msg, containsString("public key id is invalid: [key 1, key 2]")) - ) - ) + invalidArgumentContainsString("public key id is invalid: [key 1, key 2]") ) }, test("reject CreateOperation on invalid service-id") { @@ -154,33 +146,56 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { ) val op = createPrismDIDOperation(services = services) assert(DIDOperationValidator(Config(50, 50)).validate(op))( - isLeft( - isSubtype[DIDOperationError.InvalidArgument]( - hasField("msg", _.msg, containsString("service id is invalid: [service 1, service 2]")) - ) - ) + invalidArgumentContainsString("service id is invalid: [service 1, service 2]") ) }, - test("reject CreateOperation when master does not exist") { + test("reject CreateOperation when master key does not exist") { val op = createPrismDIDOperation(internalKeys = Nil) assert(DIDOperationValidator(Config(50, 50)).validate(op))( - isLeft( - isSubtype[DIDOperationError.InvalidArgument]( - hasField("msg", _.msg, containsString("operation must contain at least 1 master key")) + invalidArgumentContainsString("operation must contain at least 1 master key") + ) + }, + test("reject CreateOperation when service endpoint is empty") { + val op = createPrismDIDOperation(services = + Seq( + Service( + id = "service-0", + `type` = ServiceType.MediatorService, + serviceEndpoint = Nil + ) + ) + ) + assert(DIDOperationValidator(Config(50, 50)).validate(op))( + invalidArgumentContainsString("service must not have empty serviceEndpoint") + ) + }, + test("reject CreateOperation when service URL is not normalized") { + val op = createPrismDIDOperation(services = + Seq( + Service( + id = "service-0", + `type` = ServiceType.MediatorService, + serviceEndpoint = Seq( + URI.create("http://example.com/login/../login") + ) ) ) ) + assert(DIDOperationValidator(Config(50, 50)).validate(op))( + invalidArgumentContainsString("serviceEndpoint URIs must be normalized") + ) } ) } private val updateOperationValidationSpec = { def updatePrismDIDOperation( - actions: Seq[UpdateDIDAction] = Nil + actions: Seq[UpdateDIDAction] = Nil, + previousOperationHash: ArraySeq[Byte] = ArraySeq.fill(32)(0) ) = PrismDIDOperation.Update( - did = PrismDID.buildCanonicalFromSuffix("0" * 64).toOption.get, - ArraySeq.fill(32)(0), + PrismDID.buildCanonicalFromSuffix("0" * 64).toOption.get, + previousOperationHash, actions ) @@ -265,11 +280,7 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { val action2 = UpdateDIDAction.RemoveKey(id = "key 2") val op = updatePrismDIDOperation(Seq(action1, action2)) assert(DIDOperationValidator(Config(50, 50)).validate(op))( - isLeft( - isSubtype[DIDOperationError.InvalidArgument]( - hasField("msg", _.msg, containsString("public key id is invalid: [key 1, key 2]")) - ) - ) + invalidArgumentContainsString("public key id is invalid: [key 1, key 2]") ) }, test("reject UpdateOperation on invalid service-id") { @@ -283,32 +294,54 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { val action2 = UpdateDIDAction.RemoveService(id = "service 2") val op = updatePrismDIDOperation(Seq(action1, action2)) assert(DIDOperationValidator(Config(50, 50)).validate(op))( - isLeft( - isSubtype[DIDOperationError.InvalidArgument]( - hasField("msg", _.msg, containsString("service id is invalid: [service 1, service 2]")) - ) - ) + invalidArgumentContainsString("service id is invalid: [service 1, service 2]") + ) + }, + test("reject UpdateOperation on invalid previousOperationHash") { + val op = updatePrismDIDOperation(previousOperationHash = ArraySeq.empty) + assert(DIDOperationValidator(Config(50, 50)).validate(op))( + invalidArgumentContainsString("previousOperationHash must have a size of") ) }, test("reject UpdateOperation on empty update action") { val op = updatePrismDIDOperation(Nil) assert(DIDOperationValidator(Config(50, 50)).validate(op))( - isLeft( - isSubtype[DIDOperationError.InvalidArgument]( - hasField("msg", _.msg, containsString("operation must contain at least 1 update action")) - ) - ) + invalidArgumentContainsString("operation must contain at least 1 update action") ) }, - test("reject UpdateOperation on UpdateService action empty") { - val op = updatePrismDIDOperation(Seq(UpdateDIDAction.UpdateService("service0", None, Nil))) + test("reject UpdateOperation when action AddService serviceEndpoint is empty") { + val op = updatePrismDIDOperation( + Seq(UpdateDIDAction.AddService(Service("service-1", ServiceType.MediatorService, Nil))) + ) assert(DIDOperationValidator(Config(50, 50)).validate(op))( - isLeft( - isSubtype[DIDOperationError.InvalidArgument]( - hasField("msg", _.msg, containsString("must not have both 'type' and 'serviceEndpoints' empty")) + invalidArgumentContainsString("service must not have empty serviceEndpoint") + ) + }, + test("reject UpdateOperation when action AddService serviceEndpoint is not normalized") { + val op = updatePrismDIDOperation( + Seq( + UpdateDIDAction.AddService( + Service("service-1", ServiceType.MediatorService, Seq(URI.create("http://example.com/login/../login"))) ) ) ) + assert(DIDOperationValidator(Config(50, 50)).validate(op))( + invalidArgumentContainsString("serviceEndpoint URIs must be normalized") + ) + }, + test("reject updateOperation when action UpdateService serviceEndpoint is not normalized") { + val op = updatePrismDIDOperation( + Seq(UpdateDIDAction.UpdateService("service-1", None, Seq(URI.create("http://example.com/login/../login")))) + ) + assert(DIDOperationValidator(Config(50, 50)).validate(op))( + invalidArgumentContainsString("serviceEndpoint URIs must be normalized") + ) + }, + test("reject UpdateOperation when action UpdateService have both type and serviceEndpoint empty") { + val op = updatePrismDIDOperation(Seq(UpdateDIDAction.UpdateService("service-1", None, Nil))) + assert(DIDOperationValidator(Config(50, 50)).validate(op))( + invalidArgumentContainsString("must not have both 'type' and 'serviceEndpoints' empty") + ) } ) }