diff --git a/deploy.sh b/deploy.sh index 0cbfac8..0bd68de 100755 --- a/deploy.sh +++ b/deploy.sh @@ -4,4 +4,8 @@ cd web npm run build cd .. -sbt assembly && fly deploy \ No newline at end of file +if sbt test; then + sbt assembly && fly deploy +else + echo "Tests failed, skipping deploy!" +fi \ No newline at end of file diff --git a/src/main/scala/fetch/FetchService.scala b/src/main/scala/fetch/FetchService.scala index 943a65b..eab823a 100644 --- a/src/main/scala/fetch/FetchService.scala +++ b/src/main/scala/fetch/FetchService.scala @@ -22,11 +22,11 @@ final case class WeatherServerConfig( object 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 { case Right(config) => config 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)]]] = { - val fileNames = FileNameService.generate(from, to) + val fileNames = fileNameService.generate(from, to) fetchFiles(fileNames) } def fetchFromDate(date: LocalDate): IO[List[Either[Throwable, (String, String)]]] = { - val fileNames = FileNameService.generateFromDate(date) + val fileNames = fileNameService.generateFromDate(date) fetchFiles(fileNames) } } \ No newline at end of file diff --git a/src/main/scala/fetch/FileFetchScheduler.scala b/src/main/scala/fetch/FileFetchScheduler.scala index f4b8e3f..ed3d4e5 100644 --- a/src/main/scala/fetch/FileFetchScheduler.scala +++ b/src/main/scala/fetch/FileFetchScheduler.scala @@ -11,16 +11,15 @@ object FileFetchScheduler { def of(dbService: DBService, fetch: FetchService): IO[FileFetchScheduler] = { Scheduler.of.flatMap { scheduler => 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] = { - val fetchTask = FileNameService.generateCurrentHour.flatMap(fetch.fetchSingleFile) - + val fetchTask = fileNameService.generateCurrentHour.flatMap(fetch.fetchSingleFile) scheduler.scheduleTask(fetchTask) .evalMap { case Left(fetchErr) => diff --git a/src/main/scala/fetch/FileNameService.scala b/src/main/scala/fetch/FileNameService.scala index cacee0b..7c7e71a 100644 --- a/src/main/scala/fetch/FileNameService.scala +++ b/src/main/scala/fetch/FileNameService.scala @@ -5,7 +5,11 @@ import cats.effect._ import java.time.format.DateTimeFormatter 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 formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmm") @@ -23,7 +27,7 @@ object FileNameService { time.plusMinutes(adjustment) } - def generateCurrentHour(implicit clock: Clock[IO]): IO[String] = { + override def generateCurrentHour(implicit clock: Clock[IO]): IO[String] = { clock.realTime.map { duration => val instant = java.time.Instant.ofEpochMilli(duration.toMillis) val zonedDateTime = ZonedDateTime.ofInstant(instant, ZoneId.of("Europe/Riga")) diff --git a/src/main/scala/fetch/Scheduler.scala b/src/main/scala/fetch/Scheduler.scala index 644b541..c710a93 100644 --- a/src/main/scala/fetch/Scheduler.scala +++ b/src/main/scala/fetch/Scheduler.scala @@ -6,7 +6,6 @@ import cats.implicits.catsSyntaxApply import fs2.Stream import org.typelevel.log4cats.Logger import org.typelevel.log4cats.slf4j.Slf4jLogger -import server.Server import java.time.{Duration, LocalTime} import scala.concurrent.duration._ @@ -57,7 +56,7 @@ class Scheduler(log: Logger[IO]) { // run.compile.drain.unsafeRunSync() for { fetch <- FetchService.of - fetchTask = FileNameService.generateCurrentHour.flatMap(fetch.fetchSingleFile) + fetchTask = new FileNameService().generateCurrentHour.flatMap(fetch.fetchSingleFile) } yield scheduleTask(fetchTask).compile.drain.unsafeRunSync() } } diff --git a/src/test/scala/fetch/FileFetchSchedulerSpec.scala b/src/test/scala/fetch/FileFetchSchedulerSpec.scala new file mode 100644 index 0000000..7b59e5c --- /dev/null +++ b/src/test/scala/fetch/FileFetchSchedulerSpec.scala @@ -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 + } + } +} + + diff --git a/src/test/scala/fetch/FileNameServiceSpec.scala b/src/test/scala/fetch/FileNameServiceSpec.scala index 67a8fba..ea2730b 100644 --- a/src/test/scala/fetch/FileNameServiceSpec.scala +++ b/src/test/scala/fetch/FileNameServiceSpec.scala @@ -16,7 +16,7 @@ class FileNameServiceSpec extends AnyFunSuite { "20230517_0330.csv", ) - val actualFileNames = FileNameService.generate(startTime, endTime) + val actualFileNames = new FileNameService().generate(startTime, endTime) assert(actualFileNames == expectedFileNames) } @@ -28,7 +28,7 @@ class FileNameServiceSpec extends AnyFunSuite { .map(hour => if(hour < 10) "0"+hour else ""+hour) .map(str => s"20230517_${str}30.csv") - val actualFileNames = FileNameService.generateFromDate(date) + val actualFileNames = new FileNameService().generateFromDate(date) assert(actualFileNames == expectedFileNames) }