Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -132,14 +132,19 @@ tasks.named<JacocoReport>("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*"
)
}
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -29,6 +29,7 @@ class MemberTodoService(
private val gitIssueService: GitIssueService,
private val todoValidator: TodoValidator,
private val gitIssueUpdater: GitIssueUpdater,
private val timeProvider: TimeProvider
) {
@CreateEvent
@ReadMeCreate
Expand All @@ -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<TodoResponse> {
val member = memberReader.getMember(username)
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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")) {
Expand All @@ -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<String, Any> {
val mapper = jacksonObjectMapper()
return mapper.readValue(response, object : TypeReference<Map<String, Any>>() {})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<FcmRequest> =
todoReader.findTodoListByTodoStatus(
timeProvider.nowDate(),
Expand Down
4 changes: 4 additions & 0 deletions src/main/kotlin/io/junseok/todeveloperdo/util/Elvis.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package io.junseok.todeveloperdo.util

inline fun <T, R> T?.runIfNotNull(block: (T) -> R?): R? =
if (this != null) block(this) else null
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,14 @@ class ExceptionHandlerFilterTest : FunSpec({
}
}

test("예외가 발생하지 않으면 필터 체인이 정상적으로 실행된다") {
val request = MockHttpServletRequest()
val response = MockHttpServletResponse()
val chain = mockk<FilterChain>()

every { chain.doFilter(any(), any()) } just runs

filter.doFilter(request, response, chain)
}

})
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand All @@ -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,
Expand All @@ -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<ToDeveloperDoException>({
tokenProvider.validateAppleToken(expiredToken, "ACCESS")
}) {
it.errorCode shouldBe EXPIRED_JWT
}
}

})
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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
)

Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -27,6 +29,8 @@ class MemberTodoServiceTest : BehaviorSpec({
val gitIssueService = mockk<GitIssueService>()
val todoValidator = mockk<TodoValidator>()
val gitIssueUpdater = mockk<GitIssueUpdater>()
val today = LocalDate.of(2025, 5, 13)
val timeProvider = StubDateProvider(today)
val memberTodoService = MemberTodoService(
todoReader,
memberReader,
Expand All @@ -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())
Expand All @@ -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
Expand All @@ -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<Int>().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<Int>().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
Expand Down
Loading
Loading