Cleanup fileservice and dataservice

This commit is contained in:
Guntis Smaukstelis
2023-12-17 22:48:24 +02:00
parent 578fdd778f
commit 2aa6c5f43c
12 changed files with 73 additions and 379 deletions
+3 -6
View File
@@ -2,7 +2,7 @@ package app
import cats.effect._
import cats.implicits.catsSyntaxTuple2Parallel
import db.{DBConnection, DataService, FileService, PostgresService}
import db.{DBConnection, PostgresService}
import fetch.{FetchService, FileFetchScheduler}
import server.Server
@@ -15,15 +15,12 @@ object Main extends IOApp {
transactor <- DBConnection.transactor[IO]
postgresService <- PostgresService.of(transactor)
_ <- postgresService.createWeatherTable // create table if it does not exists
fileService <- FileService.of
dataService <- DataService.of(fileService, postgresService)
// dataService <- DataService.of(fileService)
fetch <- FetchService.of
fileFetchScheduler <- FileFetchScheduler.of(dataService, fetch)
fileFetchScheduler <- FileFetchScheduler.of(postgresService, fetch)
schedulerTask = fileFetchScheduler.run.compile.drain
server <- Server.of(dataService, fetch)
server <- Server.of(postgresService, fetch)
serverTask = server.run
exitCode <- (serverTask, schedulerTask).parMapN((_, _) => ExitCode.Success)
-1
View File
@@ -20,7 +20,6 @@ object DBConnection {
val raw = URI.create(url)
val name = raw.getPath.substring(1)
println("")
val dbUrl = s"jdbc:postgresql://${raw.getHost}:${raw.getPort}${raw.getPath}?${raw.getQuery}"
val username = raw.getUserInfo.split(":")(0)
val password = raw.getUserInfo.split(":")(1)
-108
View File
@@ -1,108 +0,0 @@
package db
import cats.effect.unsafe.implicits.global
import cats.effect.{Clock, IO, Ref}
import cats.implicits.toTraverseOps
import fetch.FileNameService
import org.typelevel.log4cats.Logger
import org.typelevel.log4cats.slf4j.Slf4jLogger
import parse.Aggregate
import parse.Aggregate.UserQuery
import java.time.{Instant, LocalDate, LocalDateTime, ZoneId}
trait DataServiceTrait {
def save(fileName: String, content: String): IO[String]
def readFile(fileName: String): IO[List[String]]
def getInRange(from: LocalDateTime, to: LocalDateTime): IO[List[String]]
def getDates: IO[List[LocalDate]]
def getDateFileNames(date: LocalDate): IO[List[String]]
def getDatesByMonths(monthList: List[LocalDate]): IO[List[LocalDate]]
}
object DataService {
def of(
fileService: FileService,
postgresService: PostgresService
): IO[DataService] = {
for {
log <- Slf4jLogger.create[IO]
// fileNameService = new FileNameService()
// fileNames <- fileNameService.generateLast24Hours
// contents <- fileNames.traverse(fileName => fileService.readFile(fileName)
// .map(content => (fileName, content)))
// state = contents.toMap
// stateRef <- Ref.of[IO, Map[String, List[String]]](state)
} yield new DataService(fileService, postgresService, log)
// } yield new DataService(fileService, postgresService, new FileNameService(), log)
// } yield new DataService(fileService, postgresService, new FileNameService(), log, stateRef)
// } yield new DataService(fileService, new FileNameService(), log, stateRef)
}
}
class DataService private(
fileService: FileService,
postgresService: PostgresService,
// fileNameService: FileNameService,
log: Logger[IO],
// private val state: Ref[IO, Map[String, List[String]]]
) extends DataServiceTrait {
// private def logState: IO[Unit] = {
// state.get.flatMap(currentState => log.info(s"State keys: ${currentState.keys.size}"))
// }
//
// private def filterState: IO[Unit] = {
// fileNameService.generateLast24Hours.flatMap { last24Hours =>
// state.update(st => st.filterKeys(last24Hours.contains).toMap)
// }
// }
def save(fileName: String, content: String): IO[String] = {
// TODO replace unsafeRunSync to redeemWith
// postgresService.save(fileName, content).unsafeRunSync()
// postgresService.save(fileName, content)
for {
result <- fileService.save(fileName, content)
_ <- postgresService.save(fileName, content)
} yield result
// TODO delete this
// fileService.save(fileName, content)
// fileService.save(fileName, content).redeemWith(
// error => IO.raiseError(error),
// savedFileName => {
// state.update(st => st.updated(savedFileName, content.split("\n").toList)) *>
// filterState *>
// logState.as(savedFileName)
// }
// ).onError(error => log.info(s"errr... $error"))
}
def readFile(fileName: String): IO[List[String]] = fileService.readFile(fileName)
def getDateTimeEntries(dateTime: LocalDateTime): IO[List[String]] = postgresService.getDateTimeEntries(dateTime)
def getInRange(from: LocalDateTime, to: LocalDateTime): IO[List[String]] = fileService.getInRange(from, to)
def query(userQuery: UserQuery): IO[Map[String, Option[Aggregate.AggregateValue]]] = postgresService.query(userQuery)
def getDates: IO[List[LocalDate]] = fileService.getDates
def getDatesByMonths(monthList: List[LocalDate]): IO[List[LocalDate]] = {
// fileService.getDatesByMonths(monthList)
postgresService.getDatesByMonths(monthList)
}
def getDateFileNames(date: LocalDate): IO[List[String]] = {
// fileService.getDateFileNames(date)
postgresService.getDateFileNames(date)
}
// TODO implement getting full data from state
// def getLast24Hours: IO[List[String]] = {
// state.get.map(_.keys.toList.sorted)
// }
}
-103
View File
@@ -1,103 +0,0 @@
package db
import cats.effect.{IO, Resource}
import cats.implicits.toTraverseOps
import org.typelevel.log4cats.Logger
import org.typelevel.log4cats.slf4j.Slf4jLogger
import java.io.File
import java.nio.file.{Files, Paths}
import java.time.{LocalDate, LocalDateTime}
import java.time.format.DateTimeFormatter
import java.util.concurrent.Executors
import scala.concurrent.ExecutionContext
import scala.io.Source
import scala.util.Try
object FileService {
def of: IO[FileService] = {
Slf4jLogger.create[IO].map(logger => new FileService(logger))
}
}
class FileService(log: Logger[IO]) extends DataServiceTrait {
private val dateFormatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmm")
private val dataPath = "./data"
private val nonDuplicatedLines = 34 // takes only first 34 lines of data as rest after 'Zosēni' is duplicated
private def readFileNames(path: String): IO[List[String]] =
IO.blocking(new File(path).listFiles.toList.map(_.getName))
.handleError(_ => List.empty)
private def inRange(fileName: String, from: LocalDateTime, to: LocalDateTime): Boolean = {
def fileToDateTime(fileName: String): Option[LocalDateTime] = {
val dateString = fileName.split("\\.").head
Try(LocalDateTime.parse(dateString, dateFormatter)).toOption
}
val fileDateTime = fileToDateTime(fileName.stripSuffix (".csv"))
fileDateTime match {
case Some (date) => date.plusSeconds (1).isAfter (from) && date.minusSeconds (1).isBefore (to)
case None => false
}
}
def save(fileName: String, content: String): IO[String] = {
val path = Paths.get(s"$dataPath/$fileName")
IO(Files.writeString(path, content))
.attempt
.flatMap {
case Left(error) => log.error(s"Write file '$fileName' failed with error: ${error.getMessage}") *> IO.raiseError(error)
case Right(_) => log.info(s"write: $fileName").as(fileName)
}
}
def readFile(fileName: String): IO[List[String]] = {
val file = new File(dataPath, fileName)
val sourceResource = Resource.fromAutoCloseable(IO.blocking(Source.fromFile(file)))
sourceResource
.use(source => IO.blocking(source.getLines().take(nonDuplicatedLines).toList))
.handleError(_ => List.empty)
}
def getInRange(from: LocalDateTime, to: LocalDateTime): IO[List[String]] = {
for {
fileNames <- readFileNames(dataPath)
.map (_.filter (inRange (_, from, to)))
fileLines <- fileNames.traverse(readFile)
} yield fileLines.flatten
}
// dates in which we have saved data
def getDates: IO[List[LocalDate]] = {
val formatter = DateTimeFormatter.ofPattern("yyyyMMdd")
for {
fileNames <- readFileNames(dataPath)
datesStr <- IO(fileNames.map(_.take(8)).distinct) // take yyyyMMdd
dates <- datesStr.traverse { str =>
IO(LocalDate.parse(str, formatter)).option
}.map(_.flatten)
} yield dates.sorted
}
def getDatesByMonths(monthList: List[LocalDate]): IO[List[LocalDate]] = {
val dateFormatter = DateTimeFormatter.ofPattern("yyyyMMdd")
val monthFormatter = DateTimeFormatter.ofPattern("yyyyMM")
val monthStrList = monthList.map(_.format(monthFormatter))
for {
fileNames <- readFileNames(dataPath)
datesStr <- IO(fileNames.map(_.take(8)).distinct) // take yyyyMMdd
filteredDatesStr = datesStr.filter(date => monthStrList.contains(date.take(6)))
dates <- filteredDatesStr.traverse { str =>
IO(LocalDate.parse(str, dateFormatter)).option
}.map(_.flatten)
} yield dates.sorted
}
def getDateFileNames(date: LocalDate): IO[List[String]] = {
val formatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd")
val dateStr: String = date.format(formatter)
readFileNames(dataPath).map(_.filter(_.startsWith(dateStr)).sorted)
}
}
+1 -3
View File
@@ -22,7 +22,7 @@ object PostgresService {
}
}
class PostgresService(transactor: Transactor[IO], log: Logger[IO]) extends DataServiceTrait {
class PostgresService(transactor: Transactor[IO], log: Logger[IO]) {
private val rigaZone = ZoneId.of("Europe/Riga")
// in pgAdmin run: ```SET TIMEZONE = 'Europe/Riga';```
@@ -40,7 +40,6 @@ class PostgresService(transactor: Transactor[IO], log: Logger[IO]) extends DataS
def query(userQuery: UserQuery): IO[Map[String, Option[AggregateValue]]] = {
if (userQuery.field == "phenomena") { // this handles strings
// TODO query list and distinct values from phenomena
println("EMPTY RESULT!!!!!111")
IO(Map()) // empty result
} else if (List(
AggregateKey.Max,
@@ -145,7 +144,6 @@ class PostgresService(transactor: Transactor[IO], log: Logger[IO]) extends DataS
}
} else {
println("EMPTY RESULT!!!!!")
IO(Map()) // empty result
}
}
@@ -1,23 +1,23 @@
package fetch
import cats.effect.IO
import db.DataServiceTrait
import db.PostgresService
import fs2.Stream
import org.typelevel.log4cats.Logger
import org.typelevel.log4cats.slf4j.Slf4jLogger
object FileFetchScheduler {
def of(dataService: DataServiceTrait, fetch: FetchService): IO[FileFetchScheduler] = {
def of(postgresService: PostgresService, fetch: FetchService): IO[FileFetchScheduler] = {
Scheduler.of.flatMap { scheduler =>
Slf4jLogger.create[IO].map {
new FileFetchScheduler(dataService, fetch, new FileNameService(), scheduler, _)
new FileFetchScheduler(postgresService, fetch, new FileNameService(), scheduler, _)
}
}
}
}
class FileFetchScheduler(dataService: DataServiceTrait, fetch: FetchService, fileNameService: FileNameService, scheduler: Scheduler, log: Logger[IO]) {
class FileFetchScheduler(postgresService: PostgresService, fetch: FetchService, fileNameService: FileNameService, scheduler: Scheduler, log: Logger[IO]) {
def run: Stream[IO, Unit] = {
val fetchTask = fileNameService.generateCurrentHour.flatMap(fetch.fetchSingleFile)
scheduler.scheduleTask(fetchTask)
@@ -25,7 +25,7 @@ class FileFetchScheduler(dataService: DataServiceTrait, fetch: FetchService, fil
case Left(fetchErr) =>
log.error(s"Fetch error: $fetchErr")
case Right((name, content)) =>
dataService.save(name, content).attempt.flatMap {
postgresService.save(name, content).attempt.flatMap {
case Left(err) => log.error(s"error: $err")
case Right(savedName) => log.info(s"saved: $savedName")
}
+8 -8
View File
@@ -2,7 +2,7 @@ package parse
import cats.data.NonEmptyList
import cats.effect.unsafe.implicits.global
import db.FileService
//import db.FileService
import io.circe.syntax.EncoderOps
import parse.Aggregate.{AggregateKey, UserQuery}
@@ -41,14 +41,14 @@ object Main {
// println(s"$key -> $value")
// }
val from = LocalDateTime.parse("20230627_0000", formatter)
val to = LocalDateTime.parse("20230627_2359", formatter)
// val from = LocalDateTime.parse("20230627_0000", formatter)
// val to = LocalDateTime.parse("20230627_2359", formatter)
val fileService = FileService.of.unsafeRunSync()
val lines = fileService.getInRange(from, to).unsafeRunSync()
val query = UserQuery(NonEmptyList.of("Ainaži"), "tempAvg", AggregateKey.Avg, ChronoUnit.HOURS, from, to)
val parsed = Parser.queryData(query, lines)
println(parsed.asJson)
// val fileService = FileService.of.unsafeRunSync()
// val lines = fileService.getInRange(from, to).unsafeRunSync()
// val query = UserQuery(NonEmptyList.of("Ainaži"), "tempAvg", AggregateKey.Avg, ChronoUnit.HOURS, from, to)
// val parsed = Parser.queryData(query, lines)
// println(parsed.asJson)
// val query2 = UserQuery(List("Ainaži"), "tempAvg", AggregateKey.Avg, ChronoUnit.DAYS)
+10 -16
View File
@@ -3,9 +3,9 @@ package server
import cats.effect._
import cats.implicits.toTraverseOps
import com.comcast.ip4s.IpLiteralSyntax
import db.DataService
import db.PostgresService
import fetch.FetchService
import parse.{Aggregate, Parser, WeatherData}
import parse.{Aggregate}
import server.ValidateRoutes.{AggKey, CityList, DateTimeRange, Granularity, ValidateDate, ValidateDateTime, ValidateMonths}
import io.circe.{Json, Printer}
import org.http4s._
@@ -25,19 +25,18 @@ import org.http4s.circe.jsonEncoder
import org.typelevel.log4cats.Logger
import org.typelevel.log4cats.slf4j.Slf4jLogger
import java.time.temporal.ChronoUnit
import scala.concurrent.duration.DurationInt
object Server {
def of(dataService: DataService, fetch: FetchService): IO[Server] = {
def of(postgresService: PostgresService, fetch: FetchService): IO[Server] = {
Slf4jLogger.create[IO].map {
new Server(dataService, fetch, _)
new Server(postgresService, fetch, _)
}
}
}
class Server(dataService: DataService, fetch: FetchService, log: Logger[IO]) {
class Server(postgresService: PostgresService, fetch: FetchService, log: Logger[IO]) {
// Define the extension method `pretty` for Json
implicit class JsonPrettyPrinter(json: Json) {
@@ -55,12 +54,7 @@ class Server(dataService: DataService, fetch: FetchService, log: Logger[IO]) {
case GET -> Root / "query" / DateTimeRange(from, to) / Granularity(granularity) / CityList(cities) / field / AggKey(key) =>
val userQuery = UserQuery(cities, field, key, granularity, from, to)
// dataService.getInRange(from, to)
// .map(Parser.queryData(userQuery, _))
// .map(result => ResponseWrapper(result, userQuery))
// .flatMap(responseWrapper => Ok(responseWrapper.asJson.pretty))
dataService.query(userQuery)
postgresService.query(userQuery)
.map(result => ResponseWrapper(result, userQuery))
.flatMap(responseWrapper => Ok(responseWrapper.asJson.pretty))
@@ -72,7 +66,7 @@ class Server(dataService: DataService, fetch: FetchService, log: Logger[IO]) {
fetchResult = fetchResultEither.getOrElse(List.empty)
(fetchErrors, successDownloads) = fetchResult.partitionMap(identity)
_ <- log.info(s"FETCHED SUCCESSFULLY files: ${successDownloads.size}")
saveResults <- successDownloads.traverse { case (name, content) => dataService.save(name, content).attempt }
saveResults <- successDownloads.traverse { case (name, content) => postgresService.save(name, content).attempt }
(saveErrors, successSaves) = saveResults.partitionMap(identity)
// successes = successDownloads.map(s => s"fetched: ${s._1}") ++ successSaves.map(s => s"saved: $s")
successes = successSaves
@@ -90,19 +84,19 @@ class Server(dataService: DataService, fetch: FetchService, log: Logger[IO]) {
// http://0.0.0.0:8080/api/show/months/202304,202305,202306
case GET -> Root / "show" / "months" / ValidateMonths(monthList) =>
dataService.getDatesByMonths(monthList).flatMap(dates =>
postgresService.getDatesByMonths(monthList).flatMap(dates =>
Ok(dates.asJson.pretty)
)
// http://0.0.0.0:8080/api/show/date/20230423
case GET -> Root / "show" / "date" / ValidateDate(date) =>
dataService.getDateFileNames(date).flatMap(fileNames =>
postgresService.getDateFileNames(date).flatMap(fileNames =>
Ok(fileNames.asJson.pretty)
)
// http://0.0.0.0:8080/api/show/datetime/20230423_1300
case GET -> Root / "show" / "datetime" / ValidateDateTime(datetime) =>
dataService.getDateTimeEntries(datetime).flatMap(content => Ok(content.asJson))
postgresService.getDateTimeEntries(datetime).flatMap(content => Ok(content.asJson))
}
private val corsConfig = CORSConfig.default
-79
View File
@@ -1,79 +0,0 @@
package db
import cats.effect.unsafe.implicits.global
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers
import java.io.IOException
import java.time.format.DateTimeFormatter
import java.time.{LocalDate, LocalDateTime}
class FileServiceSpec extends AnyFunSuite with Matchers {
private val dateFormatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmm")
test("getDateFileNames should return correct file names") {
val fileService = FileService.of.unsafeRunSync()
val datesList = fileService.getDateFileNames(LocalDate.of(2023, 5, 15))
.unsafeRunSync()
val expectedFileNames = (0 to 23).toList
.map(hour => if (hour < 10) "0" + hour else "" + hour)
.map(str => s"20230515_${str}30.csv")
datesList shouldBe expectedFileNames
}
test("getInRange should return correct count of lines") {
val from = LocalDateTime.parse("20230513_2200", dateFormatter)
val to = LocalDateTime.parse("20230516_1230", dateFormatter)
val fileService = FileService.of.unsafeRunSync()
val lines = fileService.getInRange(from, to).unsafeRunSync()
lines.length shouldBe 2142
}
test("getDatesByMonths should return filtered dates") {
val monthFormatter = DateTimeFormatter.ofPattern("yyyyMMdd")
val monthList = List(
LocalDate.parse("20230401", monthFormatter),
LocalDate.parse("20230501", monthFormatter),
LocalDate.parse("20230601", monthFormatter),
)
val fileService = FileService.of.unsafeRunSync()
val dates = fileService.getDatesByMonths(monthList).unsafeRunSync()
dates should not be empty
val exportFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
val validPrefixes = Set("2023-04", "2023-05", "2023-06")
val invalidDates = dates
.map(_.format(exportFormatter))
.filterNot(date => validPrefixes.exists(prefix => date.startsWith(prefix)))
invalidDates shouldBe empty
}
test("DBService.save should return correct result") {
val fileService = FileService.of.unsafeRunSync()
val fileName = "testFile.txt"
val fileContent = "test content..."
fileService.save(fileName, fileContent).unsafeRunSync() shouldEqual fileName
}
test("DBService.save returns error on invalid file name") {
val fileService = FileService.of.unsafeRunSync()
val fileName = "/invalid/file/name"
val fileContent = "test content..."
val result = fileService.save(fileName, fileContent).attempt.unsafeRunSync()
result match {
case Left(e) =>
assert(e.isInstanceOf[IOException])
case Right(_) => fail("Expected failure did not occur")
}
}
}
@@ -3,7 +3,6 @@ package fetch
import cats.effect.IO
import cats.effect.unsafe.implicits.global
import cats.implicits.toTraverseOps
import db.FileService
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers
import org.typelevel.log4cats.slf4j.Slf4jLogger
@@ -4,14 +4,15 @@ import base.IOSuite
import cats.effect.{Clock, IO}
import cats.effect.kernel.Ref
import cats.implicits.catsSyntaxTuple3Semigroupal
import db.FileService
import db.PostgresService
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AsyncWordSpec
import org.typelevel.log4cats.slf4j.Slf4jLogger
import fs2.Stream
import org.scalamock.scalatest.MockFactory
class FileFetchSchedulerSpec extends AsyncWordSpec with Matchers with IOSuite {
class FileFetchSchedulerSpec extends AsyncWordSpec with Matchers with MockFactory with IOSuite {
"FileFetchScheduler" should {
"save into db" in runIO {
for {
@@ -19,11 +20,7 @@ class FileFetchSchedulerSpec extends AsyncWordSpec with Matchers with IOSuite {
refFetch <- Ref.of[IO, Option[(String, String)]](None)
refScheduler <- Ref.of[IO, Option[Either[Throwable, (String, String)]]](None)
log <- Slf4jLogger.create[IO]
fileService = new FileService(log) {
override def save(fileName: String, content: String): IO[String] = {
refDb.set(Some(fileName)).as(fileName)
}
}
mockDatabaseOps = mock[PostgresService]
fileNameService = new FileNameService {
override def generateCurrentHour(implicit clock: Clock[IO]): IO[String] = IO.pure("file_test")
}
@@ -40,7 +37,7 @@ class FileFetchSchedulerSpec extends AsyncWordSpec with Matchers with IOSuite {
}
}
res = new FileFetchScheduler(fileService, fetchService, fileNameService, scheduler, log)
res = new FileFetchScheduler(mockDatabaseOps, fetchService, fileNameService, scheduler, log)
.run.compile.drain *>
(refDb.get, refFetch.get, refScheduler.get).tupled.map { case (db, fetch, scheduler) =>
db shouldBe Some("file_test")
+41 -41
View File
@@ -1,7 +1,7 @@
package parse
import cats.effect.unsafe.implicits.global
import db.FileService
//import db.FileService
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers
import parse.Aggregate.{AggregateKey, DoubleValue, TimeDoubleList, UserQuery}
@@ -13,44 +13,44 @@ import scala.collection.immutable.HashMap
class ParserSpec extends AnyFunSuite with Matchers {
test("QueryData should return correct sum result") {
val formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmm")
val from = LocalDateTime.parse("20230515_0905", formatter)
val to = LocalDateTime.parse("20230516_0942", formatter)
val userQuery = UserQuery(List("Bauska", "Dagda", "Daugavgrīva", "Rīga"), "precipitation", AggregateKey.Sum, ChronoUnit.HOURS)
val fileService = FileService.of.unsafeRunSync()
val lines = fileService.getInRange(from, to).unsafeRunSync()
val parsed = Parser.queryData(userQuery, lines)
parsed shouldBe HashMap(
"Dagda" -> Some(DoubleValue(0.6)),
"Rīga" -> Some(DoubleValue(7.9)),
"Daugavgrīva" -> Some(DoubleValue(5.9)),
"Bauska" -> Some(DoubleValue(3.0))
)
}
test("QueryData should return correct list result") {
val formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmm")
val from = LocalDateTime.parse("20230516_0400", formatter)
val to = LocalDateTime.parse("20230516_0800", formatter)
val userQuery = UserQuery(List("Rīga"), "precipitation", AggregateKey.List, ChronoUnit.HOURS)
val fileService = FileService.of.unsafeRunSync()
val lines = fileService.getInRange(from, to).unsafeRunSync()
val parsed = Parser.queryData(userQuery, lines)
parsed shouldBe HashMap(
"Rīga" ->
Some(TimeDoubleList(List(
("2023-05-16T04:00", Some(1.9)),
("2023-05-16T05:00", Some(4.5)),
("2023-05-16T06:00", Some(1.5)),
("2023-05-16T07:00", Some(0.0)),
)))
)
}
// test("QueryData should return correct sum result") {
// val formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmm")
// val from = LocalDateTime.parse("20230515_0905", formatter)
// val to = LocalDateTime.parse("20230516_0942", formatter)
// val userQuery = UserQuery(List("Bauska", "Dagda", "Daugavgrīva", "Rīga"), "precipitation", AggregateKey.Sum, ChronoUnit.HOURS)
//
// val fileService = FileService.of.unsafeRunSync()
//
// val lines = fileService.getInRange(from, to).unsafeRunSync()
// val parsed = Parser.queryData(userQuery, lines)
//
// parsed shouldBe HashMap(
// "Dagda" -> Some(DoubleValue(0.6)),
// "Rīga" -> Some(DoubleValue(7.9)),
// "Daugavgrīva" -> Some(DoubleValue(5.9)),
// "Bauska" -> Some(DoubleValue(3.0))
// )
// }
//
// test("QueryData should return correct list result") {
// val formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmm")
// val from = LocalDateTime.parse("20230516_0400", formatter)
// val to = LocalDateTime.parse("20230516_0800", formatter)
// val userQuery = UserQuery(List("Rīga"), "precipitation", AggregateKey.List, ChronoUnit.HOURS)
//
// val fileService = FileService.of.unsafeRunSync()
//
// val lines = fileService.getInRange(from, to).unsafeRunSync()
// val parsed = Parser.queryData(userQuery, lines)
//
// parsed shouldBe HashMap(
// "Rīga" ->
// Some(TimeDoubleList(List(
// ("2023-05-16T04:00", Some(1.9)),
// ("2023-05-16T05:00", Some(4.5)),
// ("2023-05-16T06:00", Some(1.5)),
// ("2023-05-16T07:00", Some(0.0)),
// )))
// )
// }
}