diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..33d6deeb --- /dev/null +++ b/.travis.yml @@ -0,0 +1,16 @@ +language: scala + +jdk: + - oraclejdk8 + +scala: + - 2.11.2 + - 2.10.4 + +addons: + postgresql: "9.3" + +before_script: + - psql -c "CREATE DATABASE octoparts_test WITH ENCODING 'UTF8';" -U postgres + - psql -c "CREATE USER octoparts_app WITH PASSWORD '';" -U postgres + - psql -c "GRANT ALL PRIVILEGES ON DATABASE octoparts_test to octoparts_app;" -U postgres \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 00000000..dba939ba --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,13 @@ +Copyright 2014 M3, Inc. + +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. diff --git a/README.md b/README.md index 0240c25a..66618875 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # Octoparts -[See documentation](http://m3dev.github.io/octoparts-site/) +[See documentation](http://m3dev.github.io/octoparts/) diff --git a/app/com/m3/octoparts/model/config/CacheGroup.scala b/app/com/m3/octoparts/model/config/CacheGroup.scala index f37ab483..e62d2bc7 100644 --- a/app/com/m3/octoparts/model/config/CacheGroup.scala +++ b/app/com/m3/octoparts/model/config/CacheGroup.scala @@ -7,15 +7,11 @@ import org.joda.time.DateTime * Defines a group of objects that need to have their cache invalidated as a group */ case class CacheGroup( - id: Option[Long] = None, // None means that the record is new - name: String, - owner: String, - description: String = "", - httpPartConfigs: Seq[HttpPartConfig] = Seq.empty, - partParams: Seq[PartParam] = Seq.empty, - createdAt: DateTime, - updatedAt: DateTime) extends ConfigModel[CacheGroup] { - - override def mapper = CacheGroupRepository - -} + id: Option[Long] = None, // None means that the record is new + name: String, + owner: String, + description: String = "", + httpPartConfigs: Seq[HttpPartConfig] = Seq.empty, + partParams: Seq[PartParam] = Seq.empty, + createdAt: DateTime, + updatedAt: DateTime) extends ConfigModel[CacheGroup] \ No newline at end of file diff --git a/app/com/m3/octoparts/model/config/ConfigModel.scala b/app/com/m3/octoparts/model/config/ConfigModel.scala index ae3cb449..40919d40 100644 --- a/app/com/m3/octoparts/model/config/ConfigModel.scala +++ b/app/com/m3/octoparts/model/config/ConfigModel.scala @@ -1,7 +1,5 @@ package com.m3.octoparts.model.config -import com.m3.octoparts.repository.config.ConfigMapper - /** * The base trait for our models. * @@ -16,13 +14,4 @@ trait ConfigModel[A <: ConfigModel[A]] { */ def id: Option[Long] - /** - * Used for easy access to a mapper of an object. - * - * For our purposes, it's much easier and faster to implement this in code within - * implementing classes than to use tricks like implicits (requires more boiler - * plate) and reflection (runtime cost) - */ - def mapper: ConfigMapper[A] - } diff --git a/app/com/m3/octoparts/model/config/HttpPartConfig.scala b/app/com/m3/octoparts/model/config/HttpPartConfig.scala index 6de41051..6769cb04 100644 --- a/app/com/m3/octoparts/model/config/HttpPartConfig.scala +++ b/app/com/m3/octoparts/model/config/HttpPartConfig.scala @@ -33,8 +33,6 @@ case class HttpPartConfig(id: Option[Long] = None, // None means that the record createdAt: DateTime, updatedAt: DateTime) extends ConfigModel[HttpPartConfig] { - override def mapper = HttpPartConfigRepository - /** * Method to use when we are sure we have a HystrixConfig inside the * hystrixConfig field. diff --git a/app/com/m3/octoparts/model/config/HystrixConfig.scala b/app/com/m3/octoparts/model/config/HystrixConfig.scala index ccd37155..14062ae0 100644 --- a/app/com/m3/octoparts/model/config/HystrixConfig.scala +++ b/app/com/m3/octoparts/model/config/HystrixConfig.scala @@ -22,8 +22,6 @@ case class HystrixConfig( createdAt: DateTime, updatedAt: DateTime) extends ConfigModel[HystrixConfig] { - override def mapper = HystrixConfigRepository - /** * Method to use when we are sure we have a ThreadPoolConfig inside the * threadPoolConfig field. Throws an error if it's empty diff --git a/app/com/m3/octoparts/model/config/PartParam.scala b/app/com/m3/octoparts/model/config/PartParam.scala index 253b1227..962dad43 100644 --- a/app/com/m3/octoparts/model/config/PartParam.scala +++ b/app/com/m3/octoparts/model/config/PartParam.scala @@ -28,8 +28,6 @@ case class PartParam( createdAt: DateTime, updatedAt: DateTime) extends ConfigModel[PartParam] { - override def mapper = PartParamRepository - /** * This is the key used to look for a value inside the PartRequest */ diff --git a/app/com/m3/octoparts/model/config/ThreadPoolConfig.scala b/app/com/m3/octoparts/model/config/ThreadPoolConfig.scala index f38b75fb..c0556fa2 100644 --- a/app/com/m3/octoparts/model/config/ThreadPoolConfig.scala +++ b/app/com/m3/octoparts/model/config/ThreadPoolConfig.scala @@ -14,8 +14,6 @@ case class ThreadPoolConfig( createdAt: DateTime, updatedAt: DateTime) extends ConfigModel[ThreadPoolConfig] { - override def mapper = ThreadPoolConfigRepository - // this setting is not yet available for users def queueSize: Int = ThreadPoolConfig.defaultQueueSize diff --git a/app/com/m3/octoparts/repository/DBConfigsRepository.scala b/app/com/m3/octoparts/repository/DBConfigsRepository.scala index 4db79388..9baf5278 100644 --- a/app/com/m3/octoparts/repository/DBConfigsRepository.scala +++ b/app/com/m3/octoparts/repository/DBConfigsRepository.scala @@ -40,9 +40,9 @@ object DBConfigsRepository extends MutableConfigsRepository with Logging { // A separate ExecutionContext to avoid starving the global one with blocking DB operations implicit val dbFetchExecutionContext = Akka.system.dispatchers.lookup("contexts.db") - def save[A <: ConfigModel[A]](obj: A): Future[Long] = { + def save[A <: ConfigModel[A]: ConfigMapper](obj: A): Future[Long] = { DB futureLocalTx { implicit session => - saveWithSession(obj.mapper, obj) + saveWithSession(implicitly[ConfigMapper[A]], obj) } } diff --git a/app/com/m3/octoparts/repository/MutableCachingRepository.scala b/app/com/m3/octoparts/repository/MutableCachingRepository.scala index a7f47531..80a23739 100644 --- a/app/com/m3/octoparts/repository/MutableCachingRepository.scala +++ b/app/com/m3/octoparts/repository/MutableCachingRepository.scala @@ -4,6 +4,7 @@ import com.m3.octoparts.cache.client.CacheAccessor import com.m3.octoparts.cache.key.HttpPartConfigCacheKey import com.m3.octoparts.http.HttpClientPool import com.m3.octoparts.model.config.ConfigModel +import com.m3.octoparts.repository.config.ConfigMapper import scala.concurrent.{ ExecutionContext, Future } @@ -18,7 +19,7 @@ class MutableCachingRepository( extends CachingRepository with MutableConfigsRepository { - def save[A <: ConfigModel[A]](obj: A): Future[Long] = reloadCacheAfter(delegate.save(obj)) + def save[A <: ConfigModel[A]: ConfigMapper](obj: A): Future[Long] = reloadCacheAfter(delegate.save(obj)) def deleteAllConfigs(): Future[Int] = reloadCacheAfter { for { diff --git a/app/com/m3/octoparts/repository/MutableConfigsRepository.scala b/app/com/m3/octoparts/repository/MutableConfigsRepository.scala index 6a3bfccf..bff29856 100644 --- a/app/com/m3/octoparts/repository/MutableConfigsRepository.scala +++ b/app/com/m3/octoparts/repository/MutableConfigsRepository.scala @@ -1,6 +1,7 @@ package com.m3.octoparts.repository import com.m3.octoparts.model.config.ConfigModel +import com.m3.octoparts.repository.config.ConfigMapper import scala.concurrent.Future @@ -43,6 +44,6 @@ trait MutableConfigsRepository extends ConfigsRepository { * * @return Long, the id of the model that was saved */ - def save[A <: ConfigModel[A]](obj: A): Future[Long] + def save[A <: ConfigModel[A]: ConfigMapper](obj: A): Future[Long] } diff --git a/app/com/m3/octoparts/repository/config/ConfigMapper.scala b/app/com/m3/octoparts/repository/config/ConfigMapper.scala index fec9f86d..79f449ef 100644 --- a/app/com/m3/octoparts/repository/config/ConfigMapper.scala +++ b/app/com/m3/octoparts/repository/config/ConfigMapper.scala @@ -1,6 +1,6 @@ package com.m3.octoparts.repository.config -import com.m3.octoparts.model.config.ConfigModel +import com.m3.octoparts.model.config._ import scalikejdbc.DBSession import skinny.logging.Logging import skinny.orm.SkinnyCRUDMapper @@ -63,4 +63,16 @@ trait ConfigMapper[A <: ConfigModel[A]] extends SkinnyCRUDMapper[A] with Logging } } +} + +/** + * This companion object exists to hold the type class instances of the [[ConfigMapper]] + * type class. + */ +object ConfigMapper { + implicit val CacheGroupMapper: ConfigMapper[CacheGroup] = CacheGroupRepository + implicit val HttpPartConfigMapper: ConfigMapper[HttpPartConfig] = HttpPartConfigRepository + implicit val HystrixConfigMapper: ConfigMapper[HystrixConfig] = HystrixConfigRepository + implicit val PartParamMapper: ConfigMapper[PartParam] = PartParamRepository + implicit val ThreadPoolConfigMapper: ConfigMapper[ThreadPoolConfig] = ThreadPoolConfigRepository } \ No newline at end of file diff --git a/plugins/m3-openid/src/main/scala/com/m3/openid/M3OpenIDAuthPlugin.scala b/plugins/m3-openid/src/main/scala/com/m3/openid/M3OpenIDAuthPlugin.scala deleted file mode 100644 index 013bf633..00000000 --- a/plugins/m3-openid/src/main/scala/com/m3/openid/M3OpenIDAuthPlugin.scala +++ /dev/null @@ -1,110 +0,0 @@ -package com.m3.openid - -import java.io.IOException - -import com.m3.octoparts.auth.{ Principal, AuthenticatedRequest, OctopartsAuthPlugin } -import play.api.libs.openid.{ UserInfo, OpenID } -import play.api.mvc.{ Request, Results } -import play.api.{ Application, Logger } - -import scala.concurrent.{ ExecutionContext, Future } - -/** - * An implementation of [[com.m3.octoparts.auth.OctopartsAuthPlugin]] that performs - * authentication/authorization using the M3 internal OpenID provider. - * - * A user is judged to be authenticated if they have one of the accepted roles. - */ -class M3OpenIDAuthPlugin(application: Application) extends OctopartsAuthPlugin with Results { - - private val openidProviderUrl = application.configuration.underlying.getString("auth.openid.providerUrl") - - private val requiredAxAttributes = Seq( - "nickname" -> "http://m3.com/nickname", - "email" -> "http://m3.com/email", - "groups" -> "http://m3.com/groups" - ) - - private val acceptedRoles: Set[String] = application.configuration.getStringSeq("auth.acceptedRoles").getOrElse(Nil).toSet - - /** - * Add the line "auth.disabled = true" if you want to disable the plugin. - */ - override def enabled = !isDisabled - - private def isDisabled = application.configuration.getBoolean("auth.disabled").getOrElse(false) - - /** - * What action to take when a request is not authenticated, e.g. redirect to an authentication server. - * - * @param request The unauthenticated request - * @param callbackUrl An absolute URL that you can use for a post-authentication callback - */ - def onNotAuthenticated(request: Request[_], callbackUrl: String)(implicit exec: ExecutionContext) = { - val redirectUrl = OpenID.redirectURL( - openID = openidProviderUrl, - callbackURL = callbackUrl, - axRequired = requiredAxAttributes.toSeq) - redirectUrl.map(Redirect(_)) - } - - /** - * Whether the given principal is authorized to use the Octoparts admin UI. - * - * @param authenticatedRequest an authenticated request with a [[com.m3.octoparts.auth.Principal]] - * @return your decision - */ - def isAuthorized(authenticatedRequest: AuthenticatedRequest[_])(implicit exec: ExecutionContext) = { - val validRoles = authenticatedRequest.principal.roles.toSet.intersect(acceptedRoles) - if (validRoles.isEmpty) { - // User does not have a valid role - Future.successful(false) - } else { - Logger.debug(s"Accepting user [${authenticatedRequest.principal.nickname}] because they have these roles: $validRoles") - Future.successful(true) - } - } - - /** - * What action to take when a request is authenticated but the principal is not authorized. - * - * @param authenticatedRequest an authenticated request with a [[com.m3.octoparts.auth.Principal]] - */ - def onUnauthorized(authenticatedRequest: AuthenticatedRequest[_])(implicit exec: ExecutionContext) = { - Future.successful(Forbidden) - } - - /** - * Use a post-authentication callback from an external authentication provider to construct a [[com.m3.octoparts.auth.Principal]] - * - * @return a Principal constructed from the HTTP request - */ - def onAuthenticationCallback(request: Request[_])(implicit exec: ExecutionContext) = { - OpenID.verifiedId(request).map { info => - userInfo2Principal(info).fold { - // Failed to extract a principal object from the info returned by the OpenID provider. Missing attribute? - throw new IOException(s"OpenID authentication failed. Most likely your OpenID provider did not return all the attributes we need. Here's what we got: $info") - } { principal => - Logger.debug(s"OpenID auth completed for ${info.id}.") - principal - } - } - } - - private def userInfo2Principal(info: UserInfo): Option[Principal] = { - val principal = for { - nickname <- info.attributes.get("nickname") - email <- info.attributes.get("email") - roles <- info.attributes.get("groups") - } yield Principal(info.id, nickname, email, roles.split(", ?")) - - if (!principal.isDefined) { - Logger.warn("Failed to build a Principal from the info returned by the OpenID provider. " + - s"Attributes returned: ${info.attributes}. " + - "We require the following attributes: nickname, email, roles") - } - - principal - } - -} diff --git a/project/Build.scala b/project/Build.scala index fc876cfe..b9dcc54a 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -15,7 +15,7 @@ import scalariform.formatter.preferences._ object OctopartsBuild extends Build { - val octopartsVersion = "2.0-SNAPSHOT" + val octopartsVersion = "2.0" val httpPort = 9000 val theScalaVersion = "2.11.2" @@ -187,21 +187,13 @@ object OctopartsBuild extends Build { // ------------------------------------------------------- // Interface for authentication plugins // ------------------------------------------------------- - lazy val authPluginApi = Project(id = "auth-plugin-api", base = file("plugins/auth-plugin-api"), settings = commonSettings).settings( + lazy val authPluginApi = Project(id = "auth-plugin-api", base = file("plugins/auth-plugin-api"), settings = nonPlayAppSettings).settings( + name := "octoparts-auth-plugin-api", libraryDependencies ++= Seq( "com.typesafe.play" %% "play" % thePlayVersion % "provided" ) ) - // ------------------------------------------------------- - // M3 OpenID implementation of the authentication plugin - // ------------------------------------------------------- - lazy val m3openidPlugin = Project(id = "m3-openid-plugin", base = file("plugins/m3-openid"), settings = commonSettings).settings( - libraryDependencies ++= Seq( - ws - ) - ).dependsOn(authPluginApi) - // ------------------------------------------------------- // Model classes // ------------------------------------------------------- @@ -260,7 +252,7 @@ object OctopartsBuild extends Build { lazy val app = Project(id = "octoparts", base = file("."), settings = playAppSettings) .enablePlugins(PlayScala) .dependsOn(models, authPluginApi) - .aggregate(scalaWsClient, javaClient, models) + .aggregate(scalaWsClient, javaClient, models, authPluginApi) // Settings for publishing to Maven Central lazy val publishSettings = Seq( diff --git a/test/com/m3/octoparts/support/mocks/MockConfigRespository.scala b/test/com/m3/octoparts/support/mocks/MockConfigRespository.scala index cc712d97..7796e60c 100644 --- a/test/com/m3/octoparts/support/mocks/MockConfigRespository.scala +++ b/test/com/m3/octoparts/support/mocks/MockConfigRespository.scala @@ -1,5 +1,6 @@ package com.m3.octoparts.support.mocks +import com.m3.octoparts.repository.config.ConfigMapper import com.m3.octoparts.repository.{ MutableConfigsRepository, ConfigsRepository } import com.m3.octoparts.model.config._ import scala.concurrent.Future @@ -39,6 +40,6 @@ trait MockMutableRepository extends MockConfigRespository with MutableConfigsRep def deleteAllConfigs() = Future.successful(1) - def save[A <: ConfigModel[A]](obj: A): Future[Long] = Future.successful(123) + def save[A <: ConfigModel[A]: ConfigMapper](obj: A): Future[Long] = Future.successful(123) } \ No newline at end of file diff --git a/test/controllers/AdminControllerSpec.scala b/test/controllers/AdminControllerSpec.scala index c17e0a10..4d156a9b 100644 --- a/test/controllers/AdminControllerSpec.scala +++ b/test/controllers/AdminControllerSpec.scala @@ -2,6 +2,7 @@ package controllers import com.m3.octoparts.model.config._ import com.m3.octoparts.repository.MutableConfigsRepository +import com.m3.octoparts.repository.config._ import com.m3.octoparts.support.mocks.ConfigDataMocks import org.mockito.ArgumentCaptor import org.mockito.Matchers._ @@ -93,7 +94,7 @@ class AdminControllerSpec extends FunSpec it("should create a new part") { val repository = mock[MutableConfigsRepository] val adminController = new AdminController(repository = repository) - doReturn(Future.successful(124L)).when(repository).save(anyObject[HttpPartConfig]()) + doReturn(Future.successful(124L)).when(repository).save(anyObject[HttpPartConfig]())(anyObject[ConfigMapper[HttpPartConfig]]) doReturn(Future.successful(Seq.empty)).when(repository).findAllCacheGroupsByName(anyVararg[String]()) doReturn(Future.successful(Seq(mockThreadConfig))).when(repository).findAllThreadPoolConfigs() doReturn(Future.successful(Seq.empty)).when(repository).findAllCacheGroups() @@ -104,7 +105,7 @@ class AdminControllerSpec extends FunSpec redirectLocation(result).get should include(routes.AdminController.showPart("aNewName").url) val newCiCaptor = ArgumentCaptor.forClass(classOf[HttpPartConfig]) - verify(repository).save(newCiCaptor.capture()) + verify(repository).save(newCiCaptor.capture())(anyObject[ConfigMapper[HttpPartConfig]]) verify(repository).findAllCacheGroupsByName(Seq.empty: _*) newCiCaptor.getValue.uriToInterpolate should be("http://www.example2.com") } @@ -129,8 +130,7 @@ class AdminControllerSpec extends FunSpec describe("when all is good with the world") { it("should update the item and redirect to the part's detail page") { val (controller, repository, part) = setupController - doReturn(Future.successful(124L)).when(repository).save(anyObject[HttpPartConfig]()) - + doReturn(Future.successful(124L)).when(repository).save(anyObject[HttpPartConfig]())(anyObject[ConfigMapper[HttpPartConfig]]) val result = controller.updatePart(part.partId).apply(FakeRequest().withFormUrlEncodedBody(validPartEditFormParams: _*)) status(result) should be(302) redirectLocation(result).get should include(routes.AdminController.showPart("aNewName").url) @@ -151,7 +151,7 @@ class AdminControllerSpec extends FunSpec describe("when the save fails") { it("should show the form again, with the user's filled-in information") { val (controller, repository, part) = setupController - doReturn(Future.failed(new RuntimeException("oooh shiiieeet"))).when(repository).save(anyObject[HttpPartConfig]()) + doReturn(Future.failed(new RuntimeException("oooh shiiieeet"))).when(repository).save(anyObject[HttpPartConfig]())(anyObject[ConfigMapper[HttpPartConfig]]) val result = controller.updatePart(part.partId).apply(FakeRequest().withFormUrlEncodedBody(validPartEditFormParams: _*)) status(result) should be(200) @@ -204,7 +204,7 @@ class AdminControllerSpec extends FunSpec val adminController = new AdminController(repository = repository) doReturn(Future.successful(Seq(part))).when(repository).findAllConfigs() doReturn(Future.successful(Some(part))).when(repository).findConfigByPartId(part.partId) - doReturn(Future.successful(124L)).when(repository).save(anyObject[HttpPartConfig]()) + doReturn(Future.successful(124L)).when(repository).save(anyObject[HttpPartConfig]())(anyObject[ConfigMapper[HttpPartConfig]]) val result = adminController.copyPart(part.partId).apply(FakeRequest()) status(result) should be(302) @@ -239,7 +239,7 @@ class AdminControllerSpec extends FunSpec doReturn(Future.successful(Some(part))).when(repository).findConfigByPartId(part.partId) doReturn(Future.successful(Some(mockPartParam))).when(repository).findParamById(mockPartParam.id.get) - doReturn(Future.successful(37L)).when(repository).save(anyObject[PartParam]()) + doReturn(Future.successful(37L)).when(repository).save(anyObject[PartParam]())(anyObject[ConfigMapper[PartParam]]) val existingParam = part.parameters.head val copyParam = adminController.copyParam(part.partId, mockPartParam.id.get)(FakeRequest()) @@ -250,7 +250,7 @@ class AdminControllerSpec extends FunSpec verify(repository).findConfigByPartId(part.partId) verify(repository).findParamById(mockPartParam.id.get) val newCiCaptor = ArgumentCaptor.forClass(classOf[PartParam]) - verify(repository).save(newCiCaptor.capture()) + verify(repository).save(newCiCaptor.capture())(anyObject[ConfigMapper[PartParam]]) newCiCaptor.getValue.inputName should be(existingParam.inputName + "_") newCiCaptor.getValue.outputName should be(existingParam.outputName + "_") @@ -284,7 +284,7 @@ class AdminControllerSpec extends FunSpec val adminController = new AdminController(repository = repository) doReturn(Future.successful(Some(part))).when(repository).findConfigByPartId(part.partId) - doReturn(Future.successful(37L)).when(repository).save(anyObject[PartParam]()) + doReturn(Future.successful(37L)).when(repository).save(anyObject[PartParam]())(anyObject[ConfigMapper[PartParam]]) doReturn(Future.successful(Seq.empty)).when(repository).findAllCacheGroupsByName(anyVararg[String]()) val createParam = adminController.createParam(part.partId)( @@ -296,7 +296,7 @@ class AdminControllerSpec extends FunSpec verify(repository).findConfigByPartId(part.partId) val newCiCaptor = ArgumentCaptor.forClass(classOf[PartParam]) - verify(repository).save(newCiCaptor.capture()) + verify(repository).save(newCiCaptor.capture())(anyObject[ConfigMapper[PartParam]]) verify(repository).findAllCacheGroupsByName(Seq.empty: _*) newCiCaptor.getValue.outputName should be("someName") @@ -309,7 +309,7 @@ class AdminControllerSpec extends FunSpec val adminController = new AdminController(repository = repository) doReturn(Future.successful(Some(part))).when(repository).findConfigByPartId(part.partId) - doReturn(Future.successful(37L)).when(repository).save(anyObject[PartParam]()) + doReturn(Future.successful(37L)).when(repository).save(anyObject[PartParam]())(anyObject[ConfigMapper[PartParam]]) doReturn(Future.successful(Some(mockPartParam))).when(repository).findParamById(mockPartParam.id.get) doReturn(Future.successful(Seq.empty)).when(repository).findAllCacheGroupsByName(anyVararg[String]()) @@ -322,7 +322,7 @@ class AdminControllerSpec extends FunSpec verify(repository).findConfigByPartId(part.partId) val newCiCaptor = ArgumentCaptor.forClass(classOf[PartParam]) - verify(repository).save(newCiCaptor.capture()) + verify(repository).save(newCiCaptor.capture())(anyObject[ConfigMapper[PartParam]]) verify(repository).findAllCacheGroupsByName(Seq.empty: _*) newCiCaptor.getValue.outputName should be("newName") @@ -333,14 +333,14 @@ class AdminControllerSpec extends FunSpec it("should add a new Thread Pool Config") { val repository = mock[MutableConfigsRepository] val adminController = new AdminController(repository = repository) - doReturn(Future.successful(76L)).when(repository).save(anyObject[ThreadPoolConfig]()) + doReturn(Future.successful(76L)).when(repository).save(anyObject[ThreadPoolConfig]())(anyObject[ConfigMapper[ThreadPoolConfig]]) val createThreadPool = adminController.createThreadPool( FakeRequest().withFormUrlEncodedBody("threadPoolKey" -> "myNewThreadPool", "coreSize" -> "99") ) whenReady(createThreadPool) { result => status(createThreadPool) should be(302) val newCiCaptor = ArgumentCaptor.forClass(classOf[ThreadPoolConfig]) - verify(repository).save(newCiCaptor.capture()) + verify(repository).save(newCiCaptor.capture())(anyObject[ConfigMapper[ThreadPoolConfig]]) newCiCaptor.getValue.threadPoolKey should be("myNewThreadPool") newCiCaptor.getValue.coreSize should be(99) } @@ -349,7 +349,7 @@ class AdminControllerSpec extends FunSpec it("should update a thread pool config") { val repository = mock[MutableConfigsRepository] val adminController = new AdminController(repository = repository) - doReturn(Future.successful(76L)).when(repository).save(anyObject[ThreadPoolConfig]()) + doReturn(Future.successful(76L)).when(repository).save(anyObject[ThreadPoolConfig]())(anyObject[ConfigMapper[ThreadPoolConfig]]) val tpc = mockThreadConfig.copy(id = Some(123)) doReturn(Future.successful(Some(tpc))).when(repository).findThreadPoolConfigById(anyLong()) @@ -358,7 +358,7 @@ class AdminControllerSpec extends FunSpec verify(repository).findThreadPoolConfigById(123L) val newCiCaptor = ArgumentCaptor.forClass(classOf[ThreadPoolConfig]) - verify(repository).save(newCiCaptor.capture()) + verify(repository).save(newCiCaptor.capture())(anyObject[ConfigMapper[ThreadPoolConfig]]) newCiCaptor.getValue.threadPoolKey should be("myNewThreadPool") newCiCaptor.getValue.coreSize should be(99) newCiCaptor.getValue.createdAt should be(tpc.createdAt) @@ -368,12 +368,12 @@ class AdminControllerSpec extends FunSpec it("should add a new CacheGroup") { val repository = mock[MutableConfigsRepository] val adminController = new AdminController(repository = repository) - doReturn(Future.successful(76L)).when(repository).save(anyObject[CacheGroup]()) + doReturn(Future.successful(76L)).when(repository).save(anyObject[CacheGroup]())(anyObject[ConfigMapper[CacheGroup]]) val createCacheGroup = adminController.createCacheGroup(FakeRequest().withFormUrlEncodedBody("name" -> "newCacheGroup", "description" -> "hello")) whenReady(createCacheGroup) { result => val newCiCaptor = ArgumentCaptor.forClass(classOf[CacheGroup]) - verify(repository).save(newCiCaptor.capture()) + verify(repository).save(newCiCaptor.capture())(anyObject[ConfigMapper[CacheGroup]]) newCiCaptor.getValue.name should be("newCacheGroup") newCiCaptor.getValue.description should be("hello") } @@ -382,7 +382,7 @@ class AdminControllerSpec extends FunSpec it("should update a CacheGroup") { val repository = mock[MutableConfigsRepository] val adminController = new AdminController(repository = repository) - doReturn(Future.successful(76L)).when(repository).save(anyObject[CacheGroup]()) + doReturn(Future.successful(76L)).when(repository).save(anyObject[CacheGroup]())(anyObject[ConfigMapper[CacheGroup]]) doReturn(Future.successful(Seq(part))).when(repository).findAllConfigs() val cacheGroup = mockCacheGroup.copy(id = Some(123)) doReturn(Future.successful(Some(cacheGroup))).when(repository).findCacheGroupByName(anyString()) @@ -392,7 +392,7 @@ class AdminControllerSpec extends FunSpec verify(repository).findCacheGroupByName("123") val newCiCaptor = ArgumentCaptor.forClass(classOf[CacheGroup]) - verify(repository).save(newCiCaptor.capture()) + verify(repository).save(newCiCaptor.capture())(anyObject[ConfigMapper[CacheGroup]]) newCiCaptor.getValue.name should be("myEditedThreadPool") newCiCaptor.getValue.description should be("harooo") newCiCaptor.getValue.createdAt should be(cacheGroup.createdAt)