diff --git a/.gradle/8.4/checksums/checksums.lock b/.gradle/8.4/checksums/checksums.lock index a40b6ec..247d63e 100644 Binary files a/.gradle/8.4/checksums/checksums.lock and b/.gradle/8.4/checksums/checksums.lock differ diff --git a/.gradle/8.4/checksums/md5-checksums.bin b/.gradle/8.4/checksums/md5-checksums.bin index c4cd60f..28773cd 100644 Binary files a/.gradle/8.4/checksums/md5-checksums.bin and b/.gradle/8.4/checksums/md5-checksums.bin differ diff --git a/.gradle/8.4/checksums/sha1-checksums.bin b/.gradle/8.4/checksums/sha1-checksums.bin index d218812..c598b03 100644 Binary files a/.gradle/8.4/checksums/sha1-checksums.bin and b/.gradle/8.4/checksums/sha1-checksums.bin differ diff --git a/.gradle/8.4/dependencies-accessors/dependencies-accessors.lock b/.gradle/8.4/dependencies-accessors/dependencies-accessors.lock index d286ed7..c15a074 100644 Binary files a/.gradle/8.4/dependencies-accessors/dependencies-accessors.lock and b/.gradle/8.4/dependencies-accessors/dependencies-accessors.lock differ diff --git a/.gradle/8.4/executionHistory/executionHistory.bin b/.gradle/8.4/executionHistory/executionHistory.bin index 13b10dd..c53b1e1 100644 Binary files a/.gradle/8.4/executionHistory/executionHistory.bin and b/.gradle/8.4/executionHistory/executionHistory.bin differ diff --git a/.gradle/8.4/executionHistory/executionHistory.lock b/.gradle/8.4/executionHistory/executionHistory.lock index 2373254..82327ff 100644 Binary files a/.gradle/8.4/executionHistory/executionHistory.lock and b/.gradle/8.4/executionHistory/executionHistory.lock differ diff --git a/.gradle/8.4/fileHashes/fileHashes.bin b/.gradle/8.4/fileHashes/fileHashes.bin index 0528af3..f22e444 100644 Binary files a/.gradle/8.4/fileHashes/fileHashes.bin and b/.gradle/8.4/fileHashes/fileHashes.bin differ diff --git a/.gradle/8.4/fileHashes/fileHashes.lock b/.gradle/8.4/fileHashes/fileHashes.lock index 0ec05f6..f71bae1 100644 Binary files a/.gradle/8.4/fileHashes/fileHashes.lock and b/.gradle/8.4/fileHashes/fileHashes.lock differ diff --git a/.gradle/8.4/fileHashes/resourceHashesCache.bin b/.gradle/8.4/fileHashes/resourceHashesCache.bin index e55db9c..61d8faf 100644 Binary files a/.gradle/8.4/fileHashes/resourceHashesCache.bin and b/.gradle/8.4/fileHashes/resourceHashesCache.bin differ diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock index 701d69c..b00ed9c 100644 Binary files a/.gradle/buildOutputCleanup/buildOutputCleanup.lock and b/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/.gradle/buildOutputCleanup/outputFiles.bin b/.gradle/buildOutputCleanup/outputFiles.bin index 06e466b..1cc9f05 100644 Binary files a/.gradle/buildOutputCleanup/outputFiles.bin and b/.gradle/buildOutputCleanup/outputFiles.bin differ diff --git a/.gradle/file-system.probe b/.gradle/file-system.probe index 88c418f..66e97ba 100644 Binary files a/.gradle/file-system.probe and b/.gradle/file-system.probe differ diff --git a/.idea/compiler.xml b/.idea/compiler.xml index a49a46e..523166c 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -8,11 +8,16 @@ + + + + + - + diff --git a/build.gradle b/build.gradle index 04b2d1f..30fb9a6 100644 --- a/build.gradle +++ b/build.gradle @@ -18,21 +18,47 @@ subprojects { repositories { mavenCentral() } - - dependencies { + dependencies { + // Spring Boot Starters implementation 'org.springframework.boot:spring-boot-starter' + implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-aop' // AOP: 로깅 처리 자동화를 위해 사용 + + // Utils + implementation 'com.google.code.gson:gson' // Lombok compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' - - // Test + + // Test Dependencies testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.junit.jupiter:junit-jupiter-api' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' + testImplementation 'org.mockito:mockito-core' + testImplementation 'org.mockito:mockito-junit-jupiter' + + // Lombok for Tests + testCompileOnly 'org.projectlombok:lombok' + testAnnotationProcessor 'org.projectlombok:lombok' } - + + // Test Configuration + sourceSets { + test { + java { + srcDirs = ['src/test/java'] + } + } + } + test { useJUnitPlatform() + include '**/*Test.class' + testLogging { + events "passed", "skipped", "failed" + } } } @@ -50,10 +76,15 @@ configure(subprojects.findAll { !it.name.endsWith('-biz') && it.name != 'common' implementation 'io.jsonwebtoken:jjwt-api:0.11.5' runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' - // AOP: 로깅 처리 자동화를 위해 사용 - implementation 'org.springframework.boot:spring-boot-starter-aop' + // Swagger implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0' + + // Test Containers + testImplementation 'org.testcontainers:postgresql' + + // WebFlux for WebMvc Testing + implementation 'org.springframework.boot:spring-boot-starter-webflux' } } diff --git a/common/build/tmp/compileJava/previous-compilation-data.bin b/common/build/tmp/compileJava/previous-compilation-data.bin index e7f40be..d14d9ec 100644 Binary files a/common/build/tmp/compileJava/previous-compilation-data.bin and b/common/build/tmp/compileJava/previous-compilation-data.bin differ diff --git a/design/Common 클래스설계서 b/design/Common 클래스설계서 new file mode 100644 index 0000000..428770c --- /dev/null +++ b/design/Common 클래스설계서 @@ -0,0 +1,82 @@ +!theme mono +title Common Module - Class Diagram + +package "com.unicorn.lifesub.common" { + package "dto" { + class ApiResponse { + -status: int + -message: String + -data: T + -timestamp: LocalDateTime + +ApiResponse(status: int, message: String, data: T) + +{static} success(data: T): ApiResponse + +{static} error(errorCode: ErrorCode): ApiResponse + } + + class JwtTokenDTO { + -accessToken: String + -refreshToken: String + } + } + + package "exception" { + class BusinessException { + -errorCode: ErrorCode + +BusinessException(errorCode: ErrorCode) + +getErrorCode(): ErrorCode + } + + class InfraException { + -errorCode: ErrorCode + +InfraException(errorCode: ErrorCode) + +getErrorCode(): ErrorCode + } + + enum ErrorCode { + INVALID_INPUT_VALUE(100, "Invalid input value") + INTERNAL_SERVER_ERROR(110, "Internal server error") + MEMBER_NOT_FOUND(200, "Member not found") + INVALID_CREDENTIALS(210, "Invalid credentials") + TOKEN_EXPIRED(220, "Token expired") + SIGNATURE_VERIFICATION_EXCEPTION(230, "서명 검증 실패") + ALGORITHM_MISMATCH_EXCEPTION(240, "알고리즘 불일치") + INVALID_CLAIM_EXCEPTION(250, "유효하지 않은 클레임") + SUBSCRIPTION_NOT_FOUND(300, "Subscription not found") + ALREADY_SUBSCRIBED(310, "Already subscribed to this service") + NO_SPENDING_DATA(400, "No spending data found") + NO_RECOMMENDATION_DATA(410, "추천 구독 카테고리 없음") + UNDIFINED_ERROR(0, "정의되지 않은 에러") + -- + -status: int + -message: String + } + } + + package "entity" { + abstract class BaseTimeEntity { + -createdAt: LocalDateTime + -updatedAt: LocalDateTime + } + } + + package "aop" { + class LoggingAspect { + -gson: Gson + +logMethodStart(joinPoint: JoinPoint): void + +logMethodEnd(joinPoint: JoinPoint, result: Object): void + +logMethodException(joinPoint: JoinPoint, exception: Exception): void + -getArgumentString(args: Object[]): String + -getResultString(result: Object): String + } + } + + package "config" { + class JpaConfig { + } + } +} + +' Relationships +BusinessException --> ErrorCode +InfraException --> ErrorCode +LoggingAspect ..> ApiResponse : uses \ No newline at end of file diff --git a/member/build/classes/java/main/com/unicorn/lifesub/member/dto/LoginRequest.class b/member/build/classes/java/main/com/unicorn/lifesub/member/dto/LoginRequest.class index e9f308a..2c29c5e 100644 Binary files a/member/build/classes/java/main/com/unicorn/lifesub/member/dto/LoginRequest.class and b/member/build/classes/java/main/com/unicorn/lifesub/member/dto/LoginRequest.class differ diff --git a/member/build/classes/java/main/com/unicorn/lifesub/member/dto/LogoutRequest.class b/member/build/classes/java/main/com/unicorn/lifesub/member/dto/LogoutRequest.class index 25ea6ff..ff852b9 100644 Binary files a/member/build/classes/java/main/com/unicorn/lifesub/member/dto/LogoutRequest.class and b/member/build/classes/java/main/com/unicorn/lifesub/member/dto/LogoutRequest.class differ diff --git a/member/build/classes/java/test/com/unicorn/lifesub/member/test/unit/config/jwt/CustomUserDetailsServiceTest.class b/member/build/classes/java/test/com/unicorn/lifesub/member/test/unit/config/jwt/CustomUserDetailsServiceTest.class new file mode 100644 index 0000000..712ad7f Binary files /dev/null and b/member/build/classes/java/test/com/unicorn/lifesub/member/test/unit/config/jwt/CustomUserDetailsServiceTest.class differ diff --git a/member/build/classes/java/test/com/unicorn/lifesub/member/test/unit/config/jwt/JwtTokenProviderTest.class b/member/build/classes/java/test/com/unicorn/lifesub/member/test/unit/config/jwt/JwtTokenProviderTest.class new file mode 100644 index 0000000..8fa38a1 Binary files /dev/null and b/member/build/classes/java/test/com/unicorn/lifesub/member/test/unit/config/jwt/JwtTokenProviderTest.class differ diff --git a/member/build/classes/java/test/com/unicorn/lifesub/member/test/unit/domain/MemberTest.class b/member/build/classes/java/test/com/unicorn/lifesub/member/test/unit/domain/MemberTest.class new file mode 100644 index 0000000..4138994 Binary files /dev/null and b/member/build/classes/java/test/com/unicorn/lifesub/member/test/unit/domain/MemberTest.class differ diff --git a/member/build/classes/java/test/com/unicorn/lifesub/member/test/unit/service/MemberServiceImplTest.class b/member/build/classes/java/test/com/unicorn/lifesub/member/test/unit/service/MemberServiceImplTest.class new file mode 100644 index 0000000..785a028 Binary files /dev/null and b/member/build/classes/java/test/com/unicorn/lifesub/member/test/unit/service/MemberServiceImplTest.class differ diff --git a/member/build/reports/tests/test/classes/com.unicorn.lifesub.member.test.unit.config.jwt.CustomUserDetailsServiceTest.html b/member/build/reports/tests/test/classes/com.unicorn.lifesub.member.test.unit.config.jwt.CustomUserDetailsServiceTest.html new file mode 100644 index 0000000..9abc6bc --- /dev/null +++ b/member/build/reports/tests/test/classes/com.unicorn.lifesub.member.test.unit.config.jwt.CustomUserDetailsServiceTest.html @@ -0,0 +1,110 @@ + + + + + +Test results - CustomUserDetailsServiceTest + + + + + +
+

CustomUserDetailsServiceTest

+ +
+ + + + + +
+
+ + + + + + + +
+
+
3
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

ignored

+
+
+
+
1.064s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+
+ +
+

Tests

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
TestMethod nameDurationResult
givenExistingUserId_whenLoadUser_thenReturnUserDetailsgivenExistingUserId_whenLoadUser_thenReturnUserDetails()0.008spassed
givenNonExistentUserId_whenLoadUser_thenThrowExceptiongivenNonExistentUserId_whenLoadUser_thenThrowException()0.864spassed
givenUserWithRoles_whenLoadUser_thenMapAuthoritiesCorrectlygivenUserWithRoles_whenLoadUser_thenMapAuthoritiesCorrectly()0.192spassed
+
+
+ +
+ + diff --git a/member/build/reports/tests/test/css/base-style.css b/member/build/reports/tests/test/css/base-style.css new file mode 100644 index 0000000..4afa73e --- /dev/null +++ b/member/build/reports/tests/test/css/base-style.css @@ -0,0 +1,179 @@ + +body { + margin: 0; + padding: 0; + font-family: sans-serif; + font-size: 12pt; +} + +body, a, a:visited { + color: #303030; +} + +#content { + padding-left: 50px; + padding-right: 50px; + padding-top: 30px; + padding-bottom: 30px; +} + +#content h1 { + font-size: 160%; + margin-bottom: 10px; +} + +#footer { + margin-top: 100px; + font-size: 80%; + white-space: nowrap; +} + +#footer, #footer a { + color: #a0a0a0; +} + +#line-wrapping-toggle { + vertical-align: middle; +} + +#label-for-line-wrapping-toggle { + vertical-align: middle; +} + +ul { + margin-left: 0; +} + +h1, h2, h3 { + white-space: nowrap; +} + +h2 { + font-size: 120%; +} + +ul.tabLinks { + padding-left: 0; + padding-top: 10px; + padding-bottom: 10px; + overflow: auto; + min-width: 800px; + width: auto !important; + width: 800px; +} + +ul.tabLinks li { + float: left; + height: 100%; + list-style: none; + padding-left: 10px; + padding-right: 10px; + padding-top: 5px; + padding-bottom: 5px; + margin-bottom: 0; + -moz-border-radius: 7px; + border-radius: 7px; + margin-right: 25px; + border: solid 1px #d4d4d4; + background-color: #f0f0f0; +} + +ul.tabLinks li:hover { + background-color: #fafafa; +} + +ul.tabLinks li.selected { + background-color: #c5f0f5; + border-color: #c5f0f5; +} + +ul.tabLinks a { + font-size: 120%; + display: block; + outline: none; + text-decoration: none; + margin: 0; + padding: 0; +} + +ul.tabLinks li h2 { + margin: 0; + padding: 0; +} + +div.tab { +} + +div.selected { + display: block; +} + +div.deselected { + display: none; +} + +div.tab table { + min-width: 350px; + width: auto !important; + width: 350px; + border-collapse: collapse; +} + +div.tab th, div.tab table { + border-bottom: solid #d0d0d0 1px; +} + +div.tab th { + text-align: left; + white-space: nowrap; + padding-left: 6em; +} + +div.tab th:first-child { + padding-left: 0; +} + +div.tab td { + white-space: nowrap; + padding-left: 6em; + padding-top: 5px; + padding-bottom: 5px; +} + +div.tab td:first-child { + padding-left: 0; +} + +div.tab td.numeric, div.tab th.numeric { + text-align: right; +} + +span.code { + display: inline-block; + margin-top: 0em; + margin-bottom: 1em; +} + +span.code pre { + font-size: 11pt; + padding-top: 10px; + padding-bottom: 10px; + padding-left: 10px; + padding-right: 10px; + margin: 0; + background-color: #f7f7f7; + border: solid 1px #d0d0d0; + min-width: 700px; + width: auto !important; + width: 700px; +} + +span.wrapped pre { + word-wrap: break-word; + white-space: pre-wrap; + word-break: break-all; +} + +label.hidden { + display: none; +} \ No newline at end of file diff --git a/member/build/reports/tests/test/css/style.css b/member/build/reports/tests/test/css/style.css new file mode 100644 index 0000000..3dc4913 --- /dev/null +++ b/member/build/reports/tests/test/css/style.css @@ -0,0 +1,84 @@ + +#summary { + margin-top: 30px; + margin-bottom: 40px; +} + +#summary table { + border-collapse: collapse; +} + +#summary td { + vertical-align: top; +} + +.breadcrumbs, .breadcrumbs a { + color: #606060; +} + +.infoBox { + width: 110px; + padding-top: 15px; + padding-bottom: 15px; + text-align: center; +} + +.infoBox p { + margin: 0; +} + +.counter, .percent { + font-size: 120%; + font-weight: bold; + margin-bottom: 8px; +} + +#duration { + width: 125px; +} + +#successRate, .summaryGroup { + border: solid 2px #d0d0d0; + -moz-border-radius: 10px; + border-radius: 10px; +} + +#successRate { + width: 140px; + margin-left: 35px; +} + +#successRate .percent { + font-size: 180%; +} + +.success, .success a { + color: #008000; +} + +div.success, #successRate.success { + background-color: #bbd9bb; + border-color: #008000; +} + +.failures, .failures a { + color: #b60808; +} + +.skipped, .skipped a { + color: #c09853; +} + +div.failures, #successRate.failures { + background-color: #ecdada; + border-color: #b60808; +} + +ul.linkList { + padding-left: 0; +} + +ul.linkList li { + list-style: none; + margin-bottom: 5px; +} diff --git a/member/build/reports/tests/test/index.html b/member/build/reports/tests/test/index.html new file mode 100644 index 0000000..514540c --- /dev/null +++ b/member/build/reports/tests/test/index.html @@ -0,0 +1,133 @@ + + + + + +Test results - Test Summary + + + + + +
+

Test Summary

+
+ + + + + +
+
+ + + + + + + +
+
+
3
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

ignored

+
+
+
+
1.064s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+
+ +
+

Packages

+ + + + + + + + + + + + + + + + + + + + + +
PackageTestsFailuresIgnoredDurationSuccess rate
+com.unicorn.lifesub.member.test.unit.config.jwt +3001.064s100%
+
+
+

Classes

+ + + + + + + + + + + + + + + + + + + + + +
ClassTestsFailuresIgnoredDurationSuccess rate
+com.unicorn.lifesub.member.test.unit.config.jwt.CustomUserDetailsServiceTest +3001.064s100%
+
+
+ +
+ + diff --git a/member/build/reports/tests/test/js/report.js b/member/build/reports/tests/test/js/report.js new file mode 100644 index 0000000..83bab4a --- /dev/null +++ b/member/build/reports/tests/test/js/report.js @@ -0,0 +1,194 @@ +(function (window, document) { + "use strict"; + + var tabs = {}; + + function changeElementClass(element, classValue) { + if (element.getAttribute("className")) { + element.setAttribute("className", classValue); + } else { + element.setAttribute("class", classValue); + } + } + + function getClassAttribute(element) { + if (element.getAttribute("className")) { + return element.getAttribute("className"); + } else { + return element.getAttribute("class"); + } + } + + function addClass(element, classValue) { + changeElementClass(element, getClassAttribute(element) + " " + classValue); + } + + function removeClass(element, classValue) { + changeElementClass(element, getClassAttribute(element).replace(classValue, "")); + } + + function initTabs() { + var container = document.getElementById("tabs"); + + tabs.tabs = findTabs(container); + tabs.titles = findTitles(tabs.tabs); + tabs.headers = findHeaders(container); + tabs.select = select; + tabs.deselectAll = deselectAll; + tabs.select(0); + + return true; + } + + function getCheckBox() { + return document.getElementById("line-wrapping-toggle"); + } + + function getLabelForCheckBox() { + return document.getElementById("label-for-line-wrapping-toggle"); + } + + function findCodeBlocks() { + var spans = document.getElementById("tabs").getElementsByTagName("span"); + var codeBlocks = []; + for (var i = 0; i < spans.length; ++i) { + if (spans[i].className.indexOf("code") >= 0) { + codeBlocks.push(spans[i]); + } + } + return codeBlocks; + } + + function forAllCodeBlocks(operation) { + var codeBlocks = findCodeBlocks(); + + for (var i = 0; i < codeBlocks.length; ++i) { + operation(codeBlocks[i], "wrapped"); + } + } + + function toggleLineWrapping() { + var checkBox = getCheckBox(); + + if (checkBox.checked) { + forAllCodeBlocks(addClass); + } else { + forAllCodeBlocks(removeClass); + } + } + + function initControls() { + if (findCodeBlocks().length > 0) { + var checkBox = getCheckBox(); + var label = getLabelForCheckBox(); + + checkBox.onclick = toggleLineWrapping; + checkBox.checked = false; + + removeClass(label, "hidden"); + } + } + + function switchTab() { + var id = this.id.substr(1); + + for (var i = 0; i < tabs.tabs.length; i++) { + if (tabs.tabs[i].id === id) { + tabs.select(i); + break; + } + } + + return false; + } + + function select(i) { + this.deselectAll(); + + changeElementClass(this.tabs[i], "tab selected"); + changeElementClass(this.headers[i], "selected"); + + while (this.headers[i].firstChild) { + this.headers[i].removeChild(this.headers[i].firstChild); + } + + var h2 = document.createElement("H2"); + + h2.appendChild(document.createTextNode(this.titles[i])); + this.headers[i].appendChild(h2); + } + + function deselectAll() { + for (var i = 0; i < this.tabs.length; i++) { + changeElementClass(this.tabs[i], "tab deselected"); + changeElementClass(this.headers[i], "deselected"); + + while (this.headers[i].firstChild) { + this.headers[i].removeChild(this.headers[i].firstChild); + } + + var a = document.createElement("A"); + + a.setAttribute("id", "ltab" + i); + a.setAttribute("href", "#tab" + i); + a.onclick = switchTab; + a.appendChild(document.createTextNode(this.titles[i])); + + this.headers[i].appendChild(a); + } + } + + function findTabs(container) { + return findChildElements(container, "DIV", "tab"); + } + + function findHeaders(container) { + var owner = findChildElements(container, "UL", "tabLinks"); + return findChildElements(owner[0], "LI", null); + } + + function findTitles(tabs) { + var titles = []; + + for (var i = 0; i < tabs.length; i++) { + var tab = tabs[i]; + var header = findChildElements(tab, "H2", null)[0]; + + header.parentNode.removeChild(header); + + if (header.innerText) { + titles.push(header.innerText); + } else { + titles.push(header.textContent); + } + } + + return titles; + } + + function findChildElements(container, name, targetClass) { + var elements = []; + var children = container.childNodes; + + for (var i = 0; i < children.length; i++) { + var child = children.item(i); + + if (child.nodeType === 1 && child.nodeName === name) { + if (targetClass && child.className.indexOf(targetClass) < 0) { + continue; + } + + elements.push(child); + } + } + + return elements; + } + + // Entry point. + + window.onload = function() { + initTabs(); + initControls(); + }; +} (window, window.document)); \ No newline at end of file diff --git a/member/build/reports/tests/test/packages/com.unicorn.lifesub.member.test.unit.config.jwt.html b/member/build/reports/tests/test/packages/com.unicorn.lifesub.member.test.unit.config.jwt.html new file mode 100644 index 0000000..7ba68f9 --- /dev/null +++ b/member/build/reports/tests/test/packages/com.unicorn.lifesub.member.test.unit.config.jwt.html @@ -0,0 +1,103 @@ + + + + + +Test results - Package com.unicorn.lifesub.member.test.unit.config.jwt + + + + + +
+

Package com.unicorn.lifesub.member.test.unit.config.jwt

+ +
+ + + + + +
+
+ + + + + + + +
+
+
3
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

ignored

+
+
+
+
1.064s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+
+ +
+

Classes

+ + + + + + + + + + + + + + + + + + + +
ClassTestsFailuresIgnoredDurationSuccess rate
+CustomUserDetailsServiceTest +3001.064s100%
+
+
+ +
+ + diff --git a/member/build/test-results/test/TEST-com.unicorn.lifesub.member.test.unit.config.jwt.CustomUserDetailsServiceTest.xml b/member/build/test-results/test/TEST-com.unicorn.lifesub.member.test.unit.config.jwt.CustomUserDetailsServiceTest.xml new file mode 100644 index 0000000..52ead08 --- /dev/null +++ b/member/build/test-results/test/TEST-com.unicorn.lifesub.member.test.unit.config.jwt.CustomUserDetailsServiceTest.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/member/build/test-results/test/binary/output.bin b/member/build/test-results/test/binary/output.bin new file mode 100644 index 0000000..e69de29 diff --git a/member/build/test-results/test/binary/output.bin.idx b/member/build/test-results/test/binary/output.bin.idx new file mode 100644 index 0000000..f76dd23 Binary files /dev/null and b/member/build/test-results/test/binary/output.bin.idx differ diff --git a/member/build/test-results/test/binary/results.bin b/member/build/test-results/test/binary/results.bin new file mode 100644 index 0000000..aa55725 Binary files /dev/null and b/member/build/test-results/test/binary/results.bin differ diff --git a/member/build/tmp/compileJava/previous-compilation-data.bin b/member/build/tmp/compileJava/previous-compilation-data.bin index 9733c62..c0eda13 100644 Binary files a/member/build/tmp/compileJava/previous-compilation-data.bin and b/member/build/tmp/compileJava/previous-compilation-data.bin differ diff --git a/member/build/tmp/compileTestJava/previous-compilation-data.bin b/member/build/tmp/compileTestJava/previous-compilation-data.bin new file mode 100644 index 0000000..096f65b Binary files /dev/null and b/member/build/tmp/compileTestJava/previous-compilation-data.bin differ diff --git a/member/src/main/java/com/unicorn/lifesub/member/dto/LoginRequest.java b/member/src/main/java/com/unicorn/lifesub/member/dto/LoginRequest.java index 8bbbfbe..1be21da 100644 --- a/member/src/main/java/com/unicorn/lifesub/member/dto/LoginRequest.java +++ b/member/src/main/java/com/unicorn/lifesub/member/dto/LoginRequest.java @@ -2,12 +2,14 @@ package com.unicorn.lifesub.member.dto; import jakarta.validation.constraints.NotBlank; import lombok.Getter; +import lombok.Setter; @Getter +@Setter public class LoginRequest { @NotBlank(message = "사용자 ID는 필수입니다.") private String userId; - + @NotBlank(message = "비밀번호는 필수입니다.") private String password; } diff --git a/member/src/main/java/com/unicorn/lifesub/member/dto/LogoutRequest.java b/member/src/main/java/com/unicorn/lifesub/member/dto/LogoutRequest.java index 4e0809a..f646d82 100644 --- a/member/src/main/java/com/unicorn/lifesub/member/dto/LogoutRequest.java +++ b/member/src/main/java/com/unicorn/lifesub/member/dto/LogoutRequest.java @@ -2,8 +2,10 @@ package com.unicorn.lifesub.member.dto; import jakarta.validation.constraints.NotBlank; import lombok.Getter; +import lombok.Setter; @Getter +@Setter public class LogoutRequest { @NotBlank(message = "사용자 ID는 필수입니다.") private String userId; diff --git a/member/src/test/java/com/unicorn/lifesub/member/test/unit/config/jwt/CustomUserDetailsServiceTest.java b/member/src/test/java/com/unicorn/lifesub/member/test/unit/config/jwt/CustomUserDetailsServiceTest.java new file mode 100644 index 0000000..b4490f0 --- /dev/null +++ b/member/src/test/java/com/unicorn/lifesub/member/test/unit/config/jwt/CustomUserDetailsServiceTest.java @@ -0,0 +1,122 @@ +package com.unicorn.lifesub.member.test.unit.config.jwt; + +import com.unicorn.lifesub.member.config.jwt.CustomUserDetailsService; +import com.unicorn.lifesub.member.repository.entity.MemberEntity; +import com.unicorn.lifesub.member.repository.jpa.MemberRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.BDDMockito.given; + +/** + * 사용자 상세 정보 서비스 테스트 클래스 + * Spring Security의 UserDetailsService 구현체 검증 + */ +@ExtendWith(MockitoExtension.class) +class CustomUserDetailsServiceTest { + + @InjectMocks + private CustomUserDetailsService userDetailsService; + + @Mock + private MemberRepository memberRepository; + + // 테스트용 상수 정의 + private static final String TEST_USER_ID = "testUser"; + private static final String TEST_PASSWORD = "testPassword"; + private static final String TEST_USER_NAME = "Test User"; + + /** + * 사용자 조회 성공 케이스 테스트 + * 존재하는 사용자 ID로 조회 시 UserDetails 객체가 정상적으로 반환되는지 검증 + */ + @Test + @DisplayName("givenExistingUserId_whenLoadUser_thenReturnUserDetails") + void givenExistingUserId_whenLoadUser_thenReturnUserDetails() { + // Given + MemberEntity memberEntity = createTestMemberEntity(); + given(memberRepository.findByUserId(TEST_USER_ID)).willReturn(Optional.of(memberEntity)); + + // When + UserDetails userDetails = userDetailsService.loadUserByUsername(TEST_USER_ID); + + // Then + assertThat(userDetails).isNotNull(); + assertThat(userDetails.getUsername()).isEqualTo(TEST_USER_ID); + assertThat(userDetails.getPassword()).isEqualTo(TEST_PASSWORD); + assertThat(userDetails.getAuthorities()).hasSize(1); + } + + /** + * 사용자 조회 실패 케이스 테스트 + * 존재하지 않는 사용자 ID로 조회 시 적절한 예외가 발생하는지 검증 + */ + @Test + @DisplayName("givenNonExistentUserId_whenLoadUser_thenThrowException") + void givenNonExistentUserId_whenLoadUser_thenThrowException() { + // Given + String nonExistentUserId = "nonexistent"; + given(memberRepository.findByUserId(nonExistentUserId)).willReturn(Optional.empty()); + + // When & Then + assertThrows(UsernameNotFoundException.class, () -> + userDetailsService.loadUserByUsername(nonExistentUserId)); + } + + /** + * 사용자 권한 매핑 테스트 + * 사용자의 역할이 Spring Security 권한으로 올바르게 매핑되는지 검증 + */ + @Test + @DisplayName("givenUserWithRoles_whenLoadUser_thenMapAuthoritiesCorrectly") + void givenUserWithRoles_whenLoadUser_thenMapAuthoritiesCorrectly() { + // Given + MemberEntity memberEntity = createTestMemberEntityWithMultipleRoles(); + given(memberRepository.findByUserId(TEST_USER_ID)).willReturn(Optional.of(memberEntity)); + + // When + UserDetails userDetails = userDetailsService.loadUserByUsername(TEST_USER_ID); + + // Then + assertThat(userDetails.getAuthorities()).hasSize(2); + assertThat(userDetails.getAuthorities()) + .extracting("authority") + .containsExactlyInAnyOrder("USER", "ADMIN"); + } + + // 테스트 헬퍼 메서드 + private MemberEntity createTestMemberEntity() { + Set roles = new HashSet<>(); + roles.add("USER"); + return MemberEntity.builder() + .userId(TEST_USER_ID) + .userName(TEST_USER_NAME) + .password(TEST_PASSWORD) + .roles(roles) + .build(); + } + + private MemberEntity createTestMemberEntityWithMultipleRoles() { + Set roles = new HashSet<>(); + roles.add("USER"); + roles.add("ADMIN"); + return MemberEntity.builder() + .userId(TEST_USER_ID) + .userName(TEST_USER_NAME) + .password(TEST_PASSWORD) + .roles(roles) + .build(); + } +} \ No newline at end of file diff --git a/member/src/test/java/com/unicorn/lifesub/member/test/unit/config/jwt/JwtTokenProviderTest.java b/member/src/test/java/com/unicorn/lifesub/member/test/unit/config/jwt/JwtTokenProviderTest.java new file mode 100644 index 0000000..f20b05d --- /dev/null +++ b/member/src/test/java/com/unicorn/lifesub/member/test/unit/config/jwt/JwtTokenProviderTest.java @@ -0,0 +1,148 @@ +package com.unicorn.lifesub.member.test.unit.config.jwt; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import com.unicorn.lifesub.common.dto.JwtTokenDTO; +import com.unicorn.lifesub.common.exception.InfraException; +import com.unicorn.lifesub.member.config.jwt.JwtTokenProvider; +import com.unicorn.lifesub.member.repository.entity.MemberEntity; +import jakarta.servlet.http.HttpServletRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * JWT 토큰 제공자 테스트 클래스 + * 토큰 생성, 검증, 파싱 등의 기능을 검증 + */ +class JwtTokenProviderTest { + + private JwtTokenProvider jwtTokenProvider; + private static final String SECRET_KEY = "test-secret-key"; + private static final long ACCESS_TOKEN_VALIDITY = 3600000; + private static final long REFRESH_TOKEN_VALIDITY = 86400000; + + @BeforeEach + void setUp() { + jwtTokenProvider = new JwtTokenProvider(SECRET_KEY, ACCESS_TOKEN_VALIDITY, REFRESH_TOKEN_VALIDITY); + } + + /** + * 토큰 생성 테스트 + * 유효한 사용자 정보로 JWT 토큰이 정상적으로 생성되는지 검증 + */ + @Test + @DisplayName("givenValidMember_whenCreateToken_thenSuccess") + void givenValidMember_whenCreateToken_thenSuccess() { + // Given + MemberEntity member = createTestMemberEntity(); + Set authorities = Collections.singleton( + new SimpleGrantedAuthority("ROLE_USER")); + + // When + JwtTokenDTO tokens = jwtTokenProvider.createToken(member, authorities); + + // Then + assertThat(tokens).isNotNull(); + assertThat(tokens.getAccessToken()).isNotNull(); + assertThat(tokens.getRefreshToken()).isNotNull(); + assertThat(jwtTokenProvider.validateToken(tokens.getAccessToken())).isEqualTo(1); + } + + /** + * 토큰 검증 테스트 + * 유효한 토큰과 유효하지 않은 토큰에 대한 검증이 정상적으로 동작하는지 확인 + */ + @Test + @DisplayName("givenToken_whenValidate_thenSuccess") + void givenToken_whenValidate_thenSuccess() { + // Given + String token = createValidToken(); + + // When & Then + assertThat(jwtTokenProvider.validateToken(token)).isEqualTo(1); + } + + /** + * 인증 정보 추출 테스트 + * JWT 토큰에서 인증 정보가 정상적으로 추출되는지 검증 + */ + @Test + @DisplayName("givenValidToken_whenGetAuthentication_thenSuccess") + void givenValidToken_whenGetAuthentication_thenSuccess() { + // Given + String token = createValidToken(); + + // When + Authentication authentication = jwtTokenProvider.getAuthentication(token); + + // Then + assertThat(authentication).isNotNull(); + assertThat(authentication.isAuthenticated()).isTrue(); + assertThat(authentication.getAuthorities()).hasSize(1); + } + + /** + * 토큰 추출 테스트 + * HTTP 요청 헤더에서 토큰이 정상적으로 추출되는지 검증 + */ + @Test + @DisplayName("givenRequest_whenResolveToken_thenSuccess") + void givenRequest_whenResolveToken_thenSuccess() { + // Given + HttpServletRequest request = mock(HttpServletRequest.class); + String token = "test-token"; + when(request.getHeader("Authorization")).thenReturn("Bearer " + token); + + // When + String resolvedToken = jwtTokenProvider.resolveToken(request); + + // Then + assertThat(resolvedToken).isEqualTo(token); + } + + /** + * 유효하지 않은 토큰 검증 테스트 + * 잘못된 형식의 토큰에 대해 적절한 예외가 발생하는지 검증 + */ + @Test + @DisplayName("givenInvalidToken_whenValidate_thenThrowException") + void givenInvalidToken_whenValidate_thenThrowException() { + // Given + String invalidToken = "invalid-token"; + + // When & Then + assertThrows(InfraException.class, () -> jwtTokenProvider.validateToken(invalidToken)); + } + + // 테스트 헬퍼 메서드 + private MemberEntity createTestMemberEntity() { + Set roles = new HashSet<>(); + roles.add("USER"); + return MemberEntity.builder() + .userId("testUser") + .userName("Test User") + .password("password") + .roles(roles) + .build(); + } + + private String createValidToken() { + Algorithm algorithm = Algorithm.HMAC512(SECRET_KEY); + return JWT.create() + .withSubject("testUser") + .withClaim("auth", Collections.singletonList("ROLE_USER")) + .sign(algorithm); + } +} \ No newline at end of file diff --git a/member/src/test/java/com/unicorn/lifesub/member/test/unit/domain/MemberTest.java b/member/src/test/java/com/unicorn/lifesub/member/test/unit/domain/MemberTest.java new file mode 100644 index 0000000..0a0ef86 --- /dev/null +++ b/member/src/test/java/com/unicorn/lifesub/member/test/unit/domain/MemberTest.java @@ -0,0 +1,141 @@ +package com.unicorn.lifesub.member.test.unit.domain; + +import com.unicorn.lifesub.member.domain.Member; +import com.unicorn.lifesub.member.repository.entity.MemberEntity; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.HashSet; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Member 도메인 객체 테스트 클래스 + * 도메인 객체의 생성 및 엔티티 변환 로직을 검증 + */ +class MemberTest { + + // 테스트용 상수 정의 + private static final String TEST_USER_ID = "testUser"; + private static final String TEST_USER_NAME = "Test User"; + private static final String TEST_PASSWORD = "testPassword"; + + /** + * Member 객체 생성 테스트 + * Builder 패턴을 사용한 Member 객체 생성이 정상적으로 동작하는지 검증 + */ + @Test + @DisplayName("givenMemberInfo_whenBuildMember_thenSuccess") + void givenMemberInfo_whenBuildMember_thenSuccess() { + // Given + Set roles = new HashSet<>(); + roles.add("USER"); + + // When + Member member = Member.builder() + .userId(TEST_USER_ID) + .userName(TEST_USER_NAME) + .password(TEST_PASSWORD) + .roles(roles) + .build(); + + // Then + assertThat(member).isNotNull(); + assertThat(member.getUserId()).isEqualTo(TEST_USER_ID); + assertThat(member.getUserName()).isEqualTo(TEST_USER_NAME); + assertThat(member.getPassword()).isEqualTo(TEST_PASSWORD); + assertThat(member.getRoles()).containsExactly("USER"); + } + + /** + * Entity에서 Domain 객체로의 변환 테스트 + * MemberEntity.toDomain() 메서드가 정상적으로 동작하는지 검증 + */ + @Test + @DisplayName("givenMemberEntity_whenConvertToDomain_thenSuccess") + void givenMemberEntity_whenConvertToDomain_thenSuccess() { + // Given + MemberEntity entity = createTestMemberEntity(); + + // When + Member member = entity.toDomain(); + + // Then + assertThat(member).isNotNull(); + assertThat(member.getUserId()).isEqualTo(TEST_USER_ID); + assertThat(member.getUserName()).isEqualTo(TEST_USER_NAME); + assertThat(member.getPassword()).isEqualTo(TEST_PASSWORD); + assertThat(member.getRoles()).containsExactly("USER"); + } + + /** + * Domain 객체에서 Entity로의 변환 테스트 + * MemberEntity.fromDomain() 메서드가 정상적으로 동작하는지 검증 + */ + @Test + @DisplayName("givenMemberDomain_whenConvertToEntity_thenSuccess") + void givenMemberDomain_whenConvertToEntity_thenSuccess() { + // Given + Member member = createTestMember(); + + // When + MemberEntity entity = MemberEntity.fromDomain(member); + + // Then + assertThat(entity).isNotNull(); + assertThat(entity.getUserId()).isEqualTo(TEST_USER_ID); + assertThat(entity.getUserName()).isEqualTo(TEST_USER_NAME); + assertThat(entity.getPassword()).isEqualTo(TEST_PASSWORD); + assertThat(entity.getRoles()).containsExactly("USER"); + } + + /** + * 다중 역할을 가진 Member 객체 생성 테스트 + * 여러 역할을 가진 Member 객체가 정상적으로 생성되는지 검증 + */ + @Test + @DisplayName("givenMultipleRoles_whenBuildMember_thenSuccess") + void givenMultipleRoles_whenBuildMember_thenSuccess() { + // Given + Set roles = new HashSet<>(); + roles.add("USER"); + roles.add("ADMIN"); + + // When + Member member = Member.builder() + .userId(TEST_USER_ID) + .userName(TEST_USER_NAME) + .password(TEST_PASSWORD) + .roles(roles) + .build(); + + // Then + assertThat(member).isNotNull(); + assertThat(member.getRoles()).hasSize(2); + assertThat(member.getRoles()).containsExactlyInAnyOrder("USER", "ADMIN"); + } + + // 테스트 헬퍼 메서드 + private MemberEntity createTestMemberEntity() { + Set roles = new HashSet<>(); + roles.add("USER"); + return MemberEntity.builder() + .userId(TEST_USER_ID) + .userName(TEST_USER_NAME) + .password(TEST_PASSWORD) + .roles(roles) + .build(); + } + + private Member createTestMember() { + Set roles = new HashSet<>(); + roles.add("USER"); + return Member.builder() + .userId(TEST_USER_ID) + .userName(TEST_USER_NAME) + .password(TEST_PASSWORD) + .roles(roles) + .build(); + } +} diff --git a/member/src/test/java/com/unicorn/lifesub/member/test/unit/service/MemberServiceImplTest.java b/member/src/test/java/com/unicorn/lifesub/member/test/unit/service/MemberServiceImplTest.java new file mode 100644 index 0000000..43c6f05 --- /dev/null +++ b/member/src/test/java/com/unicorn/lifesub/member/test/unit/service/MemberServiceImplTest.java @@ -0,0 +1,163 @@ +package com.unicorn.lifesub.member.test.unit.service; + +import com.unicorn.lifesub.common.dto.JwtTokenDTO; +import com.unicorn.lifesub.common.exception.BusinessException; +import com.unicorn.lifesub.common.exception.ErrorCode; +import com.unicorn.lifesub.common.exception.InfraException; +import com.unicorn.lifesub.member.config.jwt.JwtTokenProvider; +import com.unicorn.lifesub.member.dto.LoginRequest; +import com.unicorn.lifesub.member.dto.LogoutRequest; +import com.unicorn.lifesub.member.repository.entity.MemberEntity; +import com.unicorn.lifesub.member.repository.jpa.MemberRepository; +import com.unicorn.lifesub.member.service.MemberServiceImpl; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.crypto.password.PasswordEncoder; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.when; + +/** + * 멤버 서비스 테스트 클래스 + * 주요 비즈니스 로직인 로그인/로그아웃 기능을 검증 + */ +@ExtendWith(MockitoExtension.class) +class MemberServiceImplTest { + + @InjectMocks + private MemberServiceImpl memberService; + + @Mock + private MemberRepository memberRepository; + + @Mock + private PasswordEncoder passwordEncoder; + + @Mock + private JwtTokenProvider jwtTokenProvider; + + // 테스트용 상수 정의 + private static final String TEST_USER_ID = "testUser"; + private static final String TEST_PASSWORD = "testPassword"; + private static final String TEST_USER_NAME = "Test User"; + + /** + * 로그인 성공 케이스 테스트 + * 올바른 사용자 ID와 비밀번호로 로그인 시 JWT 토큰이 정상적으로 발급되는지 검증 + */ + @Test + @DisplayName("givenValidCredentials_whenLogin_thenSuccess") + void givenValidCredentials_whenLogin_thenSuccess() { + // Given + LoginRequest request = new LoginRequest(); + request.setUserId(TEST_USER_ID); + request.setPassword(TEST_PASSWORD); + + MemberEntity memberEntity = createTestMemberEntity(); + JwtTokenDTO expectedToken = createTestJwtTokenDTO(); + + given(memberRepository.findByUserId(TEST_USER_ID)).willReturn(Optional.of(memberEntity)); + given(passwordEncoder.matches(TEST_PASSWORD, memberEntity.getPassword())).willReturn(true); + given(jwtTokenProvider.createToken(any(), any())).willReturn(expectedToken); + + // When + JwtTokenDTO result = memberService.login(request); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getAccessToken()).isEqualTo(expectedToken.getAccessToken()); + assertThat(result.getRefreshToken()).isEqualTo(expectedToken.getRefreshToken()); + } + + /** + * 로그인 실패 케이스 테스트 - 사용자가 존재하지 않는 경우 + * 존재하지 않는 사용자 ID로 로그인 시도 시 적절한 예외가 발생하는지 검증 + */ + @Test + @DisplayName("givenNonExistentUser_whenLogin_thenThrowException") + void givenNonExistentUser_whenLogin_thenThrowException() { + // Given + LoginRequest request = new LoginRequest(); + request.setUserId("nonexistent"); + request.setPassword(TEST_PASSWORD); + + when(memberRepository.findByUserId("nonexistent")).thenReturn(Optional.empty()); + + // When & Then + InfraException exception = assertThrows(InfraException.class, + () -> memberService.login(request)); + assertThat(exception.getErrorCode()).isEqualTo(ErrorCode.MEMBER_NOT_FOUND); + } + + /** + * 로그인 실패 케이스 테스트 - 잘못된 비밀번호 + * 올바른 사용자 ID와 잘못된 비밀번호로 로그인 시도 시 적절한 예외가 발생하는지 검증 + */ + @Test + @DisplayName("givenInvalidPassword_whenLogin_thenThrowException") + void givenInvalidPassword_whenLogin_thenThrowException() { + // Given + LoginRequest request = new LoginRequest(); + request.setUserId(TEST_USER_ID); + request.setPassword("wrongPassword"); + + MemberEntity memberEntity = createTestMemberEntity(); + + given(memberRepository.findByUserId(TEST_USER_ID)).willReturn(Optional.of(memberEntity)); + given(passwordEncoder.matches("wrongPassword", memberEntity.getPassword())).willReturn(false); + + // When & Then + BusinessException exception = assertThrows(BusinessException.class, + () -> memberService.login(request)); + assertThat(exception.getErrorCode()).isEqualTo(ErrorCode.INVALID_CREDENTIALS); + } + + /** + * 로그아웃 테스트 + * 로그아웃 요청 시 정상적으로 처리되는지 검증 + */ + @Test + @DisplayName("givenLogoutRequest_whenLogout_thenSuccess") + void givenLogoutRequest_whenLogout_thenSuccess() { + // Given + LogoutRequest request = new LogoutRequest(); + request.setUserId(TEST_USER_ID); + + // When + var response = memberService.logout(request); + + // Then + assertThat(response).isNotNull(); + assertThat(response.getMessage()).contains("로그아웃이 완료되었습니다"); + } + + // 테스트 헬퍼 메서드 + private MemberEntity createTestMemberEntity() { + Set roles = new HashSet<>(); + roles.add("USER"); + return MemberEntity.builder() + .userId(TEST_USER_ID) + .userName(TEST_USER_NAME) + .password(TEST_PASSWORD) + .roles(roles) + .build(); + } + + private JwtTokenDTO createTestJwtTokenDTO() { + return JwtTokenDTO.builder() + .accessToken("test-access-token") + .refreshToken("test-refresh-token") + .build(); + } +}