Test for FileFetchScheduler

This commit is contained in:
Guntis Smaukstelis
2023-05-23 22:02:56 +03:00
parent 0581260073
commit eded85b089
7 changed files with 81 additions and 15 deletions
+5 -1
View File
@@ -4,4 +4,8 @@ cd web
npm run build npm run build
cd .. cd ..
sbt assembly && fly deploy if sbt test; then
sbt assembly && fly deploy
else
echo "Tests failed, skipping deploy!"
fi
+4 -4
View File
@@ -22,11 +22,11 @@ final case class WeatherServerConfig(
object FetchService { object FetchService {
def of: IO[FetchService] = { def of: IO[FetchService] = {
Slf4jLogger.create[IO].map(logger => new FetchService(logger)) Slf4jLogger.create[IO].map(logger => new FetchService(new FileNameService, logger))
} }
} }
class FetchService(log: Logger[IO]) { class FetchService(fileNameService: FileNameService, log: Logger[IO]) {
private val weatherServerConfig: WeatherServerConfig = ConfigSource.default.load[WeatherServerConfig] match { private val weatherServerConfig: WeatherServerConfig = ConfigSource.default.load[WeatherServerConfig] match {
case Right(config) => config case Right(config) => config
case Left(errors) => throw new RuntimeException(s"Unable to load config: $errors") case Left(errors) => throw new RuntimeException(s"Unable to load config: $errors")
@@ -71,12 +71,12 @@ class FetchService(log: Logger[IO]) {
} }
def fetchInRange(from: LocalDateTime, to: LocalDateTime): IO[List[Either[Throwable, (String, String)]]] = { def fetchInRange(from: LocalDateTime, to: LocalDateTime): IO[List[Either[Throwable, (String, String)]]] = {
val fileNames = FileNameService.generate(from, to) val fileNames = fileNameService.generate(from, to)
fetchFiles(fileNames) fetchFiles(fileNames)
} }
def fetchFromDate(date: LocalDate): IO[List[Either[Throwable, (String, String)]]] = { def fetchFromDate(date: LocalDate): IO[List[Either[Throwable, (String, String)]]] = {
val fileNames = FileNameService.generateFromDate(date) val fileNames = fileNameService.generateFromDate(date)
fetchFiles(fileNames) fetchFiles(fileNames)
} }
} }
@@ -11,16 +11,15 @@ object FileFetchScheduler {
def of(dbService: DBService, fetch: FetchService): IO[FileFetchScheduler] = { def of(dbService: DBService, fetch: FetchService): IO[FileFetchScheduler] = {
Scheduler.of.flatMap { scheduler => Scheduler.of.flatMap { scheduler =>
Slf4jLogger.create[IO].map { Slf4jLogger.create[IO].map {
new FileFetchScheduler(dbService, fetch, scheduler, _) new FileFetchScheduler(dbService, fetch, new FileNameService(), scheduler, _)
} }
} }
} }
} }
class FileFetchScheduler(dbService: DBService, fetch: FetchService, scheduler: Scheduler, log: Logger[IO]) { class FileFetchScheduler(dbService: DBService, fetch: FetchService, fileNameService: FileNameService, scheduler: Scheduler, log: Logger[IO]) {
def run: Stream[IO, Unit] = { def run: Stream[IO, Unit] = {
val fetchTask = FileNameService.generateCurrentHour.flatMap(fetch.fetchSingleFile) val fetchTask = fileNameService.generateCurrentHour.flatMap(fetch.fetchSingleFile)
scheduler.scheduleTask(fetchTask) scheduler.scheduleTask(fetchTask)
.evalMap { .evalMap {
case Left(fetchErr) => case Left(fetchErr) =>
+6 -2
View File
@@ -5,7 +5,11 @@ import cats.effect._
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.time.{Duration, LocalDate, LocalDateTime, ZoneId, ZonedDateTime} import java.time.{Duration, LocalDate, LocalDateTime, ZoneId, ZonedDateTime}
object FileNameService { trait FileNameServiceTrait {
def generateCurrentHour(implicit clock: Clock[IO]): IO[String]
}
class FileNameService extends FileNameServiceTrait {
private val interval = Duration.ofMinutes(30) private val interval = Duration.ofMinutes(30)
private val formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmm") private val formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmm")
@@ -23,7 +27,7 @@ object FileNameService {
time.plusMinutes(adjustment) time.plusMinutes(adjustment)
} }
def generateCurrentHour(implicit clock: Clock[IO]): IO[String] = { override def generateCurrentHour(implicit clock: Clock[IO]): IO[String] = {
clock.realTime.map { duration => clock.realTime.map { duration =>
val instant = java.time.Instant.ofEpochMilli(duration.toMillis) val instant = java.time.Instant.ofEpochMilli(duration.toMillis)
val zonedDateTime = ZonedDateTime.ofInstant(instant, ZoneId.of("Europe/Riga")) val zonedDateTime = ZonedDateTime.ofInstant(instant, ZoneId.of("Europe/Riga"))
+1 -2
View File
@@ -6,7 +6,6 @@ import cats.implicits.catsSyntaxApply
import fs2.Stream import fs2.Stream
import org.typelevel.log4cats.Logger import org.typelevel.log4cats.Logger
import org.typelevel.log4cats.slf4j.Slf4jLogger import org.typelevel.log4cats.slf4j.Slf4jLogger
import server.Server
import java.time.{Duration, LocalTime} import java.time.{Duration, LocalTime}
import scala.concurrent.duration._ import scala.concurrent.duration._
@@ -57,7 +56,7 @@ class Scheduler(log: Logger[IO]) {
// run.compile.drain.unsafeRunSync() // run.compile.drain.unsafeRunSync()
for { for {
fetch <- FetchService.of fetch <- FetchService.of
fetchTask = FileNameService.generateCurrentHour.flatMap(fetch.fetchSingleFile) fetchTask = new FileNameService().generateCurrentHour.flatMap(fetch.fetchSingleFile)
} yield scheduleTask(fetchTask).compile.drain.unsafeRunSync() } yield scheduleTask(fetchTask).compile.drain.unsafeRunSync()
} }
} }
@@ -0,0 +1,60 @@
package fetch
import base.IOSuite
import cats.effect.{Clock, IO}
import cats.effect.kernel.Ref
import cats.implicits.catsSyntaxTuple3Semigroupal
import db.DBService
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AsyncWordSpec
import org.typelevel.log4cats.slf4j.Slf4jLogger
import fs2.Stream
class FileFetchSchedulerSpec extends AsyncWordSpec with Matchers with IOSuite {
"FileFetchScheduler" should {
"save into db" in runIO {
for {
refDb <- Ref.of[IO, Option[String]](None)
refFetch <- Ref.of[IO, Option[(String, String)]](None)
refScheduler <- Ref.of[IO, Option[Either[Throwable, (String, String)]]](None)
services = Slf4jLogger.create[IO].flatMap { log =>
val dbService = new DBService(log) {
override def save(fileName: String, content: String): IO[Either[Throwable, String]] = {
refDb.set(Some(fileName)).as(Right(fileName))
}
}
val fileNameService = new FileNameService {
override def generateCurrentHour(implicit clock: Clock[IO]): IO[String] = IO.pure("file_test")
}
val fetchService = new FetchService(fileNameService, log) {
override def fetchSingleFile(fileName: String): IO[Either[Throwable, (String, String)]] = {
refFetch.set(Some((fileName, "content"))).as(Right((fileName, "content")))
}
}
val scheduler = new Scheduler(log) {
override def scheduleTask(task: IO[Either[Throwable, (String, String)]]): Stream[IO, Either[Throwable, (String, String)]] = {
Stream.eval(task).flatMap { result =>
Stream.eval(refScheduler.set(Some(result))).as(result)
}
}
}
IO((dbService, fetchService, fileNameService, scheduler, log))
}
res <- services.flatMap { case (dbService, fetchService, fileNameService, scheduler, log) => {
new FileFetchScheduler(dbService, fetchService, fileNameService, scheduler, log)
.run.compile.drain *>
(refDb.get, refFetch.get, refScheduler.get).tupled.map { case (db, fetch, scheduler) =>
db shouldBe Some("file_test")
fetch shouldBe Some(("file_test", "content"))
scheduler shouldBe Some(Right(("file_test", "content")))
}
}
}
} yield res
}
}
}
@@ -16,7 +16,7 @@ class FileNameServiceSpec extends AnyFunSuite {
"20230517_0330.csv", "20230517_0330.csv",
) )
val actualFileNames = FileNameService.generate(startTime, endTime) val actualFileNames = new FileNameService().generate(startTime, endTime)
assert(actualFileNames == expectedFileNames) assert(actualFileNames == expectedFileNames)
} }
@@ -28,7 +28,7 @@ class FileNameServiceSpec extends AnyFunSuite {
.map(hour => if(hour < 10) "0"+hour else ""+hour) .map(hour => if(hour < 10) "0"+hour else ""+hour)
.map(str => s"20230517_${str}30.csv") .map(str => s"20230517_${str}30.csv")
val actualFileNames = FileNameService.generateFromDate(date) val actualFileNames = new FileNameService().generateFromDate(date)
assert(actualFileNames == expectedFileNames) assert(actualFileNames == expectedFileNames)
} }