diff --git a/.github/workflows/backend-cd-dev.yml b/.github/workflows/backend-cd-dev.yml deleted file mode 100644 index e859b70..0000000 --- a/.github/workflows/backend-cd-dev.yml +++ /dev/null @@ -1,115 +0,0 @@ -name: Backend CD Dev Server - -on: - push: - branches: [ backend-dev ] - paths: [ 'backend/**' ] - workflow_dispatch: - -jobs: - build-and-deploy: - runs-on: [self-hosted, dev-runner] - - steps: - - name: Initialize workspace permissions - run: | - sudo chown -R ubuntu:ubuntu /home/ubuntu/actions-runner/_work - - - name: Checkout project repository - uses: actions/checkout@v4 - - - name: Create application-secret.yml file - run: | - mkdir -p backend/src/main/resources - echo "${{ secrets.DEV_SECRET_YML }}" > backend/src/main/resources/application-secret.yml - - - name: Create firebase-adminsdk-account.json file - run: | - mkdir -p backend/src/main/resources/firebase - echo "${{ secrets.FIREBASE_ADMINSDK_ACCOUNT_KEY }}" > backend/src/main/resources/firebase/firebase-adminsdk-account.json - - - name: Gradle build - run: | - chmod +x ./gradlew - sudo ./gradlew clean build - working-directory: ./backend - - - name: Get build jar file info - id: get_build_jar - run: | - BUILD_JAR_PATH=$(find ./backend/build/libs -name "*.jar" ! -name "*plain.jar" | head -n 1) - echo "BUILD_JAR_PATH=${BUILD_JAR_PATH}" >> $GITHUB_OUTPUT - echo "BUILD_JAR_NAME=$(basename ${BUILD_JAR_PATH})" >> $GITHUB_OUTPUT - - - name: Deploy to EC2 - env: - DEV_BLUE_WAS_PORT: ${{ secrets.DEV_BLUE_WAS_PORT }} - run: | - # 0. WAS 포트 - WAS_PORT=${DEV_BLUE_WAS_PORT} - - # 1. 이전 WAS 종료 (Graceful Shutdown) - echo "Stopping old Spring WAS..." - PID=$(sudo lsof -t -i:${WAS_PORT} || true) - if [ -n "$PID" ]; then - echo "Found running process with PID: $PID. Sending SIGTERM." - sudo kill -SIGTERM $PID - # 프로세스 종료 대기 - for i in $(seq 1 20); do - if ! sudo kill -0 $PID 2>/dev/null; then - echo "Process $PID has terminated." - break - fi - echo "Waiting for process $PID to terminate... ($i/20)" - sleep 1 - done - if sudo kill -0 $PID 2>/dev/null; then - echo "Process $PID did not terminate gracefully. Forcing shutdown with SIGKILL." - sudo kill -9 $PID - sleep 3 - fi - else - echo "No process found on port ${WAS_PORT}." - fi - - # 2. 이전 JAR 파일 삭제 및 빌드 JAR 파일 복사 - echo "Deleting old JAR files..." - DEPLOY_DIRECTORY="/home/ubuntu/${{ github.event.repository.name }}/backend" - echo "DEPLOY_DIRECTORY=${DEPLOY_DIRECTORY}" - mkdir -p "${DEPLOY_DIRECTORY}" - sudo find "${DEPLOY_DIRECTORY}" -maxdepth 1 -name "*.jar" -delete - echo "Old JAR files deleted." - - BUILD_JAR_PATH="${{ steps.get_build_jar.outputs.BUILD_JAR_PATH }}" - BUILD_JAR_NAME="${{ steps.get_build_jar.outputs.BUILD_JAR_NAME }}" - DEPLOY_JAR_PATH="${DEPLOY_DIRECTORY}/${BUILD_JAR_NAME}" - echo "BUILD_JAR_PATH=${BUILD_JAR_PATH}" - echo "BUILD_JAR_NAME=${BUILD_JAR_NAME}" - echo "DEPLOY_JAR_PATH=${DEPLOY_JAR_PATH}" - sudo cp "${BUILD_JAR_PATH}" "${DEPLOY_JAR_PATH}" - echo "New JAR file copied." - - # 3. 새로운 WAS 실행 - echo "Starting new Spring WAS..." - LOG_PATH="${DEPLOY_DIRECTORY}/application.log" - echo "LOG_PATH=${LOG_PATH}" - - sudo nohup java -jar -Duser.timezone=Asia/Seoul "${DEPLOY_JAR_PATH}" --spring.profiles.active=dev --server.port=${WAS_PORT} & - for i in $(seq 1 30); do - if sudo lsof -t -i:${WAS_PORT} > /dev/null; then - echo "WAS has started and is listening on port ${WAS_PORT}." - break - fi - echo "Waiting for port ${WAS_PORT} to be occupied... ($i/30)" - sleep 1 - done - - NEW_WAS_PID=$(sudo lsof -t -i:${WAS_PORT} || true) - if [ -n "$NEW_WAS_PID" ]; then - echo "Application started with PID: $NEW_WAS_PID." - else - echo "Error: Application failed to start." - # git action failed 처리 - exit 1 - fi - echo "Deployment complete." diff --git a/.github/workflows/ci-cd-dev.yml b/.github/workflows/ci-cd-dev.yml new file mode 100644 index 0000000..5276995 --- /dev/null +++ b/.github/workflows/ci-cd-dev.yml @@ -0,0 +1,84 @@ +name: CI/CD Build, Upload, Deploy (Dev) + +on: + push: + branches: [ dev ] + workflow_dispatch: + +jobs: + build-and-upload: + runs-on: ubuntu-latest + + permissions: + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 21 + cache: gradle + + - name: Create application-secret.yml + run: | + mkdir -p src/main/resources + echo "${{ secrets.DEV_SECRET_YML }}" > src/main/resources/application-secret.yml + + - name: Create firebase-adminsdk-account.json + run: | + mkdir -p src/main/resources/firebase + echo '${{ secrets.FIREBASE_ADMINSDK_ACCOUNT_KEY }}' \ + > src/main/resources/firebase/firebase-adminsdk-account.json + + - name: Gradle build + run: | + chmod +x ./gradlew + ./gradlew clean build + + - name: Find executable jar + id: jar + run: | + JAR_PATH=$(find build/libs -name "*.jar" ! -name "*plain.jar" | head -n 1) + if [ -z "$JAR_PATH" ]; then + echo "No executable jar found"; exit 1; + fi + echo "jar_path=$JAR_PATH" >> "$GITHUB_OUTPUT" + echo "jar_name=$(basename "$JAR_PATH")" >> "$GITHUB_OUTPUT" + + - name: Create deploy bundle + id: bundle + run: | + mkdir -p deploy + cp "${{ steps.jar.outputs.jar_path }}" deploy/ + + cp infra/appspec.yml deploy/ 2>/dev/null || true + cp infra/*.sh deploy/ 2>/dev/null || true + + cd deploy + ZIP_NAME="festabook-$(date +'%Y%m%d%H%M%S')-${GITHUB_SHA::7}.zip" + zip -r "$ZIP_NAME" . + echo "zip_name=$ZIP_NAME" >> "$GITHUB_OUTPUT" + echo "zip_path=$(pwd)/$ZIP_NAME" >> "$GITHUB_OUTPUT" + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-region: ${{ secrets.AWS_DEPLOY_REGION }} + role-to-assume: ${{ secrets.AWS_OIDC_ROLE_ARN }} + role-session-name: festabook-ci-cd + + - name: Upload artifact to S3 + run: | + aws s3 cp "${{ steps.bundle.outputs.zip_path }}" \ + "s3://${{ secrets.S3_ARTIFACT_BUCKET }}/dev/builds/${{ steps.bundle.outputs.zip_name }}" + + - name: Trigger CodeDeploy deployment + run: | + aws deploy create-deployment \ + --application-name "${{ secrets.CODEDEPLOY_APP_NAME }}" \ + --deployment-group-name "${{ secrets.CODEDEPLOY_DEPLOYMENT_GROUP_DEV }}" \ + --s3-location bucket=${{ secrets.S3_ARTIFACT_BUCKET }},bundleType=zip,key=dev/builds/${{ steps.bundle.outputs.zip_name }} diff --git a/.github/workflows/ci-cd-prod.yml b/.github/workflows/ci-cd-prod.yml new file mode 100644 index 0000000..d5bef7f --- /dev/null +++ b/.github/workflows/ci-cd-prod.yml @@ -0,0 +1,84 @@ +name: CI/CD Build, Upload, Deploy (Prod) + +on: + push: + branches: [ prod ] + workflow_dispatch: + +jobs: + build-and-upload: + runs-on: ubuntu-latest + + permissions: + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 21 + cache: gradle + + - name: Create application-secret.yml + run: | + mkdir -p src/main/resources + echo "${{ secrets.PROD_SECRET_YML }}" > src/main/resources/application-secret.yml + + - name: Create firebase-adminsdk-account.json + run: | + mkdir -p src/main/resources/firebase + echo '${{ secrets.FIREBASE_ADMINSDK_ACCOUNT_KEY }}' \ + > src/main/resources/firebase/firebase-adminsdk-account.json + + - name: Gradle build + run: | + chmod +x ./gradlew + ./gradlew clean build + + - name: Find executable jar + id: jar + run: | + JAR_PATH=$(find build/libs -name "*.jar" ! -name "*plain.jar" | head -n 1) + if [ -z "$JAR_PATH" ]; then + echo "No executable jar found"; exit 1; + fi + echo "jar_path=$JAR_PATH" >> "$GITHUB_OUTPUT" + echo "jar_name=$(basename "$JAR_PATH")" >> "$GITHUB_OUTPUT" + + - name: Create deploy bundle + id: bundle + run: | + mkdir -p deploy + cp "${{ steps.jar.outputs.jar_path }}" deploy/ + + cp infra/appspec.yml deploy/ 2>/dev/null || true + cp infra/*.sh deploy/ 2>/dev/null || true + + cd deploy + ZIP_NAME="festabook-$(date +'%Y%m%d%H%M%S')-${GITHUB_SHA::7}.zip" + zip -r "$ZIP_NAME" . + echo "zip_name=$ZIP_NAME" >> "$GITHUB_OUTPUT" + echo "zip_path=$(pwd)/$ZIP_NAME" >> "$GITHUB_OUTPUT" + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-region: ${{ secrets.AWS_DEPLOY_REGION }} + role-to-assume: ${{ secrets.AWS_OIDC_ROLE_ARN }} + role-session-name: festabook-ci-cd + + - name: Upload artifact to S3 + run: | + aws s3 cp "${{ steps.bundle.outputs.zip_path }}" \ + "s3://${{ secrets.S3_ARTIFACT_BUCKET }}/prod/builds/${{ steps.bundle.outputs.zip_name }}" + + - name: Trigger CodeDeploy deployment + run: | + aws deploy create-deployment \ + --application-name "${{ secrets.CODEDEPLOY_APP_NAME }}" \ + --deployment-group-name "${{ secrets.CODEDEPLOY_DEPLOYMENT_GROUP_PROD }}" \ + --s3-location bucket=${{ secrets.S3_ARTIFACT_BUCKET }},bundleType=zip,key=prod/builds/${{ steps.bundle.outputs.zip_name }} diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/ci.yml similarity index 52% rename from .github/workflows/backend-ci.yml rename to .github/workflows/ci.yml index e1aab5a..20dc058 100644 --- a/.github/workflows/backend-ci.yml +++ b/.github/workflows/ci.yml @@ -1,16 +1,16 @@ -name: Backend CI Test +name: CI Test on: pull_request: branches: - - backend-prod - - backend-dev + - prod + - dev jobs: Run-PR-Test: runs-on: ubuntu-latest permissions: - contents: read + contents: write pull-requests: write checks: write @@ -24,33 +24,49 @@ jobs: java-version: '21' distribution: 'temurin' + - name: Cache SonarQube packages + uses: actions/cache@v4 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + - name: Gradle cache uses: actions/cache@v4 with: path: | ~/.gradle/caches ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('backend/**/*.gradle*', 'backend/**/gradle-wrapper.properties') }} + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} restore-keys: | ${{ runner.os }}-gradle- - name: Grant execute permission for gradlew - run: chmod +x backend/gradlew + run: chmod +x ./gradlew - name: Create firebase-adminsdk-account.json run: | - mkdir -p backend/src/main/resources/firebase - echo "${{ secrets.FIREBASE_ADMINSDK_ACCOUNT_KEY }}" > backend/src/main/resources/firebase/firebase-adminsdk-account.json + mkdir -p src/main/resources/firebase + echo "${{ secrets.FIREBASE_ADMINSDK_ACCOUNT_KEY }}" > src/main/resources/firebase/firebase-adminsdk-account.json - name: Run Gradle Test - run: ./gradlew clean test - working-directory: backend + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: ./gradlew clean test jacocoTestReport sonar - name: Publish Unit Test Results if: always() uses: EnricoMi/publish-unit-test-result-action@v2 with: - files: backend/build/test-results/test/TEST-*.xml + files: build/test-results/test/TEST-*.xml check_name: '테스트 결과 🛠️' check_run_annotations: 'none' comment_mode: 'off' + + - name: Comment coverage on PR + uses: madrapps/jacoco-report@v1.6 + with: + paths: build/reports/jacoco/test/jacocoTestReport.xml + token: ${{ secrets.GITHUB_TOKEN }} + min-coverage-overall: 90 + min-coverage-changed-files: 100 diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index 6e2d19d..3411f58 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -4,7 +4,7 @@ on: push: branches: - release/* - - main + - prod permissions: contents: write @@ -26,7 +26,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Publish Release - if: github.ref == 'refs/heads/main' + if: github.ref == 'refs/heads/prod' id: drafter uses: release-drafter/release-drafter@v6 with: diff --git a/.github/workflows/common-slack-notify-opened.yml b/.github/workflows/slack-notify-opened.yml similarity index 100% rename from .github/workflows/common-slack-notify-opened.yml rename to .github/workflows/slack-notify-opened.yml diff --git a/.github/workflows/common-slack-notify-rerequested.yml b/.github/workflows/slack-notify-rerequested.yml similarity index 100% rename from .github/workflows/common-slack-notify-rerequested.yml rename to .github/workflows/slack-notify-rerequested.yml diff --git a/.github/workflows/common-slack-notify-submitted.yml b/.github/workflows/slack-notify-submitted.yml similarity index 100% rename from .github/workflows/common-slack-notify-submitted.yml rename to .github/workflows/slack-notify-submitted.yml diff --git a/build.gradle b/build.gradle index 2eb6b76..ff93eb0 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,7 @@ plugins { id 'java' + id 'jacoco' + id "org.sonarqube" version "7.2.0.6526" id 'org.springframework.boot' version '3.5.3' id 'io.spring.dependency-management' version '1.1.7' } @@ -7,6 +9,17 @@ plugins { group = 'com.daedan' version = '0.0.1-SNAPSHOT' +jacoco { + toolVersion = "0.8.14" +} + +sonar { + properties { + property "sonar.projectKey", "festabook_backend" + property "sonar.organization", "festabook" + } +} + java { toolchain { languageVersion = JavaLanguageVersion.of(21) @@ -58,7 +71,7 @@ dependencies { // Monitoring implementation 'org.springframework.boot:spring-boot-starter-actuator' - implementation 'io.micrometer:micrometer-registry-cloudwatch2' + implementation 'io.micrometer:micrometer-registry-prometheus' // Tracing implementation 'io.micrometer:micrometer-tracing-bridge-otel' @@ -88,3 +101,28 @@ tasks.named('test') { springBoot { buildInfo() } + +test { + useJUnitPlatform() +} + +jacocoTestReport { + dependsOn test + + classDirectories.setFrom( + files(classDirectories.files.collect { + fileTree(dir: it, exclude: [ + "**/*Application*", + "**/*Config*", + "**/*Aspect*", + "**/*Logging*" + ]) + }) + ) + + reports { + html.required = true + xml.required = true + csv.required = false + } +} diff --git a/infra/appspec.yml b/infra/appspec.yml index 23b17fd..c398436 100644 --- a/infra/appspec.yml +++ b/infra/appspec.yml @@ -1,11 +1,26 @@ version: 0.0 os: linux + files: - - source: backend/infra/output/ + - source: / destination: /home/ubuntu/app hooks: + ApplicationStop: + - location: stop.sh + timeout: 60 + runas: ubuntu + + BeforeInstall: + - location: clean.sh + timeout: 60 + ApplicationStart: - - location: backend/infra/output/start.sh + - location: start.sh timeout: 60 runas: ubuntu + + ValidateService: + - location: validate.sh + timeout: 90 + runas: ubuntu diff --git a/infra/clean.sh b/infra/clean.sh new file mode 100644 index 0000000..2ce36c5 --- /dev/null +++ b/infra/clean.sh @@ -0,0 +1,3 @@ +#!/bin/bash +echo "> Cleaning old jar files" +rm -rf /home/ubuntu/app/* diff --git a/infra/start.sh b/infra/start.sh index f4478ee..19f25a5 100644 --- a/infra/start.sh +++ b/infra/start.sh @@ -1,16 +1,28 @@ #!/bin/bash APP_HOME="/home/ubuntu/app" LOG_FILE="/tmp/app.log" -JAR_NAME=$(find $APP_HOME -name "*.jar" | head -n 1) +JAR_NAME=$(find "$APP_HOME" -maxdepth 1 -name "*.jar" | head -n 1) -echo "🚀========== ApplicationStart ==========" +PROFILE=prod + +if [[ "$DEPLOYMENT_GROUP_NAME" == *"dev"* ]]; then + PROFILE=dev +fi +echo "🚀========== ApplicationStart ==========" echo "▶️ Spring WAS 실행 중..." -if [ -f "$JAR_NAME" ]; then - sudo nohup java -jar -Duser.timezone=Asia/Seoul "$JAR_NAME" \ - --spring.profiles.active=prod > $LOG_FILE 2>&1 & - echo "📦 실행 파일: $JAR_NAME" -else +echo "▶️ DEPLOYMENT_GROUP_NAME = $DEPLOYMENT_GROUP_NAME" +echo "▶️ Active Spring profile = $PROFILE" + +if [ -z "$JAR_NAME" ]; then echo "❌ 오류: $APP_HOME 경로에서 JAR 파일을 찾을 수 없습니다." exit 1 fi + +echo "📦 실행 파일: $JAR_NAME" + +sudo nohup java -jar -Duser.timezone=Asia/Seoul "$JAR_NAME" \ + --spring.profiles.active=$PROFILE > $LOG_FILE 2>&1 & + +echo "✅ Spring WAS 실행 명령 전송 완료" +exit 0 diff --git a/infra/stop.sh b/infra/stop.sh new file mode 100644 index 0000000..46af485 --- /dev/null +++ b/infra/stop.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +PID=$(pgrep -f "festabook.*\.jar") + +if [ -z "$PID" ]; then + echo "> No running application found" +else + echo "> Killing process $PID" + + sudo kill -15 $PID + + TIMEOUT=30 + COUNT=0 + + while [ $COUNT -lt $TIMEOUT ]; do + if ps -p $PID > /dev/null 2>&1; then + echo "> 종료 대기 중... ($COUNT초 경과)" + sleep 1 + COUNT=$((COUNT + 1)) + else + echo "> 프로세스($PID)가 정상적으로 종료되었습니다." + break + fi + done + + if ps -p $PID > /dev/null 2>&1; then + echo "> 경고: $TIMEOUT초 안에 프로세스($PID)가 종료되지 않았습니다." + echo "> 강제 종료(SIGKILL -9)를 진행합니다." + sudo kill -9 $PID + else + echo "> 이전 애플리케이션 정리 완료." + fi +fi diff --git a/infra/validate.sh b/infra/validate.sh new file mode 100644 index 0000000..8d685ea --- /dev/null +++ b/infra/validate.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +HEALTH_URL="http://localhost:9000/actuator/health" + +echo "🔍========== ValidateService ==========" +echo "▶️ 서버 헬스 체크 시작: $HEALTH_URL" + +for i in {1..90} +do + STATUS_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$HEALTH_URL" || echo "000") + echo "⏱ 헬스 체크 시도 #$i → $STATUS_CODE" + + if [ "$STATUS_CODE" = "200" ]; then + echo "✅ 헬스 체크 통과" + exit 0 + fi + + sleep 1 +done + +echo "❌ 헬스 체크 실패: $HEALTH_URL" +exit 1 diff --git a/src/main/java/com/daedan/festabook/global/config/CloudWatchMetricsConfig.java b/src/main/java/com/daedan/festabook/global/config/CloudWatchMetricsConfig.java deleted file mode 100644 index 2dee54c..0000000 --- a/src/main/java/com/daedan/festabook/global/config/CloudWatchMetricsConfig.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.daedan.festabook.global.config; - -import io.micrometer.cloudwatch2.CloudWatchConfig; -import io.micrometer.cloudwatch2.CloudWatchMeterRegistry; -import io.micrometer.core.instrument.Clock; -import java.time.Duration; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; -import software.amazon.awssdk.services.cloudwatch.CloudWatchAsyncClient; - -@Profile({"prod", "dev"}) -@Configuration -public class CloudWatchMetricsConfig { - - @Value("${cloudwatch.namespace}") - private String cloudWatchNamespace; - - private static final Duration CLOUDWATCH_STEP = Duration.ofMinutes(1); - - @Bean - public CloudWatchConfig cloudWatchConfig() { - return new CloudWatchConfig() { - - @Override - public String get(String key) { - return null; - } - - @Override - public String namespace() { - return cloudWatchNamespace; - } - - @Override - public Duration step() { - return CLOUDWATCH_STEP; - } - }; - } - - @Bean - public CloudWatchAsyncClient cloudWatchAsyncClient() { - return CloudWatchAsyncClient.create(); - } - - @Bean - public CloudWatchMeterRegistry cloudWatchMeterRegistry(CloudWatchConfig config, - CloudWatchAsyncClient client) { - return new CloudWatchMeterRegistry(config, Clock.SYSTEM, client); - } -} diff --git a/src/main/java/com/daedan/festabook/global/config/S3ClientConfig.java b/src/main/java/com/daedan/festabook/global/config/S3ClientConfig.java index e52cdda..4755bdd 100644 --- a/src/main/java/com/daedan/festabook/global/config/S3ClientConfig.java +++ b/src/main/java/com/daedan/festabook/global/config/S3ClientConfig.java @@ -8,7 +8,7 @@ import software.amazon.awssdk.services.s3.S3Client; @Configuration -@Profile("prod") +@Profile("prod | dev") public class S3ClientConfig { @Value("${cloud.aws.region.static}") diff --git a/src/main/java/com/daedan/festabook/global/logging/LocalLoggingAspect.java b/src/main/java/com/daedan/festabook/global/logging/LocalLoggingAspect.java index e51288a..ac2e623 100644 --- a/src/main/java/com/daedan/festabook/global/logging/LocalLoggingAspect.java +++ b/src/main/java/com/daedan/festabook/global/logging/LocalLoggingAspect.java @@ -17,16 +17,7 @@ @Order(Ordered.HIGHEST_PRECEDENCE) public class LocalLoggingAspect { - @Around(""" - execution(* com.daedan.festabook..*.*(..)) && - ( - within(@org.springframework.web.bind.annotation.RestController *) || - within(@org.springframework.stereotype.Service *) || - within(@com.daedan.festabook.global.logging.Loggable *) || - execution(* org.springframework.data.jpa.repository.JpaRepository+.*(..)) - ) - """ - ) + @Around("com.daedan.festabook.global.logging.LoggingPointcuts.applicationLayers()") public Object allLayersLogging(ProceedingJoinPoint joinPoint) throws Throwable { StopWatch stopWatch = new StopWatch(); String className = joinPoint.getSignature().getDeclaringType().getSimpleName(); diff --git a/src/main/java/com/daedan/festabook/global/logging/LoggingAspect.java b/src/main/java/com/daedan/festabook/global/logging/LoggingAspect.java index 0e3f9c8..d39a906 100644 --- a/src/main/java/com/daedan/festabook/global/logging/LoggingAspect.java +++ b/src/main/java/com/daedan/festabook/global/logging/LoggingAspect.java @@ -4,9 +4,6 @@ import com.daedan.festabook.global.logging.dto.MethodEventLog; import com.daedan.festabook.global.logging.dto.MethodLog; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.Tracer; -import io.opentelemetry.context.Scope; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; @@ -23,21 +20,10 @@ @Component @Profile("prod | dev") @RequiredArgsConstructor -@Order(Ordered.HIGHEST_PRECEDENCE) +@Order(Ordered.HIGHEST_PRECEDENCE + 1) public class LoggingAspect { - private final Tracer tracer; - - @Around(""" - execution(* com.daedan.festabook..*.*(..)) && - ( - within(@org.springframework.web.bind.annotation.RestController *) || - within(@org.springframework.stereotype.Service *) || - within(@com.daedan.festabook.global.logging.Loggable *) || - execution(* org.springframework.data.jpa.repository.JpaRepository+.*(..)) - ) - """ - ) + @Around("com.daedan.festabook.global.logging.LoggingPointcuts.applicationLayers()") public Object allLayersLogging(ProceedingJoinPoint joinPoint) throws Throwable { StopWatch stopWatch = new StopWatch(); String className = joinPoint.getSignature().getDeclaringType().getSimpleName(); @@ -46,23 +32,15 @@ public Object allLayersLogging(ProceedingJoinPoint joinPoint) throws Throwable { MethodEventLog methodEvent = MethodEventLog.from(className, methodName); log.info("", kv("event", methodEvent)); - String spanName = className + "::" + methodName; - Span span = tracer.spanBuilder(spanName).startSpan(); - - Object result = null; stopWatch.start(); - try (Scope scope = span.makeCurrent()) { - result = joinPoint.proceed(); + try { + return joinPoint.proceed(); } finally { stopWatch.stop(); long executionTime = stopWatch.getTotalTimeMillis(); MethodLog methodLog = MethodLog.from(className, methodName, executionTime); log.info("", kv("event", methodLog)); - - span.end(); } - - return result; } } diff --git a/src/main/java/com/daedan/festabook/global/logging/LoggingPointcuts.java b/src/main/java/com/daedan/festabook/global/logging/LoggingPointcuts.java new file mode 100644 index 0000000..1885fc7 --- /dev/null +++ b/src/main/java/com/daedan/festabook/global/logging/LoggingPointcuts.java @@ -0,0 +1,21 @@ +package com.daedan.festabook.global.logging; + +import org.aspectj.lang.annotation.Pointcut; + +public final class LoggingPointcuts { + + private LoggingPointcuts() { + } + + @Pointcut(""" + execution(* com.daedan.festabook..*.*(..)) && + ( + within(@org.springframework.web.bind.annotation.RestController *) || + within(@org.springframework.stereotype.Service *) || + within(@com.daedan.festabook.global.logging.Loggable *) || + execution(* org.springframework.data.jpa.repository.JpaRepository+.*(..)) + ) + """) + public void applicationLayers() { + } +} diff --git a/src/main/java/com/daedan/festabook/global/logging/TracingAspect.java b/src/main/java/com/daedan/festabook/global/logging/TracingAspect.java new file mode 100644 index 0000000..6e13b94 --- /dev/null +++ b/src/main/java/com/daedan/festabook/global/logging/TracingAspect.java @@ -0,0 +1,48 @@ +package com.daedan.festabook.global.logging; + +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Scope; +import lombok.RequiredArgsConstructor; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Profile; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +@Aspect +@Component +@Profile("prod | dev") +@Order(Ordered.HIGHEST_PRECEDENCE) +public class TracingAspect { + + private final Tracer tracer; + private final String env; + + public TracingAspect( + Tracer tracer, + @Value("${env}") String env + ) { + this.tracer = tracer; + this.env = env; + } + + @Around("com.daedan.festabook.global.logging.LoggingPointcuts.applicationLayers()") + public Object trace(ProceedingJoinPoint joinPoint) throws Throwable { + String className = joinPoint.getSignature().getDeclaringType().getSimpleName(); + String methodName = joinPoint.getSignature().getName(); + + String spanName = className + "::" + methodName; + Span span = tracer.spanBuilder(spanName).startSpan(); + span.setAttribute("env", env); + + try (Scope scope = span.makeCurrent()) { + return joinPoint.proceed(); + } finally { + span.end(); + } + } +} diff --git a/src/main/java/com/daedan/festabook/global/security/config/SecurityConfig.java b/src/main/java/com/daedan/festabook/global/security/config/SecurityConfig.java index 1c8309c..58accba 100644 --- a/src/main/java/com/daedan/festabook/global/security/config/SecurityConfig.java +++ b/src/main/java/com/daedan/festabook/global/security/config/SecurityConfig.java @@ -50,6 +50,7 @@ public class SecurityConfig { "/lineups", "/time-tags", "/actuator/health", + "/actuator/prometheus", "/test/**" }; diff --git a/src/main/java/com/daedan/festabook/storage/infrastructure/LocalStorageManager.java b/src/main/java/com/daedan/festabook/storage/infrastructure/LocalStorageManager.java index 25b8171..6b95e44 100644 --- a/src/main/java/com/daedan/festabook/storage/infrastructure/LocalStorageManager.java +++ b/src/main/java/com/daedan/festabook/storage/infrastructure/LocalStorageManager.java @@ -11,14 +11,16 @@ import java.nio.file.Paths; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Profile; import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Component; @Slf4j @Loggable -@Component -@Profile("dev") +@Deprecated(since = "2025/11/27") +/** + * @Deprecated 날짜 : 2025/11/27 + * Dev 환경 로컬 저장 방식에서 S3 방식으로 변경되어 사용하지 않게 되었습니다. + * 하지만, 추후 로컬에서 사용할 수 있는 가능성이 있어 남겨둡니다. + */ public class LocalStorageManager implements StorageManager { @Value("${local.storage.base-path}") diff --git a/src/main/java/com/daedan/festabook/storage/infrastructure/S3StorageManager.java b/src/main/java/com/daedan/festabook/storage/infrastructure/S3StorageManager.java index 8a73842..96b7ac8 100644 --- a/src/main/java/com/daedan/festabook/storage/infrastructure/S3StorageManager.java +++ b/src/main/java/com/daedan/festabook/storage/infrastructure/S3StorageManager.java @@ -17,7 +17,7 @@ @Loggable @Component -@Profile("prod") +@Profile("prod | dev") public class S3StorageManager implements StorageManager { private final S3Client s3Client; diff --git a/src/main/resources/application-monitoring.yml b/src/main/resources/application-monitoring.yml new file mode 100644 index 0000000..fed9687 --- /dev/null +++ b/src/main/resources/application-monitoring.yml @@ -0,0 +1,26 @@ +server: + tomcat: + mbeanregistry: + enabled: true + +management: + server: + port: 9000 + endpoints: + web: + exposure: + include: prometheus, health + metrics: + tags: + application: ${spring.application.name} + prometheus: + metrics: + export: + enabled: true + tracing: + sampling: + probability: 1.0 + otlp: + tracing: + endpoint: "http://localhost:4317" + transport: grpc diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 34f086c..ae88145 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -4,9 +4,6 @@ server: context-path: /api shutdown: graceful forward-headers-strategy: native - tomcat: - mbeanregistry: - enabled: true spring: datasource: @@ -21,7 +18,7 @@ spring: hibernate: ddl-auto: none profiles: - include: secret + include: secret, monitoring lifecycle: # WAS 종료 대기 시간 15초로 설정 timeout-per-shutdown-phase: 15s @@ -31,7 +28,6 @@ spring: max-file-size: 10MB max-request-size: 11MB - storage: image: max-size: 10485760 # 10 * 1024 * 1024, 10MB diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml index 4031761..088b3e6 100644 --- a/src/main/resources/logback-spring.xml +++ b/src/main/resources/logback-spring.xml @@ -2,7 +2,7 @@ - + ${LOG_FILE_PATH}/application.json diff --git a/src/test/java/com/daedan/festabook/FestabookApplicationTests.java b/src/test/java/com/daedan/festabook/FestabookApplicationTests.java index 3449f7b..cc31320 100644 --- a/src/test/java/com/daedan/festabook/FestabookApplicationTests.java +++ b/src/test/java/com/daedan/festabook/FestabookApplicationTests.java @@ -1,13 +1,11 @@ package com.daedan.festabook; +import com.daedan.festabook.support.AcceptanceTestSupport; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; -@SpringBootTest -class FestabookApplicationTests { - - @Test - void contextLoads() { - } +class FestabookApplicationTests extends AcceptanceTestSupport { + @Test + void contextLoads() { + } } diff --git a/src/test/java/com/daedan/festabook/announcement/controller/AnnouncementControllerTest.java b/src/test/java/com/daedan/festabook/announcement/controller/AnnouncementControllerTest.java index 51621f1..071e0a9 100644 --- a/src/test/java/com/daedan/festabook/announcement/controller/AnnouncementControllerTest.java +++ b/src/test/java/com/daedan/festabook/announcement/controller/AnnouncementControllerTest.java @@ -26,29 +26,20 @@ import com.daedan.festabook.global.lock.ConcurrencyTestHelper; import com.daedan.festabook.global.security.JwtTestHelper; import com.daedan.festabook.global.security.role.RoleType; -import com.daedan.festabook.notification.infrastructure.FcmNotificationManager; +import com.daedan.festabook.support.AcceptanceTestSupport; import io.restassured.RestAssured; import io.restassured.http.ContentType; import io.restassured.http.Header; import java.util.List; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.EnumSource; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.HttpStatus; -import org.springframework.test.context.bean.override.mockito.MockitoBean; -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class AnnouncementControllerTest { +class AnnouncementControllerTest extends AcceptanceTestSupport { private static final String FESTIVAL_HEADER_NAME = "festival"; @@ -61,17 +52,6 @@ class AnnouncementControllerTest { @Autowired private JwtTestHelper jwtTestHelper; - @MockitoBean - private FcmNotificationManager fcmNotificationManager; - - @LocalServerPort - private int port; - - @BeforeEach - void setUp() { - RestAssured.port = port; - } - @Nested class createAnnouncement { @@ -443,7 +423,7 @@ class sendAnnouncementNotification { Announcement announcement = AnnouncementFixture.create(festival); announcementJpaRepository.save(announcement); - willDoNothing().given(fcmNotificationManager) + willDoNothing().given(festivalNotificationManager) .sendToFestivalTopic(any(), any()); // when & then @@ -455,7 +435,7 @@ class sendAnnouncementNotification { .then() .statusCode(HttpStatus.OK.value()); - then(fcmNotificationManager).should() + then(festivalNotificationManager).should() .sendToFestivalTopic(any(), any()); } } diff --git a/src/test/java/com/daedan/festabook/council/controller/CouncilControllerTest.java b/src/test/java/com/daedan/festabook/council/controller/CouncilControllerTest.java index 441b9f8..52e18dd 100644 --- a/src/test/java/com/daedan/festabook/council/controller/CouncilControllerTest.java +++ b/src/test/java/com/daedan/festabook/council/controller/CouncilControllerTest.java @@ -16,25 +16,18 @@ import com.daedan.festabook.festival.infrastructure.FestivalJpaRepository; import com.daedan.festabook.global.security.JwtTestHelper; import com.daedan.festabook.global.security.role.RoleType; +import com.daedan.festabook.support.AcceptanceTestSupport; import io.restassured.RestAssured; import io.restassured.http.ContentType; import io.restassured.http.Header; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.HttpStatus; -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class CouncilControllerTest { +class CouncilControllerTest extends AcceptanceTestSupport { @Autowired private FestivalJpaRepository festivalJpaRepository; @@ -42,14 +35,6 @@ class CouncilControllerTest { @Autowired private JwtTestHelper jwtTestHelper; - @LocalServerPort - private int port; - - @BeforeEach - void setUp() { - RestAssured.port = port; - } - @Nested class createCouncil { diff --git a/src/test/java/com/daedan/festabook/device/controller/DeviceControllerTest.java b/src/test/java/com/daedan/festabook/device/controller/DeviceControllerTest.java index 89667b6..ee03147 100644 --- a/src/test/java/com/daedan/festabook/device/controller/DeviceControllerTest.java +++ b/src/test/java/com/daedan/festabook/device/controller/DeviceControllerTest.java @@ -10,34 +10,19 @@ import com.daedan.festabook.device.dto.DeviceUpdateRequest; import com.daedan.festabook.device.dto.DeviceUpdateRequestFixture; import com.daedan.festabook.device.infrastructure.DeviceJpaRepository; +import com.daedan.festabook.support.AcceptanceTestSupport; import io.restassured.RestAssured; import io.restassured.http.ContentType; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.HttpStatus; -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class DeviceControllerTest { +class DeviceControllerTest extends AcceptanceTestSupport { @Autowired private DeviceJpaRepository deviceJpaRepository; - @LocalServerPort - private int port; - - @BeforeEach - void setUp() { - RestAssured.port = port; - } - @Nested class registerDevice { diff --git a/src/test/java/com/daedan/festabook/event/controller/EventControllerTest.java b/src/test/java/com/daedan/festabook/event/controller/EventControllerTest.java index 72b9f0e..a027730 100644 --- a/src/test/java/com/daedan/festabook/event/controller/EventControllerTest.java +++ b/src/test/java/com/daedan/festabook/event/controller/EventControllerTest.java @@ -22,6 +22,7 @@ import com.daedan.festabook.festival.infrastructure.FestivalJpaRepository; import com.daedan.festabook.global.security.JwtTestHelper; import com.daedan.festabook.global.security.role.RoleType; +import com.daedan.festabook.support.AcceptanceTestSupport; import io.restassured.RestAssured; import io.restassured.http.ContentType; import io.restassured.http.Header; @@ -31,24 +32,15 @@ import java.time.LocalTime; import java.time.ZoneId; import java.util.List; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.EnumSource; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.HttpStatus; -import org.springframework.test.context.bean.override.mockito.MockitoBean; -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class EventControllerTest { +class EventControllerTest extends AcceptanceTestSupport { @Autowired private EventDateJpaRepository eventDateJpaRepository; @@ -62,17 +54,6 @@ class EventControllerTest { @Autowired private JwtTestHelper jwtTestHelper; - @MockitoBean - private Clock clock; - - @LocalServerPort - private int port; - - @BeforeEach - void setUp() { - RestAssured.port = port; - } - @Nested class createEvent { diff --git a/src/test/java/com/daedan/festabook/event/controller/EventDateControllerTest.java b/src/test/java/com/daedan/festabook/event/controller/EventDateControllerTest.java index fec6fd2..18a524e 100644 --- a/src/test/java/com/daedan/festabook/event/controller/EventDateControllerTest.java +++ b/src/test/java/com/daedan/festabook/event/controller/EventDateControllerTest.java @@ -21,29 +21,20 @@ import com.daedan.festabook.festival.infrastructure.FestivalJpaRepository; import com.daedan.festabook.global.security.JwtTestHelper; import com.daedan.festabook.global.security.role.RoleType; +import com.daedan.festabook.support.AcceptanceTestSupport; import io.restassured.RestAssured; import io.restassured.http.ContentType; import io.restassured.http.Header; -import java.time.Clock; import java.time.LocalDate; import java.util.List; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.HttpStatus; -import org.springframework.test.context.bean.override.mockito.MockitoBean; -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class EventDateControllerTest { +class EventDateControllerTest extends AcceptanceTestSupport { private static final String FESTIVAL_HEADER_NAME = "festival"; @@ -59,17 +50,6 @@ class EventDateControllerTest { @Autowired private JwtTestHelper jwtTestHelper; - @MockitoBean - private Clock clock; - - @LocalServerPort - private int port; - - @BeforeEach - void setUp() { - RestAssured.port = port; - } - @Nested class createEventDate { diff --git a/src/test/java/com/daedan/festabook/festival/controller/AndroidFestivalNotificationSubscriptionControllerTest.java b/src/test/java/com/daedan/festabook/festival/controller/AndroidFestivalNotificationSubscriptionControllerTest.java index bc4359c..771d1fd 100644 --- a/src/test/java/com/daedan/festabook/festival/controller/AndroidFestivalNotificationSubscriptionControllerTest.java +++ b/src/test/java/com/daedan/festabook/festival/controller/AndroidFestivalNotificationSubscriptionControllerTest.java @@ -16,24 +16,15 @@ import com.daedan.festabook.festival.dto.FestivalNotificationRequestFixture; import com.daedan.festabook.festival.infrastructure.FestivalJpaRepository; import com.daedan.festabook.festival.infrastructure.FestivalNotificationJpaRepository; -import com.daedan.festabook.notification.infrastructure.FcmNotificationManager; +import com.daedan.festabook.support.AcceptanceTestSupport; import io.restassured.RestAssured; import io.restassured.http.ContentType; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.HttpStatus; -import org.springframework.test.context.bean.override.mockito.MockitoBean; -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class AndroidFestivalNotificationSubscriptionControllerTest { +class AndroidFestivalNotificationSubscriptionControllerTest extends AcceptanceTestSupport { @Autowired private FestivalJpaRepository festivalJpaRepository; @@ -44,17 +35,6 @@ class AndroidFestivalNotificationSubscriptionControllerTest { @Autowired private FestivalNotificationJpaRepository festivalNotificationJpaRepository; - @MockitoBean - private FcmNotificationManager fcmNotificationManager; - - @LocalServerPort - private int port; - - @BeforeEach - void setUp() { - RestAssured.port = port; - } - @Nested class subscribeAndroidFestivalNotification { @@ -83,7 +63,7 @@ class subscribeAndroidFestivalNotification { .body("size()", equalTo(expectedFieldSize)) .body("festivalNotificationId", notNullValue()); - then(fcmNotificationManager).should() + then(festivalNotificationManager).should() .subscribeAndroidFestivalTopic(any(), any()); } @@ -113,7 +93,7 @@ class subscribeAndroidFestivalNotification { .statusCode(HttpStatus.CONFLICT.value()) .body("message", equalTo("FestivalNotification 데이터베이스에 이미 존재합니다.")); - then(fcmNotificationManager).shouldHaveNoInteractions(); + then(festivalNotificationManager).shouldHaveNoInteractions(); } @Test @@ -136,7 +116,7 @@ class subscribeAndroidFestivalNotification { .statusCode(HttpStatus.BAD_REQUEST.value()) .body("message", equalTo("존재하지 않는 디바이스입니다.")); - then(fcmNotificationManager).shouldHaveNoInteractions(); + then(festivalNotificationManager).shouldHaveNoInteractions(); } @Test @@ -160,7 +140,7 @@ class subscribeAndroidFestivalNotification { .statusCode(HttpStatus.BAD_REQUEST.value()) .body("message", equalTo("존재하지 않는 축제입니다.")); - then(fcmNotificationManager).shouldHaveNoInteractions(); + then(festivalNotificationManager).shouldHaveNoInteractions(); } } } diff --git a/src/test/java/com/daedan/festabook/festival/controller/FestivalControllerTest.java b/src/test/java/com/daedan/festabook/festival/controller/FestivalControllerTest.java index f58abb0..cfc9dbd 100644 --- a/src/test/java/com/daedan/festabook/festival/controller/FestivalControllerTest.java +++ b/src/test/java/com/daedan/festabook/festival/controller/FestivalControllerTest.java @@ -23,6 +23,7 @@ import com.daedan.festabook.festival.infrastructure.FestivalJpaRepository; import com.daedan.festabook.global.security.JwtTestHelper; import com.daedan.festabook.global.security.role.RoleType; +import com.daedan.festabook.support.AcceptanceTestSupport; import io.restassured.RestAssured; import io.restassured.config.JsonConfig; import io.restassured.config.RestAssuredConfig; @@ -33,23 +34,15 @@ import java.util.List; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.EnumSource; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.HttpStatus; -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class FestivalControllerTest { +class FestivalControllerTest extends AcceptanceTestSupport { private static final String FESTIVAL_HEADER_NAME = "festival"; @@ -62,9 +55,6 @@ class FestivalControllerTest { @Autowired private JwtTestHelper jwtTestHelper; - @LocalServerPort - private int port; - @BeforeAll static void beforeAll() { RestAssured.config = RestAssured.config() @@ -77,11 +67,6 @@ static void afterAll() { RestAssured.config = RestAssuredConfig.config(); } - @BeforeEach - void setUp() { - RestAssured.port = port; - } - @Nested class createFestival { diff --git a/src/test/java/com/daedan/festabook/festival/controller/FestivalNotificationSubscriptionControllerTest.java b/src/test/java/com/daedan/festabook/festival/controller/FestivalNotificationSubscriptionControllerTest.java index 75eb673..84a8b53 100644 --- a/src/test/java/com/daedan/festabook/festival/controller/FestivalNotificationSubscriptionControllerTest.java +++ b/src/test/java/com/daedan/festabook/festival/controller/FestivalNotificationSubscriptionControllerTest.java @@ -18,25 +18,16 @@ import com.daedan.festabook.festival.dto.FestivalNotificationRequestFixture; import com.daedan.festabook.festival.infrastructure.FestivalJpaRepository; import com.daedan.festabook.festival.infrastructure.FestivalNotificationJpaRepository; -import com.daedan.festabook.notification.infrastructure.FcmNotificationManager; +import com.daedan.festabook.support.AcceptanceTestSupport; import io.restassured.RestAssured; import io.restassured.http.ContentType; import java.util.List; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.HttpStatus; -import org.springframework.test.context.bean.override.mockito.MockitoBean; -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class FestivalNotificationSubscriptionControllerTest { +class FestivalNotificationSubscriptionControllerTest extends AcceptanceTestSupport { @Autowired private FestivalJpaRepository festivalJpaRepository; @@ -47,17 +38,6 @@ class FestivalNotificationSubscriptionControllerTest { @Autowired private FestivalNotificationJpaRepository festivalNotificationJpaRepository; - @MockitoBean - private FcmNotificationManager fcmNotificationManager; - - @LocalServerPort - private int port; - - @BeforeEach - void setUp() { - RestAssured.port = port; - } - @Nested class subscribeFestivalNotification { @@ -86,7 +66,7 @@ class subscribeFestivalNotification { .body("size()", equalTo(expectedFieldSize)) .body("festivalNotificationId", notNullValue()); - then(fcmNotificationManager).should() + then(festivalNotificationManager).should() .subscribeFestivalTopic(any(), any()); } @@ -116,7 +96,7 @@ class subscribeFestivalNotification { .statusCode(HttpStatus.CREATED.value()) .body("festivalNotificationId", notNullValue()); - then(fcmNotificationManager).should() + then(festivalNotificationManager).should() .subscribeFestivalTopic(festival.getId(), device.getFcmToken()); } @@ -146,7 +126,7 @@ class subscribeFestivalNotification { .statusCode(HttpStatus.CONFLICT.value()) .body("message", equalTo("FestivalNotification 데이터베이스에 이미 존재합니다.")); - then(fcmNotificationManager).shouldHaveNoInteractions(); + then(festivalNotificationManager).shouldHaveNoInteractions(); } @Test @@ -169,7 +149,7 @@ class subscribeFestivalNotification { .statusCode(HttpStatus.BAD_REQUEST.value()) .body("message", equalTo("존재하지 않는 디바이스입니다.")); - then(fcmNotificationManager).shouldHaveNoInteractions(); + then(festivalNotificationManager).shouldHaveNoInteractions(); } @Test @@ -193,7 +173,7 @@ class subscribeFestivalNotification { .statusCode(HttpStatus.BAD_REQUEST.value()) .body("message", equalTo("존재하지 않는 축제입니다.")); - then(fcmNotificationManager).shouldHaveNoInteractions(); + then(festivalNotificationManager).shouldHaveNoInteractions(); } } @@ -269,7 +249,7 @@ class unsubscribeFestivalNotification { boolean exists = festivalNotificationJpaRepository.existsById(festivalNotification.getId()); assertThat(exists).isFalse(); - then(fcmNotificationManager).should() + then(festivalNotificationManager).should() .unsubscribeFestivalTopic(any(), any()); } @@ -301,7 +281,7 @@ class unsubscribeFestivalNotification { boolean exists = festivalNotificationJpaRepository.existsById(second.getId()); assertThat(exists).isFalse(); - then(fcmNotificationManager).should() + then(festivalNotificationManager).should() .unsubscribeFestivalTopic(any(), any()); } @@ -320,7 +300,7 @@ class unsubscribeFestivalNotification { .then() .statusCode(HttpStatus.NO_CONTENT.value()); - then(fcmNotificationManager).shouldHaveNoInteractions(); + then(festivalNotificationManager).shouldHaveNoInteractions(); } } } diff --git a/src/test/java/com/daedan/festabook/festival/controller/IosFestivalNotificationSubscriptionControllerTest.java b/src/test/java/com/daedan/festabook/festival/controller/IosFestivalNotificationSubscriptionControllerTest.java index 6105313..642af0f 100644 --- a/src/test/java/com/daedan/festabook/festival/controller/IosFestivalNotificationSubscriptionControllerTest.java +++ b/src/test/java/com/daedan/festabook/festival/controller/IosFestivalNotificationSubscriptionControllerTest.java @@ -16,24 +16,15 @@ import com.daedan.festabook.festival.dto.FestivalNotificationRequestFixture; import com.daedan.festabook.festival.infrastructure.FestivalJpaRepository; import com.daedan.festabook.festival.infrastructure.FestivalNotificationJpaRepository; -import com.daedan.festabook.notification.infrastructure.FcmNotificationManager; +import com.daedan.festabook.support.AcceptanceTestSupport; import io.restassured.RestAssured; import io.restassured.http.ContentType; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.HttpStatus; -import org.springframework.test.context.bean.override.mockito.MockitoBean; -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class IosFestivalNotificationSubscriptionControllerTest { +class IosFestivalNotificationSubscriptionControllerTest extends AcceptanceTestSupport { @Autowired private FestivalJpaRepository festivalJpaRepository; @@ -44,17 +35,6 @@ class IosFestivalNotificationSubscriptionControllerTest { @Autowired private FestivalNotificationJpaRepository festivalNotificationJpaRepository; - @MockitoBean - private FcmNotificationManager fcmNotificationManager; - - @LocalServerPort - private int port; - - @BeforeEach - void setUp() { - RestAssured.port = port; - } - @Nested class subscribeIosFestivalNotification { @@ -83,7 +63,7 @@ class subscribeIosFestivalNotification { .body("size()", equalTo(expectedFieldSize)) .body("festivalNotificationId", notNullValue()); - then(fcmNotificationManager).should() + then(festivalNotificationManager).should() .subscribeIosFestivalTopic(any(), any()); } @@ -113,7 +93,7 @@ class subscribeIosFestivalNotification { .statusCode(HttpStatus.CONFLICT.value()) .body("message", equalTo("FestivalNotification 데이터베이스에 이미 존재합니다.")); - then(fcmNotificationManager).shouldHaveNoInteractions(); + then(festivalNotificationManager).shouldHaveNoInteractions(); } @Test @@ -136,7 +116,7 @@ class subscribeIosFestivalNotification { .statusCode(HttpStatus.BAD_REQUEST.value()) .body("message", equalTo("존재하지 않는 디바이스입니다.")); - then(fcmNotificationManager).shouldHaveNoInteractions(); + then(festivalNotificationManager).shouldHaveNoInteractions(); } @Test @@ -160,7 +140,7 @@ class subscribeIosFestivalNotification { .statusCode(HttpStatus.BAD_REQUEST.value()) .body("message", equalTo("존재하지 않는 축제입니다.")); - then(fcmNotificationManager).shouldHaveNoInteractions(); + then(festivalNotificationManager).shouldHaveNoInteractions(); } } } diff --git a/src/test/java/com/daedan/festabook/festival/service/FestivalNotificationConcurrencyTest.java b/src/test/java/com/daedan/festabook/festival/service/FestivalNotificationConcurrencyTest.java index 51d54af..668a1d7 100644 --- a/src/test/java/com/daedan/festabook/festival/service/FestivalNotificationConcurrencyTest.java +++ b/src/test/java/com/daedan/festabook/festival/service/FestivalNotificationConcurrencyTest.java @@ -7,31 +7,22 @@ import com.daedan.festabook.device.infrastructure.DeviceJpaRepository; import com.daedan.festabook.festival.domain.Festival; import com.daedan.festabook.festival.domain.FestivalFixture; -import com.daedan.festabook.festival.domain.FestivalNotificationManager; import com.daedan.festabook.festival.dto.FestivalNotificationRequest; import com.daedan.festabook.festival.dto.FestivalNotificationRequestFixture; import com.daedan.festabook.festival.infrastructure.FestivalJpaRepository; import com.daedan.festabook.festival.infrastructure.FestivalNotificationJpaRepository; import com.daedan.festabook.global.lock.ConcurrencyTestHelper; +import com.daedan.festabook.support.AcceptanceTestSupport; import io.restassured.RestAssured; import io.restassured.http.ContentType; import io.restassured.response.Response; import java.util.concurrent.atomic.AtomicInteger; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.HttpStatus; -import org.springframework.test.context.bean.override.mockito.MockitoBean; -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class FestivalNotificationConcurrencyTest { +class FestivalNotificationConcurrencyTest extends AcceptanceTestSupport { @Autowired private FestivalJpaRepository festivalJpaRepository; @@ -42,17 +33,6 @@ class FestivalNotificationConcurrencyTest { @Autowired private FestivalNotificationJpaRepository festivalNotificationJpaRepository; - @MockitoBean - private FestivalNotificationManager festivalNotificationManager; - - @LocalServerPort - private int port; - - @BeforeEach - void setUp() { - RestAssured.port = port; - } - @Nested class subscribeFestivalNotification { diff --git a/src/test/java/com/daedan/festabook/global/argumentresolver/FestivalIdArgumentResolverTest.java b/src/test/java/com/daedan/festabook/global/argumentresolver/FestivalIdArgumentResolverTest.java index 5e0d22b..deda558 100644 --- a/src/test/java/com/daedan/festabook/global/argumentresolver/FestivalIdArgumentResolverTest.java +++ b/src/test/java/com/daedan/festabook/global/argumentresolver/FestivalIdArgumentResolverTest.java @@ -6,41 +6,23 @@ import com.daedan.festabook.festival.domain.Festival; import com.daedan.festabook.festival.domain.FestivalFixture; import com.daedan.festabook.festival.infrastructure.FestivalJpaRepository; -import com.daedan.festabook.global.config.TestSecurityConfig; +import com.daedan.festabook.support.AcceptanceTestSupport; import io.restassured.RestAssured; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.context.annotation.Import; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; -@Import(TestSecurityConfig.class) -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class FestivalIdArgumentResolverTest { +class FestivalIdArgumentResolverTest extends AcceptanceTestSupport { private static final String FESTIVAL_HEADER_NAME = "festival"; @Autowired private FestivalJpaRepository festivalJpaRepository; - @LocalServerPort - private int port; - - @BeforeEach - void setUp() { - RestAssured.port = port; - } - @Nested class resolveArgument { diff --git a/src/test/java/com/daedan/festabook/global/config/WebMvcConfigTest.java b/src/test/java/com/daedan/festabook/global/config/WebMvcConfigTest.java index f671bd8..796cef6 100644 --- a/src/test/java/com/daedan/festabook/global/config/WebMvcConfigTest.java +++ b/src/test/java/com/daedan/festabook/global/config/WebMvcConfigTest.java @@ -6,39 +6,23 @@ import com.daedan.festabook.festival.domain.FestivalFixture; import com.daedan.festabook.festival.infrastructure.FestivalJpaRepository; import com.daedan.festabook.global.argumentresolver.FestivalId; +import com.daedan.festabook.support.AcceptanceTestSupport; import io.restassured.RestAssured; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.context.annotation.Import; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; -@Import(TestSecurityConfig.class) -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class WebMvcConfigTest { +class WebMvcConfigTest extends AcceptanceTestSupport { private static final String FESTIVAL_HEADER_NAME = "festival"; @Autowired private FestivalJpaRepository festivalJpaRepository; - @LocalServerPort - private int port; - - @BeforeEach - void setUp() { - RestAssured.port = port; - } - @Nested class addArgumentResolvers { diff --git a/src/test/java/com/daedan/festabook/global/exception/GlobalExceptionHandlerTest.java b/src/test/java/com/daedan/festabook/global/exception/GlobalExceptionHandlerTest.java index a0e3754..a218481 100644 --- a/src/test/java/com/daedan/festabook/global/exception/GlobalExceptionHandlerTest.java +++ b/src/test/java/com/daedan/festabook/global/exception/GlobalExceptionHandlerTest.java @@ -8,24 +8,18 @@ import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.read.ListAppender; -import com.daedan.festabook.global.config.TestSecurityConfig; +import com.daedan.festabook.support.AcceptanceTestSupport; import io.restassured.RestAssured; import java.sql.SQLException; import java.util.stream.Stream; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.context.annotation.Import; import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessResourceFailureException; import org.springframework.dao.DataIntegrityViolationException; @@ -38,10 +32,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -@Import(TestSecurityConfig.class) -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class GlobalExceptionHandlerTest { +class GlobalExceptionHandlerTest extends AcceptanceTestSupport { private static final String MESSAGE_PARAM_NAME = "message"; private static final String STATUS_CODE_PARAM_NAME = "status-code"; @@ -49,14 +40,6 @@ class GlobalExceptionHandlerTest { @Autowired private ExceptionController exceptionController; - @LocalServerPort - private int port; - - @BeforeEach - void setUp() { - RestAssured.port = port; - } - @Nested class handleDatabaseException { diff --git a/src/test/java/com/daedan/festabook/global/security/config/SecurityConfigTest.java b/src/test/java/com/daedan/festabook/global/security/config/SecurityConfigTest.java index b619366..3d4e2c9 100644 --- a/src/test/java/com/daedan/festabook/global/security/config/SecurityConfigTest.java +++ b/src/test/java/com/daedan/festabook/global/security/config/SecurityConfigTest.java @@ -8,17 +8,11 @@ import com.daedan.festabook.festival.domain.Festival; import com.daedan.festabook.festival.domain.FestivalFixture; import com.daedan.festabook.festival.infrastructure.FestivalJpaRepository; -import com.daedan.festabook.global.config.TestSecurityConfig; +import com.daedan.festabook.support.AcceptanceTestSupport; import io.restassured.RestAssured; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.context.annotation.Import; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -28,10 +22,7 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; -@Import({TestSecurityConfig.class, SecurityConfig.class}) -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class SecurityConfigTest { +class SecurityConfigTest extends AcceptanceTestSupport { private static final String ORIGIN_LOCALHOST = "http://localhost:5173"; private static final String ORIGIN_LOCALHOST_IP = "http://127.0.0.1:5173"; @@ -39,14 +30,6 @@ class SecurityConfigTest { @Autowired private FestivalJpaRepository festivalJpaRepository; - @LocalServerPort - int port; - - @BeforeEach - void setUp() { - RestAssured.port = port; - } - @Nested class CORS_검증 { diff --git a/src/test/java/com/daedan/festabook/lineup/controller/LineupControllerTest.java b/src/test/java/com/daedan/festabook/lineup/controller/LineupControllerTest.java index 0288113..553d1d5 100644 --- a/src/test/java/com/daedan/festabook/lineup/controller/LineupControllerTest.java +++ b/src/test/java/com/daedan/festabook/lineup/controller/LineupControllerTest.java @@ -16,27 +16,20 @@ import com.daedan.festabook.lineup.dto.LineupUpdateRequest; import com.daedan.festabook.lineup.dto.LineupUpdateRequestFixture; import com.daedan.festabook.lineup.infrastructure.LineupJpaRepository; +import com.daedan.festabook.support.AcceptanceTestSupport; import io.restassured.RestAssured; import io.restassured.http.ContentType; import io.restassured.http.Header; import java.time.LocalDateTime; import java.util.List; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.HttpStatus; -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class LineupControllerTest { +class LineupControllerTest extends AcceptanceTestSupport { private static final String FESTIVAL_HEADER_NAME = "festival"; @@ -49,14 +42,6 @@ class LineupControllerTest { @Autowired private JwtTestHelper jwtTestHelper; - @LocalServerPort - private int port; - - @BeforeEach - void setUp() { - RestAssured.port = port; - } - @Nested class addLineup { diff --git a/src/test/java/com/daedan/festabook/lostitem/controller/LostItemControllerTest.java b/src/test/java/com/daedan/festabook/lostitem/controller/LostItemControllerTest.java index d2d7c2e..d4a1382 100644 --- a/src/test/java/com/daedan/festabook/lostitem/controller/LostItemControllerTest.java +++ b/src/test/java/com/daedan/festabook/lostitem/controller/LostItemControllerTest.java @@ -19,26 +19,20 @@ import com.daedan.festabook.lostitem.dto.LostItemStatusUpdateRequest; import com.daedan.festabook.lostitem.dto.LostItemStatusUpdateRequestFixture; import com.daedan.festabook.lostitem.infrastructure.LostItemJpaRepository; +import com.daedan.festabook.support.AcceptanceTestSupport; import io.restassured.RestAssured; import io.restassured.http.ContentType; import io.restassured.http.Header; import java.util.List; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.EnumSource; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.HttpStatus; -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class LostItemControllerTest { +class LostItemControllerTest extends AcceptanceTestSupport { private static final String FESTIVAL_HEADER_NAME = "festival"; @@ -51,14 +45,6 @@ class LostItemControllerTest { @Autowired private JwtTestHelper jwtTestHelper; - @LocalServerPort - private int port; - - @BeforeEach - void setUp() { - RestAssured.port = port; - } - @Nested class createLostItem { diff --git a/src/test/java/com/daedan/festabook/place/controller/PlaceAnnouncementControllerTest.java b/src/test/java/com/daedan/festabook/place/controller/PlaceAnnouncementControllerTest.java index 5fa6fc1..558e470 100644 --- a/src/test/java/com/daedan/festabook/place/controller/PlaceAnnouncementControllerTest.java +++ b/src/test/java/com/daedan/festabook/place/controller/PlaceAnnouncementControllerTest.java @@ -20,26 +20,19 @@ import com.daedan.festabook.place.dto.PlaceAnnouncementUpdateRequestFixture; import com.daedan.festabook.place.infrastructure.PlaceAnnouncementJpaRepository; import com.daedan.festabook.place.infrastructure.PlaceJpaRepository; +import com.daedan.festabook.support.AcceptanceTestSupport; import io.restassured.RestAssured; import io.restassured.http.ContentType; import io.restassured.http.Header; import java.util.List; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.HttpStatus; -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class PlaceAnnouncementControllerTest { +class PlaceAnnouncementControllerTest extends AcceptanceTestSupport { @Autowired private FestivalJpaRepository festivalJpaRepository; @@ -53,14 +46,6 @@ class PlaceAnnouncementControllerTest { @Autowired private JwtTestHelper jwtTestHelper; - @LocalServerPort - private int port; - - @BeforeEach - void setUp() { - RestAssured.port = port; - } - @Nested class createPlaceAnnouncement { diff --git a/src/test/java/com/daedan/festabook/place/controller/PlaceControllerTest.java b/src/test/java/com/daedan/festabook/place/controller/PlaceControllerTest.java index 17b54db..1e05147 100644 --- a/src/test/java/com/daedan/festabook/place/controller/PlaceControllerTest.java +++ b/src/test/java/com/daedan/festabook/place/controller/PlaceControllerTest.java @@ -15,7 +15,6 @@ import com.daedan.festabook.festival.domain.Festival; import com.daedan.festabook.festival.domain.FestivalFixture; import com.daedan.festabook.festival.infrastructure.FestivalJpaRepository; -import com.daedan.festabook.global.infrastructure.ShuffleManager; import com.daedan.festabook.global.security.JwtTestHelper; import com.daedan.festabook.global.security.role.RoleType; import com.daedan.festabook.place.domain.Place; @@ -40,6 +39,7 @@ import com.daedan.festabook.place.infrastructure.PlaceFavoriteJpaRepository; import com.daedan.festabook.place.infrastructure.PlaceImageJpaRepository; import com.daedan.festabook.place.infrastructure.PlaceJpaRepository; +import com.daedan.festabook.support.AcceptanceTestSupport; import com.daedan.festabook.timetag.domain.PlaceTimeTag; import com.daedan.festabook.timetag.domain.PlaceTimeTagFixture; import com.daedan.festabook.timetag.domain.TimeTag; @@ -51,23 +51,14 @@ import io.restassured.http.Header; import java.time.LocalTime; import java.util.List; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.HttpStatus; -import org.springframework.test.context.bean.override.mockito.MockitoBean; -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class PlaceControllerTest { +class PlaceControllerTest extends AcceptanceTestSupport { private static final String FESTIVAL_HEADER_NAME = "festival"; @@ -98,17 +89,6 @@ class PlaceControllerTest { @Autowired private JwtTestHelper jwtTestHelper; - @MockitoBean - private ShuffleManager shuffleManager; - - @LocalServerPort - private int port; - - @BeforeEach - void setUp() { - RestAssured.port = port; - } - @Nested class createPlace { diff --git a/src/test/java/com/daedan/festabook/place/controller/PlaceFavoriteControllerTest.java b/src/test/java/com/daedan/festabook/place/controller/PlaceFavoriteControllerTest.java index 822960c..b7db09f 100644 --- a/src/test/java/com/daedan/festabook/place/controller/PlaceFavoriteControllerTest.java +++ b/src/test/java/com/daedan/festabook/place/controller/PlaceFavoriteControllerTest.java @@ -18,22 +18,15 @@ import com.daedan.festabook.place.dto.PlaceFavoriteRequestFixture; import com.daedan.festabook.place.infrastructure.PlaceFavoriteJpaRepository; import com.daedan.festabook.place.infrastructure.PlaceJpaRepository; +import com.daedan.festabook.support.AcceptanceTestSupport; import io.restassured.RestAssured; import io.restassured.http.ContentType; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.HttpStatus; -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class PlaceFavoriteControllerTest { +class PlaceFavoriteControllerTest extends AcceptanceTestSupport { @Autowired private FestivalJpaRepository festivalJpaRepository; @@ -47,14 +40,6 @@ class PlaceFavoriteControllerTest { @Autowired private PlaceFavoriteJpaRepository placeFavoriteJpaRepository; - @LocalServerPort - private int port; - - @BeforeEach - void setUp() { - RestAssured.port = port; - } - @Nested class addPlaceFavorite { diff --git a/src/test/java/com/daedan/festabook/place/controller/PlaceGeographyControllerTest.java b/src/test/java/com/daedan/festabook/place/controller/PlaceGeographyControllerTest.java index 6c05202..b57ba4e 100644 --- a/src/test/java/com/daedan/festabook/place/controller/PlaceGeographyControllerTest.java +++ b/src/test/java/com/daedan/festabook/place/controller/PlaceGeographyControllerTest.java @@ -15,6 +15,7 @@ import com.daedan.festabook.place.dto.PlaceCoordinateRequest; import com.daedan.festabook.place.dto.PlaceCoordinateRequestFixture; import com.daedan.festabook.place.infrastructure.PlaceJpaRepository; +import com.daedan.festabook.support.AcceptanceTestSupport; import com.daedan.festabook.timetag.domain.PlaceTimeTag; import com.daedan.festabook.timetag.domain.PlaceTimeTagFixture; import com.daedan.festabook.timetag.domain.TimeTag; @@ -30,22 +31,14 @@ import java.util.List; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.HttpStatus; -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class PlaceGeographyControllerTest { +class PlaceGeographyControllerTest extends AcceptanceTestSupport { private static final String FESTIVAL_HEADER_NAME = "festival"; @@ -64,9 +57,6 @@ class PlaceGeographyControllerTest { @Autowired private PlaceTimeTagJpaRepository placeTimeTagJpaRepository; - @LocalServerPort - private int port; - @BeforeAll static void beforeAll() { RestAssured.config = RestAssured.config() @@ -79,11 +69,6 @@ static void afterAll() { RestAssured.config = RestAssuredConfig.config(); } - @BeforeEach - void setUp() { - RestAssured.port = port; - } - @Nested class getAllPlaceGeographyByFestivalId { diff --git a/src/test/java/com/daedan/festabook/place/controller/PlaceImageControllerTest.java b/src/test/java/com/daedan/festabook/place/controller/PlaceImageControllerTest.java index 403560e..a95a4d1 100644 --- a/src/test/java/com/daedan/festabook/place/controller/PlaceImageControllerTest.java +++ b/src/test/java/com/daedan/festabook/place/controller/PlaceImageControllerTest.java @@ -20,25 +20,18 @@ import com.daedan.festabook.place.dto.PlaceImageSequenceUpdateRequestFixture; import com.daedan.festabook.place.infrastructure.PlaceImageJpaRepository; import com.daedan.festabook.place.infrastructure.PlaceJpaRepository; +import com.daedan.festabook.support.AcceptanceTestSupport; import io.restassured.RestAssured; import io.restassured.http.ContentType; import io.restassured.http.Header; import java.util.List; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Nested; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.HttpStatus; -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class PlaceImageControllerTest { +class PlaceImageControllerTest extends AcceptanceTestSupport { @Autowired private FestivalJpaRepository festivalJpaRepository; @@ -52,14 +45,6 @@ class PlaceImageControllerTest { @Autowired private JwtTestHelper jwtTestHelper; - @LocalServerPort - private int port; - - @BeforeEach - void setUp() { - RestAssured.port = port; - } - @Nested class addPlaceImage { diff --git a/src/test/java/com/daedan/festabook/question/controller/QuestionControllerTest.java b/src/test/java/com/daedan/festabook/question/controller/QuestionControllerTest.java index 3c1a5ef..7c519d7 100644 --- a/src/test/java/com/daedan/festabook/question/controller/QuestionControllerTest.java +++ b/src/test/java/com/daedan/festabook/question/controller/QuestionControllerTest.java @@ -16,26 +16,19 @@ import com.daedan.festabook.question.dto.QuestionSequenceUpdateRequest; import com.daedan.festabook.question.dto.QuestionSequenceUpdateRequestFixture; import com.daedan.festabook.question.infrastructure.QuestionJpaRepository; +import com.daedan.festabook.support.AcceptanceTestSupport; import io.restassured.RestAssured; import io.restassured.http.ContentType; import io.restassured.http.Header; import java.util.List; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.HttpStatus; -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class QuestionControllerTest { +class QuestionControllerTest extends AcceptanceTestSupport { private static final String FESTIVAL_HEADER_NAME = "festival"; @@ -48,14 +41,6 @@ class QuestionControllerTest { @Autowired private JwtTestHelper jwtTestHelper; - @LocalServerPort - private int port; - - @BeforeEach - void setup() { - RestAssured.port = port; - } - @Nested class createQuestion { diff --git a/src/test/java/com/daedan/festabook/storage/controller/ImageStoreControllerTest.java b/src/test/java/com/daedan/festabook/storage/controller/ImageStoreControllerTest.java index d7da021..8d1fb82 100644 --- a/src/test/java/com/daedan/festabook/storage/controller/ImageStoreControllerTest.java +++ b/src/test/java/com/daedan/festabook/storage/controller/ImageStoreControllerTest.java @@ -8,24 +8,18 @@ import com.daedan.festabook.festival.infrastructure.FestivalJpaRepository; import com.daedan.festabook.global.security.JwtTestHelper; import com.daedan.festabook.global.security.role.RoleType; +import com.daedan.festabook.support.AcceptanceTestSupport; import io.restassured.RestAssured; import io.restassured.http.Header; import java.io.IOException; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Nested; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.HttpStatus; import org.springframework.mock.web.MockMultipartFile; -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class ImageStoreControllerTest { +class ImageStoreControllerTest extends AcceptanceTestSupport { @Autowired private FestivalJpaRepository festivalJpaRepository; @@ -33,14 +27,6 @@ class ImageStoreControllerTest { @Autowired private JwtTestHelper jwtTestHelper; - @LocalServerPort - private int port; - - @BeforeEach - void setUp() { - RestAssured.port = port; - } - @Nested class UploadImage { diff --git a/src/test/java/com/daedan/festabook/support/AcceptanceTestSupport.java b/src/test/java/com/daedan/festabook/support/AcceptanceTestSupport.java new file mode 100644 index 0000000..6a7fc8c --- /dev/null +++ b/src/test/java/com/daedan/festabook/support/AcceptanceTestSupport.java @@ -0,0 +1,38 @@ +package com.daedan.festabook.support; + +import com.daedan.festabook.festival.domain.FestivalNotificationManager; +import com.daedan.festabook.global.config.TestSecurityConfig; +import com.daedan.festabook.global.infrastructure.ShuffleManager; +import io.restassured.RestAssured; +import java.time.Clock; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; + +@Import(TestSecurityConfig.class) +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +public abstract class AcceptanceTestSupport { + + @MockitoBean + protected Clock clock; + + @MockitoBean + protected FestivalNotificationManager festivalNotificationManager; + + @MockitoBean + protected ShuffleManager shuffleManager; + + @LocalServerPort + private int port; + + @BeforeEach + void setUp() { + RestAssured.port = port; + } +} diff --git a/src/test/java/com/daedan/festabook/timetag/controller/TimeTagControllerTest.java b/src/test/java/com/daedan/festabook/timetag/controller/TimeTagControllerTest.java index 17bc229..2484f3f 100644 --- a/src/test/java/com/daedan/festabook/timetag/controller/TimeTagControllerTest.java +++ b/src/test/java/com/daedan/festabook/timetag/controller/TimeTagControllerTest.java @@ -10,6 +10,7 @@ import com.daedan.festabook.festival.infrastructure.FestivalJpaRepository; import com.daedan.festabook.global.security.JwtTestHelper; import com.daedan.festabook.global.security.role.RoleType; +import com.daedan.festabook.support.AcceptanceTestSupport; import com.daedan.festabook.timetag.domain.TimeTag; import com.daedan.festabook.timetag.domain.TimeTagFixture; import com.daedan.festabook.timetag.dto.TimeTagCreateRequest; @@ -20,22 +21,14 @@ import io.restassured.RestAssured; import io.restassured.http.ContentType; import io.restassured.http.Header; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.HttpStatus; -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class TimeTagControllerTest { +class TimeTagControllerTest extends AcceptanceTestSupport { private static final String FESTIVAL_HEADER_NAME = "festival"; @@ -48,14 +41,6 @@ class TimeTagControllerTest { @Autowired private JwtTestHelper jwtTestHelper; - @LocalServerPort - private int port; - - @BeforeEach - void setup() { - RestAssured.port = port; - } - @Nested class createTimeTag {