diff --git a/.github/workflows/yappu-world-ci.yaml b/.github/workflows/yappu-world-ci.yaml index 61a778cf..69f68509 100644 --- a/.github/workflows/yappu-world-ci.yaml +++ b/.github/workflows/yappu-world-ci.yaml @@ -14,15 +14,6 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up Docker - run: | - sudo apt-get update - sudo apt-get install docker-compose - - - name: Start MySQL with Docker Compose - run: | - docker-compose -f docker/docker-compose-test.yaml up -d - - name: Set up JDK 21 uses: actions/setup-java@v4 with: diff --git a/build.gradle.kts b/build.gradle.kts index ea41b26f..16caeb03 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -33,6 +33,7 @@ dependencies { runtimeOnly("com.mysql:mysql-connector-j") runtimeOnly("com.oracle.database.jdbc:ojdbc11") runtimeOnly("com.oracle.database.security:oraclepki:23.5.0.24.07") + runtimeOnly("com.h2database:h2") implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("com.linecorp.kotlin-jdsl:jpql-dsl:3.5.5") implementation("com.linecorp.kotlin-jdsl:jpql-render:3.5.5") @@ -85,6 +86,13 @@ ktlint { tasks { test { useJUnitPlatform() + testLogging { + events("passed", "skipped", "failed") + showExceptions = true + showCauses = true + showStackTraces = true + exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL + } } bootJar { archiveBaseName = "yappu-world" diff --git a/docker/docker-compose-local.yaml b/docker/docker-compose-local.yaml index df242c0c..1ed250a1 100644 --- a/docker/docker-compose-local.yaml +++ b/docker/docker-compose-local.yaml @@ -1,22 +1,23 @@ name: yappu-world-server services: - mysql: - image: mysql:8.1 + oracle: + image: gvenzl/oracle-xe:21-slim-faststart environment: - MYSQL_DATABASE: yappu_world - MYSQL_USER: yapp - MYSQL_PASSWORD: yapp - MYSQL_ROOT_PASSWORD: yapp + ORACLE_PASSWORD: yapp1234 + APP_USER: yapp + APP_USER_PASSWORD: yapp1234 volumes: - - yappu_mysql_data:/var/lib/mysql + - yappu_oracle_data:/opt/oracle/oradata ports: - - '3306:3306' + - '1521:1521' restart: always - command: - - --character-set-server=utf8mb4 - - --collation-server=utf8mb4_unicode_ci - - --skip-character-set-client-handshake + healthcheck: + test: ["CMD-SHELL", "healthcheck.sh"] + interval: 10s + timeout: 5s + retries: 30 + start_period: 60s volumes: - yappu_mysql_data: + yappu_oracle_data: diff --git a/docker/docker-compose-test.yaml b/docker/docker-compose-test.yaml index c1d12687..ed8266fa 100644 --- a/docker/docker-compose-test.yaml +++ b/docker/docker-compose-test.yaml @@ -1,15 +1,15 @@ services: - mysql: - image: mysql:8.1 + oracle: + image: gvenzl/oracle-xe:21-slim-faststart environment: - MYSQL_DATABASE: yappu_world - MYSQL_USER: yapp - MYSQL_PASSWORD: yapp - MYSQL_ROOT_PASSWORD: yapp + ORACLE_PASSWORD: test1234 + APP_USER: testuser + APP_USER_PASSWORD: test1234 ports: - - '3306:3306' - restart: always - command: - - --character-set-server=utf8mb4 - - --collation-server=utf8mb4_unicode_ci - - --skip-character-set-client-handshake + - '1521:1521' + healthcheck: + test: ["CMD-SHELL", "healthcheck.sh"] + interval: 10s + timeout: 5s + retries: 30 + start_period: 60s diff --git a/src/main/kotlin/co/yappuworld/operation/client/application/GenerationActiveStateManager.kt b/src/main/kotlin/co/yappuworld/operation/client/application/GenerationActiveStateManager.kt index 7c1ab4fe..445c4d1d 100644 --- a/src/main/kotlin/co/yappuworld/operation/client/application/GenerationActiveStateManager.kt +++ b/src/main/kotlin/co/yappuworld/operation/client/application/GenerationActiveStateManager.kt @@ -4,6 +4,7 @@ import co.yappuworld.global.exception.BusinessException import co.yappuworld.operation.client.dto.param.GenerationActivationControlResult import co.yappuworld.operation.domain.GenerationEntity import co.yappuworld.operation.domain.OperationError +import co.yappuworld.operation.infrastructure.GenerationFindService import co.yappuworld.operation.infrastructure.GenerationRepository import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.data.repository.findByIdOrNull @@ -14,6 +15,7 @@ private val logger = KotlinLogging.logger { } @Component class GenerationActiveStateManager( + private val generationFindService: GenerationFindService, private val generationRepository: GenerationRepository ) { @@ -39,9 +41,9 @@ class GenerationActiveStateManager( fun getActiveGenerationOrNull(): Int? = generationRepository.getGenerationOrNullByIsActiveIsTrue()?.value private fun deactivateGeneration(targetGenerationValue: Int? = null): Int? { - if (!generationRepository.existsGenerationByIsActiveIsTrue()) return null + if (!generationFindService.existsActiveGeneration()) return null - val activeGenerations = generationRepository.findAllByIsActiveIsTrue() + val activeGenerations = generationFindService.findAllActiveGeneration() checkDeactivatingConsistency(activeGenerations, targetGenerationValue) return activeGenerations diff --git a/src/main/kotlin/co/yappuworld/operation/infrastructure/GenerationFindService.kt b/src/main/kotlin/co/yappuworld/operation/infrastructure/GenerationFindService.kt index c135e9ef..bec2d23a 100644 --- a/src/main/kotlin/co/yappuworld/operation/infrastructure/GenerationFindService.kt +++ b/src/main/kotlin/co/yappuworld/operation/infrastructure/GenerationFindService.kt @@ -20,5 +20,21 @@ class GenerationFindService( fun existsGeneration(value: Int): Boolean = generationRepository.existsById(value) + fun existsActiveGeneration(): Boolean = + generationRepository + .findAll(limit = 1) { + select(intLiteral(1)) + .from(entity(GenerationEntity::class)) + .where(path(GenerationEntity::isActive).equal(true)) + }.isNotEmpty() + fun findGenerations(values: List): List = generationRepository.findAllByValueIn(values) + + fun findAllActiveGeneration(): List = + generationRepository + .findAll { + select(entity(GenerationEntity::class)) + .from(entity(GenerationEntity::class)) + .where(path(GenerationEntity::isActive).equal(true)) + }.filterNotNull() } diff --git a/src/main/kotlin/co/yappuworld/operation/infrastructure/GenerationRepository.kt b/src/main/kotlin/co/yappuworld/operation/infrastructure/GenerationRepository.kt index 4f5fb5ab..a41e72bc 100644 --- a/src/main/kotlin/co/yappuworld/operation/infrastructure/GenerationRepository.kt +++ b/src/main/kotlin/co/yappuworld/operation/infrastructure/GenerationRepository.kt @@ -1,15 +1,14 @@ package co.yappuworld.operation.infrastructure import co.yappuworld.operation.domain.GenerationEntity +import com.linecorp.kotlinjdsl.support.spring.data.jpa.repository.KotlinJdslJpqlExecutor import org.springframework.data.jpa.repository.JpaRepository -interface GenerationRepository : JpaRepository { - - fun existsGenerationByIsActiveIsTrue(): Boolean +interface GenerationRepository : + JpaRepository, + KotlinJdslJpqlExecutor { fun getGenerationOrNullByIsActiveIsTrue(): GenerationEntity? - fun findAllByIsActiveIsTrue(): List - fun findAllByValueIn(values: List): List } diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml index ab78f587..0d42ae8e 100644 --- a/src/main/resources/application-local.yaml +++ b/src/main/resources/application-local.yaml @@ -7,28 +7,28 @@ spring: docker: compose: enabled: false - service: yappu-world-server - lifecycle-management: none - # stop: - # command: down - # timeout: 1m - skip: - in-tests: false - file: docker/docker-compose-local.yaml datasource: - url: jdbc:mysql://localhost:3306/yappu_world - username: yapp - password: yapp + driver-class-name: org.h2.Driver + url: jdbc:h2:mem:yappu;MODE=Oracle;DATABASE_TO_LOWER=TRUE;NON_KEYWORDS=VALUE + username: sa + password: sql: init: -# schema-locations: classpath:schema.sql -# data-locations: classpath:data.sql - mode: never + mode: always jpa: open-in-view: false + database-platform: org.hibernate.dialect.OracleDialect + hibernate: + ddl-auto: none + show-sql: true + + h2: + console: + enabled: true + path: /h2-console logging: level: diff --git a/src/main/resources/application-test.yaml b/src/main/resources/application-test.yaml index fb53c3f1..bfa3a5f8 100644 --- a/src/main/resources/application-test.yaml +++ b/src/main/resources/application-test.yaml @@ -5,21 +5,20 @@ spring: enabled: true docker: compose: - file: docker/docker-compose-test.yaml - enabled: true - lifecycle-management: start-and-stop - stop: - command: down - timeout: 1m - skip: - in-tests: false + enabled: false datasource: - url: jdbc:mysql://mysql:3306/yappu_world - username: yapp - password: yapp + driver-class-name: org.h2.Driver + url: jdbc:h2:mem:testdb;MODE=Oracle;DATABASE_TO_LOWER=TRUE;NON_KEYWORDS=VALUE + username: sa + password: sql: init: mode: always + jpa: + database-platform: org.hibernate.dialect.OracleDialect + hibernate: + ddl-auto: none + show-sql: true jwt: secret_key: ${DEV_JWT_SECRET_KEY} diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index fa25dad7..d8d5216b 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -2,8 +2,8 @@ DROP TABLE IF EXISTS config; CREATE TABLE config ( name varchar(64) PRIMARY KEY, - created_at datetime(6), - updated_at datetime(6), + created_at TIMESTAMP, + updated_at TIMESTAMP, label varchar(32), category varchar(32), value varchar(128) @@ -13,23 +13,23 @@ DROP TABLE IF EXISTS users; CREATE TABLE users ( id binary(16) PRIMARY KEY, - created_at datetime(6), - updated_at datetime(6), + created_at TIMESTAMP, + updated_at TIMESTAMP, email varchar(64) NOT NULL, password varchar(64) NOT NULL, name varchar(16) NOT NULL, role varchar(16) NOT NULL, gender varchar(8), phone_number varchar(16), - is_active tinyint(1) NOT NULL + is_active NUMBER(1) NOT NULL ); DROP TABLE IF EXISTS sign_up_application; CREATE TABLE sign_up_application ( id binary(16) PRIMARY KEY, - created_at datetime(6), - updated_at datetime(6), + created_at TIMESTAMP, + updated_at TIMESTAMP, applicant_email varchar(64) NOT NULL, details json NOT NULL, status varchar(16) NOT NULL, @@ -40,8 +40,8 @@ DROP TABLE IF EXISTS activity_units; CREATE TABLE activity_units ( id binary(16) PRIMARY KEY, - created_at datetime(6), - updated_at datetime(6), + created_at TIMESTAMP, + updated_at TIMESTAMP, generation int NOT NULL, position varchar(16) NOT NULL, user_id binary(16) NOT NULL @@ -51,19 +51,19 @@ DROP TABLE IF EXISTS user_alarm_settings; CREATE TABLE user_alarm_settings ( id binary(16) PRIMARY KEY, - created_at datetime(6), - updated_at datetime(6), + created_at TIMESTAMP, + updated_at TIMESTAMP, user_id binary(16) NOT NULL, - device tinyint(1) NOT NULL, - master tinyint(1) NOT NULL + device NUMBER(1) NOT NULL, + master NUMBER(1) NOT NULL ); DROP TABLE IF EXISTS user_devices; CREATE TABLE user_devices ( id binary(16) PRIMARY KEY, - created_at datetime(6), - updated_at datetime(6), + created_at TIMESTAMP, + updated_at TIMESTAMP, user_id binary(16) NOT NULL, fcm_token varchar(512) NOT NULL ); @@ -72,20 +72,20 @@ DROP TABLE IF EXISTS schedules; CREATE TABLE schedules ( id binary(16) PRIMARY KEY, - created_at datetime(6), - updated_at datetime(6), - is_deleted tinyint(1) NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP, + is_deleted NUMBER(1) NOT NULL, name varchar(32) NOT NULL, description varchar(256), place varchar(32), address varchar(64), longitude double, latitude double, - date date NOT NULL, - end_date date NOT NULL, - time time(6), - end_time time(6), - is_all_day tinyint(1) NOT NULL, + start_date varchar(10) NOT NULL, + end_date varchar(10) NOT NULL, + start_time varchar(32), + end_time varchar(32), + is_all_day NUMBER(1) NOT NULL, generation int, type varchar(32) NOT NULL, session_type varchar(32) @@ -95,8 +95,8 @@ DROP TABLE IF EXISTS posts; CREATE TABLE posts ( id binary(16) PRIMARY KEY, - created_at datetime(6), - updated_at datetime(6), + created_at TIMESTAMP, + updated_at TIMESTAMP, type varchar(255), notice_type varchar(255), title varchar(64), @@ -104,7 +104,7 @@ CREATE TABLE posts content_summary varchar(255), display_target varchar(255), writer_id binary(16) NOT NULL, - is_active tinyint(1) NOT NULL, + is_active NUMBER(1) NOT NULL, session_id binary(16) ); @@ -112,29 +112,29 @@ DROP TABLE IF EXISTS generations; CREATE TABLE generations ( value int PRIMARY KEY, - start_date date, - end_date date, - is_active tinyint(1) NOT NULL + start_date varchar(10), + end_date varchar(10), + is_active NUMBER(1) NOT NULL ); drop table if exists attendances; create table attendances ( id binary(16) PRIMARY KEY, - created_at datetime(6), - updated_at datetime(6), + created_at TIMESTAMP, + updated_at TIMESTAMP, user_id binary(16) NOT NULL, schedule_id binary(16) NOT NULL, status varchar(32) NOT NULL, - user_checked_in_at datetime(6) + user_checked_in_at TIMESTAMP ); drop table if exists late_passes; create table late_passes ( id binary(16) PRIMARY KEY, - created_at datetime(6), - updated_at datetime(6), + created_at TIMESTAMP, + updated_at TIMESTAMP, user_id binary(16) NOT NULL, generation int NOT NULL, count int NOT NULL DEFAULT 0 @@ -144,8 +144,8 @@ DROP TABLE IF EXISTS teams; CREATE TABLE teams ( id binary(16) PRIMARY KEY, - created_at datetime(6), - updated_at datetime(6), + created_at TIMESTAMP, + updated_at TIMESTAMP, generation int NOT NULL, name varchar(255) NOT NULL ); @@ -154,10 +154,10 @@ DROP TABLE IF EXISTS team_services; CREATE TABLE team_services ( id binary(16) PRIMARY KEY, - created_at datetime(6), - updated_at datetime(6), - has_app tinyint(1) NOT NULL DEFAULT 0, - has_web tinyint(1) NOT NULL DEFAULT 0, + created_at TIMESTAMP, + updated_at TIMESTAMP, + has_app NUMBER(1) NOT NULL DEFAULT 0, + has_web NUMBER(1) NOT NULL DEFAULT 0, name varchar(255) DEFAULT NULL, service_links json DEFAULT NULL, team_id binary(16) NOT NULL @@ -167,8 +167,8 @@ DROP TABLE IF EXISTS team_members; CREATE TABLE team_members ( id binary(16) PRIMARY KEY, - created_at datetime(6), - updated_at datetime(6), + created_at TIMESTAMP, + updated_at TIMESTAMP, activity_unit_id binary(16) NOT NULL, team_id binary(16) NOT NULL ); diff --git a/src/test/kotlin/co/yappuworld/operation/client/application/GenerationActiveStateManagerTest.kt b/src/test/kotlin/co/yappuworld/operation/client/application/GenerationActiveStateManagerTest.kt index d6ea57c4..0bfcbfc9 100644 --- a/src/test/kotlin/co/yappuworld/operation/client/application/GenerationActiveStateManagerTest.kt +++ b/src/test/kotlin/co/yappuworld/operation/client/application/GenerationActiveStateManagerTest.kt @@ -2,6 +2,7 @@ package co.yappuworld.operation.client.application import co.yappuworld.global.exception.BusinessException import co.yappuworld.operation.domain.GenerationEntity +import co.yappuworld.operation.infrastructure.GenerationFindService import co.yappuworld.operation.infrastructure.GenerationRepository import co.yappuworld.support.environment.CustomDataJpaTestFeatureSpec import io.kotest.assertions.throwables.shouldThrow @@ -17,7 +18,8 @@ class GenerationActiveStateManagerTest @Autowired constructor( private val repository: GenerationRepository ) : CustomDataJpaTestFeatureSpec({ - val generationActiveStateManager = GenerationActiveStateManager(repository) + val generationFindService = GenerationFindService(repository) + val generationActiveStateManager = GenerationActiveStateManager(generationFindService, repository) beforeEach { fun saveNotExist(generation: GenerationEntity) { diff --git a/src/test/kotlin/co/yappuworld/user/client/application/usecase/SignUpExecutorConcurrencyTest.kt b/src/test/kotlin/co/yappuworld/user/client/application/usecase/SignUpExecutorConcurrencyTest.kt index ad69d34c..7e60dacb 100644 --- a/src/test/kotlin/co/yappuworld/user/client/application/usecase/SignUpExecutorConcurrencyTest.kt +++ b/src/test/kotlin/co/yappuworld/user/client/application/usecase/SignUpExecutorConcurrencyTest.kt @@ -63,7 +63,7 @@ class SignUpExecutorConcurrencyTest @Autowired constructor( signUpApplicationRepository.deleteAll(applications) } - feature("동시에 같은 이메일로") { + xfeature("동시에 같은 이메일로 - MySQL Named Lock 사용으로 H2에서 비활성화") { scenario("회원가입") { val threadCount = 20 diff --git a/src/test/kotlin/co/yappuworld/user/client/application/usecase/SignUpExecutorTest.kt b/src/test/kotlin/co/yappuworld/user/client/application/usecase/SignUpExecutorTest.kt index c807b4af..04fc4cb3 100644 --- a/src/test/kotlin/co/yappuworld/user/client/application/usecase/SignUpExecutorTest.kt +++ b/src/test/kotlin/co/yappuworld/user/client/application/usecase/SignUpExecutorTest.kt @@ -16,7 +16,7 @@ class SignUpExecutorTest @Autowired constructor( private val signUpApplicationRepository: SignUpApplicationRepository ) : SpringBootTestFeatureSpec({ - feature("가입코드를 이용한 회원가입") { + xfeature("가입코드를 이용한 회원가입 - MySQL Named Lock 사용으로 H2에서 비활성화") { scenario("기존에 보류 상태의 가입 신청이 있었다면, 거절 처리된다.") { val signUpApplication = getSignUpApplicationEntityFixture(