diff --git a/.editorconfig b/.editorconfig index 215eb9e..1d71e74 100644 --- a/.editorconfig +++ b/.editorconfig @@ -17,4 +17,8 @@ ij_kotlin_name_count_to_use_star_import_for_members = 99999 ij_java_names_count_to_use_import_on_demand = 99999 ktlint_code_style = ktlint_official -ktlint_standard_filename = disabled \ No newline at end of file +ktlint_standard_filename = disabled +ktlint_function_signature_body_expression_wrapping = default +ktlint_standard_multiline-expression-wrapping = disabled +ktlint_standard_string-template-indent = disabled +ktlint_function_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = 5 \ No newline at end of file diff --git a/backend/src/main/kotlin/me/sujanpoudel/playdeals/DIConfigurer.kt b/backend/src/main/kotlin/me/sujanpoudel/playdeals/DIConfigurer.kt index 61b634f..a17eb24 100644 --- a/backend/src/main/kotlin/me/sujanpoudel/playdeals/DIConfigurer.kt +++ b/backend/src/main/kotlin/me/sujanpoudel/playdeals/DIConfigurer.kt @@ -12,8 +12,8 @@ import com.google.firebase.messaging.FirebaseMessaging import io.vertx.core.Vertx import io.vertx.core.eventbus.DeliveryOptions import io.vertx.core.json.jackson.DatabindCodec +import io.vertx.pgclient.PgBuilder import io.vertx.pgclient.PgConnectOptions -import io.vertx.pgclient.PgPool import io.vertx.sqlclient.PoolOptions import me.sujanpoudel.playdeals.api.ApiVerticle import me.sujanpoudel.playdeals.jobs.AndroidAppExpiryCheckScheduler @@ -50,10 +50,7 @@ import java.time.Duration inline fun DI.get(tag: String? = null) = direct.instance(tag) -fun configureDI( - vertx: Vertx, - conf: Conf, -) = DI { +fun configureDI(vertx: Vertx, conf: Conf) = DI { bindSingleton { conf } bindSingleton { ApiVerticle(di = this) } @@ -93,11 +90,12 @@ fun configureDI( } bindSingleton { - PgPool.client(vertx, instance(), PoolOptions().setMaxSize(conf.db.poolSize)) - } - - bindSingleton { - PgPool.pool(vertx, instance(), PoolOptions()) + PgBuilder + .client() + .using(vertx) + .connectingTo(instance()) + .with(PoolOptions().setMaxSize(conf.db.poolSize)) + .build() } bindSingleton { diff --git a/backend/src/main/kotlin/me/sujanpoudel/playdeals/Main.kt b/backend/src/main/kotlin/me/sujanpoudel/playdeals/Main.kt index 2bf4f45..198339d 100644 --- a/backend/src/main/kotlin/me/sujanpoudel/playdeals/Main.kt +++ b/backend/src/main/kotlin/me/sujanpoudel/playdeals/Main.kt @@ -12,22 +12,20 @@ import org.kodein.di.instance import kotlin.system.exitProcess private val vertx = Vertx.vertx() -val configuration = - buildConf(System.getenv()).getOrThrow { - (it as BootstrapException).violations.forEach(::println) - exitProcess(-1) - } +val configuration = buildConf(System.getenv()).getOrThrow { + (it as BootstrapException).violations.forEach(::println) + exitProcess(-1) +} val primaryDI = configureDI(vertx, configuration) -fun main(): Unit = - runBlocking { - primaryDI.direct.instance() +fun main(): Unit = runBlocking { + primaryDI.direct.instance() - vertx.deployVerticle(primaryDI.direct.instance()) - .onSuccess { logger.infoNotify("Deployed MainVerticle : $it") } - .onFailure { - logger.error(it) { "Error deploying main verticle" } - vertx.close() - }.coAwait() - } + vertx.deployVerticle(primaryDI.direct.instance()) + .onSuccess { logger.infoNotify("Deployed MainVerticle : $it") } + .onFailure { + logger.error(it) { "Error deploying main verticle" } + vertx.close() + }.coAwait() +} diff --git a/backend/src/main/kotlin/me/sujanpoudel/playdeals/api/ForexRate.kt b/backend/src/main/kotlin/me/sujanpoudel/playdeals/api/ForexRate.kt index 4648e24..c61bf41 100644 --- a/backend/src/main/kotlin/me/sujanpoudel/playdeals/api/ForexRate.kt +++ b/backend/src/main/kotlin/me/sujanpoudel/playdeals/api/ForexRate.kt @@ -8,19 +8,15 @@ import me.sujanpoudel.playdeals.usecases.executeUseCase import org.kodein.di.DirectDI import org.kodein.di.instance -fun forexRateApi( - di: DirectDI, - vertx: io.vertx.core.Vertx, -): Router = - Router.router(vertx).apply { - get() - .coHandler { ctx -> - ctx.executeUseCase( - useCase = di.instance(), - toContext = { }, - toInput = { }, - ) { - ctx.json(jsonResponse(data = it)) - } +fun forexRateApi(di: DirectDI, vertx: io.vertx.core.Vertx): Router = Router.router(vertx).apply { + get() + .coHandler { ctx -> + ctx.executeUseCase( + useCase = di.instance(), + toContext = { }, + toInput = { }, + ) { + ctx.json(jsonResponse(data = it)) } - } + } +} diff --git a/backend/src/main/kotlin/me/sujanpoudel/playdeals/api/Health.kt b/backend/src/main/kotlin/me/sujanpoudel/playdeals/api/Health.kt index 765fc1d..375195f 100644 --- a/backend/src/main/kotlin/me/sujanpoudel/playdeals/api/Health.kt +++ b/backend/src/main/kotlin/me/sujanpoudel/playdeals/api/Health.kt @@ -12,34 +12,30 @@ import me.sujanpoudel.playdeals.usecases.DBHealthUseCase import org.kodein.di.DirectDI import org.kodein.di.instance -fun healthApi( - di: DirectDI, - vertx: Vertx, -): Router = - Router.router(vertx).apply { - val dbHealthChecker = di.instance() +fun healthApi(di: DirectDI, vertx: Vertx): Router = Router.router(vertx).apply { + val dbHealthChecker = di.instance() - val livenessHandler = HealthCheckHandler.create(vertx) - val readinessHandler = HealthCheckHandler.create(vertx) + val livenessHandler = HealthCheckHandler.create(vertx) + val readinessHandler = HealthCheckHandler.create(vertx) - livenessHandler.register("status") { promise -> - promise.complete(Status.OK()) - } + livenessHandler.register("status") { promise -> + promise.complete(Status.OK()) + } - readinessHandler.register("status") { promise -> - promise.complete(Status.OK()) - } + readinessHandler.register("status") { promise -> + promise.complete(Status.OK()) + } - readinessHandler.register("postgres") { promise -> - CoroutineScope(Dispatchers.IO).launch(vertx.dispatcher()) { - if (dbHealthChecker.execute(Unit)) { - promise.complete(Status.OK()) - } else { - promise.complete(Status.KO()) - } + readinessHandler.register("postgres") { promise -> + CoroutineScope(Dispatchers.IO).launch(vertx.dispatcher()) { + if (dbHealthChecker.execute(Unit)) { + promise.complete(Status.OK()) + } else { + promise.complete(Status.KO()) } } - - get("/liveness").handler(livenessHandler) - get("/readiness").handler(readinessHandler) } + + get("/liveness").handler(livenessHandler) + get("/readiness").handler(readinessHandler) +} diff --git a/backend/src/main/kotlin/me/sujanpoudel/playdeals/api/deals/Api.kt b/backend/src/main/kotlin/me/sujanpoudel/playdeals/api/deals/Api.kt index 1d44008..b44a586 100644 --- a/backend/src/main/kotlin/me/sujanpoudel/playdeals/api/deals/Api.kt +++ b/backend/src/main/kotlin/me/sujanpoudel/playdeals/api/deals/Api.kt @@ -12,31 +12,27 @@ import me.sujanpoudel.playdeals.usecases.executeUseCase import org.kodein.di.DirectDI import org.kodein.di.instance -fun appDealsApi( - di: DirectDI, - vertx: Vertx, -): Router = - Router.router(vertx).apply { - get() - .coHandler { ctx -> - ctx.executeUseCase( - useCase = di.instance(), - toContext = { GetDealsContext(ctx.request().params()) }, - toInput = { GetDealsUseCase.Input(it.skip, it.take) }, - ) { - ctx.json(jsonResponse(data = it)) - } +fun appDealsApi(di: DirectDI, vertx: Vertx): Router = Router.router(vertx).apply { + get() + .coHandler { ctx -> + ctx.executeUseCase( + useCase = di.instance(), + toContext = { GetDealsContext(ctx.request().params()) }, + toInput = { GetDealsUseCase.Input(it.skip, it.take) }, + ) { + ctx.json(jsonResponse(data = it)) } + } - post() - .consumes(ContentTypes.JSON) - .coHandler { ctx -> - ctx.executeUseCase( - useCase = di.instance(), - toContext = { NewDealContext(ctx.request().body().coAwait().toJsonObject()) }, - toInput = { it.packageName }, - ) { - ctx.json(jsonResponse("App added for queue")) - } + post() + .consumes(ContentTypes.JSON) + .coHandler { ctx -> + ctx.executeUseCase( + useCase = di.instance(), + toContext = { NewDealContext(ctx.request().body().coAwait().toJsonObject()) }, + toInput = { it.packageName }, + ) { + ctx.json(jsonResponse("App added for queue")) } - } + } +} diff --git a/backend/src/main/kotlin/me/sujanpoudel/playdeals/common/Conf.kt b/backend/src/main/kotlin/me/sujanpoudel/playdeals/common/Conf.kt index 100d678..089cc2d 100644 --- a/backend/src/main/kotlin/me/sujanpoudel/playdeals/common/Conf.kt +++ b/backend/src/main/kotlin/me/sujanpoudel/playdeals/common/Conf.kt @@ -9,72 +9,66 @@ import java.util.Base64 class BootstrapException(val violations: List) : RuntimeException() -fun buildConf(envs: Map) = - com.github.michaelbull.result.runCatching { - val violations = mutableListOf() +fun buildConf(envs: Map) = com.github.michaelbull.result.runCatching { + val violations = mutableListOf() - @Suppress("UNCHECKED_CAST") - fun env( - envVarName: String, - default: String? = null, - converter: (String) -> T? = { it as? T }, - ): T? = - ( - envs[envVarName] ?: default ?: run { - violations += "No '$envVarName' env var defined!".also { logger.error { it } } - null - } - )?.let(converter) ?: run { - violations += "Invalid '$envVarName'" - null - } + @Suppress("UNCHECKED_CAST") + fun env(envVarName: String, default: String? = null, converter: (String) -> T? = { it as? T }): T? = ( + envs[envVarName] ?: default ?: run { + violations += "No '$envVarName' env var defined!".also { logger.error { it } } + null + } + )?.let(converter) ?: run { + violations += "Invalid '$envVarName'" + null + } - val environment = env("ENV", Environment.PRODUCTION.name) { it.asEnumOrNull() } + val environment = env("ENV", Environment.PRODUCTION.name) { it.asEnumOrNull() } - val appPort = env("APP_PORT", "8888") { it.toIntOrNull() } - val cors = env("CORS", ".*.") + val appPort = env("APP_PORT", "8888") { it.toIntOrNull() } + val cors = env("CORS", ".*.") - val dbPort = env("DB_PORT", "5432") { it.toIntOrNull() } - val dbName = env("DB_NAME", "play_deals") - val dbPoolSize = env("DB_POOL_SIZE", "5") { it.toIntOrNull() } - val dbHost = env("DB_HOST") - val dbUsername = env("DB_USERNAME") - val dbPassword = env("DB_PASSWORD", "password") + val dbPort = env("DB_PORT", "5432") { it.toIntOrNull() } + val dbName = env("DB_NAME", "play_deals") + val dbPoolSize = env("DB_POOL_SIZE", "5") { it.toIntOrNull() } + val dbHost = env("DB_HOST") + val dbUsername = env("DB_USERNAME") + val dbPassword = env("DB_PASSWORD", "password") - val dashboardEnabled = env("DASHBOARD", "true") { it.toBooleanStrictOrNull() } - val dashboardUser = env("DASHBOARD_USER", "admin") - val dashboardPassword = env("DASHBOARD_PASS", "admin") + val dashboardEnabled = env("DASHBOARD", "true") { it.toBooleanStrictOrNull() } + val dashboardUser = env("DASHBOARD_USER", "admin") + val dashboardPassword = env("DASHBOARD_PASS", "admin") - val firebaseAuthCredential = - env("FIREBASE_ADMIN_AUTH_CREDENTIALS") { - Base64.getDecoder().decode(it).decodeToString() - } + val firebaseAuthCredential = + env("FIREBASE_ADMIN_AUTH_CREDENTIALS") { + Base64.getDecoder().decode(it).decodeToString() + } - val forexApiKey = env("FOREX_API_KEY") + val forexApiKey = env("FOREX_API_KEY") - if (violations.isNotEmpty()) { - throw BootstrapException(violations) - } else { - Conf( - api = Conf.Api(appPort!!, cors = cors!!), - environment = environment!!, - db = - Conf.DB( - host = dbHost!!, - port = dbPort!!, - name = dbName!!, - username = dbUsername!!, - password = dbPassword!!, - poolSize = dbPoolSize!!, - ), - backgroundTask = - Conf.BackgroundTask( - dashboardEnabled = dashboardEnabled!!, - dashboardUserName = dashboardUser!!, - dashboardPassword = dashboardPassword!!, - ), - firebaseAuthCredential = firebaseAuthCredential!!, - forexApiKey = forexApiKey!!, - ) - } + if (violations.isNotEmpty()) { + throw BootstrapException(violations) + } else { + Conf( + api = Conf.Api(appPort!!, cors = cors!!), + environment = environment!!, + db = + Conf.DB( + host = dbHost!!, + port = dbPort!!, + name = dbName!!, + username = dbUsername!!, + password = dbPassword!!, + poolSize = dbPoolSize!!, + ), + backgroundTask = + Conf.BackgroundTask( + dashboardEnabled = dashboardEnabled!!, + dashboardUserName = dashboardUser!!, + dashboardPassword = dashboardPassword!!, + ), + firebaseAuthCredential = firebaseAuthCredential!!, + forexApiKey = forexApiKey!!, + ) } +} diff --git a/backend/src/main/kotlin/me/sujanpoudel/playdeals/common/Enums.kt b/backend/src/main/kotlin/me/sujanpoudel/playdeals/common/Enums.kt index da3680d..c52cd92 100644 --- a/backend/src/main/kotlin/me/sujanpoudel/playdeals/common/Enums.kt +++ b/backend/src/main/kotlin/me/sujanpoudel/playdeals/common/Enums.kt @@ -2,9 +2,8 @@ package me.sujanpoudel.playdeals.common inline fun > String.asEnum() = enumValueOf(this) -inline fun > String.asEnumOrNull() = - try { - asEnum() - } catch (e: Exception) { - null - } +inline fun > String.asEnumOrNull() = try { + asEnum() +} catch (e: Exception) { + null +} diff --git a/backend/src/main/kotlin/me/sujanpoudel/playdeals/common/Metrices.kt b/backend/src/main/kotlin/me/sujanpoudel/playdeals/common/Metrices.kt index 1730c63..3a4f9bf 100644 --- a/backend/src/main/kotlin/me/sujanpoudel/playdeals/common/Metrices.kt +++ b/backend/src/main/kotlin/me/sujanpoudel/playdeals/common/Metrices.kt @@ -4,10 +4,7 @@ import me.sujanpoudel.playdeals.logger import kotlin.time.DurationUnit import kotlin.time.measureTimedValue -inline fun loggingExecutionTime( - message: String, - action: () -> T, -): T { +inline fun loggingExecutionTime(message: String, action: () -> T): T { val timedValue = measureTimedValue { action.invoke() diff --git a/backend/src/main/kotlin/me/sujanpoudel/playdeals/common/Routing.kt b/backend/src/main/kotlin/me/sujanpoudel/playdeals/common/Routing.kt index c3f8f81..3951e3d 100644 --- a/backend/src/main/kotlin/me/sujanpoudel/playdeals/common/Routing.kt +++ b/backend/src/main/kotlin/me/sujanpoudel/playdeals/common/Routing.kt @@ -27,14 +27,10 @@ fun Route.coHandler(fn: suspend (RoutingContext) -> Unit): Route { fun HttpServerResponse.contentType(value: String): HttpServerResponse = putHeader("Content-Type", value) -fun jsonResponse( - message: String = "Success", - data: T? = null, -): JsonObject = - jsonObjectOf( - "message" to message, - "data" to data, - ) +fun jsonResponse(message: String = "Success", data: T? = null): JsonObject = jsonObjectOf( + "message" to message, + "data" to data, +) const val UNKNOWN_ERROR_MESSAGE = "Something went wrong" diff --git a/backend/src/main/kotlin/me/sujanpoudel/playdeals/domain/AndroidAppDetail.kt b/backend/src/main/kotlin/me/sujanpoudel/playdeals/domain/AndroidAppDetail.kt index 9a7b196..eefab6a 100644 --- a/backend/src/main/kotlin/me/sujanpoudel/playdeals/domain/AndroidAppDetail.kt +++ b/backend/src/main/kotlin/me/sujanpoudel/playdeals/domain/AndroidAppDetail.kt @@ -19,20 +19,19 @@ data class AndroidAppDetail( val source: String, ) -fun AndroidAppDetail.asNewDeal() = - NewDeal( - id = id, - name = name, - icon = icon, - images = images, - normalPrice = normalPrice, - currentPrice = currentPrice!!, - currency = currency, - storeUrl = storeUrl, - category = category, - downloads = downloads, - rating = rating, - offerExpiresIn = offerExpiresIn!!, - type = DealType.ANDROID_APP, - source = source, - ) +fun AndroidAppDetail.asNewDeal() = NewDeal( + id = id, + name = name, + icon = icon, + images = images, + normalPrice = normalPrice, + currentPrice = currentPrice!!, + currency = currency, + storeUrl = storeUrl, + category = category, + downloads = downloads, + rating = rating, + offerExpiresIn = offerExpiresIn!!, + type = DealType.ANDROID_APP, + source = source, +) diff --git a/backend/src/main/kotlin/me/sujanpoudel/playdeals/domain/entities/DealEntity.kt b/backend/src/main/kotlin/me/sujanpoudel/playdeals/domain/entities/DealEntity.kt index 4db6a33..20b5411 100644 --- a/backend/src/main/kotlin/me/sujanpoudel/playdeals/domain/entities/DealEntity.kt +++ b/backend/src/main/kotlin/me/sujanpoudel/playdeals/domain/entities/DealEntity.kt @@ -29,11 +29,10 @@ data class DealEntity( val updatedAt: OffsetDateTime, ) -private fun String.asCurrencySymbol() = - when (this) { - "USD" -> "$" - else -> this - } +private fun String.asCurrencySymbol() = when (this) { + "USD" -> "$" + else -> this +} private fun Float.formatAsPrice(): String { val int = toInt() diff --git a/backend/src/main/kotlin/me/sujanpoudel/playdeals/jobs/AndroidAppDetailScrapper.kt b/backend/src/main/kotlin/me/sujanpoudel/playdeals/jobs/AndroidAppDetailScrapper.kt index 3484232..3a98016 100644 --- a/backend/src/main/kotlin/me/sujanpoudel/playdeals/jobs/AndroidAppDetailScrapper.kt +++ b/backend/src/main/kotlin/me/sujanpoudel/playdeals/jobs/AndroidAppDetailScrapper.kt @@ -51,36 +51,35 @@ class AppDetailScrapper( WebClient.create(jobsVerticle.vertx, WebClientOptions().setDefaultHost("play.google.com")) } - override suspend fun handleRequest(jobRequest: Request): Unit = - loggingExecutionTime( - "$SIMPLE_NAME:: handleRequest ${jobRequest.packageName}", - ) { - val packageName = jobRequest.packageName - - val app = - loggingExecutionTime("$SIMPLE_NAME:: scrapping app details $packageName") { - getAppDetail(packageName) - } + override suspend fun handleRequest(jobRequest: Request): Unit = loggingExecutionTime( + "$SIMPLE_NAME:: handleRequest ${jobRequest.packageName}", + ) { + val packageName = jobRequest.packageName + + val app = + loggingExecutionTime("$SIMPLE_NAME:: scrapping app details $packageName") { + getAppDetail(packageName) + } - when { - app.normalPrice == 0f -> { - logger.infoNotify("App $packageName(${app.name}) doesn't have any price") - repository.delete(packageName) - } + when { + app.normalPrice == 0f -> { + logger.infoNotify("App $packageName(${app.name}) doesn't have any price") + repository.delete(packageName) + } - app.normalPrice == app.currentPrice -> { - logger.infoNotify("App $packageName(${app.name}) deals has been expired") - repository.delete(packageName) - } + app.normalPrice == app.currentPrice -> { + logger.infoNotify("App $packageName(${app.name}) deals has been expired") + repository.delete(packageName) + } - (app.currentPrice ?: 0f) < app.normalPrice -> { - logger.info("Found deal for $packageName(${app.name}) ${app.currentPrice} ${app.currency}(${app.normalPrice} ${app.currency})") - repository.upsert(app.asNewDeal()).also { - messagingService.sendMessageForNewDeal(it) - } + (app.currentPrice ?: 0f) < app.normalPrice -> { + logger.info("Found deal for $packageName(${app.name}) ${app.currentPrice} ${app.currency}(${app.normalPrice} ${app.currency})") + repository.upsert(app.asNewDeal()).also { + messagingService.sendMessageForNewDeal(it) } } } + } private suspend fun getAppDetail(packageName: String): AndroidAppDetail { val response = @@ -153,10 +152,7 @@ class AppDetailScrapper( return getValue(getJsonArray(value.root), value.path.toTypedArray()) as T } - fun getValue( - jsonObject: JsonArray, - path: Array, - ): Any { + fun getValue(jsonObject: JsonArray, path: Array): Any { return if (path.size == 1) { jsonObject.getValue(path.first()) } else { diff --git a/backend/src/main/kotlin/me/sujanpoudel/playdeals/jobs/AndroidAppExpiryCheckScheduler.kt b/backend/src/main/kotlin/me/sujanpoudel/playdeals/jobs/AndroidAppExpiryCheckScheduler.kt index c374dda..ccf6097 100644 --- a/backend/src/main/kotlin/me/sujanpoudel/playdeals/jobs/AndroidAppExpiryCheckScheduler.kt +++ b/backend/src/main/kotlin/me/sujanpoudel/playdeals/jobs/AndroidAppExpiryCheckScheduler.kt @@ -19,20 +19,19 @@ class AndroidAppExpiryCheckScheduler( private val requestScheduler: JobRequestScheduler, private val storageProvider: StorageProvider, ) : CoJobRequestHandler() { - override suspend fun handleRequest(jobRequest: Request): Unit = - loggingExecutionTime( - "$SIMPLE_NAME:: handleRequest", - ) { - val apps = - repository.getPotentiallyExpiredDeals().stream() - .map { AppDetailScrapper.Request(it.id) } + override suspend fun handleRequest(jobRequest: Request): Unit = loggingExecutionTime( + "$SIMPLE_NAME:: handleRequest", + ) { + val apps = + repository.getPotentiallyExpiredDeals().stream() + .map { AppDetailScrapper.Request(it.id) } - requestScheduler.enqueue(apps) + requestScheduler.enqueue(apps) - val lastUpdatedTime = Instant.now().minus(1, ChronoUnit.HOURS) - val jobs = storageProvider.deleteJobsPermanently(StateName.FAILED, lastUpdatedTime) - logger.info("deleted FAILED `$jobs`") - } + val lastUpdatedTime = Instant.now().minus(1, ChronoUnit.HOURS) + val jobs = storageProvider.deleteJobsPermanently(StateName.FAILED, lastUpdatedTime) + logger.info("deleted FAILED `$jobs`") + } class Request private constructor() : JobRequest { override fun getJobRequestHandler() = AndroidAppExpiryCheckScheduler::class.java @@ -40,12 +39,11 @@ class AndroidAppExpiryCheckScheduler( companion object { private val JOB_ID: UUID = UUID.nameUUIDFromBytes("AppExpiryCheckScheduler".toByteArray()) - operator fun invoke(): RecurringJobBuilder = - RecurringJobBuilder.aRecurringJob() - .withJobRequest(Request()) - .withName("App Expiry Checker") - .withId(JOB_ID.toString()) - .withDuration(Duration.ofHours(6)) + operator fun invoke(): RecurringJobBuilder = RecurringJobBuilder.aRecurringJob() + .withJobRequest(Request()) + .withName("App Expiry Checker") + .withId(JOB_ID.toString()) + .withDuration(Duration.ofHours(6)) } } } diff --git a/backend/src/main/kotlin/me/sujanpoudel/playdeals/jobs/CoJobRequestHandler.kt b/backend/src/main/kotlin/me/sujanpoudel/playdeals/jobs/CoJobRequestHandler.kt index 0175e3e..ac0608b 100644 --- a/backend/src/main/kotlin/me/sujanpoudel/playdeals/jobs/CoJobRequestHandler.kt +++ b/backend/src/main/kotlin/me/sujanpoudel/playdeals/jobs/CoJobRequestHandler.kt @@ -5,10 +5,9 @@ import org.jobrunr.jobs.lambdas.JobRequest import org.jobrunr.jobs.lambdas.JobRequestHandler abstract class CoJobRequestHandler : JobRequestHandler { - override fun run(jobRequest: T): Unit = - runBlocking { - handleRequest(jobRequest) - } + override fun run(jobRequest: T): Unit = runBlocking { + handleRequest(jobRequest) + } abstract suspend fun handleRequest(jobRequest: T) } diff --git a/backend/src/main/kotlin/me/sujanpoudel/playdeals/jobs/DealSummarizer.kt b/backend/src/main/kotlin/me/sujanpoudel/playdeals/jobs/DealSummarizer.kt index ac9b57e..6516ece 100644 --- a/backend/src/main/kotlin/me/sujanpoudel/playdeals/jobs/DealSummarizer.kt +++ b/backend/src/main/kotlin/me/sujanpoudel/playdeals/jobs/DealSummarizer.kt @@ -26,56 +26,54 @@ class DealSummarizer( private val keyValueRepository by instance() private val messagingService by instance() - override suspend fun handleRequest(jobRequest: Request): Unit = - loggingExecutionTime( - "$SIMPLE_NAME:: handleRequest", - ) { - val lastTimestamp = - keyValueRepository.get(LAST_SUMMARY_TIMESTAMP)?.let(OffsetDateTime::parse) - ?: OffsetDateTime.now() + override suspend fun handleRequest(jobRequest: Request): Unit = loggingExecutionTime( + "$SIMPLE_NAME:: handleRequest", + ) { + val lastTimestamp = + keyValueRepository.get(LAST_SUMMARY_TIMESTAMP)?.let(OffsetDateTime::parse) + ?: OffsetDateTime.now() - val deals = dealRepository.getNewDeals(lastTimestamp) + val deals = dealRepository.getNewDeals(lastTimestamp) - if (deals.isNotEmpty()) { - val maxCount = 6 - val dealsDescription = - deals - .take(maxCount) - .mapIndexed { index, deal -> - "${index + 1}. ${deal.name} was ${deal.formattedNormalPrice()} is now ${deal.formattedCurrentPrice()}" - }.joinToString("\n") + if (deals.isNotEmpty()) { + val maxCount = 6 + val dealsDescription = + deals + .take(maxCount) + .mapIndexed { index, deal -> + "${index + 1}. ${deal.name} was ${deal.formattedNormalPrice()} is now ${deal.formattedCurrentPrice()}" + }.joinToString("\n") - messagingService.sendMessageToTopic( - topic = Constants.PushNotificationTopic.DEALS_SUMMARY, - title = "New ${deals.size} app deals are found since yesterday", - body = - if (deals.size > maxCount) { - "$dealsDescription\n\n +${deals.size - maxCount} more..." - } else { - dealsDescription - }, - ) - } else { - logger.infoNotify("$SIMPLE_NAME:: haven't got any deals since $lastTimestamp") - } - - keyValueRepository.set(LAST_SUMMARY_TIMESTAMP, OffsetDateTime.now().toString()) + messagingService.sendMessageToTopic( + topic = Constants.PushNotificationTopic.DEALS_SUMMARY, + title = "New ${deals.size} app deals are found since yesterday", + body = + if (deals.size > maxCount) { + "$dealsDescription\n\n +${deals.size - maxCount} more..." + } else { + dealsDescription + }, + ) + } else { + logger.infoNotify("$SIMPLE_NAME:: haven't got any deals since $lastTimestamp") } + keyValueRepository.set(LAST_SUMMARY_TIMESTAMP, OffsetDateTime.now().toString()) + } + class Request private constructor() : JobRequest { override fun getJobRequestHandler() = DealSummarizer::class.java companion object { private val JOB_ID: UUID = UUID.nameUUIDFromBytes("deal-summarizer".toByteArray()) - operator fun invoke(): RecurringJobBuilder = - RecurringJobBuilder.aRecurringJob() - .withJobRequest(Request()) - .withCron(Cron.daily(16)) - .withAmountOfRetries(2) - .withLabels("Deal Summarizer") - .withName("Deal Summarizer") - .withId(JOB_ID.toString()) + operator fun invoke(): RecurringJobBuilder = RecurringJobBuilder.aRecurringJob() + .withJobRequest(Request()) + .withCron(Cron.daily(16)) + .withAmountOfRetries(2) + .withLabels("Deal Summarizer") + .withName("Deal Summarizer") + .withId(JOB_ID.toString()) } } diff --git a/backend/src/main/kotlin/me/sujanpoudel/playdeals/jobs/ForexFetcher.kt b/backend/src/main/kotlin/me/sujanpoudel/playdeals/jobs/ForexFetcher.kt index 66e47d4..0f54f5b 100644 --- a/backend/src/main/kotlin/me/sujanpoudel/playdeals/jobs/ForexFetcher.kt +++ b/backend/src/main/kotlin/me/sujanpoudel/playdeals/jobs/ForexFetcher.kt @@ -51,14 +51,13 @@ class ForexFetcher( private val repository by instance() - override suspend fun handleRequest(jobRequest: Request): Unit = - loggingExecutionTime( - "$SIMPLE_NAME:: handleRequest", - ) { - val rates = getForexRates() - logger.info("got ${rates.rates.size} forex rate") - repository.saveForexRate(rates) - } + override suspend fun handleRequest(jobRequest: Request): Unit = loggingExecutionTime( + "$SIMPLE_NAME:: handleRequest", + ) { + val rates = getForexRates() + logger.info("got ${rates.rates.size} forex rate") + repository.saveForexRate(rates) + } private suspend fun getForexRates(): ForexRate { val currencies = loadCurrencies() @@ -97,13 +96,12 @@ class ForexFetcher( companion object { private val JOB_ID: UUID = UUID.nameUUIDFromBytes("ForexFetch".toByteArray()) - operator fun invoke(): RecurringJobBuilder = - RecurringJobBuilder.aRecurringJob() - .withJobRequest(Request()) - .withName("ForexFetch") - .withId(JOB_ID.toString()) - .withDuration(Duration.ofDays(1)) - .withAmountOfRetries(3) + operator fun invoke(): RecurringJobBuilder = RecurringJobBuilder.aRecurringJob() + .withJobRequest(Request()) + .withName("ForexFetch") + .withId(JOB_ID.toString()) + .withDuration(Duration.ofDays(1)) + .withAmountOfRetries(3) fun immediate(): JobRequest = Request() } @@ -112,10 +110,9 @@ class ForexFetcher( private const val KEY_FOREX_RATE = "FOREX_RATE" -suspend fun KeyValuesRepository.getForexRate(): ForexRate? = - get(KEY_FOREX_RATE)?.let { - Json.decodeValue(it, ForexRate::class.java) - } +suspend fun KeyValuesRepository.getForexRate(): ForexRate? = get(KEY_FOREX_RATE)?.let { + Json.decodeValue(it, ForexRate::class.java) +} suspend fun KeyValuesRepository.saveForexRate(forexRate: ForexRate) = set(KEY_FOREX_RATE, Json.encode(forexRate)) diff --git a/backend/src/main/kotlin/me/sujanpoudel/playdeals/jobs/RedditPostsScrapper.kt b/backend/src/main/kotlin/me/sujanpoudel/playdeals/jobs/RedditPostsScrapper.kt index e0e5634..29bebf4 100644 --- a/backend/src/main/kotlin/me/sujanpoudel/playdeals/jobs/RedditPostsScrapper.kt +++ b/backend/src/main/kotlin/me/sujanpoudel/playdeals/jobs/RedditPostsScrapper.kt @@ -41,38 +41,37 @@ class RedditPostsScrapper( } private val jobRequestScheduler by instance() - override suspend fun handleRequest(jobRequest: Request): Unit = - loggingExecutionTime( - "$SIMPLE_NAME:: handleRequest", - ) { - val lastPostTime = keyValueRepository.get(LAST_REDDIT_POST_TIME)?.let(OffsetDateTime::parse) + override suspend fun handleRequest(jobRequest: Request): Unit = loggingExecutionTime( + "$SIMPLE_NAME:: handleRequest", + ) { + val lastPostTime = keyValueRepository.get(LAST_REDDIT_POST_TIME)?.let(OffsetDateTime::parse) - val posts = - loggingExecutionTime( - "$SIMPLE_NAME:: Fetched reddit post, last created post was at : '$lastPostTime'", - ) { - getLatestRedditPosts(lastPostTime ?: OffsetDateTime.MIN) - } + val posts = + loggingExecutionTime( + "$SIMPLE_NAME:: Fetched reddit post, last created post was at : '$lastPostTime'", + ) { + getLatestRedditPosts(lastPostTime ?: OffsetDateTime.MIN) + } - val appIds = - posts.flatMap { post -> - PLAY_CONSOLE_REGX.findAll(post.content).toList().mapNotNull { - it.groupValues.lastOrNull() - } - }.distinct() + val appIds = + posts.flatMap { post -> + PLAY_CONSOLE_REGX.findAll(post.content).toList().mapNotNull { + it.groupValues.lastOrNull() + } + }.distinct() - logger.infoNotify("$SIMPLE_NAME:: got ${posts.size} new posts (${appIds.size} Links)") + logger.infoNotify("$SIMPLE_NAME:: got ${posts.size} new posts (${appIds.size} Links)") - appIds.forEach { packageName -> - val id = UUID.nameUUIDFromBytes(packageName.toByteArray()) - jobRequestScheduler.enqueue(id, AppDetailScrapper.Request(packageName)) - } + appIds.forEach { packageName -> + val id = UUID.nameUUIDFromBytes(packageName.toByteArray()) + jobRequestScheduler.enqueue(id, AppDetailScrapper.Request(packageName)) + } - posts.firstOrNull()?.let { - logger.info("$SIMPLE_NAME:: Last reddit post was at ${it.createdAt} with id ${it.id}") - keyValueRepository.set(LAST_REDDIT_POST_TIME, it.createdAt.toString()) - } + posts.firstOrNull()?.let { + logger.info("$SIMPLE_NAME:: Last reddit post was at ${it.createdAt} with id ${it.id}") + keyValueRepository.set(LAST_REDDIT_POST_TIME, it.createdAt.toString()) } + } private suspend fun getLatestRedditPosts(lastPostTime: OffsetDateTime): List { val path = "/r/googleplaydeals/new.json?limit=100" @@ -114,14 +113,13 @@ class RedditPostsScrapper( companion object { private val JOB_ID: UUID = UUID.nameUUIDFromBytes("Reddit Posts".toByteArray()) - operator fun invoke(): RecurringJobBuilder = - RecurringJobBuilder.aRecurringJob() - .withJobRequest(Request()) - .withAmountOfRetries(2) - .withLabels("Reddit") - .withName("Reddit Post Scrap") - .withId(JOB_ID.toString()) - .withDuration(Duration.ofHours(1)) + operator fun invoke(): RecurringJobBuilder = RecurringJobBuilder.aRecurringJob() + .withJobRequest(Request()) + .withAmountOfRetries(2) + .withLabels("Reddit") + .withName("Reddit Post Scrap") + .withId(JOB_ID.toString()) + .withDuration(Duration.ofHours(1)) } } diff --git a/backend/src/main/kotlin/me/sujanpoudel/playdeals/repositories/DealRepository.kt b/backend/src/main/kotlin/me/sujanpoudel/playdeals/repositories/DealRepository.kt index 0ef9251..979bb3c 100644 --- a/backend/src/main/kotlin/me/sujanpoudel/playdeals/repositories/DealRepository.kt +++ b/backend/src/main/kotlin/me/sujanpoudel/playdeals/repositories/DealRepository.kt @@ -5,10 +5,7 @@ import me.sujanpoudel.playdeals.domain.entities.DealEntity import java.time.OffsetDateTime interface DealRepository { - suspend fun getAll( - skip: Int, - take: Int, - ): List + suspend fun getAll(skip: Int, take: Int): List suspend fun upsert(appDeal: NewDeal): DealEntity diff --git a/backend/src/main/kotlin/me/sujanpoudel/playdeals/repositories/KeyValuesRepository.kt b/backend/src/main/kotlin/me/sujanpoudel/playdeals/repositories/KeyValuesRepository.kt index 28592bf..9e02f25 100644 --- a/backend/src/main/kotlin/me/sujanpoudel/playdeals/repositories/KeyValuesRepository.kt +++ b/backend/src/main/kotlin/me/sujanpoudel/playdeals/repositories/KeyValuesRepository.kt @@ -1,10 +1,7 @@ package me.sujanpoudel.playdeals.repositories interface KeyValuesRepository { - suspend fun set( - key: String, - value: String, - ): String + suspend fun set(key: String, value: String): String suspend fun get(key: String): String? diff --git a/backend/src/main/kotlin/me/sujanpoudel/playdeals/repositories/caching/CachingDealRepository.kt b/backend/src/main/kotlin/me/sujanpoudel/playdeals/repositories/caching/CachingDealRepository.kt index f30463b..8770ce6 100644 --- a/backend/src/main/kotlin/me/sujanpoudel/playdeals/repositories/caching/CachingDealRepository.kt +++ b/backend/src/main/kotlin/me/sujanpoudel/playdeals/repositories/caching/CachingDealRepository.kt @@ -27,10 +27,7 @@ class CachingDealRepository( } } - override suspend fun getAll( - skip: Int, - take: Int, - ): List { + override suspend fun getAll(skip: Int, take: Int): List { initialize() return if (cacheInitialized) { cache.values.toList().drop(skip).take(take) diff --git a/backend/src/main/kotlin/me/sujanpoudel/playdeals/repositories/persistent/PersistentDealRepository.kt b/backend/src/main/kotlin/me/sujanpoudel/playdeals/repositories/persistent/PersistentDealRepository.kt index 45fb7fd..11cbb6e 100644 --- a/backend/src/main/kotlin/me/sujanpoudel/playdeals/repositories/persistent/PersistentDealRepository.kt +++ b/backend/src/main/kotlin/me/sujanpoudel/playdeals/repositories/persistent/PersistentDealRepository.kt @@ -13,10 +13,7 @@ import java.time.OffsetDateTime class PersistentDealRepository( private val sqlClient: SqlClient, ) : DealRepository { - override suspend fun getAll( - skip: Int, - take: Int, - ): List { + override suspend fun getAll(skip: Int, take: Int): List { return sqlClient.preparedQuery( """ SELECT * FROM "deal" ORDER BY created_at DESC OFFSET $1 LIMIT $2 diff --git a/backend/src/main/kotlin/me/sujanpoudel/playdeals/repositories/persistent/PersistentKeyValuesRepository.kt b/backend/src/main/kotlin/me/sujanpoudel/playdeals/repositories/persistent/PersistentKeyValuesRepository.kt index b643049..a605267 100644 --- a/backend/src/main/kotlin/me/sujanpoudel/playdeals/repositories/persistent/PersistentKeyValuesRepository.kt +++ b/backend/src/main/kotlin/me/sujanpoudel/playdeals/repositories/persistent/PersistentKeyValuesRepository.kt @@ -10,10 +10,7 @@ import me.sujanpoudel.playdeals.repositories.KeyValuesRepository class PersistentKeyValuesRepository( private val sqlClient: SqlClient, ) : KeyValuesRepository { - override suspend fun set( - key: String, - value: String, - ): String { + override suspend fun set(key: String, value: String): String { return sqlClient.preparedQuery( """ INSERT INTO "key_value_store" VALUES ($1,$2) diff --git a/backend/src/main/kotlin/me/sujanpoudel/playdeals/services/MessagingService.kt b/backend/src/main/kotlin/me/sujanpoudel/playdeals/services/MessagingService.kt index e16c197..f8fdbf6 100644 --- a/backend/src/main/kotlin/me/sujanpoudel/playdeals/services/MessagingService.kt +++ b/backend/src/main/kotlin/me/sujanpoudel/playdeals/services/MessagingService.kt @@ -32,12 +32,7 @@ class MessagingService( ) { private fun String.asTopic() = if (environment == Environment.PRODUCTION) this else "$this-dev" - suspend fun sendMessageToTopic( - topic: String, - title: String, - body: String, - imageUrl: String? = null, - ) { + suspend fun sendMessageToTopic(topic: String, title: String, body: String, imageUrl: String? = null) { val message = Message.builder() .setTopic(topic.asTopic()) @@ -64,22 +59,20 @@ class MessagingService( } } -suspend inline fun MessagingService.sendMessageForNewDeal(deal: DealEntity) = - sendMessageToTopic( - topic = - if (deal.currentPrice == 0f) { - Constants.PushNotificationTopic.NEW_FREE_DEAL - } else { - Constants.PushNotificationTopic.NEW_DISCOUNT_DEAL - }, - title = "New deal found", - body = "${deal.name} was ${deal.formattedNormalPrice()} is now ${deal.formattedCurrentPrice()}", - imageUrl = deal.icon, - ) +suspend inline fun MessagingService.sendMessageForNewDeal(deal: DealEntity) = sendMessageToTopic( + topic = + if (deal.currentPrice == 0f) { + Constants.PushNotificationTopic.NEW_FREE_DEAL + } else { + Constants.PushNotificationTopic.NEW_DISCOUNT_DEAL + }, + title = "New deal found", + body = "${deal.name} was ${deal.formattedNormalPrice()} is now ${deal.formattedCurrentPrice()}", + imageUrl = deal.icon, +) -suspend inline fun MessagingService.sendMaintenanceLog(message: String) = - sendMessageToTopic( - topic = Constants.PushNotificationTopic.DEV_LOG, - title = "Maintenance Log", - body = message, - ) +suspend inline fun MessagingService.sendMaintenanceLog(message: String) = sendMessageToTopic( + topic = Constants.PushNotificationTopic.DEV_LOG, + title = "Maintenance Log", + body = message, +) diff --git a/backend/src/main/kotlin/me/sujanpoudel/playdeals/usecases/DBHealthUseCase.kt b/backend/src/main/kotlin/me/sujanpoudel/playdeals/usecases/DBHealthUseCase.kt index d3e5fc1..f11aff9 100644 --- a/backend/src/main/kotlin/me/sujanpoudel/playdeals/usecases/DBHealthUseCase.kt +++ b/backend/src/main/kotlin/me/sujanpoudel/playdeals/usecases/DBHealthUseCase.kt @@ -10,12 +10,11 @@ class DBHealthUseCase( ) : UseCase { private val sqlClient by di.instance() - override suspend fun doExecute(input: Unit): Boolean = - runCatching { - sqlClient.preparedQuery("""SELECT 1""") - .execute() - .coAwait() - }.map { rs -> - rs.count() == 1 - }.getOrDefault(false) + override suspend fun doExecute(input: Unit): Boolean = runCatching { + sqlClient.preparedQuery("""SELECT 1""") + .execute() + .coAwait() + }.map { rs -> + rs.count() == 1 + }.getOrDefault(false) } diff --git a/backend/src/main/kotlin/me/sujanpoudel/playdeals/usecases/UseCaseEngine.kt b/backend/src/main/kotlin/me/sujanpoudel/playdeals/usecases/UseCaseEngine.kt index 877db18..9f213d8 100644 --- a/backend/src/main/kotlin/me/sujanpoudel/playdeals/usecases/UseCaseEngine.kt +++ b/backend/src/main/kotlin/me/sujanpoudel/playdeals/usecases/UseCaseEngine.kt @@ -29,15 +29,14 @@ suspend fun RoutingContext.executeUseCase( toInput: (Request) -> Input, onError: (Throwable) -> Unit = this::handleExceptions, onSuccess: (Output) -> Unit, -): Result = - runCatching { toContext.invoke() } - .andThen { - runCatching { - (it as? Validated)?.validate() - it - } +): Result = runCatching { toContext.invoke() } + .andThen { + runCatching { + (it as? Validated)?.validate() + it } - .andThen { runCatching { toInput.invoke(it) } } - .andThen { runCatching { useCase.execute(it) } } - .onSuccess(onSuccess) - .onFailure(onError) + } + .andThen { runCatching { toInput.invoke(it) } } + .andThen { runCatching { useCase.execute(it) } } + .onSuccess(onSuccess) + .onFailure(onError) diff --git a/backend/src/test/kotlin/me/sujanpoudel/playdeals/IntegrationTest.kt b/backend/src/test/kotlin/me/sujanpoudel/playdeals/IntegrationTest.kt index 0919d55..4e513de 100644 --- a/backend/src/test/kotlin/me/sujanpoudel/playdeals/IntegrationTest.kt +++ b/backend/src/test/kotlin/me/sujanpoudel/playdeals/IntegrationTest.kt @@ -65,21 +65,19 @@ abstract class IntegrationTest(private val vertx: Vertx) { var di = configureDI(vertx, conf) - protected fun runTest(block: suspend () -> Unit): Unit = - runBlocking(vertx.dispatcher()) { - di.direct.instance() - try { - block() - } catch (e: Exception) { - e.printStackTrace() - throw e - } + protected fun runTest(block: suspend () -> Unit): Unit = runBlocking(vertx.dispatcher()) { + di.direct.instance() + try { + block() + } catch (e: Exception) { + e.printStackTrace() + throw e } + } - private fun deployVerticle(): String = - runBlocking(vertx.dispatcher()) { - vertx.deployVerticle(di.direct.instance()).coAwait() - } + private fun deployVerticle(): String = runBlocking(vertx.dispatcher()) { + vertx.deployVerticle(di.direct.instance()).coAwait() + } @BeforeEach fun assignDeploymentId() { @@ -98,11 +96,10 @@ abstract class IntegrationTest(private val vertx: Vertx) { } @AfterEach - fun undeployVerticle() = - runBlocking(vertx.dispatcher()) { - vertx.undeploy(deploymentId).coAwait() - log.info { "un-deployed deployment id $deploymentId" } - } + fun undeployVerticle() = runBlocking(vertx.dispatcher()) { + vertx.undeploy(deploymentId).coAwait() + log.info { "un-deployed deployment id $deploymentId" } + } companion object { @JvmStatic diff --git a/backend/src/test/kotlin/me/sujanpoudel/playdeals/api/deals/GetDealsApiTest.kt b/backend/src/test/kotlin/me/sujanpoudel/playdeals/api/deals/GetDealsApiTest.kt index c9df63b..68755d3 100644 --- a/backend/src/test/kotlin/me/sujanpoudel/playdeals/api/deals/GetDealsApiTest.kt +++ b/backend/src/test/kotlin/me/sujanpoudel/playdeals/api/deals/GetDealsApiTest.kt @@ -38,108 +38,103 @@ private val newDeal = class GetDealsApiTest(vertx: Vertx) : IntegrationTest(vertx) { @Test - fun `should send error if skip param is less than 0`() = - runTest { - val response = - httpClient.get("/api/deals/?skip=-1") - .send() - .coAwait() + fun `should send error if skip param is less than 0`() = runTest { + val response = + httpClient.get("/api/deals/?skip=-1") + .send() + .coAwait() - val responseBody = response.bodyAsJsonObject() + val responseBody = response.bodyAsJsonObject() - response.statusCode() shouldBe 400 - responseBody.getString("message") shouldBe "skip Can't be less than 0" - } + response.statusCode() shouldBe 400 + responseBody.getString("message") shouldBe "skip Can't be less than 0" + } @Test - fun `should send error if take param is less than 1`() = - runTest { - val response = - httpClient.get("/api/deals/?take=0") - .send() - .coAwait() + fun `should send error if take param is less than 1`() = runTest { + val response = + httpClient.get("/api/deals/?take=0") + .send() + .coAwait() - val responseBody = response.bodyAsJsonObject() + val responseBody = response.bodyAsJsonObject() - response.statusCode() shouldBe 400 - responseBody.getString("message") shouldBe "take Can't be less than 1" - } + response.statusCode() shouldBe 400 + responseBody.getString("message") shouldBe "take Can't be less than 1" + } @Test - fun `should return app deals`() = - runTest { - val repository = di.get() + fun `should return app deals`() = runTest { + val repository = di.get() - val app0 = repository.upsert(newDeal) - val app1 = repository.upsert(newDeal.copy(id = "id1")) + val app0 = repository.upsert(newDeal) + val app1 = repository.upsert(newDeal.copy(id = "id1")) - val response = - httpClient.get("/api/deals/") - .send() - .coAwait() + val response = + httpClient.get("/api/deals/") + .send() + .coAwait() - val deals: ApiResponse> = di.get().readValue(response.bodyAsString()) + val deals: ApiResponse> = di.get().readValue(response.bodyAsString()) - response.statusCode() shouldBe 200 - deals.data!!.size shouldBe 2 - deals.data.shouldContainAll(listOf(app0, app1)) - } + response.statusCode() shouldBe 200 + deals.data!!.size shouldBe 2 + deals.data.shouldContainAll(listOf(app0, app1)) + } @Test - fun `should correctly handle skip parameter`() = - runTest { - val repository = di.get() + fun `should correctly handle skip parameter`() = runTest { + val repository = di.get() - repository.upsert(newDeal) - repository.upsert(newDeal.copy(id = "id1")) - repository.upsert(newDeal.copy(id = "id2")) - repository.upsert(newDeal.copy(id = "id3")) + repository.upsert(newDeal) + repository.upsert(newDeal.copy(id = "id1")) + repository.upsert(newDeal.copy(id = "id2")) + repository.upsert(newDeal.copy(id = "id3")) - httpClient.get("/api/deals?skip=1") - .send() - .coAwait().also { response -> - val deals: ApiResponse> = di.get().readValue(response.bodyAsString()) + httpClient.get("/api/deals?skip=1") + .send() + .coAwait().also { response -> + val deals: ApiResponse> = di.get().readValue(response.bodyAsString()) - response.statusCode() shouldBe 200 - deals.data!!.size shouldBe 3 - } + response.statusCode() shouldBe 200 + deals.data!!.size shouldBe 3 + } - httpClient.get("/api/deals?skip=3") - .send() - .coAwait().also { response -> - val deals: ApiResponse> = di.get().readValue(response.bodyAsString()) + httpClient.get("/api/deals?skip=3") + .send() + .coAwait().also { response -> + val deals: ApiResponse> = di.get().readValue(response.bodyAsString()) - response.statusCode() shouldBe 200 - deals.data!!.size shouldBe 1 - } - } + response.statusCode() shouldBe 200 + deals.data!!.size shouldBe 1 + } + } @Test - fun `should correctly handle take parameter`() = - runTest { - val repository = di.get() - - repository.upsert(newDeal) - repository.upsert(newDeal.copy(id = "id1")) - repository.upsert(newDeal.copy(id = "id2")) - repository.upsert(newDeal.copy(id = "id3")) - - httpClient.get("/api/deals?take=2") - .send() - .coAwait().also { response -> - val deals: ApiResponse> = di.get().readValue(response.bodyAsString()) - - response.statusCode() shouldBe 200 - deals.data!!.size shouldBe 2 - } - - httpClient.get("/api/deals?take=1") - .send() - .coAwait().also { response -> - val deals: ApiResponse> = di.get().readValue(response.bodyAsString()) - - response.statusCode() shouldBe 200 - deals.data!!.size shouldBe 1 - } - } + fun `should correctly handle take parameter`() = runTest { + val repository = di.get() + + repository.upsert(newDeal) + repository.upsert(newDeal.copy(id = "id1")) + repository.upsert(newDeal.copy(id = "id2")) + repository.upsert(newDeal.copy(id = "id3")) + + httpClient.get("/api/deals?take=2") + .send() + .coAwait().also { response -> + val deals: ApiResponse> = di.get().readValue(response.bodyAsString()) + + response.statusCode() shouldBe 200 + deals.data!!.size shouldBe 2 + } + + httpClient.get("/api/deals?take=1") + .send() + .coAwait().also { response -> + val deals: ApiResponse> = di.get().readValue(response.bodyAsString()) + + response.statusCode() shouldBe 200 + deals.data!!.size shouldBe 1 + } + } } diff --git a/backend/src/test/kotlin/me/sujanpoudel/playdeals/api/deals/NewDealApiTest.kt b/backend/src/test/kotlin/me/sujanpoudel/playdeals/api/deals/NewDealApiTest.kt index 1923317..c6890da 100644 --- a/backend/src/test/kotlin/me/sujanpoudel/playdeals/api/deals/NewDealApiTest.kt +++ b/backend/src/test/kotlin/me/sujanpoudel/playdeals/api/deals/NewDealApiTest.kt @@ -18,85 +18,81 @@ import java.util.UUID class NewDealApiTest(vertx: Vertx) : IntegrationTest(vertx) { @Test - fun `should send error response if packageName is null`() = - runTest { - val response = - httpClient.post("/api/deals") - .sendJson(jsonObjectOf()) - .coAwait() + fun `should send error response if packageName is null`() = runTest { + val response = + httpClient.post("/api/deals") + .sendJson(jsonObjectOf()) + .coAwait() - val responseBody = response.bodyAsJsonObject() + val responseBody = response.bodyAsJsonObject() - response.statusCode() shouldBe 400 - responseBody.getString("message") shouldBe "packageName is required" - } + response.statusCode() shouldBe 400 + responseBody.getString("message") shouldBe "packageName is required" + } @Test - fun `should send error response if packageName is invalid`() = - runTest { - val response = - httpClient.post("/api/deals") - .sendJson(jsonObjectOf("packageName" to "11111")) - .coAwait() + fun `should send error response if packageName is invalid`() = runTest { + val response = + httpClient.post("/api/deals") + .sendJson(jsonObjectOf("packageName" to "11111")) + .coAwait() - val responseBody = response.bodyAsJsonObject() + val responseBody = response.bodyAsJsonObject() - response.statusCode() shouldBe 400 - responseBody.getString("message") shouldBe "Invalid value for packageName" - } + response.statusCode() shouldBe 400 + responseBody.getString("message") shouldBe "Invalid value for packageName" + } @Test - fun `should enqueue a app detail scrap request on success`() = - runTest { - val storageProvider = di.get() + fun `should enqueue a app detail scrap request on success`() = runTest { + val storageProvider = di.get() - val packageName = "com.example.app" + val packageName = "com.example.app" - val response = - httpClient.post("/api/deals") - .sendJson(jsonObjectOf("packageName" to packageName)) - .coAwait() + val response = + httpClient.post("/api/deals") + .sendJson(jsonObjectOf("packageName" to packageName)) + .coAwait() - val job = storageProvider.getJobById(UUID.nameUUIDFromBytes(packageName.encodeToByteArray())) + val job = storageProvider.getJobById(UUID.nameUUIDFromBytes(packageName.encodeToByteArray())) - job.state shouldBe StateName.ENQUEUED + job.state shouldBe StateName.ENQUEUED - response.statusCode() shouldBe 200 - } + response.statusCode() shouldBe 200 + } @Test - fun `should should 200 if the app already exists`() = - runTest { - di.get() - val repository = di.get() - - val packageName = "com.example.app" - - val newDeal = - NewDeal( - id = packageName, - name = "name", - icon = "icon", - images = listOf("img0", "img1"), - normalPrice = 12f, - currentPrice = 12f, - currency = "USD", - storeUrl = "store_url", - category = "unknown", - downloads = "12+", - rating = "12", - offerExpiresIn = OffsetDateTime.now(), - type = DealType.ANDROID_APP, - source = Constants.DealSources.APP_DEAL_SUBREDDIT, - ) - - repository.upsert(newDeal) - - val response = - httpClient.post("/api/deals") - .sendJson(jsonObjectOf("packageName" to packageName)) - .coAwait() - - response.statusCode() shouldBe 200 - } + fun `should should 200 if the app already exists`() = runTest { + di.get() + val repository = di.get() + + val packageName = "com.example.app" + + val newDeal = + NewDeal( + id = packageName, + name = "name", + icon = "icon", + images = listOf("img0", "img1"), + normalPrice = 12f, + currentPrice = 12f, + currency = "USD", + storeUrl = "store_url", + category = "unknown", + downloads = "12+", + rating = "12", + offerExpiresIn = OffsetDateTime.now(), + type = DealType.ANDROID_APP, + source = Constants.DealSources.APP_DEAL_SUBREDDIT, + ) + + repository.upsert(newDeal) + + val response = + httpClient.post("/api/deals") + .sendJson(jsonObjectOf("packageName" to packageName)) + .coAwait() + + response.statusCode() shouldBe 200 + } } diff --git a/backend/src/test/kotlin/me/sujanpoudel/playdeals/api/forex/GetForexApiTest.kt b/backend/src/test/kotlin/me/sujanpoudel/playdeals/api/forex/GetForexApiTest.kt index a7745aa..43fd0bb 100644 --- a/backend/src/test/kotlin/me/sujanpoudel/playdeals/api/forex/GetForexApiTest.kt +++ b/backend/src/test/kotlin/me/sujanpoudel/playdeals/api/forex/GetForexApiTest.kt @@ -17,64 +17,61 @@ import java.time.ZoneOffset class GetForexApiTest(vertx: Vertx) : IntegrationTest(vertx) { @Test - fun `Key value repo should properly store the forex rate`() = - runTest { - val repository = di.get() + fun `Key value repo should properly store the forex rate`() = runTest { + val repository = di.get() - val forexRate = - ForexRate( - timestamp = OffsetDateTime.now().withOffsetSameInstant(ZoneOffset.UTC), - rates = listOf(ConversionRate("USD", "$", "US Dollar", 1.1f)), - ) - repository.saveForexRate(forexRate) + val forexRate = + ForexRate( + timestamp = OffsetDateTime.now().withOffsetSameInstant(ZoneOffset.UTC), + rates = listOf(ConversionRate("USD", "$", "US Dollar", 1.1f)), + ) + repository.saveForexRate(forexRate) - val savedForexRate = repository.getForexRate() + val savedForexRate = repository.getForexRate() - savedForexRate shouldBe forexRate - } + savedForexRate shouldBe forexRate + } @Test - fun `should return forex if there is data`() = - runTest { - val repository = di.get() + fun `should return forex if there is data`() = runTest { + val repository = di.get() - val forexRate = - ForexRate( - timestamp = OffsetDateTime.now().withOffsetSameInstant(ZoneOffset.UTC), - rates = listOf(ConversionRate("USD", "$", "US Dollar", 1.1f)), - ) + val forexRate = + ForexRate( + timestamp = OffsetDateTime.now().withOffsetSameInstant(ZoneOffset.UTC), + rates = listOf(ConversionRate("USD", "$", "US Dollar", 1.1f)), + ) - repository.saveForexRate(forexRate) + repository.saveForexRate(forexRate) - val response = - httpClient.get("/api/forex") - .send() - .coAwait() - .bodyAsJsonObject() + val response = + httpClient.get("/api/forex") + .send() + .coAwait() + .bodyAsJsonObject() - response.getJsonObject("data").also { data -> - OffsetDateTime.parse(data.getString("timestamp")) shouldBe forexRate.timestamp - data.getJsonArray("rates").also { rates -> - rates.size() shouldBe 1 - (rates.first() as JsonObject).also { rate -> - rate.getString("currency") shouldBe "USD" - rate.getString("symbol") shouldBe "$" - rate.getString("name") shouldBe "US Dollar" - rate.getFloat("rate") shouldBe 1.1f - } + response.getJsonObject("data").also { data -> + OffsetDateTime.parse(data.getString("timestamp")) shouldBe forexRate.timestamp + data.getJsonArray("rates").also { rates -> + rates.size() shouldBe 1 + (rates.first() as JsonObject).also { rate -> + rate.getString("currency") shouldBe "USD" + rate.getString("symbol") shouldBe "$" + rate.getString("name") shouldBe "US Dollar" + rate.getFloat("rate") shouldBe 1.1f } } } + } @Test - fun `should return null if there is no data`() = - runTest { - val response = - httpClient.get("/api/forex") - .send() - .coAwait() - .bodyAsJsonObject() + fun `should return null if there is no data`() = runTest { + val response = + httpClient.get("/api/forex") + .send() + .coAwait() + .bodyAsJsonObject() - response.getJsonObject("data") shouldBe null - } + response.getJsonObject("data") shouldBe null + } } diff --git a/backend/src/test/kotlin/me/sujanpoudel/playdeals/api/health/DBCleanupTest.kt b/backend/src/test/kotlin/me/sujanpoudel/playdeals/api/health/DBCleanupTest.kt index 135f6e8..3d82deb 100644 --- a/backend/src/test/kotlin/me/sujanpoudel/playdeals/api/health/DBCleanupTest.kt +++ b/backend/src/test/kotlin/me/sujanpoudel/playdeals/api/health/DBCleanupTest.kt @@ -10,19 +10,18 @@ import org.junit.jupiter.api.Test class DBCleanupTest(vertx: Vertx) : IntegrationTest(vertx) { @Test - fun `Does cleanup`() = - runTest { - val sqlClient = di.get() + fun `Does cleanup`() = runTest { + val sqlClient = di.get() - sqlClient - .query(CLEAN_UP_DB_QUERY).execute() - .onFailure { it.printStackTrace() } + sqlClient + .query(CLEAN_UP_DB_QUERY).execute() + .onFailure { it.printStackTrace() } - val totalDeals = - sqlClient.preparedQuery("""select count(*) from deal """) - .execute() - .coAwait().first().getInteger(0) + val totalDeals = + sqlClient.preparedQuery("""select count(*) from deal """) + .execute() + .coAwait().first().getInteger(0) - totalDeals shouldBe 0 - } + totalDeals shouldBe 0 + } } diff --git a/backend/src/test/kotlin/me/sujanpoudel/playdeals/api/health/HealthTest.kt b/backend/src/test/kotlin/me/sujanpoudel/playdeals/api/health/HealthTest.kt index 8141c81..c0b4dd5 100644 --- a/backend/src/test/kotlin/me/sujanpoudel/playdeals/api/health/HealthTest.kt +++ b/backend/src/test/kotlin/me/sujanpoudel/playdeals/api/health/HealthTest.kt @@ -8,23 +8,21 @@ import org.junit.jupiter.api.Test class HealthTest(vertx: Vertx) : IntegrationTest(vertx) { @Test - fun `GET liveness should return 200`() = - runTest { - val response = httpClient.get(8888, "localhost", "/health/liveness").send().coAwait() + fun `GET liveness should return 200`() = runTest { + val response = httpClient.get(8888, "localhost", "/health/liveness").send().coAwait() - response.statusCode() shouldBe 200 - val responseJson = response.bodyAsJsonObject() - responseJson.getString("status") shouldBe "UP" - } + response.statusCode() shouldBe 200 + val responseJson = response.bodyAsJsonObject() + responseJson.getString("status") shouldBe "UP" + } @Test - fun `GET readiness should return 200`() = - runTest { - val response = httpClient.get(8888, "localhost", "/health/readiness").send().coAwait() + fun `GET readiness should return 200`() = runTest { + val response = httpClient.get(8888, "localhost", "/health/readiness").send().coAwait() - val responseJson = response.bodyAsJsonObject() - response.statusCode() shouldBe 200 - responseJson.getString("status") shouldBe "UP" - responseJson.getString("outcome") shouldBe "UP" - } + val responseJson = response.bodyAsJsonObject() + response.statusCode() shouldBe 200 + responseJson.getString("status") shouldBe "UP" + responseJson.getString("outcome") shouldBe "UP" + } } diff --git a/backend/src/test/kotlin/me/sujanpoudel/playdeals/repositories/CachingDealRepositoryTest.kt b/backend/src/test/kotlin/me/sujanpoudel/playdeals/repositories/CachingDealRepositoryTest.kt index 9eecc7d..312c1dc 100644 --- a/backend/src/test/kotlin/me/sujanpoudel/playdeals/repositories/CachingDealRepositoryTest.kt +++ b/backend/src/test/kotlin/me/sujanpoudel/playdeals/repositories/CachingDealRepositoryTest.kt @@ -29,108 +29,104 @@ class CachingDealRepositoryTest(vertx: Vertx) : IntegrationTest(vertx) { } @Test - fun `should delegate new deal call`() = - runTest { - val dealEntity = mockk() - val newDeal = mockk() + fun `should delegate new deal call`() = runTest { + val dealEntity = mockk() + val newDeal = mockk() - coEvery { persistentDealRepository.upsert(any()) } returns dealEntity + coEvery { persistentDealRepository.upsert(any()) } returns dealEntity - repository.upsert(newDeal) shouldBe dealEntity + repository.upsert(newDeal) shouldBe dealEntity - coVerify(exactly = 1) { - persistentDealRepository.upsert(newDeal) - } + coVerify(exactly = 1) { + persistentDealRepository.upsert(newDeal) } + } @Test - fun `should cache newly added deal`() = - runTest { - val dealEntity = mockk() - val newDeal = mockk() + fun `should cache newly added deal`() = runTest { + val dealEntity = mockk() + val newDeal = mockk() - every { newDeal.id } returns "1" - every { dealEntity.id } returns newDeal.id - coEvery { persistentDealRepository.upsert(any()) } returns dealEntity - coEvery { persistentDealRepository.getAll(any(), any()) } returns emptyList() + every { newDeal.id } returns "1" + every { dealEntity.id } returns newDeal.id + coEvery { persistentDealRepository.upsert(any()) } returns dealEntity + coEvery { persistentDealRepository.getAll(any(), any()) } returns emptyList() - repository.upsert(newDeal) + repository.upsert(newDeal) - repository.getAll(0, Int.MAX_VALUE).shouldContainExactly(dealEntity) + repository.getAll(0, Int.MAX_VALUE).shouldContainExactly(dealEntity) - clearAllMocks() + clearAllMocks() - coVerify(exactly = 0) { - persistentDealRepository.getAll(any(), any()) - } + coVerify(exactly = 0) { + persistentDealRepository.getAll(any(), any()) } + } @Test - fun `should remove entry when deal is deleted`() = - runTest { - val entity1 = mockk() - val entity2 = mockk() + fun `should remove entry when deal is deleted`() = runTest { + val entity1 = mockk() + val entity2 = mockk() - val newDeal1 = mockk() - val newDeal2 = mockk() + val newDeal1 = mockk() + val newDeal2 = mockk() - every { newDeal1.id } returns "1" - every { newDeal2.id } returns "2" + every { newDeal1.id } returns "1" + every { newDeal2.id } returns "2" - every { entity1.id } returns newDeal1.id - every { entity2.id } returns newDeal2.id + every { entity1.id } returns newDeal1.id + every { entity2.id } returns newDeal2.id - coEvery { persistentDealRepository.upsert(newDeal1) } returns entity1 - coEvery { persistentDealRepository.upsert(newDeal2) } returns entity2 + coEvery { persistentDealRepository.upsert(newDeal1) } returns entity1 + coEvery { persistentDealRepository.upsert(newDeal2) } returns entity2 - coEvery { persistentDealRepository.delete("1") } returns entity1 - coEvery { persistentDealRepository.delete("2") } returns entity2 + coEvery { persistentDealRepository.delete("1") } returns entity1 + coEvery { persistentDealRepository.delete("2") } returns entity2 - coEvery { persistentDealRepository.getAll(any(), any()) } returns emptyList() + coEvery { persistentDealRepository.getAll(any(), any()) } returns emptyList() - repository.upsert(newDeal1) - repository.upsert(newDeal2) + repository.upsert(newDeal1) + repository.upsert(newDeal2) - repository.getAll(0, Int.MAX_VALUE).shouldContainExactly(entity1, entity2) + repository.getAll(0, Int.MAX_VALUE).shouldContainExactly(entity1, entity2) - repository.delete(entity2.id) + repository.delete(entity2.id) - repository.getAll(0, Int.MAX_VALUE).shouldContainExactly(entity1) - } + repository.getAll(0, Int.MAX_VALUE).shouldContainExactly(entity1) + } @Test - fun `should respect skip and take`() = - runTest { - val entity1 = mockk() - val entity2 = mockk() - val entity3 = mockk() + fun `should respect skip and take`() = runTest { + val entity1 = mockk() + val entity2 = mockk() + val entity3 = mockk() - val newDeal1 = mockk() - val newDeal2 = mockk() - val newDeal3 = mockk() + val newDeal1 = mockk() + val newDeal2 = mockk() + val newDeal3 = mockk() - every { newDeal1.id } returns "1" - every { newDeal2.id } returns "2" - every { newDeal3.id } returns "3" + every { newDeal1.id } returns "1" + every { newDeal2.id } returns "2" + every { newDeal3.id } returns "3" - every { entity1.id } returns newDeal1.id - every { entity2.id } returns newDeal2.id - every { entity3.id } returns newDeal3.id + every { entity1.id } returns newDeal1.id + every { entity2.id } returns newDeal2.id + every { entity3.id } returns newDeal3.id - coEvery { persistentDealRepository.upsert(newDeal1) } returns entity1 - coEvery { persistentDealRepository.upsert(newDeal2) } returns entity2 - coEvery { persistentDealRepository.upsert(newDeal3) } returns entity3 + coEvery { persistentDealRepository.upsert(newDeal1) } returns entity1 + coEvery { persistentDealRepository.upsert(newDeal2) } returns entity2 + coEvery { persistentDealRepository.upsert(newDeal3) } returns entity3 - coEvery { persistentDealRepository.getAll(any(), any()) } returns emptyList() + coEvery { persistentDealRepository.getAll(any(), any()) } returns emptyList() - repository.upsert(newDeal1) - repository.upsert(newDeal2) - repository.upsert(newDeal3) + repository.upsert(newDeal1) + repository.upsert(newDeal2) + repository.upsert(newDeal3) - println(repository.getAll(0, Int.MAX_VALUE)) + println(repository.getAll(0, Int.MAX_VALUE)) - repository.getAll(0, 1).shouldContainExactly(entity1) - repository.getAll(1, 1).shouldContainExactly(entity2) - repository.getAll(2, 1).shouldContainExactly(entity3) - } + repository.getAll(0, 1).shouldContainExactly(entity1) + repository.getAll(1, 1).shouldContainExactly(entity2) + repository.getAll(2, 1).shouldContainExactly(entity3) + } } diff --git a/backend/src/test/kotlin/me/sujanpoudel/playdeals/repositories/PersistentDealRepositoryTest.kt b/backend/src/test/kotlin/me/sujanpoudel/playdeals/repositories/PersistentDealRepositoryTest.kt index ad2f0be..69a81d2 100644 --- a/backend/src/test/kotlin/me/sujanpoudel/playdeals/repositories/PersistentDealRepositoryTest.kt +++ b/backend/src/test/kotlin/me/sujanpoudel/playdeals/repositories/PersistentDealRepositoryTest.kt @@ -41,108 +41,100 @@ class PersistentDealRepositoryTest(vertx: Vertx) : IntegrationTest(vertx) { ) @Test - fun `should create new app deal in db`() = - runTest { - val appDeal = repository.upsert(newDeal) + fun `should create new app deal in db`() = runTest { + val appDeal = repository.upsert(newDeal) - val appDealFromDb = - sqlClient.preparedQuery(""" SELECT * from "deal" where id=$1""") - .exec(newDeal.id) - .coAwait() - .first() - .asAppDeal() + val appDealFromDb = + sqlClient.preparedQuery(""" SELECT * from "deal" where id=$1""") + .exec(newDeal.id) + .coAwait() + .first() + .asAppDeal() - appDeal.shouldBeEqualToComparingFields(appDealFromDb) - } + appDeal.shouldBeEqualToComparingFields(appDealFromDb) + } @Test - fun `should perform update if item with id already exists`() = - runTest { - repository.upsert(newDeal) + fun `should perform update if item with id already exists`() = runTest { + repository.upsert(newDeal) - repository.upsert(newDeal.copy(name = "Updated Name")) + repository.upsert(newDeal.copy(name = "Updated Name")) - val appDealFromDb = - sqlClient.preparedQuery(""" SELECT * from "deal" where id=$1""") - .exec(newDeal.id) - .coAwait() - .first() - .asAppDeal() + val appDealFromDb = + sqlClient.preparedQuery(""" SELECT * from "deal" where id=$1""") + .exec(newDeal.id) + .coAwait() + .first() + .asAppDeal() - appDealFromDb.name.shouldBe("Updated Name") - } + appDealFromDb.name.shouldBe("Updated Name") + } @Test - fun `should delete app deal in db`() = - runTest { - repository.upsert(newDeal) - repository.delete(newDeal.id) + fun `should delete app deal in db`() = runTest { + repository.upsert(newDeal) + repository.delete(newDeal.id) - sqlClient.preparedQuery("""SELECT * from "deal" where id=$1""") - .exec(newDeal.id) - .coAwait() - .rowCount() shouldBe 0 - } + sqlClient.preparedQuery("""SELECT * from "deal" where id=$1""") + .exec(newDeal.id) + .coAwait() + .rowCount() shouldBe 0 + } @Test - fun `should be able to get all app deals from db`() = - runTest { - val deal0 = repository.upsert(newDeal) - val deal1 = repository.upsert(newDeal.copy(id = "id_1")) + fun `should be able to get all app deals from db`() = runTest { + val deal0 = repository.upsert(newDeal) + val deal1 = repository.upsert(newDeal.copy(id = "id_1")) - val appDeal = repository.getAll(0, 100) + val appDeal = repository.getAll(0, 100) - appDeal shouldContainAll listOf(deal0, deal1) - } + appDeal shouldContainAll listOf(deal0, deal1) + } @Test - fun `should be able to get all app deals from db in order`() = - runTest { - val deal0 = repository.upsert(newDeal) - val deal1 = repository.upsert(newDeal.copy(id = "id_1")) + fun `should be able to get all app deals from db in order`() = runTest { + val deal0 = repository.upsert(newDeal) + val deal1 = repository.upsert(newDeal.copy(id = "id_1")) - val appDeal = repository.getAll(0, 100) + val appDeal = repository.getAll(0, 100) - appDeal shouldContainInOrder listOf(deal1, deal0) - } + appDeal shouldContainInOrder listOf(deal1, deal0) + } @Test - fun `should get deals added after a time`() = - runTest { - val deal0 = repository.upsert(newDeal) + fun `should get deals added after a time`() = runTest { + val deal0 = repository.upsert(newDeal) - val now = OffsetDateTime.now() + val now = OffsetDateTime.now() - val deal1 = repository.upsert(newDeal.copy(id = "id_1")) - val deal2 = repository.upsert(newDeal.copy(id = "id_2")) + val deal1 = repository.upsert(newDeal.copy(id = "id_1")) + val deal2 = repository.upsert(newDeal.copy(id = "id_2")) - repository.upsert(newDeal.copy(id = "id_2")) + repository.upsert(newDeal.copy(id = "id_2")) - val count = repository.getNewDeals(now) + val count = repository.getNewDeals(now) - count.shouldContainAll(deal1, deal2) - } + count.shouldContainAll(deal1, deal2) + } @Test - fun `getDealByPackageName should return correct deal by packageName`() = - runTest { - val deal0 = repository.upsert(newDeal) + fun `getDealByPackageName should return correct deal by packageName`() = runTest { + val deal0 = repository.upsert(newDeal) - repository.upsert(newDeal.copy(id = "id_1")) - repository.upsert(newDeal.copy(id = "id_2")) + repository.upsert(newDeal.copy(id = "id_1")) + repository.upsert(newDeal.copy(id = "id_2")) - val deal01 = repository.getDealByPackageName(deal0.id) + val deal01 = repository.getDealByPackageName(deal0.id) - deal0 shouldBe deal01 - } + deal0 shouldBe deal01 + } @Test - fun `getDealByPackageName should return null when there is no deal`() = - runTest { - val deal0 = repository.upsert(newDeal) + fun `getDealByPackageName should return null when there is no deal`() = runTest { + val deal0 = repository.upsert(newDeal) - val deal1 = repository.getDealByPackageName("id_3") + val deal1 = repository.getDealByPackageName("id_3") - deal1 shouldBe null - } + deal1 shouldBe null + } } diff --git a/backend/src/test/kotlin/me/sujanpoudel/playdeals/repositories/PersistentKeyValueRepositoryTest.kt b/backend/src/test/kotlin/me/sujanpoudel/playdeals/repositories/PersistentKeyValueRepositoryTest.kt index 26c6766..fd488b0 100644 --- a/backend/src/test/kotlin/me/sujanpoudel/playdeals/repositories/PersistentKeyValueRepositoryTest.kt +++ b/backend/src/test/kotlin/me/sujanpoudel/playdeals/repositories/PersistentKeyValueRepositoryTest.kt @@ -16,48 +16,45 @@ class PersistentKeyValueRepositoryTest(vertx: Vertx) : IntegrationTest(vertx) { private val sqlClient by lazy { di.get() } @Test - fun `should create new entry on db`() = - runTest { - val value = repository.set(KEY, "test") + fun `should create new entry on db`() = runTest { + val value = repository.set(KEY, "test") - val valueFromDb = - sqlClient.preparedQuery(""" SELECT * from "key_value_store" where key=$1""") - .exec(KEY) - .coAwait() - .first() - .getString("value") + val valueFromDb = + sqlClient.preparedQuery(""" SELECT * from "key_value_store" where key=$1""") + .exec(KEY) + .coAwait() + .first() + .getString("value") - value shouldBe valueFromDb - } + value shouldBe valueFromDb + } @Test - fun `should perform update if item with id already exists`() = - runTest { - repository.set(KEY, "test") + fun `should perform update if item with id already exists`() = runTest { + repository.set(KEY, "test") - val updated = repository.set(KEY, "test1") + val updated = repository.set(KEY, "test1") - val fromDb = - sqlClient.preparedQuery(""" SELECT * from "key_value_store" where key=$1""") - .exec(KEY) - .coAwait() - .first() - .value() + val fromDb = + sqlClient.preparedQuery(""" SELECT * from "key_value_store" where key=$1""") + .exec(KEY) + .coAwait() + .first() + .value() - fromDb shouldBe updated - } + fromDb shouldBe updated + } @Test - fun `should be able to serialize unknown types`() = - runTest { - val value = OffsetDateTime.now() + fun `should be able to serialize unknown types`() = runTest { + val value = OffsetDateTime.now() - repository.set(KEY, value.toString()) + repository.set(KEY, value.toString()) - val fromDb = repository.get(KEY).let(OffsetDateTime::parse) + val fromDb = repository.get(KEY).let(OffsetDateTime::parse) - fromDb shouldBe value - } + fromDb shouldBe value + } companion object { const val KEY = "test_key" diff --git a/build.gradle.kts b/build.gradle.kts index c4aaf24..f951076 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,8 @@ @file:Suppress("UnstableApiUsage") import org.gradle.api.tasks.testing.logging.TestLogEvent +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import org.jlleitschuh.gradle.ktlint.KtlintExtension import org.jlleitschuh.gradle.ktlint.KtlintPlugin plugins { @@ -21,17 +23,34 @@ allprojects { task("preCommitHook") { dependsOn(tasks.ktlintCheck) } + + extensions.configure { + version = rootProject.libs.versions.ktlint.get() + enableExperimentalRules = false + coloredOutput = true + + filter { + exclude { + it.file.absoluteFile.startsWith(layout.buildDirectory.asFile.get().absolutePath) + } + } + } + + tasks.withType { + compilerOptions { + allWarningsAsErrors.set(true) + } + } } tasks.withType { useJUnitPlatform() testLogging { - events = - setOf( - TestLogEvent.PASSED, - TestLogEvent.SKIPPED, - TestLogEvent.FAILED, - ) + events = setOf( + TestLogEvent.PASSED, + TestLogEvent.SKIPPED, + TestLogEvent.FAILED, + ) } }