diff --git a/README.md b/README.md index c4352a8..f670347 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,11 @@ # Шаблон приложения Kora Kotlin CRUD -Пример Kotlin сервиса реализованного на Kora с HTTP [CRUD](https://github.com/swagger-api/swagger-petstore) API, -в качестве базы данных выступает Postgres, используется кэш Caffeine, а также другие модули которые использовались бы в реальном приложении в бою. +Шаблон для быстрого старта нового проекта на Kotlin и Kora с базовым настроенным HTTP [CRUD](https://github.com/swagger-api/swagger-petstore) API для одной сущности. +В качестве базы данных выступает Postgres, используется кэш Caffeine, +а также другие модули которые использовались бы в реальном приложении в бою. -В примере использовались модули: +В шаблоне используются модули: - [HTTP сервер](https://kora-projects.github.io/kora-docs/ru/documentation/http-server/) - [OpenAPI HTTP серверная генерация](https://kora-projects.github.io/kora-docs/ru/documentation/openapi-codegen/) - [Пробы](https://kora-projects.github.io/kora-docs/ru/documentation/probes/) @@ -18,7 +19,7 @@ ## Build -Собрать классы (может потребоваться запустить 2 раза из-за APT): +Собрать классы (может потребоваться запустить 2 раза из-за Kotlin APT & KSP): ```shell ./gradlew classes @@ -37,6 +38,13 @@ ./gradlew openApiGenerateHttpServer ``` +### Image + +Собрать образ приложения: +```shell +docker build -t kora-kotlin-crud . +``` + ## Run Запустить локально: @@ -44,6 +52,13 @@ ./gradlew run ``` +## Migration + +Миграции вызываются с помощью Flyway Gradle Plugin: +```shell +./gradlew flywayMigrate +``` + ## Test Тесты используют [Testcontainers](https://java.testcontainers.org/), требуется [Docker](https://docs.docker.com/engine/install/) окружение для запуска тестов или аналогичные контейнерные окружения ([colima](https://github.com/abiosoft/colima) / итп) diff --git a/build.gradle.kts b/build.gradle.kts index 5cc5747..3569afc 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,5 @@ import com.google.devtools.ksp.gradle.KspTask +import org.gradle.api.tasks.testing.logging.TestExceptionFormat import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.openapitools.generator.gradle.plugin.tasks.GenerateTask @@ -21,11 +22,6 @@ plugins { group = property("groupId")!! version = property("koraVersion")!! -application { - applicationName = "application" - mainClass.set("ru.tinkoff.kora.kotlin.crud.ApplicationKt") -} - kotlin { jvmToolchain { languageVersion.set(JavaLanguageVersion.of(17)) } sourceSets.main { kotlin.srcDir("build/generated/openapi") } @@ -37,13 +33,13 @@ kotlin { val koraBom: Configuration by configurations.creating configurations { ksp.get().extendsFrom(koraBom) + compileOnly.get().extendsFrom(koraBom) api.get().extendsFrom(koraBom) implementation.get().extendsFrom(koraBom) } repositories { mavenCentral() - maven("https://oss.sonatype.org/content/repositories/snapshots") } dependencies { @@ -73,8 +69,31 @@ dependencies { testImplementation("io.mockk:mockk:1.13.8") testImplementation("ru.tinkoff.kora:test-junit5") - testImplementation("io.goodforgod:testcontainers-extensions-postgres:0.11.0") - testImplementation("org.testcontainers:junit-jupiter:1.17.6") + testImplementation("io.goodforgod:testcontainers-extensions-postgres:0.12.0") + testImplementation("org.testcontainers:junit-jupiter:1.19.8") +} + +application { + applicationName = "application" + mainClass.set("ru.tinkoff.kora.kotlin.crud.ApplicationKt") + applicationDefaultJvmArgs = listOf("-Dfile.encoding=UTF-8") +} + +tasks.distTar { + archiveFileName.set("application.tar") +} + +val postgresHost: String by project +val postgresPort: String by project +val postgresDatabase: String by project +val postgresUser: String by project +val postgresPassword: String by project +tasks.withType { + environment( + "POSTGRES_JDBC_URL" to "jdbc:postgresql://${postgresHost}:${postgresPort}/${postgresDatabase}", + "POSTGRES_USER" to postgresUser, + "POSTGRES_PASS" to postgresPassword, + ) } tasks.register("openApiGenerateHttpServer", GenerateTask::class) { @@ -101,38 +120,20 @@ tasks.withType { tasks.withType().configureEach { dependsOn(tasks.named("openApiGenerateHttpServer")) } -tasks.named("test") { + +tasks.test { dependsOn("distTar") -} -val postgresHost: String by project -val postgresPort: String by project -val postgresDatabase: String by project -val postgresUser: String by project -val postgresPassword: String by project -tasks.withType { - environment( - "POSTGRES_JDBC_URL" to "jdbc:postgresql://${postgresHost}:${postgresPort}/${postgresDatabase}", - "POSTGRES_USER" to postgresUser, - "POSTGRES_PASS" to postgresPassword, + jvmArgs( + "-XX:+TieredCompilation", + "-XX:TieredStopAtLevel=1", ) -} - -flyway { - url = "jdbc:postgresql://$postgresHost:$postgresPort/$postgresDatabase" - user = postgresUser - password = postgresPassword - locations = arrayOf("classpath:db/migration") -} - -tasks.distTar { - archiveFileName.set("application.tar") -} -tasks.test { useJUnitPlatform() testLogging { + showStandardStreams = true events("passed", "skipped", "failed") + exceptionFormat = TestExceptionFormat.FULL } reports { @@ -147,3 +148,10 @@ tasks.jacocoTestReport { html.outputLocation = layout.buildDirectory.dir("jacocoHtml") } } + +flyway { + url = "jdbc:postgresql://$postgresHost:$postgresPort/$postgresDatabase" + user = postgresUser + password = postgresPassword + locations = arrayOf("classpath:db/migration") +} diff --git a/gradle.properties b/gradle.properties index 9eefcd6..996c02c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ groupId=ru.tinkoff.kora -koraVersion=1.1.9 +koraVersion=1.1.11 ##### GRADLE ##### diff --git a/settings.gradle.kts b/settings.gradle.kts index d234286..1fb3f90 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,7 +2,6 @@ pluginManagement { repositories { gradlePluginPortal() mavenCentral() - maven("https://oss.sonatype.org/content/repositories/snapshots") } } diff --git a/src/main/java/ru/tinkoff/kora/kotlin/crud/controller/HttpExceptionHandler.kt b/src/main/java/ru/tinkoff/kora/kotlin/crud/controller/HttpExceptionHandler.kt index 4854d31..99f4ba6 100644 --- a/src/main/java/ru/tinkoff/kora/kotlin/crud/controller/HttpExceptionHandler.kt +++ b/src/main/java/ru/tinkoff/kora/kotlin/crud/controller/HttpExceptionHandler.kt @@ -1,4 +1,4 @@ -package ru.tinkoff.kora.kotlin.example.crud.controller +package ru.tinkoff.kora.kotlin.crud.controller import io.micrometer.core.instrument.config.validate.ValidationException import ru.tinkoff.kora.common.Component diff --git a/src/main/java/ru/tinkoff/kora/kotlin/crud/controller/PetDelegate.kt b/src/main/java/ru/tinkoff/kora/kotlin/crud/controller/PetDelegate.kt index cb948c6..a24dbc8 100644 --- a/src/main/java/ru/tinkoff/kora/kotlin/crud/controller/PetDelegate.kt +++ b/src/main/java/ru/tinkoff/kora/kotlin/crud/controller/PetDelegate.kt @@ -1,14 +1,13 @@ -package ru.tinkoff.kora.kotlin.example.crud.controller +package ru.tinkoff.kora.kotlin.crud.controller import ru.tinkoff.kora.common.Component +import ru.tinkoff.kora.kotlin.crud.model.PetMapper import ru.tinkoff.kora.kotlin.crud.openapi.http.server.api.PetApiDelegate import ru.tinkoff.kora.kotlin.crud.openapi.http.server.api.PetApiResponses import ru.tinkoff.kora.kotlin.crud.openapi.http.server.model.MessageTO import ru.tinkoff.kora.kotlin.crud.openapi.http.server.model.PetCreateTO import ru.tinkoff.kora.kotlin.crud.openapi.http.server.model.PetUpdateTO -import ru.tinkoff.kora.kotlin.example.crud.model.PetMapper -import ru.tinkoff.kora.kotlin.example.crud.model.PetWithCategory -import ru.tinkoff.kora.kotlin.example.crud.service.PetService +import ru.tinkoff.kora.kotlin.crud.service.PetService @Component class PetDelegate( @@ -23,7 +22,7 @@ class PetDelegate( val pet = petService.findByID(id) if (pet != null) { - val body = petMapper.petWithCategoryToPetTO(pet) + val body = petMapper.asDTO(pet) return PetApiResponses.GetPetByIdApiResponse.GetPetById200ApiResponse(body) } else { return PetApiResponses.GetPetByIdApiResponse.GetPetById404ApiResponse(notFound(id)) @@ -31,8 +30,8 @@ class PetDelegate( } override fun addPet(petCreateTO: PetCreateTO): PetApiResponses.AddPetApiResponse { - val pet: PetWithCategory = petService.add(petCreateTO) - val body = petMapper.petWithCategoryToPetTO(pet) + val pet = petService.add(petCreateTO) + val body = petMapper.asDTO(pet) return PetApiResponses.AddPetApiResponse.AddPet200ApiResponse(body) } @@ -43,7 +42,7 @@ class PetDelegate( val updated = petService.update(id, petUpdateTO) if (updated != null) { - val body = petMapper.petWithCategoryToPetTO(updated) + val body = petMapper.asDTO(updated) return PetApiResponses.UpdatePetApiResponse.UpdatePet200ApiResponse(body) } else { return PetApiResponses.UpdatePetApiResponse.UpdatePet404ApiResponse(notFound(id)) @@ -57,7 +56,7 @@ class PetDelegate( return if (petService.delete(id)) { PetApiResponses.DeletePetApiResponse.DeletePet200ApiResponse( - MessageTO("Successfully deleted pet with ID: $id") + MessageTO("Successfully deleted Pet with ID: $id") ) } else { PetApiResponses.DeletePetApiResponse.DeletePet404ApiResponse(notFound(id)) diff --git a/src/main/java/ru/tinkoff/kora/kotlin/crud/model/DAO.kt b/src/main/java/ru/tinkoff/kora/kotlin/crud/model/DAO.kt index 6568b83..3fcd66d 100644 --- a/src/main/java/ru/tinkoff/kora/kotlin/crud/model/DAO.kt +++ b/src/main/java/ru/tinkoff/kora/kotlin/crud/model/DAO.kt @@ -1,7 +1,6 @@ -package ru.tinkoff.kora.kotlin.example.crud.model +package ru.tinkoff.kora.kotlin.crud.model import ru.tinkoff.kora.database.common.annotation.Column -import ru.tinkoff.kora.database.common.annotation.Embedded import ru.tinkoff.kora.database.common.annotation.Id import ru.tinkoff.kora.database.common.annotation.Table @@ -10,7 +9,6 @@ data class Pet( @field:Column("id") @field:Id val id: Long, @field:Column("name") val name: String, @field:Column("status") val status: Status, - @field:Column("category_id") val categoryId: Long ) { enum class Status(val code: Int) { AVAILABLE(0), @@ -18,19 +16,3 @@ data class Pet( SOLD(20) } } - -@Table("categories") -data class PetCategory( - @field:Id val id: Long, - val name: String -) - -data class PetWithCategory( - @field:Column("id") val id: Long, - @field:Column("name") val name: String, - @field:Column("status") val status: Pet.Status, - @field:Embedded("category_") val category: PetCategory -) { - - fun getPet(): Pet = Pet(id, name, status, category.id) -} diff --git a/src/main/java/ru/tinkoff/kora/kotlin/crud/model/PetMapper.kt b/src/main/java/ru/tinkoff/kora/kotlin/crud/model/PetMapper.kt index 454a174..628de9e 100644 --- a/src/main/java/ru/tinkoff/kora/kotlin/crud/model/PetMapper.kt +++ b/src/main/java/ru/tinkoff/kora/kotlin/crud/model/PetMapper.kt @@ -1,13 +1,10 @@ -package ru.tinkoff.kora.kotlin.example.crud.model +package ru.tinkoff.kora.kotlin.crud.model import org.mapstruct.Mapper -import ru.tinkoff.kora.kotlin.crud.openapi.http.server.model.CategoryTO import ru.tinkoff.kora.kotlin.crud.openapi.http.server.model.PetTO @Mapper interface PetMapper { - fun petWithCategoryToPetTO(pet: PetWithCategory): PetTO - - fun petCategoryToCategoryTO(category: PetCategory): CategoryTO + fun asDTO(pet: Pet): PetTO } diff --git a/src/main/java/ru/tinkoff/kora/kotlin/crud/repository/CategoryRepository.kt b/src/main/java/ru/tinkoff/kora/kotlin/crud/repository/CategoryRepository.kt deleted file mode 100644 index 3fb1d90..0000000 --- a/src/main/java/ru/tinkoff/kora/kotlin/crud/repository/CategoryRepository.kt +++ /dev/null @@ -1,21 +0,0 @@ -package ru.tinkoff.kora.kotlin.example.crud.repository - -import ru.tinkoff.kora.database.common.annotation.Id -import ru.tinkoff.kora.database.common.annotation.Query -import ru.tinkoff.kora.database.common.annotation.Repository -import ru.tinkoff.kora.database.jdbc.JdbcRepository -import ru.tinkoff.kora.kotlin.example.crud.model.PetCategory - -@Repository -interface CategoryRepository : JdbcRepository { - - @Query("SELECT %{return#selects} FROM %{return#table} WHERE name = :name") - fun findByName(name: String): PetCategory? - - @Id - @Query("INSERT INTO categories(name) VALUES (:categoryName)") - fun insert(categoryName: String): Long - - @Query("DELETE FROM categories WHERE id = :id") - fun deleteById(id: Long) -} diff --git a/src/main/java/ru/tinkoff/kora/kotlin/crud/repository/Mappers.kt b/src/main/java/ru/tinkoff/kora/kotlin/crud/repository/Mappers.kt index c6143ae..fd85661 100644 --- a/src/main/java/ru/tinkoff/kora/kotlin/crud/repository/Mappers.kt +++ b/src/main/java/ru/tinkoff/kora/kotlin/crud/repository/Mappers.kt @@ -1,9 +1,9 @@ -package ru.tinkoff.kora.kotlin.example.crud.repository +package ru.tinkoff.kora.kotlin.crud.repository import ru.tinkoff.kora.common.Component import ru.tinkoff.kora.database.jdbc.mapper.parameter.JdbcParameterColumnMapper import ru.tinkoff.kora.database.jdbc.mapper.result.JdbcResultColumnMapper -import ru.tinkoff.kora.kotlin.example.crud.model.Pet +import ru.tinkoff.kora.kotlin.crud.model.Pet import java.sql.PreparedStatement import java.sql.ResultSet import java.sql.SQLException diff --git a/src/main/java/ru/tinkoff/kora/kotlin/crud/repository/PetRepository.kt b/src/main/java/ru/tinkoff/kora/kotlin/crud/repository/PetRepository.kt index c48c9b9..b48ae6f 100644 --- a/src/main/java/ru/tinkoff/kora/kotlin/crud/repository/PetRepository.kt +++ b/src/main/java/ru/tinkoff/kora/kotlin/crud/repository/PetRepository.kt @@ -1,25 +1,17 @@ -package ru.tinkoff.kora.kotlin.example.crud.repository +package ru.tinkoff.kora.kotlin.crud.repository import ru.tinkoff.kora.database.common.UpdateCount import ru.tinkoff.kora.database.common.annotation.Id import ru.tinkoff.kora.database.common.annotation.Query import ru.tinkoff.kora.database.common.annotation.Repository import ru.tinkoff.kora.database.jdbc.JdbcRepository -import ru.tinkoff.kora.kotlin.example.crud.model.Pet -import ru.tinkoff.kora.kotlin.example.crud.model.PetWithCategory +import ru.tinkoff.kora.kotlin.crud.model.Pet @Repository interface PetRepository : JdbcRepository { - @Query( - """ - SELECT p.id, p.name, p.status, p.category_id, c.name as category_name - FROM pets p - JOIN categories c on c.id = p.category_id - WHERE p.id = :id - """ - ) - fun findById(id: Long): PetWithCategory? + @Query("SELECT %{return#selects} FROM %{return#table} WHERE id = :id") + fun findById(id: Long): Pet? @Id @Query("INSERT INTO %{entity#inserts -= id}") diff --git a/src/main/java/ru/tinkoff/kora/kotlin/crud/service/Cache.kt b/src/main/java/ru/tinkoff/kora/kotlin/crud/service/Cache.kt index cb95c28..87d3e62 100644 --- a/src/main/java/ru/tinkoff/kora/kotlin/crud/service/Cache.kt +++ b/src/main/java/ru/tinkoff/kora/kotlin/crud/service/Cache.kt @@ -1,8 +1,8 @@ -package ru.tinkoff.kora.kotlin.example.crud.service +package ru.tinkoff.kora.kotlin.crud.service import ru.tinkoff.kora.cache.annotation.Cache import ru.tinkoff.kora.cache.caffeine.CaffeineCache -import ru.tinkoff.kora.kotlin.example.crud.model.PetWithCategory +import ru.tinkoff.kora.kotlin.crud.model.Pet @Cache("pet-cache") -interface PetCache : CaffeineCache +interface PetCache : CaffeineCache diff --git a/src/main/java/ru/tinkoff/kora/kotlin/crud/service/PetService.kt b/src/main/java/ru/tinkoff/kora/kotlin/crud/service/PetService.kt index 87b7de7..1916db2 100644 --- a/src/main/java/ru/tinkoff/kora/kotlin/crud/service/PetService.kt +++ b/src/main/java/ru/tinkoff/kora/kotlin/crud/service/PetService.kt @@ -1,64 +1,47 @@ -package ru.tinkoff.kora.kotlin.example.crud.service +package ru.tinkoff.kora.kotlin.crud.service import ru.tinkoff.kora.cache.annotation.CacheInvalidate import ru.tinkoff.kora.cache.annotation.CachePut import ru.tinkoff.kora.cache.annotation.Cacheable import ru.tinkoff.kora.common.Component +import ru.tinkoff.kora.kotlin.crud.model.Pet import ru.tinkoff.kora.kotlin.crud.openapi.http.server.model.PetCreateTO import ru.tinkoff.kora.kotlin.crud.openapi.http.server.model.PetUpdateTO -import ru.tinkoff.kora.kotlin.example.crud.model.Pet -import ru.tinkoff.kora.kotlin.example.crud.model.PetCategory -import ru.tinkoff.kora.kotlin.example.crud.model.PetWithCategory -import ru.tinkoff.kora.kotlin.example.crud.repository.CategoryRepository -import ru.tinkoff.kora.kotlin.example.crud.repository.PetRepository +import ru.tinkoff.kora.kotlin.crud.repository.PetRepository import ru.tinkoff.kora.resilient.circuitbreaker.annotation.CircuitBreaker import ru.tinkoff.kora.resilient.retry.annotation.Retry import ru.tinkoff.kora.resilient.timeout.annotation.Timeout @Component open class PetService( - private val petRepository: PetRepository, - private val categoryRepository: CategoryRepository + private val petRepository: PetRepository ) { @Cacheable(PetCache::class) @CircuitBreaker("pet") @Retry("pet") @Timeout("pet") - open fun findByID(petId: Long): PetWithCategory? { + open fun findByID(petId: Long): Pet? { return petRepository.findById(petId) } @CircuitBreaker("pet") @Timeout("pet") - open fun add(createTO: PetCreateTO): PetWithCategory { - val petCategoryId = categoryRepository.findByName(createTO.category.name)?.id - ?: categoryRepository.insert(createTO.category.name) - - val pet = Pet(0, createTO.name, Pet.Status.AVAILABLE, petCategoryId) + open fun add(createTO: PetCreateTO): Pet { + val pet = Pet(0, createTO.name, Pet.Status.AVAILABLE) val petId = petRepository.insert(pet) - - return PetWithCategory( - petId.toLong(), pet.name, pet.status, - PetCategory(petCategoryId, createTO.category.name) - ) + return Pet(petId.toLong(), pet.name, pet.status) } @CircuitBreaker("pet") @Timeout("pet") @CachePut(value = PetCache::class, parameters = ["id"]) - open fun update(id: Long, updateTO: PetUpdateTO): PetWithCategory? { + open fun update(id: Long, updateTO: PetUpdateTO): Pet? { val existing = petRepository.findById(id) ?: return null - var category = existing.category - if (updateTO.category != null) { - category = categoryRepository.findByName(updateTO.category.name) - ?: PetCategory(categoryRepository.insert(updateTO.category.name), updateTO.category.name) - } - val status = if (updateTO.status == null) existing.status else toStatus(updateTO.status) - val result = PetWithCategory(existing.id, updateTO.name, status, category) - petRepository.update(result.getPet()) + val result = Pet(existing.id, updateTO.name, status) + petRepository.update(result) return result } diff --git a/src/main/resources/db/migration/V1__setup-tables.sql b/src/main/resources/db/migration/V1__setup-tables.sql index 3e70499..6d2057b 100644 --- a/src/main/resources/db/migration/V1__setup-tables.sql +++ b/src/main/resources/db/migration/V1__setup-tables.sql @@ -1,16 +1,7 @@ -CREATE TABLE IF NOT EXISTS categories -( - id BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY, - name VARCHAR NOT NULL, - PRIMARY KEY (id) -); - - CREATE TABLE IF NOT EXISTS pets ( id BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY, name VARCHAR NOT NULL, status SMALLINT NOT NULL, - category_id BIGINT NOT NULL REFERENCES categories(id), PRIMARY KEY (id) ); diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index a676b45..ae84fae 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -1,5 +1,5 @@ - + diff --git a/src/main/resources/openapi/http-server.yaml b/src/main/resources/openapi/http-server.yaml index 8d99f49..73c6e2a 100644 --- a/src/main/resources/openapi/http-server.yaml +++ b/src/main/resources/openapi/http-server.yaml @@ -233,27 +233,6 @@ components: properties: message: type: string - CategoryTO: - required: - - id - - name - type: object - properties: - id: - type: integer - format: int64 - example: 1 - name: - type: string - example: Dogs - CategoryCreateTO: - required: - - name - type: object - properties: - name: - type: string - example: Dogs PetStatusTO: properties: status: @@ -282,8 +261,6 @@ components: nullable: false minLength: 1 maxLength: 50 - category: - $ref: '#/components/schemas/CategoryTO' PetCreateTO: required: - name @@ -296,8 +273,6 @@ components: nullable: false minLength: 1 maxLength: 50 - category: - $ref: '#/components/schemas/CategoryCreateTO' PetUpdateTO: allOf: - $ref: '#/components/schemas/PetStatusTO' @@ -311,5 +286,3 @@ components: nullable: false minLength: 1 maxLength: 50 - category: - $ref: '#/components/schemas/CategoryCreateTO' diff --git a/src/test/java/ru/tinkoff/kora/kotlin/crud/AppContainer.kt b/src/test/java/ru/tinkoff/kora/kotlin/crud/AppContainer.kt index 7f6afc2..e77b806 100644 --- a/src/test/java/ru/tinkoff/kora/kotlin/crud/AppContainer.kt +++ b/src/test/java/ru/tinkoff/kora/kotlin/crud/AppContainer.kt @@ -1,4 +1,4 @@ -package ru.tinkoff.kora.kotlin.example.crud +package ru.tinkoff.kora.kotlin.crud import org.slf4j.LoggerFactory import org.testcontainers.containers.GenericContainer @@ -23,10 +23,11 @@ class AppContainer : GenericContainer { val appImage = System.getenv("IMAGE_KORA_KOTLIN_CRUD") return if (appImage != null && appImage.isNotBlank()) AppContainer(DockerImageName.parse(appImage)) - else AppContainer( - ImageFromDockerfile("kora-kotlin-crud", true) - .withDockerfile(Paths.get("Dockerfile").toAbsolutePath()) - ) + else + AppContainer( + ImageFromDockerfile("kora-kotlin-crud", true) + .withDockerfile(Paths.get("Dockerfile").toAbsolutePath()) + ) } } diff --git a/src/test/java/ru/tinkoff/kora/kotlin/crud/PetControllerTests.kt b/src/test/java/ru/tinkoff/kora/kotlin/crud/BlackBoxTests.kt similarity index 90% rename from src/test/java/ru/tinkoff/kora/kotlin/crud/PetControllerTests.kt rename to src/test/java/ru/tinkoff/kora/kotlin/crud/BlackBoxTests.kt index 928e966..4e1e629 100644 --- a/src/test/java/ru/tinkoff/kora/kotlin/crud/PetControllerTests.kt +++ b/src/test/java/ru/tinkoff/kora/kotlin/crud/BlackBoxTests.kt @@ -1,4 +1,4 @@ -package ru.tinkoff.kora.kotlin.example.crud +package ru.tinkoff.kora.kotlin.crud import io.goodforgod.testcontainers.extensions.ContainerMode import io.goodforgod.testcontainers.extensions.Network @@ -28,7 +28,7 @@ import java.time.Duration drop = Migration.Mode.PER_METHOD ) ) -class PetControllerTests(@ConnectionPostgreSQL val connection: JdbcConnection) { +class BlackBoxTests(@ConnectionPostgreSQL val connection: JdbcConnection) { companion object { @@ -76,14 +76,11 @@ class PetControllerTests(@ConnectionPostgreSQL val connection: JdbcConnection) { // then connection.assertCountsEquals(1, "pets") - connection.assertCountsEquals(1, "categories") val responseBody = JSONObject(response.body()) assertNotNull(responseBody.query("/id")) assertNotEquals(0L, responseBody.query("/id")) assertNotNull(responseBody.query("/status")) assertEquals(requestBody.query("/name"), responseBody.query("/name")) - assertNotNull(responseBody.query("/category/id")) - assertEquals(requestBody.query("/category/name"), responseBody.query("/category/name")) } @Test @@ -92,7 +89,6 @@ class PetControllerTests(@ConnectionPostgreSQL val connection: JdbcConnection) { val httpClient = HttpClient.newHttpClient() val createRequestBody = JSONObject() .put("name", "doggie") - .put("category", JSONObject().put("name", "Dogs")) // when val createRequest = HttpRequest.newBuilder() @@ -104,7 +100,6 @@ class PetControllerTests(@ConnectionPostgreSQL val connection: JdbcConnection) { val createResponse = httpClient.send(createRequest, HttpResponse.BodyHandlers.ofString()) assertEquals(200, createResponse.statusCode(), createResponse.body()) connection.assertCountsEquals(1, "pets") - connection.assertCountsEquals(1, "categories") val createResponseBody = JSONObject(createResponse.body()) // then @@ -127,7 +122,6 @@ class PetControllerTests(@ConnectionPostgreSQL val connection: JdbcConnection) { val httpClient = HttpClient.newHttpClient() val createRequestBody = JSONObject() .put("name", "doggie") - .put("category", JSONObject().put("name", "Dogs")) val createRequest = HttpRequest.newBuilder() .POST(HttpRequest.BodyPublishers.ofString(createRequestBody.toString())) @@ -138,14 +132,12 @@ class PetControllerTests(@ConnectionPostgreSQL val connection: JdbcConnection) { val createResponse = httpClient.send(createRequest, HttpResponse.BodyHandlers.ofString()) assertEquals(200, createResponse.statusCode(), createResponse.body()) connection.assertCountsEquals(1, "pets") - connection.assertCountsEquals(1, "categories") val createResponseBody = JSONObject(createResponse.body()) // when val updateRequestBody = JSONObject() .put("name", "doggie2") .put("status", "pending") - .put("category", JSONObject().put("name", "Dogs2")) val updateRequest = HttpRequest.newBuilder() .PUT(HttpRequest.BodyPublishers.ofString(updateRequestBody.toString())) @@ -177,7 +169,6 @@ class PetControllerTests(@ConnectionPostgreSQL val connection: JdbcConnection) { val httpClient = HttpClient.newHttpClient() val createRequestBody = JSONObject() .put("name", "doggie") - .put("category", JSONObject().put("name", "Dogs")) val createRequest = HttpRequest.newBuilder() .POST(HttpRequest.BodyPublishers.ofString(createRequestBody.toString())) @@ -188,7 +179,6 @@ class PetControllerTests(@ConnectionPostgreSQL val connection: JdbcConnection) { val createResponse = httpClient.send(createRequest, HttpResponse.BodyHandlers.ofString()) assertEquals(200, createResponse.statusCode(), createResponse.body()) connection.assertCountsEquals(1, "pets") - connection.assertCountsEquals(1, "categories") val createResponseBody = JSONObject(createResponse.body()) // when diff --git a/src/test/java/ru/tinkoff/kora/kotlin/crud/PetServiceTests.kt b/src/test/java/ru/tinkoff/kora/kotlin/crud/UnitTests.kt similarity index 59% rename from src/test/java/ru/tinkoff/kora/kotlin/crud/PetServiceTests.kt rename to src/test/java/ru/tinkoff/kora/kotlin/crud/UnitTests.kt index f8f4a01..cfac4d1 100644 --- a/src/test/java/ru/tinkoff/kora/kotlin/crud/PetServiceTests.kt +++ b/src/test/java/ru/tinkoff/kora/kotlin/crud/UnitTests.kt @@ -1,4 +1,4 @@ -package ru.tinkoff.kora.kotlin.example.crud +package ru.tinkoff.kora.kotlin.crud import io.mockk.every import io.mockk.impl.annotations.MockK @@ -6,22 +6,18 @@ import io.mockk.verify import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Test -import ru.tinkoff.kora.kotlin.crud.Application -import ru.tinkoff.kora.kotlin.crud.openapi.http.server.model.CategoryCreateTO import ru.tinkoff.kora.kotlin.crud.openapi.http.server.model.PetCreateTO import ru.tinkoff.kora.kotlin.crud.openapi.http.server.model.PetUpdateTO -import ru.tinkoff.kora.kotlin.example.crud.model.PetWithCategory -import ru.tinkoff.kora.kotlin.example.crud.repository.CategoryRepository -import ru.tinkoff.kora.kotlin.example.crud.repository.PetRepository -import ru.tinkoff.kora.kotlin.example.crud.service.PetCache -import ru.tinkoff.kora.kotlin.example.crud.service.PetService +import ru.tinkoff.kora.kotlin.crud.repository.PetRepository +import ru.tinkoff.kora.kotlin.crud.service.PetCache +import ru.tinkoff.kora.kotlin.crud.service.PetService import ru.tinkoff.kora.test.extension.junit5.KoraAppTest import ru.tinkoff.kora.test.extension.junit5.KoraAppTestConfigModifier import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification import ru.tinkoff.kora.test.extension.junit5.TestComponent @KoraAppTest(Application::class) -class PetServiceTests : KoraAppTestConfigModifier { +class UnitTests : KoraAppTestConfigModifier { @field:MockK @TestComponent @@ -31,10 +27,6 @@ class PetServiceTests : KoraAppTestConfigModifier { @TestComponent lateinit var petRepository: PetRepository - @field:MockK - @TestComponent - lateinit var categoryRepository: CategoryRepository - @TestComponent lateinit var petService: PetService @@ -63,66 +55,54 @@ class PetServiceTests : KoraAppTestConfigModifier { fun updatePetWithNewCategoryCreated() { // given mockCache() - mockRepository(mapOf("dog" to 1, "cat" to 2)) + mockRepository() - val added = petService.add(PetCreateTO("dog", CategoryCreateTO("dog"))) + val added = petService.add(PetCreateTO("dog")) assertEquals(1, added.id) - assertEquals(1, added.category.id) verify { petRepository.insert(any()) } - verify { categoryRepository.insert(any()) } // when every { petRepository.findById(any()) } returns added val updated = petService.update( added.id, - PetUpdateTO("cat", PetUpdateTO.StatusEnum.PENDING, CategoryCreateTO("cat")) + PetUpdateTO("cat", PetUpdateTO.StatusEnum.PENDING) ) assertNotNull(updated) assertEquals(1, updated!!.id) - assertEquals(2, updated.category.id) // then verify { petRepository.update(any()) } - verify { categoryRepository.insert(any()) } } @Test fun updatePetWithSameCategory() { // given mockCache() - mockRepository(mapOf("dog" to 1, "cat" to 2)) + mockRepository() - val added = petService.add(PetCreateTO("dog", CategoryCreateTO("dog"))) + val added = petService.add(PetCreateTO("dog")) assertEquals(1, added.id) - assertEquals(1, added.category.id) verify { petRepository.insert(any()) } - verify { categoryRepository.insert(any()) } // when every { petRepository.findById(any()) } returns added - every { categoryRepository.findByName(any()) } returns added.category val updated = petService.update( added.id, - PetUpdateTO("cat", PetUpdateTO.StatusEnum.PENDING, CategoryCreateTO("dog")) + PetUpdateTO("cat", PetUpdateTO.StatusEnum.PENDING) ) assertNotNull(updated) assertEquals(1, updated!!.id) - assertEquals(1, updated.category.id) // then verify { petRepository.update(any()) } - verify { categoryRepository.insert(any()) } } private fun mockCache() { every { petCache.get(any()) } returns null every { petCache.put(any(), any()) } returnsArgument (1) - every { petCache.get(any>()) } returns emptyMap() } - private fun mockRepository(categoryNameToId: Map) { - categoryNameToId.forEach { (name, id) -> every { categoryRepository.insert(name) } returns id } - every { categoryRepository.findByName(any()) } returns null + private fun mockRepository() { every { petRepository.insert(any()) } returns 1 every { petRepository.findById(any()) } returns null every { petRepository.update(any()) } returns Unit diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml index adccdab..807a108 100644 --- a/src/test/resources/logback-test.xml +++ b/src/test/resources/logback-test.xml @@ -1,5 +1,5 @@ - + @@ -17,8 +17,8 @@ - + - +