diff --git a/.gitignore b/.gitignore index c426c32..679caf1 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ bin/ out/ !**/src/main/**/out/ !**/src/test/**/out/ +lib/ ### NetBeans ### /nbproject/private/ diff --git a/Jenkinsfile-service b/Jenkinsfile-service new file mode 100644 index 0000000..2797aad --- /dev/null +++ b/Jenkinsfile-service @@ -0,0 +1,32 @@ +pipeline { + agent any + tools { + jdk("openjdk-17") + } + + stages { + stage('Init') { + steps { + sh 'printenv' + } + } + + stage('Github clone') { + steps { + git branch: '${BUILD_BRANCH}', url: 'https://github.com/beaniejoy/dongne-cafe-api.git' + } + } + + stage('Test') { + steps { + sh './gradlew clean :dongne-service-api:test' + } + } + + stage('Build') { + steps { + sh './gradlew clean :dongne-service-api:build -x test' + } + } + } +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 0e2608e..64fb392 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,20 +1,26 @@ +import org.gradle.api.tasks.testing.logging.TestExceptionFormat +import org.gradle.api.tasks.testing.logging.TestLogEvent import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.springframework.boot.gradle.tasks.bundling.BootJar - -plugins { - id(Plugins.Spring.boot).version(Version.Spring.boot) - id(Plugins.Spring.dependencyManagement).version(Version.Spring.dependencyManagement).apply(false) - kotlin(Plugins.Kotlin.jvm).version(Version.kotlin) - kotlin(Plugins.Kotlin.pluginSpring).version(Version.kotlin).apply(false) - kotlin(Plugins.Kotlin.pluginJpa).version(Version.kotlin).apply(false) -} +import plugin.BuildLifecyclePlugin +import task.test.TestContainer +import task.test.TestLoggingUtils +import task.test.TestSummary val bootJar: BootJar by tasks bootJar.enabled = false +plugins { + id(Plugins.Spring.BOOT).version(Version.Spring.BOOT) + id(Plugins.Spring.DEPENDENCY_MANAGEMENT).version(Version.Spring.DEPENDENCY_MANAGEMENT).apply(false) + kotlin(Plugins.Kotlin.JVM).version(Version.KOTLIN) + kotlin(Plugins.Kotlin.PLUGIN_SPRING).version(Version.KOTLIN).apply(false) + kotlin(Plugins.Kotlin.PLUGIN_JPA).version(Version.KOTLIN).apply(false) +} + allprojects { group = "io.beaniejoy.dongecafe" - version = Version.projectVersion + version = Version.PROJECT_VERSION repositories { mavenCentral() @@ -24,15 +30,18 @@ allprojects { subprojects { apply { plugin(Plugins.java) - plugin(Plugins.Spring.dependencyManagement) - plugin(Plugins.Spring.boot) + plugin(Plugins.Spring.DEPENDENCY_MANAGEMENT) + plugin(Plugins.Spring.BOOT) plugin(Plugins.Kotlin.KOTLIN) - plugin(Plugins.Kotlin.kotlinSpring) - plugin(Plugins.Kotlin.kotlinJpa) + plugin(Plugins.Kotlin.KOTLIN_SPRING) + plugin(Plugins.Kotlin.KOTLIN_JPA) } - java.sourceCompatibility = JavaVersion.VERSION_17 + java.apply { + sourceCompatibility = Version.JAVA + targetCompatibility = Version.JAVA + } dependencies { // Spring Boot Project @@ -52,9 +61,9 @@ subprojects { runtimeOnly("com.h2database:h2") // H2 // JWT - implementation("io.jsonwebtoken:jjwt-api:${Version.Deps.Jwt}") - runtimeOnly("io.jsonwebtoken:jjwt-impl:${Version.Deps.Jwt}") - runtimeOnly("io.jsonwebtoken:jjwt-jackson:${Version.Deps.Jwt}") + implementation("io.jsonwebtoken:jjwt-api:${Version.Deps.JWT}") + runtimeOnly("io.jsonwebtoken:jjwt-impl:${Version.Deps.JWT}") + runtimeOnly("io.jsonwebtoken:jjwt-jackson:${Version.Deps.JWT}") // Logging implementation("io.github.microutils:kotlin-logging:${Version.Deps.KOTLIN_LOGGING}") @@ -65,14 +74,54 @@ subprojects { } tasks.withType { - println("### Configuring $name in project ${project.name} ###") kotlinOptions { freeCompilerArgs = listOf("-Xjsr305=strict") - jvmTarget = JavaVersion.VERSION_17.toString() + jvmTarget = Version.JAVA.toString() } } + // for logging when build finished + apply() + + // gradle test logging tasks.withType { useJUnitPlatform() + + testLogging { + events = setOf( + TestLogEvent.FAILED, + TestLogEvent.SKIPPED, + TestLogEvent.STANDARD_ERROR + ) + + exceptionFormat = TestExceptionFormat.FULL + showExceptions = true + showCauses = true + showStackTraces = true + } + +// ignoreFailures = true + + addTestListener(object : TestListener { + override fun beforeSuite(desc: TestDescriptor) {} + // handling after all test finished + override fun afterSuite(desc: TestDescriptor, result: TestResult) { + if (desc.parent != null) return + + val summary = TestSummary( + projectName = project.name, + taskName = name, + result = result + ) + + TestContainer.testResults = summary + } + + override fun beforeTest(desc: TestDescriptor) {} + // handling after each test finished + override fun afterTest(desc: TestDescriptor, result: TestResult) { + TestLoggingUtils.printEachResult(desc, result) + } + }) } } \ No newline at end of file diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 876c922..b22ed73 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -4,4 +4,4 @@ plugins { repositories { mavenCentral() -} +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/Plugins.kt b/buildSrc/src/main/kotlin/Plugins.kt index 3af7358..fc50b9f 100644 --- a/buildSrc/src/main/kotlin/Plugins.kt +++ b/buildSrc/src/main/kotlin/Plugins.kt @@ -2,18 +2,18 @@ object Plugins { const val java = "java" object Spring { - const val boot = "org.springframework.boot" - const val dependencyManagement = "io.spring.dependency-management" + const val BOOT = "org.springframework.boot" + const val DEPENDENCY_MANAGEMENT = "io.spring.dependency-management" } object Kotlin { const val KOTLIN = "kotlin" - const val kotlinSpring = "kotlin-spring" - const val kotlinJpa = "kotlin-jpa" + const val KOTLIN_SPRING = "kotlin-spring" + const val KOTLIN_JPA = "kotlin-jpa" - const val jvm = "jvm" - const val pluginSpring = "plugin.spring" - const val pluginJpa = "plugin.jpa" + const val JVM = "jvm" + const val PLUGIN_SPRING = "plugin.spring" + const val PLUGIN_JPA = "plugin.jpa" } object FlywayDB { diff --git a/buildSrc/src/main/kotlin/Version.kt b/buildSrc/src/main/kotlin/Version.kt index c94d8cf..7dec8b7 100644 --- a/buildSrc/src/main/kotlin/Version.kt +++ b/buildSrc/src/main/kotlin/Version.kt @@ -1,15 +1,19 @@ +import org.gradle.api.JavaVersion + object Version { - const val projectVersion = "0.0.1-SNAPSHOT" - const val kotlin = "1.6.21" + const val PROJECT_VERSION = "0.0.1-SNAPSHOT" + const val KOTLIN = "1.6.21" + + val JAVA = JavaVersion.VERSION_17 object Spring { - const val boot = "2.7.0" - const val dependencyManagement = "1.0.11.RELEASE" + const val BOOT = "2.7.0" + const val DEPENDENCY_MANAGEMENT = "1.0.11.RELEASE" } object Deps { const val KOTLIN_LOGGING = "3.0.4" - const val Jwt = "0.11.5" + const val JWT = "0.11.5" } object FlywayDB { diff --git a/buildSrc/src/main/kotlin/plugin/BuildLifecyclePlugin.kt b/buildSrc/src/main/kotlin/plugin/BuildLifecyclePlugin.kt new file mode 100644 index 0000000..6b1da7f --- /dev/null +++ b/buildSrc/src/main/kotlin/plugin/BuildLifecyclePlugin.kt @@ -0,0 +1,23 @@ +package plugin + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.internal.build.event.BuildEventListenerRegistryInternal +import org.gradle.invocation.DefaultGradle + +class BuildLifecyclePlugin : Plugin { + override fun apply(project: Project) { + val gradle = project.gradle + + val services = (gradle as DefaultGradle).services + val registry = services[BuildEventListenerRegistryInternal::class.java] + + val operationService = gradle.sharedServices.registerIfAbsent("operationService", BuildOperationService::class.java) { + gradle.taskGraph.whenReady { + parameters.lastTaskPath = this.allTasks.last().path + } + } + + registry.subscribe(operationService) + } +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/plugin/BuildOperationService.kt b/buildSrc/src/main/kotlin/plugin/BuildOperationService.kt new file mode 100644 index 0000000..7c4edb4 --- /dev/null +++ b/buildSrc/src/main/kotlin/plugin/BuildOperationService.kt @@ -0,0 +1,26 @@ +package plugin + +import org.gradle.api.services.BuildService +import org.gradle.api.services.BuildServiceParameters +import org.gradle.tooling.events.FinishEvent +import org.gradle.tooling.events.OperationCompletionListener +import org.gradle.tooling.events.task.TaskFinishEvent +import task.test.TestContainer +import task.test.TestLoggingUtils + +abstract class BuildOperationService : BuildService, OperationCompletionListener { + interface Params : BuildServiceParameters { + var lastTaskPath: String + } + + override fun onFinish(event: FinishEvent?) { + if (event == null || event !is TaskFinishEvent) { + return + } + + if (event.descriptor.taskPath == parameters.lastTaskPath) { + TestLoggingUtils.printTotalResult(TestContainer.testResults) + TestContainer.testResults = null + } + } +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/task/test/TestContainer.kt b/buildSrc/src/main/kotlin/task/test/TestContainer.kt new file mode 100644 index 0000000..6520003 --- /dev/null +++ b/buildSrc/src/main/kotlin/task/test/TestContainer.kt @@ -0,0 +1,8 @@ +package task.test + +class TestContainer { + companion object { + var testResults: TestSummary? = null + const val colorMode: Boolean = false + } +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/task/test/TestLoggingUtils.kt b/buildSrc/src/main/kotlin/task/test/TestLoggingUtils.kt new file mode 100644 index 0000000..7e604ba --- /dev/null +++ b/buildSrc/src/main/kotlin/task/test/TestLoggingUtils.kt @@ -0,0 +1,55 @@ +package task.test + +import org.gradle.api.tasks.testing.TestDescriptor +import org.gradle.api.tasks.testing.TestResult + +class TestLoggingUtils { + companion object { + const val ANSI_RESET = "\u001B[0m" + const val ANSI_GREEN = "\u001B[32m" + const val ANSI_RED = "\u001B[31m" + + fun printTotalResult(summary: TestSummary?) { + if (summary == null) return + + val maxLength = summary.maxWidth() + + println( + """ + |┌${"─".repeat(maxLength)}┐ + |${ + summary.toLogList().joinToString("│\n│", "│", "│") { + val coloredResult = colorResultType(summary.result.resultType) + "$it${" ".repeat(maxLength - it.length)}" + .replace( + oldValue = coloredResult.first.toString(), + newValue = coloredResult.second + ) + } + } + |└${"─".repeat(maxLength)}┘ + """.trimMargin() + ) + } + + fun printEachResult(desc: TestDescriptor, result: TestResult) { + println("[${desc.className}] ${desc.displayName} >> result: ${colorResultType(result.resultType).second}") + } + + fun colorResultType(resultType: TestResult.ResultType): Pair { + if (TestContainer.colorMode.not()) { + return resultType to "${resultType}" + } + + val color = when (resultType) { + TestResult.ResultType.SUCCESS -> ANSI_GREEN + TestResult.ResultType.FAILURE -> ANSI_RED + else -> "" + } + + return resultType to if (color.isNotEmpty()) { + "${color}${resultType}$ANSI_RESET" + } else "${resultType}" + } + } +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/task/test/TestSummary.kt b/buildSrc/src/main/kotlin/task/test/TestSummary.kt new file mode 100644 index 0000000..9db03a0 --- /dev/null +++ b/buildSrc/src/main/kotlin/task/test/TestSummary.kt @@ -0,0 +1,20 @@ +package task.test + +import org.gradle.api.tasks.testing.TestResult + +data class TestSummary( + val projectName: String? = null, + val taskName: String? = null, + val result: TestResult +) { + fun maxWidth(): Int { + return toLogList().maxByOrNull { it.length }?.length ?: 0 + } + + fun toLogList(): List { + return listOf( + "${projectName}:${taskName} Test result: ${result.resultType}", + "Test summary: ${result.testCount} tests, ${result.successfulTestCount} succeeded, ${result.failedTestCount} failed, ${result.skippedTestCount} skipped" + ) + } +} \ No newline at end of file diff --git a/dongne-service-api/src/test/kotlin/io/beaniejoy/dongnecafe/domain/cafe/service/CafeServiceTest.kt b/dongne-service-api/src/test/kotlin/io/beaniejoy/dongnecafe/domain/cafe/service/CafeServiceTest.kt index 42d3315..b07b694 100644 --- a/dongne-service-api/src/test/kotlin/io/beaniejoy/dongnecafe/domain/cafe/service/CafeServiceTest.kt +++ b/dongne-service-api/src/test/kotlin/io/beaniejoy/dongnecafe/domain/cafe/service/CafeServiceTest.kt @@ -87,13 +87,13 @@ internal class CafeServiceTest { fun update_cafe_test() { // given val (name, address, phoneNumber, description, cafeMenuList) = CafeTestUtils.createCafeRegisterRequest() - val cafe = Cafe.createCafe( - name = name!!, - address = address!!, - phoneNumber = phoneNumber!!, - description = description!!, - cafeMenuRequestList = cafeMenuList - ) +// val cafe = Cafe.createCafe( +// name = name!!, +// address = address!!, +// phoneNumber = phoneNumber!!, +// description = description!!, +// cafeMenuRequestList = cafeMenuList +// ) val cafeId = 50L // TODO 'findByIdOrNull'은 kotlin test 라이브러리 필요한 듯 diff --git a/dongne-service-api/src/test/resources/application.yml b/dongne-service-api/src/test/resources/application.yml index 9619c20..7f31e3b 100644 --- a/dongne-service-api/src/test/resources/application.yml +++ b/dongne-service-api/src/test/resources/application.yml @@ -1,6 +1,4 @@ spring: - flyway: - enabled: false jpa: properties: hibernate: @@ -9,7 +7,7 @@ spring: logging: level: - org.hibernate.SQL: debug + org.hibernate.SQL: info # ionShutdownHook logging issue (in 'DEBUG' level) org.hibernate.type: trace # 실제 sql 쿼리의 parameter 값을 확인하고자 함 jwt: