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
21 changes: 20 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,27 @@ tasks.named<JacocoReport>("jacocoTestReport") {

reports {
xml.required.set(true)
html.required.set(false)
html.required.set(true)
}

classDirectories.setFrom(
files(classDirectories.files.map {
fileTree(it) {
exclude(
"**/persistence/entity/**",
"**/presentation/**/dto/**",
"**/oauth/apple/dto/**",
"**/oauth/git/dto/**",
"**/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"
)
}
})
)
}

openapi3 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,32 +41,33 @@ class UpdateAspect(
val member = memberReader.getMember(username)
val findTodoList = todoReader.findTodoList(todoListId)
val gitIssue = gitIssueReader.findGitIssueByTodoList(findTodoList)
val issueNumber = findTodoList.issueNumber

// 수정했는데 다른 날에서 오늘인 경우 -> create, issueNumber가 null아 아닌 경우 open
if (timeProvider.nowDate() == gitIssue.deadline && findTodoList.issueNumber == null) {
val issueNumber = (
eventProcessor.createIssue(member, todoRequest).issueNumber.get()
?: throw ToDeveloperDoException { ErrorCode.FAILED_TO_GENERATE_ISSUE }
)
todoUpdater.modifyIssueNumber(issueNumber, findTodoList)
} else if (timeProvider.nowDate() == gitIssue.deadline && findTodoList.issueNumber != null) {
issueEventProcessor.close(member, findTodoList.issueNumber!!, ISSUE_OPEN)
if (issueNumber == null) {
val issueEventRequest = eventProcessor.createIssue(member, todoRequest)
val createdIssueNumber = issueEventRequest.issueNumber.get()
?: throw ToDeveloperDoException{ErrorCode.FAILED_TO_GENERATE_ISSUE}

todoUpdater.modifyIssueNumber(createdIssueNumber, findTodoList)
}

if (issueNumber != null && timeProvider.nowDate() == gitIssue.deadline) {
issueEventProcessor.close(member, issueNumber, ISSUE_OPEN)
}

// 요일은 수정안한경우(오늘이거나 다른 날 인경우)
findTodoList.issueNumber?.let {
if (issueNumber != null && timeProvider.nowDate() != gitIssue.deadline) {
issueEventProcessor.close(member, issueNumber, ISSUE_CLOSED)
}

// 이슈가 존재하면 업데이트
if (issueNumber != null) {
eventProcessor.updateIssueWithReadMe(
member,
findTodoList.issueNumber!!,
issueNumber,
todoRequest.toTodoCreate(member)
)
}

// 오늘에서 다른 날로 수정한 경우 -> closed
if (timeProvider.nowDate() != gitIssue.deadline && findTodoList.issueNumber != null) {
issueEventProcessor.close(member, findTodoList.issueNumber!!, ISSUE_CLOSED)
}

readMeEventProcessor.create(member)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@ class TokenProvider(
log.error(e.message)
false
}

}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,4 @@ class Content(
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "curriculum_id")
val curriculum: Curriculum
) {
}
)
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import org.springframework.transaction.annotation.Transactional
import java.time.LocalDate

@Component
class GitIssueReader(private val gitIssueRepository: GitIssueRepository) {
class GitIssueReader(
private val gitIssueRepository: GitIssueRepository,
) {
@Transactional
fun findGitIssueList(today: LocalDate = LocalDate.now()): List<TodoCreate> =
gitIssueRepository.findByDeadlineList(today)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package io.junseok.todeveloperdo.oauth.git.client

import io.junseok.todeveloperdo.client.openai.config.OpenChatAiConfig.Companion.AUTHORIZATION
import io.junseok.todeveloperdo.oauth.git.config.GitHubRepoConfig
import io.junseok.todeveloperdo.oauth.git.domain.GItHubRepo
import io.junseok.todeveloperdo.oauth.git.dto.request.GItHubRepo
import io.junseok.todeveloperdo.oauth.git.dto.request.WebhookRequest
import io.junseok.todeveloperdo.oauth.git.dto.response.GitHubResponse
import io.junseok.todeveloperdo.oauth.git.dto.response.WebhookResponse
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.junseok.todeveloperdo.oauth.git.domain
package io.junseok.todeveloperdo.oauth.git.dto.request

import com.fasterxml.jackson.annotation.JsonProperty

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package io.junseok.todeveloperdo.oauth.git.dto.request

import io.junseok.todeveloperdo.oauth.git.domain.GItHubRepo

fun GitHubRequest.toGithubRepo() =
GItHubRepo(
name = this.repoName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,4 +171,55 @@ class UpdateAspectTest : FunSpec({
readMeEventProcessor.create(member)
}
}

test("마감일이 오늘이고 issueNumber가 존재할 경우 ISSUE_OPEN으로 이슈를 종료한다") {
val todoListId = 1L
val username = "testUser"
val issueNumber = 1
val member = createMember(1, "token", "repo")
val todoRequest = createTodoRequest()
val todoList = createMemberTodoList(1, today, TodoStatus.PROCEED, member, issueNumber)
val gitIssue = createGitIssue(1, today, todoList) // == today

every { memberReader.getMember(username) } returns member
every { todoReader.findTodoList(todoListId) } returns todoList
every { gitIssueReader.findGitIssueByTodoList(todoList) } returns gitIssue
every { issueEventProcessor.close(member, issueNumber, ISSUE_OPEN) } just runs

val joinPoint = mockk<JoinPoint> {
every { args } returns arrayOf(todoListId, todoRequest, username)
}

updateAspect.update(joinPoint)

verify {
issueEventProcessor.close(member, issueNumber, ISSUE_OPEN)
}
}

test("마감일이 오늘이 아니고 issueNumber가 존재할 경우 ISSUE_CLOSED로 이슈를 종료한다") {
val todoListId = 1L
val username = "testUser"
val issueNumber = 1
val member = createMember(1, "token", "repo")
val todoRequest = createTodoRequest()
val todoList = createMemberTodoList(1, today, TodoStatus.PROCEED, member, issueNumber)
val gitIssue = createGitIssue(1, today.plusDays(1), todoList) // != today

every { memberReader.getMember(username) } returns member
every { todoReader.findTodoList(todoListId) } returns todoList
every { gitIssueReader.findGitIssueByTodoList(todoList) } returns gitIssue
every { issueEventProcessor.close(member, issueNumber, ISSUE_CLOSED) } just runs

val joinPoint = mockk<JoinPoint> {
every { args } returns arrayOf(todoListId, todoRequest, username)
}

updateAspect.update(joinPoint)

verify {
issueEventProcessor.close(member, issueNumber, ISSUE_CLOSED)
}
}

})
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,57 @@ class GitHubRepoVerificationFilterTest : FunSpec({

verify(exactly = 0) { memberValidator.isExistRepo(any()) }
}

test("principal이 UserDetails 타입이 아니면 memberValidator는 호출되지 않는다") {
val nonUserDetailsPrincipal = mockk<Any>()

every { authentication.isAuthenticated } returns true
every { authentication.principal } returns nonUserDetailsPrincipal

SecurityContextHolder.getContext().authentication = authentication

val request = MockHttpServletRequest("GET", "/api/github/commit")
val response = MockHttpServletResponse()
val filterChain = mockk<FilterChain>(relaxed = true)

gitHubRepoVerificationFilter.doFilter(request, response, filterChain)

verify(exactly = 0) { memberValidator.isExistRepo(any()) }
}

test("요청 URI가 /api/github/create/repo 인 경우, memberValidator는 호출되지 않는다") {
val request = MockHttpServletRequest("GET", "/api/github/create/repo")
val response = MockHttpServletResponse()
val filterChain = mockk<FilterChain>(relaxed = true)

gitHubRepoVerificationFilter.doFilter(request, response, filterChain)

verify(exactly = 0) { memberValidator.isExistRepo(any()) }
}

test("authentication이 null이면 memberValidator는 호출되지 않는다") {
SecurityContextHolder.getContext().authentication = null

val request = MockHttpServletRequest("GET", "/api/github/commit")
val response = MockHttpServletResponse()
val filterChain = mockk<FilterChain>(relaxed = true)

gitHubRepoVerificationFilter.doFilter(request, response, filterChain)

verify(exactly = 0) { memberValidator.isExistRepo(any()) }
}

test("authentication이 존재하지만 인증되지 않았으면 memberValidator는 호출되지 않는다") {
every { authentication.isAuthenticated } returns false
SecurityContextHolder.getContext().authentication = authentication

val request = MockHttpServletRequest("GET", "/api/github/commit")
val response = MockHttpServletResponse()
val filterChain = mockk<FilterChain>(relaxed = true)

gitHubRepoVerificationFilter.doFilter(request, response, filterChain)

verify(exactly = 0) { memberValidator.isExistRepo(any()) }
}

})
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
package io.junseok.todeveloperdo.auth.jwt

import io.kotest.assertions.throwables.shouldNotThrow
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.mockk.mockk
import org.springframework.mock.web.MockHttpServletRequest
import org.springframework.mock.web.MockHttpServletResponse
import io.mockk.verify
import org.springframework.security.core.AuthenticationException
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse

class JwtAuthenticationEntryPointTest : FunSpec({
test("commence should respond with 401 Unauthorized") {
val entryPoint = JwtAuthenticationEntryPoint()
val request = MockHttpServletRequest()
val response = MockHttpServletResponse()
test("commence should call sendError(401)") {
val response = mockk<HttpServletResponse>(relaxed = true)
val request = mockk<HttpServletRequest>()
val authException = mockk<AuthenticationException>()
val entryPoint = JwtAuthenticationEntryPoint()

entryPoint.commence(request, response, authException)

response.status shouldBe HttpServletResponse.SC_UNAUTHORIZED
verify { response.sendError(HttpServletResponse.SC_UNAUTHORIZED) }
}

test("commence should not throw when response is null") {
val request = mockk<HttpServletRequest>()
val authException = mockk<AuthenticationException>()
val entryPoint = JwtAuthenticationEntryPoint()

shouldNotThrow<Exception> {
entryPoint.commence(request, null, authException)
}
}
})
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ 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 All @@ -22,6 +23,7 @@ import io.mockk.*
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.core.userdetails.User
import java.security.Key
import java.util.*

class TokenProviderTest : FunSpec({
Expand Down Expand Up @@ -86,22 +88,28 @@ class TokenProviderTest : FunSpec({
result.credentials shouldBe token
}

test("type이 REFRESH이고, 토큰이 만료되지 않았다면 유효성 검사를 진행한다.") {
test("type이 REFRESH이고, 토큰이 정상적이면 AppleJwtUtil.decodeAndVerify가 실행된다") {
val authentication = UsernamePasswordAuthenticationToken(
"testuser", "password",
"testuser",
"password",
listOf(SimpleGrantedAuthority("ROLE_USER"))
)
val token = tokenProvider.createToken(authentication)

val applePublicKeys = listOf(createApplePublicKey())
val jwt = mockk<DecodedJWT>()
mockkObject(AppleJwtUtil)

mockkObject(AppleJwtUtil)
every { appleClient.getApplePublicKeys().keys } returns applePublicKeys
every { AppleJwtUtil.decodeAndVerify(any(), any()) } returns jwt
every { AppleJwtUtil.decodeAndVerify(token, applePublicKeys) } returns jwt

val result = tokenProvider.validateAppleToken(token, "REFRESH")

result shouldBe true

verify(exactly = 1) {
AppleJwtUtil.decodeAndVerify(token, applePublicKeys)
}
}

test("type이 ACCESS이고, 토큰이 유효하면 true를 반환한다") {
Expand All @@ -127,7 +135,7 @@ class TokenProviderTest : FunSpec({
val invalidToken = "this.is.invalid"
val result = tokenProvider.validateAppleToken(invalidToken, "REFRESH")

result shouldBe true
result shouldBe false
}

test("type이 UNKNOWN이면 ACCESS와 동일하게 else 분기를 탄다") {
Expand Down Expand Up @@ -188,4 +196,45 @@ class TokenProviderTest : FunSpec({
}
}

test("getAuthentication()에서 권한 문자열에 빈 값이 포함되면 dropLastWhile이 실행된다") {
val authStringWithEmpty = "ROLE_USER," // 마지막이 빈 문자열이 되도록
val token = Jwts.builder()
.setSubject("testuser")
.setIssuer("TDD")
.claim(AUTHORITIES_KEY, authStringWithEmpty)
.signWith(tokenProvider.javaClass.getDeclaredField("key").apply {
isAccessible = true
}.get(tokenProvider) as Key, SignatureAlgorithm.HS512)
.setExpiration(Date(System.currentTimeMillis() + 10000))
.compact()

val result = tokenProvider.getAuthentication(token)

result shouldBe instanceOf<UsernamePasswordAuthenticationToken>()
result.authorities.map { it.authority } shouldContainExactly listOf("ROLE_USER")
}

test("REFRESH 타입이고 만료된 토큰이 DB에 존재하지 않으면 로그만 남기고 예외를 던진다") {
val expiredToken = "expired.invalid.token"

mockkObject(AppleJwtUtil)
val applePublicKeys = listOf(createApplePublicKey())

every { appleClient.getApplePublicKeys().keys } returns applePublicKeys

every { AppleJwtUtil.decodeAndVerify(any(), any()) } throws ExpiredJwtException(
null,
null,
"expired"
)

every { memberRepository.existsByAppleRefreshToken(expiredToken) } returns false

throwsWith<ToDeveloperDoException>({
tokenProvider.validateAppleToken(expiredToken, "REFRESH")
}) { ex ->
ex.errorCode shouldBe EXPIRED_JWT
}
}

})
Loading
Loading