Browse Source

Server: fix the getLatestTransactionBy method

when sending an empty list
added the tests for sending an empty list and retrieving valid data
prometheus-integration
Adinael Perez Ruelas 6 years ago
committed by Alexis Hernandez
parent
commit
bcdbf1cd0f
  1. 3
      server/app/com/xsn/explorer/data/TransactionDataHandler.scala
  2. 5
      server/app/com/xsn/explorer/data/anorm/TransactionPostgresDataHandler.scala
  3. 7
      server/app/com/xsn/explorer/data/anorm/dao/TransactionPostgresDAO.scala
  4. 4
      server/app/com/xsn/explorer/data/async/TransactionFutureDataHandler.scala
  5. 5
      server/app/com/xsn/explorer/services/TransactionService.scala
  6. 7
      server/app/controllers/TransactionsController.scala
  7. 19
      server/app/controllers/common/Codecs.scala
  8. 4
      server/test/com/xsn/explorer/data/TransactionPostgresDataHandlerSpec.scala
  9. 3
      server/test/com/xsn/explorer/helpers/TransactionDummyDataHandler.scala
  10. 66
      server/test/controllers/TransactionsControllerSpec.scala

3
server/app/com/xsn/explorer/data/TransactionDataHandler.scala

@ -4,6 +4,7 @@ import com.alexitc.playsonify.core.ApplicationResult
import com.alexitc.playsonify.models.{FieldOrdering, PaginatedQuery, PaginatedResult} import com.alexitc.playsonify.models.{FieldOrdering, PaginatedQuery, PaginatedResult}
import com.xsn.explorer.models._ import com.xsn.explorer.models._
import com.xsn.explorer.models.fields.TransactionField import com.xsn.explorer.models.fields.TransactionField
import org.scalactic.Every
import scala.language.higherKinds import scala.language.higherKinds
@ -21,7 +22,7 @@ trait TransactionDataHandler[F[_]] {
paginatedQuery: PaginatedQuery, paginatedQuery: PaginatedQuery,
ordering: FieldOrdering[TransactionField]): F[PaginatedResult[TransactionWithValues]] ordering: FieldOrdering[TransactionField]): F[PaginatedResult[TransactionWithValues]]
def getLatestTransactionBy(addresses: List[Address]): F[Map[String, String]] def getLatestTransactionBy(addresses: Every[Address]): F[Map[String, String]]
} }
trait TransactionBlockingDataHandler extends TransactionDataHandler[ApplicationResult] trait TransactionBlockingDataHandler extends TransactionDataHandler[ApplicationResult]

5
server/app/com/xsn/explorer/data/anorm/TransactionPostgresDataHandler.scala

@ -1,14 +1,13 @@
package com.xsn.explorer.data.anorm package com.xsn.explorer.data.anorm
import javax.inject.Inject import javax.inject.Inject
import com.alexitc.playsonify.core.ApplicationResult import com.alexitc.playsonify.core.ApplicationResult
import com.alexitc.playsonify.models.{FieldOrdering, PaginatedQuery, PaginatedResult} import com.alexitc.playsonify.models.{FieldOrdering, PaginatedQuery, PaginatedResult}
import com.xsn.explorer.data.TransactionBlockingDataHandler import com.xsn.explorer.data.TransactionBlockingDataHandler
import com.xsn.explorer.data.anorm.dao.TransactionPostgresDAO import com.xsn.explorer.data.anorm.dao.TransactionPostgresDAO
import com.xsn.explorer.models._ import com.xsn.explorer.models._
import com.xsn.explorer.models.fields.TransactionField import com.xsn.explorer.models.fields.TransactionField
import org.scalactic.Good import org.scalactic.{Every, Good}
import play.api.db.Database import play.api.db.Database
class TransactionPostgresDataHandler @Inject() ( class TransactionPostgresDataHandler @Inject() (
@ -46,7 +45,7 @@ class TransactionPostgresDataHandler @Inject() (
Good(result) Good(result)
} }
def getLatestTransactionBy(addresses: List[Address]): ApplicationResult[Map[String, String]] = withConnection { implicit conn => def getLatestTransactionBy(addresses: Every[Address]): ApplicationResult[Map[String, String]] = withConnection { implicit conn =>
val result = transactionPostgresDAO.getLatestTransactionBy(addresses) val result = transactionPostgresDAO.getLatestTransactionBy(addresses)
Good(result) Good(result)

7
server/app/com/xsn/explorer/data/anorm/dao/TransactionPostgresDAO.scala

@ -1,14 +1,15 @@
package com.xsn.explorer.data.anorm.dao package com.xsn.explorer.data.anorm.dao
import java.sql.Connection import java.sql.Connection
import javax.inject.Inject
import javax.inject.Inject
import anorm._ import anorm._
import com.alexitc.playsonify.models.{Count, FieldOrdering, PaginatedQuery} import com.alexitc.playsonify.models.{Count, FieldOrdering, PaginatedQuery}
import com.xsn.explorer.data.anorm.interpreters.FieldOrderingSQLInterpreter import com.xsn.explorer.data.anorm.interpreters.FieldOrderingSQLInterpreter
import com.xsn.explorer.data.anorm.parsers.TransactionParsers._ import com.xsn.explorer.data.anorm.parsers.TransactionParsers._
import com.xsn.explorer.models._ import com.xsn.explorer.models._
import com.xsn.explorer.models.fields.TransactionField import com.xsn.explorer.models.fields.TransactionField
import org.scalactic.Every
class TransactionPostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderingSQLInterpreter) { class TransactionPostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderingSQLInterpreter) {
@ -184,7 +185,7 @@ class TransactionPostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderi
).as(parseTransactionOutput.*).flatten ).as(parseTransactionOutput.*).flatten
} }
def getLatestTransactionBy(addresses: List[Address])(implicit conn: Connection): Map[String, String] = { def getLatestTransactionBy(addresses: Every[Address])(implicit conn: Connection): Map[String, String] = {
import SqlParser._ import SqlParser._
@ -202,7 +203,7 @@ class TransactionPostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderi
|HAVING address IN ({addresses}); |HAVING address IN ({addresses});
""".stripMargin """.stripMargin
).on( ).on(
'addresses -> addresses.map(_.string) 'addresses -> addresses.map(_.string).toList
).as((str("address") ~ str("txid")).map(flatten).*) ).as((str("address") ~ str("txid")).map(flatten).*)
result.toMap result.toMap

4
server/app/com/xsn/explorer/data/async/TransactionFutureDataHandler.scala

@ -1,13 +1,13 @@
package com.xsn.explorer.data.async package com.xsn.explorer.data.async
import javax.inject.Inject import javax.inject.Inject
import com.alexitc.playsonify.core.{FutureApplicationResult, FuturePaginatedResult} import com.alexitc.playsonify.core.{FutureApplicationResult, FuturePaginatedResult}
import com.alexitc.playsonify.models.{FieldOrdering, PaginatedQuery, PaginatedResult} import com.alexitc.playsonify.models.{FieldOrdering, PaginatedQuery, PaginatedResult}
import com.xsn.explorer.data.{TransactionBlockingDataHandler, TransactionDataHandler} import com.xsn.explorer.data.{TransactionBlockingDataHandler, TransactionDataHandler}
import com.xsn.explorer.executors.DatabaseExecutionContext import com.xsn.explorer.executors.DatabaseExecutionContext
import com.xsn.explorer.models._ import com.xsn.explorer.models._
import com.xsn.explorer.models.fields.TransactionField import com.xsn.explorer.models.fields.TransactionField
import org.scalactic.Every
import scala.concurrent.Future import scala.concurrent.Future
@ -36,7 +36,7 @@ class TransactionFutureDataHandler @Inject() (
blockingDataHandler.getByBlockhash(blockhash, paginatedQuery, ordering) blockingDataHandler.getByBlockhash(blockhash, paginatedQuery, ordering)
} }
override def getLatestTransactionBy(addresses: List[Address]): FutureApplicationResult[Map[String, String]] = Future { override def getLatestTransactionBy(addresses: Every[Address]): FutureApplicationResult[Map[String, String]] = Future {
blockingDataHandler.getLatestTransactionBy(addresses) blockingDataHandler.getLatestTransactionBy(addresses)
} }
} }

5
server/app/com/xsn/explorer/services/TransactionService.scala

@ -1,7 +1,6 @@
package com.xsn.explorer.services package com.xsn.explorer.services
import javax.inject.Inject import javax.inject.Inject
import com.alexitc.playsonify.core.FutureOr.Implicits.{FutureListOps, FutureOps, OrOps} import com.alexitc.playsonify.core.FutureOr.Implicits.{FutureListOps, FutureOps, OrOps}
import com.alexitc.playsonify.core.{FutureApplicationResult, FuturePaginatedResult} import com.alexitc.playsonify.core.{FutureApplicationResult, FuturePaginatedResult}
import com.alexitc.playsonify.models.{OrderingQuery, PaginatedQuery} import com.alexitc.playsonify.models.{OrderingQuery, PaginatedQuery}
@ -11,7 +10,7 @@ import com.xsn.explorer.errors._
import com.xsn.explorer.models._ import com.xsn.explorer.models._
import com.xsn.explorer.models.rpc.TransactionVIN import com.xsn.explorer.models.rpc.TransactionVIN
import com.xsn.explorer.parsers.TransactionOrderingParser import com.xsn.explorer.parsers.TransactionOrderingParser
import org.scalactic.{Bad, Good, One, Or} import org.scalactic._
import play.api.libs.json.{JsObject, JsString, JsValue} import play.api.libs.json.{JsObject, JsString, JsValue}
import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.{ExecutionContext, Future}
@ -115,7 +114,7 @@ class TransactionService @Inject() (
result.toFuture result.toFuture
} }
def getLatestTransactionBy(addresses: List[Address]): FutureApplicationResult[Map[String, String]] = { def getLatestTransactionBy(addresses: Every[Address]): FutureApplicationResult[Map[String, String]] = {
transactionFutureDataHandler.getLatestTransactionBy(addresses) transactionFutureDataHandler.getLatestTransactionBy(addresses)
} }

7
server/app/controllers/TransactionsController.scala

@ -1,18 +1,19 @@
package controllers package controllers
import javax.inject.Inject import javax.inject.Inject
import com.alexitc.playsonify.models.PublicContextWithModel import com.alexitc.playsonify.models.PublicContextWithModel
import com.xsn.explorer.models.Address import com.xsn.explorer.models.Address
import com.xsn.explorer.models.request.SendRawTransactionRequest import com.xsn.explorer.models.request.SendRawTransactionRequest
import com.xsn.explorer.services.TransactionService import com.xsn.explorer.services.TransactionService
import controllers.common.{MyJsonController, MyJsonControllerComponents} import controllers.common.{Codecs,MyJsonController, MyJsonControllerComponents}
import org.scalactic.Every
class TransactionsController @Inject() ( class TransactionsController @Inject() (
transactionService: TransactionService, transactionService: TransactionService,
cc: MyJsonControllerComponents) cc: MyJsonControllerComponents)
extends MyJsonController(cc) { extends MyJsonController(cc) {
import Codecs._
def getTransaction(txid: String) = publicNoInput { _ => def getTransaction(txid: String) = publicNoInput { _ =>
transactionService.getTransactionDetails(txid) transactionService.getTransactionDetails(txid)
} }
@ -25,7 +26,7 @@ class TransactionsController @Inject() (
transactionService.sendRawTransaction(ctx.model.hex) transactionService.sendRawTransaction(ctx.model.hex)
} }
def getLatestByAddresses() = publicWithInput { ctx: PublicContextWithModel[List[Address]] => def getLatestByAddresses() = publicWithInput { ctx: PublicContextWithModel[Every[Address]] =>
transactionService.getLatestTransactionBy(ctx.model) transactionService.getLatestTransactionBy(ctx.model)
} }
} }

19
server/app/controllers/common/Codecs.scala

@ -0,0 +1,19 @@
package controllers.common
import org.scalactic.Every
import play.api.libs.json._
object Codecs {
implicit def everyReads[T](implicit readsT: Reads[T]): Reads[Every[T]] = Reads[Every[T]] { json =>
json
.validate[List[T]]
.flatMap { list =>
Every.from(list)
.map(JsSuccess.apply(_))
.getOrElse {
JsError.apply("A non-empty list is expected")
}
}
}
}

4
server/test/com/xsn/explorer/data/TransactionPostgresDataHandlerSpec.scala

@ -11,7 +11,7 @@ import com.xsn.explorer.helpers.{BlockLoader, TransactionLoader}
import com.xsn.explorer.models._ import com.xsn.explorer.models._
import com.xsn.explorer.models.fields.TransactionField import com.xsn.explorer.models.fields.TransactionField
import com.xsn.explorer.models.rpc.Block import com.xsn.explorer.models.rpc.Block
import org.scalactic.{Good, One, Or} import org.scalactic.{Every, Good, One, Or}
import org.scalatest.BeforeAndAfter import org.scalatest.BeforeAndAfter
class TransactionPostgresDataHandlerSpec extends PostgresDataHandlerSpec with BeforeAndAfter { class TransactionPostgresDataHandlerSpec extends PostgresDataHandlerSpec with BeforeAndAfter {
@ -307,7 +307,7 @@ class TransactionPostgresDataHandlerSpec extends PostgresDataHandlerSpec with Be
"XdJnCKYNwzCz8ATv8Eu75gonaHyfr9qXg9" -> "1e591eae200f719344fc5df0c4286e3fb191fb8a645bdf054f9b36a856fce41e" "XdJnCKYNwzCz8ATv8Eu75gonaHyfr9qXg9" -> "1e591eae200f719344fc5df0c4286e3fb191fb8a645bdf054f9b36a856fce41e"
) )
val addresses = List( val addresses = Every(
createAddress("XdJnCKYNwzCz8ATv8Eu75gonaHyfr9qXg9"), createAddress("XdJnCKYNwzCz8ATv8Eu75gonaHyfr9qXg9"),
createAddress("XcqpUChZhNkVDgQqFF9U4DdewDGUMWwG53"), createAddress("XcqpUChZhNkVDgQqFF9U4DdewDGUMWwG53"),
createAddress("XcqpUChZhNkVDgQqFF9U4DdewDGUMWwG54"), createAddress("XcqpUChZhNkVDgQqFF9U4DdewDGUMWwG54"),

3
server/test/com/xsn/explorer/helpers/TransactionDummyDataHandler.scala

@ -5,6 +5,7 @@ import com.alexitc.playsonify.models.{FieldOrdering, PaginatedQuery, PaginatedRe
import com.xsn.explorer.data.TransactionBlockingDataHandler import com.xsn.explorer.data.TransactionBlockingDataHandler
import com.xsn.explorer.models._ import com.xsn.explorer.models._
import com.xsn.explorer.models.fields.TransactionField import com.xsn.explorer.models.fields.TransactionField
import org.scalactic.Every
class TransactionDummyDataHandler extends TransactionBlockingDataHandler { class TransactionDummyDataHandler extends TransactionBlockingDataHandler {
@ -14,5 +15,5 @@ class TransactionDummyDataHandler extends TransactionBlockingDataHandler {
override def getByBlockhash(blockhash: Blockhash, paginatedQuery: PaginatedQuery, ordering: FieldOrdering[TransactionField]): ApplicationResult[PaginatedResult[TransactionWithValues]] = ??? override def getByBlockhash(blockhash: Blockhash, paginatedQuery: PaginatedQuery, ordering: FieldOrdering[TransactionField]): ApplicationResult[PaginatedResult[TransactionWithValues]] = ???
override def getLatestTransactionBy(addresses: List[Address]): ApplicationResult[Map[String, String]] = ??? override def getLatestTransactionBy(addresses: Every[Address]): ApplicationResult[Map[String, String]] = ???
} }

66
server/test/controllers/TransactionsControllerSpec.scala

@ -1,16 +1,17 @@
package controllers package controllers
import com.alexitc.playsonify.PublicErrorRenderer import com.alexitc.playsonify.PublicErrorRenderer
import com.alexitc.playsonify.core.FutureApplicationResult import com.alexitc.playsonify.core.{ApplicationResult, FutureApplicationResult}
import com.xsn.explorer.data.TransactionBlockingDataHandler
import com.xsn.explorer.errors.TransactionNotFoundError import com.xsn.explorer.errors.TransactionNotFoundError
import com.xsn.explorer.helpers.{DataHelper, DummyXSNService, TransactionLoader} import com.xsn.explorer.helpers.{DataHelper, DummyXSNService, TransactionDummyDataHandler, TransactionLoader}
import com.xsn.explorer.models._ import com.xsn.explorer.models._
import com.xsn.explorer.models.rpc.Transaction import com.xsn.explorer.models.rpc.Transaction
import com.xsn.explorer.services.XSNService import com.xsn.explorer.services.XSNService
import controllers.common.MyAPISpec import controllers.common.MyAPISpec
import org.scalactic.{Bad, Good} import org.scalactic.{Bad, Every, Good}
import play.api.inject.bind import play.api.inject.bind
import play.api.libs.json.JsValue import play.api.libs.json.{JsValue, Json}
import play.api.test.Helpers._ import play.api.test.Helpers._
import scala.concurrent.Future import scala.concurrent.Future
@ -23,6 +24,10 @@ class TransactionsControllerSpec extends MyAPISpec {
val nonCoinbaseTx = TransactionLoader.get("0834641a7d30d8a2d2b451617599670445ee94ed7736e146c13be260c576c641") val nonCoinbaseTx = TransactionLoader.get("0834641a7d30d8a2d2b451617599670445ee94ed7736e146c13be260c576c641")
val nonCoinbasePreviousTx = TransactionLoader.get("585cec5009c8ca19e83e33d282a6a8de65eb2ca007b54d6572167703768967d9") val nonCoinbasePreviousTx = TransactionLoader.get("585cec5009c8ca19e83e33d282a6a8de65eb2ca007b54d6572167703768967d9")
val severalInputsTx = TransactionLoader.get("a3c43d22bbba31a6e5c00f565cb9c5a1a365407df4cc90efa8a865656b52c0eb") val severalInputsTx = TransactionLoader.get("a3c43d22bbba31a6e5c00f565cb9c5a1a365407df4cc90efa8a865656b52c0eb")
val firstAddress = createAddress("fygsydgfygsdyfgsdyg")
val secondAddress = createAddress("56wedf5wedweedw")
val firstTxId = DataHelper.createTransactionId("a3c43d223658a8656a31a6e5c407df4bbb0f565cb9c5a1acc90efa056b52c0eb")
val secondTxId = DataHelper.createTransactionId("8a865656b5a3c43d22b00f565cb9c5a1a3bba31a6e5c65407df4cc90efa2c0eb")
val customXSNService = new DummyXSNService { val customXSNService = new DummyXSNService {
val map = Map( val map = Map(
@ -54,9 +59,23 @@ class TransactionsControllerSpec extends MyAPISpec {
} }
} }
val transactionDataHandler = new TransactionDummyDataHandler {
val map: Map[Address, TransactionId] = Map(firstAddress -> firstTxId, secondAddress -> secondTxId)
override def getLatestTransactionBy(addresses: Every[Address]): ApplicationResult[Map[String, String]] = {
val result = map
.filterKeys(addresses contains _)
.map( x => x._1.string -> x._2.string )
Good(result)
}
}
override val application = guiceApplicationBuilder override val application = guiceApplicationBuilder
.overrides(bind[XSNService].to(customXSNService)) .overrides(bind[XSNService].to(customXSNService))
.build() .overrides(bind[TransactionBlockingDataHandler].to(transactionDataHandler))
.build()
"GET /transactions/:txid" should { "GET /transactions/:txid" should {
def url(txid: String) = s"/transactions/$txid" def url(txid: String) = s"/transactions/$txid"
@ -205,4 +224,39 @@ class TransactionsControllerSpec extends MyAPISpec {
json mustEqual expected json mustEqual expected
} }
} }
"POST /transactions/latest" should {
def url = s"/transactions/latest"
"return the latest transactions for the addresses" in {
val body = List(firstAddress.string, secondAddress.string, "3rdaddress")
.map(x => s""" "$x" """)
.mkString("[", ",", "]")
val expected = Json.obj(firstAddress.string -> firstTxId, secondAddress.string -> secondTxId)
val response = POST(url, Some(body))
status(response) mustEqual OK
val json = contentAsJson(response)
json mustEqual expected
}
"fail while passing no addresses" in {
val body = """[]"""
val response = POST(url, Some(body))
status(response) mustEqual BAD_REQUEST
val json = contentAsJson(response)
val errorList = (json \ "errors").as[List[JsValue]]
errorList.size mustEqual 1
val error = errorList.head
(error \ "type").as[String] mustEqual PublicErrorRenderer.FieldValidationErrorType
(error \ "field").as[String].nonEmpty mustEqual false
(error \ "message").as[String].nonEmpty mustEqual true
}
}
} }

Loading…
Cancel
Save