Browse Source

debugging

master
Christof Nolle 4 years ago
parent
commit
fe9d2aef24
  1. 2
      src/main/scala/de/codingchallenge/Main.scala
  2. 10
      src/main/scala/de/codingchallenge/Routes.scala
  3. 20
      src/main/scala/de/codingchallenge/models/ProductExport.scala
  4. 21
      src/main/scala/de/codingchallenge/repositories/ProductExportRepository.scala
  5. 16
      src/main/scala/de/codingchallenge/services/ArticleExportService.scala
  6. 10
      src/test/scala/de/codingchallenge/fixtures/ArticleFixture.scala
  7. 30
      src/test/scala/de/codingchallenge/services/ArticleExportServiceSpec.scala

2
src/main/scala/de/codingchallenge/Main.scala

@ -51,7 +51,7 @@ object Main extends App with DependencyInjectionModule with LazyLogging { @@ -51,7 +51,7 @@ object Main extends App with DependencyInjectionModule with LazyLogging {
bindingFuture.onComplete {
case Success(_) =>
println(s"Server for started at localhost:8080")
println(s"Server for started at localhost:9080")
case Failure(cause) =>
println(s"Server failed to start", cause)
}

10
src/main/scala/de/codingchallenge/Routes.scala

@ -4,7 +4,7 @@ import akka.actor.ActorSystem @@ -4,7 +4,7 @@ import akka.actor.ActorSystem
import akka.http.scaladsl.model._
import akka.http.scaladsl.server.Directives.{pathPrefix, _}
import akka.http.scaladsl.server._
import akka.stream.ActorMaterializer
import akka.stream.{ActorMaterializer, Materializer}
import akka.util.ByteString
import de.codingchallenge.configuration.Environment
import com.typesafe.scalalogging.LazyLogging
@ -16,8 +16,8 @@ class Routes(articleExportService: ArticleExportService, @@ -16,8 +16,8 @@ class Routes(articleExportService: ArticleExportService,
environment: Environment)
extends LazyLogging {
implicit val ec = actorSystem
implicit val amaterializer = actorMaterializer
implicit val as: ActorSystem = actorSystem
implicit val mat: Materializer = actorMaterializer
var on: Boolean = true
@ -44,8 +44,8 @@ class Routes(articleExportService: ArticleExportService, @@ -44,8 +44,8 @@ class Routes(articleExportService: ArticleExportService,
val exportRoute: Route =
path("articles") {
get {
onSuccess(articleExportService.exportArticles()) { _ =>
complete(StatusCodes.NoContent)
onSuccess(articleExportService.exportArticles()) { res =>
complete(res)
}
}
}

20
src/main/scala/de/codingchallenge/models/ProductExport.scala

@ -1,13 +1,27 @@ @@ -1,13 +1,27 @@
package de.codingchallenge.models
import java.text.DecimalFormat
import java.util.Locale
import de.codingchallenge.csv.CsvColumnWrites
/**
* Data structure representing the CSV export
* @param productId product group identifier
* @param name name of the product
* @param description description of the product
* @param price price
* @param stockSum stock over group
*/
case class ProductExport(productId: String, name: String, description: String, price:Float, stockSum: Int)
object ProductExport{
object ProductExport {
implicit val columnWrites: CsvColumnWrites[ProductExport] = (p: ProductExport) => Seq(
p.productId, p.name, p.description,"%.2f".formatLocal(java.util.Locale.US, p.price), p.stockSum.toString
)
implicit val columnWrites: CsvColumnWrites[ProductExport] = (p: ProductExport) =>
p.productIterator.map(_.toString).toSeq
def apply(a: Article, stockSum: Int): ProductExport = new ProductExport(
productId = a.productId,

21
src/main/scala/de/codingchallenge/repositories/ProductExportRepository.scala

@ -3,9 +3,9 @@ package de.codingchallenge.repositories @@ -3,9 +3,9 @@ package de.codingchallenge.repositories
import akka.NotUsed
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.HttpEntity.Chunked
import akka.http.scaladsl.model._
import akka.stream.scaladsl.{Flow, Source}
import akka.stream.{ActorMaterializer, Materializer}
import akka.util.ByteString
import com.typesafe.scalalogging.LazyLogging
import de.codingchallenge.csv.CsvOps._
@ -16,6 +16,7 @@ import scala.concurrent.{ExecutionContext, Future} @@ -16,6 +16,7 @@ import scala.concurrent.{ExecutionContext, Future}
class ProductExportRepository(actorSystem: ActorSystem)(implicit ec: ExecutionContext) extends LazyLogging {
private implicit val as: ActorSystem = actorSystem
private implicit val mat: Materializer = ActorMaterializer()
val headerLine = "produktId|name|beschreibung|preis|summeBestand"
@ -28,27 +29,25 @@ class ProductExportRepository(actorSystem: ActorSystem)(implicit ec: ExecutionCo @@ -28,27 +29,25 @@ class ProductExportRepository(actorSystem: ActorSystem)(implicit ec: ExecutionCo
.intersperse("\n")
.map(ByteString.apply)
// it did not work with charset information
val entity = Chunked.fromData(
ContentType.WithMissingCharset(MediaTypes.`text/csv`),
sourceWithHeader
)
/**
* Setting charset to utf8 results in HTTP.406
*/
Http()
.singleRequest(
HttpRequest(
method = HttpMethods.PUT,
uri = s"$baseUrl/products/$articlesSize",
entity = entity))
.map{res =>
entity = HttpEntity(ContentType.WithMissingCharset(MediaTypes.`text/csv`), sourceWithHeader)
))
.map { res =>
logger.info(s"Server responded with $res")
res
}
}
private val csvFlow: Flow[ProductExport, String, NotUsed] =
Flow.fromFunction{p =>
logger.info(s"processing export record $p")
Flow.fromFunction { p =>
logger.info(s"streaming export record $p")
p.toCsvLine
}

16
src/main/scala/de/codingchallenge/services/ArticleExportService.scala

@ -18,7 +18,7 @@ class ArticleExportService( @@ -18,7 +18,7 @@ class ArticleExportService(
) extends LazyLogging {
implicit val m: Materializer = mat
val productsSize: Int = 100
val productsSize: Int = 20
/**
* Streams articles to generate a product export.
@ -34,16 +34,20 @@ class ArticleExportService( @@ -34,16 +34,20 @@ class ArticleExportService(
def exportArticles(): Future[HttpResponse] = productExportRepository.add(Source.fromGraph[ProductExport, NotUsed](
articleRepository
.getArticles(productsSize)
.filter(_.stock > 0)
// .filter(_.stock > 0)
.map(a => (a, a.stock))
.via(new AccumulateWhileUnchanged[(Article, Int), String](_._1.productId))
.map(_.reduce[(Article, Int)] {
case ((a1, c1), (a2, c2)) if a1.price > a2.price => (a2, c1 + c2)
case ((a1, c1), (_, c2)) => (a1, c1 + c2)
})
.map(reduceTuples)
.map { case (article, stockSum) =>
logger.info(s"Reduced to article: $article and stockSum: $stockSum")
ProductExport(article, stockSum) }
), productsSize)
def reduceTuples(t: Seq[(Article, Int)]): (Article, Int) = {
logger.info(s"reducing group $t")
t.reduceLeft[(Article, Int)] {
case ((a1, c1), (a2, c2)) if a1.price > a2.price => (a2, c1 + c2)
case ((a1, c1), (_, c2)) => (a1, c1 + c2)
}
}
}

10
src/test/scala/de/codingchallenge/fixtures/ArticleFixture.scala

@ -123,4 +123,14 @@ trait ArticleFixture { @@ -123,4 +123,14 @@ trait ArticleFixture {
A-BdclQqXJ|P-BdclQqXJ|J YSQPS||22.28|49
A-OHAeNBak|P-BdclQqXJ|JWWJVYSQPS C TRYDYX|pvAlqsw bnln ijxmdog roqumthtp lijqubnzz|97.32|24
A-dclQqXJ7|P-BdclQqXJ|QPSU||77.34|17""".split("\n").map(_.trim)
val sampleGroup = Seq(
"A-C7PeyUjD|P-C7PeyUjD|MGT|swwfkg rzDawtwl vvvjebHd aenb aXkcwJyqsc|77.34|17",
"A-LPeyUjDg|P-C7PeyUjD|BMSNWL HFH||45.85|0",
"A-7PeyUjDg|P-C7PeyUjD|BMSNWL HFH||45.85|0",
"A-peyUjDgF|P-C7PeyUjD|SN L|hkqrz u wtwllbkxkl Hdwcpl|87.72|1",
"A-DYMxSQVE|P-C7PeyUjD|GTFB|wwfkgu zDawtw|84.12|7",
"A-PeyUjDgF|P-C7PeyUjD|SN L|hkqrz u wtwllbkxkl Hdwcpl|87.72|1"
)
}

30
src/test/scala/de/codingchallenge/services/ArticleExportServiceSpec.scala

@ -49,7 +49,7 @@ class ArticleExportServiceSpec extends BaseSpec with AkkaSpec{ @@ -49,7 +49,7 @@ class ArticleExportServiceSpec extends BaseSpec with AkkaSpec{
verify(productExportRepositoryMock).add(sourceCaptor.capture(), any())
Await.result(sourceCaptor.getValue.runWith(Sink.head), 1.second) mustBe productExport
}
"pass sample data as expected" in new TestSetup with ArticleFixture {
"contain only one entry per product" in new TestSetup with ArticleFixture {
val articles = sampleData.map(_.csvToOptOf[Article].get).toList
doReturn(Source(articles), Nil: _*)
@ -61,9 +61,31 @@ class ArticleExportServiceSpec extends BaseSpec with AkkaSpec{ @@ -61,9 +61,31 @@ class ArticleExportServiceSpec extends BaseSpec with AkkaSpec{
Await.result(service.exportArticles(), 5.second)
val sourceCaptor: ArgumentCaptor[Source[ProductExport, NotUsed]] = ArgumentCaptor.forClass(classOf[Source[ProductExport, NotUsed]])
verify(productExportRepositoryMock).add(sourceCaptor.capture(), any())
Await.result(sourceCaptor.getValue.runWith(Sink.seq), 1.second).foreach { e =>
e mustBe a[ProductExport]
}
val results = Await.result(sourceCaptor.getValue.runWith(Sink.seq), 1.second)
results.foreach{
r => results.count(_.productId == r.productId) == 1
}
}
"find the cheapest element of a group" in new TestSetup with ArticleFixture {
val articles = sampleGroup.map(_.csvToOptOf[Article].get).toList
doReturn(Source(articles), Nil: _*)
.when(articleRepositoryMock)
.getArticles(100)
when(productExportRepositoryMock.add(any(), any()))
.thenReturn(Future.successful(HttpResponse()))
Await.result(service.exportArticles(), 5.second)
val sourceCaptor: ArgumentCaptor[Source[ProductExport, NotUsed]] = ArgumentCaptor.forClass(classOf[Source[ProductExport, NotUsed]])
verify(productExportRepositoryMock).add(sourceCaptor.capture(), any())
val results = Await.result(sourceCaptor.getValue.runWith(Sink.seq), 1.second)
results.head mustBe ProductExport(
productId = "P-C7PeyUjD",
name = "BMSNWL HFH",
description = "",
price = 45.85f,
stockSum = 26
)
}
}
}

Loading…
Cancel
Save