diff --git a/.github/workflows/continuous-intergration.yml b/.github/workflows/continuous-intergration.yml index c4766825..f11aac4f 100644 --- a/.github/workflows/continuous-intergration.yml +++ b/.github/workflows/continuous-intergration.yml @@ -3,8 +3,8 @@ name: continuous integration on: pull_request: branches: - - 'main' - - 'dev' + - "main" + - "dev" permissions: checks: write @@ -15,7 +15,12 @@ permissions: jobs: check-application: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 + + env: + DOCKER_HOST: "unix:///var/run/docker.sock" + DOCKER_API_VERSION: "1.44" + TESTCONTAINERS_CHECKS_DISABLE: "true" steps: - uses: actions/checkout@v4 @@ -24,22 +29,30 @@ jobs: uses: actions/setup-java@v4 with: java-version: 21 - distribution: 'corretto' + distribution: "corretto" - name: Grant execute permission for gradlew run: chmod +x gradlew + - name: Check Docker Environment + run: | + docker version + docker info + - name: Setup Gradle - uses: gradle/gradle-build-action@v3 + uses: gradle/actions/setup-gradle@v3 with: - arguments: build cache-read-only: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/dev' }} - name: Build with Gradle - run: ./gradlew build -x test + run: ./gradlew build -x test --no-daemon - name: Test with Gradle - run: ./gradlew test + run: > + ./gradlew test jacocoTestReport jacocoTestCoverageVerification + --no-daemon --stacktrace + -Ddocker.api.version=1.44 + -Dorg.testcontainers.docker.client.strategy=org.testcontainers.dockerclient.UnixSocketClientProviderStrategy - name: Report Test Results uses: EnricoMi/publish-unit-test-result-action@v2 @@ -52,7 +65,7 @@ jobs: uses: madrapps/jacoco-report@v1.7.2 if: always() with: - title: ๐Ÿ“Šํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€ + title: "๐Ÿ“Šํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€" paths: ${{ github.workspace }}/build/reports/jacoco/test/jacocoTestReport.xml token: ${{ secrets.GITHUB_TOKEN }} min-coverage-overall: 70 @@ -60,6 +73,7 @@ jobs: update-comment: true - name: Get the Coverage info + if: always() run: | echo "Total coverage ${{ steps.jacoco.outputs.coverage-overall }}" echo "Changed Files coverage ${{ steps.jacoco.outputs.coverage-changed-files }}" diff --git a/Dockerfile b/Dockerfile index 2a996fc4..e376cb40 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,20 +1,31 @@ - - FROM eclipse-temurin:21-jdk-alpine + WORKDIR /app +# ๋ณด์•ˆ ๊ณ„์ • ์ƒ์„ฑ RUN apk add --no-cache curl && \ addgroup -S spring && adduser -S spring -G spring +# JAR ํŒŒ์ผ ๋ณต์‚ฌ COPY build/libs/ject*.jar app.jar + +# ๊ถŒํ•œ ๋ณ€๊ฒฝ RUN chown spring:spring app.jar +# ๊ณ„์ • ์ „ํ™˜ USER spring:spring + EXPOSE 8080 -ENV JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:+UseStringDeduplication" +# ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ • +ENV JAVA_OPTS="-XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:+UseStringDeduplication" + +# ํ”„๋กœํŒŒ์ผ์„ ํ™˜๊ฒฝ๋ณ€์ˆ˜๋กœ ๋ถ„๋ฆฌ +ENV SPRING_PROFILES_ACTIVE=prod + +# ํ—ฌ์Šค์ฒดํฌ HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ CMD curl -f http://localhost:8080/health || exit 1 - -ENTRYPOINT ["sh", "-c", "exec java $JAVA_OPTS -Dspring.profiles.active=prod -Duser.timezone=Asia/Seoul -jar app.jar"] +# ์‹คํ–‰ ๋ช…๋ น์–ด +ENTRYPOINT ["sh", "-c", "exec java -XX:MaxRAMPercentage=75.0 $JAVA_OPTS -Dspring.profiles.active=$SPRING_PROFILES_ACTIVE -Duser.timezone=Asia/Seoul -jar app.jar"] \ No newline at end of file diff --git a/build.gradle b/build.gradle index 5bf6c6ef..8972301b 100644 --- a/build.gradle +++ b/build.gradle @@ -84,13 +84,10 @@ dependencies { testImplementation 'com.h2database:h2' // TestContainers - testImplementation 'org.testcontainers:junit-jupiter:1.20.4' - testImplementation 'org.testcontainers:mysql:1.20.4' - testImplementation 'org.testcontainers:rabbitmq:1.20.4' - testImplementation 'com.redis:testcontainers-redis:2.2.2' - implementation 'org.testcontainers:testcontainers-bom:1.20.2' - implementation 'org.testcontainers:testcontainers' - + testImplementation platform("org.testcontainers:testcontainers-bom:1.20.4") + testImplementation 'org.testcontainers:junit-jupiter' + testImplementation 'org.testcontainers:mysql' + testImplementation 'org.testcontainers:rabbitmq' //p6spy implementation "com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.9.2" @@ -157,6 +154,11 @@ test { // ํ…Œ์ŠคํŠธ ์ข…๋ฃŒ ํ›„ jacoco ์‹คํ–‰ finalizedBy(jacocoTestReport) + + // CI ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์ „๋‹ฌ + environment "DOCKER_HOST", System.getenv("DOCKER_HOST") + environment "TESTCONTAINERS_RYUK_DISABLED", "true" + environment "TESTCONTAINERS_CHECKS_DISABLE", "true" } jacocoTestReport { @@ -184,7 +186,7 @@ jacocoTestCoverageVerification { limit { counter = "LINE" // ๋ผ์ธ ์ปค๋ฒ„๋ฆฌ์ง€ value = "COVEREDRATIO" - minimum = 0.70 // ์ตœ์†Œ 70% ์ด์ƒ ๋งŒ์กฑ + minimum = 0.70 } excludes = jacocoExcludes diff --git a/src/main/java/org/ject/support/common/data/redis/RedisCacheConfig.java b/src/main/java/org/ject/support/common/data/redis/RedisCacheConfig.java new file mode 100644 index 00000000..03b1163c --- /dev/null +++ b/src/main/java/org/ject/support/common/data/redis/RedisCacheConfig.java @@ -0,0 +1,56 @@ +package org.ject.support.common.data.redis; + +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.RedisSerializationContext; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +import java.time.Duration; + +@Configuration +@EnableCaching +public class RedisCacheConfig { + + @Bean + public CacheManager redisCacheManager( + RedisConnectionFactory connectionFactory + ) { + return RedisCacheManager.builder(connectionFactory) + .cacheDefaults(redisCacheConfiguration()) + .build(); + } + + private RedisCacheConfiguration redisCacheConfiguration() { + StringRedisSerializer keySerializer = new StringRedisSerializer(); + + GenericJackson2JsonRedisSerializer valueSerializer = + new GenericJackson2JsonRedisSerializer() + .configure(objectMapper -> { + objectMapper.registerModule(new JavaTimeModule()); + objectMapper.configure( + SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, + false + ); + }); + + return RedisCacheConfiguration + .defaultCacheConfig() + .disableCachingNullValues() + .entryTtl(Duration.ofDays(1)) + .computePrefixWith(name -> "cache::" + name + "::") + .serializeKeysWith( + RedisSerializationContext.SerializationPair.fromSerializer(keySerializer) + ) + .serializeValuesWith( + RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer) + ); + } +} diff --git a/src/main/java/org/ject/support/common/data/redis/RedisConfig.java b/src/main/java/org/ject/support/common/data/redis/RedisConfig.java deleted file mode 100644 index d81e7704..00000000 --- a/src/main/java/org/ject/support/common/data/redis/RedisConfig.java +++ /dev/null @@ -1,95 +0,0 @@ -package org.ject.support.common.data.redis; - -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import java.time.Duration; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.cache.CacheManager; -import org.springframework.cache.annotation.EnableCaching; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.cache.RedisCacheConfiguration; -import org.springframework.data.redis.cache.RedisCacheManager; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.connection.RedisPassword; -import org.springframework.data.redis.connection.RedisStandaloneConfiguration; -import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration; -import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; -import org.springframework.data.redis.serializer.RedisSerializationContext; -import org.springframework.data.redis.serializer.StringRedisSerializer; - -@Configuration -@EnableCaching -public class RedisConfig { - - @Value("${spring.data.redis.host}") - private String redisHost; - - @Value("${spring.data.redis.port}") - private int redisPort; - - @Value("${spring.data.redis.password}") - private String redisPassword; - - @Value("${spring.data.redis.local}") - private boolean isLocal; - - @Bean - @ConditionalOnMissingBean - public RedisConnectionFactory redisConnectionFactory() { - RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration(redisHost, redisPort); - - if (!redisPassword.isEmpty()) { - redisConfig.setPassword(RedisPassword.of(redisPassword)); - } - - if (isLocal) { - return new LettuceConnectionFactory(redisConfig); - } - - LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder() - .useSsl() - .build(); - - return new LettuceConnectionFactory(redisConfig, clientConfig); - } - - @Bean - public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { - RedisTemplate template = new RedisTemplate<>(); - template.setConnectionFactory(connectionFactory); - - template.setKeySerializer(new StringRedisSerializer()); - template.setValueSerializer(new StringRedisSerializer()); - template.setHashKeySerializer(new StringRedisSerializer()); - template.setHashValueSerializer(new StringRedisSerializer()); - - template.afterPropertiesSet(); - return template; - } - - @Bean - public CacheManager redisCacheManager(RedisConnectionFactory connectionFactory) { - return RedisCacheManager.builder(connectionFactory) - .cacheDefaults(redisCacheConfiguration()) - .build(); - } - - private RedisCacheConfiguration redisCacheConfiguration() { - StringRedisSerializer redisKeySerializer = new StringRedisSerializer(); - GenericJackson2JsonRedisSerializer redisValueSerializer = new GenericJackson2JsonRedisSerializer() - .configure(objectMapper -> { - objectMapper.registerModule(new JavaTimeModule()); - objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); - }); - return RedisCacheConfiguration - .defaultCacheConfig() - .disableCachingNullValues() - .entryTtl(Duration.ofDays(1)) - .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisKeySerializer)) - .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisValueSerializer)); - } -} diff --git a/src/main/java/org/ject/support/common/data/redis/RedisConnectionConfig.java b/src/main/java/org/ject/support/common/data/redis/RedisConnectionConfig.java new file mode 100644 index 00000000..7b89c0d0 --- /dev/null +++ b/src/main/java/org/ject/support/common/data/redis/RedisConnectionConfig.java @@ -0,0 +1,49 @@ +package org.ject.support.common.data.redis; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.RedisPassword; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; + +@Configuration +public class RedisConnectionConfig { + + @Value("${spring.data.redis.host}") + private String redisHost; + + @Value("${spring.data.redis.port}") + private int redisPort; + + @Value("${spring.data.redis.password}") + private String redisPassword; + + @Value("${spring.data.redis.local}") + private boolean isLocal; + + @Bean + @ConditionalOnMissingBean + public RedisConnectionFactory redisConnectionFactory() { + RedisStandaloneConfiguration redisConfig = + new RedisStandaloneConfiguration(redisHost, redisPort); + + if (redisPassword != null && !redisPassword.isEmpty()) { + redisConfig.setPassword(RedisPassword.of(redisPassword)); + } + + if (isLocal) { + return new LettuceConnectionFactory(redisConfig); + } + + LettuceClientConfiguration clientConfig = + LettuceClientConfiguration.builder() + .useSsl() + .build(); + + return new LettuceConnectionFactory(redisConfig, clientConfig); + } +} diff --git a/src/main/java/org/ject/support/common/data/redis/RedisTemplateConfig.java b/src/main/java/org/ject/support/common/data/redis/RedisTemplateConfig.java new file mode 100644 index 00000000..6cf09342 --- /dev/null +++ b/src/main/java/org/ject/support/common/data/redis/RedisTemplateConfig.java @@ -0,0 +1,27 @@ +package org.ject.support.common.data.redis; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@Configuration +public class RedisTemplateConfig { + + @Bean + public RedisTemplate redisTemplate( + RedisConnectionFactory connectionFactory + ) { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(connectionFactory); + + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(new StringRedisSerializer()); + template.setHashKeySerializer(new StringRedisSerializer()); + template.setHashValueSerializer(new StringRedisSerializer()); + + template.afterPropertiesSet(); + return template; + } +} diff --git a/src/main/java/org/ject/support/domain/admin/service/MemberManagementService.java b/src/main/java/org/ject/support/domain/admin/service/MemberManagementService.java index 0e9b2535..3be3f16b 100644 --- a/src/main/java/org/ject/support/domain/admin/service/MemberManagementService.java +++ b/src/main/java/org/ject/support/domain/admin/service/MemberManagementService.java @@ -13,9 +13,11 @@ import org.ject.support.domain.member.entity.Member; import org.ject.support.domain.member.entity.MemberEditor; import org.ject.support.domain.member.entity.MemberEditor.MemberEditorBuilder; +import org.ject.support.domain.member.entity.TeamMember; import org.ject.support.domain.member.exception.MemberErrorCode; import org.ject.support.domain.member.exception.MemberException; import org.ject.support.domain.member.repository.MemberRepository; +import org.ject.support.domain.member.repository.TeamMemberRepository; import org.ject.support.domain.recruit.domain.Semester; import org.ject.support.domain.recruit.repository.SemesterRepository; import org.springframework.data.domain.Page; @@ -29,6 +31,7 @@ public class MemberManagementService { private final MemberRepository memberRepository; private final SemesterRepository semesterRepository; + private final TeamMemberRepository teamMemberRepository; @Transactional(readOnly = true) public Page findMembers( @@ -89,6 +92,21 @@ public void editMember(final Long memberId, .build(); member.edit(editor); + + // TeamMember.jobFamily ๋™๊ธฐํ™” (ํ”„๋กœ์ ํŠธ ์กฐํšŒ ์ผ๊ด€์„ฑ ์œ ์ง€) + if (request.jobFamily() != null) { + syncTeamMemberJobFamily(memberId, request.jobFamily()); + } + } + + /** + * ๋ฉค๋ฒ„์˜ ์ง๊ตฐ ๋ณ€๊ฒฝ ์‹œ ํ•ด๋‹น ๋ฉค๋ฒ„๊ฐ€ ์†ํ•œ ๋ชจ๋“  TeamMember์˜ jobFamily๋ฅผ ๋™๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค. + * ๋‹จ, ํ–ฅํ›„ ๊ธฐ์ˆ˜๋ณ„ ์ง๊ตฐ ๋ถ„๋ฆฌ๊ฐ€ ์™„๋ฃŒ๋˜๋ฉด ์ด ๋กœ์ง์€ ์ œ๊ฑฐ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + */ + @Deprecated + private void syncTeamMemberJobFamily(final Long memberId, final JobFamily jobFamily) { + List teamMembers = teamMemberRepository.findByMemberId(memberId); + teamMembers.forEach(teamMember -> teamMember.updateJobFamily(jobFamily)); } @Transactional diff --git a/src/main/java/org/ject/support/domain/apply/service/ApplyService.java b/src/main/java/org/ject/support/domain/apply/service/ApplyService.java index 9008aaac..42a0fc0c 100644 --- a/src/main/java/org/ject/support/domain/apply/service/ApplyService.java +++ b/src/main/java/org/ject/support/domain/apply/service/ApplyService.java @@ -27,8 +27,6 @@ import org.ject.support.domain.recruit.exception.RecruitErrorCode; import org.ject.support.domain.recruit.exception.RecruitException; import org.ject.support.domain.recruit.repository.RecruitRepository; -import org.ject.support.external.n8n.event.ApplicationSubmittedEvent; -import org.springframework.context.ApplicationEventPublisher; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -54,8 +52,6 @@ public class ApplyService implements ApplyUsecase { private final Map2JsonSerializer map2JsonSerializer; private final String2MapSerializer string2MapSerializer; - private final ApplicationEventPublisher applicationEventPublisher; - @Override @PeriodAccessible(permitAllJob = true) @Transactional(readOnly = true) @@ -170,10 +166,6 @@ public void submitApplication(Long memberId, // 5. Apply ์—”ํ‹ฐํ‹ฐ์— ์ œ์ถœ ์œ„์ž„ (๊ฒ€์ฆ ๋ฐ ์ƒํƒœ ๋ณ€๊ฒฝ ํฌํ•จ) apply.submit(applicationForm); - - // 6. n8n์— ์ง€์› ์™„๋ฃŒ ์ด๋ฒคํŠธ ๋ฐœํ–‰ - applicationEventPublisher - .publishEvent(new ApplicationSubmittedEvent(apply.getId())); } @Override diff --git a/src/main/java/org/ject/support/domain/member/entity/TeamMember.java b/src/main/java/org/ject/support/domain/member/entity/TeamMember.java index 14e20914..05a9503b 100644 --- a/src/main/java/org/ject/support/domain/member/entity/TeamMember.java +++ b/src/main/java/org/ject/support/domain/member/entity/TeamMember.java @@ -1,6 +1,9 @@ package org.ject.support.domain.member.entity; +import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; @@ -10,10 +13,13 @@ import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; +import lombok.Getter; import lombok.NoArgsConstructor; import org.ject.support.domain.base.BaseTimeEntity; +import org.ject.support.domain.member.JobFamily; @Entity +@Getter @Builder @AllArgsConstructor(access = AccessLevel.PRIVATE) @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -30,4 +36,12 @@ public class TeamMember extends BaseTimeEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "team_id", nullable = false) private Team team; + + @Enumerated(EnumType.STRING) + @Column(columnDefinition = "varchar(45)") + private JobFamily jobFamily; + + public void updateJobFamily(JobFamily jobFamily) { + this.jobFamily = jobFamily; + } } diff --git a/src/main/java/org/ject/support/domain/member/repository/MemberQueryRepositoryImpl.java b/src/main/java/org/ject/support/domain/member/repository/MemberQueryRepositoryImpl.java index dd0ddece..41511a3e 100644 --- a/src/main/java/org/ject/support/domain/member/repository/MemberQueryRepositoryImpl.java +++ b/src/main/java/org/ject/support/domain/member/repository/MemberQueryRepositoryImpl.java @@ -43,19 +43,23 @@ public TeamMemberNames findMemberNamesByTeamId(Long teamId) { member.isDeleted.eq(false)) .transform(GroupBy.groupBy(teamMember.team.id).as(new QTeamMemberNames( GroupBy.list(new CaseBuilder() - .when(member.jobFamily.eq(PM)) + .when(teamMember.jobFamily.eq(PM) + .or(teamMember.jobFamily.isNull().and(member.jobFamily.eq(PM)))) .then(member.name) .otherwise((String) null)), GroupBy.list(new CaseBuilder() - .when(member.jobFamily.eq(PD)) + .when(teamMember.jobFamily.eq(PD) + .or(teamMember.jobFamily.isNull().and(member.jobFamily.eq(PD)))) .then(member.name) .otherwise((String) null)), GroupBy.list(new CaseBuilder() - .when(member.jobFamily.eq(FE)) + .when(teamMember.jobFamily.eq(FE) + .or(teamMember.jobFamily.isNull().and(member.jobFamily.eq(FE)))) .then(member.name) .otherwise((String) null)), GroupBy.list(new CaseBuilder() - .when(member.jobFamily.eq(BE)) + .when(teamMember.jobFamily.eq(BE) + .or(teamMember.jobFamily.isNull().and(member.jobFamily.eq(BE)))) .then(member.name) .otherwise((String) null)) ))).get(teamId); diff --git a/src/main/java/org/ject/support/domain/member/repository/TeamMemberRepository.java b/src/main/java/org/ject/support/domain/member/repository/TeamMemberRepository.java index 5894c61c..84c19186 100644 --- a/src/main/java/org/ject/support/domain/member/repository/TeamMemberRepository.java +++ b/src/main/java/org/ject/support/domain/member/repository/TeamMemberRepository.java @@ -3,5 +3,8 @@ import org.ject.support.domain.member.entity.TeamMember; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; + public interface TeamMemberRepository extends JpaRepository { + List findByMemberId(Long memberId); } diff --git a/src/main/java/org/ject/support/domain/review/controller/ReviewApiSpec.java b/src/main/java/org/ject/support/domain/review/controller/ReviewApiSpec.java deleted file mode 100644 index f5cb7bcf..00000000 --- a/src/main/java/org/ject/support/domain/review/controller/ReviewApiSpec.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.ject.support.domain.review.controller; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.ject.support.domain.review.dto.ReviewResponse; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; - -@Tag(name = "Review", description = "๋ฆฌ๋ทฐ API") -public interface ReviewApiSpec { - - @Operation( - summary = "๋ฆฌ๋ทฐ ๋ชฉ๋ก ์กฐํšŒ", - description = "๋ฆฌ๋ทฐ ๋ชฉ๋ก์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.") - Page findReviews(Pageable pageable); -} diff --git a/src/main/java/org/ject/support/domain/review/controller/ReviewController.java b/src/main/java/org/ject/support/domain/review/controller/ReviewController.java deleted file mode 100644 index 3b27ba90..00000000 --- a/src/main/java/org/ject/support/domain/review/controller/ReviewController.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.ject.support.domain.review.controller; - -import lombok.RequiredArgsConstructor; -import org.ject.support.domain.review.dto.ReviewResponse; -import org.ject.support.domain.review.service.ReviewService; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/reviews") -@RequiredArgsConstructor -public class ReviewController implements ReviewApiSpec { - - private final ReviewService reviewService; - - @Override - @GetMapping - public Page findReviews(Pageable pageable) { - return reviewService.findReviews(pageable); - } -} diff --git a/src/main/java/org/ject/support/domain/review/dto/ReviewResponse.java b/src/main/java/org/ject/support/domain/review/dto/ReviewResponse.java deleted file mode 100644 index 957955fc..00000000 --- a/src/main/java/org/ject/support/domain/review/dto/ReviewResponse.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.ject.support.domain.review.dto; - -import com.querydsl.core.annotations.QueryProjection; - -public record ReviewResponse(Long id, String linkUrl, String title, String description, String summary) { - - @QueryProjection - public ReviewResponse { - } -} diff --git a/src/main/java/org/ject/support/domain/review/entity/Review.java b/src/main/java/org/ject/support/domain/review/entity/Review.java deleted file mode 100644 index 42ebb404..00000000 --- a/src/main/java/org/ject/support/domain/review/entity/Review.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.ject.support.domain.review.entity; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.NoArgsConstructor; -import org.ject.support.domain.base.BaseTimeEntity; - -@Entity -@Builder -@AllArgsConstructor(access = AccessLevel.PRIVATE) -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class Review extends BaseTimeEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(length = 2083) - private String linkUrl; - - @Column(nullable = false) - private String title; - - @Column(nullable = false) - private String description; - - @Column(nullable = false) - private String summary; -} diff --git a/src/main/java/org/ject/support/domain/review/repository/ReviewQueryRepository.java b/src/main/java/org/ject/support/domain/review/repository/ReviewQueryRepository.java deleted file mode 100644 index 1436e6fa..00000000 --- a/src/main/java/org/ject/support/domain/review/repository/ReviewQueryRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.ject.support.domain.review.repository; - -import org.ject.support.domain.review.dto.ReviewResponse; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; - -public interface ReviewQueryRepository { - - Page findReviews(Pageable pageable); -} diff --git a/src/main/java/org/ject/support/domain/review/repository/ReviewQueryRepositoryImpl.java b/src/main/java/org/ject/support/domain/review/repository/ReviewQueryRepositoryImpl.java deleted file mode 100644 index e13fd96a..00000000 --- a/src/main/java/org/ject/support/domain/review/repository/ReviewQueryRepositoryImpl.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.ject.support.domain.review.repository; - -import com.querydsl.jpa.impl.JPAQuery; -import com.querydsl.jpa.impl.JPAQueryFactory; -import lombok.RequiredArgsConstructor; -import org.ject.support.common.data.PageResponse; -import org.ject.support.domain.review.dto.QReviewResponse; -import org.ject.support.domain.review.dto.ReviewResponse; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Repository; - -import java.util.List; - -import static org.ject.support.domain.review.entity.QReview.review; - -@Repository -@RequiredArgsConstructor -public class ReviewQueryRepositoryImpl implements ReviewQueryRepository { - - private final JPAQueryFactory queryFactory; - - @Override - public Page findReviews(Pageable pageable) { - List content = queryFactory.select(new QReviewResponse( - review.id, - review.linkUrl, - review.title, - review.description, - review.summary)) - .from(review) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .orderBy(review.createdAt.desc()) - .fetch(); - - JPAQuery countQuery = queryFactory.select(review.count()) - .from(review); - - return PageResponse.from(content, pageable, countQuery.fetchFirst()); - } -} diff --git a/src/main/java/org/ject/support/domain/review/repository/ReviewRepository.java b/src/main/java/org/ject/support/domain/review/repository/ReviewRepository.java deleted file mode 100644 index 64862ee7..00000000 --- a/src/main/java/org/ject/support/domain/review/repository/ReviewRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.ject.support.domain.review.repository; - -import org.ject.support.domain.review.entity.Review; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface ReviewRepository extends JpaRepository, ReviewQueryRepository { -} diff --git a/src/main/java/org/ject/support/domain/review/service/ReviewService.java b/src/main/java/org/ject/support/domain/review/service/ReviewService.java deleted file mode 100644 index 040ad5d4..00000000 --- a/src/main/java/org/ject/support/domain/review/service/ReviewService.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.ject.support.domain.review.service; - -import lombok.RequiredArgsConstructor; -import org.ject.support.domain.review.dto.ReviewResponse; -import org.ject.support.domain.review.repository.ReviewRepository; -import org.springframework.cache.annotation.Cacheable; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@RequiredArgsConstructor -public class ReviewService { - - private final ReviewRepository reviewRepository; - - @Cacheable(value = "review", key = "#pageable.pageNumber + ':' + #pageable.pageSize") - @Transactional(readOnly = true) - public Page findReviews(Pageable pageable) { - return reviewRepository.findReviews(pageable); - } -} diff --git a/src/main/java/org/ject/support/domain/tempapply/domain/TemporaryApplication.java b/src/main/java/org/ject/support/domain/tempapply/domain/TemporaryApplication.java deleted file mode 100644 index b5d8ddeb..00000000 --- a/src/main/java/org/ject/support/domain/tempapply/domain/TemporaryApplication.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.ject.support.domain.tempapply.domain; - -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; -import lombok.Setter; -import org.ject.support.domain.apply.dto.ApplyPortfolioDto; -import org.ject.support.domain.member.JobFamily; -import org.ject.support.external.dynamodb.domain.CompositeKey; -import org.ject.support.external.dynamodb.domain.EntityWithPrimaryKey; -import org.ject.support.external.dynamodb.util.ApplyTemporaryPortfolioConverter; -import org.ject.support.external.dynamodb.util.LocalDateTimeConverter; -import org.ject.support.external.dynamodb.util.MapConverter; -import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; -import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; -import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbConvertedBy; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Map; - -@Deprecated -@DynamoDBTable(tableName = "temporary_application") -@DynamoDbBean -@NoArgsConstructor -@Setter -@EqualsAndHashCode(callSuper = false) -public class TemporaryApplication extends EntityWithPrimaryKey { - public static final String PK_PREFIX = "MEMBER"; - public static final String SK_PREFIX = "TIMESTAMP"; - - private String memberId; - private LocalDateTime timestamp; - private String jobFamily; - private Map answers; - private List portfolios; - - public TemporaryApplication(final String memberId, - final Map answers, - final String jobFamily, - final List portfolios) { - this.memberId = memberId; - this.timestamp = LocalDateTime.now(); - this.answers = answers; - this.jobFamily = jobFamily; - this.portfolios = portfolios; - this.pk = new CompositeKey(PK_PREFIX, this.memberId); - this.sk = new CompositeKey(SK_PREFIX, this.timestamp.toString()); - } - - @DynamoDbAttribute(value = "member_id") - public String getMemberId() { - return memberId; - } - - @DynamoDbAttribute(value = "timestamp") - @DynamoDbConvertedBy(value = LocalDateTimeConverter.class) - public LocalDateTime getTimestamp() { - return timestamp; - } - - @DynamoDbAttribute(value = "job_family") - public String getJobFamily() { - return jobFamily; - } - - @DynamoDbAttribute(value = "answers") - @DynamoDbConvertedBy(value = MapConverter.class) - public Map getAnswers() { - return answers; - } - - @DynamoDbAttribute(value = "portfolios") - @DynamoDbConvertedBy(value = ApplyTemporaryPortfolioConverter.class) - public List getPortfolios() { - return portfolios; - } - - public boolean isSameJobFamily(JobFamily jobFamily) { - return this.jobFamily.equals(jobFamily.name()); - } -} diff --git a/src/main/java/org/ject/support/domain/tempapply/exception/TemporaryApplicationErrorCode.java b/src/main/java/org/ject/support/domain/tempapply/exception/TemporaryApplicationErrorCode.java deleted file mode 100644 index 662d03a9..00000000 --- a/src/main/java/org/ject/support/domain/tempapply/exception/TemporaryApplicationErrorCode.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.ject.support.domain.tempapply.exception; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import org.ject.support.common.exception.ErrorCode; -import org.springframework.http.HttpStatus; - -import static org.springframework.http.HttpStatus.NOT_FOUND; - -@Deprecated -@Getter -@AllArgsConstructor -public enum TemporaryApplicationErrorCode implements ErrorCode { - NOT_FOUND_TEMP_APPLICATION_FORM(NOT_FOUND, "TEMP_APPLICATION_NOT_FOUND", "์ž„์‹œ ์ง€์›์„œ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค."); - - private final HttpStatus httpStatus; - private final String code; - private final String message; -} diff --git a/src/main/java/org/ject/support/domain/tempapply/exception/TemporaryApplicationException.java b/src/main/java/org/ject/support/domain/tempapply/exception/TemporaryApplicationException.java deleted file mode 100644 index a7ebb098..00000000 --- a/src/main/java/org/ject/support/domain/tempapply/exception/TemporaryApplicationException.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.ject.support.domain.tempapply.exception; - -import org.ject.support.common.exception.BusinessException; -import org.ject.support.common.exception.ErrorCode; - -@Deprecated -public class TemporaryApplicationException extends BusinessException { - public TemporaryApplicationException(final ErrorCode errorCode) { - super(errorCode); - } -} diff --git a/src/main/java/org/ject/support/domain/tempapply/repository/TemporaryApplicationRepository.java b/src/main/java/org/ject/support/domain/tempapply/repository/TemporaryApplicationRepository.java deleted file mode 100644 index 81584ca2..00000000 --- a/src/main/java/org/ject/support/domain/tempapply/repository/TemporaryApplicationRepository.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.ject.support.domain.tempapply.repository; - -import io.awspring.cloud.dynamodb.DynamoDbTemplate; -import org.ject.support.domain.tempapply.domain.TemporaryApplication; -import org.ject.support.external.dynamodb.domain.CompositeKey; -import org.ject.support.external.dynamodb.repository.AbstractDynamoDbRepository; -import org.springframework.stereotype.Repository; -import software.amazon.awssdk.enhanced.dynamodb.model.ScanEnhancedRequest; - -import java.time.LocalDateTime; -import java.util.Comparator; -import java.util.List; -import java.util.Optional; - -@Deprecated -@Repository -public class TemporaryApplicationRepository extends AbstractDynamoDbRepository { - public TemporaryApplicationRepository(final DynamoDbTemplate dynamoDbTemplate) { - super(dynamoDbTemplate); - } - - @Override - protected void setEntityClass() { - entityClass = TemporaryApplication.class; - } - - //TODO 2025 02 21 10:08:04 : ์ตœ์‹  ๋ฌธ์„œ์ž„์„ ๊ผญ application level์—์„œ ํ™•์ธํ•ด์•ผ ํ•˜๋Š”๊ฐ€? - public Optional findLatestByMemberId(String memberId) { - CompositeKey partitionKey = new CompositeKey(TemporaryApplication.PK_PREFIX, memberId); - return findByPartitionWithSortType(partitionKey, TemporaryApplication.SK_PREFIX) - .stream().max(Comparator.comparing(TemporaryApplication::getTimestamp)); - } - - public List findMemberIdsByJobFamilyAndAfter(String jobFamily, LocalDateTime recruitStartDateTime) { - ScanEnhancedRequest scanEnhancedRequest = ScanEnhancedRequest.builder().build(); - return dynamoDbTemplate.scan(scanEnhancedRequest, entityClass) - .items().stream() - .filter(item -> item.getTimestamp().isAfter(recruitStartDateTime)) - .filter(item -> item.getJobFamily().equals(jobFamily)) - .map(TemporaryApplication::getMemberId) - .distinct() - .toList(); - } - - public void deleteByMemberId(String memberId) { - CompositeKey partitionKey = new CompositeKey(TemporaryApplication.PK_PREFIX, memberId); - deleteByPartitionKey(partitionKey); - } -} diff --git a/src/main/java/org/ject/support/domain/tempapply/service/TemporaryApplyService.java b/src/main/java/org/ject/support/domain/tempapply/service/TemporaryApplyService.java deleted file mode 100644 index 304a07e1..00000000 --- a/src/main/java/org/ject/support/domain/tempapply/service/TemporaryApplyService.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.ject.support.domain.tempapply.service; - -import org.ject.support.domain.apply.dto.ApplyPortfolioDto; -import org.ject.support.domain.apply.dto.TempApplicationFormResponse; -import org.ject.support.domain.member.JobFamily; -import org.ject.support.domain.recruit.domain.Recruit; - -import java.util.List; -import java.util.Map; - -@Deprecated -public interface TemporaryApplyService { - /** - * ์‚ฌ์šฉ์ž์˜ ์ž„์‹œ ์ง€์›์„œ๋ฅผ ์กฐํšŒ
- */ - TempApplicationFormResponse findMembersRecentTemporaryApplication(Long memberId); - - /** - * ์‚ฌ์šฉ์ž์˜ ์ž„์‹œ ์ง€์›์„œ๋ฅผ ์ €์žฅ
์ž„์‹œ์ง€์›์„œ์˜ ์–‘์‹์ด ์ง€์› ํŒŒํŠธ(์ง๊ตฐ)์— ์ ์ ˆํ•œ์ง€ ํŒ๋ณ„ ํ›„ ์ €์žฅ
์ž„์‹œ ์ง€์›์„œ๋Š” ๋ฎ์–ด์จ์ง€๋Š” ํ˜•ํƒœ๊ฐ€ ์•„๋‹Œ ์ƒˆ๋กœ์šด ์ž„์‹œ์ €์žฅ๋ณธ์ด ์ถ”๊ฐ€๋กœ ์ €์žฅ๋˜๋Š” ํ˜•ํƒœ
- */ - void saveTemporaryApplication(Long memberId, - Map answers, - JobFamily jobFamily, - List portfolios); - - /** - * ์‚ฌ์šฉ์ž์˜ ์ž„์‹œ ์ง€์›์„œ๋ฅผ ๋ชจ๋‘ ์ œ๊ฑฐ - */ - void deleteTemporaryApplicationsByMemberId(Long memberId); - - /** - * ํ™œ์„ฑํ™”๋œ ๋ชจ์ง‘ ๊ธฐ๊ฐ„ ์ค‘ ์ €์žฅ๋œ ์ž„์‹œ ์ง€์›์„œ์˜ ์‚ฌ์šฉ์ž ID ์ค‘๋ณต ์—†์ด ์กฐํšŒ - */ - List findMemberIdsByRecruit(Recruit recruit); -} diff --git a/src/main/java/org/ject/support/domain/tempapply/service/TemporaryApplyServiceImpl.java b/src/main/java/org/ject/support/domain/tempapply/service/TemporaryApplyServiceImpl.java deleted file mode 100644 index d0a89f71..00000000 --- a/src/main/java/org/ject/support/domain/tempapply/service/TemporaryApplyServiceImpl.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.ject.support.domain.tempapply.service; - -import lombok.RequiredArgsConstructor; -import org.ject.support.domain.apply.dto.ApplyPortfolioDto; -import org.ject.support.domain.apply.dto.TempApplicationFormResponse; -import org.ject.support.domain.member.JobFamily; -import org.ject.support.domain.recruit.domain.Recruit; -import org.ject.support.domain.tempapply.domain.TemporaryApplication; -import org.ject.support.domain.tempapply.repository.TemporaryApplicationRepository; -import org.springframework.stereotype.Service; - -import java.util.List; -import java.util.Map; - -@Deprecated -@Service -@RequiredArgsConstructor -public class TemporaryApplyServiceImpl implements TemporaryApplyService { - private final TemporaryApplicationRepository temporaryApplicationRepository; - - @Override - public TempApplicationFormResponse findMembersRecentTemporaryApplication(final Long memberId) { -// TemporaryApplication latestApplication = -// temporaryApplicationRepository.findLatestByMemberId(memberId.toString()) -// .orElseThrow(() -> new TemporaryApplicationException(NOT_FOUND_TEMP_APPLICATION_FORM)); -// -// return ApplyTemporaryResponse.from(latestApplication); - return null; - } - - @Override - public void saveTemporaryApplication(final Long memberId, - final Map answers, - final JobFamily jobFamily, - final List portfolios) { - TemporaryApplication temporaryApplication = - new TemporaryApplication(memberId.toString(), answers, jobFamily.name(), portfolios); - temporaryApplicationRepository.save(temporaryApplication); - } - - @Override - public void deleteTemporaryApplicationsByMemberId(final Long memberId) { - temporaryApplicationRepository.deleteByMemberId(memberId.toString()); - } - - @Override - public List findMemberIdsByRecruit(Recruit recruit) { - return temporaryApplicationRepository - .findMemberIdsByJobFamilyAndAfter(recruit.getJobFamily().name(), recruit.getStartDate()) - .stream() - .distinct() - .map(Long::parseLong) - .toList(); - } -} diff --git a/src/main/java/org/ject/support/external/email/service/EmailAuthService.java b/src/main/java/org/ject/support/external/email/service/EmailAuthService.java index 63df0e99..09b5249f 100644 --- a/src/main/java/org/ject/support/external/email/service/EmailAuthService.java +++ b/src/main/java/org/ject/support/external/email/service/EmailAuthService.java @@ -1,17 +1,15 @@ package org.ject.support.external.email.service; +import java.time.Duration; +import java.util.Map; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.ject.support.common.util.CodeGeneratorUtil; import org.ject.support.external.email.domain.EmailTemplate; -import org.ject.support.external.email.exception.EmailErrorCode; import org.ject.support.external.email.exception.RateLimitException; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; -import java.time.Duration; -import java.util.Map; - @Slf4j @Service @RequiredArgsConstructor @@ -34,7 +32,7 @@ public void sendAuthCode(EmailTemplate sendGroupCode, String toEmail) { } private void checkRateLimit(EmailTemplate sendGroupCode, String toEmail) { - if (sendGroupCode != EmailTemplate.AUTH_CODE) { + if (sendGroupCode == EmailTemplate.REMIND_APPLY) { return; } diff --git a/src/main/java/org/ject/support/external/n8n/client/N8nClient.java b/src/main/java/org/ject/support/external/n8n/client/N8nClient.java deleted file mode 100644 index 321c3f7c..00000000 --- a/src/main/java/org/ject/support/external/n8n/client/N8nClient.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.ject.support.external.n8n.client; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.ject.support.domain.admin.dto.SubmittedApplyDetailResponse; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Component; -import org.springframework.web.reactive.function.client.WebClient; - -@Slf4j -@Component -@RequiredArgsConstructor -public class N8nClient { - - private final WebClient webClient; - - @Value("${n8n.secret-key}") - private String secretKey; - - @Value("${n8n.webhook.application-submit}") - private String applicationSubmitWebhook; - - public void send(SubmittedApplyDetailResponse payload) { - webClient.post() - .uri(applicationSubmitWebhook) - .header("Apply-Webhook-Secret", secretKey) - .bodyValue(payload) - .retrieve() - .toBodilessEntity() - .doOnSuccess(this::logSuccess) - .doOnError(this::logError) - .block(); - } - - private void logSuccess(ResponseEntity response) { - log.info("N8n message sent successfully (status={})", response.getStatusCode()); - } - - private void logError(Throwable e) { - log.error( - "N8n message send failed apply: {} - {}", - e.getClass().getSimpleName(), - e.getMessage() - ); - } -} diff --git a/src/main/java/org/ject/support/external/n8n/event/ApplicationSubmittedEvent.java b/src/main/java/org/ject/support/external/n8n/event/ApplicationSubmittedEvent.java deleted file mode 100644 index 49e19385..00000000 --- a/src/main/java/org/ject/support/external/n8n/event/ApplicationSubmittedEvent.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.ject.support.external.n8n.event; - -public record ApplicationSubmittedEvent( - Long applyId -) {} diff --git a/src/main/java/org/ject/support/external/n8n/listener/N8nApplicationSubmittedListener.java b/src/main/java/org/ject/support/external/n8n/listener/N8nApplicationSubmittedListener.java deleted file mode 100644 index 0752f6e4..00000000 --- a/src/main/java/org/ject/support/external/n8n/listener/N8nApplicationSubmittedListener.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.ject.support.external.n8n.listener; - -import lombok.RequiredArgsConstructor; -import org.ject.support.external.n8n.event.ApplicationSubmittedEvent; -import org.ject.support.external.n8n.service.N8nApplyService; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Component; -import org.springframework.transaction.event.TransactionPhase; -import org.springframework.transaction.event.TransactionalEventListener; - -/** - * Admin Page๊ฐ€ ์ƒ๊ธฐ๊ธฐ ์ „ ๊นŒ์ง€ n8n์œผ๋กœ ์ง€์›์„œ ์ œ์ถœ ์•Œ๋ฆผ์„ ๋ณด๋ƒ„ - * - Admin Page๊ฐ€ ์ƒ๊ธฐ๋ฉด ํ•ด๋‹น ๊ธฐ๋Šฅ์€ ์ œ๊ฑฐ๋  ์˜ˆ์ • - */ -@Component -@RequiredArgsConstructor -public class N8nApplicationSubmittedListener { - - private final N8nApplyService n8nApplyService; - - @Async - @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) - public void handle(ApplicationSubmittedEvent event) { - n8nApplyService.sendToN8n(event.applyId()); - } -} diff --git a/src/main/java/org/ject/support/external/n8n/service/N8nApplyService.java b/src/main/java/org/ject/support/external/n8n/service/N8nApplyService.java deleted file mode 100644 index 3d6a2e86..00000000 --- a/src/main/java/org/ject/support/external/n8n/service/N8nApplyService.java +++ /dev/null @@ -1,66 +0,0 @@ -package org.ject.support.external.n8n.service; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.ject.support.common.util.String2MapSerializer; -import org.ject.support.domain.admin.dto.SubmittedApplyDetailResponse; -import org.ject.support.domain.apply.domain.ApplicationForm; -import org.ject.support.domain.apply.domain.Apply; -import org.ject.support.domain.apply.dto.ApplyPortfolioDto; -import org.ject.support.domain.apply.exception.ApplyErrorCode; -import org.ject.support.domain.apply.exception.ApplyException; -import org.ject.support.domain.apply.repository.ApplyRepository; -import org.ject.support.external.n8n.client.N8nClient; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; -import java.util.Map; -import java.util.Optional; - -@Slf4j -@Service -@RequiredArgsConstructor -public class N8nApplyService { - - private final ApplyRepository applyRepository; - private final String2MapSerializer string2MapSerializer; - private final N8nClient n8nClient; - - @Transactional(readOnly = true) - public void sendToN8n(final Long applyId) { - // SubmittedApplyDetailResponse n8n ์ „์†ก์šฉ์œผ๋กœ ์ž„์‹œ ์žฌ์‚ฌ์šฉ - SubmittedApplyDetailResponse response = applyRepository.findByIdAndStatusWithMember(applyId, Apply.Status.SUBMITTED) - .map(this::toSubmittedApplyDetailResponse) - .orElseThrow(() -> new ApplyException(ApplyErrorCode.NOT_FOUND_APPLY)); - try { - n8nClient.send(response); - } catch (Exception e) { - log.error("Failed to send apply {} to n8n", applyId, e); - } - - } - - private SubmittedApplyDetailResponse toSubmittedApplyDetailResponse(final Apply apply) { - ApplicationForm submittedApplicationForm = apply.getApplicationForm(); - Map content = extractContent(submittedApplicationForm); - List portfolios = extractPortfolios(submittedApplicationForm); - return SubmittedApplyDetailResponse.from(apply, content, portfolios); - } - - private Map extractContent(final ApplicationForm applicationForm) { - return Optional.ofNullable(applicationForm) - .map(ApplicationForm::getContent) - .map(string2MapSerializer::serializeAsMap) - .orElse(Map.of()); - } - - private List extractPortfolios(final ApplicationForm applicationForm) { - return Optional.ofNullable(applicationForm) - .map(ApplicationForm::getPortfolios) - .orElse(List.of()) - .stream() - .map(ApplyPortfolioDto::from) - .toList(); - } -} diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index d0376df3..a78911b1 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -69,8 +69,3 @@ notification: webhook: admin-login: dummy-url-for-test supporter-token-issue: dummy-url-for-test - -n8n: - secret-key: n8n-test-secret-key - webhook: - application-submit: http://localhost:5678/webhook/application-submit diff --git a/src/main/resources/db/migration/V17__drop_review_table.sql b/src/main/resources/db/migration/V17__drop_review_table.sql new file mode 100644 index 00000000..f9115c89 --- /dev/null +++ b/src/main/resources/db/migration/V17__drop_review_table.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS review; diff --git a/src/main/resources/db/migration/V18__add_job_family_to_team_member.sql b/src/main/resources/db/migration/V18__add_job_family_to_team_member.sql new file mode 100644 index 00000000..01201cf7 --- /dev/null +++ b/src/main/resources/db/migration/V18__add_job_family_to_team_member.sql @@ -0,0 +1,3 @@ +-- team_member ํ…Œ์ด๋ธ”์— job_family ์ปฌ๋Ÿผ ์ถ”๊ฐ€ +ALTER TABLE team_member ADD COLUMN job_family VARCHAR(45) NULL; + diff --git a/src/main/resources/db/migration/V19__populate_team_member_job_family.sql b/src/main/resources/db/migration/V19__populate_team_member_job_family.sql new file mode 100644 index 00000000..13bbaf23 --- /dev/null +++ b/src/main/resources/db/migration/V19__populate_team_member_job_family.sql @@ -0,0 +1,5 @@ +-- ๊ธฐ์กด member.job_family ๋ฐ์ดํ„ฐ๋ฅผ team_member.job_family๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ +UPDATE team_member tm +JOIN member m ON tm.member_id = m.id +SET tm.job_family = m.job_family +WHERE tm.job_family IS NULL AND m.job_family IS NOT NULL; diff --git a/src/test/java/org/ject/support/domain/admin/service/MemberManagementServiceTest.java b/src/test/java/org/ject/support/domain/admin/service/MemberManagementServiceTest.java index a538cbb2..3cab9c6f 100644 --- a/src/test/java/org/ject/support/domain/admin/service/MemberManagementServiceTest.java +++ b/src/test/java/org/ject/support/domain/admin/service/MemberManagementServiceTest.java @@ -17,9 +17,11 @@ import org.ject.support.domain.member.Region; import org.ject.support.domain.member.Role; import org.ject.support.domain.member.entity.Member; +import org.ject.support.domain.member.entity.TeamMember; import org.ject.support.domain.member.exception.MemberErrorCode; import org.ject.support.domain.member.exception.MemberException; import org.ject.support.domain.member.repository.MemberRepository; +import org.ject.support.domain.member.repository.TeamMemberRepository; import org.ject.support.domain.recruit.domain.Semester; import org.ject.support.domain.recruit.repository.SemesterRepository; import org.junit.jupiter.api.Test; @@ -39,6 +41,9 @@ class MemberManagementServiceTest extends UnitTestSupport { @Mock private SemesterRepository semesterRepository; + @Mock + private TeamMemberRepository teamMemberRepository; + private final String TEST_NAME = "ํ™๊ธธ๋™"; private final String TEST_EMAIL = "test@example.com"; private final String TEST_PHONE_NUMBER = "01012345678"; @@ -280,12 +285,14 @@ class MemberManagementServiceTest extends UnitTestSupport { given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); given(semesterRepository.findByName("1๊ธฐ")).willReturn(Optional.of(semester)); + given(teamMemberRepository.findByMemberId(memberId)).willReturn(List.of()); // when memberManagementService.editMember(memberId, request); // then verify(memberRepository).findById(memberId); + verify(teamMemberRepository).findByMemberId(memberId); assertThat(member.getName()).isEqualTo(request.name()); assertThat(member.getPhoneNumber()).isEqualTo(request.phoneNumber()); assertThat(member.getEmail()).isEqualTo(request.email()); @@ -293,6 +300,60 @@ class MemberManagementServiceTest extends UnitTestSupport { assertThat(member.getSemesterId()).isEqualTo(semester.getId()); } + @Test + void ํšŒ์›_์ •๋ณด_์ˆ˜์ •_์‹œ_TeamMember_jobFamily_๋™๊ธฐํ™”() { + // given + var memberId = 1L; + var request = MemberEditRequest.builder() + .role(Role.SEMESTER) + .name("์ˆ˜์ •๋œ์ด๋ฆ„") + .phoneNumber("01087654321") + .email("updated@test.com") + .jobFamily(JobFamily.PM) // BE -> PM์œผ๋กœ ๋ณ€๊ฒฝ + .semesterName("1๊ธฐ") + .build(); + + var member = Member.builder() + .id(memberId) + .name(TEST_NAME) + .phoneNumber(TEST_PHONE_NUMBER) + .email(TEST_EMAIL) + .jobFamily(JobFamily.BE) + .role(Role.SEMESTER) + .semesterId(1L) + .build(); + + var semester = Semester.builder() + .id(1L) + .name("1๊ธฐ") + .build(); + + // ํ•ด๋‹น ๋ฉค๋ฒ„๊ฐ€ ์†ํ•œ TeamMember ๋ชฉ๋ก + var teamMember1 = TeamMember.builder() + .id(1L) + .member(member) + .jobFamily(JobFamily.BE) + .build(); + var teamMember2 = TeamMember.builder() + .id(2L) + .member(member) + .jobFamily(JobFamily.BE) + .build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(semesterRepository.findByName("1๊ธฐ")).willReturn(Optional.of(semester)); + given(teamMemberRepository.findByMemberId(memberId)).willReturn(List.of(teamMember1, teamMember2)); + + // when + memberManagementService.editMember(memberId, request); + + // then + verify(teamMemberRepository).findByMemberId(memberId); + // TeamMember์˜ jobFamily๋„ PM์œผ๋กœ ๋ณ€๊ฒฝ๋˜์–ด์•ผ ํ•จ + assertThat(teamMember1.getJobFamily()).isEqualTo(JobFamily.PM); + assertThat(teamMember2.getJobFamily()).isEqualTo(JobFamily.PM); + } + @Test void ํšŒ์›_์ •๋ณด_์ˆ˜์ •_์‹คํŒจ_์กด์žฌํ•˜์ง€_์•Š๋Š”_ํšŒ์›() { // given diff --git a/src/test/java/org/ject/support/domain/apply/service/ApplyServiceTest.java b/src/test/java/org/ject/support/domain/apply/service/ApplyServiceTest.java index dac07094..5478b6d1 100644 --- a/src/test/java/org/ject/support/domain/apply/service/ApplyServiceTest.java +++ b/src/test/java/org/ject/support/domain/apply/service/ApplyServiceTest.java @@ -27,7 +27,6 @@ import org.ject.support.domain.recruit.domain.Semester; import org.ject.support.domain.recruit.exception.QuestionException; import org.ject.support.domain.recruit.repository.RecruitRepository; -import org.ject.support.external.n8n.event.ApplicationSubmittedEvent; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; @@ -119,10 +118,6 @@ class ApplyServiceTest extends UnitTestSupport { // 3. apply์˜ ์ƒํƒœ๊ฐ€ SUBMITTED๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ๋Š”์ง€ ํ™•์ธ assertThat(apply.getStatus()).isEqualTo(SUBMITTED); - - // 4. AdminLoginNotificationEvent ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœํ–‰๋˜์—ˆ๋Š”์ง€ ํ™•์ธ - verify(applicationEventPublisher) - .publishEvent(any(ApplicationSubmittedEvent.class)); } @Test @@ -174,8 +169,6 @@ class ApplyServiceTest extends UnitTestSupport { // then assertThat(apply.getStatus()).isEqualTo(SUBMITTED); assertThat(applicationForm.getPortfolios()).hasSize(1); - verify(applicationEventPublisher) - .publishEvent(any(ApplicationSubmittedEvent.class)); } @Test diff --git a/src/test/java/org/ject/support/domain/member/repository/MemberQueryRepositoryTest.java b/src/test/java/org/ject/support/domain/member/repository/MemberQueryRepositoryTest.java index 308e0daf..25315258 100644 --- a/src/test/java/org/ject/support/domain/member/repository/MemberQueryRepositoryTest.java +++ b/src/test/java/org/ject/support/domain/member/repository/MemberQueryRepositoryTest.java @@ -76,10 +76,11 @@ void setUp() { be2 = createMember("์™•์ ํŠธ", "01011112226", "be2Email", BE); memberRepository.saveAll(List.of(pd1, fe1, be1, be2)); - teamApd1 = createTeamMember(teamA, pd1); - teamAfe1 = createTeamMember(teamA, fe1); - teamAbe1 = createTeamMember(teamA, be1); - teamAbe2 = createTeamMember(teamA, be2); + // TeamMember ์ƒ์„ฑ ์‹œ ํ•ด๋‹น ํŒ€์—์„œ์˜ jobFamily ์„ค์ • + teamApd1 = createTeamMember(teamA, pd1, PD); + teamAfe1 = createTeamMember(teamA, fe1, FE); + teamAbe1 = createTeamMember(teamA, be1, BE); + teamAbe2 = createTeamMember(teamA, be2, BE); teamMemberRepository.saveAll(List.of(teamApd1, teamAfe1, teamAbe1, teamAbe2)); } @@ -95,6 +96,60 @@ void setUp() { assertThat(teamMemberNames.backendDevelopers()).hasSize(2); } + @Test + void TeamMember_jobFamily_๊ธฐ๋ฐ˜_์ง๊ตฐ๋ณ„_ํŒ€์›_์ด๋ฆ„_์กฐํšŒ() { + // given + // 1๊ธฐ์—๋Š” BE๋กœ, 2๊ธฐ์—๋Š” PM์œผ๋กœ ํ™œ๋™ + // 1๊ธฐ ํŒ€ (teamA๋Š” setUp์—์„œ ์ƒ์„ฑ๋จ, semesterId=1) + // 2๊ธฐ ํŒ€ ์ƒ์„ฑ + Team otherTeam = teamRepository.save(Team.builder().name("otherTeam").semesterId(2L).build()); + + // Member.jobFamily๋Š” ์ ์ง„์  ์ ์šฉ์œผ๋กœ ์œ ์ง€๋˜๋Š” ๊ฐ’ + Member member = memberRepository.save(createMember("๊น€์ ํŠธ", "01099998888", "ject@test.com", BE)); + + // 1๊ธฐ ํŒ€A์—์„œ๋Š” BE๋กœ ์ฐธ์—ฌ + teamMemberRepository.save(createTeamMember(teamA, member, BE)); + + // 2๊ธฐ ํŒ€์—์„œ๋Š” PM์œผ๋กœ ์ฐธ์—ฌ + teamMemberRepository.save(createTeamMember(otherTeam, member, PM)); + + // when + // 2๊ธฐ ํŒ€ ์กฐํšŒ + TeamMemberNames team2ndMemberNames = memberRepository.findMemberNamesByTeamId(otherTeam.getId()); + + // then + // 2๊ธฐ ํŒ€์—์„œ๋Š” PM์œผ๋กœ ์กฐํšŒ + assertThat(team2ndMemberNames.productManagers()).hasSize(1); + assertThat(team2ndMemberNames.productManagers()).contains("๊น€์ ํŠธ"); + assertThat(team2ndMemberNames.backendDevelopers()).isEmpty(); + + // when + // 1๊ธฐ ํŒ€ ์กฐํšŒ + TeamMemberNames teamAMemberNames = memberRepository.findMemberNamesByTeamId(teamA.getId()); + + // then + // 1๊ธฐ ํŒ€์—์„œ๋Š” BE๋กœ ์กฐํšŒ๋˜์–ด์•ผ ํ•จ + assertThat(teamAMemberNames.backendDevelopers()).contains("๊น€์ ํŠธ"); + } + + @Test + void TeamMember_jobFamily๊ฐ€_null์ด๋ฉด_Member_jobFamily๋กœ_fallback() { + // given - TeamMember.jobFamily๊ฐ€ null์ธ ๊ฒฝ์šฐ (๊ธฐ์กด ๋ฐ์ดํ„ฐ ํ˜ธํ™˜์„ฑ) + Team teamC = teamRepository.save(createTeam("teamC")); + + Member fallbackMember = memberRepository.save(createMember("ํด๋ฐฑ", "01088887777", "fallback@test.com", FE)); + + // TeamMember์— jobFamily๋ฅผ ์„ค์ •ํ•˜์ง€ ์•Š์Œ (null) + TeamMember teamCMember = teamMemberRepository.save(createTeamMember(teamC, fallbackMember)); + + // when + TeamMemberNames teamMemberNames = memberRepository.findMemberNamesByTeamId(teamC.getId()); + + // then - Member.jobFamily ๊ธฐ์ค€์œผ๋กœ FE์— ์†ํ•ด์•ผ ํ•จ + assertThat(teamMemberNames.frontendDevelopers()).hasSize(1); + assertThat(teamMemberNames.frontendDevelopers()).contains("ํด๋ฐฑ"); + } + @Test void ์ „๋‹ฌ_๋ฐ›์€_ID_์ค‘_์ง€์›์„œ๋ฅผ_์ œ์ถœํ•˜์ง€_์•Š์€_์‚ฌ์šฉ์ž์˜_์ด๋ฉ”์ผ_๋ชฉ๋ก_์กฐํšŒ() { // given @@ -343,6 +398,14 @@ private TeamMember createTeamMember(Team team, Member member) { .build(); } + private TeamMember createTeamMember(Team team, Member member, JobFamily jobFamily) { + return TeamMember.builder() + .team(team) + .member(member) + .jobFamily(jobFamily) + .build(); + } + private Recruit createRecruit(Semester semester, JobFamily jobFamily) { return Recruit.builder() .semester(semester) diff --git a/src/test/java/org/ject/support/domain/recruit/controller/ApplyControllerTest.java b/src/test/java/org/ject/support/domain/recruit/controller/ApplyControllerTest.java deleted file mode 100644 index 4da6cf04..00000000 --- a/src/test/java/org/ject/support/domain/recruit/controller/ApplyControllerTest.java +++ /dev/null @@ -1,319 +0,0 @@ -//package org.ject.support.domain.recruit.controller; -// -//import org.ject.support.domain.apply.domain.Apply; -//import org.ject.support.domain.apply.dto.ApplyPortfolioDto; -//import org.ject.support.domain.apply.repository.ApplicationFormRepository; -//import org.ject.support.domain.apply.repository.ApplyRepository; -//import org.ject.support.domain.member.JobFamily; -//import org.ject.support.domain.member.MemberStatus; -//import org.ject.support.domain.member.Role; -//import org.ject.support.domain.member.entity.Member; -//import org.ject.support.domain.member.repository.MemberRepository; -//import org.ject.support.domain.recruit.domain.Question; -//import org.ject.support.domain.recruit.domain.Recruit; -//import org.ject.support.domain.recruit.domain.Semester; -//import org.ject.support.domain.recruit.repository.RecruitRepository; -//import org.ject.support.domain.recruit.repository.SemesterRepository; -//import org.ject.support.domain.tempapply.domain.TemporaryApplication; -//import org.ject.support.domain.tempapply.repository.TemporaryApplicationRepository; -//import org.ject.support.external.dynamodb.domain.CompositeKey; -//import org.ject.support.testconfig.ApplicationPeriodTest; -//import org.ject.support.testconfig.AuthenticatedUser; -//import org.ject.support.testconfig.IntegrationTest; -//import org.junit.jupiter.api.AfterEach; -//import org.junit.jupiter.api.BeforeEach; -//import org.junit.jupiter.api.DisplayName; -//import org.junit.jupiter.api.Test; -//import org.springframework.beans.factory.annotation.Autowired; -//import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -//import org.springframework.test.context.TestPropertySource; -//import org.springframework.test.web.servlet.MockMvc; -//import org.springframework.test.web.servlet.ResultActions; -//import org.springframework.transaction.annotation.Transactional; -// -//import java.time.LocalDateTime; -//import java.util.List; -//import java.util.Map; -// -//import static org.assertj.core.api.Assertions.assertThat; -//import static org.hamcrest.Matchers.containsString; -//import static org.ject.support.domain.recruit.domain.Question.InputType.TEXT; -//import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; -//import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -//import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -//import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -//import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -//import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -// -//// Redis ๊ด€๋ จ ์„ค์ •์„ ์ œ์™ธํ•˜๊ณ  ํ•„์š”ํ•œ ์„ค์ •๋งŒ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค -//@IntegrationTest -//@AutoConfigureMockMvc -//@Transactional -//@TestPropertySource(properties = {"spring.data.redis.repositories.enabled=false"}) -//class ApplyControllerTest extends ApplicationPeriodTest { -// @Autowired -// MockMvc mockMvc; -// -// @Autowired -// RecruitRepository recruitRepository; -// -// @Autowired -// MemberRepository memberRepository; -// -// @Autowired -// TemporaryApplicationRepository temporaryApplicationRepository; -// -// @Autowired -// ApplicationFormRepository applicationFormRepository; -// -// @Autowired -// SemesterRepository semesterRepository; -// -// @Autowired -// ApplyRepository applyRepository; -// -// Member member; -// -// @BeforeEach -// void setUp() { -// List questions = List.of( -// Question.builder().sequence(1).inputType(TEXT).isRequired(true).title("title1").label("label1").build(), -// Question.builder().sequence(2).inputType(TEXT).isRequired(true).title("title2").label("label2").build(), -// Question.builder().sequence(3).inputType(TEXT).isRequired(true).title("title3").label("label3").build(), -// Question.builder().sequence(4).inputType(TEXT).isRequired(true).title("title4").label("label4").build(), -// Question.builder().sequence(5).inputType(TEXT).isRequired(true).title("title5").label("label5").build() -// ); -// -// Semester savedSemester = semesterRepository.save(Semester.builder() -// .name("1๊ธฐ") -// .isRecruiting(true) -// .build()); -// -// Recruit recruit = Recruit.builder() -// .startDate(LocalDateTime.now().minusDays(1)) -// .endDate(LocalDateTime.now().plusDays(1)) -// .semester(savedSemester) -// .jobFamily(JobFamily.BE) -// .build(); -// -// for (Question question : questions) { -// recruit.addQuestion(question); -// } -// -// Recruit savedRecruit = recruitRepository.save(recruit); -// -// member = Member.builder() -// .email("test32@gmail.com") -// .semesterId(savedSemester.getId()) -// .jobFamily(JobFamily.BE) -// .name("๊น€์ ํŠธ") -// .role(Role.APPLY) -// .phoneNumber("01012345678") -// .pin("123456") -// .status(MemberStatus.ACTIVE) -// .build(); -// Member savedMember = memberRepository.save(member); -// -// Apply apply = Apply.builder() -// .recruit(savedRecruit) -// .member(savedMember) -// .status(Apply.Status.JOINED) -// .build(); -// applyRepository.save(apply); -// } -// -// @AfterEach -// void tearDown() { -// temporaryApplicationRepository.deleteAll(); -// applicationFormRepository.deleteAll(); -// recruitRepository.deleteAll(); -// memberRepository.deleteAll(); -// applyRepository.deleteAll(); -// } -// -// @Test -// @DisplayName("apply temporal test") -// @AuthenticatedUser -// void test_temp_apply() throws Exception { -// mockMvc.perform(post("/apply/temp") -// .contentType("application/json") -// .param("jobFamily", "BE") -// .content(""" -// { -// "answers": { -// "1": "1๋ฒˆ ๋‹ต๋ณ€์ž„", -// "2": "2๋ฒˆ ๋‹ต๋ณ€์ž„~", -// "3": "3๋ฒˆ ๋‹ต๋ณ€์ž„~~", -// "4": "4๋ฒˆ.", -// "5": "5๋ฒˆ ๋‹ต๋ณ€~" -// }, -// "portfolios": [ -// { -// "fileUrl": "filrUrlA", -// "fileName": "fileNameA", -// "fileSize": "105021", -// "sequence": "1" -// }, -// { -// "fileUrl": "filrUrlB", -// "fileName": "fileNameB", -// "fileSize": "105021", -// "sequence": "2" -// } -// ] -// } -// """) -// ) -//// .andExpect(status().isOk()) -//// .andExpect(content().string(containsString("SUCCESS"))) -// .andDo(print()) -// .andReturn(); -// } -// -// @Test -// @DisplayName("invalid question semesterId") -// @AuthenticatedUser -// @Transactional -// void invalid_question_id() throws Exception { -// mockMvc.perform(post("/apply/temp") -// .contentType("application/json") -// .param("jobFamily", "BE") -// .content(""" -// { -// "answers": { -// "1": "1๋ฒˆ ๋‹ต๋ณ€์ž„", -// "2": "2๋ฒˆ ๋‹ต๋ณ€์ž„~", -// "3": "3๋ฒˆ ๋‹ต๋ณ€์ž„~~", -// "4": "4๋ฒˆ.", -// "6": "???" -// } -// } -// """) -// ) -// .andExpect(status().isNotFound()) -// .andExpect(content().string(containsString("QUESTION_NOT_FOUND"))) -// .andDo(print()) -// .andReturn(); -// } -// -// @Test -// @AuthenticatedUser -// void inquire_temporal_application() throws Exception { -// // given: ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ ์ €์žฅ -// temporaryApplicationRepository.save(createTemporaryApplication( -// "1", -// Map.of("1", "๋‹ต๋ณ€1", "2", "๋‹ต๋ณ€2"), -// "PM", -// List.of(createApplyTemporaryPortfolio("1")))); -// temporaryApplicationRepository.save(createTemporaryApplication( -// "1", -// Map.of("3", "๋‹ต๋ณ€3", "4", "๋‹ต๋ณ€4", "5", "๋‹ต๋ณ€5"), -// "BE", -// List.of(createApplyTemporaryPortfolio("1"), createApplyTemporaryPortfolio("2")))); -// -// // when & then -// ResultActions resultActions = mockMvc.perform(get("/apply/temp")) -// .andExpect(status().isOk()) -// .andExpect(content().string(containsString("SUCCESS"))) -// .andExpectAll( -// content().string(containsString("jobFamily")), -// content().string(containsString("๋‹ต๋ณ€3")), -// content().string(containsString("๋‹ต๋ณ€4")), -// content().string(containsString("๋‹ต๋ณ€5")), -// content().string(containsString("fileName")), -// content().string(containsString("fileUrl")) -// ); -// -// resultActions.andDo(print()); -// } -// -// @Test -// @DisplayName("delete temp application") -// @AuthenticatedUser -// void delete_temp_application() throws Exception { -// // given -// temporaryApplicationRepository.save(new TemporaryApplication("1", Map.of( -// "8", "answer 1-1 for 8", -// "9", "answer 1-1 for 9", -// "10", "answer 1-1 for 10"), "BE", List.of(createApplyTemporaryPortfolio("1")))); -// temporaryApplicationRepository.save(new TemporaryApplication("1", Map.of( -// "8", "answer 1-2 for 8", -// "9", "answer 1-2 for 9", -// "10", "answer 1-2 for 10"), "BE", List.of(createApplyTemporaryPortfolio("1")))); -// temporaryApplicationRepository.save(new TemporaryApplication("1", Map.of( -// "8", "answer 1-3 for 8", -// "9", "answer 1-3 for 9", -// "10", "answer 1-3 for 10"), "BE", List.of(createApplyTemporaryPortfolio("1")))); -// temporaryApplicationRepository.save(new TemporaryApplication("2", Map.of( -// "8", "answer 2-1 for 8", -// "9", "answer 2-1 for 9", -// "10", "answer 2-1 for 10"), "BE", List.of(createApplyTemporaryPortfolio("1")))); -// temporaryApplicationRepository.save(new TemporaryApplication("2", Map.of( -// "8", "answer 2-2 for 8", -// "9", "answer 2-2 for 9", -// "10", "answer 2-2 for 10"), "BE", List.of(createApplyTemporaryPortfolio("1")))); -// -// // when, then -// mockMvc.perform(delete("/apply/temp") -// .contentType("application/json") -// ) -// .andExpect(status().isOk()) -// .andExpect(content().string(containsString("SUCCESS"))) -// .andDo(print()) -// .andReturn(); -// -// assertThat(temporaryApplicationRepository.findByPartitionKey(new CompositeKey("MEMBER", "1"))).isEmpty(); -// assertThat(temporaryApplicationRepository.findByPartitionKey(new CompositeKey("MEMBER", "2"))).hasSize(2); -// } -// -// @Test -// @DisplayName("submit application form") -// @AuthenticatedUser -// void submit_application_form() throws Exception { -// // when, then -// mockMvc.perform(post("/apply/submit") -// .contentType("application/json") -// .param("jobFamily", "BE") -// .content(""" -// { -// "answers": { -// "1": "1๋ฒˆ ๋‹ต๋ณ€์ž„", -// "2": "2๋ฒˆ ๋‹ต๋ณ€์ž„~", -// "3": "3๋ฒˆ ๋‹ต๋ณ€์ž„~~", -// "4": "4๋ฒˆ.", -// "5": "5๋ฒˆ ๋‹ต๋ณ€~" -// }, -// "portfolios": [ -// { -// "fileUrl": "filrUrlA", -// "fileName": "fileNameA", -// "fileSize": "105021", -// "sequence": "1" -// }, -// { -// "fileUrl": "filrUrlB", -// "fileName": "fileNameB", -// "fileSize": "105021", -// "sequence": "2" -// } -// ] -// } -// """) -// ) -//// .andExpect(status().isOk()) -//// .andExpect(content().string(containsString("SUCCESS"))) -// .andDo(print()) -// .andReturn(); -// } -// -// private TemporaryApplication createTemporaryApplication(String memberId, -// Map answers, -// String jobFamily, -// List portfolios) { -// return new TemporaryApplication(memberId, answers, jobFamily, portfolios); -// } -// -// private ApplyPortfolioDto createApplyTemporaryPortfolio(String sequence) { -// return new ApplyPortfolioDto("url", "name", "10202", sequence); -// } -//} diff --git a/src/test/java/org/ject/support/domain/recruit/controller/QuestionControllerTest.java b/src/test/java/org/ject/support/domain/recruit/controller/QuestionControllerTest.java index e38d47be..87b93eef 100644 --- a/src/test/java/org/ject/support/domain/recruit/controller/QuestionControllerTest.java +++ b/src/test/java/org/ject/support/domain/recruit/controller/QuestionControllerTest.java @@ -111,7 +111,7 @@ void find_questions_cache() throws Exception { .andDo(print()); // then - Long countExistingKeys = redisTemplate.countExistingKeys(List.of("question::BE")); + Long countExistingKeys = redisTemplate.countExistingKeys(List.of("cache::question::BE")); Assertions.assertThat(countExistingKeys).isEqualTo(1); } } diff --git a/src/test/java/org/ject/support/domain/review/repository/ReviewRepositoryTest.java b/src/test/java/org/ject/support/domain/review/repository/ReviewRepositoryTest.java deleted file mode 100644 index fc8079c1..00000000 --- a/src/test/java/org/ject/support/domain/review/repository/ReviewRepositoryTest.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.ject.support.domain.review.repository; - -import java.util.List; -import org.ject.support.domain.review.dto.ReviewResponse; -import org.ject.support.domain.review.entity.Review; -import org.ject.support.testconfig.QueryDslTestConfig; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; -import org.springframework.context.annotation.Import; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; - -import static org.assertj.core.api.Assertions.assertThat; - -@Import(QueryDslTestConfig.class) -@DataJpaTest -class ReviewRepositoryTest { - - @Autowired - ReviewRepository reviewRepository; - - @Test - @DisplayName("๋ฆฌ๋ทฐ ๋ชฉ๋ก ์กฐํšŒ") - void find_reviews() { - // given - Review review1 = createReview(); - Review review2 = createReview(); - Review review3 = createReview(); - Review review4 = createReview(); - Review review5 = createReview(); - reviewRepository.saveAll(List.of(review1, review2, review3, review4, review5)); - - // when - Page result = reviewRepository.findReviews(PageRequest.of(0, 4)); - - // then - assertThat(result.getContent()).hasSize(4); - result.getContent().forEach(reviewResponse -> { - assertThat(reviewResponse.id()).isNotNull(); - assertThat(reviewResponse.title()).isNotNull(); - assertThat(reviewResponse.linkUrl()).isNotNull(); - assertThat(reviewResponse.description()).isNotNull(); - assertThat(reviewResponse.summary()).isNotNull(); - }); - } - - private Review createReview() { - return Review.builder() - .linkUrl("https://test.com") - .title("title") - .description("description") - .summary("summary") - .build(); - } -} \ No newline at end of file diff --git a/src/test/java/org/ject/support/domain/tempapply/service/TemporaryApplyServiceImplTest.java b/src/test/java/org/ject/support/domain/tempapply/service/TemporaryApplyServiceImplTest.java deleted file mode 100644 index e4d3cb77..00000000 --- a/src/test/java/org/ject/support/domain/tempapply/service/TemporaryApplyServiceImplTest.java +++ /dev/null @@ -1,85 +0,0 @@ -package org.ject.support.domain.tempapply.service; - -import org.ject.support.domain.member.JobFamily; -import org.ject.support.domain.recruit.domain.Recruit; -import org.ject.support.domain.recruit.domain.Semester; -import org.ject.support.domain.recruit.repository.RecruitRepository; -import org.ject.support.domain.tempapply.domain.TemporaryApplication; -import org.ject.support.domain.tempapply.repository.TemporaryApplicationRepository; -import org.ject.support.testconfig.IntegrationTest; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.transaction.annotation.Transactional; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.ject.support.domain.member.JobFamily.BE; -import static org.ject.support.domain.member.JobFamily.FE; -import static org.ject.support.domain.member.JobFamily.PD; -import static org.ject.support.domain.member.JobFamily.PM; - -@Deprecated -@IntegrationTest -@Transactional -class TemporaryApplyServiceImplTest { - - @Autowired - TemporaryApplyService temporaryApplyService; - - @Autowired - TemporaryApplicationRepository temporaryApplicationRepository; - - @Autowired - RecruitRepository recruitRepository; - - @AfterEach - void tearDown() { - temporaryApplicationRepository.deleteAll(); - } - - @Test - @DisplayName("ํ™œ์„ฑํ™”๋œ ๋ชจ์ง‘ ๊ธฐ๊ฐ„ ์ค‘ ์ €์žฅ๋œ ์ž„์‹œ ์ง€์›์„œ์˜ ์‚ฌ์šฉ์ž ID ์ค‘๋ณต ์—†์ด ์กฐํšŒ") - void find_member_ids_by_active_recruits() { - // given - temporaryApplicationRepository.save(createTemporaryApplication("1", Map.of(), "BE")); - temporaryApplicationRepository.save(createTemporaryApplication("1", Map.of(), "BE")); - temporaryApplicationRepository.save(createTemporaryApplication("2", Map.of(), "FE")); - temporaryApplicationRepository.save(createTemporaryApplication("3", Map.of(), "FE")); - temporaryApplicationRepository.save(createTemporaryApplication("4", Map.of(), "BE")); - temporaryApplicationRepository.save(createTemporaryApplication("4", Map.of(), "BE")); - temporaryApplicationRepository.save(createTemporaryApplication("4", Map.of(), "BE")); - temporaryApplicationRepository.save(createTemporaryApplication("5", Map.of(), "PD")); - - // when - List resultOfPm = temporaryApplyService.findMemberIdsByRecruit(getRecruit(PM)); - List resultOfPd = temporaryApplyService.findMemberIdsByRecruit(getRecruit(PD)); - List resultOfFe = temporaryApplyService.findMemberIdsByRecruit(getRecruit(FE)); - List resultOfBe = temporaryApplyService.findMemberIdsByRecruit(getRecruit(BE)); - - // then - assertThat(resultOfPm).hasSize(0); - assertThat(resultOfPd).hasSize(1); - assertThat(resultOfFe).hasSize(2); - assertThat(resultOfBe).hasSize(2); - } - - private Recruit getRecruit(JobFamily jobFamily) { - return Recruit.builder() - .semester(Semester.builder().id(1L).name("1๊ธฐ").isRecruiting(true).build()) - .jobFamily(jobFamily) - .startDate(LocalDateTime.now().minusDays(1)) - .endDate(LocalDateTime.now().plusDays(1)) - .build(); - } - - private TemporaryApplication createTemporaryApplication(String memberId, - Map answers, - String jobFamily) { - return new TemporaryApplication(memberId, answers, jobFamily, List.of()); - } -} \ No newline at end of file diff --git a/src/test/java/org/ject/support/external/dynamodb/repository/TemporaryApplicationRepositoryTest.java b/src/test/java/org/ject/support/external/dynamodb/repository/TemporaryApplicationRepositoryTest.java deleted file mode 100644 index 42f8dac5..00000000 --- a/src/test/java/org/ject/support/external/dynamodb/repository/TemporaryApplicationRepositoryTest.java +++ /dev/null @@ -1,219 +0,0 @@ -package org.ject.support.external.dynamodb.repository; - -import org.ject.support.domain.apply.dto.ApplyPortfolioDto; -import org.ject.support.domain.tempapply.domain.TemporaryApplication; -import org.ject.support.domain.tempapply.repository.TemporaryApplicationRepository; -import org.ject.support.external.dynamodb.domain.CompositeKey; -import org.ject.support.testconfig.IntegrationTest; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; - -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; - -@Deprecated -@IntegrationTest -class TemporaryApplicationRepositoryTest { - - @Autowired - private TemporaryApplicationRepository temporaryApplicationRepository; - - @AfterEach - void tearDown() { - temporaryApplicationRepository.deleteAll(); - } - - @Order(1) - @Test - @DisplayName("dynamodb repository save test") - void dynamodb_save() { - // given - TemporaryApplication temporaryApplication = createTemporaryApplication( - "1", - Map.of("key", "value"), - "BE", - List.of(createApplyTemporaryPortfolio("1"))); - - // when - temporaryApplicationRepository.save(temporaryApplication); - - // then - Optional optional = temporaryApplicationRepository.findByPartitionKeyAndSortKey( - temporaryApplication.getPk(), temporaryApplication.getSk()); - assertThat(optional).isPresent(); - TemporaryApplication saved = optional.get(); - assertThat(saved).isEqualTo(temporaryApplication); - } - - @Order(2) - @Test - @DisplayName("dynamodb repository find by partition key test") - void find_by_partition_key() { - // given - temporaryApplicationRepository.save(createTemporaryApplication( - "1", - Map.of("key", "value"), - "BE", - List.of(createApplyTemporaryPortfolio("1")))); - temporaryApplicationRepository.save(createTemporaryApplication( - "1", - Map.of("key", "value"), - "BE", - List.of(createApplyTemporaryPortfolio("1")))); - temporaryApplicationRepository.save(createTemporaryApplication( - "1", - Map.of("key", "value"), - "BE", - List.of(createApplyTemporaryPortfolio("1")))); - temporaryApplicationRepository.save(createTemporaryApplication( - "2", - Map.of("key", "value"), - "FE", - List.of(createApplyTemporaryPortfolio("1")))); - temporaryApplicationRepository.save(createTemporaryApplication( - "2", - Map.of("key", "value"), - "FE", - List.of(createApplyTemporaryPortfolio("1")))); - temporaryApplicationRepository.save(createTemporaryApplication( - "3", - Map.of("key", "value"), - "BE", - List.of(createApplyTemporaryPortfolio("1")))); - temporaryApplicationRepository.save(createTemporaryApplication( - "4", - Map.of("key", "value"), - "BE", - List.of(createApplyTemporaryPortfolio("1")))); - - // when - String prefix = "MEMBER"; - List members1 = temporaryApplicationRepository.findByPartitionKey( - new CompositeKey(prefix, "1")); - - // then - assertThat(members1).hasSize(3); - assertThat(members1).allMatch(temporaryApplication -> temporaryApplication.getMemberId().equals("1")); - assertThat(members1).isSortedAccordingTo(Comparator.comparing(TemporaryApplication::getTimestamp)); - - List members2 = temporaryApplicationRepository.findByPartitionKey( - new CompositeKey(prefix, "2")); - assertThat(members2).hasSize(2); - assertThat(members2).allMatch(temporaryApplication -> temporaryApplication.getMemberId().equals("2")); - assertThat(members2).isSortedAccordingTo(Comparator.comparing(TemporaryApplication::getTimestamp)); - } - - @Order(3) - @Test - @DisplayName("dynamodb repository find by partition with sort type test") - void find_by_partition_with_sort_type() { - // given - temporaryApplicationRepository.save(createTemporaryApplication( - "1", - Map.of("key", "value"), - "BE", - List.of(createApplyTemporaryPortfolio("1")))); - temporaryApplicationRepository.save(createTemporaryApplication( - "1", - Map.of("key", "value"), - "BE", - List.of(createApplyTemporaryPortfolio("1")))); - temporaryApplicationRepository.save(createTemporaryApplication( - "1", - Map.of("key", "value"), - "BE", - List.of(createApplyTemporaryPortfolio("1")))); - temporaryApplicationRepository.save(createTemporaryApplication( - "2", - Map.of("key", "value"), - "FE", - List.of(createApplyTemporaryPortfolio("1")))); - temporaryApplicationRepository.save(createTemporaryApplication( - "2", - Map.of("key", "value"), - "FE", - List.of(createApplyTemporaryPortfolio("1")))); - temporaryApplicationRepository.save(createTemporaryApplication( - "3", - Map.of("key", "value"), - "BE", - List.of(createApplyTemporaryPortfolio("1")))); - temporaryApplicationRepository.save(createTemporaryApplication( - "4", - Map.of("key", "value"), - "BE", - List.of(createApplyTemporaryPortfolio("1")))); - - // when - String prefix = "TIMESTAMP"; - List members1 = temporaryApplicationRepository.findByPartitionWithSortType( - new CompositeKey("MEMBER", "1"), prefix); - // then - assertThat(members1).hasSize(3); - assertThat(members1).allMatch(temporaryApplication -> temporaryApplication.getMemberId().equals("1")); - assertThat(members1).isSortedAccordingTo(Comparator.comparing(TemporaryApplication::getTimestamp)); - - List members2 = temporaryApplicationRepository - .findByPartitionWithSortType(new CompositeKey("MEMBER", "2"), prefix); - assertThat(members2).hasSize(2); - assertThat(members2).allMatch(temporaryApplication -> temporaryApplication.getMemberId().equals("2")); - assertThat(members2).isSortedAccordingTo(Comparator.comparing(TemporaryApplication::getTimestamp)); - } - - @Order(4) - @Test - @DisplayName("dynamodb repository delete by member semesterId test") - void delete_by_member_id() { - // given - temporaryApplicationRepository.save(createTemporaryApplication("1", Map.of( - "8", "answer 1-1 for 8", - "9", "answer 1-1 for 9", - "10", "answer 1-1 for 10"), "BE", List.of(createApplyTemporaryPortfolio("1")))); - temporaryApplicationRepository.save(new TemporaryApplication("1", Map.of( - "8", "answer 1-2 for 8", - "9", "answer 1-2 for 9", - "10", "answer 1-2 for 10"), "BE", List.of(createApplyTemporaryPortfolio("1")))); - temporaryApplicationRepository.save(new TemporaryApplication("1", Map.of( - "8", "answer 1-3 for 8", - "9", "answer 1-3 for 9", - "10", "answer 1-3 for 10"), "BE", List.of(createApplyTemporaryPortfolio("1")))); - temporaryApplicationRepository.save(new TemporaryApplication("2", Map.of( - "8", "answer 2-1 for 8", - "9", "answer 2-1 for 9", - "10", "answer 2-1 for 10"), "BE", List.of(createApplyTemporaryPortfolio("1")))); - temporaryApplicationRepository.save(new TemporaryApplication("2", Map.of( - "8", "answer 2-2 for 8", - "9", "answer 2-2 for 9", - "10", "answer 2-2 for 10"), "BE", List.of(createApplyTemporaryPortfolio("1")))); - - // when - temporaryApplicationRepository.deleteByPartitionKey(new CompositeKey("MEMBER", "1")); - - // then - List temporaryApplicationsByMemberId1 = - temporaryApplicationRepository.findByPartitionKey(new CompositeKey("MEMBER", "1")); - assertThat(temporaryApplicationsByMemberId1).isEmpty(); - - List temporaryApplicationsByMemberId2 = - temporaryApplicationRepository.findByPartitionKey(new CompositeKey("MEMBER", "2")); - assertThat(temporaryApplicationsByMemberId2).hasSize(2); - } - - private TemporaryApplication createTemporaryApplication(String memberId, - Map answers, - String jobFamily, - List portfolios) { - return new TemporaryApplication(memberId, answers, jobFamily, portfolios); - } - - private ApplyPortfolioDto createApplyTemporaryPortfolio(String sequence) { - return new ApplyPortfolioDto("url", "name", "10202", sequence); - } -} diff --git a/src/test/java/org/ject/support/external/email/service/EmailAuthServiceTest.java b/src/test/java/org/ject/support/external/email/service/EmailAuthServiceTest.java index 3e1d60ee..e2d5782c 100644 --- a/src/test/java/org/ject/support/external/email/service/EmailAuthServiceTest.java +++ b/src/test/java/org/ject/support/external/email/service/EmailAuthServiceTest.java @@ -51,6 +51,25 @@ class EmailAuthServiceTest extends UnitTestSupport { verify(emailSendService).sendTemplatedEmail(eq(EmailTemplate.AUTH_CODE), eq(email), any()); } + @Test + void ์ธ์ฆ๋ฒˆํ˜ธ_์žฌ์„ค์ •_์‹œ_Rate_Limit์ด_์ ์šฉ๋˜์–ด_์žˆ์ง€_์•Š๋‹ค๋ฉด_์ •์ƒ_๋ฐœ์†ก๋œ๋‹ค() { + // given + String email = "test@example.com"; + + given(redisTemplate.hasKey(anyString())).willReturn(false); + given(redisTemplate.opsForValue()).willReturn(valueOperations); + + // when + emailAuthService.sendAuthCode(EmailTemplate.PIN_RESET, email); + + // then + // 1. Rate Limit ํ‚ค๊ฐ€ ์„ค์ •๋˜์—ˆ๋Š”์ง€ ๊ฒ€์ฆ (3๋ถ„) + verify(valueOperations).set(eq("email:rate_limit:" + email), eq("1"), eq(Duration.ofMinutes(3))); + // 2. ์ด๋ฉ”์ผ ๋ฐœ์†ก์ด ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€ ๊ฒ€์ฆ + verify(emailSendService).sendTemplatedEmail(eq(EmailTemplate.PIN_RESET), eq(email), any()); + } + + @Test void _3๋ถ„_๋‚ด์—_์žฌ์š”์ฒญ_์‹œ_RateLimitException์ด_๋ฐœ์ƒํ•œ๋‹ค() { // given @@ -69,19 +88,19 @@ class EmailAuthServiceTest extends UnitTestSupport { } @Test - void PIN_์žฌ์„ค์ •_๋ฉ”์ผ์€_Rate_Limit_์˜ํ–ฅ์„_๋ฐ›์ง€_์•Š๋Š”๋‹ค() { + void ๋ฆฌ๋งˆ์ธ๋”_๋ฉ”์ผ์€_Rate_Limit_์˜ํ–ฅ์„_๋ฐ›์ง€_์•Š๋Š”๋‹ค() { // given String email = "test@example.com"; given(redisTemplate.opsForValue()).willReturn(valueOperations); // when - emailAuthService.sendAuthCode(EmailTemplate.PIN_RESET, email); + emailAuthService.sendAuthCode(EmailTemplate.REMIND_APPLY, email); // then // Rate Limit ์ฒดํฌ(hasKey)๋ฅผ ํ•˜์ง€ ์•Š์•„์•ผ ํ•จ verify(redisTemplate, never()).hasKey(anyString()); // ์ด๋ฉ”์ผ ๋ฐœ์†ก์€ ์ •์ƒ ํ˜ธ์ถœ - verify(emailSendService).sendTemplatedEmail(eq(EmailTemplate.PIN_RESET), eq(email), any()); + verify(emailSendService).sendTemplatedEmail(eq(EmailTemplate.REMIND_APPLY), eq(email), any()); } } diff --git a/src/test/java/org/ject/support/testconfig/RedisTestContainersConfig.java b/src/test/java/org/ject/support/testconfig/RedisTestContainersConfig.java index 7c25d606..fae049cf 100644 --- a/src/test/java/org/ject/support/testconfig/RedisTestContainersConfig.java +++ b/src/test/java/org/ject/support/testconfig/RedisTestContainersConfig.java @@ -1,6 +1,7 @@ package org.ject.support.testconfig; import java.time.Duration; + import org.springframework.beans.factory.DisposableBean; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; @@ -13,18 +14,19 @@ import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; +import lombok.extern.slf4j.Slf4j; + @Profile("test") @TestConfiguration @Testcontainers +@Slf4j public class RedisTestContainersConfig implements DisposableBean { private static final int REDIS_PORT = 6379; - @Container - private static final GenericContainer redisContainer = new GenericContainer<>("redis:8.0-M03-alpine") + // @Container ์–ด๋…ธํ…Œ์ด์…˜ ์ œ๊ฑฐ + private static final GenericContainer redisContainer = new GenericContainer<>("redis:7.4-alpine") .withExposedPorts(REDIS_PORT) - .waitingFor(Wait.forListeningPort()) - .withStartupTimeout(Duration.ofSeconds(60)); - + .withStartupTimeout(Duration.ofSeconds(180)); static { try { if (!redisContainer.isRunning()) {