From 687152abf96168d45eca8b7f9f7c41e77d577751 Mon Sep 17 00:00:00 2001 From: Everett Sochowski Date: Fri, 13 Feb 2015 11:22:19 +0100 Subject: [PATCH 01/17] Add v1.4.0 customer info call --- src/main/scala/bootstrap/liftweb/Boot.scala | 5 +- .../scala/code/api/v1_4_0/APIMethods140.scala | 33 ++++++ .../code/api/v1_4_0/JSONFactory1_4_0.scala | 26 +++++ .../scala/code/api/v1_4_0/OBPAPI1_4_0.scala | 16 +++ .../customerinfo/CustomerInfoProvider.scala | 32 ++++++ .../MappedCustomerInfoProvider.scala | 45 ++++++++ .../code/api/v1_4_0/CustomerInfoTest.scala | 108 ++++++++++++++++++ .../customerinfo/MappedCustomerInfoTest.scala | 59 ++++++++++ 8 files changed, 323 insertions(+), 1 deletion(-) create mode 100644 src/main/scala/code/api/v1_4_0/APIMethods140.scala create mode 100644 src/main/scala/code/api/v1_4_0/JSONFactory1_4_0.scala create mode 100644 src/main/scala/code/api/v1_4_0/OBPAPI1_4_0.scala create mode 100644 src/main/scala/code/customerinfo/CustomerInfoProvider.scala create mode 100644 src/main/scala/code/customerinfo/MappedCustomerInfoProvider.scala create mode 100644 src/test/scala/code/api/v1_4_0/CustomerInfoTest.scala create mode 100644 src/test/scala/code/customerinfo/MappedCustomerInfoTest.scala diff --git a/src/main/scala/bootstrap/liftweb/Boot.scala b/src/main/scala/bootstrap/liftweb/Boot.scala index 44c290543..9b89cd185 100755 --- a/src/main/scala/bootstrap/liftweb/Boot.scala +++ b/src/main/scala/bootstrap/liftweb/Boot.scala @@ -32,6 +32,7 @@ Berlin 13359, Germany package bootstrap.liftweb import code.api.sandbox.SandboxApiCalls +import code.customerinfo.MappedCustomerInfo import net.liftweb._ import util._ import common._ @@ -169,6 +170,7 @@ class Boot extends Loggable{ LiftRules.statelessDispatch.append(v1_2.OBPAPI1_2) LiftRules.statelessDispatch.append(v1_2_1.OBPAPI1_2_1) LiftRules.statelessDispatch.append(v1_3_0.OBPAPI1_3_0) + LiftRules.statelessDispatch.append(v1_4_0.OBPAPI1_4_0) // add other apis LiftRules.statelessDispatch.append(BankMockAPI) @@ -317,5 +319,6 @@ class Boot extends Loggable{ object ToSchemify { val models = List(OBPUser, Admin, Nonce, Token, Consumer, - ViewPrivileges, ViewImpl, APIUser, MappedAccountHolder) + ViewPrivileges, ViewImpl, APIUser, MappedAccountHolder, + MappedCustomerInfo) } diff --git a/src/main/scala/code/api/v1_4_0/APIMethods140.scala b/src/main/scala/code/api/v1_4_0/APIMethods140.scala new file mode 100644 index 000000000..ac31eac11 --- /dev/null +++ b/src/main/scala/code/api/v1_4_0/APIMethods140.scala @@ -0,0 +1,33 @@ +package code.api.v1_4_0 + +import code.api.APIFailure +import code.customerinfo.CustomerInfo +import code.model.{BankId, User} +import net.liftweb.common.Box +import net.liftweb.http.{JsonResponse, Req} +import net.liftweb.http.rest.RestHelper +import code.api.util.APIUtil._ +import net.liftweb.json.Extraction + +trait APIMethods140 { + //needs to be a RestHelper to get access to JsonGet, JsonPost, etc. + self: RestHelper => + + + val Implementations1_4_0 = new Object(){ + lazy val getCustomerInfo : PartialFunction[Req, Box[User] => Box[JsonResponse]] = { + case "banks" :: BankId(bankId) :: "customer" :: Nil JsonGet _ => { + user => { + for { + u <- user ?~! "User must be logged in to retrieve customer info" + info <- CustomerInfo.customerInfoProvider.vend.getInfo(bankId, u) ~> APIFailure("No customer info found", 404) + } yield { + val json = JSONFactory1_4_0.createCustomerInfoJson(info) + successJsonResponse(Extraction.decompose(json)) + } + } + } + } + } + +} diff --git a/src/main/scala/code/api/v1_4_0/JSONFactory1_4_0.scala b/src/main/scala/code/api/v1_4_0/JSONFactory1_4_0.scala new file mode 100644 index 000000000..4347fa1e1 --- /dev/null +++ b/src/main/scala/code/api/v1_4_0/JSONFactory1_4_0.scala @@ -0,0 +1,26 @@ +package code.api.v1_4_0 + +import java.util.Date + +import code.customerinfo.CustomerInfo + +object JSONFactory1_4_0 { + + case class CustomerInfoJson(customer_number : String, + legal_name : String, + mobile_phone_number : String, + email : String, + face_image : CustomerFaceImageJson) + + case class CustomerFaceImageJson(url : String, date : Date) + + def createCustomerInfoJson(cInfo : CustomerInfo) : CustomerInfoJson = { + + CustomerInfoJson(customer_number = cInfo.number, + legal_name = cInfo.legalName, mobile_phone_number = cInfo.mobileNumber, + email = cInfo.email, face_image = CustomerFaceImageJson(url = cInfo.faceImage.url, date = cInfo.faceImage.date)) + + } + + +} diff --git a/src/main/scala/code/api/v1_4_0/OBPAPI1_4_0.scala b/src/main/scala/code/api/v1_4_0/OBPAPI1_4_0.scala new file mode 100644 index 000000000..ac50f7e3d --- /dev/null +++ b/src/main/scala/code/api/v1_4_0/OBPAPI1_4_0.scala @@ -0,0 +1,16 @@ +package code.api.v1_4_0 + +import code.api.OBPRestHelper +import net.liftweb.common.Loggable + +object OBPAPI1_4_0 extends OBPRestHelper with APIMethods140 with Loggable { + + + val VERSION = "1.4.0" + + val routes = List(Implementations1_4_0.getCustomerInfo) + + routes.foreach(route => { + oauthServe(apiPrefix{route}) + }) +} diff --git a/src/main/scala/code/customerinfo/CustomerInfoProvider.scala b/src/main/scala/code/customerinfo/CustomerInfoProvider.scala new file mode 100644 index 000000000..023e29897 --- /dev/null +++ b/src/main/scala/code/customerinfo/CustomerInfoProvider.scala @@ -0,0 +1,32 @@ +package code.customerinfo + +import java.util.Date + +import code.model.{BankId, User} +import net.liftweb.common.Box +import net.liftweb.util.SimpleInjector + +object CustomerInfo extends SimpleInjector { + + val customerInfoProvider = new Inject(buildOne _) {} + + def buildOne: CustomerInfoProvider = MappedCustomerInfoProvider + +} + +trait CustomerInfoProvider { + def getInfo(bankId : BankId, user : User) : Box[CustomerInfo] +} + +trait CustomerInfo { + val number : String + val legalName : String + val mobileNumber : String + val email : String + val faceImage : CustomerFaceImage +} + +trait CustomerFaceImage { + val url : String + val date : Date +} \ No newline at end of file diff --git a/src/main/scala/code/customerinfo/MappedCustomerInfoProvider.scala b/src/main/scala/code/customerinfo/MappedCustomerInfoProvider.scala new file mode 100644 index 000000000..c3ddd1346 --- /dev/null +++ b/src/main/scala/code/customerinfo/MappedCustomerInfoProvider.scala @@ -0,0 +1,45 @@ +package code.customerinfo + +import java.util.Date + +import code.model.{BankId, User} +import code.model.dataAccess.APIUser +import net.liftweb.common.Box +import net.liftweb.mapper._ + +object MappedCustomerInfoProvider extends CustomerInfoProvider { + override def getInfo(bankId : BankId, user: User): Box[CustomerInfo] = { + MappedCustomerInfo.find( + By(MappedCustomerInfo.mUser, user.apiId.value), + By(MappedCustomerInfo.mBank, bankId.value)) + } +} + +class MappedCustomerInfo extends CustomerInfo with LongKeyedMapper[MappedCustomerInfo] with IdPK with CreatedUpdated { + + def getSingleton = MappedCustomerInfo + + object mUser extends MappedLongForeignKey(this, APIUser) + object mBank extends MappedText(this) + + object mNumber extends MappedText(this) + object mMobileNumber extends MappedText(this) + object mLegalName extends MappedText(this) + object mEmail extends MappedEmail(this, 200) + object mFaceImageUrl extends MappedText(this) + object mFaceImageTime extends MappedDateTime(this) + + override val number: String = mNumber.get + override val mobileNumber: String = mMobileNumber.get + override val legalName: String = mLegalName.get + override val email: String = mEmail.get + override val faceImage: CustomerFaceImage = new CustomerFaceImage { + override val date: Date = mFaceImageTime.get + override val url: String = mFaceImageUrl.get + } +} + +object MappedCustomerInfo extends MappedCustomerInfo with LongKeyedMetaMapper[MappedCustomerInfo] { + //one customer info per bank for each api user + override def dbIndexes = UniqueIndex(mUser, mBank) :: super.dbIndexes +} \ No newline at end of file diff --git a/src/test/scala/code/api/v1_4_0/CustomerInfoTest.scala b/src/test/scala/code/api/v1_4_0/CustomerInfoTest.scala new file mode 100644 index 000000000..132ebdd77 --- /dev/null +++ b/src/test/scala/code/api/v1_4_0/CustomerInfoTest.scala @@ -0,0 +1,108 @@ +package code.api.v1_4_0 + +import java.util.Date + +import code.api.DefaultUsers +import code.api.test.ServerSetup +import code.api.util.APIUtil +import code.api.v1_4_0.JSONFactory1_4_0.CustomerInfoJson +import code.customerinfo.{CustomerFaceImage, CustomerInfo, CustomerInfoProvider} +import code.model.{User, BankId} +import net.liftweb.common.{Full, Empty, Box} +import dispatch._ +import code.api.util.APIUtil.OAuth._ + +class CustomerInfoTest extends ServerSetup with DefaultUsers { + + def v1_4Request = baseRequest / "obp" / "v1.4.0" + + val mockBankId = BankId("mockbank1") + + case class MockFaceImage(date : Date, url : String) extends CustomerFaceImage + case class MockCustomerInfo(number : String, mobileNumber : String, + legalName : String, email : String, + faceImage : MockFaceImage) extends CustomerInfo + + val mockCustomerFaceImage = MockFaceImage(new Date(1234000), "http://example.com/image1") + + val mockCustomerInfo = MockCustomerInfo("123", "3939", "Bob", "bob@example.com", mockCustomerFaceImage) + + object MockedCustomerInfoProvider extends CustomerInfoProvider { + override def getInfo(bankId: BankId, user: User): Box[CustomerInfo] = { + if(bankId == mockBankId) Full(mockCustomerInfo) + else Empty + } + } + + override def beforeAll() { + super.beforeAll() + //use the mock connector + CustomerInfo.customerInfoProvider.default.set(MockedCustomerInfoProvider) + } + + override def afterAll() { + super.afterAll() + //reset the default connector + CustomerInfo.customerInfoProvider.default.set(CustomerInfo.buildOne) + } + + + feature("Getting a bank's customer info of the current user") { + + scenario("There is no current user") { + Given("There is no logged in user") + + When("We make the request") + val request = (v1_4Request / "banks" / mockBankId.value / "customer").GET + val response = makeGetRequest(request) + + Then("We should get a 400") + response.code should equal(400) + } + + scenario("There is a user, but the bank in questions has no customer info") { + Given("The bank in question has no customer info") + val testBank = BankId("test-bank") + val user = obpuser1 + + CustomerInfo.customerInfoProvider.vend.getInfo(testBank, user).isEmpty should equal(true) + + When("We make the request") + //TODO: need stronger link between obpuser1 and user1 + val request = (v1_4Request / "banks" / testBank.value / "customer").GET <@(user1) + val response = makeGetRequest(request) + + Then("We should get a 404") + response.code should equal(404) + } + + scenario("There is a user, and the bank in questions has customer info for that user") { + Given("The bank in question has customer info") + val testBank = mockBankId + val user = obpuser1 + + CustomerInfo.customerInfoProvider.vend.getInfo(testBank, user).isEmpty should equal(false) + + When("We make the request") + //TODO: need stronger link between obpuser1 and user1 + val request = (v1_4Request / "banks" / testBank.value / "customer").GET <@(user1) + val response = makeGetRequest(request) + + Then("We should get a 200") + response.code should equal(200) + + And("We should get the right information back") + + implicit val format = APIUtil.formats + + val info = response.body.extract[CustomerInfoJson] + val received = MockCustomerInfo(info.customer_number, info.mobile_phone_number, + info.legal_name, info.email, MockFaceImage(info.face_image.date, info.face_image.url)) + + received should equal(mockCustomerInfo) + } + + } + + +} diff --git a/src/test/scala/code/customerinfo/MappedCustomerInfoTest.scala b/src/test/scala/code/customerinfo/MappedCustomerInfoTest.scala new file mode 100644 index 000000000..644166110 --- /dev/null +++ b/src/test/scala/code/customerinfo/MappedCustomerInfoTest.scala @@ -0,0 +1,59 @@ +package code.customerinfo + +import java.util.Date + +import code.api.DefaultUsers +import code.api.test.ServerSetup +import code.model.BankId +import net.liftweb.mapper.By + +class MappedCustomerInfoProviderTest extends ServerSetup with DefaultUsers { + + val testBankId = BankId("bank") + + lazy val customerInfo1 = MappedCustomerInfo.create + .mBank(testBankId.value).mEmail("bob@example.com").mFaceImageTime(new Date(12340000)) + .mFaceImageUrl("http://example.com/image.jpg").mLegalName("John Johnson") + .mMobileNumber("12343434").mNumber("343").mUser(obpuser1).saveMe + + + feature("Getting customer info") { + + scenario("No customer info exists for user and we try to get it") { + Given("No MappedCustomerInfo exists for a user") + MappedCustomerInfo.find(By(MappedCustomerInfo.mUser, obpuser2)).isDefined should equal(false) + + When("We try to get it") + val found = MappedCustomerInfoProvider.getInfo(testBankId, obpuser2) + + Then("We don't") + found.isDefined should equal(false) + } + + scenario("Customer info exists and we try to get it") { + Given("MappedCustomerInfo exists for a user") + MappedCustomerInfo.find(By(MappedCustomerInfo.mUser, obpuser1)).isDefined should equal(true) + + When("We try to get it") + val foundOpt = MappedCustomerInfoProvider.getInfo(testBankId, obpuser1) + + Then("We do") + foundOpt.isDefined should equal(true) + + And("It is the right info") + val found = foundOpt.get + found should equal(customerInfo1) + } + } + + + override def beforeAll() = { + super.beforeAll + MappedCustomerInfo.bulkDelete_!!() + } + + override def afterAll() = { + super.afterAll() + MappedCustomerInfo.bulkDelete_!!() + } +} From 2dda3a35c34f9a3d7df251457d3806d93a546f90 Mon Sep 17 00:00:00 2001 From: Everett Sochowski Date: Sat, 31 Jan 2015 20:15:07 +0100 Subject: [PATCH 02/17] Ensure test MappedCustomerInfo exists before tests are run --- .../scala/code/customerinfo/MappedCustomerInfoTest.scala | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/test/scala/code/customerinfo/MappedCustomerInfoTest.scala b/src/test/scala/code/customerinfo/MappedCustomerInfoTest.scala index 644166110..d70fa2936 100644 --- a/src/test/scala/code/customerinfo/MappedCustomerInfoTest.scala +++ b/src/test/scala/code/customerinfo/MappedCustomerInfoTest.scala @@ -11,12 +11,11 @@ class MappedCustomerInfoProviderTest extends ServerSetup with DefaultUsers { val testBankId = BankId("bank") - lazy val customerInfo1 = MappedCustomerInfo.create + val customerInfo1 = MappedCustomerInfo.create .mBank(testBankId.value).mEmail("bob@example.com").mFaceImageTime(new Date(12340000)) .mFaceImageUrl("http://example.com/image.jpg").mLegalName("John Johnson") .mMobileNumber("12343434").mNumber("343").mUser(obpuser1).saveMe - feature("Getting customer info") { scenario("No customer info exists for user and we try to get it") { @@ -32,7 +31,7 @@ class MappedCustomerInfoProviderTest extends ServerSetup with DefaultUsers { scenario("Customer info exists and we try to get it") { Given("MappedCustomerInfo exists for a user") - MappedCustomerInfo.find(By(MappedCustomerInfo.mUser, obpuser1)).isDefined should equal(true) + MappedCustomerInfo.find(By(MappedCustomerInfo.mUser, obpuser1.apiId.value)).isDefined should equal(true) When("We try to get it") val foundOpt = MappedCustomerInfoProvider.getInfo(testBankId, obpuser1) @@ -48,8 +47,7 @@ class MappedCustomerInfoProviderTest extends ServerSetup with DefaultUsers { override def beforeAll() = { - super.beforeAll - MappedCustomerInfo.bulkDelete_!!() + super.beforeAll() } override def afterAll() = { From e9f69fab3bc096e8e1d3db57ca17d42e881c3224 Mon Sep 17 00:00:00 2001 From: Everett Sochowski Date: Sat, 31 Jan 2015 20:51:20 +0100 Subject: [PATCH 03/17] Actually ensure test MappedCustomerInfo exists before tests are run --- .../scala/code/customerinfo/MappedCustomerInfoTest.scala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/test/scala/code/customerinfo/MappedCustomerInfoTest.scala b/src/test/scala/code/customerinfo/MappedCustomerInfoTest.scala index d70fa2936..5f0a0bf8c 100644 --- a/src/test/scala/code/customerinfo/MappedCustomerInfoTest.scala +++ b/src/test/scala/code/customerinfo/MappedCustomerInfoTest.scala @@ -11,10 +11,10 @@ class MappedCustomerInfoProviderTest extends ServerSetup with DefaultUsers { val testBankId = BankId("bank") - val customerInfo1 = MappedCustomerInfo.create + lazy val customerInfo1 = MappedCustomerInfo.create .mBank(testBankId.value).mEmail("bob@example.com").mFaceImageTime(new Date(12340000)) .mFaceImageUrl("http://example.com/image.jpg").mLegalName("John Johnson") - .mMobileNumber("12343434").mNumber("343").mUser(obpuser1).saveMe + .mMobileNumber("12343434").mNumber("343").mUser(obpuser1).saveMe() feature("Getting customer info") { @@ -48,6 +48,10 @@ class MappedCustomerInfoProviderTest extends ServerSetup with DefaultUsers { override def beforeAll() = { super.beforeAll() + MappedCustomerInfo.bulkDelete_!!() + + //init/save customerInfo1 + customerInfo1 } override def afterAll() = { From fb6bb150d15facc226fd6f762650edb5e545b6c4 Mon Sep 17 00:00:00 2001 From: Everett Sochowski Date: Sat, 31 Jan 2015 22:54:22 +0100 Subject: [PATCH 04/17] Change Customer info traits to use defs instead of vals --- .../code/customerinfo/CustomerInfoProvider.scala | 14 +++++++------- .../customerinfo/MappedCustomerInfoProvider.scala | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/scala/code/customerinfo/CustomerInfoProvider.scala b/src/main/scala/code/customerinfo/CustomerInfoProvider.scala index 023e29897..dbce388b9 100644 --- a/src/main/scala/code/customerinfo/CustomerInfoProvider.scala +++ b/src/main/scala/code/customerinfo/CustomerInfoProvider.scala @@ -19,14 +19,14 @@ trait CustomerInfoProvider { } trait CustomerInfo { - val number : String - val legalName : String - val mobileNumber : String - val email : String - val faceImage : CustomerFaceImage + def number : String + def legalName : String + def mobileNumber : String + def email : String + def faceImage : CustomerFaceImage } trait CustomerFaceImage { - val url : String - val date : Date + def url : String + def date : Date } \ No newline at end of file diff --git a/src/main/scala/code/customerinfo/MappedCustomerInfoProvider.scala b/src/main/scala/code/customerinfo/MappedCustomerInfoProvider.scala index c3ddd1346..cd4beb51f 100644 --- a/src/main/scala/code/customerinfo/MappedCustomerInfoProvider.scala +++ b/src/main/scala/code/customerinfo/MappedCustomerInfoProvider.scala @@ -29,13 +29,13 @@ class MappedCustomerInfo extends CustomerInfo with LongKeyedMapper[MappedCustome object mFaceImageUrl extends MappedText(this) object mFaceImageTime extends MappedDateTime(this) - override val number: String = mNumber.get - override val mobileNumber: String = mMobileNumber.get - override val legalName: String = mLegalName.get - override val email: String = mEmail.get - override val faceImage: CustomerFaceImage = new CustomerFaceImage { - override val date: Date = mFaceImageTime.get - override val url: String = mFaceImageUrl.get + override def number: String = mNumber.get + override def mobileNumber: String = mMobileNumber.get + override def legalName: String = mLegalName.get + override def email: String = mEmail.get + override def faceImage: CustomerFaceImage = new CustomerFaceImage { + override def date: Date = mFaceImageTime.get + override def url: String = mFaceImageUrl.get } } From 035e07a66b30e4c4b930e13574d72f220c9f5c4a Mon Sep 17 00:00:00 2001 From: Everett Sochowski Date: Fri, 13 Feb 2015 11:38:52 +0100 Subject: [PATCH 05/17] Create a field for MappedUUIDs --- src/main/scala/code/util/MappedUUID.scala | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/scala/code/util/MappedUUID.scala diff --git a/src/main/scala/code/util/MappedUUID.scala b/src/main/scala/code/util/MappedUUID.scala new file mode 100644 index 000000000..4433b4086 --- /dev/null +++ b/src/main/scala/code/util/MappedUUID.scala @@ -0,0 +1,9 @@ +package code.util + +import java.util.UUID + +import net.liftweb.mapper.{MappedString, Mapper} + +class MappedUUID[T <: Mapper[T]] (override val fieldOwner : T) extends MappedString(fieldOwner, 36) { + override def defaultValue = UUID.randomUUID().toString +} From d09cf8b1ca327671f03234e3bf61c23cb7d9121f Mon Sep 17 00:00:00 2001 From: Everett Sochowski Date: Sun, 1 Feb 2015 15:28:08 +0100 Subject: [PATCH 06/17] Add first draft of customer messages --- src/main/scala/bootstrap/liftweb/Boot.scala | 4 +- .../scala/code/api/v1_4_0/APIMethods140.scala | 41 +++++++++- .../code/api/v1_4_0/JSONFactory1_4_0.scala | 16 +++- .../scala/code/api/v1_4_0/OBPAPI1_4_0.scala | 5 +- .../customerinfo/CustomerInfoProvider.scala | 2 + .../code/customerinfo/CustomerMessage.scala | 33 ++++++++ .../MappedCustomerInfoProvider.scala | 9 ++- .../MappedCustomerMessageProvider.scala | 53 ++++++++++++ .../code/api/v1_4_0/CustomerInfoTest.scala | 2 + .../v1_4_0/MappedCustomerMessagesTest.scala | 80 +++++++++++++++++++ .../customerinfo/MappedCustomerInfoTest.scala | 61 ++++++++++++-- 11 files changed, 294 insertions(+), 12 deletions(-) create mode 100644 src/main/scala/code/customerinfo/CustomerMessage.scala create mode 100644 src/main/scala/code/customerinfo/MappedCustomerMessageProvider.scala create mode 100644 src/test/scala/code/api/v1_4_0/MappedCustomerMessagesTest.scala diff --git a/src/main/scala/bootstrap/liftweb/Boot.scala b/src/main/scala/bootstrap/liftweb/Boot.scala index 9b89cd185..d910dff66 100755 --- a/src/main/scala/bootstrap/liftweb/Boot.scala +++ b/src/main/scala/bootstrap/liftweb/Boot.scala @@ -32,7 +32,7 @@ Berlin 13359, Germany package bootstrap.liftweb import code.api.sandbox.SandboxApiCalls -import code.customerinfo.MappedCustomerInfo +import code.customerinfo.{MappedCustomerMessage, MappedCustomerInfo} import net.liftweb._ import util._ import common._ @@ -320,5 +320,5 @@ class Boot extends Loggable{ object ToSchemify { val models = List(OBPUser, Admin, Nonce, Token, Consumer, ViewPrivileges, ViewImpl, APIUser, MappedAccountHolder, - MappedCustomerInfo) + MappedCustomerInfo, MappedCustomerMessage) } diff --git a/src/main/scala/code/api/v1_4_0/APIMethods140.scala b/src/main/scala/code/api/v1_4_0/APIMethods140.scala index ac31eac11..4ecf2f420 100644 --- a/src/main/scala/code/api/v1_4_0/APIMethods140.scala +++ b/src/main/scala/code/api/v1_4_0/APIMethods140.scala @@ -1,13 +1,18 @@ package code.api.v1_4_0 import code.api.APIFailure -import code.customerinfo.CustomerInfo +import code.api.v1_4_0.JSONFactory1_4_0.AddCustomerMessageJson +import code.customerinfo.{CustomerMessages, CustomerInfo} import code.model.{BankId, User} import net.liftweb.common.Box +import net.liftweb.http.js.JE.JsRaw import net.liftweb.http.{JsonResponse, Req} import net.liftweb.http.rest.RestHelper import code.api.util.APIUtil._ import net.liftweb.json.Extraction +import net.liftweb.json.JsonAST.JObject +import net.liftweb.util.Helpers.tryo +import code.util.Helper._ trait APIMethods140 { //needs to be a RestHelper to get access to JsonGet, JsonPost, etc. @@ -15,6 +20,7 @@ trait APIMethods140 { val Implementations1_4_0 = new Object(){ + lazy val getCustomerInfo : PartialFunction[Req, Box[User] => Box[JsonResponse]] = { case "banks" :: BankId(bankId) :: "customer" :: Nil JsonGet _ => { user => { @@ -28,6 +34,39 @@ trait APIMethods140 { } } } + + lazy val getCustomerMessages : PartialFunction[Req, Box[User] => Box[JsonResponse]] = { + case "banks" :: BankId(bankId) :: "customer" :: "messages" :: Nil JsonGet _ => { + user => { + for { + u <- user ?~! "User must be logged in to retrieve customer messages" + } yield { + val messages = CustomerMessages.customerMessageProvider.vend.getMessages(u, bankId) + val json = JSONFactory1_4_0.createCustomerMessagesJson(messages) + successJsonResponse(Extraction.decompose(json)) + } + } + } + } + + lazy val addCustomerMessage : PartialFunction[Req, Box[User] => Box[JsonResponse]] = { + case "banks" :: BankId(bankId) :: "customer" :: customerNumber :: "messages" :: Nil JsonPost json -> _ => { + user => { + for { + postedData <- tryo{json.extract[AddCustomerMessageJson]} ?~! "Incorrect json format" + customer <- CustomerInfo.customerInfoProvider.vend.getUser(bankId, customerNumber) ?~! "No customer found" + messageCreated <- booleanToBox( + CustomerMessages.customerMessageProvider.vend.addMessage( + customer, bankId, postedData.message, postedData.from_department, postedData.from_person), + "Server error: could not add message") + } yield { + successJsonResponse(JsRaw("{}"), 201) + } + } + } + } + + } } diff --git a/src/main/scala/code/api/v1_4_0/JSONFactory1_4_0.scala b/src/main/scala/code/api/v1_4_0/JSONFactory1_4_0.scala index 4347fa1e1..43a18d70c 100644 --- a/src/main/scala/code/api/v1_4_0/JSONFactory1_4_0.scala +++ b/src/main/scala/code/api/v1_4_0/JSONFactory1_4_0.scala @@ -2,7 +2,7 @@ package code.api.v1_4_0 import java.util.Date -import code.customerinfo.CustomerInfo +import code.customerinfo.{CustomerMessage, CustomerInfo} object JSONFactory1_4_0 { @@ -14,6 +14,11 @@ object JSONFactory1_4_0 { case class CustomerFaceImageJson(url : String, date : Date) + case class CustomerMessagesJson(messages : List[CustomerMessageJson]) + case class CustomerMessageJson(id : String, date : Date, message : String, from_department : String, from_person : String) + + case class AddCustomerMessageJson(message : String, from_department : String, from_person : String) + def createCustomerInfoJson(cInfo : CustomerInfo) : CustomerInfoJson = { CustomerInfoJson(customer_number = cInfo.number, @@ -22,5 +27,14 @@ object JSONFactory1_4_0 { } + def createCustomerMessageJson(cMessage : CustomerMessage) : CustomerMessageJson = { + CustomerMessageJson(id = cMessage.messageId, date = cMessage.date, + message = cMessage.message, from_department = cMessage.fromDepartment, + from_person = cMessage.fromPerson) + } + + def createCustomerMessagesJson(messages : List[CustomerMessage]) : CustomerMessagesJson = { + CustomerMessagesJson(messages.map(createCustomerMessageJson)) + } } diff --git a/src/main/scala/code/api/v1_4_0/OBPAPI1_4_0.scala b/src/main/scala/code/api/v1_4_0/OBPAPI1_4_0.scala index ac50f7e3d..e7c1b6fbc 100644 --- a/src/main/scala/code/api/v1_4_0/OBPAPI1_4_0.scala +++ b/src/main/scala/code/api/v1_4_0/OBPAPI1_4_0.scala @@ -8,7 +8,10 @@ object OBPAPI1_4_0 extends OBPRestHelper with APIMethods140 with Loggable { val VERSION = "1.4.0" - val routes = List(Implementations1_4_0.getCustomerInfo) + val routes = List( + Implementations1_4_0.getCustomerInfo, + Implementations1_4_0.getCustomerMessages, + Implementations1_4_0.addCustomerMessage) routes.foreach(route => { oauthServe(apiPrefix{route}) diff --git a/src/main/scala/code/customerinfo/CustomerInfoProvider.scala b/src/main/scala/code/customerinfo/CustomerInfoProvider.scala index dbce388b9..e7f16b310 100644 --- a/src/main/scala/code/customerinfo/CustomerInfoProvider.scala +++ b/src/main/scala/code/customerinfo/CustomerInfoProvider.scala @@ -16,6 +16,8 @@ object CustomerInfo extends SimpleInjector { trait CustomerInfoProvider { def getInfo(bankId : BankId, user : User) : Box[CustomerInfo] + + def getUser(bankId : BankId, customerId : String) : Box[User] } trait CustomerInfo { diff --git a/src/main/scala/code/customerinfo/CustomerMessage.scala b/src/main/scala/code/customerinfo/CustomerMessage.scala new file mode 100644 index 000000000..e476447b2 --- /dev/null +++ b/src/main/scala/code/customerinfo/CustomerMessage.scala @@ -0,0 +1,33 @@ +package code.customerinfo + +import java.util.Date + +import code.model.{BankId, User} +import net.liftweb.util.SimpleInjector + + +object CustomerMessages extends SimpleInjector { + + val customerMessageProvider = new Inject(buildOne _) {} + + def buildOne: CustomerMessageProvider = MappedCustomerMessageProvider + +} + +trait CustomerMessageProvider { + + //TODO: pagination? is this sorted by date? + def getMessages(user : User, bankId : BankId) : List[CustomerMessage] + + def addMessage(user : User, bankId : BankId, message : String, fromDepartment : String, fromPerson : String) : Boolean + +} + +trait CustomerMessage { + //TODO: message language? + def messageId : String + def date : Date + def message : String + def fromDepartment : String + def fromPerson : String +} diff --git a/src/main/scala/code/customerinfo/MappedCustomerInfoProvider.scala b/src/main/scala/code/customerinfo/MappedCustomerInfoProvider.scala index cd4beb51f..0fdbe686e 100644 --- a/src/main/scala/code/customerinfo/MappedCustomerInfoProvider.scala +++ b/src/main/scala/code/customerinfo/MappedCustomerInfoProvider.scala @@ -13,6 +13,13 @@ object MappedCustomerInfoProvider extends CustomerInfoProvider { By(MappedCustomerInfo.mUser, user.apiId.value), By(MappedCustomerInfo.mBank, bankId.value)) } + + override def getUser(bankId: BankId, customerNumber: String): Box[User] = { + MappedCustomerInfo.find( + By(MappedCustomerInfo.mBank, bankId.value), + By(MappedCustomerInfo.mNumber, customerNumber) + ).flatMap(_.mUser.obj) + } } class MappedCustomerInfo extends CustomerInfo with LongKeyedMapper[MappedCustomerInfo] with IdPK with CreatedUpdated { @@ -41,5 +48,5 @@ class MappedCustomerInfo extends CustomerInfo with LongKeyedMapper[MappedCustome object MappedCustomerInfo extends MappedCustomerInfo with LongKeyedMetaMapper[MappedCustomerInfo] { //one customer info per bank for each api user - override def dbIndexes = UniqueIndex(mUser, mBank) :: super.dbIndexes + override def dbIndexes = UniqueIndex(mBank, mNumber) :: UniqueIndex(mUser, mBank) :: super.dbIndexes } \ No newline at end of file diff --git a/src/main/scala/code/customerinfo/MappedCustomerMessageProvider.scala b/src/main/scala/code/customerinfo/MappedCustomerMessageProvider.scala new file mode 100644 index 000000000..b5d90e59b --- /dev/null +++ b/src/main/scala/code/customerinfo/MappedCustomerMessageProvider.scala @@ -0,0 +1,53 @@ +package code.customerinfo + +import java.util.Date + +import code.model.{BankId, User} +import code.model.dataAccess.APIUser +import code.util.MappedUUID +import net.liftweb.mapper._ + +object MappedCustomerMessageProvider extends CustomerMessageProvider { + + override def getMessages(user: User, bankId : BankId): List[CustomerMessage] = { + MappedCustomerMessage.findAll( + By(MappedCustomerMessage.user, user.apiId.value), + By(MappedCustomerMessage.bank, bankId.value), + OrderBy(MappedCustomerMessage.updatedAt, Descending)) + } + + + override def addMessage(user: User, bankId: BankId, message: String, fromDepartment: String, fromPerson: String): Boolean = { + MappedCustomerMessage.create + .mFromDepartment(fromDepartment) + .mFromPerson(fromPerson) + .mMessage(message) + .user(user.apiId.value) + .bank(bankId.value).save() + } +} + +class MappedCustomerMessage extends CustomerMessage + with LongKeyedMapper[MappedCustomerMessage] with IdPK with CreatedUpdated { + + def getSingleton = MappedCustomerMessage + + object user extends MappedLongForeignKey(this, APIUser) + object bank extends MappedText(this) + + object mFromPerson extends MappedText(this) + object mFromDepartment extends MappedText(this) + object mMessage extends MappedText(this) + object mMessageId extends MappedUUID(this) + + + override def messageId: String = mMessageId.get + override def date: Date = createdAt.get + override def fromPerson: String = mFromPerson.get + override def fromDepartment: String = mFromDepartment.get + override def message: String = mMessage.get +} + +object MappedCustomerMessage extends MappedCustomerMessage with LongKeyedMetaMapper[MappedCustomerMessage] { + override def dbIndexes = UniqueIndex(mMessageId) :: Index(user, bank, updatedAt) :: super.dbIndexes +} \ No newline at end of file diff --git a/src/test/scala/code/api/v1_4_0/CustomerInfoTest.scala b/src/test/scala/code/api/v1_4_0/CustomerInfoTest.scala index 132ebdd77..0e8c3e58e 100644 --- a/src/test/scala/code/api/v1_4_0/CustomerInfoTest.scala +++ b/src/test/scala/code/api/v1_4_0/CustomerInfoTest.scala @@ -32,6 +32,8 @@ class CustomerInfoTest extends ServerSetup with DefaultUsers { if(bankId == mockBankId) Full(mockCustomerInfo) else Empty } + + override def getUser(bankId: BankId, customerId: String): Box[User] = Empty } override def beforeAll() { diff --git a/src/test/scala/code/api/v1_4_0/MappedCustomerMessagesTest.scala b/src/test/scala/code/api/v1_4_0/MappedCustomerMessagesTest.scala new file mode 100644 index 000000000..c0b761f0c --- /dev/null +++ b/src/test/scala/code/api/v1_4_0/MappedCustomerMessagesTest.scala @@ -0,0 +1,80 @@ +package code.api.v1_4_0 + +import code.api.DefaultUsers +import code.api.test.ServerSetup +import code.api.util.APIUtil +import code.api.v1_4_0.JSONFactory1_4_0.{AddCustomerMessageJson, CustomerMessagesJson} +import code.customerinfo.{MappedCustomerMessage, MappedCustomerInfo, CustomerInfo} +import code.model.BankId +import dispatch._ +import code.api.util.APIUtil.OAuth._ +import net.liftweb.json.Serialization.{read, write} + +//TODO: API test should be independent of CustomerMessages implementation +class MappedCustomerMessagesTest extends ServerSetup with DefaultUsers { + + implicit val format = APIUtil.formats + + def v1_4Request = baseRequest / "obp" / "v1.4.0" + + val mockBankId = BankId("mockbank1") + val mockCustomerNumber = "9393490320" + + //TODO: need better tests + feature("Customer messages") { + scenario("Getting messages when none exist") { + Given("No messages exist") + MappedCustomerMessage.count() should equal(0) + + When("We get the messages") + val request = (v1_4Request / "banks" / mockBankId.value / "customer" / "messages").GET <@ user1 + val response = makeGetRequest(request) + + Then("We should get a 200") + response.code should equal(200) + + And("We should get no messages") + val json = response.body.extract[CustomerMessagesJson] + json.messages.size should equal(0) + } + + scenario("Adding a message") { + When("We add a message") + val request = (v1_4Request / "banks" / mockBankId.value / "customer" / mockCustomerNumber / "messages").POST + val messageJson = AddCustomerMessageJson("some message", "some department", "some person") + val response = makePostRequest(request, write(messageJson)) + + Then("We should get a 201") + response.code should equal(201) + + And("We should get that message when we do a get messages request ") + val getMessagesRequest = (v1_4Request / "banks" / mockBankId.value / "customer" / "messages").GET <@ user1 + val getMessagesResponse = makeGetRequest(getMessagesRequest) + val json = getMessagesResponse.body.extract[CustomerMessagesJson] + json.messages.size should equal(1) + + val msg = json.messages(0) + msg.message should equal(messageJson.message) + msg.from_department should equal(messageJson.from_department) + msg.from_person should equal(messageJson.from_person) + msg.id.nonEmpty should equal(true) + } + } + + + override def beforeAll(): Unit = { + super.beforeAll() + //TODO: this shouldn't be tied to an implementation + //need to create a customer info obj since the customer messages call needs to find user by customer number + MappedCustomerInfo.create + .mBank(mockBankId.value) + .mUser(obpuser1) + .mNumber(mockCustomerNumber).save() + } + + override def beforeEach(): Unit = { + super.beforeEach() + MappedCustomerMessage.bulkDelete_!!() + } + +} diff --git a/src/test/scala/code/customerinfo/MappedCustomerInfoTest.scala b/src/test/scala/code/customerinfo/MappedCustomerInfoTest.scala index 5f0a0bf8c..1304a4423 100644 --- a/src/test/scala/code/customerinfo/MappedCustomerInfoTest.scala +++ b/src/test/scala/code/customerinfo/MappedCustomerInfoTest.scala @@ -11,7 +11,7 @@ class MappedCustomerInfoProviderTest extends ServerSetup with DefaultUsers { val testBankId = BankId("bank") - lazy val customerInfo1 = MappedCustomerInfo.create + def createCustomerInfo1() = MappedCustomerInfo.create .mBank(testBankId.value).mEmail("bob@example.com").mFaceImageTime(new Date(12340000)) .mFaceImageUrl("http://example.com/image.jpg").mLegalName("John Johnson") .mMobileNumber("12343434").mNumber("343").mUser(obpuser1).saveMe() @@ -30,6 +30,7 @@ class MappedCustomerInfoProviderTest extends ServerSetup with DefaultUsers { } scenario("Customer info exists and we try to get it") { + val customerInfo1 = createCustomerInfo1() Given("MappedCustomerInfo exists for a user") MappedCustomerInfo.find(By(MappedCustomerInfo.mUser, obpuser1.apiId.value)).isDefined should equal(true) @@ -45,17 +46,65 @@ class MappedCustomerInfoProviderTest extends ServerSetup with DefaultUsers { } } + feature("Getting a user from a bankId and customer number") { + + scenario("We try to get a user from a customer number that doesn't exist") { + val customerNumber = "123213213213213" + + Given("No customer info exists for a certain customer number") + MappedCustomerInfo.find(By(MappedCustomerInfo.mNumber, customerNumber)).isDefined should equal(false) + + When("We try to get the user for a bank with that customer number") + val found = MappedCustomerInfoProvider.getUser(BankId("some-bank"), customerNumber) + + Then("We should not find a user") + found.isDefined should equal(false) + } + + scenario("We try to get a user from a customer number that doesn't exist at the bank in question") { + val customerNumber = "123213213213213" + val bankId = BankId("a-bank") + + Given("Customer info exists for a different bank") + MappedCustomerInfo.create.mNumber(customerNumber).mBank(bankId.value).mUser(obpuser1).saveMe() + MappedCustomerInfo.count(By(MappedCustomerInfo.mNumber, customerNumber), + By(MappedCustomerInfo.mBank, bankId.value)) should equal({ + MappedCustomerInfo.count(By(MappedCustomerInfo.mNumber, customerNumber)) + }) + + When("We try to get the user for a different bank") + val found = MappedCustomerInfoProvider.getUser(BankId(bankId.value + "asdsad"), customerNumber) + + Then("We should not find a user") + found.isDefined should equal(false) + } + + scenario("We try to get a user from a customer number that does exist at the bank in question") { + val customerNumber = "123213213213213" + val bankId = BankId("a-bank") + + Given("Customer info exists for that bank") + MappedCustomerInfo.create.mNumber(customerNumber).mBank(bankId.value).mUser(obpuser1).saveMe() + MappedCustomerInfo.count(By(MappedCustomerInfo.mNumber, customerNumber), + By(MappedCustomerInfo.mBank, bankId.value)) should equal(1) + + When("We try to get the user for that bank") + val found = MappedCustomerInfoProvider.getUser(bankId, customerNumber) + + Then("We should not find a user") + found.isDefined should equal(true) + } + + } + override def beforeAll() = { super.beforeAll() MappedCustomerInfo.bulkDelete_!!() - - //init/save customerInfo1 - customerInfo1 } - override def afterAll() = { - super.afterAll() + override def afterEach() = { + super.afterEach() MappedCustomerInfo.bulkDelete_!!() } } From 6e24f9475257aa61a523aa15df3667bbfadc23b3 Mon Sep 17 00:00:00 2001 From: Everett Sochowski Date: Fri, 6 Feb 2015 17:48:11 +0000 Subject: [PATCH 07/17] Refactoring --- src/test/scala/code/api/v1_4_0/CustomerInfoTest.scala | 5 +---- .../code/api/v1_4_0/MappedCustomerMessagesTest.scala | 5 +---- src/test/scala/code/api/v1_4_0/V140ServerSetup.scala | 10 ++++++++++ 3 files changed, 12 insertions(+), 8 deletions(-) create mode 100644 src/test/scala/code/api/v1_4_0/V140ServerSetup.scala diff --git a/src/test/scala/code/api/v1_4_0/CustomerInfoTest.scala b/src/test/scala/code/api/v1_4_0/CustomerInfoTest.scala index 0e8c3e58e..6b8ccedaa 100644 --- a/src/test/scala/code/api/v1_4_0/CustomerInfoTest.scala +++ b/src/test/scala/code/api/v1_4_0/CustomerInfoTest.scala @@ -3,7 +3,6 @@ package code.api.v1_4_0 import java.util.Date import code.api.DefaultUsers -import code.api.test.ServerSetup import code.api.util.APIUtil import code.api.v1_4_0.JSONFactory1_4_0.CustomerInfoJson import code.customerinfo.{CustomerFaceImage, CustomerInfo, CustomerInfoProvider} @@ -12,9 +11,7 @@ import net.liftweb.common.{Full, Empty, Box} import dispatch._ import code.api.util.APIUtil.OAuth._ -class CustomerInfoTest extends ServerSetup with DefaultUsers { - - def v1_4Request = baseRequest / "obp" / "v1.4.0" +class CustomerInfoTest extends V140ServerSetup with DefaultUsers { val mockBankId = BankId("mockbank1") diff --git a/src/test/scala/code/api/v1_4_0/MappedCustomerMessagesTest.scala b/src/test/scala/code/api/v1_4_0/MappedCustomerMessagesTest.scala index c0b761f0c..718052e0b 100644 --- a/src/test/scala/code/api/v1_4_0/MappedCustomerMessagesTest.scala +++ b/src/test/scala/code/api/v1_4_0/MappedCustomerMessagesTest.scala @@ -1,7 +1,6 @@ package code.api.v1_4_0 import code.api.DefaultUsers -import code.api.test.ServerSetup import code.api.util.APIUtil import code.api.v1_4_0.JSONFactory1_4_0.{AddCustomerMessageJson, CustomerMessagesJson} import code.customerinfo.{MappedCustomerMessage, MappedCustomerInfo, CustomerInfo} @@ -11,12 +10,10 @@ import code.api.util.APIUtil.OAuth._ import net.liftweb.json.Serialization.{read, write} //TODO: API test should be independent of CustomerMessages implementation -class MappedCustomerMessagesTest extends ServerSetup with DefaultUsers { +class MappedCustomerMessagesTest extends V140ServerSetup with DefaultUsers { implicit val format = APIUtil.formats - def v1_4Request = baseRequest / "obp" / "v1.4.0" - val mockBankId = BankId("mockbank1") val mockCustomerNumber = "9393490320" diff --git a/src/test/scala/code/api/v1_4_0/V140ServerSetup.scala b/src/test/scala/code/api/v1_4_0/V140ServerSetup.scala new file mode 100644 index 000000000..a76f4c1cb --- /dev/null +++ b/src/test/scala/code/api/v1_4_0/V140ServerSetup.scala @@ -0,0 +1,10 @@ +package code.api.v1_4_0 + +import code.api.test.ServerSetup +import dispatch._ + +trait V140ServerSetup extends ServerSetup { + + def v1_4Request = baseRequest / "obp" / "v1.4.0" + +} From 9531a3871af0c02f3af20ca62eadba1ca592041d Mon Sep 17 00:00:00 2001 From: Everett Sochowski Date: Mon, 9 Feb 2015 17:44:00 +0100 Subject: [PATCH 08/17] Add bank branches API call --- src/main/scala/bootstrap/liftweb/Boot.scala | 4 +- .../scala/code/api/v1_4_0/APIMethods140.scala | 14 ++ .../code/api/v1_4_0/JSONFactory1_4_0.scala | 23 +++ .../scala/code/api/v1_4_0/OBPAPI1_4_0.scala | 3 +- .../code/bankbranches/BankBranches.scala | 60 ++++++++ .../MappedBankBranchesProvider.scala | 65 +++++++++ .../code/api/v1_4_0/BankBranchesTest.scala | 117 +++++++++++++++ .../MappedBankBranchesProviderTest.scala | 133 ++++++++++++++++++ 8 files changed, 417 insertions(+), 2 deletions(-) create mode 100644 src/main/scala/code/bankbranches/BankBranches.scala create mode 100644 src/main/scala/code/bankbranches/MappedBankBranchesProvider.scala create mode 100644 src/test/scala/code/api/v1_4_0/BankBranchesTest.scala create mode 100644 src/test/scala/code/bankbranches/MappedBankBranchesProviderTest.scala diff --git a/src/main/scala/bootstrap/liftweb/Boot.scala b/src/main/scala/bootstrap/liftweb/Boot.scala index d910dff66..33652cb99 100755 --- a/src/main/scala/bootstrap/liftweb/Boot.scala +++ b/src/main/scala/bootstrap/liftweb/Boot.scala @@ -32,6 +32,7 @@ Berlin 13359, Germany package bootstrap.liftweb import code.api.sandbox.SandboxApiCalls +import code.bankbranches.{MappedBankBranch, MappedDataLicense} import code.customerinfo.{MappedCustomerMessage, MappedCustomerInfo} import net.liftweb._ import util._ @@ -320,5 +321,6 @@ class Boot extends Loggable{ object ToSchemify { val models = List(OBPUser, Admin, Nonce, Token, Consumer, ViewPrivileges, ViewImpl, APIUser, MappedAccountHolder, - MappedCustomerInfo, MappedCustomerMessage) + MappedCustomerInfo, MappedCustomerMessage, + MappedBankBranch, MappedDataLicense) } diff --git a/src/main/scala/code/api/v1_4_0/APIMethods140.scala b/src/main/scala/code/api/v1_4_0/APIMethods140.scala index 4ecf2f420..2d2e8b906 100644 --- a/src/main/scala/code/api/v1_4_0/APIMethods140.scala +++ b/src/main/scala/code/api/v1_4_0/APIMethods140.scala @@ -2,6 +2,7 @@ package code.api.v1_4_0 import code.api.APIFailure import code.api.v1_4_0.JSONFactory1_4_0.AddCustomerMessageJson +import code.bankbranches.BankBranches import code.customerinfo.{CustomerMessages, CustomerInfo} import code.model.{BankId, User} import net.liftweb.common.Box @@ -66,6 +67,19 @@ trait APIMethods140 { } } + lazy val getBranches : PartialFunction[Req, Box[User] => Box[JsonResponse]] = { + case "banks" :: BankId(bankId) :: "branches" :: Nil JsonGet _ => { + user => { + for { + branches <- Box(BankBranches.bankBranchesProvider.vend.getBranches(bankId)) ~> APIFailure("No branch data available", 404) + } yield { + val json = JSONFactory1_4_0.createBranchesJson(branches) + successJsonResponse(Extraction.decompose(json)) + } + } + } + } + } diff --git a/src/main/scala/code/api/v1_4_0/JSONFactory1_4_0.scala b/src/main/scala/code/api/v1_4_0/JSONFactory1_4_0.scala index 43a18d70c..810369c9e 100644 --- a/src/main/scala/code/api/v1_4_0/JSONFactory1_4_0.scala +++ b/src/main/scala/code/api/v1_4_0/JSONFactory1_4_0.scala @@ -2,6 +2,8 @@ package code.api.v1_4_0 import java.util.Date +import code.bankbranches.BankBranches +import code.bankbranches.BankBranches.{BankBranch, DataLicense, BranchData} import code.customerinfo.{CustomerMessage, CustomerInfo} object JSONFactory1_4_0 { @@ -19,6 +21,11 @@ object JSONFactory1_4_0 { case class AddCustomerMessageJson(message : String, from_department : String, from_person : String) + case class BranchDataJson(license : DataLicenseJson, branches : List[BranchJson]) + case class DataLicenseJson(name : String, url : String) + case class BranchJson(id : String, name : String, address : AddressJson) + case class AddressJson(line_1 : String, line_2 : String, line_3 : String, line_4 : String, line_5 : String, postcode_zip : String, country : String) + def createCustomerInfoJson(cInfo : CustomerInfo) : CustomerInfoJson = { CustomerInfoJson(customer_number = cInfo.number, @@ -37,4 +44,20 @@ object JSONFactory1_4_0 { CustomerMessagesJson(messages.map(createCustomerMessageJson)) } + def createDataLicenseJson(dataLicense : DataLicense) : DataLicenseJson = { + DataLicenseJson(dataLicense.name, dataLicense.url) + } + + def createAddressJson(address : BankBranches.Address) : AddressJson = { + AddressJson(address.line1, address.line2, address.line3, address.line4, address.line5, address.postCode, address.countryCode) + } + + def createBranchJson(bankBranch: BankBranch) : BranchJson = { + BranchJson(bankBranch.branchId.value, bankBranch.name, createAddressJson(bankBranch.address)) + } + + def createBranchesJson(branchData : BranchData) : BranchDataJson = { + BranchDataJson(createDataLicenseJson(branchData.license), branchData.branches.map(createBranchJson)) + } + } diff --git a/src/main/scala/code/api/v1_4_0/OBPAPI1_4_0.scala b/src/main/scala/code/api/v1_4_0/OBPAPI1_4_0.scala index e7c1b6fbc..7feab3430 100644 --- a/src/main/scala/code/api/v1_4_0/OBPAPI1_4_0.scala +++ b/src/main/scala/code/api/v1_4_0/OBPAPI1_4_0.scala @@ -11,7 +11,8 @@ object OBPAPI1_4_0 extends OBPRestHelper with APIMethods140 with Loggable { val routes = List( Implementations1_4_0.getCustomerInfo, Implementations1_4_0.getCustomerMessages, - Implementations1_4_0.addCustomerMessage) + Implementations1_4_0.addCustomerMessage, + Implementations1_4_0.getBranches) routes.foreach(route => { oauthServe(apiPrefix{route}) diff --git a/src/main/scala/code/bankbranches/BankBranches.scala b/src/main/scala/code/bankbranches/BankBranches.scala new file mode 100644 index 000000000..5041a8cd1 --- /dev/null +++ b/src/main/scala/code/bankbranches/BankBranches.scala @@ -0,0 +1,60 @@ +package code.bankbranches + +import code.bankbranches.BankBranches.{BankBranch, DataLicense, BranchData} +import code.model.BankId +import net.liftweb.common.Logger +import net.liftweb.util.SimpleInjector + +object BankBranches extends SimpleInjector { + + case class BankBranchId(value : String) + case class BranchData(branches : List[BankBranch], license : DataLicense) + + trait DataLicense { + def name : String + def url : String + } + + trait BankBranch { + def branchId : BankBranchId + def name : String + def address : Address + } + + trait Address { + def line1 : String + def line2 : String + def line3 : String + def line4 : String + def line5 : String + def postCode : String + //ISO_3166-1_alpha-2 + def countryCode : String + } + + val bankBranchesProvider = new Inject(buildOne _) {} + + def buildOne: BankBranchesProvider = MappedBankBranchesProvider + +} + +trait BankBranchesProvider { + + private val logger = Logger(classOf[BankBranchesProvider]) + + final def getBranches(bank : BankId) : Option[BranchData] = { + branchDataLicense(bank) match { + case Some(license) => + Some(BranchData(branchData(bank), license)) + case None => { + logger.info(s"No branch data license found for bank ${bank.value}") + None + } + } + } + + protected def branchData(bank : BankId) : List[BankBranch] + protected def branchDataLicense(bank : BankId) : Option[DataLicense] +} + + diff --git a/src/main/scala/code/bankbranches/MappedBankBranchesProvider.scala b/src/main/scala/code/bankbranches/MappedBankBranchesProvider.scala new file mode 100644 index 000000000..7606497db --- /dev/null +++ b/src/main/scala/code/bankbranches/MappedBankBranchesProvider.scala @@ -0,0 +1,65 @@ +package code.bankbranches + +import code.bankbranches.BankBranches.{DataLicense, BankBranchId, Address, BankBranch} +import code.model.BankId +import net.liftweb.mapper._ + +object MappedBankBranchesProvider extends BankBranchesProvider { + override protected def branchData(bank: BankId): List[BankBranch] = + MappedBankBranch.findAll(By(MappedBankBranch.mBankId, bank.value)) + + override protected def branchDataLicense(bank: BankId): Option[DataLicense] = + MappedDataLicense.find(By(MappedDataLicense.mBankId, bank.value)) +} + +class MappedBankBranch extends BankBranch with LongKeyedMapper[MappedBankBranch] with IdPK { + + override def getSingleton = MappedBankBranch + + object mBankId extends MappedText(this) + object mName extends MappedText(this) + + object mBranchId extends MappedText(this) + + object mLine1 extends MappedText(this) + object mLine2 extends MappedText(this) + object mLine3 extends MappedText(this) + object mLine4 extends MappedText(this) + object mLine5 extends MappedText(this) + + object mCountryCode extends MappedString(this, 2) + object mPostCode extends MappedText(this) + + + override def branchId: BankBranchId = BankBranchId(mBranchId.get) + override def name: String = mName.get + + override def address: Address = new Address { + override def line1: String = mLine1.get + override def line2: String = mLine2.get + override def line3: String = mLine3.get + override def line4: String = mLine4.get + override def line5: String = mLine5.get + override def countryCode: String = mCountryCode.get + override def postCode: String = mPostCode.get + } +} + +object MappedBankBranch extends MappedBankBranch with LongKeyedMetaMapper[MappedBankBranch] { + override def dbIndexes = UniqueIndex(mBankId, mBranchId) :: Index(mBankId) :: super.dbIndexes +} + +class MappedDataLicense extends DataLicense with LongKeyedMapper[MappedDataLicense] with IdPK { + override def getSingleton = MappedDataLicense + + object mBankId extends MappedText(this) + object mName extends MappedText(this) + object mUrl extends MappedText(this) + + override def name: String = mName.get + override def url: String = mUrl.get +} + +object MappedDataLicense extends MappedDataLicense with LongKeyedMetaMapper[MappedDataLicense] { + override def dbIndexes = Index(mBankId) :: super.dbIndexes +} \ No newline at end of file diff --git a/src/test/scala/code/api/v1_4_0/BankBranchesTest.scala b/src/test/scala/code/api/v1_4_0/BankBranchesTest.scala new file mode 100644 index 000000000..bf08db450 --- /dev/null +++ b/src/test/scala/code/api/v1_4_0/BankBranchesTest.scala @@ -0,0 +1,117 @@ +package code.api.v1_4_0 + +import code.api.v1_4_0.JSONFactory1_4_0.{BranchJson, BranchDataJson} +import dispatch._ +import code.bankbranches.BankBranches.{Address, BankBranchId, BankBranch, DataLicense} +import code.bankbranches.{BankBranches, BankBranchesProvider} +import code.model.BankId + +class BankBranchesTest extends V140ServerSetup { + + val BankWithLicense = BankId("bank-with-license") + val BankWithoutLicense = BankId("bank-without-license") + + case class BankBranchImpl(branchId : BankBranchId, name : String, address : Address) extends BankBranch + case class AddressImpl(line1 : String, line2 : String, line3 : String, line4 : String, + line5 : String, postCode : String, countryCode : String) extends Address + + val fakeAddress1 = AddressImpl("134", "32432", "fff", "fsfsfs", "mvmvmv", "C4SF5", "DE") + val fakeAddress2 = fakeAddress1.copy(line1 = "00000") + + val fakeBranch1 = BankBranchImpl(BankBranchId("branch1"), "Branch 1", fakeAddress1) + val fakeBranch2 = BankBranchImpl(BankBranchId("branch2"), "Branch 2", fakeAddress2) + + val fakeLicense = new DataLicense { + override def name: String = "sample-license" + override def url: String = "http://example.com/license" + } + + val mockConnector = new BankBranchesProvider { + override protected def branchData(bank: BankId): List[BankBranch] = { + bank match { + // have it return branches even for the bank without a license so we can test the connector does not return them + case BankWithLicense | BankWithoutLicense=> List(fakeBranch1, fakeBranch2) + case _ => Nil + } + } + + override protected def branchDataLicense(bank: BankId): Option[DataLicense] = { + bank match { + case BankWithLicense => Some(fakeLicense) + case _ => None + } + } + } + + def verifySameData(branch: BankBranch, branchJson : BranchJson) = { + branch.name should equal (branchJson.name) + branch.branchId should equal(BankBranchId(branchJson.id)) + branch.address.line1 should equal(branchJson.address.line_1) + branch.address.line2 should equal(branchJson.address.line_2) + branch.address.line3 should equal(branchJson.address.line_3) + branch.address.line4 should equal(branchJson.address.line_4) + branch.address.line5 should equal(branchJson.address.line_5) + branch.address.countryCode should equal(branchJson.address.country) + branch.address.postCode should equal(branchJson.address.postcode_zip) + } + + override def beforeAll() { + super.beforeAll() + //use the mock connector + BankBranches.bankBranchesProvider.default.set(mockConnector) + } + + override def afterAll() { + super.afterAll() + //reset the default connector + BankBranches.bankBranchesProvider.default.set(BankBranches.buildOne) + } + + feature("Getting bank branches") { + + scenario("We try to get bank branches for a bank without a data license for branch information") { + + When("We make a request") + val request = (v1_4Request / "banks" / BankWithoutLicense.value / "branches").GET + val response = makeGetRequest(request) + + Then("We should get a 404") + response.code should equal(404) + } + + scenario("We try to get bank branches for a bank with a data license for branch information") { + When("We make a request") + val request = (v1_4Request / "banks" / BankWithLicense.value / "branches").GET + val response = makeGetRequest(request) + + Then("We should get a 200") + response.code should equal(200) + + And("We should get the right json format") + val responseBodyOpt = response.body.extractOpt[BranchDataJson] + responseBodyOpt.isDefined should equal(true) + val responseBody = responseBodyOpt.get + + And("We should get the right license") + val license = responseBody.license + license.name should equal(fakeLicense.name) + license.url should equal(fakeLicense.url) + + And("We should get the right branches") + val branches = responseBody.branches + branches.size should equal(2) + val first = branches(0) + if(first.id == fakeBranch1.branchId.value) { + verifySameData(fakeBranch1, first) + verifySameData(fakeBranch2, branches(1)) + } else if (first.id == fakeBranch2.branchId.value) { + verifySameData(fakeBranch2, first) + verifySameData(fakeBranch1, branches(1)) + } else { + fail("incorrect branches") + } + + } + } + +} diff --git a/src/test/scala/code/bankbranches/MappedBankBranchesProviderTest.scala b/src/test/scala/code/bankbranches/MappedBankBranchesProviderTest.scala new file mode 100644 index 000000000..7b4be1455 --- /dev/null +++ b/src/test/scala/code/bankbranches/MappedBankBranchesProviderTest.scala @@ -0,0 +1,133 @@ +package code.bankbranches + +import code.api.test.ServerSetup +import code.model.BankId +import net.liftweb.mapper.By + +class MappedBankBranchesProviderTest extends ServerSetup { + + private def delete(): Unit = { + MappedBankBranch.bulkDelete_!!() + MappedDataLicense.bulkDelete_!!() + } + + override def beforeAll() = { + super.beforeAll() + delete() + } + + override def afterEach() = { + super.afterEach() + delete() + } + + def defaultSetup() = + new { + val bankIdWithLicenseAndData = "some-bank" + val bankIdWithNoLicense = "unlicensed-bank" + + val license = MappedDataLicense.create + .mBankId(bankIdWithLicenseAndData) + .mName("some-license") + .mUrl("http://www.example.com/license").saveMe() + + val unlicensedBranch = MappedBankBranch.create + .mBankId(bankIdWithNoLicense) + .mName("unlicensed") + .mBranchId("unlicensed") + .mCountryCode("es") + .mPostCode("4444") + .mLine1("a4") + .mLine2("b4") + .mLine3("c4") + .mLine4("d4") + .mLine5("e4").saveMe() + + val branch1 = MappedBankBranch.create + .mBankId(bankIdWithLicenseAndData) + .mName("branch 1") + .mBranchId("branch1") + .mCountryCode("de") + .mPostCode("123213213") + .mLine1("a") + .mLine2("b") + .mLine3("c") + .mLine4("d") + .mLine5("e").saveMe() + + val branch2 = MappedBankBranch.create + .mBankId(bankIdWithLicenseAndData) + .mName("branch 2") + .mBranchId("branch2") + .mCountryCode("fr") + .mPostCode("898989") + .mLine1("a2") + .mLine2("b2") + .mLine3("c2") + .mLine4("d2") + .mLine5("e2").saveMe() + } + + feature("MappedBankBranchesProvider") { + + scenario("We try to get branch data for a bank which does not have a data license set") { + val fixture = defaultSetup() + + Given("The bank in question has no data license") + MappedDataLicense.count(By(MappedDataLicense.mBankId, fixture.bankIdWithNoLicense)) should equal(0) + + And("The bank in question has branches") + MappedBankBranch.find(By(MappedBankBranch.mBankId, fixture.bankIdWithNoLicense)).isDefined should equal(true) + + When("We try to get the branch data for that bank") + val branchData = MappedBankBranchesProvider.getBranches(BankId(fixture.bankIdWithNoLicense)) + + Then("We should get an empty option") + branchData should equal(None) + + } + + scenario("We try to get branch data for a bank which does have a data license set") { + val fixture = defaultSetup() + val expectedBranches = Set(fixture.branch1, fixture.branch2) + Given("We have a data license and branches for a bank") + MappedDataLicense.count(By(MappedDataLicense.mBankId, fixture.bankIdWithLicenseAndData)) should equal(1) + MappedBankBranch.findAll(By(MappedBankBranch.mBankId, fixture.bankIdWithLicenseAndData)).toSet should equal(expectedBranches) + + When("We try to get the branch data for that bank") + val branchDataOpt = MappedBankBranchesProvider.getBranches(BankId(fixture.bankIdWithLicenseAndData)) + + Then("We should get back the data license and the branches") + branchDataOpt.isDefined should equal(true) + val branchData = branchDataOpt.get + + branchData.license should equal(fixture.license) + branchData.branches.toSet should equal(expectedBranches) + } + + scenario("We try to get branch data for a bank with a data license, but no branches") { + + Given("We have a data license for a bank, but no branches") + + val bankWithNoBranches = "bank-with-no-branches" + val license = MappedDataLicense.create + .mBankId(bankWithNoBranches) + .mName("some-license") + .mUrl("http://www.example.com/license").saveMe() + + MappedBankBranch.find(By(MappedBankBranch.mBankId, bankWithNoBranches)).isDefined should equal(false) + + When("We try to get the branch data for that bank") + val branchDataOpt = MappedBankBranchesProvider.getBranches(BankId(bankWithNoBranches)) + + Then("We should get back the data license, and a list branches of size 0") + branchDataOpt.isDefined should equal(true) + val branchData = branchDataOpt.get + + branchData.license should equal(license) + branchData.branches should equal(Nil) + + } + + } +} From 4696a77f36129b6df550ffa6b3bfefb50134d839 Mon Sep 17 00:00:00 2001 From: Everett Sochowski Date: Fri, 13 Feb 2015 15:47:39 +0100 Subject: [PATCH 09/17] Refactoring --- .../BankAccountCreationDispatcher.scala | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/main/scala/code/model/dataAccess/BankAccountCreationDispatcher.scala b/src/main/scala/code/model/dataAccess/BankAccountCreationDispatcher.scala index 44c108a27..3db4fa5f0 100644 --- a/src/main/scala/code/model/dataAccess/BankAccountCreationDispatcher.scala +++ b/src/main/scala/code/model/dataAccess/BankAccountCreationDispatcher.scala @@ -55,7 +55,8 @@ package code.model.dataAccess { import java.util.UUID -import code.model.{AccountId, BankId} +import code.model.{User, AccountId, BankId} +import code.users.Users import com.rabbitmq.client.{ConnectionFactory,Channel} import net.liftmodules.amqp.{ AMQPDispatcher, @@ -114,7 +115,7 @@ import scala.util.Random } } - private def createAccount(bankAccountNumber: BankAccountNumber, accountId : AccountId, bank : HostedBank, u: APIUser) : Account = { + private def createAccount(bankAccountNumber: BankAccountNumber, accountId : AccountId, bank : HostedBank, u: User) : Account = { //TODO: fill these fields using the HBCI library. import net.liftweb.mongodb.BsonDSL._ Account.find( @@ -148,11 +149,11 @@ import scala.util.Random } } - def createAccount(bankAccountNumber: BankAccountNumber, bank: HostedBank, u: APIUser): Account = { + def createAccount(bankAccountNumber: BankAccountNumber, bank: HostedBank, u: User): Account = { createAccount(bankAccountNumber, AccountId(UUID.randomUUID().toString), bank, u) } - def createAccount(accountId: AccountId, bank: HostedBank, u: APIUser): Account = { + def createAccount(accountId: AccountId, bank: HostedBank, u: User): Account = { import net.liftweb.mongodb.BsonDSL._ val uniqueAccountNumber = { def exists(number : String) = Account.count((Account.accountNumber.name -> number) ~ (Account.bankID.name -> bank.id.get)) > 0 @@ -173,20 +174,20 @@ import scala.util.Random }, accountId, bank, u) } - def setAsOwner(bankId : BankId, accountId : AccountId, user: APIUser): Unit = { + def setAsOwner(bankId : BankId, accountId : AccountId, user: User): Unit = { createOwnerView(bankId, accountId, user) setAsAccountOwner(bankId, accountId, user) } - private def setAsAccountOwner(bankId : BankId, accountId : AccountId, user : APIUser) : Unit = { + private def setAsAccountOwner(bankId : BankId, accountId : AccountId, user : User) : Unit = { MappedAccountHolder.create .accountBankPermalink(bankId.value) .accountPermalink(accountId.value) - .user(user) + .user(user.apiId.value) .save } - private def createOwnerView(bankId : BankId, accountId : AccountId, user: APIUser): Unit = { + private def createOwnerView(bankId : BankId, accountId : AccountId, user: User): Unit = { val existingOwnerView = ViewImpl.find( By(ViewImpl.permalink_, "owner") :: @@ -195,16 +196,16 @@ import scala.util.Random existingOwnerView match { case Full(v) => { logger.info(s"account $accountId at bank $bankId has already an owner view") - v.users_.toList.find(_.id == user.id) match { + v.users_.toList.find(_.id == user.apiId.value) match { case Some(u) => { - logger.info(s"user ${user.email.get} has already an owner view access on account $accountId at bank $bankId") + logger.info(s"user ${user.emailAddress} has already an owner view access on account $accountId at bank $bankId") } case _ =>{ //TODO: When can this case occur? - logger.info(s"creating owner view access to user ${user.email.get}") + logger.info(s"creating owner view access to user ${user.emailAddress}") ViewPrivileges .create - .user(user) + .user(user.apiId.value) .view(v) .save } @@ -216,10 +217,10 @@ import scala.util.Random logger.info(s"creating owner view on account account $accountId at bank $bankId") val view = ViewImpl.createAndSaveOwnerView(bankId, accountId, "") - logger.info(s"creating owner view access to user ${user.email.get}") + logger.info(s"creating owner view access to user ${user.emailAddress}") ViewPrivileges .create - .user(user) + .user(user.apiId.value) .view(view) .save } @@ -246,10 +247,8 @@ import scala.util.Random case msg@AMQPMessage(message: CreateBankAccount) => { logger.info(s"got message to create account/bank: ${message.accountNumber} / ${message.bankIdentifier}") - APIUser.find( - By(APIUser.provider_, message.accountOwnerProvider), - By(APIUser.providerId, message.accountOwnerId) - ).map{ user => { + val foundUser = Users.users.vend.getUserByProviderId(message.accountOwnerProvider, message.accountOwnerId) + foundUser.map{ user => { logger.info("user found for owner view") val bank: HostedBank = BankAccountCreation.createBank(message) From c1695a36bfd6ed624880b4e69511d7e836f1677a Mon Sep 17 00:00:00 2001 From: Everett Sochowski Date: Sat, 14 Feb 2015 15:31:30 +0100 Subject: [PATCH 10/17] More refactoring --- src/test/scala/code/api/DefaultConnectorTestSetup.scala | 4 ++++ src/test/scala/code/api/ServerSetup.scala | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 src/test/scala/code/api/DefaultConnectorTestSetup.scala diff --git a/src/test/scala/code/api/DefaultConnectorTestSetup.scala b/src/test/scala/code/api/DefaultConnectorTestSetup.scala new file mode 100644 index 000000000..5aa2d48b3 --- /dev/null +++ b/src/test/scala/code/api/DefaultConnectorTestSetup.scala @@ -0,0 +1,4 @@ +package code.api + +//Set the default connector setup here by extending it +trait DefaultConnectorTestSetup extends LocalConnectorTestSetup \ No newline at end of file diff --git a/src/test/scala/code/api/ServerSetup.scala b/src/test/scala/code/api/ServerSetup.scala index 39f16a84c..ff9431fad 100644 --- a/src/test/scala/code/api/ServerSetup.scala +++ b/src/test/scala/code/api/ServerSetup.scala @@ -33,7 +33,7 @@ Berlin 13359, Germany package code.api.test import code.TestServer -import code.api.{TestConnectorSetup, LocalConnectorTestSetup} +import code.api.{DefaultConnectorTestSetup, TestConnectorSetup, LocalConnectorTestSetup} import org.scalatest._ import dispatch._ import net.liftweb.json.{Serialization, NoTypeHints} @@ -51,7 +51,7 @@ trait ServerSetup extends FeatureSpec with SendServerRequests } -trait ServerSetupWithTestData extends ServerSetup with LocalConnectorTestSetup { +trait ServerSetupWithTestData extends ServerSetup with DefaultConnectorTestSetup { override def beforeEach() = { super.beforeEach() From 33afae1c7891cd392f6a0179b7cc6e0aebc96544 Mon Sep 17 00:00:00 2001 From: Everett Sochowski Date: Sun, 15 Feb 2015 13:22:07 +0100 Subject: [PATCH 11/17] Fix bank account creation issue with incorrect account ids --- .../BankAccountCreationDispatcher.scala | 2 +- .../BankAccountCreationListenerTest.scala | 111 ++++++++++++++---- 2 files changed, 86 insertions(+), 27 deletions(-) diff --git a/src/main/scala/code/model/dataAccess/BankAccountCreationDispatcher.scala b/src/main/scala/code/model/dataAccess/BankAccountCreationDispatcher.scala index 3db4fa5f0..1618d6dec 100644 --- a/src/main/scala/code/model/dataAccess/BankAccountCreationDispatcher.scala +++ b/src/main/scala/code/model/dataAccess/BankAccountCreationDispatcher.scala @@ -253,7 +253,7 @@ import scala.util.Random val bank: HostedBank = BankAccountCreation.createBank(message) val bankAccount = BankAccountCreation.createAccount(message, bank, user) - BankAccountCreation.setAsOwner(BankId(bank.permalink.get), AccountId(message.accountNumber), user) + BankAccountCreation.setAsOwner(BankId(bank.permalink.get), bankAccount.accountId, user) logger.info(s"created account ${message.accountNumber} at ${message.bankIdentifier}") diff --git a/src/test/scala/code/bankaccountcreation/BankAccountCreationListenerTest.scala b/src/test/scala/code/bankaccountcreation/BankAccountCreationListenerTest.scala index 7156706d2..45302212c 100644 --- a/src/test/scala/code/bankaccountcreation/BankAccountCreationListenerTest.scala +++ b/src/test/scala/code/bankaccountcreation/BankAccountCreationListenerTest.scala @@ -1,49 +1,108 @@ package code.bankaccountcreation +import code.api.DefaultConnectorTestSetup import code.api.test.ServerSetup -import code.model.{Consumer => OBPConsumer, Token => OBPToken, AccountId, BankId} +import code.model.BankId +import code.views.Views +import net.liftweb.common.Full import org.scalatest.Tag import com.tesobe.model.CreateBankAccount -import code.model.dataAccess.{HostedBank, APIUser, BankAccountCreationListener} +import code.model.dataAccess.{APIUser, BankAccountCreationListener} import net.liftmodules.amqp.AMQPMessage -import net.liftweb.mapper.By import code.bankconnectors.Connector -class BankAccountCreationListenerTest extends ServerSetup { +class BankAccountCreationListenerTest extends ServerSetup with DefaultConnectorTestSetup { - object AccountHolderSetup extends Tag("account_holder_setup") + object BankAccountCreationListenerTag extends Tag("bank_account_creation_listener") - feature("The account holder gets properly set when a bank account is created"){ - scenario("a bank account is created", AccountHolderSetup) { + override def beforeEach() = { + super.beforeEach() + wipeTestData() + } + + override def afterEach() = { + super.afterEach() + wipeTestData() + } + + feature("Bank account creation via AMQP messages"){ + + val userId = "foo" + val userProvider = "bar" + + //need to create the user for the bank accout creation process to work + val user = + APIUser.create. + provider_(userProvider). + providerId(userId). + saveMe + + val expectedBankId = "quxbank" + val accountNumber = "123456" + + def thenCheckAccountCreated() = { + Then("An account with the proper parameters should be created") + val userAccounts = Views.views.vend.getAllAccountsUserCanSee(Full(user)) + userAccounts.size should equal(1) + val createdAccount = userAccounts(0) + + //the account id should be randomly generated + createdAccount.accountId.value.nonEmpty should be(true) + + createdAccount.bankId.value should equal(expectedBankId) + createdAccount.number should equal(accountNumber) + + And("The account holder should be set correctly") + Connector.connector.vend.getAccountHolders(BankId(expectedBankId), createdAccount.accountId) should equal(Set(user)) + } + + + scenario("a bank account is created at a bank that does not yet exist", BankAccountCreationListenerTag) { + val bankIdentifier = "qux" + + Given("The account doesn't already exist") + Views.views.vend.getAllAccountsUserCanSee(Full(user)).size should equal(0) + + And("The bank in question doesn't already exist") + Connector.connector.vend.getBank(BankId(expectedBankId)).isDefined should equal(false) When("We create a bank account") - val userId = "foo" - val userProvider = "bar" - val accountNumber = "123456" - val expectedAccountId = AccountId(accountNumber) - val bankIdentifier = "qux" - val expectedBankId = "quxbank" - - //need to create the user for the bank accout creation process to work - val user = - APIUser.create. - provider_(userProvider). - providerId(userId). - saveMe + //using expectedBankId as the bank name should be okay as the behaviour should be to slugify the bank name to get the id + //what to do if this slugification results in an id collision has not been determined yet val msgContent = CreateBankAccount(userId, userProvider, accountNumber, bankIdentifier, expectedBankId) - - //before the bank account is created, it should obviously have no holders - Connector.connector.vend.getAccountHolders(BankId(expectedBankId), expectedAccountId) should equal (Set.empty) - BankAccountCreationListener.createBankAccountListener ! AMQPMessage(msgContent) //sleep to give the actor time to process the message Thread.sleep(5000) - Then("The should be considered the account holder") - Connector.connector.vend.getAccountHolders(BankId(expectedBankId), expectedAccountId) should equal(Set(user)) + thenCheckAccountCreated() + + And("A bank should be created") + val createdBankBox = Connector.connector.vend.getBank(BankId(expectedBankId)) + createdBankBox.isDefined should equal(true) + val createdBank = createdBankBox.get + createdBank.nationalIdentifier should equal(bankIdentifier) + + } + + scenario("a bank account is created at a bank that already exists", BankAccountCreationListenerTag) { + Given("The account doesn't already exist") + Views.views.vend.getAllAccountsUserCanSee(Full(user)).size should equal(0) + + And("The bank in question already exists") + val createdBank = createBank(expectedBankId) + + When("We create a bank account") + val msgContent = CreateBankAccount(userId, userProvider, accountNumber, createdBank.nationalIdentifier, createdBank.bankId.value) + + BankAccountCreationListener.createBankAccountListener ! AMQPMessage(msgContent) + + //sleep to give the actor time to process the message + Thread.sleep(5000) + + thenCheckAccountCreated() } } From cf5af1fe6692b4d75e2bcd5f5fb261e6966b5900 Mon Sep 17 00:00:00 2001 From: Everett Sochowski Date: Sun, 15 Feb 2015 13:26:02 +0100 Subject: [PATCH 12/17] Update log message --- .../code/model/dataAccess/BankAccountCreationDispatcher.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/code/model/dataAccess/BankAccountCreationDispatcher.scala b/src/main/scala/code/model/dataAccess/BankAccountCreationDispatcher.scala index 1618d6dec..12698a112 100644 --- a/src/main/scala/code/model/dataAccess/BankAccountCreationDispatcher.scala +++ b/src/main/scala/code/model/dataAccess/BankAccountCreationDispatcher.scala @@ -255,7 +255,7 @@ import scala.util.Random val bankAccount = BankAccountCreation.createAccount(message, bank, user) BankAccountCreation.setAsOwner(BankId(bank.permalink.get), bankAccount.accountId, user) - logger.info(s"created account ${message.accountNumber} at ${message.bankIdentifier}") + logger.info(s"created account with id ${bankAccount.bankId.value} with number ${bankAccount.accountNumber} at bank with identifier ${message.bankIdentifier}") logger.info(s"Send message to get updates for the account ${message.accountNumber} at ${message.bankIdentifier}") UpdatesRequestSender.sendMsg(UpdateBankAccount(message.accountNumber, message.bankIdentifier)) From e2fe0179833bf19bed19ac6607470eb816140d8e Mon Sep 17 00:00:00 2001 From: Everett Sochowski Date: Sun, 15 Feb 2015 13:32:58 +0100 Subject: [PATCH 13/17] Use trait field in log message --- .../code/model/dataAccess/BankAccountCreationDispatcher.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/code/model/dataAccess/BankAccountCreationDispatcher.scala b/src/main/scala/code/model/dataAccess/BankAccountCreationDispatcher.scala index 12698a112..7c114457f 100644 --- a/src/main/scala/code/model/dataAccess/BankAccountCreationDispatcher.scala +++ b/src/main/scala/code/model/dataAccess/BankAccountCreationDispatcher.scala @@ -255,7 +255,7 @@ import scala.util.Random val bankAccount = BankAccountCreation.createAccount(message, bank, user) BankAccountCreation.setAsOwner(BankId(bank.permalink.get), bankAccount.accountId, user) - logger.info(s"created account with id ${bankAccount.bankId.value} with number ${bankAccount.accountNumber} at bank with identifier ${message.bankIdentifier}") + logger.info(s"created account with id ${bankAccount.bankId.value} with number ${bankAccount.number} at bank with identifier ${message.bankIdentifier}") logger.info(s"Send message to get updates for the account ${message.accountNumber} at ${message.bankIdentifier}") UpdatesRequestSender.sendMsg(UpdateBankAccount(message.accountNumber, message.bankIdentifier)) From fc401a2743cf90cce8cd50822b6ae98ef708b6dd Mon Sep 17 00:00:00 2001 From: Everett Sochowski Date: Sun, 15 Feb 2015 16:59:04 +0100 Subject: [PATCH 14/17] Fix account creations tests to take care of user setup --- .../BankAccountCreationListenerTest.scala | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/test/scala/code/bankaccountcreation/BankAccountCreationListenerTest.scala b/src/test/scala/code/bankaccountcreation/BankAccountCreationListenerTest.scala index 45302212c..434c1ff76 100644 --- a/src/test/scala/code/bankaccountcreation/BankAccountCreationListenerTest.scala +++ b/src/test/scala/code/bankaccountcreation/BankAccountCreationListenerTest.scala @@ -2,9 +2,10 @@ package code.bankaccountcreation import code.api.DefaultConnectorTestSetup import code.api.test.ServerSetup -import code.model.BankId +import code.model.{User, BankId} import code.views.Views import net.liftweb.common.Full +import net.liftweb.mapper.By import org.scalatest.Tag import com.tesobe.model.CreateBankAccount import code.model.dataAccess.{APIUser, BankAccountCreationListener} @@ -31,16 +32,18 @@ class BankAccountCreationListenerTest extends ServerSetup with DefaultConnectorT val userProvider = "bar" //need to create the user for the bank accout creation process to work - val user = - APIUser.create. - provider_(userProvider). - providerId(userId). - saveMe + def getTestUser() = + APIUser.find(By(APIUser.provider_, userProvider), By(APIUser.providerId, userId)).getOrElse{ + APIUser.create. + provider_(userProvider). + providerId(userId). + saveMe + } val expectedBankId = "quxbank" val accountNumber = "123456" - def thenCheckAccountCreated() = { + def thenCheckAccountCreated(user : User) = { Then("An account with the proper parameters should be created") val userAccounts = Views.views.vend.getAllAccountsUserCanSee(Full(user)) userAccounts.size should equal(1) @@ -59,6 +62,7 @@ class BankAccountCreationListenerTest extends ServerSetup with DefaultConnectorT scenario("a bank account is created at a bank that does not yet exist", BankAccountCreationListenerTag) { val bankIdentifier = "qux" + val user = getTestUser() Given("The account doesn't already exist") Views.views.vend.getAllAccountsUserCanSee(Full(user)).size should equal(0) @@ -77,7 +81,7 @@ class BankAccountCreationListenerTest extends ServerSetup with DefaultConnectorT //sleep to give the actor time to process the message Thread.sleep(5000) - thenCheckAccountCreated() + thenCheckAccountCreated(user) And("A bank should be created") val createdBankBox = Connector.connector.vend.getBank(BankId(expectedBankId)) @@ -88,6 +92,7 @@ class BankAccountCreationListenerTest extends ServerSetup with DefaultConnectorT } scenario("a bank account is created at a bank that already exists", BankAccountCreationListenerTag) { + val user = getTestUser() Given("The account doesn't already exist") Views.views.vend.getAllAccountsUserCanSee(Full(user)).size should equal(0) @@ -102,7 +107,7 @@ class BankAccountCreationListenerTest extends ServerSetup with DefaultConnectorT //sleep to give the actor time to process the message Thread.sleep(5000) - thenCheckAccountCreated() + thenCheckAccountCreated(user) } } From 7c8bf3f8417e8caba94795682715dd545c386207 Mon Sep 17 00:00:00 2001 From: Everett Sochowski Date: Wed, 18 Feb 2015 12:40:23 +0100 Subject: [PATCH 15/17] Improve log message --- .../code/model/dataAccess/BankAccountCreationDispatcher.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/scala/code/model/dataAccess/BankAccountCreationDispatcher.scala b/src/main/scala/code/model/dataAccess/BankAccountCreationDispatcher.scala index 7c114457f..a10efbaa1 100644 --- a/src/main/scala/code/model/dataAccess/BankAccountCreationDispatcher.scala +++ b/src/main/scala/code/model/dataAccess/BankAccountCreationDispatcher.scala @@ -257,7 +257,8 @@ import scala.util.Random logger.info(s"created account with id ${bankAccount.bankId.value} with number ${bankAccount.number} at bank with identifier ${message.bankIdentifier}") - logger.info(s"Send message to get updates for the account ${message.accountNumber} at ${message.bankIdentifier}") + logger.info(s"Send message to get updates for the account with id ${bankAccount.bankId.value}" + + s" with number ${bankAccount.number} at bank with identifier ${message.bankIdentifier}") UpdatesRequestSender.sendMsg(UpdateBankAccount(message.accountNumber, message.bankIdentifier)) } }.getOrElse( From a8afd4d5c6884a28b087c32b6c42c5e1597f1a28 Mon Sep 17 00:00:00 2001 From: Everett Sochowski Date: Wed, 18 Feb 2015 12:43:45 +0100 Subject: [PATCH 16/17] Fix log message --- .../code/model/dataAccess/BankAccountCreationDispatcher.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/code/model/dataAccess/BankAccountCreationDispatcher.scala b/src/main/scala/code/model/dataAccess/BankAccountCreationDispatcher.scala index a10efbaa1..1ca1ca72d 100644 --- a/src/main/scala/code/model/dataAccess/BankAccountCreationDispatcher.scala +++ b/src/main/scala/code/model/dataAccess/BankAccountCreationDispatcher.scala @@ -257,7 +257,7 @@ import scala.util.Random logger.info(s"created account with id ${bankAccount.bankId.value} with number ${bankAccount.number} at bank with identifier ${message.bankIdentifier}") - logger.info(s"Send message to get updates for the account with id ${bankAccount.bankId.value}" + + logger.info(s"Send message to get updates for the account with id ${bankAccount.accountId.value}" + s" with number ${bankAccount.number} at bank with identifier ${message.bankIdentifier}") UpdatesRequestSender.sendMsg(UpdateBankAccount(message.accountNumber, message.bankIdentifier)) } From 92fcfedc881184aa7873ae44e8491ec57162ad8e Mon Sep 17 00:00:00 2001 From: Everett Sochowski Date: Thu, 19 Feb 2015 12:20:38 +0100 Subject: [PATCH 17/17] Use standard api json format everywhere in the tests --- src/test/scala/code/api/ServerSetup.scala | 5 ++--- src/test/scala/code/api/v1_4_0/CustomerInfoTest.scala | 2 -- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/test/scala/code/api/ServerSetup.scala b/src/test/scala/code/api/ServerSetup.scala index ff9431fad..5fd01cecc 100644 --- a/src/test/scala/code/api/ServerSetup.scala +++ b/src/test/scala/code/api/ServerSetup.scala @@ -36,7 +36,7 @@ import code.TestServer import code.api.{DefaultConnectorTestSetup, TestConnectorSetup, LocalConnectorTestSetup} import org.scalatest._ import dispatch._ -import net.liftweb.json.{Serialization, NoTypeHints} +import net.liftweb.json.{DefaultFormats, Serialization, NoTypeHints} import net.liftweb.common._ trait ServerSetup extends FeatureSpec with SendServerRequests @@ -45,7 +45,7 @@ trait ServerSetup extends FeatureSpec with SendServerRequests with ShouldMatchers with Loggable { var server = TestServer - implicit val formats = Serialization.formats(NoTypeHints) + implicit val formats = DefaultFormats val h = Http def baseRequest = host(server.host, server.port) @@ -56,7 +56,6 @@ trait ServerSetupWithTestData extends ServerSetup with DefaultConnectorTestSetup override def beforeEach() = { super.beforeEach() - implicit val dateFormats = net.liftweb.json.DefaultFormats //create fake data for the tests //fake banks diff --git a/src/test/scala/code/api/v1_4_0/CustomerInfoTest.scala b/src/test/scala/code/api/v1_4_0/CustomerInfoTest.scala index 6b8ccedaa..c3acee25e 100644 --- a/src/test/scala/code/api/v1_4_0/CustomerInfoTest.scala +++ b/src/test/scala/code/api/v1_4_0/CustomerInfoTest.scala @@ -92,8 +92,6 @@ class CustomerInfoTest extends V140ServerSetup with DefaultUsers { And("We should get the right information back") - implicit val format = APIUtil.formats - val info = response.body.extract[CustomerInfoJson] val received = MockCustomerInfo(info.customer_number, info.mobile_phone_number, info.legal_name, info.email, MockFaceImage(info.face_image.date, info.face_image.url))