diff --git a/build.gradle.kts b/build.gradle.kts index a1f5be5..77f3588 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -132,14 +132,19 @@ tasks.named("jacocoTestReport") { exclude( "**/persistence/entity/**", "**/presentation/**/dto/**", - "**/oauth/apple/dto/**", "**/oauth/git/dto/**", + "**/dto/request/**", + "**/dto/response/**", "**/io/junseok/todeveloperdo/oauth/apple/service/serviceimpl/ClientSecretCreator.class", "**/io/junseok/todeveloperdo/oauth/apple/service/serviceimpl/ClientSecretCreatorTest.class", "**/io/junseok/todeveloperdo/auth/config/SecurityConfig.class", "**/io/junseok/todeveloperdo/ToDeveloperDoApplicationKt.class", "**/io/junseok/todeveloperdo/oauth/git/service/CustomOAuth2UserService.class", - "**/io/junseok/todeveloperdo/global/fcm/FcmCredentials.class" + "**/io/junseok/todeveloperdo/global/fcm/FcmCredentials.class", + "**/io/junseok/todeveloperdo/util/ElvisKt.class", + "**/io/junseok/todeveloperdo/domains/gitissue/TodoCreate*", + "**/io/junseok/todeveloperdo/oauth/git/config/WebhookConfig*", + "**/io/junseok/todeveloperdo/util/StubDateProvider*" ) } }) diff --git a/src/main/kotlin/io/junseok/todeveloperdo/auth/jwt/TokenProvider.kt b/src/main/kotlin/io/junseok/todeveloperdo/auth/jwt/TokenProvider.kt index eb8df70..7255b1e 100644 --- a/src/main/kotlin/io/junseok/todeveloperdo/auth/jwt/TokenProvider.kt +++ b/src/main/kotlin/io/junseok/todeveloperdo/auth/jwt/TokenProvider.kt @@ -106,6 +106,9 @@ class TokenProvider( log.info("No refresh token found to delete.") } } + else -> { + log.info("ExpiredJwtException occurred, but not a REFRESH token: type=$type") + } } throw ToDeveloperDoException { ErrorCode.EXPIRED_JWT } diff --git a/src/main/kotlin/io/junseok/todeveloperdo/domains/todo/service/MemberTodoService.kt b/src/main/kotlin/io/junseok/todeveloperdo/domains/todo/service/MemberTodoService.kt index 20b72f5..b8d22bf 100644 --- a/src/main/kotlin/io/junseok/todeveloperdo/domains/todo/service/MemberTodoService.kt +++ b/src/main/kotlin/io/junseok/todeveloperdo/domains/todo/service/MemberTodoService.kt @@ -15,9 +15,9 @@ import io.junseok.todeveloperdo.presentation.membertodolist.dto.request.TodoDate import io.junseok.todeveloperdo.presentation.membertodolist.dto.request.TodoRequest import io.junseok.todeveloperdo.presentation.membertodolist.dto.response.TodoCountResponse import io.junseok.todeveloperdo.presentation.membertodolist.dto.response.TodoResponse +import io.junseok.todeveloperdo.util.TimeProvider +import io.junseok.todeveloperdo.util.runIfNotNull import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional -import java.time.LocalDate @Service class MemberTodoService( @@ -29,6 +29,7 @@ class MemberTodoService( private val gitIssueService: GitIssueService, private val todoValidator: TodoValidator, private val gitIssueUpdater: GitIssueUpdater, + private val timeProvider: TimeProvider ) { @CreateEvent @ReadMeCreate @@ -40,18 +41,22 @@ class MemberTodoService( val member = memberReader.getMember(username) val memberTodoLists = todoRequest.map { todo -> - todoCreator.generatorTodo( - todo, - member, - issueEventRequest?.issueNumber?.get() - ?.takeIf { LocalDate.now() == todo.deadline } - ) + val issueNumber = issueEventRequest + .runIfNotNull { it.issueNumber } + .runIfNotNull { it.get() } + .runIfNotNull { number -> + if (timeProvider.nowDate() == todo.deadline) number else null + } + + todoCreator.generatorTodo(todo, member, issueNumber) } + val saveTodoList = todoSaver.saveTodoList(memberTodoLists) gitIssueService.saveGitIssue(member, memberTodoLists) return saveTodoList } + // 할 일 찾기 fun findTodoLists(todoDateRequest: TodoDateRequest, username: String): List { val member = memberReader.getMember(username) @@ -77,7 +82,8 @@ class MemberTodoService( //할 일 삭제 NOTE @DeleteEventHandler - fun removeTodoList(todoListId: Long, username: String, state: String) { } + fun removeTodoList(todoListId: Long, username: String, state: String) { + } fun calculateTodoList( todoCountRequest: TodoCountRequest, diff --git a/src/main/kotlin/io/junseok/todeveloperdo/oauth/git/service/GitHubOAuthService.kt b/src/main/kotlin/io/junseok/todeveloperdo/oauth/git/service/GitHubOAuthService.kt index 81bfac3..6d51234 100644 --- a/src/main/kotlin/io/junseok/todeveloperdo/oauth/git/service/GitHubOAuthService.kt +++ b/src/main/kotlin/io/junseok/todeveloperdo/oauth/git/service/GitHubOAuthService.kt @@ -8,6 +8,7 @@ import io.junseok.todeveloperdo.oauth.git.client.GitHubAccessTokenClient import io.junseok.todeveloperdo.oauth.git.client.GitHubApiClient import io.junseok.todeveloperdo.oauth.git.dto.response.TokenResponse import io.junseok.todeveloperdo.oauth.git.dto.response.toGitUserResponse +import io.junseok.todeveloperdo.util.runIfNotNull import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Service @@ -22,7 +23,7 @@ class GitHubOAuthService( private val clientSecret: String ) { - fun processGitHubOAuth(code: String,userName: String): TokenResponse { + fun processGitHubOAuth(code: String, userName: String): TokenResponse { val accessTokenResponse = accessTokenClient.getAccessToken(clientId, clientSecret, code) if (accessTokenResponse.contains("error")) { @@ -35,17 +36,25 @@ class GitHubOAuthService( val userInfoResponse = gitHubApiClient.getUserInfo(bearerToken) val userInfo = parseUserInfo(userInfoResponse) - memberService.createGitMember(userInfo.toGitUserResponse(),accessToken,userName) + memberService.createGitMember(userInfo.toGitUserResponse(), accessToken, userName) return TokenResponse(bearerToken) } fun extractAccessToken(response: String): String { - val firstOrNull = response.split("&") + return response.split("&") .firstOrNull { it.startsWith("access_token") } - return (firstOrNull?.split("="))?.get(1) - ?: throw IllegalArgumentException("Access token not found in response: $response") + .runIfNotNull { it -> + it.split("=") + .runIfNotNull { it -> + it.getOrNull(1) + .runIfNotNull { token -> + token.takeIf { it.isNotBlank() } + } + } + } ?: throw IllegalArgumentException("Access token not found or malformed: $response") } + fun parseUserInfo(response: String): Map { val mapper = jacksonObjectMapper() return mapper.readValue(response, object : TypeReference>() {}) diff --git a/src/main/kotlin/io/junseok/todeveloperdo/oauth/git/service/reposerviceimpl/WebHookProcessor.kt b/src/main/kotlin/io/junseok/todeveloperdo/oauth/git/service/reposerviceimpl/WebHookProcessor.kt index 9b2eade..591b566 100644 --- a/src/main/kotlin/io/junseok/todeveloperdo/oauth/git/service/reposerviceimpl/WebHookProcessor.kt +++ b/src/main/kotlin/io/junseok/todeveloperdo/oauth/git/service/reposerviceimpl/WebHookProcessor.kt @@ -17,19 +17,16 @@ class WebHookProcessor( if (event == GitHubService.REPOSITORY) { val action = payload["action"] as String - when (action) { - RENAMED_REPO_ACTION -> { - val payloadResponse = payloadCreator.create(payload) - val member = memberReader.findByGitUserName(payloadResponse.username) - memberUpdater.updateMemberRepo(payloadResponse.newRepoName, member) - } - DELETE_REPO_ACTION -> { - val payloadResponse = payloadCreator.create(payload) - val member = memberReader.findByGitUserName(payloadResponse.username) - memberUpdater.removeMemberRepo(member) - } + if (action == RENAMED_REPO_ACTION) { + val payloadResponse = payloadCreator.create(payload) + val member = memberReader.findByGitUserName(payloadResponse.username) + memberUpdater.updateMemberRepo(payloadResponse.newRepoName, member) + } + if (action == DELETE_REPO_ACTION) { + val payloadResponse = payloadCreator.create(payload) + val member = memberReader.findByGitUserName(payloadResponse.username) + memberUpdater.removeMemberRepo(member) } } } - -} \ No newline at end of file +} diff --git a/src/main/kotlin/io/junseok/todeveloperdo/scheduler/fcm/AllTodosCompletedStrategy.kt b/src/main/kotlin/io/junseok/todeveloperdo/scheduler/fcm/AllTodosCompletedStrategy.kt index 38cfeed..2cb9eac 100644 --- a/src/main/kotlin/io/junseok/todeveloperdo/scheduler/fcm/AllTodosCompletedStrategy.kt +++ b/src/main/kotlin/io/junseok/todeveloperdo/scheduler/fcm/AllTodosCompletedStrategy.kt @@ -12,7 +12,7 @@ import org.springframework.stereotype.Component class AllTodosCompletedStrategy( private val todoReader: TodoReader, private val timeProvider: TimeProvider -) : NotificationStrategy{ +) : NotificationStrategy { override fun getFcmRequests(): List = todoReader.findTodoListByTodoStatus( timeProvider.nowDate(), diff --git a/src/main/kotlin/io/junseok/todeveloperdo/util/Elvis.kt b/src/main/kotlin/io/junseok/todeveloperdo/util/Elvis.kt new file mode 100644 index 0000000..3405f14 --- /dev/null +++ b/src/main/kotlin/io/junseok/todeveloperdo/util/Elvis.kt @@ -0,0 +1,4 @@ +package io.junseok.todeveloperdo.util + +inline fun T?.runIfNotNull(block: (T) -> R?): R? = + if (this != null) block(this) else null diff --git a/src/test/kotlin/io/junseok/todeveloperdo/auth/jwt/ExceptionHandlerFilterTest.kt b/src/test/kotlin/io/junseok/todeveloperdo/auth/jwt/ExceptionHandlerFilterTest.kt index def3014..59e0215 100644 --- a/src/test/kotlin/io/junseok/todeveloperdo/auth/jwt/ExceptionHandlerFilterTest.kt +++ b/src/test/kotlin/io/junseok/todeveloperdo/auth/jwt/ExceptionHandlerFilterTest.kt @@ -53,5 +53,14 @@ class ExceptionHandlerFilterTest : FunSpec({ } } + test("예외가 발생하지 않으면 필터 체인이 정상적으로 실행된다") { + val request = MockHttpServletRequest() + val response = MockHttpServletResponse() + val chain = mockk() + + every { chain.doFilter(any(), any()) } just runs + + filter.doFilter(request, response, chain) + } }) diff --git a/src/test/kotlin/io/junseok/todeveloperdo/auth/jwt/TokenProviderTest.kt b/src/test/kotlin/io/junseok/todeveloperdo/auth/jwt/TokenProviderTest.kt index 983fa04..564f264 100644 --- a/src/test/kotlin/io/junseok/todeveloperdo/auth/jwt/TokenProviderTest.kt +++ b/src/test/kotlin/io/junseok/todeveloperdo/auth/jwt/TokenProviderTest.kt @@ -7,7 +7,6 @@ import io.jsonwebtoken.SignatureAlgorithm import io.jsonwebtoken.security.Keys import io.junseok.todeveloperdo.auth.jwt.TokenProvider.Companion.AUTHORITIES_KEY import io.junseok.todeveloperdo.domains.member.persistence.repository.MemberRepository -import io.junseok.todeveloperdo.exception.ErrorCode import io.junseok.todeveloperdo.exception.ErrorCode.EXPIRED_JWT import io.junseok.todeveloperdo.exception.ToDeveloperDoException import io.junseok.todeveloperdo.oauth.apple.client.AppleClient @@ -197,7 +196,7 @@ class TokenProviderTest : FunSpec({ } test("getAuthentication()에서 권한 문자열에 빈 값이 포함되면 dropLastWhile이 실행된다") { - val authStringWithEmpty = "ROLE_USER," // 마지막이 빈 문자열이 되도록 + val authStringWithEmpty = "ROLE_USER," val token = Jwts.builder() .setSubject("testuser") .setIssuer("TDD") @@ -221,7 +220,6 @@ class TokenProviderTest : FunSpec({ val applePublicKeys = listOf(createApplePublicKey()) every { appleClient.getApplePublicKeys().keys } returns applePublicKeys - every { AppleJwtUtil.decodeAndVerify(any(), any()) } throws ExpiredJwtException( null, null, @@ -237,4 +235,22 @@ class TokenProviderTest : FunSpec({ } } + test("ACCESS 타입에서 ExpiredJwtException이 발생하면 else 분기가 실행된다") { + val expiredToken = Jwts.builder() + .setSubject("testuser") + .setIssuer("TDD") + .claim(AUTHORITIES_KEY, "ROLE_USER") + .setExpiration(Date(System.currentTimeMillis() - 10000)) + .signWith(tokenProvider.javaClass.getDeclaredField("key").apply { + isAccessible = true + }.get(tokenProvider) as Key, SignatureAlgorithm.HS512) + .compact() + + throwsWith({ + tokenProvider.validateAppleToken(expiredToken, "ACCESS") + }) { + it.errorCode shouldBe EXPIRED_JWT + } + } + }) diff --git a/src/test/kotlin/io/junseok/todeveloperdo/domains/member/service/serviceimpl/MemberReaderTest.kt b/src/test/kotlin/io/junseok/todeveloperdo/domains/member/service/serviceimpl/MemberReaderTest.kt index abd0268..5f853f0 100644 --- a/src/test/kotlin/io/junseok/todeveloperdo/domains/member/service/serviceimpl/MemberReaderTest.kt +++ b/src/test/kotlin/io/junseok/todeveloperdo/domains/member/service/serviceimpl/MemberReaderTest.kt @@ -137,7 +137,11 @@ class MemberReaderTest : BehaviorSpec({ } }) -fun createMember(id: Long, appleId: String, repo: String? = null) = Member( +fun createMember( + id: Long, + appleId: String, + repo: String? = null, + clientToken: String ?="Fcm") = Member( memberId = id, appleId = appleId, appleRefreshToken = "appleRefreshToken", @@ -147,7 +151,7 @@ fun createMember(id: Long, appleId: String, repo: String? = null) = Member( gitHubRepo = repo, avatarUrl = "avatar", gitHubUrl = "gitUrl", - clientToken = "Fcm", + clientToken = clientToken, authority = Authority.ROLE_USER ) diff --git a/src/test/kotlin/io/junseok/todeveloperdo/domains/todo/service/MemberTodoServiceTest.kt b/src/test/kotlin/io/junseok/todeveloperdo/domains/todo/service/MemberTodoServiceTest.kt index 025ceac..eafb2a3 100644 --- a/src/test/kotlin/io/junseok/todeveloperdo/domains/todo/service/MemberTodoServiceTest.kt +++ b/src/test/kotlin/io/junseok/todeveloperdo/domains/todo/service/MemberTodoServiceTest.kt @@ -12,9 +12,11 @@ import io.junseok.todeveloperdo.presentation.membertodolist.createDateRequest import io.junseok.todeveloperdo.presentation.membertodolist.createTodoCountRequest import io.junseok.todeveloperdo.presentation.membertodolist.createTodoCountResponse import io.junseok.todeveloperdo.presentation.membertodolist.createTodoRequest +import io.junseok.todeveloperdo.util.StubDateProvider import io.kotest.core.spec.style.BehaviorSpec import io.kotest.matchers.shouldBe import io.mockk.* +import io.mockk.impl.stub.Stub import java.time.LocalDate import java.util.concurrent.CompletableFuture @@ -27,6 +29,8 @@ class MemberTodoServiceTest : BehaviorSpec({ val gitIssueService = mockk() val todoValidator = mockk() val gitIssueUpdater = mockk() + val today = LocalDate.of(2025, 5, 13) + val timeProvider = StubDateProvider(today) val memberTodoService = MemberTodoService( todoReader, memberReader, @@ -35,9 +39,9 @@ class MemberTodoServiceTest : BehaviorSpec({ todoUpdater, gitIssueService, todoValidator, - gitIssueUpdater + gitIssueUpdater, + timeProvider ) - val today = LocalDate.of(2025, 5, 13) Given("TodoList를 생성할 때") { val todoRequests = listOf(createTodoRequest()) @@ -48,7 +52,7 @@ class MemberTodoServiceTest : BehaviorSpec({ every { memberReader.getMember(any()) } returns member every { - todoCreator.generatorTodo(todoRequests[0], any(), isNull()) + todoCreator.generatorTodo(todoRequests[0], member, eq(123)) } returns memberTodoList every { todoSaver.saveTodoList(listOf(memberTodoList)) } returns 1L every { gitIssueService.saveGitIssue(member, any()) } just runs @@ -64,6 +68,84 @@ class MemberTodoServiceTest : BehaviorSpec({ } } + Given("issueEventRequest가 null일 때") { + val todoRequests = listOf(createTodoRequest()) + val member = createMember(1, "appleId") + val memberTodoList = createMemberTodoList(1, today, TodoStatus.PROCEED, member) + + every { memberReader.getMember(any()) } returns member + every { + todoCreator.generatorTodo(todoRequests[0], any(), isNull()) + } returns memberTodoList + every { todoSaver.saveTodoList(listOf(memberTodoList)) } returns 1L + every { gitIssueService.saveGitIssue(member, any()) } just runs + + When("createTodoList()를 호출하면") { + val result = memberTodoService.createTodoList( + todoRequests, + member.gitHubUsername!!, + null + ) + Then("정상적으로 커리큘럼이 생성되어야 한다.") { + result shouldBe 1L + } + } + } + + Given("issueNumber가 null일 때") { + val todoRequests = listOf(createTodoRequest()) + val member = createMember(1, "appleId") + val memberTodoList = createMemberTodoList(1, today, TodoStatus.PROCEED, member) + val issueFuture = CompletableFuture().apply { complete(null) } + val issueEventRequest = IssueEventRequest(member, todoRequests[0], issueFuture) + + every { memberReader.getMember(any()) } returns member + every { + todoCreator.generatorTodo(todoRequests[0], any(), isNull()) + } returns memberTodoList + every { todoSaver.saveTodoList(listOf(memberTodoList)) } returns 1L + every { gitIssueService.saveGitIssue(member, any()) } just runs + + When("createTodoList()를 호출하면") { + val result = memberTodoService.createTodoList( + todoRequests, + member.gitHubUsername!!, + issueEventRequest + ) + Then("정상적으로 커리큘럼이 생성되어야 한다.") { + result shouldBe 1L + } + } + } + + Given("issueNumber가 있지만 deadline이 오늘이 아닐 때") { + val wrongDate = timeProvider.nowDate().plusDays(1) + val todoRequests = listOf(createTodoRequest(wrongDate)) + val member = createMember(1, "appleId") + val memberTodoList = createMemberTodoList(1, wrongDate, TodoStatus.PROCEED, member) + val issueFuture = CompletableFuture().apply { complete(123) } + val issueEventRequest = IssueEventRequest(member, todoRequests[0], issueFuture) + + every { memberReader.getMember(any()) } returns member + every { + todoCreator.generatorTodo(todoRequests[0], any(), isNull()) + } returns memberTodoList + every { todoSaver.saveTodoList(listOf(memberTodoList)) } returns 1L + every { gitIssueService.saveGitIssue(member, any()) } just runs + + When("createTodoList()를 호출하면") { + val result = memberTodoService.createTodoList( + todoRequests, + member.gitHubUsername!!, + issueEventRequest + ) + Then("정상적으로 커리큘럼이 생성되어야 한다.") { + result shouldBe 1L + } + } + } + + Given("할 일을 조회할 때") { val todoDateRequest = createDateRequest() val today = todoDateRequest.deadline diff --git a/src/test/kotlin/io/junseok/todeveloperdo/oauth/git/service/GitHubOAuthServiceTest.kt b/src/test/kotlin/io/junseok/todeveloperdo/oauth/git/service/GitHubOAuthServiceTest.kt index 6e0e58d..a015a03 100644 --- a/src/test/kotlin/io/junseok/todeveloperdo/oauth/git/service/GitHubOAuthServiceTest.kt +++ b/src/test/kotlin/io/junseok/todeveloperdo/oauth/git/service/GitHubOAuthServiceTest.kt @@ -98,10 +98,32 @@ class GitHubOAuthServiceTest : FunSpec({ gitHubOAuthService.extractAccessToken(response) }, { - ex -> ex.message shouldBe "Access token not found in response: $response" + it.message shouldBe "Access token not found or malformed: $response" } ) } + + test("access_token 응답이 key=value 형식이 아니면 예외가 발생해야 한다") { + val malformed = "access_token" + + throwsWith({ + gitHubOAuthService.extractAccessToken(malformed) + }, { + it.message shouldBe "Access token not found or malformed: $malformed" + }) + } + + test("access_token= 형식이나 값이 없으면 takeIf에서 null이 되어 예외 발생") { + val response = "access_token=&scope=repo" + + throwsWith({ + gitHubOAuthService.extractAccessToken(response) + }) { + it.message shouldBe "Access token not found or malformed: $response" + } + } + + }) fun createGitTokenResponse() = TokenResponse(token = "Bearer abc123") diff --git a/src/test/kotlin/io/junseok/todeveloperdo/oauth/git/service/reposerviceimpl/WebHookProcessorTest.kt b/src/test/kotlin/io/junseok/todeveloperdo/oauth/git/service/reposerviceimpl/WebHookProcessorTest.kt index 814d067..fd5eb94 100644 --- a/src/test/kotlin/io/junseok/todeveloperdo/oauth/git/service/reposerviceimpl/WebHookProcessorTest.kt +++ b/src/test/kotlin/io/junseok/todeveloperdo/oauth/git/service/reposerviceimpl/WebHookProcessorTest.kt @@ -23,7 +23,6 @@ class WebHookProcessorTest : FunSpec({ clearMocks(payloadCreator, memberReader, memberUpdater) } - test("repository renamed 이벤트를 처리해야 한다") { val member = createMember(1, "appleId", "repo") val payload = mapOf( @@ -81,7 +80,8 @@ class WebHookProcessorTest : FunSpec({ verify(exactly = 0) { memberReader.findByGitUserName(member.gitHubUsername!!) } verify(exactly = 0) { memberUpdater.updateMemberRepo(payloadResponse.newRepoName, member) } } - test("repositoryㅇ 외의 이벤트는 DELETE_REPO_ACTION 작업을 하지 않는다") { + + test("repository 외의 이벤트는 DELETE_REPO_ACTION 작업을 하지 않는다") { val member = createMember(1, "appleId", "repo") val payload = mapOf( "action" to DELETE_REPO_ACTION, @@ -97,6 +97,7 @@ class WebHookProcessorTest : FunSpec({ verify(exactly = 0) { memberReader.findByGitUserName(member.gitHubUsername!!) } verify(exactly = 0) { memberUpdater.removeMemberRepo(member) } } + }) fun createPayloadResponse() = PayloadResponse( diff --git a/src/test/kotlin/io/junseok/todeveloperdo/presentation/membertodolist/MemberTodoControllerTest.kt b/src/test/kotlin/io/junseok/todeveloperdo/presentation/membertodolist/MemberTodoControllerTest.kt index 6073601..ee87129 100644 --- a/src/test/kotlin/io/junseok/todeveloperdo/presentation/membertodolist/MemberTodoControllerTest.kt +++ b/src/test/kotlin/io/junseok/todeveloperdo/presentation/membertodolist/MemberTodoControllerTest.kt @@ -235,14 +235,13 @@ class MemberTodoControllerTest : FunSpec({ const val TODO_PATH = "/api/todo/" } } - -fun createTodoRequest(): TodoRequest { - val today = LocalDate.of(2025, 5, 13) +val today = LocalDate.of(2025,5,13) +fun createTodoRequest(deadline: LocalDate = today): TodoRequest { return TodoRequest( content = "content", memo = "memo", tag = "tag", - deadline = today + deadline = deadline ) } diff --git a/src/test/kotlin/io/junseok/todeveloperdo/scheduler/fcm/AllTodosCompletedStrategyTest.kt b/src/test/kotlin/io/junseok/todeveloperdo/scheduler/fcm/AllTodosCompletedStrategyTest.kt index 7187611..7ceea6c 100644 --- a/src/test/kotlin/io/junseok/todeveloperdo/scheduler/fcm/AllTodosCompletedStrategyTest.kt +++ b/src/test/kotlin/io/junseok/todeveloperdo/scheduler/fcm/AllTodosCompletedStrategyTest.kt @@ -1,8 +1,10 @@ package io.junseok.todeveloperdo.scheduler.fcm +import io.junseok.todeveloperdo.domains.member.service.serviceimpl.createMember import io.junseok.todeveloperdo.domains.todo.persistence.entity.TodoStatus import io.junseok.todeveloperdo.domains.todo.service.serviceimpl.SetUpData import io.junseok.todeveloperdo.domains.todo.service.serviceimpl.TodoReader +import io.junseok.todeveloperdo.domains.todo.service.serviceimpl.createMemberTodoList import io.junseok.todeveloperdo.util.StubDateProvider import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe @@ -14,7 +16,7 @@ class AllTodosCompletedStrategyTest : FunSpec({ val todoReader = mockk() val today = LocalDate.of(2025, 5, 6) val stubDate = StubDateProvider(today) - val strategy = AllTodosCompletedStrategy(todoReader,stubDate) + val strategy = AllTodosCompletedStrategy(todoReader, stubDate) test("FCM 요청 리스트를 필터링하여 반환해야 한다") { @@ -33,4 +35,29 @@ class AllTodosCompletedStrategyTest : FunSpec({ test("알림 타입이 ALL_TODOS_COMPLETED이어야 한다") { strategy.getNotificationType() shouldBe NotificationType.ALL_TODOS_COMPLETED } + + test("clientToken이 빈 값일 경우 필터링 되어야 한다") { + val member1 = createMember(1L, "appleId", "repo", "FcmToken") + val member2 = createMember(2L, "appleId", "repo", "") + val member3 = createMember(3L, "appleId", "repo", " ") + val todoLists = listOf( + createMemberTodoList(1L, today.minusWeeks(1), TodoStatus.DONE, member1), + createMemberTodoList(2L, today.minusWeeks(2), TodoStatus.DONE, member2), + createMemberTodoList(3L, today.minusWeeks(3), TodoStatus.DONE, member3) + ) + + every { + todoReader.findTodoListByTodoStatus( + today, + TodoStatus.DONE + ) + } returns todoLists + + val result = strategy.getFcmRequests() + + result.size shouldBe 1 + result.first().clientToken shouldBe "FcmToken" + } + }) + diff --git a/src/test/kotlin/io/junseok/todeveloperdo/scheduler/fcm/ProceedingTodoReminderStrategyTest.kt b/src/test/kotlin/io/junseok/todeveloperdo/scheduler/fcm/ProceedingTodoReminderStrategyTest.kt index 434579c..36a164b 100644 --- a/src/test/kotlin/io/junseok/todeveloperdo/scheduler/fcm/ProceedingTodoReminderStrategyTest.kt +++ b/src/test/kotlin/io/junseok/todeveloperdo/scheduler/fcm/ProceedingTodoReminderStrategyTest.kt @@ -1,8 +1,10 @@ package io.junseok.todeveloperdo.scheduler.fcm +import io.junseok.todeveloperdo.domains.member.service.serviceimpl.createMember import io.junseok.todeveloperdo.domains.todo.persistence.entity.TodoStatus import io.junseok.todeveloperdo.domains.todo.service.serviceimpl.SetUpData import io.junseok.todeveloperdo.domains.todo.service.serviceimpl.TodoReader +import io.junseok.todeveloperdo.domains.todo.service.serviceimpl.createMemberTodoList import io.junseok.todeveloperdo.util.StubDateProvider import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe @@ -32,4 +34,26 @@ class ProceedingTodoReminderStrategyTest : FunSpec({ strategy.getNotificationType() shouldBe NotificationType.DAILY_TODO_REMINDER } + test("clientToken이 빈 값일 경우 필터링 되어야 한다") { + val member1 = createMember(1L, "appleId", "repo", "FcmToken") + val member2 = createMember(2L, "appleId", "repo", "") + val member3 = createMember(3L, "appleId", "repo", " ") + val todoLists = listOf( + createMemberTodoList(1L, today.minusWeeks(1), TodoStatus.PROCEED, member1), + createMemberTodoList(2L, today.minusWeeks(2), TodoStatus.PROCEED, member2), + createMemberTodoList(3L, today.minusWeeks(3), TodoStatus.PROCEED, member3) + ) + + every { + todoReader.findTodoListByTodoStatus( + today, + TodoStatus.PROCEED + ) + } returns todoLists + + val result = strategy.getFcmRequests() + + result.size shouldBe 1 + result.first().clientToken shouldBe "FcmToken" + } })