From 1e10ccda99c26b6d5af79eced0e853b63cce7a8c Mon Sep 17 00:00:00 2001 From: Anton Kurako Date: Sun, 27 Oct 2024 18:28:38 +0300 Subject: [PATCH] Java template simplified and updated --- README.md | 21 ++++- build.gradle | 91 ++++++++----------- gradle.properties | 2 +- settings.gradle | 1 - .../crud/controller/HttpExceptionHandler.java | 7 +- .../java/crud/controller/PetDelegate.java | 5 +- .../tinkoff/kora/java/crud/model/dao/Pet.java | 3 +- .../kora/java/crud/model/dao/PetCategory.java | 8 -- .../java/crud/model/dao/PetWithCategory.java | 14 --- .../java/crud/model/mapper/PetMapper.java | 8 +- .../crud/repository/CategoryRepository.java | 23 ----- .../java/crud/repository/PetRepository.java | 13 +-- .../mapper/PetStatusParameterMapper.java | 7 +- .../mapper/PetStatusResultMapper.java | 5 +- .../kora/java/crud/service/PetCache.java | 4 +- .../kora/java/crud/service/PetService.java | 41 +++------ .../db/migration/V1__setup-tables.sql | 9 -- src/main/resources/openapi/http-server.yaml | 27 ------ .../tinkoff/kora/java/crud/AppContainer.java | 7 +- ...ontrollerTests.java => BlackBoxTests.java} | 39 +++----- .../{PetServiceTests.java => UnitTests.java} | 51 ++++------- src/test/resources/logback-test.xml | 4 +- 22 files changed, 123 insertions(+), 267 deletions(-) delete mode 100644 src/main/java/ru/tinkoff/kora/java/crud/model/dao/PetCategory.java delete mode 100644 src/main/java/ru/tinkoff/kora/java/crud/model/dao/PetWithCategory.java delete mode 100644 src/main/java/ru/tinkoff/kora/java/crud/repository/CategoryRepository.java rename src/test/java/ru/tinkoff/kora/java/crud/{PetControllerTests.java => BlackBoxTests.java} (88%) rename src/test/java/ru/tinkoff/kora/java/crud/{PetServiceTests.java => UnitTests.java} (67%) diff --git a/README.md b/README.md index d828b5c..d8ca17f 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,11 @@ # Шаблон приложения Kora Java CRUD -Пример сервиса реализованного на Kora с HTTP [CRUD](https://github.com/swagger-api/swagger-petstore) API, -в качестве базы данных выступает Postgres, используется кэш Caffeine, а также другие модули которые использовались бы в реальном приложении в бою. +Шаблон для быстрого старта нового проекта на Java и 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/) @@ -31,6 +32,13 @@ ./gradlew openApiGenerateHttpServer ``` +### Image + +Собрать образ приложения: +```shell +docker build -t kora-java-crud . +``` + ## Run Запустить локально: @@ -38,6 +46,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 b/build.gradle index 5f25756..095c339 100644 --- a/build.gradle +++ b/build.gradle @@ -16,9 +16,6 @@ plugins { id "com.diffplug.spotless" version "6.19.0" } -applicationName = "application" -mainClassName = "ru.tinkoff.kora.java.crud.Application" - group = groupId version = koraVersion @@ -27,13 +24,14 @@ targetCompatibility = JavaVersion.VERSION_21 repositories { mavenCentral() - maven { url "https://oss.sonatype.org/content/repositories/snapshots" } } configurations { koraBom - implementation.extendsFrom(koraBom) annotationProcessor.extendsFrom(koraBom) + compileOnly.extendsFrom(koraBom) + implementation.extendsFrom(koraBom) + api.extendsFrom(koraBom) } dependencies { @@ -58,22 +56,32 @@ dependencies { testImplementation "org.json:json:20231013" testImplementation "org.skyscreamer:jsonassert:1.5.1" - testImplementation "org.mockito:mockito-core:5.6.0" + testImplementation "org.mockito:mockito-core:5.7.0" 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" } -// for snapshot dependencies -configurations { - configureEach { - resolutionStrategy { - cacheChangingModulesFor 0, "seconds" // check for updates every build - } - } +application { + applicationName = "application" + mainClassName = "ru.tinkoff.kora.java.crud.Application" + applicationDefaultJvmArgs = ["-Dfile.encoding=UTF-8"] +} + +distTar { + archiveFileName = "application.tar" +} + +//noinspection GroovyAssignabilityCheck +run { + environment([ + "POSTGRES_JDBC_URL": "jdbc:postgresql://$postgresHost:$postgresPort/$postgresDatabase", + "POSTGRES_USER" : "$postgresUser", + "POSTGRES_PASS" : "$postgresPassword", + ]) } -openApiGenerate { +tasks.register("openApiGenerateHttpServer", GenerateTask) { generatorName = "kora" group = "openapi tools" inputSpec = "$projectDir/src/main/resources/openapi/http-server.yaml" @@ -87,21 +95,12 @@ openApiGenerate { ] } -compileJava.dependsOn tasks.openApiGenerate -test.dependsOn tasks.distTar - -application { - applicationDefaultJvmArgs = ["-Dfile.encoding=UTF-8"] +sourceSets.main { + java.srcDirs += "$buildDir/generated/openapi" } -//noinspection GroovyAssignabilityCheck -run { - environment([ - "POSTGRES_JDBC_URL": "jdbc:postgresql://$postgresHost:$postgresPort/$postgresDatabase", - "POSTGRES_USER" : "$postgresUser", - "POSTGRES_PASS" : "$postgresPassword", - ]) -} +compileJava.dependsOn tasks.openApiGenerateHttpServer +test.dependsOn tasks.distTar test { jvmArgs += [ @@ -130,9 +129,11 @@ test { } } -sourceSets { - main { - java.srcDirs += "$buildDir/generated/openapi" +check.dependsOn jacocoTestReport +jacocoTestReport { + reports { + xml.required = true + html.outputLocation = layout.buildDirectory.dir("jacocoHtml") } } @@ -143,16 +144,19 @@ flyway { locations = ["classpath:db/migration"] } -distTar { - archiveFileName = "application.tar" -} - compileJava { options.encoding("UTF-8") options.incremental(true) options.fork = false } +javadoc { + options.encoding = "UTF-8" + if (JavaVersion.current().isJava9Compatible()) { + options.addBooleanOption("html5", true) + } +} + spotless { java { encoding("UTF-8") @@ -162,18 +166,3 @@ spotless { targetExclude("**/proto/**", "**/generated/openapi/**", "**/generated/soap/**", "**/generated/grpc/**",) } } - -check.dependsOn jacocoTestReport -jacocoTestReport { - reports { - xml.required = true - html.outputLocation = layout.buildDirectory.dir("jacocoHtml") - } -} - -javadoc { - options.encoding = "UTF-8" - if (JavaVersion.current().isJava9Compatible()) { - options.addBooleanOption("html5", true) - } -} \ No newline at end of file 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 b/settings.gradle index 57b61e0..7cce991 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,7 +2,6 @@ pluginManagement { repositories { gradlePluginPortal() mavenCentral() - maven { url "https://oss.sonatype.org/content/repositories/snapshots" } } } diff --git a/src/main/java/ru/tinkoff/kora/java/crud/controller/HttpExceptionHandler.java b/src/main/java/ru/tinkoff/kora/java/crud/controller/HttpExceptionHandler.java index 9a6c85a..0e99157 100644 --- a/src/main/java/ru/tinkoff/kora/java/crud/controller/HttpExceptionHandler.java +++ b/src/main/java/ru/tinkoff/kora/java/crud/controller/HttpExceptionHandler.java @@ -1,17 +1,16 @@ package ru.tinkoff.kora.java.crud.controller; import io.micrometer.core.instrument.config.validate.ValidationException; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeoutException; import ru.tinkoff.kora.common.Component; import ru.tinkoff.kora.common.Context; import ru.tinkoff.kora.common.Tag; -import ru.tinkoff.kora.java.crud.openapi.http.server.model.MessageTO; import ru.tinkoff.kora.http.common.body.HttpBody; import ru.tinkoff.kora.http.server.common.*; +import ru.tinkoff.kora.java.crud.openapi.http.server.model.MessageTO; import ru.tinkoff.kora.json.common.JsonWriter; -import java.util.concurrent.CompletionStage; -import java.util.concurrent.TimeoutException; - @Tag(HttpServerModule.class) @Component public final class HttpExceptionHandler implements HttpServerInterceptor { diff --git a/src/main/java/ru/tinkoff/kora/java/crud/controller/PetDelegate.java b/src/main/java/ru/tinkoff/kora/java/crud/controller/PetDelegate.java index 653cd24..d33d5b8 100644 --- a/src/main/java/ru/tinkoff/kora/java/crud/controller/PetDelegate.java +++ b/src/main/java/ru/tinkoff/kora/java/crud/controller/PetDelegate.java @@ -3,11 +3,11 @@ import ru.tinkoff.kora.common.Component; import ru.tinkoff.kora.java.crud.model.mapper.PetMapper; import ru.tinkoff.kora.java.crud.openapi.http.server.api.PetApiDelegate; -import ru.tinkoff.kora.java.crud.service.PetService; import ru.tinkoff.kora.java.crud.openapi.http.server.api.PetApiResponses; import ru.tinkoff.kora.java.crud.openapi.http.server.model.MessageTO; import ru.tinkoff.kora.java.crud.openapi.http.server.model.PetCreateTO; import ru.tinkoff.kora.java.crud.openapi.http.server.model.PetUpdateTO; +import ru.tinkoff.kora.java.crud.service.PetService; @Component public final class PetDelegate implements PetApiDelegate { @@ -64,7 +64,8 @@ public PetApiResponses.DeletePetApiResponse deletePet(long petId) { } if (petService.delete(petId)) { - return new PetApiResponses.DeletePetApiResponse.DeletePet200ApiResponse(new MessageTO("Successfully deleted pet with ID: " + petId)); + return new PetApiResponses.DeletePetApiResponse.DeletePet200ApiResponse( + new MessageTO("Successfully deleted pet with ID: " + petId)); } else { return new PetApiResponses.DeletePetApiResponse.DeletePet404ApiResponse(notFound(petId)); } diff --git a/src/main/java/ru/tinkoff/kora/java/crud/model/dao/Pet.java b/src/main/java/ru/tinkoff/kora/java/crud/model/dao/Pet.java index 18d98cc..0c2d567 100644 --- a/src/main/java/ru/tinkoff/kora/java/crud/model/dao/Pet.java +++ b/src/main/java/ru/tinkoff/kora/java/crud/model/dao/Pet.java @@ -7,8 +7,7 @@ @Table("pets") public record Pet(@Id @Column("id") long id, @Column("name") String name, - @Column("status") Status status, - @Column("category_id") long categoryId) { + @Column("status") Status status) { public enum Status { diff --git a/src/main/java/ru/tinkoff/kora/java/crud/model/dao/PetCategory.java b/src/main/java/ru/tinkoff/kora/java/crud/model/dao/PetCategory.java deleted file mode 100644 index 9c87822..0000000 --- a/src/main/java/ru/tinkoff/kora/java/crud/model/dao/PetCategory.java +++ /dev/null @@ -1,8 +0,0 @@ -package ru.tinkoff.kora.java.crud.model.dao; - -import ru.tinkoff.kora.database.common.annotation.Id; -import ru.tinkoff.kora.database.common.annotation.Table; - -@Table("categories") -public record PetCategory(@Id long id, - String name) {} diff --git a/src/main/java/ru/tinkoff/kora/java/crud/model/dao/PetWithCategory.java b/src/main/java/ru/tinkoff/kora/java/crud/model/dao/PetWithCategory.java deleted file mode 100644 index b7e9481..0000000 --- a/src/main/java/ru/tinkoff/kora/java/crud/model/dao/PetWithCategory.java +++ /dev/null @@ -1,14 +0,0 @@ -package ru.tinkoff.kora.java.crud.model.dao; - -import ru.tinkoff.kora.database.common.annotation.Column; -import ru.tinkoff.kora.database.common.annotation.Embedded; - -public record PetWithCategory(@Column("id") long id, - @Column("name") String name, - @Column("status") Pet.Status status, - @Embedded("category_") PetCategory category) { - - public Pet getPet() { - return new Pet(id, name, status, category.id()); - } -} diff --git a/src/main/java/ru/tinkoff/kora/java/crud/model/mapper/PetMapper.java b/src/main/java/ru/tinkoff/kora/java/crud/model/mapper/PetMapper.java index 4baf019..fd9fde7 100644 --- a/src/main/java/ru/tinkoff/kora/java/crud/model/mapper/PetMapper.java +++ b/src/main/java/ru/tinkoff/kora/java/crud/model/mapper/PetMapper.java @@ -1,15 +1,11 @@ package ru.tinkoff.kora.java.crud.model.mapper; import org.mapstruct.Mapper; -import ru.tinkoff.kora.java.crud.model.dao.PetCategory; -import ru.tinkoff.kora.java.crud.model.dao.PetWithCategory; -import ru.tinkoff.kora.java.crud.openapi.http.server.model.CategoryTO; +import ru.tinkoff.kora.java.crud.model.dao.Pet; import ru.tinkoff.kora.java.crud.openapi.http.server.model.PetTO; @Mapper public interface PetMapper { - PetTO asDTO(PetWithCategory pet); - - CategoryTO asDTO(PetCategory category); + PetTO asDTO(Pet pet); } diff --git a/src/main/java/ru/tinkoff/kora/java/crud/repository/CategoryRepository.java b/src/main/java/ru/tinkoff/kora/java/crud/repository/CategoryRepository.java deleted file mode 100644 index e324e38..0000000 --- a/src/main/java/ru/tinkoff/kora/java/crud/repository/CategoryRepository.java +++ /dev/null @@ -1,23 +0,0 @@ -package ru.tinkoff.kora.java.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.java.crud.model.dao.PetCategory; - -import java.util.Optional; - -@Repository -public interface CategoryRepository extends JdbcRepository { - - @Query("SELECT %{return#selects} FROM %{return#table} WHERE name = :name") - Optional findByName(String name); - - @Id - @Query("INSERT INTO categories(name) VALUES (:categoryName)") - long insert(String categoryName); - - @Query("DELETE FROM categories WHERE id = :id") - void deleteById(long id); -} diff --git a/src/main/java/ru/tinkoff/kora/java/crud/repository/PetRepository.java b/src/main/java/ru/tinkoff/kora/java/crud/repository/PetRepository.java index 4ae28e3..3327c73 100644 --- a/src/main/java/ru/tinkoff/kora/java/crud/repository/PetRepository.java +++ b/src/main/java/ru/tinkoff/kora/java/crud/repository/PetRepository.java @@ -1,25 +1,18 @@ package ru.tinkoff.kora.java.crud.repository; +import java.util.Optional; 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.java.crud.model.dao.Pet; -import ru.tinkoff.kora.java.crud.model.dao.PetWithCategory; - -import java.util.Optional; @Repository public interface PetRepository extends 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 - """) - Optional findById(long id); + @Query("SELECT %{return#selects} FROM %{return#table} WHERE id = :id") + Optional findById(long id); @Id @Query("INSERT INTO %{entity#inserts -= id}") diff --git a/src/main/java/ru/tinkoff/kora/java/crud/repository/mapper/PetStatusParameterMapper.java b/src/main/java/ru/tinkoff/kora/java/crud/repository/mapper/PetStatusParameterMapper.java index b63197a..5210131 100644 --- a/src/main/java/ru/tinkoff/kora/java/crud/repository/mapper/PetStatusParameterMapper.java +++ b/src/main/java/ru/tinkoff/kora/java/crud/repository/mapper/PetStatusParameterMapper.java @@ -1,12 +1,11 @@ package ru.tinkoff.kora.java.crud.repository.mapper; -import ru.tinkoff.kora.common.Component; -import ru.tinkoff.kora.database.jdbc.mapper.parameter.JdbcParameterColumnMapper; -import ru.tinkoff.kora.java.crud.model.dao.Pet; - import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Types; +import ru.tinkoff.kora.common.Component; +import ru.tinkoff.kora.database.jdbc.mapper.parameter.JdbcParameterColumnMapper; +import ru.tinkoff.kora.java.crud.model.dao.Pet; @Component public final class PetStatusParameterMapper implements JdbcParameterColumnMapper { diff --git a/src/main/java/ru/tinkoff/kora/java/crud/repository/mapper/PetStatusResultMapper.java b/src/main/java/ru/tinkoff/kora/java/crud/repository/mapper/PetStatusResultMapper.java index 9b3fde9..4267a5f 100644 --- a/src/main/java/ru/tinkoff/kora/java/crud/repository/mapper/PetStatusResultMapper.java +++ b/src/main/java/ru/tinkoff/kora/java/crud/repository/mapper/PetStatusResultMapper.java @@ -1,12 +1,11 @@ package ru.tinkoff.kora.java.crud.repository.mapper; +import java.sql.ResultSet; +import java.sql.SQLException; import ru.tinkoff.kora.common.Component; import ru.tinkoff.kora.database.jdbc.mapper.result.JdbcResultColumnMapper; import ru.tinkoff.kora.java.crud.model.dao.Pet; -import java.sql.ResultSet; -import java.sql.SQLException; - @Component public final class PetStatusResultMapper implements JdbcResultColumnMapper { diff --git a/src/main/java/ru/tinkoff/kora/java/crud/service/PetCache.java b/src/main/java/ru/tinkoff/kora/java/crud/service/PetCache.java index ac78cac..f247e65 100644 --- a/src/main/java/ru/tinkoff/kora/java/crud/service/PetCache.java +++ b/src/main/java/ru/tinkoff/kora/java/crud/service/PetCache.java @@ -2,9 +2,9 @@ import ru.tinkoff.kora.cache.annotation.Cache; import ru.tinkoff.kora.cache.caffeine.CaffeineCache; -import ru.tinkoff.kora.java.crud.model.dao.PetWithCategory; +import ru.tinkoff.kora.java.crud.model.dao.Pet; @Cache("pet-cache") -public interface PetCache extends CaffeineCache { +public interface PetCache extends CaffeineCache { } diff --git a/src/main/java/ru/tinkoff/kora/java/crud/service/PetService.java b/src/main/java/ru/tinkoff/kora/java/crud/service/PetService.java index 2e379fe..dacbda3 100644 --- a/src/main/java/ru/tinkoff/kora/java/crud/service/PetService.java +++ b/src/main/java/ru/tinkoff/kora/java/crud/service/PetService.java @@ -1,78 +1,61 @@ package ru.tinkoff.kora.java.crud.service; +import java.util.Optional; 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.java.crud.model.dao.Pet; -import ru.tinkoff.kora.java.crud.model.dao.PetCategory; -import ru.tinkoff.kora.java.crud.model.dao.PetWithCategory; import ru.tinkoff.kora.java.crud.openapi.http.server.model.PetCreateTO; import ru.tinkoff.kora.java.crud.openapi.http.server.model.PetUpdateTO; -import ru.tinkoff.kora.java.crud.repository.CategoryRepository; import ru.tinkoff.kora.java.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; -import java.util.Optional; - @Component public class PetService { private final PetRepository petRepository; - private final CategoryRepository categoryRepository; - public PetService(PetRepository petRepository, CategoryRepository categoryRepository) { + public PetService(PetRepository petRepository) { this.petRepository = petRepository; - this.categoryRepository = categoryRepository; } @Cacheable(PetCache.class) @CircuitBreaker("pet") @Retry("pet") @Timeout("pet") - public Optional findByID(long petId) { + public Optional findByID(long petId) { return petRepository.findById(petId); } @CircuitBreaker("pet") @Timeout("pet") - public PetWithCategory add(PetCreateTO createTO) { - final long petCategoryId = categoryRepository.findByName(createTO.category().name()) - .map(PetCategory::id) - .orElseGet(() -> categoryRepository.insert(createTO.category().name())); - - var pet = new Pet(0, createTO.name(), Pet.Status.AVAILABLE, petCategoryId); + public Pet add(PetCreateTO createTO) { + var pet = new Pet(0, createTO.name(), Pet.Status.AVAILABLE); var petId = petRepository.insert(pet); - - return new PetWithCategory(petId, pet.name(), pet.status(), - new PetCategory(petCategoryId, createTO.category().name())); + return new Pet(petId, pet.name(), pet.status()); } @CircuitBreaker("pet") @Timeout("pet") @CachePut(value = PetCache.class, parameters = "id") - public Optional update(long id, PetUpdateTO updateTO) { - final Optional existing = petRepository.findById(id); + public Optional update(long id, PetUpdateTO updateTO) { + final Optional existing = petRepository.findById(id); if (existing.isEmpty()) { return Optional.empty(); } - var category = existing.get().category(); - if (updateTO.category() != null) { - category = categoryRepository.findByName(updateTO.category().name()).orElseGet(() -> { - final long newCategoryId = categoryRepository.insert(updateTO.category().name()); - return new PetCategory(newCategoryId, updateTO.category().name()); - }); + if (existing.get().name().equals(updateTO.name()) && existing.get().status().equals(updateTO.status())) { + return existing; } var status = (updateTO.status() == null) ? existing.get().status() : toStatus(updateTO.status()); - var result = new PetWithCategory(existing.get().id(), updateTO.name(), status, category); - - petRepository.update(result.getPet()); + var result = new Pet(existing.get().id(), updateTO.name(), status); + petRepository.update(result); return Optional.of(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/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/java/crud/AppContainer.java b/src/test/java/ru/tinkoff/kora/java/crud/AppContainer.java index b562eb0..110f24b 100644 --- a/src/test/java/ru/tinkoff/kora/java/crud/AppContainer.java +++ b/src/test/java/ru/tinkoff/kora/java/crud/AppContainer.java @@ -1,5 +1,8 @@ package ru.tinkoff.kora.java.crud; +import java.net.URI; +import java.nio.file.Paths; +import java.time.Duration; import org.slf4j.LoggerFactory; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.output.Slf4jLogConsumer; @@ -7,10 +10,6 @@ import org.testcontainers.images.builder.ImageFromDockerfile; import org.testcontainers.utility.DockerImageName; -import java.net.URI; -import java.nio.file.Paths; -import java.time.Duration; - public final class AppContainer extends GenericContainer { private AppContainer() { diff --git a/src/test/java/ru/tinkoff/kora/java/crud/PetControllerTests.java b/src/test/java/ru/tinkoff/kora/java/crud/BlackBoxTests.java similarity index 88% rename from src/test/java/ru/tinkoff/kora/java/crud/PetControllerTests.java rename to src/test/java/ru/tinkoff/kora/java/crud/BlackBoxTests.java index e930eef..e52b3fd 100644 --- a/src/test/java/ru/tinkoff/kora/java/crud/PetControllerTests.java +++ b/src/test/java/ru/tinkoff/kora/java/crud/BlackBoxTests.java @@ -1,11 +1,18 @@ package ru.tinkoff.kora.java.crud; +import static org.junit.jupiter.api.Assertions.*; + import io.goodforgod.testcontainers.extensions.ContainerMode; import io.goodforgod.testcontainers.extensions.Network; import io.goodforgod.testcontainers.extensions.jdbc.ConnectionPostgreSQL; import io.goodforgod.testcontainers.extensions.jdbc.JdbcConnection; import io.goodforgod.testcontainers.extensions.jdbc.Migration; import io.goodforgod.testcontainers.extensions.jdbc.TestcontainersPostgreSQL; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.Map; import org.json.JSONObject; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -13,14 +20,6 @@ import org.skyscreamer.jsonassert.JSONAssert; import org.skyscreamer.jsonassert.JSONCompareMode; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.time.Duration; -import java.util.Map; - -import static org.junit.jupiter.api.Assertions.*; - @TestcontainersPostgreSQL( network = @Network(shared = true), mode = ContainerMode.PER_RUN, @@ -28,7 +27,7 @@ engine = Migration.Engines.FLYWAY, apply = Migration.Mode.PER_METHOD, drop = Migration.Mode.PER_METHOD)) -class PetControllerTests { +class BlackBoxTests { private static final AppContainer container = AppContainer.build() .withNetwork(org.testcontainers.containers.Network.SHARED); @@ -74,14 +73,11 @@ void addPet() throws Exception { // then connection.assertCountsEquals(1, "pets"); - connection.assertCountsEquals(1, "categories"); var responseBody = new 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 @@ -89,9 +85,7 @@ void getPet() throws Exception { // given var httpClient = HttpClient.newHttpClient(); var createRequestBody = new JSONObject() - .put("name", "doggie") - .put("category", new JSONObject() - .put("name", "Dogs")); + .put("name", "doggie"); // when var createRequest = HttpRequest.newBuilder() @@ -103,7 +97,6 @@ void getPet() throws Exception { var createResponse = httpClient.send(createRequest, HttpResponse.BodyHandlers.ofString()); assertEquals(200, createResponse.statusCode(), createResponse.body()); connection.assertCountsEquals(1, "pets"); - connection.assertCountsEquals(1, "categories"); var createResponseBody = new JSONObject(createResponse.body()); // then @@ -125,9 +118,7 @@ void updatePet() throws Exception { // given var httpClient = HttpClient.newHttpClient(); var createRequestBody = new JSONObject() - .put("name", "doggie") - .put("category", new JSONObject() - .put("name", "Dogs")); + .put("name", "doggie"); var createRequest = HttpRequest.newBuilder() .POST(HttpRequest.BodyPublishers.ofString(createRequestBody.toString())) @@ -138,15 +129,12 @@ void updatePet() throws Exception { var createResponse = httpClient.send(createRequest, HttpResponse.BodyHandlers.ofString()); assertEquals(200, createResponse.statusCode(), createResponse.body()); connection.assertCountsEquals(1, "pets"); - connection.assertCountsEquals(1, "categories"); var createResponseBody = new JSONObject(createResponse.body()); // when var updateRequestBody = new JSONObject() .put("name", "doggie2") - .put("status", "pending") - .put("category", new JSONObject() - .put("name", "Dogs2")); + .put("status", "pending"); var updateRequest = HttpRequest.newBuilder() .PUT(HttpRequest.BodyPublishers.ofString(updateRequestBody.toString())) @@ -177,9 +165,7 @@ void deletePet() throws Exception { // given var httpClient = HttpClient.newHttpClient(); var createRequestBody = new JSONObject() - .put("name", "doggie") - .put("category", new JSONObject() - .put("name", "Dogs")); + .put("name", "doggie"); var createRequest = HttpRequest.newBuilder() .POST(HttpRequest.BodyPublishers.ofString(createRequestBody.toString())) @@ -190,7 +176,6 @@ void deletePet() throws Exception { var createResponse = httpClient.send(createRequest, HttpResponse.BodyHandlers.ofString()); assertEquals(200, createResponse.statusCode(), createResponse.body()); connection.assertCountsEquals(1, "pets"); - connection.assertCountsEquals(1, "categories"); var createResponseBody = new JSONObject(createResponse.body()); // when diff --git a/src/test/java/ru/tinkoff/kora/java/crud/PetServiceTests.java b/src/test/java/ru/tinkoff/kora/java/crud/UnitTests.java similarity index 67% rename from src/test/java/ru/tinkoff/kora/java/crud/PetServiceTests.java rename to src/test/java/ru/tinkoff/kora/java/crud/UnitTests.java index e3d12c9..e43917c 100644 --- a/src/test/java/ru/tinkoff/kora/java/crud/PetServiceTests.java +++ b/src/test/java/ru/tinkoff/kora/java/crud/UnitTests.java @@ -1,13 +1,16 @@ package ru.tinkoff.kora.java.crud; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; + +import java.util.Collections; +import java.util.Optional; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.mockito.Mockito; -import ru.tinkoff.kora.java.crud.openapi.http.server.model.CategoryCreateTO; import ru.tinkoff.kora.java.crud.openapi.http.server.model.PetCreateTO; import ru.tinkoff.kora.java.crud.openapi.http.server.model.PetUpdateTO; -import ru.tinkoff.kora.java.crud.repository.CategoryRepository; import ru.tinkoff.kora.java.crud.repository.PetRepository; import ru.tinkoff.kora.java.crud.service.PetCache; import ru.tinkoff.kora.java.crud.service.PetService; @@ -16,15 +19,8 @@ import ru.tinkoff.kora.test.extension.junit5.KoraConfigModification; import ru.tinkoff.kora.test.extension.junit5.TestComponent; -import java.util.Collections; -import java.util.Map; -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; - @KoraAppTest(Application.class) -class PetServiceTests implements KoraAppTestConfigModifier { +class UnitTests implements KoraAppTestConfigModifier { @Mock @TestComponent @@ -32,9 +28,6 @@ class PetServiceTests implements KoraAppTestConfigModifier { @Mock @TestComponent private PetRepository petRepository; - @Mock - @TestComponent - private CategoryRepository categoryRepository; @TestComponent private PetService petService; @@ -51,62 +44,52 @@ public KoraConfigModification config() { permittedCallsInHalfOpenState = 1 waitDurationInOpenState = 15s } - timeout.pet { - duration = 5000ms - } + timeout.pet.duration = 5000ms retry.pet { delay = 100ms - attempts = 2 + attempts = 0 } - } - """); + }"""); } @Test void updatePetWithNewCategoryCreated() { // given mockCache(); - mockRepository(Map.of("dog", 1L, "cat", 2L)); + mockRepository(); - var added = petService.add(new PetCreateTO("dog", new CategoryCreateTO("dog"))); + var added = petService.add(new PetCreateTO("dog")); assertEquals(1, added.id()); - assertEquals(1, added.category().id()); // when Mockito.when(petRepository.findById(anyLong())).thenReturn(Optional.of(added)); var updated = petService.update(added.id(), - new PetUpdateTO(PetUpdateTO.StatusEnum.PENDING, "cat", new CategoryCreateTO("cat"))); + new PetUpdateTO(PetUpdateTO.StatusEnum.PENDING, "cat")); assertTrue(updated.isPresent()); assertEquals(1, updated.get().id()); - assertEquals(2, updated.get().category().id()); // then Mockito.verify(petRepository).insert(any()); - Mockito.verify(categoryRepository, Mockito.times(2)).insert(any()); } @Test void updatePetWithSameCategory() { // given mockCache(); - mockRepository(Map.of("dog", 1L)); + mockRepository(); - var added = petService.add(new PetCreateTO("dog", new CategoryCreateTO("dog"))); + var added = petService.add(new PetCreateTO("dog")); assertEquals(1, added.id()); - assertEquals(1, added.category().id()); // when Mockito.when(petRepository.findById(anyLong())).thenReturn(Optional.of(added)); - Mockito.when(categoryRepository.findByName(any())).thenReturn(Optional.of(added.category())); var updated = petService.update(added.id(), - new PetUpdateTO(PetUpdateTO.StatusEnum.PENDING, "cat", new CategoryCreateTO("dog"))); + new PetUpdateTO(PetUpdateTO.StatusEnum.PENDING, "cat")); assertTrue(updated.isPresent()); assertNotEquals(0, updated.get().id()); - assertNotEquals(0, updated.get().category().id()); // then Mockito.verify(petRepository).insert(any()); - Mockito.verify(categoryRepository).insert(any()); } private void mockCache() { @@ -115,9 +98,7 @@ private void mockCache() { Mockito.when(petCache.get(anyCollection())).thenReturn(Collections.emptyMap()); } - private void mockRepository(Map categoryNameToId) { - categoryNameToId.forEach((k, v) -> Mockito.when(categoryRepository.insert(k)).thenReturn(v)); - Mockito.when(categoryRepository.findByName(any())).thenReturn(Optional.empty()); + private void mockRepository() { Mockito.when(petRepository.insert(any())).thenReturn(1L); Mockito.when(petRepository.findById(anyLong())).thenReturn(Optional.empty()); } diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml index adccdab..4352145 100644 --- a/src/test/resources/logback-test.xml +++ b/src/test/resources/logback-test.xml @@ -17,8 +17,8 @@ - + - +