diff --git a/Makefile b/Makefile index c680dd6..4b05d93 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ SPANNER_DATABASE_ID=gsync .PHONY: build build: - ./gradlew quarkusBuild + ./gradlew build -x test .PHONY: build_native build_native: diff --git a/build.gradle.kts b/build.gradle.kts index d14bb8c..aacbc94 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,6 @@ plugins { kotlin("jvm") version libs.versions.kotlin + kotlin("plugin.allopen") version libs.versions.kotlin alias(libs.plugins.versions) alias(libs.plugins.version.catalog.update) @@ -16,8 +17,15 @@ plugins { idea } +buildscript { + dependencies { + classpath("com.github.ben-manes:gradle-versions-plugin:0.47.0") + } +} + repositories { mavenCentral() + mavenLocal() gradlePluginPortal() } @@ -47,28 +55,27 @@ dependencies { implementation(libs.jackson.module.kotlin) // Test Framework & utils - testImplementation(project(":testkit")) testImplementation(libs.quarkus.junit5) testImplementation(libs.spock.core) + testImplementation(libs.spock.junit4) testImplementation(libs.groovy) testImplementation(libs.groovy.sql) testImplementation(libs.easy.random) } java { - toolchain { - languageVersion = JavaLanguageVersion.of(17) - } + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } kotlin { - sourceSets { - all { - languageSettings { - languageVersion = "2.0" - } - } - } + jvmToolchain(17) +} + +allOpen { + annotation("jakarta.ws.rs.Path") + annotation("jakarta.enterprise.context.ApplicationScoped") + annotation("io.quarkus.test.junit.QuarkusTest") } spotless { @@ -86,7 +93,6 @@ sonar { property("sonar.projectKey", "averak_gsync") property("sonar.organization", "averak") property("sonar.host.url", "https://sonarcloud.io") - property("sonar.exclusions", "testkit/**") } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5f4a395..1ee5709 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,12 +1,11 @@ [versions] easy-random = "6.2.1" groovy = "4.0.7" -kotlin = "1.9.20" +kotlin = "1.9.10" quarkus = "3.5.0" spock = "2.4-M1-groovy-4.0" [libraries] -commons-lang3 = "org.apache.commons:commons-lang3:3.13.0" easy-random = { module = "io.github.dvgaba:easy-random-core", version.ref = "easy-random" } groovy = { module = "org.apache.groovy:groovy", version.ref = "groovy" } groovy-sql = { module = "org.apache.groovy:groovy-sql", version.ref = "groovy" } @@ -24,6 +23,7 @@ quarkus-logging-json = { module = "io.quarkus:quarkus-logging-json", version.ref quarkus-resteasy-reactive = { module = "io.quarkus:quarkus-resteasy-reactive", version.ref = "quarkus" } quarkus-resteasy-reactive-jackson = { module = "io.quarkus:quarkus-resteasy-reactive-jackson", version.ref = "quarkus" } spock-core = { module = "org.spockframework:spock-core", version.ref = "spock" } +spock-junit4 = { module = "org.spockframework:spock-junit4", version.ref = "spock" } [plugins] gradle-git-properties = "com.gorylenko.gradle-git-properties:2.4.1" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e411586..db9a6b8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle b/settings.gradle index 33b12cc..029126b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1 @@ rootProject.name = "gsync" -include(":testkit") diff --git a/src/main/kotlin/net/averak/gsync/adapter/handler/exception/ErrorResponse.kt b/src/main/kotlin/net/averak/gsync/adapter/handler/exception/ErrorResponse.kt index b92c98b..9ab0595 100644 --- a/src/main/kotlin/net/averak/gsync/adapter/handler/exception/ErrorResponse.kt +++ b/src/main/kotlin/net/averak/gsync/adapter/handler/exception/ErrorResponse.kt @@ -4,7 +4,7 @@ import net.averak.gsync.core.exception.ErrorCode data class ErrorResponse( val code: String, - val summary: String, + val description: String, ) { - constructor(errorCode: ErrorCode) : this(errorCode.name, errorCode.summary) + constructor(errorCode: ErrorCode) : this(errorCode.name, errorCode.description) } diff --git a/src/main/kotlin/net/averak/gsync/adapter/handler/exception/NotFoundExceptionMapper.kt b/src/main/kotlin/net/averak/gsync/adapter/handler/exception/NotFoundExceptionMapper.kt index 6576c32..50f6265 100644 --- a/src/main/kotlin/net/averak/gsync/adapter/handler/exception/NotFoundExceptionMapper.kt +++ b/src/main/kotlin/net/averak/gsync/adapter/handler/exception/NotFoundExceptionMapper.kt @@ -1,19 +1,15 @@ package net.averak.gsync.adapter.handler.exception import jakarta.ws.rs.NotFoundException -import jakarta.ws.rs.core.MediaType import jakarta.ws.rs.core.Response import jakarta.ws.rs.ext.ExceptionMapper import jakarta.ws.rs.ext.Provider import net.averak.gsync.core.exception.ErrorCode +import org.apache.http.HttpStatus @Provider class NotFoundExceptionMapper : ExceptionMapper { override fun toResponse(exception: NotFoundException?): Response { - return Response - .status(Response.Status.NOT_FOUND) - .type(MediaType.APPLICATION_JSON_TYPE) - .entity(ErrorResponse(ErrorCode.NOT_FOUND_API)) - .build() + return Response.status(HttpStatus.SC_NOT_FOUND).entity(ErrorResponse(ErrorCode.NOT_FOUND_API)).build() } } diff --git a/src/main/kotlin/net/averak/gsync/core/exception/ErrorCode.kt b/src/main/kotlin/net/averak/gsync/core/exception/ErrorCode.kt index 6501591..c79b077 100644 --- a/src/main/kotlin/net/averak/gsync/core/exception/ErrorCode.kt +++ b/src/main/kotlin/net/averak/gsync/core/exception/ErrorCode.kt @@ -1,6 +1,6 @@ package net.averak.gsync.core.exception -enum class ErrorCode(val summary: String) { +enum class ErrorCode(val description: String) { // 400 Bad Request VALIDATION_ERROR("Request validation exception was thrown."), INVALID_REQUEST_PARAMETERS("Request parameters is invalid."), diff --git a/src/main/kotlin/net/averak/gsync/core/exception/GsyncException.kt b/src/main/kotlin/net/averak/gsync/core/exception/GsyncException.kt deleted file mode 100644 index a269e3d..0000000 --- a/src/main/kotlin/net/averak/gsync/core/exception/GsyncException.kt +++ /dev/null @@ -1,5 +0,0 @@ -package net.averak.gsync.core.exception - -class GsyncException(val errorCode: ErrorCode, val causedBy: Throwable?) : RuntimeException(errorCode.summary, causedBy) { - constructor(errorCode: ErrorCode) : this(errorCode, null) -} diff --git a/src/main/kotlin/net/averak/gsync/domain/model/Echo.kt b/src/main/kotlin/net/averak/gsync/domain/model/Echo.kt deleted file mode 100644 index 2740b67..0000000 --- a/src/main/kotlin/net/averak/gsync/domain/model/Echo.kt +++ /dev/null @@ -1,16 +0,0 @@ -package net.averak.gsync.domain.model - -import net.averak.gsync.domain.primitive.common.ID -import java.time.LocalDateTime - -data class Echo( - val id: ID, - val message: String, - val timestamp: LocalDateTime, -) { - constructor(message: String) : this( - ID(), - message, - LocalDateTime.now(), - ) -} diff --git a/src/main/kotlin/net/averak/gsync/domain/primitive/common/ID.kt b/src/main/kotlin/net/averak/gsync/domain/primitive/common/ID.kt deleted file mode 100644 index ef22892..0000000 --- a/src/main/kotlin/net/averak/gsync/domain/primitive/common/ID.kt +++ /dev/null @@ -1,17 +0,0 @@ -package net.averak.gsync.domain.primitive.common - -import net.averak.gsync.core.exception.ErrorCode -import net.averak.gsync.core.exception.GsyncException -import java.util.UUID - -data class ID(val value: String) { - init { - try { - UUID.fromString(value) - } catch (_: IllegalArgumentException) { - throw GsyncException(ErrorCode.ID_FORMAT_IS_INVALID) - } - } - - constructor() : this(UUID.randomUUID().toString()) -} diff --git a/src/main/kotlin/net/averak/gsync/usecase/EchoUsecase.kt b/src/main/kotlin/net/averak/gsync/usecase/EchoUsecase.kt deleted file mode 100644 index 390de61..0000000 --- a/src/main/kotlin/net/averak/gsync/usecase/EchoUsecase.kt +++ /dev/null @@ -1,11 +0,0 @@ -package net.averak.gsync.usecase - -import jakarta.inject.Singleton -import net.averak.gsync.domain.model.Echo - -@Singleton -class EchoUsecase { - fun echo(message: String): Echo { - return Echo(message) - } -} diff --git a/src/test/groovy/net/averak/gsync/AbstractSpec.groovy b/src/test/groovy/net/averak/gsync/AbstractSpec.groovy index fbc18b6..93fe2ae 100644 --- a/src/test/groovy/net/averak/gsync/AbstractSpec.groovy +++ b/src/test/groovy/net/averak/gsync/AbstractSpec.groovy @@ -1,31 +1,8 @@ package net.averak.gsync -import io.quarkus.arc.All import io.quarkus.test.junit.QuarkusTest -import jakarta.annotation.PostConstruct -import jakarta.inject.Inject -import net.averak.gsync.core.exception.GsyncException -import net.averak.gsync.testkit.Faker -import net.averak.gsync.testkit.IRandomizer import spock.lang.Specification @QuarkusTest abstract class AbstractSpec extends Specification { - - @Inject - @All - List randomizers - - @PostConstruct - void init() { - Faker.init(randomizers) - } - - /** - * 例外を検証 - */ - static void verify(final GsyncException actual, final GsyncException expected) { - assert actual.errorCode == expected.errorCode - } - } diff --git a/src/test/groovy/net/averak/gsync/adapter/handler/controller/AbstractController_IT.groovy b/src/test/groovy/net/averak/gsync/adapter/handler/controller/AbstractController_IT.groovy index ca0759d..e97bce7 100644 --- a/src/test/groovy/net/averak/gsync/adapter/handler/controller/AbstractController_IT.groovy +++ b/src/test/groovy/net/averak/gsync/adapter/handler/controller/AbstractController_IT.groovy @@ -2,5 +2,5 @@ package net.averak.gsync.adapter.handler.controller import net.averak.gsync.AbstractSpec -abstract class AbstractController_IT extends AbstractSpec { +class AbstractController_IT extends AbstractSpec { } diff --git a/src/test/groovy/net/averak/gsync/domain/primitive/common/ID_UT.groovy b/src/test/groovy/net/averak/gsync/domain/primitive/common/ID_UT.groovy deleted file mode 100644 index 04e1b45..0000000 --- a/src/test/groovy/net/averak/gsync/domain/primitive/common/ID_UT.groovy +++ /dev/null @@ -1,39 +0,0 @@ -package net.averak.gsync.domain.primitive.common - -import net.averak.gsync.AbstractSpec -import net.averak.gsync.core.exception.ErrorCode -import net.averak.gsync.core.exception.GsyncException -import net.averak.gsync.testkit.Faker - -class ID_UT extends AbstractSpec { - - def "constructor: 正常に作成できる"() { - when: - new ID(value) - - then: - noExceptionThrown() - - where: - value << [ - Faker.uuidv4(), - Faker.uuidv5("1"), - ] - } - - def "ID: 制約違反の場合は例外を返す"() { - when: - new ID(value) - - then: - final exception = thrown(GsyncException) - verify(exception, new GsyncException(ErrorCode.ID_FORMAT_IS_INVALID)) - - where: - value << [ - Faker.alphanumeric(36), - Faker.uuidv4() + "A", - ] - } - -} \ No newline at end of file diff --git a/src/test/groovy/net/averak/gsync/randomizer/domain/primitive/common/IDRandomizer.groovy b/src/test/groovy/net/averak/gsync/randomizer/domain/primitive/common/IDRandomizer.groovy deleted file mode 100644 index 3bf8e90..0000000 --- a/src/test/groovy/net/averak/gsync/randomizer/domain/primitive/common/IDRandomizer.groovy +++ /dev/null @@ -1,16 +0,0 @@ -package net.averak.gsync.randomizer.domain.primitive.common - -import net.averak.gsync.domain.primitive.common.ID -import net.averak.gsync.testkit.IRandomizer - -@Singleton -class IDRandomizer implements IRandomizer { - - final Class typeToGenerate = ID.class - - @Override - Object getRandomValue() { - return new ID() - } - -} diff --git a/src/test/groovy/net/averak/gsync/usecase/AbstractUsecase_UT.groovy b/src/test/groovy/net/averak/gsync/usecase/AbstractUsecase_UT.groovy deleted file mode 100644 index fa65aa0..0000000 --- a/src/test/groovy/net/averak/gsync/usecase/AbstractUsecase_UT.groovy +++ /dev/null @@ -1,6 +0,0 @@ -package net.averak.gsync.usecase - -import net.averak.gsync.AbstractSpec - -abstract class AbstractUsecase_UT extends AbstractSpec { -} diff --git a/src/test/groovy/net/averak/gsync/usecase/EchoUsecase_UT.groovy b/src/test/groovy/net/averak/gsync/usecase/EchoUsecase_UT.groovy deleted file mode 100644 index a974b1d..0000000 --- a/src/test/groovy/net/averak/gsync/usecase/EchoUsecase_UT.groovy +++ /dev/null @@ -1,21 +0,0 @@ -package net.averak.gsync.usecase - -import jakarta.inject.Inject -import net.averak.gsync.testkit.Faker - -class EchoUsecase_Echo_UT extends AbstractUsecase_UT { - - @Inject - EchoUsecase sut - - def "echo: 正常系 Echoを作成できる"() { - given: - final message = Faker.alphanumeric() - - when: - final result = sut.echo(message) - - then: - result.message == message - } -} diff --git a/testkit/build.gradle.kts b/testkit/build.gradle.kts deleted file mode 100644 index 19bc9e2..0000000 --- a/testkit/build.gradle.kts +++ /dev/null @@ -1,24 +0,0 @@ -plugins { - kotlin("jvm") version libs.versions.kotlin -} - -repositories { - mavenCentral() -} - -dependencies { - implementation(libs.groovy.sql) - implementation(libs.easy.random) - implementation(libs.commons.lang3) - implementation(libs.guava) -} - -kotlin { - sourceSets { - all { - languageSettings { - languageVersion = "2.0" - } - } - } -} diff --git a/testkit/src/main/kotlin/net/averak/gsync/testkit/Faker.kt b/testkit/src/main/kotlin/net/averak/gsync/testkit/Faker.kt deleted file mode 100644 index 3ccafcd..0000000 --- a/testkit/src/main/kotlin/net/averak/gsync/testkit/Faker.kt +++ /dev/null @@ -1,185 +0,0 @@ -package net.averak.gsync.testkit - -import org.apache.commons.lang3.RandomStringUtils -import org.jeasy.random.EasyRandom -import org.jeasy.random.EasyRandomParameters -import java.time.LocalDate -import java.util.* - -class Faker { - - companion object { - - private lateinit var easyRandom: EasyRandom - - /** - * 初期化する - * テストのエントリーポイントから必ず呼び出すこと - */ - @JvmStatic - fun init(randomizers: List>) { - val easyRandomParameters = EasyRandomParameters() - randomizers.forEach { - easyRandomParameters.randomize(it.getTypeToGenerate(), it) - } - easyRandom = EasyRandom(easyRandomParameters) - } - - /** - * 各フィールドにランダム値を格納したフェイクオブジェクトを生成する - * - * @param clazz target class - * @param fields ランダムに生成されたフィールドの値を強制上書きするマップ - */ - @JvmStatic - @JvmOverloads - fun fake(clazz: Class, fields: Map = mapOf()): T { - val obj = easyRandom.nextObject(clazz) - fields.forEach { (key, value) -> - val field = clazz.getDeclaredField(key) - field.isAccessible = true - field[obj] = value - field.isAccessible = false - } - return obj - } - - /** - * 各フィールドにランダム値を格納したフェイクオブジェクトリストを生成する - * - * @param clazz target class - * @param size number of generated objects - * @param fields ランダムに生成されたフィールドの値を強制上書きするマップ - */ - @JvmStatic - @JvmOverloads - fun fakes(clazz: Class, size: Int = 10, fields: Map = mapOf()): List { - val objs = easyRandom.objects(clazz, size).toList() - fields.forEach { (key, value) -> - val field = clazz.getDeclaredField(key) - field.isAccessible = true - objs.forEach { field[it] = value } - field.isAccessible = false - } - return objs - } - - /** - * メールアドレスを生成する - */ - @JvmStatic - fun email(): String { - return "${RandomStringUtils.randomAlphanumeric(10)}@${RandomStringUtils.randomAlphanumeric(5)}.com".lowercase(Locale.getDefault()) - } - - /** - * パスワードを生成する - */ - @JvmStatic - fun password(): String { - return "b9Fj5QYP" + RandomStringUtils.randomAlphanumeric(8) - } - - /** - * URLを生成する - */ - @JvmStatic - fun url(): String { - return "https://${RandomStringUtils.randomAlphanumeric(5)}.com/${RandomStringUtils.randomAlphanumeric(10)}" - } - - /** - * 数字のみの文字列を生成する - */ - @JvmStatic - @JvmOverloads - fun numeric(length: Int = 31): String { - return RandomStringUtils.randomNumeric(length) - } - - /** - * 英数字の文字列を生成する - */ - @JvmStatic - @JvmOverloads - fun alphanumeric(length: Int = 31): String { - return RandomStringUtils.randomAlphanumeric(length) - } - - /** - * 整数を生成する - */ - @JvmStatic - @JvmOverloads - fun integer(min: Int = 0, max: Int = Int.MAX_VALUE): Int { - val rand = Random() - return min + rand.nextInt(max - min) - } - - /** - * 自然数を生成する - */ - @JvmStatic - @JvmOverloads - fun naturalNumber(max: Int = Int.MAX_VALUE): Int { - return integer(1, max) - } - - /** - * BASE64エンコードされた文字列を生成 - */ - @JvmStatic - fun base64encoded(): String { - val encoder = Base64.getEncoder() - return encoder.encodeToString(RandomStringUtils.randomAlphanumeric(10).toByteArray()) - } - - /** - * リストからランダムに要素を抽出する - */ - @JvmStatic - fun dice(elements: List): T { - return elements[integer(0, elements.size - 1)] - } - - /** - * UUIDv4を生成する - */ - @JvmStatic - fun uuidv4(): String { - return UUID.randomUUID().toString() - } - - /** - * UUIDv5を生成する - */ - @JvmStatic - fun uuidv5(name: String): String { - return UUID.nameUUIDFromBytes(name.toByteArray()).toString() - } - - /** - * 本日の日付を生成する - */ - @JvmStatic - fun today(): LocalDate { - return LocalDate.now() - } - - /** - * 本日の日付を生成する - */ - @JvmStatic - fun tomorrow(): LocalDate { - return LocalDate.now().plusDays(1) - } - - /** - * 本日の日付を生成する - */ - @JvmStatic - fun yesterday(): LocalDate { - return LocalDate.now().minusDays(1) - } - } -} diff --git a/testkit/src/main/kotlin/net/averak/gsync/testkit/Fixture.kt b/testkit/src/main/kotlin/net/averak/gsync/testkit/Fixture.kt deleted file mode 100644 index 9671164..0000000 --- a/testkit/src/main/kotlin/net/averak/gsync/testkit/Fixture.kt +++ /dev/null @@ -1,59 +0,0 @@ -package net.averak.gsync.testkit - -import com.google.common.base.CaseFormat -import groovy.sql.Sql - -class Fixture { - - companion object { - - private lateinit var sql: Sql - - /** - * 初期化する - * テストのエントリーポイントから必ず呼び出すこと - */ - @JvmStatic - fun init(sql: Sql) { - Fixture.sql = sql - } - - /** - * テストフィクスチャをセットアップする - */ - @JvmStatic - fun setup(entity: T): T { - require(entity != null) { - "entity must not be null" - } - - sql.dataSet(extractTableName(entity)).add(extractColumns(entity)) - return entity - } - - /** - * テストフィクスチャをセットアップする - */ - @JvmStatic - fun setup(vararg entities: T): List { - entities.forEach { setup(it) } - return entities.toList() - } - - private fun extractTableName(entity: Any): String { - val tableName = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, entity.javaClass.simpleName).replace("_entity", "") - return "`$tableName`" - } - - private fun extractColumns(entity: Any): Map { - val result = LinkedHashMap() - entity.javaClass.declaredFields.forEach { - val columnName = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, it.name) - it.isAccessible = true - result["`$columnName`"] = it[entity] - it.isAccessible = false - } - return result - } - } -} diff --git a/testkit/src/main/kotlin/net/averak/gsync/testkit/IRandomizer.kt b/testkit/src/main/kotlin/net/averak/gsync/testkit/IRandomizer.kt deleted file mode 100644 index 5b89296..0000000 --- a/testkit/src/main/kotlin/net/averak/gsync/testkit/IRandomizer.kt +++ /dev/null @@ -1,14 +0,0 @@ -package net.averak.gsync.testkit - -import org.jeasy.random.api.Randomizer - -/** - * 任意の型の各フィールドにランダム値を格納したインスタンスを生成するための生成ルールセット - * ドメイン制約やDB制約に準拠したオブジェクトを生成したい場合に定義すること - */ -interface IRandomizer : Randomizer { - - override fun getRandomValue(): T - - fun getTypeToGenerate(): Class -}