From f93d284eb40e4068466563e1f48b45a4693eabb3 Mon Sep 17 00:00:00 2001 From: "siva.isikella" <7499500+sivaprakashiv@users.noreply.github.com> Date: Wed, 12 Oct 2022 13:34:39 +0100 Subject: [PATCH] APID-520 - Porting hmrc mongo driver - Initial changes --- .../AcceptanceTestSpec.scala | 5 +- .../FieldDefinitionRepository.scala | 128 +++++++---- .../repository/MongoCrudHelper.scala | 135 ++++++------ .../repository/MongoDb.scala | 46 ++-- .../repository/MongoErrorHandler.scala | 86 +++++--- .../repository/MongoIndexCreator.scala | 64 +++--- .../SubscriptionFieldsRepository.scala | 201 ++++++++++++------ conf/application.conf | 2 +- project/AppDependencies.scala | 17 +- .../ApiFieldDefinitionsRepositorySpec.scala | 68 +++--- .../repository/MongoErrorHandlerSpec.scala | 200 +++++++++-------- .../SubscriptionFieldsRepositorySpec.scala | 71 ++++--- 12 files changed, 603 insertions(+), 420 deletions(-) diff --git a/acceptance/uk/gov/hmrc/apisubscriptionfields/AcceptanceTestSpec.scala b/acceptance/uk/gov/hmrc/apisubscriptionfields/AcceptanceTestSpec.scala index fee7de3..d19691b 100644 --- a/acceptance/uk/gov/hmrc/apisubscriptionfields/AcceptanceTestSpec.scala +++ b/acceptance/uk/gov/hmrc/apisubscriptionfields/AcceptanceTestSpec.scala @@ -17,7 +17,6 @@ package uk.gov.hmrc.apisubscriptionfields import java.util.UUID - import org.scalatest._ import org.scalatestplus.play.guice.GuiceOneServerPerSuite import play.api.Application @@ -27,7 +26,6 @@ import play.api.mvc._ import play.api.mvc.request.RequestTarget import play.api.test.FakeRequest import play.api.test.Helpers._ -import play.modules.reactivemongo.ReactiveMongoComponent import uk.gov.hmrc.apisubscriptionfields.model._ import scala.concurrent.ExecutionContext.Implicits.global @@ -35,6 +33,7 @@ import scala.concurrent.duration._ import scala.concurrent.{Await, Future} import cats.data.NonEmptyList import uk.gov.hmrc.apisubscriptionfields.controller.Helper +import uk.gov.hmrc.mongo.MongoComponent trait AcceptanceTestSpec extends FeatureSpec with GivenWhenThen @@ -97,7 +96,7 @@ trait AcceptanceTestSpec extends FeatureSpec } private def dropDatabase(): Unit = { - await( app.injector.instanceOf[ReactiveMongoComponent].mongoConnector.db().drop()) + await( app.injector.instanceOf[MongoComponent].database.drop().toFuture()) } def createRequest(method: String, path: String) = diff --git a/app/uk/gov/hmrc/apisubscriptionfields/repository/FieldDefinitionRepository.scala b/app/uk/gov/hmrc/apisubscriptionfields/repository/FieldDefinitionRepository.scala index 60caf4a..1a427d7 100644 --- a/app/uk/gov/hmrc/apisubscriptionfields/repository/FieldDefinitionRepository.scala +++ b/app/uk/gov/hmrc/apisubscriptionfields/repository/FieldDefinitionRepository.scala @@ -16,18 +16,24 @@ package uk.gov.hmrc.apisubscriptionfields.repository -import javax.inject.{Inject, Singleton} +import akka.stream.Materializer import com.google.inject.ImplementedBy +import org.bson.codecs.configuration.CodecRegistries.{fromCodecs, fromRegistries} +import org.mongodb.scala.model.Filters.{and, equal} +import org.mongodb.scala.model.Indexes.ascending +import org.mongodb.scala.model.{Filters, IndexModel, IndexOptions} +import org.mongodb.scala.{MongoClient, MongoCollection} import play.api.libs.json._ -import reactivemongo.api.indexes.IndexType -import reactivemongo.bson.BSONObjectID -import reactivemongo.play.json.collection.JSONCollection -import uk.gov.hmrc.mongo.ReactiveRepository -import uk.gov.hmrc.mongo.json.ReactiveMongoFormats +import uk.gov.hmrc.apisubscriptionfields.model.JsonFormatters.ApiFieldDefinitionsJF +import uk.gov.hmrc.apisubscriptionfields.model.Types._ import uk.gov.hmrc.apisubscriptionfields.model._ -import Types._ +import uk.gov.hmrc.apisubscriptionfields.utils.ApplicationLogger +import uk.gov.hmrc.mongo.MongoComponent +import uk.gov.hmrc.mongo.play.json.{Codecs, CollectionFactory, PlayMongoRepository} -import scala.concurrent.Future +import javax.inject.{Inject, Singleton} +import scala.concurrent.{ExecutionContext, Future} +import scala.util.control.NonFatal @ImplementedBy(classOf[ApiFieldDefinitionsMongoRepository]) trait ApiFieldDefinitionsRepository { @@ -42,53 +48,83 @@ trait ApiFieldDefinitionsRepository { } @Singleton -class ApiFieldDefinitionsMongoRepository @Inject() (mongoDbProvider: MongoDbProvider) - extends ReactiveRepository[ApiFieldDefinitions, BSONObjectID]("fieldsDefinitions", mongoDbProvider.mongo, JsonFormatters.ApiFieldDefinitionsJF, ReactiveMongoFormats.objectIdFormats) +class ApiFieldDefinitionsMongoRepository @Inject() (mongo: MongoComponent) + (implicit ec: ExecutionContext, val mat: Materializer) + extends PlayMongoRepository[ApiFieldDefinitions]( + collectionName = "fieldsDefinitions", + mongoComponent = mongo, + domainFormat = JsonFormatters.ApiFieldDefinitionsJF, + indexes = Seq( + IndexModel(ascending(List("apiContext", "apiVersion"): _*), + IndexOptions() + .name("apiContext-apiVersion_index") + .background(true) + .unique(true)) + )) with ApiFieldDefinitionsRepository - with MongoCrudHelper[ApiFieldDefinitions] { - - override val mongoCollection: JSONCollection = collection - - override def indexes = Seq( - createCompoundIndex( - indexFieldMappings = Seq( - "apiContext" -> IndexType.Ascending, - "apiVersion" -> IndexType.Ascending - ), - indexName = Some("apiContext-apiVersion_index"), - isUnique = true - ) - ) - - override def save(definitions: ApiFieldDefinitions): Future[(ApiFieldDefinitions, IsInsert)] = { - import JsonFormatters.ApiFieldDefinitionsJF - save(definitions, selectorFor(definitions)) + with ApplicationLogger { +// with MongoCrudHelper[ApiFieldDefinitions] { + + override lazy val collection: MongoCollection[ApiFieldDefinitions] = + CollectionFactory + .collection(mongo.database, collectionName, domainFormat) + .withCodecRegistry( + fromRegistries( + fromCodecs( + Codecs.playFormatCodec(domainFormat), + Codecs.playFormatCodec(JsonFormatters.ApiContextJF), + Codecs.playFormatCodec(JsonFormatters.ApiVersionJF), + Codecs.playFormatCodec(JsonFormatters.ApiFieldDefinitionsJF), + Codecs.playFormatCodec(JsonFormatters.ValidationJF) + ), + MongoClient.DEFAULT_CODEC_REGISTRY + ) + ) + + def save(definitions: ApiFieldDefinitions): Future[(ApiFieldDefinitions, IsInsert)] = { + val query = and(equal("apiContext", Codecs.toBson(definitions.apiContext.value)), + equal("apiVersion", Codecs.toBson(definitions.apiVersion.value))) + + collection.find(query).headOption flatMap { + case Some(_: ApiFieldDefinitions) => + for { + updatedDefinitions <- collection.replaceOne( + filter = query, + replacement = definitions + ).toFuture().map(_ => definitions) + } yield (updatedDefinitions, false) + + case None => + for { + newDefinitions <- collection.insertOne(definitions).toFuture().map(_ => definitions) + } yield (newDefinitions, true) + } + +// collection +// .findOneAndUpdate(Filters.and(equal("apiContext", Codecs.toBson(definitions.apiContext.value)), +// equal("apiVersion", Codecs.toBson(definitions.apiVersion.value))), +// definitions +// ).map(_.asInstanceOf[ApiFieldDefinitions]).head } override def fetch(apiContext: ApiContext, apiVersion: ApiVersion): Future[Option[ApiFieldDefinitions]] = { - getOne(selectorFor(apiContext, apiVersion)) + collection.find(Filters.and(equal("apiContext", Codecs.toBson(apiContext.value)), + equal("apiVersion", Codecs.toBson(apiVersion.value)))).headOption() } override def fetchAll(): Future[List[ApiFieldDefinitions]] = { - getMany(Json.obj()) + collection.find().toFuture().map(_.toList) } override def delete(apiContext: ApiContext, apiVersion: ApiVersion): Future[Boolean] = { - deleteOne(selectorFor(apiContext, apiVersion)) - } - - private def selectorFor(apiContext: ApiContext, apiVersion: ApiVersion): JsObject = { - selector(apiContext, apiVersion) - } - - private def selectorFor(fd: ApiFieldDefinitions): JsObject = { - selector(fd.apiContext, fd.apiVersion) - } - - private def selector(apiContext: ApiContext, apiVersion: ApiVersion): JsObject = { - Json.obj( - "apiContext" -> apiContext.value, - "apiVersion" -> apiVersion.value - ) + collection.deleteOne(Filters.and(equal("apiContext", Codecs.toBson(apiContext.value)), + equal("apiVersion", Codecs.toBson(apiVersion.value)))) + .toFuture() + .map(_.wasAcknowledged()) + +// .head().map(result => result.getDeletedCount > 0) recoverWith { +// case NonFatal(e) => +// appLogger.error(s"Could not delete entity for apiContext ${apiContext.value} and apiVersion ${apiVersion.value}") +// Future.successful(false)} } } diff --git a/app/uk/gov/hmrc/apisubscriptionfields/repository/MongoCrudHelper.scala b/app/uk/gov/hmrc/apisubscriptionfields/repository/MongoCrudHelper.scala index e63c718..49ea33a 100644 --- a/app/uk/gov/hmrc/apisubscriptionfields/repository/MongoCrudHelper.scala +++ b/app/uk/gov/hmrc/apisubscriptionfields/repository/MongoCrudHelper.scala @@ -14,64 +14,77 @@ * limitations under the License. */ -package uk.gov.hmrc.apisubscriptionfields.repository - -import play.api.libs.json._ -import reactivemongo.play.json._ -import reactivemongo.play.json.collection.JSONCollection - -import reactivemongo.api.Cursor - -import scala.concurrent.ExecutionContext.Implicits.global -import scala.concurrent.Future -import uk.gov.hmrc.apisubscriptionfields.model.Types.IsInsert - -trait MongoCrudHelper[T] extends MongoIndexCreator with MongoErrorHandler { - - protected val mongoCollection: JSONCollection - - def saveAtomic(selector: JsObject, updateOperations: JsObject)(implicit w: OFormat[T]): Future[(T, IsInsert)] = { - val updateOp = mongoCollection.updateModifier( - update = updateOperations, - fetchNewObject = true, - upsert = true - ) - - mongoCollection.findAndModify(selector, updateOp).map { findAndModifyResult => - val maybeTuple: Option[(T, IsInsert)] = for { - value <- findAndModifyResult.value - updateLastError <- findAndModifyResult.lastError - } yield (value.as[T], !updateLastError.updatedExisting) - - maybeTuple.fold[(T, IsInsert)] { - handleError(selector, findAndModifyResult) - }(tuple => tuple) - } - } - - private def handleError(selector: JsObject, findAndModifyResult: mongoCollection.BatchCommands.FindAndModifyCommand.FindAndModifyResult) = { - val error = s"Error upserting database for $selector." - appLogger.error(s"$error lastError: ${findAndModifyResult.lastError}") - throw new RuntimeException(error) - } - - def save(entity: T, selector: JsObject)(implicit w: OWrites[T]): Future[(T, IsInsert)] = { - mongoCollection.update(ordered=false).one(selector, entity, upsert = true).map { updateWriteResult => (entity, handleSaveError(updateWriteResult, s"Could not save entity: $entity")) } - } - - def getMany(selector: JsObject)(implicit r: Reads[T]): Future[List[T]] = { - mongoCollection.find[JsObject, JsObject](selector, None).cursor[T]().collect[List](Int.MaxValue, Cursor.FailOnError[List[T]]()) - } - - def getOne(selector: JsObject)(implicit r: Reads[T]): Future[Option[T]] = { - mongoCollection.find[JsObject, JsObject](selector, None).one[T] - } - - def deleteOne(selector: JsObject): Future[Boolean] = { - mongoCollection.delete(ordered=false).one(selector, limit = Some(1)).map(handleDeleteError(_, s"Could not delete entity for selector: $selector")) - } - - def deleteMany(selector: JsObject): Future[Boolean] = { - mongoCollection.delete().one(selector).map(handleDeleteError(_, s"Could not delete entity for selector: $selector")) - } -} +///* +// * Copyright 2022 HM Revenue & Customs +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ +// +//package uk.gov.hmrc.apisubscriptionfields.repository +// +//import org.mongodb.scala.MongoCollection +//import play.api.libs.json._ +// +//import scala.concurrent.ExecutionContext.Implicits.global +//import scala.concurrent.Future +//import uk.gov.hmrc.apisubscriptionfields.model.Types.IsInsert +// +//trait MongoCrudHelper[T] extends MongoIndexCreator with MongoErrorHandler { +// +// protected val collection: MongoCollection[T] +// +// def saveAtomic(selector: JsObject, updateOperations: JsObject)(implicit w: OFormat[T]): Future[(T, IsInsert)] = { +// val updateOp = collection.updateModifier( +// update = updateOperations, +// fetchNewObject = true, +// upsert = true +// ) +// +// collection.findAndModify(selector, updateOp).map { findAndModifyResult => +// val maybeTuple: Option[(T, IsInsert)] = for { +// value <- findAndModifyResult.value +// updateLastError <- findAndModifyResult.lastError +// } yield (value.as[T], !updateLastError.updatedExisting) +// +// maybeTuple.fold[(T, IsInsert)] { +// handleError(selector, findAndModifyResult) +// }(tuple => tuple) +// } +// } +// +// private def handleError(selector: JsObject, findAndModifyResult: collection.BatchCommands.FindAndModifyCommand.FindAndModifyResult) = { +// val error = s"Error upserting database for $selector." +// appLogger.error(s"$error lastError: ${findAndModifyResult.lastError}") +// throw new RuntimeException(error) +// } +// +// def save(entity: T, selector: JsObject)(implicit w: OWrites[T]): Future[(T, IsInsert)] = { +// collection.findOneAndUpdate(ordered=false).one(selector, entity, upsert = true).map { updateWriteResult => (entity, handleSaveError(updateWriteResult, s"Could not save entity: $entity")) } +// } +// +// def getMany(selector: JsObject)(implicit r: Reads[T]): Future[List[T]] = { +// collection.find[JsObject, JsObject](selector, None).cursor[T]().collect[List](Int.MaxValue, Cursor.FailOnError[List[T]]()) +// } +// +// def getOne(selector: JsObject)(implicit r: Reads[T]): Future[Option[T]] = { +// collection.find[JsObject, JsObject](selector, None).one[T] +// } +// +// def deleteOne(selector: JsObject): Future[Boolean] = { +// collection.delete(ordered=false).one(selector, limit = Some(1)).map(handleDeleteError(_, s"Could not delete entity for selector: $selector")) +// } +// +// def deleteMany(selector: JsObject): Future[Boolean] = { +// collection.delete().one(selector).map(handleDeleteError(_, s"Could not delete entity for selector: $selector")) +// } +//} diff --git a/app/uk/gov/hmrc/apisubscriptionfields/repository/MongoDb.scala b/app/uk/gov/hmrc/apisubscriptionfields/repository/MongoDb.scala index f04a305..2f16b18 100644 --- a/app/uk/gov/hmrc/apisubscriptionfields/repository/MongoDb.scala +++ b/app/uk/gov/hmrc/apisubscriptionfields/repository/MongoDb.scala @@ -14,19 +14,33 @@ * limitations under the License. */ -package uk.gov.hmrc.apisubscriptionfields.repository - -import com.google.inject.ImplementedBy -import javax.inject.{Inject, Singleton} -import play.modules.reactivemongo.ReactiveMongoComponent -import reactivemongo.api.DB - -@ImplementedBy(classOf[MongoDb]) -trait MongoDbProvider { - def mongo: () => DB -} - -@Singleton -class MongoDb @Inject()(component: ReactiveMongoComponent) extends MongoDbProvider { - override val mongo: () => DB = component.mongoConnector.db -} +///* +// * Copyright 2022 HM Revenue & Customs +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ +// +//package uk.gov.hmrc.apisubscriptionfields.repository +// +//import com.google.inject.ImplementedBy +//import javax.inject.{Inject, Singleton} +// +//@ImplementedBy(classOf[MongoDb]) +//trait MongoDbProvider { +// def mongo: () => DB +//} +// +//@Singleton +//class MongoDb @Inject()(component: ReactiveMongoComponent) extends MongoDbProvider { +// override val mongo: () => DB = component.mongoConnector.db +//} diff --git a/app/uk/gov/hmrc/apisubscriptionfields/repository/MongoErrorHandler.scala b/app/uk/gov/hmrc/apisubscriptionfields/repository/MongoErrorHandler.scala index 25ad247..4d97280 100644 --- a/app/uk/gov/hmrc/apisubscriptionfields/repository/MongoErrorHandler.scala +++ b/app/uk/gov/hmrc/apisubscriptionfields/repository/MongoErrorHandler.scala @@ -14,38 +14,54 @@ * limitations under the License. */ -package uk.gov.hmrc.apisubscriptionfields.repository - -import reactivemongo.api.commands.{UpdateWriteResult, WriteResult} -import uk.gov.hmrc.apisubscriptionfields.model.Types.IsInsert -import uk.gov.hmrc.apisubscriptionfields.utils.ApplicationLogger - -trait MongoErrorHandler extends ApplicationLogger { - - def handleDeleteError(result: WriteResult, exceptionMsg: => String): Boolean = { - handleError(result, databaseAltered, exceptionMsg) - } - - def handleSaveError(updateWriteResult: UpdateWriteResult, exceptionMsg: => String): IsInsert = { - - def handleUpsertError(result: WriteResult) = - if (databaseAltered(result)) - updateWriteResult.upserted.nonEmpty - else - throw new RuntimeException(exceptionMsg) - - handleError(updateWriteResult, handleUpsertError, exceptionMsg) - } - - private def handleError(result: WriteResult, f: WriteResult => Boolean, exceptionMsg: String): Boolean = { - result.writeConcernError.fold(f(result)) { - errMsg => { - val errorMsg = s"""$exceptionMsg. $errMsg""" - appLogger.error(errorMsg) - throw new RuntimeException(errorMsg) - } - } - } - - private def databaseAltered(writeResult: WriteResult): Boolean = writeResult.n > 0 -} +///* +// * Copyright 2022 HM Revenue & Customs +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ +// +//package uk.gov.hmrc.apisubscriptionfields.repository +// +//import com.mongodb.client.result.DeleteResult +//import uk.gov.hmrc.apisubscriptionfields.model.Types.IsInsert +//import uk.gov.hmrc.apisubscriptionfields.utils.ApplicationLogger +// +//trait MongoErrorHandler extends ApplicationLogger { +// +// def handleDeleteError(result: DeleteResult, exceptionMsg: => String): Boolean = { +// handleError(result, databaseAltered, exceptionMsg) +// } +// +// def handleSaveError(updateWriteResult: UpdateWriteResult, exceptionMsg: => String): IsInsert = { +// +// def handleUpsertError(result: WriteResult) = +// if (databaseAltered(result)) +// updateWriteResult.upserted.nonEmpty +// else +// throw new RuntimeException(exceptionMsg) +// +// handleError(updateWriteResult, handleUpsertError, exceptionMsg) +// } +// +// private def handleError(result: DeleteResult, f: DeleteResult => Boolean, exceptionMsg: String): Boolean = { +// result.writeConcernError.fold(f(result)) { +// errMsg => { +// val errorMsg = s"""$exceptionMsg. $errMsg""" +// appLogger.error(errorMsg) +// throw new RuntimeException(errorMsg) +// } +// } +// } +// +// private def databaseAltered(writeResult: WriteResult): Boolean = writeResult.n > 0 +//} diff --git a/app/uk/gov/hmrc/apisubscriptionfields/repository/MongoIndexCreator.scala b/app/uk/gov/hmrc/apisubscriptionfields/repository/MongoIndexCreator.scala index 9d37efd..364154e 100644 --- a/app/uk/gov/hmrc/apisubscriptionfields/repository/MongoIndexCreator.scala +++ b/app/uk/gov/hmrc/apisubscriptionfields/repository/MongoIndexCreator.scala @@ -14,27 +14,43 @@ * limitations under the License. */ -package uk.gov.hmrc.apisubscriptionfields.repository - -import reactivemongo.api.indexes.IndexType.Ascending -import reactivemongo.api.indexes.{Index, IndexType} - - -trait MongoIndexCreator { - - def createSingleFieldAscendingIndex(indexFieldKey: String, indexName: Option[String], isUnique: Boolean): Index = { - - createCompoundIndex(indexFieldMappings = Seq(indexFieldKey -> Ascending), indexName, isUnique) - } - - def createCompoundIndex(indexFieldMappings: Seq[(String, IndexType)], indexName: Option[String], - isUnique: Boolean, isBackground: Boolean = true): Index = { - - Index( - key = indexFieldMappings, - name = indexName, - unique = isUnique, - background = isBackground - ) - } -} +///* +// * Copyright 2022 HM Revenue & Customs +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ +// +//package uk.gov.hmrc.apisubscriptionfields.repository +// +//import reactivemongo.api.indexes.IndexType.Ascending +//import reactivemongo.api.indexes.{Index, IndexType} +// +// +//trait MongoIndexCreator { +// +// def createSingleFieldAscendingIndex(indexFieldKey: String, indexName: Option[String], isUnique: Boolean): Index = { +// +// createCompoundIndex(indexFieldMappings = Seq(indexFieldKey -> Ascending), indexName, isUnique) +// } +// +// def createCompoundIndex(indexFieldMappings: Seq[(String, IndexType)], indexName: Option[String], +// isUnique: Boolean, isBackground: Boolean = true): Index = { +// +// Index( +// key = indexFieldMappings, +// name = indexName, +// unique = isUnique, +// background = isBackground +// ) +// } +//} diff --git a/app/uk/gov/hmrc/apisubscriptionfields/repository/SubscriptionFieldsRepository.scala b/app/uk/gov/hmrc/apisubscriptionfields/repository/SubscriptionFieldsRepository.scala index b09f6f4..30f22b1 100644 --- a/app/uk/gov/hmrc/apisubscriptionfields/repository/SubscriptionFieldsRepository.scala +++ b/app/uk/gov/hmrc/apisubscriptionfields/repository/SubscriptionFieldsRepository.scala @@ -17,19 +17,21 @@ package uk.gov.hmrc.apisubscriptionfields.repository import javax.inject.{Inject, Singleton} - import com.google.inject.ImplementedBy -import reactivemongo.api.indexes.IndexType -import reactivemongo.bson.BSONObjectID -import reactivemongo.play.json._ -import reactivemongo.play.json.collection.JSONCollection import uk.gov.hmrc.apisubscriptionfields.model._ import Types.IsInsert -import uk.gov.hmrc.mongo.ReactiveRepository -import uk.gov.hmrc.mongo.json.ReactiveMongoFormats -import scala.concurrent.Future +import akka.stream.Materializer +import org.bson.codecs.configuration.CodecRegistries.{fromCodecs, fromRegistries} +import org.mongodb.scala.model.Filters.{and, equal} +import org.mongodb.scala.{MongoClient, MongoCollection} +import org.mongodb.scala.model.{Filters, IndexModel, IndexOptions} +import org.mongodb.scala.model.Indexes.ascending +import uk.gov.hmrc.mongo.MongoComponent + +import scala.concurrent.{ExecutionContext, Future} import play.api.libs.json.Json import play.api.libs.json.JsObject +import uk.gov.hmrc.mongo.play.json.{Codecs, CollectionFactory, PlayMongoRepository} @ImplementedBy(classOf[SubscriptionFieldsMongoRepository]) trait SubscriptionFieldsRepository { @@ -48,89 +50,152 @@ trait SubscriptionFieldsRepository { @Singleton -class SubscriptionFieldsMongoRepository @Inject()(mongoDbProvider: MongoDbProvider) - extends ReactiveRepository[SubscriptionFields, BSONObjectID]( - "subscriptionFields", - mongoDbProvider.mongo, - JsonFormatters.SubscriptionFieldsJF, - ReactiveMongoFormats.objectIdFormats - ) +class SubscriptionFieldsMongoRepository @Inject()(mongo: MongoComponent) + (implicit ec: ExecutionContext, val mat: Materializer) + extends PlayMongoRepository[SubscriptionFields]( + collectionName = "subscriptionFields", + mongoComponent = mongo, + domainFormat = JsonFormatters.SubscriptionFieldsJF, + indexes = Seq( + IndexModel(ascending(List("clientId", "apiContext", "apiVersion"): _*), + IndexOptions() + .name("clientId-apiContext-apiVersion_Index") + .unique(true)), + IndexModel(ascending("clientId"), + IndexOptions() + .name("clientIdIndex") + .unique(false)), + IndexModel(ascending("fieldsId"), + IndexOptions() + .name("fieldsIdIndex") + .unique(true)) + )) +// { + +// extends ReactiveRepository[SubscriptionFields, BSONObjectID]( +// "subscriptionFields", +// mongoDbProvider.mongo, +// JsonFormatters.SubscriptionFieldsJF, +// ReactiveMongoFormats.objectIdFormats +// ) with SubscriptionFieldsRepository - with MongoCrudHelper[SubscriptionFields] +// with MongoCrudHelper[SubscriptionFields] with JsonFormatters { +// +// override val mongoCollection: JSONCollection = collection +// +// override def indexes = Seq( +// createCompoundIndex( +// indexFieldMappings = Seq( +// "clientId" -> IndexType.Ascending, +// "apiContext" -> IndexType.Ascending, +// "apiVersion" -> IndexType.Ascending +// ), +// indexName = Some("clientId-apiContext-apiVersion_Index"), +// isUnique = true +// ), +// createSingleFieldAscendingIndex( +// indexFieldKey = "clientId", +// indexName = Some("clientIdIndex"), +// isUnique = false +// ), +// createSingleFieldAscendingIndex( +// indexFieldKey = "fieldsId", +// indexName = Some("fieldsIdIndex"), +// isUnique = true +// ) +// ) + + override lazy val collection: MongoCollection[SubscriptionFields] = + CollectionFactory + .collection(mongo.database, collectionName, domainFormat) + .withCodecRegistry( + fromRegistries( + fromCodecs( + Codecs.playFormatCodec(domainFormat), + Codecs.playFormatCodec(JsonFormatters.ApiContextJF), + Codecs.playFormatCodec(JsonFormatters.ApiVersionJF), + Codecs.playFormatCodec(JsonFormatters.SubscriptionFieldsIdjsonFormat), + Codecs.playFormatCodec(JsonFormatters.ValidationJF) + ), + MongoClient.DEFAULT_CODEC_REGISTRY + ) + ) - override val mongoCollection: JSONCollection = collection - - override def indexes = Seq( - createCompoundIndex( - indexFieldMappings = Seq( - "clientId" -> IndexType.Ascending, - "apiContext" -> IndexType.Ascending, - "apiVersion" -> IndexType.Ascending - ), - indexName = Some("clientId-apiContext-apiVersion_Index"), - isUnique = true - ), - createSingleFieldAscendingIndex( - indexFieldKey = "clientId", - indexName = Some("clientIdIndex"), - isUnique = false - ), - createSingleFieldAscendingIndex( - indexFieldKey = "fieldsId", - indexName = Some("fieldsIdIndex"), - isUnique = true - ) - ) override def saveAtomic(subscription: SubscriptionFields): Future[(SubscriptionFields, IsInsert)] = { - saveAtomic( - selector = subscriptionFieldsSelector(subscription), - updateOperations = Json.obj( - "$setOnInsert" -> Json.obj("fieldsId" -> subscription.fieldsId), - "$set" -> Json.obj("fields" -> Json.toJson(subscription.fields)) - ) - ) + val query = and(equal("clientId", Codecs.toBson(subscription.clientId.value)), + equal("apiContext", Codecs.toBson(subscription.apiContext.value)), + equal("apiVersion", Codecs.toBson(subscription.apiVersion.value))) + + + collection.find(query).headOption flatMap { + case Some(_: SubscriptionFields) => + for { + updatedDefinitions <- collection.replaceOne( + filter = query, + replacement = subscription + ).toFuture().map(_ => subscription) + } yield (updatedDefinitions, false) + + case None => + for { + newSubscriptionFields <- collection.insertOne(subscription).toFuture().map(_ => subscription) + } yield (newSubscriptionFields, true) + } +// +// +// saveAtomic( +// selector = subscriptionFieldsSelector(subscription), +// updateOperations = Json.obj( +// "$setOnInsert" -> Json.obj("fieldsId" -> subscription.fieldsId), +// "$set" -> Json.obj("fields" -> Json.toJson(subscription.fields)) +// ) +// ) +// +// collection +// .findOneAndReplace(Filters.and(equal("apiContext", Codecs.toBson(definitions.apiContext.value)), +// equal("apiVersion", Codecs.toBson(definitions.apiVersion.value))), +// definitions +// ).map(_.asInstanceOf[ApiFieldDefinitions]).head } override def fetch(clientId: ClientId, apiContext: ApiContext, apiVersion: ApiVersion): Future[Option[SubscriptionFields]] = { - getOne(subscriptionFieldsSelector(clientId, apiContext, apiVersion)) + val query = and(equal("clientId", Codecs.toBson(clientId.value)), + equal("apiContext", Codecs.toBson(apiContext.value)), + equal("apiVersion", Codecs.toBson(apiVersion.value))) + + collection.find(query).headOption() } override def fetchByFieldsId(fieldsId: SubscriptionFieldsId): Future[Option[SubscriptionFields]] = { - getOne(fieldsIdSelector(fieldsId)) + val query = and(equal("fieldsId", Codecs.toBson(fieldsId.value))) + collection.find(query).headOption() } override def fetchByClientId(clientId: ClientId): Future[List[SubscriptionFields]] = { - getMany(clientIdSelector(clientId)) + val query = equal("clientId", Codecs.toBson(clientId.value)) + collection.find(query).toFuture().map(_.toList) } override def fetchAll: Future[List[SubscriptionFields]] = { - getMany(Json.obj()) + collection.find().toFuture().map(_.toList) } override def delete(clientId: ClientId, apiContext: ApiContext, apiVersion: ApiVersion): Future[Boolean] = { - deleteOne(subscriptionFieldsSelector(clientId, apiContext, apiVersion)) + val query = and(equal("clientId", Codecs.toBson(clientId.value)), + equal("apiContext", Codecs.toBson(apiContext.value)), + equal("apiVersion", Codecs.toBson(apiVersion.value))) + collection.deleteOne(query) + .toFuture() + .map(_.wasAcknowledged()) } override def delete(clientId: ClientId): Future[Boolean] = { - deleteMany(clientIdSelector(clientId)) + val query = equal("clientId", Codecs.toBson(clientId.value)) + collection.deleteMany(query) + .toFuture() + .map(_.wasAcknowledged()) } - private def clientIdSelector(clientId: ClientId) = Json.obj("clientId" -> clientId.value) - - private def fieldsIdSelector(fieldsId: SubscriptionFieldsId) = Json.obj("fieldsId" -> fieldsId.value) - - private def subscriptionFieldsSelector(clientId: ClientId, apiContext: ApiContext, apiVersion: ApiVersion): JsObject = { - Json.obj( - "clientId" -> clientId.value, - "apiContext" -> apiContext.value, - "apiVersion" -> apiVersion.value - ) - } - - private def subscriptionFieldsSelector(subscription: SubscriptionFields): JsObject = subscriptionFieldsSelector( - subscription.clientId, subscription.apiContext, subscription.apiVersion - ) - } diff --git a/conf/application.conf b/conf/application.conf index e0a37fe..75b83cc 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -41,7 +41,7 @@ play.http.errorHandler = "uk.gov.hmrc.play.bootstrap.backend.http.JsonErrorHandl # Play Modules # ~~~~ # Additional play modules can be added here -play.modules.enabled += "play.modules.reactivemongo.ReactiveMongoHmrcModule" +play.modules.enabled += "uk.gov.hmrc.mongo.play.PlayMongoModule" play.modules.enabled += "uk.gov.hmrc.play.bootstrap.HttpClientModule" play.modules.enabled += "uk.gov.hmrc.play.http.metrics.Module" diff --git a/project/AppDependencies.scala b/project/AppDependencies.scala index 20539c3..3f9735d 100644 --- a/project/AppDependencies.scala +++ b/project/AppDependencies.scala @@ -9,18 +9,29 @@ object AppDependencies { private lazy val dependencies = Seq( "uk.gov.hmrc" %% "bootstrap-backend-play-28" % bootstrapVersion, - "uk.gov.hmrc" %% "simple-reactivemongo" % "8.1.0-play-28", + "uk.gov.hmrc.mongo" %% "hmrc-mongo-play-28" % "0.68.0", "org.julienrf" %% "play-json-derived-codecs" % "6.0.0", "com.typesafe.play" %% "play-json" % "2.9.2", "uk.gov.hmrc" %% "http-metrics" % "2.5.0-play-28", "org.typelevel" %% "cats-core" % "2.6.1", "eu.timepit" %% "refined" % "0.9.13", "be.venneborg" %% "play28-refined" % "0.6.0", - "commons-validator" % "commons-validator" % "1.6" + "commons-validator" % "commons-validator" % "1.6", + "com.beachape" %% "enumeratum-play-json" % "1.6.0" ) private lazy val testDependencies = Seq( - "uk.gov.hmrc" %% "reactivemongo-test" % "5.1.0-play-28", +// "uk.gov.hmrc" %% "bootstrap-test-play-28" % bootstrapVersion, +// "uk.gov.hmrc.mongo" %% "hmrc-mongo-test-play-28" % mongoVersion, +// "org.mockito" %% "mockito-scala-scalatest" % "1.16.46", +// "org.jsoup" % "jsoup" % "1.13.1", +// "org.scalaj" %% "scalaj-http" % "2.4.2", +// "com.github.tomakehurst" % "wiremock-jre8-standalone" % "2.31.0", +// "org.scalacheck" %% "scalacheck" % "1.15.4", +// "org.scalatestplus" %% "scalacheck-1-15" % "3.2.10.0" + + "uk.gov.hmrc" %% "bootstrap-test-play-28" % bootstrapVersion, + "uk.gov.hmrc.mongo" %% "hmrc-mongo-test-play-28" % "0.68.0", "org.scalatestplus.play" %% "scalatestplus-play" % "3.1.3", "org.mockito" %% "mockito-scala-scalatest" % "1.16.42", "org.pegdown" % "pegdown" % "1.6.0", diff --git a/test/uk/gov/hmrc/apisubscriptionfields/repository/ApiFieldDefinitionsRepositorySpec.scala b/test/uk/gov/hmrc/apisubscriptionfields/repository/ApiFieldDefinitionsRepositorySpec.scala index 92b4a68..306da96 100644 --- a/test/uk/gov/hmrc/apisubscriptionfields/repository/ApiFieldDefinitionsRepositorySpec.scala +++ b/test/uk/gov/hmrc/apisubscriptionfields/repository/ApiFieldDefinitionsRepositorySpec.scala @@ -16,58 +16,53 @@ package uk.gov.hmrc.apisubscriptionfields.repository -import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} -import reactivemongo.api.DB -import reactivemongo.bson.BSONDocument -import uk.gov.hmrc.apisubscriptionfields.model.{ApiContext, ApiFieldDefinitions, JsonFormatters} -import uk.gov.hmrc.apisubscriptionfields.FieldDefinitionTestData -import uk.gov.hmrc.mongo.MongoSpecSupport -import uk.gov.hmrc.apisubscriptionfields.AsyncHmrcSpec +import org.mongodb.scala.model.Filters +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpec +import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach, OptionValues} +import org.scalatestplus.play.guice.GuiceOneAppPerSuite +import play.api.test.{DefaultAwaitTimeout, FutureAwaits} +import uk.gov.hmrc.apisubscriptionfields.model.{ApiContext, ApiFieldDefinitions, ApiVersion, JsonFormatters} +import uk.gov.hmrc.apisubscriptionfields.SubscriptionFieldsTestData.{FakeContext, FakeVersion, NelOfFieldDefinitions, uniqueApiContext} +import uk.gov.hmrc.mongo.play.json.Codecs + import scala.concurrent.ExecutionContext.Implicits.global -class ApiFieldDefinitionsRepositorySpec extends AsyncHmrcSpec - with BeforeAndAfterAll - with BeforeAndAfterEach - with MongoSpecSupport - with JsonFormatters - with FieldDefinitionTestData - { self => +class ApiFieldDefinitionsRepositorySpec extends AnyWordSpec + with GuiceOneAppPerSuite + with Matchers + with OptionValues + with DefaultAwaitTimeout + with FutureAwaits + with BeforeAndAfterEach { - private val mongoDbProvider = new MongoDbProvider { - override val mongo: () => DB = self.mongo - } + private val repository = app.injector.instanceOf[ApiFieldDefinitionsMongoRepository] - private val repository = new ApiFieldDefinitionsMongoRepository(mongoDbProvider) +// private val repository = new ApiFieldDefinitionsMongoRepository(mongoDbProvider) - override def beforeEach() { + override protected def beforeEach() { super.beforeEach() - await(repository.drop) - } - - override def afterAll() { - super.afterAll() - await(repository.drop) + await(repository.collection.drop.toFuture()) } - private def collectionSize: Int = { - await(repository.collection.count()) + def collectionSize: Long = { + await(repository.collection.countDocuments().toFuture()) } - private def createApiFieldDefinitions = ApiFieldDefinitions(FakeContext, FakeVersion, NelOfFieldDefinitions) + def createApiFieldDefinitions(apiContext: ApiContext = FakeContext) = ApiFieldDefinitions(apiContext = FakeContext, FakeVersion, NelOfFieldDefinitions) - private trait Setup { - val definitions: ApiFieldDefinitions = createApiFieldDefinitions + trait Setup { + val definitions: ApiFieldDefinitions = createApiFieldDefinitions() } "save" should { - import reactivemongo.play.json._ "insert the record in the collection" in new Setup { collectionSize shouldBe 0 await(repository.save(definitions)) shouldBe ((definitions, true)) collectionSize shouldBe 1 - await(repository.collection.find(selector(definitions)).one[ApiFieldDefinitions]) shouldBe Some(definitions) + await(repository.collection.find(selector(definitions)).toFuture()) shouldBe Some(definitions) } "update the record in the collection" in new Setup { @@ -79,7 +74,7 @@ class ApiFieldDefinitionsRepositorySpec extends AsyncHmrcSpec val edited = definitions.copy(fieldDefinitions = NelOfFieldDefinitions) await(repository.save(edited)) shouldBe ((edited, false)) collectionSize shouldBe 1 - await(repository.collection.find(selector(edited)).one[ApiFieldDefinitions]) shouldBe Some(edited) + await(repository.collection.find(selector(edited)).toFuture()) shouldBe Some(edited) } } @@ -120,7 +115,7 @@ class ApiFieldDefinitionsRepositorySpec extends AsyncHmrcSpec "delete" should { "remove the record with a specific fields definition" in { - val definitions = createApiFieldDefinitions + val definitions = createApiFieldDefinitions() await(repository.save(definitions)) collectionSize shouldBe 1 @@ -130,7 +125,7 @@ class ApiFieldDefinitionsRepositorySpec extends AsyncHmrcSpec } "not alter the collection for unknown fields definition" in { - await(repository.save(createApiFieldDefinitions)) + await(repository.save(createApiFieldDefinitions())) collectionSize shouldBe 1 await(repository.delete(ApiContext("DOES_NOT_EXIST"), FakeVersion)) shouldBe false @@ -150,6 +145,7 @@ class ApiFieldDefinitionsRepositorySpec extends AsyncHmrcSpec } private def selector(fd: ApiFieldDefinitions) = { - BSONDocument("apiContext" -> fd.apiContext.value, "apiVersion" -> fd.apiVersion.value) + Filters.and(Filters.equal("apiContext", Codecs.toBson(fd.apiContext.value)), + Filters.equal("apiVersion", Codecs.toBson(fd.apiVersion.value))) } } diff --git a/test/uk/gov/hmrc/apisubscriptionfields/repository/MongoErrorHandlerSpec.scala b/test/uk/gov/hmrc/apisubscriptionfields/repository/MongoErrorHandlerSpec.scala index 49d966c..e61e250 100644 --- a/test/uk/gov/hmrc/apisubscriptionfields/repository/MongoErrorHandlerSpec.scala +++ b/test/uk/gov/hmrc/apisubscriptionfields/repository/MongoErrorHandlerSpec.scala @@ -14,95 +14,111 @@ * limitations under the License. */ -package uk.gov.hmrc.apisubscriptionfields.repository - -import reactivemongo.api.commands._ -import reactivemongo.bson.BSONInteger -import uk.gov.hmrc.apisubscriptionfields.AsyncHmrcSpec - -class MongoErrorHandlerSpec extends AsyncHmrcSpec { - - private val mongoErrorHandler = new MongoErrorHandler {} - private val BsonValue = BSONInteger(1) - private val SomeWriteConcernError = Some(WriteConcernError(1, "ERROR")) - - "handleDeleteError" should { - "return true if there are no database errors and at least one record deleted" in { - val successfulWriteResult = writeResult(alteredRecords = 1) - - mongoErrorHandler.handleDeleteError(successfulWriteResult, "ERROR_MSG") shouldBe true - } - - "return false if there are no database errors and no record deleted" in { - val noDeletedRecordsWriteResult = writeResult(alteredRecords = 0) - - mongoErrorHandler.handleDeleteError(noDeletedRecordsWriteResult, "ERROR_MSG") shouldBe false - } - - "throw a RuntimeException if there is a database error" in { - val writeConcernError = Some(WriteConcernError(1, "ERROR")) - val errorWriteResult = writeResult(alteredRecords = 0, writeConcernError = writeConcernError) - - val caught = intercept[RuntimeException](mongoErrorHandler.handleDeleteError(errorWriteResult, "ERROR_MSG")) - - caught.getMessage shouldBe "ERROR_MSG. WriteConcernError(1,ERROR)" - } - } - - "handleSaveError" should { - "return true if there are no database errors and at least one record inserted" in { - val Inserted = Seq(Upserted(0, BsonValue)) - val successfulInsertWriteResult = updateWriteResult(alteredRecords = 1, upserted = Inserted) - - mongoErrorHandler.handleSaveError(successfulInsertWriteResult, "ERROR_MSG") shouldBe true - } - - "return false if there are no database errors and no record deleted and at least one record updated" in { - val successfulUpdateWriteResult = updateWriteResult(alteredRecords = 1) - - mongoErrorHandler.handleSaveError(successfulUpdateWriteResult, "ERROR_MSG") shouldBe false - } - - "throw a RuntimeException if there is a database error" in { - val errorUpdateWriteResult = updateWriteResult(alteredRecords = 0, writeConcernError = SomeWriteConcernError) - - val caught = intercept[RuntimeException](mongoErrorHandler.handleSaveError(errorUpdateWriteResult, "ERROR_MSG")) - - caught.getMessage shouldBe "ERROR_MSG. WriteConcernError(1,ERROR)" - } - - "throw a RuntimeException if there are no records altered" in { - val errorUpdateWriteResult = updateWriteResult(alteredRecords = 0) - - val caught = intercept[RuntimeException](mongoErrorHandler.handleSaveError(errorUpdateWriteResult, "ERROR_MSG")) - - caught.getMessage shouldBe "ERROR_MSG" - } - - } - - private def writeResult(alteredRecords: Int, writeErrors: Seq[WriteError] = Nil, - writeConcernError: Option[WriteConcernError] = None) = { - DefaultWriteResult( - ok = true, - n = alteredRecords, - writeErrors = writeErrors, - writeConcernError = writeConcernError, - code = None, - errmsg = None) - } - - private def updateWriteResult(alteredRecords: Int, upserted: Seq[Upserted] = Nil, writeErrors: Seq[WriteError] = Nil, - writeConcernError: Option[WriteConcernError] = None) = { - UpdateWriteResult( - ok = true, - n = alteredRecords, - nModified = 0, - upserted = upserted, - writeErrors = writeErrors, - writeConcernError = writeConcernError, - code = None, - errmsg = None) - } - -} +///* +// * Copyright 2022 HM Revenue & Customs +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ +// +//package uk.gov.hmrc.apisubscriptionfields.repository +// +//import reactivemongo.api.commands._ +//import reactivemongo.bson.BSONInteger +//import uk.gov.hmrc.apisubscriptionfields.AsyncHmrcSpec +// +//class MongoErrorHandlerSpec extends AsyncHmrcSpec { +// +// private val mongoErrorHandler = new MongoErrorHandler {} +// private val BsonValue = BSONInteger(1) +// private val SomeWriteConcernError = Some(WriteConcernError(1, "ERROR")) +// +// "handleDeleteError" should { +// "return true if there are no database errors and at least one record deleted" in { +// val successfulWriteResult = writeResult(alteredRecords = 1) +// +// mongoErrorHandler.handleDeleteError(successfulWriteResult, "ERROR_MSG") shouldBe true +// } +// +// "return false if there are no database errors and no record deleted" in { +// val noDeletedRecordsWriteResult = writeResult(alteredRecords = 0) +// +// mongoErrorHandler.handleDeleteError(noDeletedRecordsWriteResult, "ERROR_MSG") shouldBe false +// } +// +// "throw a RuntimeException if there is a database error" in { +// val writeConcernError = Some(WriteConcernError(1, "ERROR")) +// val errorWriteResult = writeResult(alteredRecords = 0, writeConcernError = writeConcernError) +// +// val caught = intercept[RuntimeException](mongoErrorHandler.handleDeleteError(errorWriteResult, "ERROR_MSG")) +// +// caught.getMessage shouldBe "ERROR_MSG. WriteConcernError(1,ERROR)" +// } +// } +// +// "handleSaveError" should { +// "return true if there are no database errors and at least one record inserted" in { +// val Inserted = Seq(Upserted(0, BsonValue)) +// val successfulInsertWriteResult = updateWriteResult(alteredRecords = 1, upserted = Inserted) +// +// mongoErrorHandler.handleSaveError(successfulInsertWriteResult, "ERROR_MSG") shouldBe true +// } +// +// "return false if there are no database errors and no record deleted and at least one record updated" in { +// val successfulUpdateWriteResult = updateWriteResult(alteredRecords = 1) +// +// mongoErrorHandler.handleSaveError(successfulUpdateWriteResult, "ERROR_MSG") shouldBe false +// } +// +// "throw a RuntimeException if there is a database error" in { +// val errorUpdateWriteResult = updateWriteResult(alteredRecords = 0, writeConcernError = SomeWriteConcernError) +// +// val caught = intercept[RuntimeException](mongoErrorHandler.handleSaveError(errorUpdateWriteResult, "ERROR_MSG")) +// +// caught.getMessage shouldBe "ERROR_MSG. WriteConcernError(1,ERROR)" +// } +// +// "throw a RuntimeException if there are no records altered" in { +// val errorUpdateWriteResult = updateWriteResult(alteredRecords = 0) +// +// val caught = intercept[RuntimeException](mongoErrorHandler.handleSaveError(errorUpdateWriteResult, "ERROR_MSG")) +// +// caught.getMessage shouldBe "ERROR_MSG" +// } +// +// } +// +// private def writeResult(alteredRecords: Int, writeErrors: Seq[WriteError] = Nil, +// writeConcernError: Option[WriteConcernError] = None) = { +// DefaultWriteResult( +// ok = true, +// n = alteredRecords, +// writeErrors = writeErrors, +// writeConcernError = writeConcernError, +// code = None, +// errmsg = None) +// } +// +// private def updateWriteResult(alteredRecords: Int, upserted: Seq[Upserted] = Nil, writeErrors: Seq[WriteError] = Nil, +// writeConcernError: Option[WriteConcernError] = None) = { +// UpdateWriteResult( +// ok = true, +// n = alteredRecords, +// nModified = 0, +// upserted = upserted, +// writeErrors = writeErrors, +// writeConcernError = writeConcernError, +// code = None, +// errmsg = None) +// } +// +//} diff --git a/test/uk/gov/hmrc/apisubscriptionfields/repository/SubscriptionFieldsRepositorySpec.scala b/test/uk/gov/hmrc/apisubscriptionfields/repository/SubscriptionFieldsRepositorySpec.scala index f730484..56eeecd 100644 --- a/test/uk/gov/hmrc/apisubscriptionfields/repository/SubscriptionFieldsRepositorySpec.scala +++ b/test/uk/gov/hmrc/apisubscriptionfields/repository/SubscriptionFieldsRepositorySpec.scala @@ -17,48 +17,41 @@ package uk.gov.hmrc.apisubscriptionfields.repository import java.util.UUID - -import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} -import reactivemongo.api.DB -import reactivemongo.bson._ +import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach, OptionValues} import uk.gov.hmrc.apisubscriptionfields.model._ import Types._ +import org.mongodb.scala.model.Updates.set +import org.mongodb.scala.model.{Filters, FindOneAndUpdateOptions, ReturnDocument} +import org.scalatest.matchers.should.Matchers +import org.scalatestplus.play.guice.GuiceOneAppPerSuite +import play.api.test.{DefaultAwaitTimeout, FutureAwaits} import uk.gov.hmrc.apisubscriptionfields.SubscriptionFieldsTestData -import uk.gov.hmrc.mongo.MongoSpecSupport import uk.gov.hmrc.apisubscriptionfields.AsyncHmrcSpec +import uk.gov.hmrc.apisubscriptionfields.SubscriptionFieldsTestData.{FakeClientId, FakeClientId2, FakeContext, FakeContext2, FakeVersion, createSubscriptionFieldsWithApiContext, fakeRawContext2, fieldN, uniqueClientId} +import uk.gov.hmrc.mongo.play.json.Codecs import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future class SubscriptionFieldsRepositorySpec extends AsyncHmrcSpec - with BeforeAndAfterAll - with BeforeAndAfterEach - with MongoSpecSupport - with JsonFormatters - with SubscriptionFieldsTestData { self => - - private val mongoDbProvider = new MongoDbProvider { - override val mongo: () => DB = self.mongo - } - - private val repository = new SubscriptionFieldsMongoRepository(mongoDbProvider) { + with GuiceOneAppPerSuite + with Matchers + with OptionValues + with DefaultAwaitTimeout + with FutureAwaits + with BeforeAndAfterEach { - import play.api.libs.json._ +// private val mongoDbProvider = new MongoDbProvider { +// override val mongo: () => DB = self.mongo +// } + private val repository = app.injector.instanceOf[SubscriptionFieldsMongoRepository] - def saveByFieldsId(subscription: SubscriptionFields): Future[(SubscriptionFields, IsInsert)] = { - save(subscription, Json.obj("fieldsId" -> subscription.fieldsId.value)) - } - } + import play.api.libs.json._ override def beforeEach() { super.beforeEach() - await(repository.drop) - } - - override def afterAll() { - super.afterAll() - await(repository.drop) + await(repository.collection.drop.toFuture()) } private def createApiSubscriptionFields(clientId: ClientId = FakeClientId): SubscriptionFields = { @@ -66,25 +59,33 @@ class SubscriptionFieldsRepositorySpec extends AsyncHmrcSpec SubscriptionFields(clientId, FakeContext, FakeVersion, SubscriptionFieldsId(UUID.randomUUID()), fields) } - private def collectionSize: Int = { - await(repository.collection.count()) + private def collectionSize: Long = { + await(repository.collection.countDocuments().toFuture()) } private def selector(s: SubscriptionFields) = { - BSONDocument("clientId" -> s.clientId.value, "apiContext" -> s.apiContext.value, "apiVersion" -> s.apiVersion.value) + Filters.and(Filters.equal("apiContext", Codecs.toBson(s.apiContext.value)), + Filters.equal("apiVersion", Codecs.toBson(s.apiVersion.value)), + Filters.equal("clientId", Codecs.toBson(s.apiVersion.value))) + } + + def saveByFieldsId(subscription: SubscriptionFields): Future[SubscriptionFields] = { + val query = Filters.equal("fieldsId", Codecs.toBson(subscription.fieldsId.value)) + repository.collection.findOneAndUpdate(filter = query, + update = set("fieldsId", Codecs.toBson(subscription.fieldsId.value)), + options = FindOneAndUpdateOptions().upsert(false).returnDocument(ReturnDocument.AFTER) + ).toFuture() } "saveAtomic" should { val apiSubscriptionFields = createApiSubscriptionFields() - import reactivemongo.play.json._ - "insert the record in the collection" in { collectionSize shouldBe 0 await(repository.saveAtomic(apiSubscriptionFields)) shouldBe ((apiSubscriptionFields, true)) collectionSize shouldBe 1 - await(repository.collection.find(selector(apiSubscriptionFields)).one[SubscriptionFields]) shouldBe Some(apiSubscriptionFields) + await(repository.collection.find(selector(apiSubscriptionFields)).toFuture()) shouldBe Some(apiSubscriptionFields) } "update the record in the collection" in { @@ -96,7 +97,7 @@ class SubscriptionFieldsRepositorySpec extends AsyncHmrcSpec val edited = apiSubscriptionFields.copy(fields = Map(fieldN(4) -> "value_4")) await(repository.saveAtomic(edited)) shouldBe ((edited, false)) collectionSize shouldBe 1 - await(repository.collection.find(selector(edited)).one[SubscriptionFields]) shouldBe Some(edited) + await(repository.collection.find(selector(edited)).toFuture()) shouldBe Some(edited) } } @@ -239,7 +240,7 @@ class SubscriptionFieldsRepositorySpec extends AsyncHmrcSpec await(repository.saveAtomic(apiSubscription)) collectionSize shouldBe 1 - await(repository.saveByFieldsId(apiSubscription.copy(apiVersion = ApiVersion("2.2")))) + await(saveByFieldsId(apiSubscription.copy(apiVersion = ApiVersion("2.2")))) collectionSize shouldBe 1 }