Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

M7l1 repo #28

Merged
merged 7 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", ve
coroutines-reactor = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-reactor", version.ref = "coroutines" }
coroutines-reactive = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-reactive", version.ref = "coroutines" }
cor = { module = "ru.otus.otuskotlin.marketplace.libs:ok-marketplace-lib-cor" }
uuid = "com.benasher44:uuid:0.8.4"

jackson-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson" }
jackson-datatype = { module = "com.fasterxml.jackson.datatype:jackson-datatype-jsr310", version.ref = "jackson" }
Expand Down Expand Up @@ -74,11 +75,15 @@ spring-actuator = { module = "org.springframework.boot:spring-boot-starter-actua
spring-webflux = { module = "org.springframework.boot:spring-boot-starter-webflux" }
spring-webflux-ui = { module = "org.springdoc:springdoc-openapi-starter-webflux-ui", version = "2.3.0" }
spring-test = { module = "org.springframework.boot:spring-boot-starter-test" }
spring-mockk = "com.ninja-squad:springmockk:4.0.2"

# Message Queues
rabbitmq-client = { module = "com.rabbitmq:amqp-client", version = "5.20.0" }
kafka-client = { module = "org.apache.kafka:kafka-clients", version = "3.7.0" }

# Databases
db-cache4k = "io.github.reactivecircus.cache4k:cache4k:0.13.0"

# Testing
kotest-junit5 = { module = "io.kotest:kotest-runner-junit5", version.ref = "kotest" }
kotest-core = { module = "io.kotest:kotest-assertions-core", version.ref = "kotest" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ fun MkplContext.fromTransport(request: AdOffersRequest) {
}

private fun AdSearchFilter?.toInternal(): MkplAdFilter = MkplAdFilter(
searchString = this?.searchString ?: ""
searchString = this?.searchString ?: "",
ownerId = this?.ownerId?.let { MkplUserId(it) } ?: MkplUserId.NONE,
dealSide = this?.adType.fromTransport(),
)

private fun AdCreateObject.toInternal(): MkplAd = MkplAd(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package ru.otus.otuskotlin.marketplace.mappers.v1

import ru.otus.otuskotlin.marketplace.api.v1.models.*
import ru.otus.otuskotlin.marketplace.common.models.*

fun MkplAd.toTransportCreate() = AdCreateObject(
title = title.takeIf { it.isNotBlank() },
description = description.takeIf { it.isNotBlank() },
adType = adType.toTransportAd(),
visibility = visibility.toTransportAd(),
)

fun MkplAd.toTransportRead() = AdReadObject(
id = id.takeIf { it != MkplAdId.NONE }?.asString(),
)

fun MkplAd.toTransportUpdate() = AdUpdateObject(
id = id.takeIf { it != MkplAdId.NONE }?.asString(),
title = title.takeIf { it.isNotBlank() },
description = description.takeIf { it.isNotBlank() },
adType = adType.toTransportAd(),
visibility = visibility.toTransportAd(),
lock = lock.takeIf { it != MkplAdLock.NONE }?.asString(),
)

fun MkplAd.toTransportDelete() = AdDeleteObject(
id = id.takeIf { it != MkplAdId.NONE }?.asString(),
lock = lock.takeIf { it != MkplAdLock.NONE }?.asString(),
)
Original file line number Diff line number Diff line change
Expand Up @@ -91,32 +91,32 @@ private fun MkplAdPermissionClient.toTransportAd() = when (this) {
MkplAdPermissionClient.DELETE -> AdPermissions.DELETE
}

private fun MkplVisibility.toTransportAd(): AdVisibility? = when (this) {
internal fun MkplVisibility.toTransportAd(): AdVisibility? = when (this) {
MkplVisibility.VISIBLE_PUBLIC -> AdVisibility.PUBLIC
MkplVisibility.VISIBLE_TO_GROUP -> AdVisibility.REGISTERED_ONLY
MkplVisibility.VISIBLE_TO_OWNER -> AdVisibility.OWNER_ONLY
MkplVisibility.NONE -> null
}

private fun MkplDealSide.toTransportAd(): DealSide? = when (this) {
internal fun MkplDealSide.toTransportAd(): DealSide? = when (this) {
MkplDealSide.DEMAND -> DealSide.DEMAND
MkplDealSide.SUPPLY -> DealSide.SUPPLY
MkplDealSide.NONE -> null
}

private fun List<MkplError>.toTransportErrors(): List<Error>? = this
internal fun List<MkplError>.toTransportErrors(): List<Error>? = this
.map { it.toTransportAd() }
.toList()
.takeIf { it.isNotEmpty() }

private fun MkplError.toTransportAd() = Error(
internal fun MkplError.toTransportAd() = Error(
code = code.takeIf { it.isNotBlank() },
group = group.takeIf { it.isNotBlank() },
field = field.takeIf { it.isNotBlank() },
message = message.takeIf { it.isNotBlank() },
)

private fun MkplState.toResult(): ResponseResult? = when (this) {
internal fun MkplState.toResult(): ResponseResult? = when (this) {
MkplState.RUNNING -> ResponseResult.SUCCESS
MkplState.FAILING -> ResponseResult.ERROR
MkplState.FINISHING -> ResponseResult.SUCCESS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@ fun MkplContext.fromTransport(request: AdOffersRequest) {
}

private fun AdSearchFilter?.toInternal(): MkplAdFilter = MkplAdFilter(
searchString = this?.searchString ?: ""
searchString = this?.searchString ?: "",
ownerId = this?.ownerId?.let { MkplUserId(it) } ?: MkplUserId.NONE,
dealSide = this?.adType.fromTransport(),
)

private fun AdCreateObject.toInternal(): MkplAd = MkplAd(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package ru.otus.otuskotlin.marketplace.api.v2.mappers

import ru.otus.otuskotlin.marketplace.api.v2.models.*
import ru.otus.otuskotlin.marketplace.common.models.*

fun MkplAd.toTransportCreate() = AdCreateObject(
title = title.takeIf { it.isNotBlank() },
description = description.takeIf { it.isNotBlank() },
adType = adType.toTransportAd(),
visibility = visibility.toTransportAd(),
)

fun MkplAd.toTransportRead() = AdReadObject(
id = id.takeIf { it != MkplAdId.NONE }?.asString(),
)

fun MkplAd.toTransportUpdate() = AdUpdateObject(
id = id.takeIf { it != MkplAdId.NONE }?.asString(),
title = title.takeIf { it.isNotBlank() },
description = description.takeIf { it.isNotBlank() },
adType = adType.toTransportAd(),
visibility = visibility.toTransportAd(),
lock = lock.takeIf { it != MkplAdLock.NONE }?.asString(),
)

fun MkplAd.toTransportDelete() = AdDeleteObject(
id = id.takeIf { it != MkplAdId.NONE }?.asString(),
lock = lock.takeIf { it != MkplAdLock.NONE }?.asString(),
)
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,14 @@ private fun MkplAdPermissionClient.toTransportAd() = when (this) {
MkplAdPermissionClient.DELETE -> AdPermissions.DELETE
}

private fun MkplVisibility.toTransportAd(): AdVisibility? = when (this) {
internal fun MkplVisibility.toTransportAd(): AdVisibility? = when (this) {
MkplVisibility.VISIBLE_PUBLIC -> AdVisibility.PUBLIC
MkplVisibility.VISIBLE_TO_GROUP -> AdVisibility.REGISTERED_ONLY
MkplVisibility.VISIBLE_TO_OWNER -> AdVisibility.OWNER_ONLY
MkplVisibility.NONE -> null
}

private fun MkplDealSide.toTransportAd(): DealSide? = when (this) {
internal fun MkplDealSide.toTransportAd(): DealSide? = when (this) {
MkplDealSide.DEMAND -> DealSide.DEMAND
MkplDealSide.SUPPLY -> DealSide.SUPPLY
MkplDealSide.NONE -> null
Expand Down
7 changes: 7 additions & 0 deletions ok-marketplace-be/ok-marketplace-app-ktor/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ kotlin {
implementation(libs.kotlinx.serialization.json)
implementation(libs.ktor.serialization.json)

// DB
implementation(projects.okMarketplaceRepoStubs)
implementation(projects.okMarketplaceRepoInmemory)

// logging
implementation(project(":ok-marketplace-api-log1"))
implementation("ru.otus.otuskotlin.marketplace.libs:ok-marketplace-lib-logging-common")
Expand All @@ -88,6 +92,9 @@ kotlin {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))

// DB
implementation(projects.okMarketplaceRepoCommon)

implementation(libs.ktor.server.test)
implementation(libs.ktor.client.negotiation)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ data class MkplAppSettings(
val appUrls: List<String> = emptyList(),
override val corSettings: MkplCorSettings = MkplCorSettings(),
override val processor: MkplAdProcessor = MkplAdProcessor(corSettings),
): IMkplAppSettings
) : IMkplAppSettings
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@ package ru.otus.otuskotlin.marketplace.app.ktor.plugins
import io.ktor.server.application.*
import ru.otus.otuskotlin.marketplace.app.ktor.MkplAppSettings
import ru.otus.otuskotlin.marketplace.app.ktor.base.KtorWsSessionRepo
import ru.otus.otuskotlin.marketplace.backend.repository.inmemory.AdRepoStub
import ru.otus.otuskotlin.marketplace.biz.MkplAdProcessor
import ru.otus.otuskotlin.marketplace.common.MkplCorSettings
import ru.otus.otuskotlin.marketplace.repo.inmemory.AdRepoInMemory

fun Application.initAppSettings(): MkplAppSettings {
val corSettings = MkplCorSettings(
loggerProvider = getLoggerProviderConf(),
wsSessions = KtorWsSessionRepo(),
repoTest = AdRepoInMemory(),
repoProd = AdRepoInMemory(),
repoStub = AdRepoStub(),
)
return MkplAppSettings(
appUrls = environment.config.propertyOrNull("ktor.urls")?.getList() ?: emptyList(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package ru.otus.otuskotlin.marketplace.app.ktor.repo

import io.ktor.client.call.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.testing.*
import ru.otus.otuskotlin.marketplace.api.v2.apiV2Mapper
import ru.otus.otuskotlin.marketplace.api.v2.mappers.toTransportCreate
import ru.otus.otuskotlin.marketplace.api.v2.mappers.toTransportDelete
import ru.otus.otuskotlin.marketplace.api.v2.mappers.toTransportRead
import ru.otus.otuskotlin.marketplace.api.v2.mappers.toTransportUpdate
import ru.otus.otuskotlin.marketplace.api.v2.models.*
import ru.otus.otuskotlin.marketplace.app.ktor.MkplAppSettings
import ru.otus.otuskotlin.marketplace.app.ktor.module
import ru.otus.otuskotlin.marketplace.common.models.MkplAdId
import ru.otus.otuskotlin.marketplace.common.models.MkplAdLock
import ru.otus.otuskotlin.marketplace.common.models.MkplDealSide
import ru.otus.otuskotlin.marketplace.stubs.MkplAdStub
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals

abstract class V2AdRepoBaseTest {
abstract val workMode: AdRequestDebugMode
abstract val appSettingsCreate: MkplAppSettings
abstract val appSettingsRead: MkplAppSettings
abstract val appSettingsUpdate: MkplAppSettings
abstract val appSettingsDelete: MkplAppSettings
abstract val appSettingsSearch: MkplAppSettings
abstract val appSettingsOffers: MkplAppSettings

protected val uuidOld = "10000000-0000-0000-0000-000000000001"
protected val uuidNew = "10000000-0000-0000-0000-000000000002"
protected val uuidSup = "10000000-0000-0000-0000-000000000003"
protected val initAd = MkplAdStub.prepareResult {
id = MkplAdId(uuidOld)
adType = MkplDealSide.DEMAND
lock = MkplAdLock(uuidOld)
}
protected val initAdSupply = MkplAdStub.prepareResult {
id = MkplAdId(uuidSup)
adType = MkplDealSide.SUPPLY
}


@Test
fun create() {
val ad = initAd.toTransportCreate()
v2TestApplication(
conf = appSettingsCreate,
func = "create",
request = AdCreateRequest(
ad = ad,
debug = AdDebug(mode = workMode),
),
) { response ->
val responseObj = response.body<AdCreateResponse>()
assertEquals(200, response.status.value)
assertEquals(uuidNew, responseObj.ad?.id)
assertEquals(ad.title, responseObj.ad?.title)
assertEquals(ad.description, responseObj.ad?.description)
assertEquals(ad.adType, responseObj.ad?.adType)
assertEquals(ad.visibility, responseObj.ad?.visibility)
}
}

@Test
fun read() {
val ad = initAd.toTransportRead()
v2TestApplication(
conf = appSettingsRead,
func = "read",
request = AdReadRequest(
ad = ad,
debug = AdDebug(mode = workMode),
),
) { response ->
val responseObj = response.body<AdReadResponse>()
assertEquals(200, response.status.value)
assertEquals(uuidOld, responseObj.ad?.id)
}
}

@Test
fun update() {
val ad = initAd.toTransportUpdate()
v2TestApplication(
conf = appSettingsUpdate,
func = "update",
request = AdUpdateRequest(
ad = ad,
debug = AdDebug(mode = workMode),
),
) { response ->
val responseObj = response.body<AdUpdateResponse>()
assertEquals(200, response.status.value)
assertEquals(ad.id, responseObj.ad?.id)
assertEquals(ad.title, responseObj.ad?.title)
assertEquals(ad.description, responseObj.ad?.description)
assertEquals(ad.adType, responseObj.ad?.adType)
assertEquals(ad.visibility, responseObj.ad?.visibility)
}
}
@Test
fun delete() {
val ad = initAd.toTransportDelete()
v2TestApplication(
conf = appSettingsDelete,
func = "delete",
request = AdDeleteRequest(
ad = ad,
debug = AdDebug(mode = workMode),
),
) { response ->
val responseObj = response.body<AdDeleteResponse>()
assertEquals(200, response.status.value)
assertEquals(uuidOld, responseObj.ad?.id)
}
}

@Test
fun search() = v2TestApplication(
conf = appSettingsSearch,
func = "search",
request = AdSearchRequest(
adFilter = AdSearchFilter(),
debug = AdDebug(mode = workMode),
),
) { response ->
val responseObj = response.body<AdSearchResponse>()
assertEquals(200, response.status.value)
assertNotEquals(0, responseObj.ads?.size)
assertEquals(uuidOld, responseObj.ads?.first()?.id)
}

@Test
fun offers() = v2TestApplication(
conf = appSettingsOffers,
func = "offers",
request = AdOffersRequest(
ad = initAd.toTransportRead(),
debug = AdDebug(mode = workMode),
),
) { response ->
val responseObj = response.body<AdOffersResponse>()
assertEquals(200, response.status.value)
assertNotEquals(0, responseObj.ads?.size)
assertEquals(uuidSup, responseObj.ads?.first()?.id)
}

private inline fun <reified T: IRequest> v2TestApplication(
conf: MkplAppSettings,
func: String,
request: T,
crossinline function: suspend (HttpResponse) -> Unit,
): Unit = testApplication {
application { module(appSettings = conf) }
val client = createClient {
install(ContentNegotiation) {
json(apiV2Mapper)
}
}
val response = client.post("/v2/ad/$func") {
contentType(ContentType.Application.Json)
header("X-Trace-Id", "12345")
setBody(request)
}
function(response)
}
}
Loading
Loading