diff --git a/.gitignore b/.gitignore index 4e91a957..671e0e9f 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,6 @@ out/ ### Rest Docs /src/main/resources/static/docs/openapi3.yaml + +### application-local.yml +/src/main/resources/application-local.yml diff --git a/build.gradle b/build.gradle index bdcae96c..031c7a72 100644 --- a/build.gradle +++ b/build.gradle @@ -50,6 +50,11 @@ dependencies { // Excel Export implementation 'org.apache.poi:poi-ooxml:5.2.3' implementation 'org.apache.poi:poi:5.2.3' + + // JWT + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + implementation 'io.jsonwebtoken:jjwt-impl:0.11.5' + implementation 'io.jsonwebtoken:jjwt-gson:0.11.5' } bootJar { @@ -99,7 +104,20 @@ generateSwaggerUI { } openapi3 { - server = "http://localhost:8080" + servers = [ + { + url = "https://api.dev.debate-timer.com" + description = "Dev Server" + }, + { + url = "https://api.prod.debate-timer.com" + description = "Prod Server" + }, + { + url = "http://localhost:8080" + description = "Local Server" + } + ] title = "토론 타이머 API" description = "토론 타이머 API" version = "0.0.1" diff --git a/src/main/java/com/debatetimer/client/OAuthClient.java b/src/main/java/com/debatetimer/client/OAuthClient.java new file mode 100644 index 00000000..e2fa1607 --- /dev/null +++ b/src/main/java/com/debatetimer/client/OAuthClient.java @@ -0,0 +1,39 @@ +package com.debatetimer.client; + +import com.debatetimer.dto.member.MemberCreateRequest; +import com.debatetimer.dto.member.MemberInfo; +import com.debatetimer.dto.member.OAuthToken; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestClient; + +@Component +@EnableConfigurationProperties(OAuthProperties.class) +public class OAuthClient { + + private final RestClient restClient; + private final OAuthProperties oauthProperties; + + public OAuthClient(OAuthProperties oauthProperties) { + this.restClient = RestClient.create(); + this.oauthProperties = oauthProperties; + } + + public OAuthToken requestToken(MemberCreateRequest request) { + return restClient.post() + .uri("https://oauth2.googleapis.com/token") + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .body(oauthProperties.createTokenRequestBody(request)) + .retrieve() + .body(OAuthToken.class); + } + + public MemberInfo requestMemberInfo(OAuthToken response) { + return restClient.get() + .uri("https://www.googleapis.com/oauth2/v3/userinfo") + .headers(headers -> headers.setBearerAuth(response.access_token())) + .retrieve() + .body(MemberInfo.class); + } +} diff --git a/src/main/java/com/debatetimer/client/OAuthProperties.java b/src/main/java/com/debatetimer/client/OAuthProperties.java new file mode 100644 index 00000000..a747f84a --- /dev/null +++ b/src/main/java/com/debatetimer/client/OAuthProperties.java @@ -0,0 +1,41 @@ +package com.debatetimer.client; + +import com.debatetimer.dto.member.MemberCreateRequest; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import lombok.Getter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +@Getter +@ConfigurationProperties(prefix = "oauth") +public class OAuthProperties { + + private final String clientId; + private final String clientSecret; + private final String grantType; + + public OAuthProperties( + String clientId, + String clientSecret, + String grantType) { + this.clientId = clientId; + this.clientSecret = clientSecret; + this.grantType = grantType; + } + + public MultiValueMap createTokenRequestBody(MemberCreateRequest request) { + String code = request.code(); + String decodedVerificationCode = URLDecoder.decode(code, StandardCharsets.UTF_8); + + MultiValueMap map = new LinkedMultiValueMap<>(); + map.add("grant_type", grantType); + map.add("client_id", clientId); + map.add("redirect_uri", request.redirectUrl()); + map.add("code", decodedVerificationCode); + map.add("client_secret", clientSecret); + + return map; + } +} diff --git a/src/main/java/com/debatetimer/config/AuthMemberArgumentResolver.java b/src/main/java/com/debatetimer/config/AuthMemberArgumentResolver.java index 0a542f60..c3be291e 100644 --- a/src/main/java/com/debatetimer/config/AuthMemberArgumentResolver.java +++ b/src/main/java/com/debatetimer/config/AuthMemberArgumentResolver.java @@ -1,13 +1,14 @@ package com.debatetimer.config; import com.debatetimer.controller.auth.AuthMember; +import com.debatetimer.controller.tool.jwt.AuthManager; import com.debatetimer.exception.custom.DTClientErrorException; -import com.debatetimer.exception.custom.DTException; import com.debatetimer.exception.errorcode.ClientErrorCode; -import com.debatetimer.repository.member.MemberRepository; +import com.debatetimer.service.auth.AuthService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.core.MethodParameter; +import org.springframework.http.HttpHeaders; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; @@ -17,7 +18,8 @@ @RequiredArgsConstructor public class AuthMemberArgumentResolver implements HandlerMethodArgumentResolver { - private final MemberRepository memberRepository; + private final AuthManager authManager; + private final AuthService authService; @Override public boolean supportsParameter(MethodParameter parameter) { @@ -31,14 +33,11 @@ public Object resolveArgument( NativeWebRequest webRequest, WebDataBinderFactory binderFactory ) { - try { - long memberId = Long.parseLong(webRequest.getParameter("memberId")); - return memberRepository.getById(memberId); - } catch (DTException | NumberFormatException exception) { - log.warn(exception.getMessage()); + String accessToken = webRequest.getHeader(HttpHeaders.AUTHORIZATION); + if (accessToken == null) { throw new DTClientErrorException(ClientErrorCode.UNAUTHORIZED_MEMBER); } + String email = authManager.resolveAccessToken(accessToken); + return authService.getMember(email); } } - - diff --git a/src/main/java/com/debatetimer/config/AuthenticationConfig.java b/src/main/java/com/debatetimer/config/AuthenticationConfig.java new file mode 100644 index 00000000..3b1073fa --- /dev/null +++ b/src/main/java/com/debatetimer/config/AuthenticationConfig.java @@ -0,0 +1,12 @@ +package com.debatetimer.config; + +import com.debatetimer.client.OAuthProperties; +import com.debatetimer.controller.tool.jwt.JwtTokenProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableConfigurationProperties({OAuthProperties.class, JwtTokenProperties.class}) +public class AuthenticationConfig { + +} diff --git a/src/main/java/com/debatetimer/config/CorsConfig.java b/src/main/java/com/debatetimer/config/CorsConfig.java index 29fbf866..a63eb1d1 100644 --- a/src/main/java/com/debatetimer/config/CorsConfig.java +++ b/src/main/java/com/debatetimer/config/CorsConfig.java @@ -2,6 +2,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -28,7 +29,8 @@ public void addCorsMappings(CorsRegistry registry) { HttpMethod.OPTIONS.name() ) .allowCredentials(true) - .allowedHeaders("*"); + .allowedHeaders("*") + .exposedHeaders(HttpHeaders.AUTHORIZATION); } } diff --git a/src/main/java/com/debatetimer/config/WebConfig.java b/src/main/java/com/debatetimer/config/WebConfig.java index a983c443..0ce8ab1e 100644 --- a/src/main/java/com/debatetimer/config/WebConfig.java +++ b/src/main/java/com/debatetimer/config/WebConfig.java @@ -1,6 +1,7 @@ package com.debatetimer.config; -import com.debatetimer.repository.member.MemberRepository; +import com.debatetimer.controller.tool.jwt.AuthManager; +import com.debatetimer.service.auth.AuthService; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Configuration; @@ -11,10 +12,11 @@ @RequiredArgsConstructor public class WebConfig implements WebMvcConfigurer { - private final MemberRepository memberRepository; + private final AuthManager authManager; + private final AuthService authService; @Override public void addArgumentResolvers(List argumentResolvers) { - argumentResolvers.add(new AuthMemberArgumentResolver(memberRepository)); + argumentResolvers.add(new AuthMemberArgumentResolver(authManager, authService)); } } diff --git a/src/main/java/com/debatetimer/controller/member/MemberController.java b/src/main/java/com/debatetimer/controller/member/MemberController.java index 94b9b393..25d12555 100644 --- a/src/main/java/com/debatetimer/controller/member/MemberController.java +++ b/src/main/java/com/debatetimer/controller/member/MemberController.java @@ -1,13 +1,22 @@ package com.debatetimer.controller.member; import com.debatetimer.controller.auth.AuthMember; +import com.debatetimer.controller.tool.cookie.CookieManager; +import com.debatetimer.controller.tool.jwt.AuthManager; import com.debatetimer.domain.member.Member; +import com.debatetimer.dto.member.JwtTokenResponse; import com.debatetimer.dto.member.MemberCreateRequest; import com.debatetimer.dto.member.MemberCreateResponse; +import com.debatetimer.dto.member.MemberInfo; import com.debatetimer.dto.member.TableResponses; +import com.debatetimer.service.auth.AuthService; import com.debatetimer.service.member.MemberService; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseCookie; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -19,6 +28,9 @@ public class MemberController { private final MemberService memberService; + private final AuthService authService; + private final CookieManager cookieManager; + private final AuthManager authManager; @GetMapping("/api/table") public TableResponses getTables(@AuthMember Member member) { @@ -27,7 +39,35 @@ public TableResponses getTables(@AuthMember Member member) { @PostMapping("/api/member") @ResponseStatus(HttpStatus.CREATED) - public MemberCreateResponse createMember(@RequestBody MemberCreateRequest request) { - return memberService.createMember(request); + public MemberCreateResponse createMember(@RequestBody MemberCreateRequest request, HttpServletResponse response) { + MemberInfo memberInfo = authService.getMemberInfo(request); + MemberCreateResponse memberCreateResponse = memberService.createMember(memberInfo); + JwtTokenResponse jwtTokenResponse = authManager.issueToken(memberInfo); + ResponseCookie refreshTokenCookie = cookieManager.createRefreshTokenCookie(jwtTokenResponse.refreshToken()); + + response.addHeader(HttpHeaders.AUTHORIZATION, jwtTokenResponse.accessToken()); + response.addHeader(HttpHeaders.SET_COOKIE, refreshTokenCookie.toString()); + return memberCreateResponse; + } + + @PostMapping("/api/member/reissue") + public void reissueAccessToken(HttpServletRequest request, HttpServletResponse response) { + String refreshToken = cookieManager.extractRefreshToken(request.getCookies()); + JwtTokenResponse jwtTokenResponse = authManager.reissueToken(refreshToken); + ResponseCookie refreshTokenCookie = cookieManager.createRefreshTokenCookie(jwtTokenResponse.refreshToken()); + + response.addHeader(HttpHeaders.AUTHORIZATION, jwtTokenResponse.accessToken()); + response.addHeader(HttpHeaders.SET_COOKIE, refreshTokenCookie.toString()); + } + + @PostMapping("/api/member/logout") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void logout(@AuthMember Member member, HttpServletRequest request, HttpServletResponse response) { + String refreshToken = cookieManager.extractRefreshToken(request.getCookies()); + String email = authManager.resolveRefreshToken(refreshToken); + authService.logout(member, email); + ResponseCookie deletedRefreshTokenCookie = cookieManager.deleteRefreshTokenCookie(); + + response.addHeader(HttpHeaders.SET_COOKIE, deletedRefreshTokenCookie.toString()); } } diff --git a/src/main/java/com/debatetimer/controller/tool/cookie/CookieExtractor.java b/src/main/java/com/debatetimer/controller/tool/cookie/CookieExtractor.java new file mode 100644 index 00000000..936ea6d8 --- /dev/null +++ b/src/main/java/com/debatetimer/controller/tool/cookie/CookieExtractor.java @@ -0,0 +1,19 @@ +package com.debatetimer.controller.tool.cookie; + +import com.debatetimer.exception.custom.DTClientErrorException; +import com.debatetimer.exception.errorcode.ClientErrorCode; +import jakarta.servlet.http.Cookie; +import java.util.Arrays; +import org.springframework.stereotype.Component; + +@Component +public class CookieExtractor { + + public String extractCookie(String cookieName, Cookie[] cookies) { + return Arrays.stream(cookies) + .filter(cookie -> cookie.getName().equals(cookieName)) + .findAny() + .map(Cookie::getValue) + .orElseThrow(() -> new DTClientErrorException(ClientErrorCode.EMPTY_COOKIE)); + } +} diff --git a/src/main/java/com/debatetimer/controller/tool/cookie/CookieManager.java b/src/main/java/com/debatetimer/controller/tool/cookie/CookieManager.java new file mode 100644 index 00000000..c54ee6f8 --- /dev/null +++ b/src/main/java/com/debatetimer/controller/tool/cookie/CookieManager.java @@ -0,0 +1,31 @@ +package com.debatetimer.controller.tool.cookie; + +import com.debatetimer.controller.tool.jwt.JwtTokenProperties; +import jakarta.servlet.http.Cookie; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseCookie; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CookieManager { + + private static final String REFRESH_TOKEN_COOKIE_NAME = "refreshToken"; + + private final CookieProvider cookieProvider; + private final CookieExtractor cookieExtractor; + private final JwtTokenProperties jwtTokenProperties; + + public ResponseCookie createRefreshTokenCookie(String token) { + return cookieProvider.createCookie(REFRESH_TOKEN_COOKIE_NAME, token, + jwtTokenProperties.getRefreshTokenExpirationMillis()); + } + + public String extractRefreshToken(Cookie[] cookies) { + return cookieExtractor.extractCookie(REFRESH_TOKEN_COOKIE_NAME, cookies); + } + + public ResponseCookie deleteRefreshTokenCookie() { + return cookieProvider.deleteCookie(REFRESH_TOKEN_COOKIE_NAME); + } +} diff --git a/src/main/java/com/debatetimer/controller/tool/cookie/CookieProvider.java b/src/main/java/com/debatetimer/controller/tool/cookie/CookieProvider.java new file mode 100644 index 00000000..6fd4456d --- /dev/null +++ b/src/main/java/com/debatetimer/controller/tool/cookie/CookieProvider.java @@ -0,0 +1,29 @@ +package com.debatetimer.controller.tool.cookie; + +import java.time.Duration; +import org.springframework.http.ResponseCookie; +import org.springframework.stereotype.Component; + +@Component +public class CookieProvider { + + private static final String PATH = "/"; + + public ResponseCookie createCookie(String cookieName, String token, long expirationMillis) { + return ResponseCookie.from(cookieName, token) + .maxAge(Duration.ofMillis(expirationMillis)) + .path(PATH) + .sameSite("None") + .secure(true) + .build(); + } + + public ResponseCookie deleteCookie(String cookieName) { + return ResponseCookie.from(cookieName, "") + .maxAge(0) + .path(PATH) + .sameSite("None") + .secure(true) + .build(); + } +} diff --git a/src/main/java/com/debatetimer/controller/tool/jwt/AuthManager.java b/src/main/java/com/debatetimer/controller/tool/jwt/AuthManager.java new file mode 100644 index 00000000..2cc86a3d --- /dev/null +++ b/src/main/java/com/debatetimer/controller/tool/jwt/AuthManager.java @@ -0,0 +1,36 @@ +package com.debatetimer.controller.tool.jwt; + +import com.debatetimer.dto.member.JwtTokenResponse; +import com.debatetimer.dto.member.MemberInfo; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class AuthManager { + + private final JwtTokenProvider jwtTokenProvider; + private final JwtTokenResolver jwtTokenResolver; + + public JwtTokenResponse issueToken(MemberInfo memberInfo) { + String accessToken = jwtTokenProvider.createAccessToken(memberInfo); + String refreshToken = jwtTokenProvider.createRefreshToken(memberInfo); + return new JwtTokenResponse(accessToken, refreshToken); + } + + public JwtTokenResponse reissueToken(String refreshToken) { + String email = jwtTokenResolver.resolveRefreshToken(refreshToken); + MemberInfo memberInfo = new MemberInfo(email); + String accessToken = jwtTokenProvider.createAccessToken(memberInfo); + String newRefreshToken = jwtTokenProvider.createRefreshToken(memberInfo); + return new JwtTokenResponse(accessToken, newRefreshToken); + } + + public String resolveAccessToken(String accessToken) { + return jwtTokenResolver.resolveAccessToken(accessToken); + } + + public String resolveRefreshToken(String refreshToken) { + return jwtTokenResolver.resolveRefreshToken(refreshToken); + } +} diff --git a/src/main/java/com/debatetimer/controller/tool/jwt/JwtTokenProperties.java b/src/main/java/com/debatetimer/controller/tool/jwt/JwtTokenProperties.java new file mode 100644 index 00000000..95653df5 --- /dev/null +++ b/src/main/java/com/debatetimer/controller/tool/jwt/JwtTokenProperties.java @@ -0,0 +1,25 @@ +package com.debatetimer.controller.tool.jwt; + +import io.jsonwebtoken.security.Keys; +import javax.crypto.SecretKey; +import lombok.Getter; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@Getter +@ConfigurationProperties(prefix = "jwt") +public class JwtTokenProperties { + + private final String secretKey; + private final long accessTokenExpirationMillis; + private final long refreshTokenExpirationMillis; + + public JwtTokenProperties(String secretKey, long accessTokenExpirationMillis, long refreshTokenExpirationMillis) { + this.secretKey = secretKey; + this.accessTokenExpirationMillis = accessTokenExpirationMillis; + this.refreshTokenExpirationMillis = refreshTokenExpirationMillis; + } + + public SecretKey getSecretKey() { + return Keys.hmacShaKeyFor(secretKey.getBytes()); + } +} diff --git a/src/main/java/com/debatetimer/controller/tool/jwt/JwtTokenProvider.java b/src/main/java/com/debatetimer/controller/tool/jwt/JwtTokenProvider.java new file mode 100644 index 00000000..5b903884 --- /dev/null +++ b/src/main/java/com/debatetimer/controller/tool/jwt/JwtTokenProvider.java @@ -0,0 +1,36 @@ +package com.debatetimer.controller.tool.jwt; + +import com.debatetimer.dto.member.MemberInfo; +import io.jsonwebtoken.Jwts; +import java.util.Date; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class JwtTokenProvider { + + private final JwtTokenProperties jwtTokenProperties; + + public String createAccessToken(MemberInfo memberInfo) { + long accessTokenExpirationMillis = jwtTokenProperties.getAccessTokenExpirationMillis(); + return createToken(memberInfo, accessTokenExpirationMillis, TokenType.ACCESS_TOKEN); + } + + public String createRefreshToken(MemberInfo memberInfo) { + long refreshTokenExpirationMillis = jwtTokenProperties.getRefreshTokenExpirationMillis(); + return createToken(memberInfo, refreshTokenExpirationMillis, TokenType.REFRESH_TOKEN); + } + + private String createToken(MemberInfo memberInfo, long expirationMillis, TokenType tokenType) { + Date now = new Date(); + Date expiredDate = new Date(now.getTime() + expirationMillis); + return Jwts.builder() + .setSubject(memberInfo.email()) + .setIssuedAt(now) + .setExpiration(expiredDate) + .claim("type", tokenType.name()) + .signWith(jwtTokenProperties.getSecretKey()) + .compact(); + } +} diff --git a/src/main/java/com/debatetimer/controller/tool/jwt/JwtTokenResolver.java b/src/main/java/com/debatetimer/controller/tool/jwt/JwtTokenResolver.java new file mode 100644 index 00000000..de5d33f7 --- /dev/null +++ b/src/main/java/com/debatetimer/controller/tool/jwt/JwtTokenResolver.java @@ -0,0 +1,48 @@ +package com.debatetimer.controller.tool.jwt; + +import com.debatetimer.exception.custom.DTClientErrorException; +import com.debatetimer.exception.errorcode.ClientErrorCode; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class JwtTokenResolver { + + private final JwtTokenProperties jwtTokenProperties; + + public String resolveAccessToken(String accessToken) { + return resolveToken(accessToken, TokenType.ACCESS_TOKEN); + } + + public String resolveRefreshToken(String refreshToken) { + return resolveToken(refreshToken, TokenType.REFRESH_TOKEN); + } + + private String resolveToken(String token, TokenType tokenType) { + try { + Claims claims = Jwts.parserBuilder() + .setSigningKey(jwtTokenProperties.getSecretKey()) + .build() + .parseClaimsJws(token) + .getBody(); + validateTokenType(claims, tokenType); + return claims.getSubject(); + } catch (ExpiredJwtException exception) { + throw new DTClientErrorException(ClientErrorCode.EXPIRED_TOKEN); + } catch (JwtException exception) { + throw new DTClientErrorException(ClientErrorCode.UNAUTHORIZED_MEMBER); + } + } + + private void validateTokenType(Claims claims, TokenType tokenType) { + String extractTokenType = claims.get("type", String.class); + if (!extractTokenType.equals(tokenType.name())) { + throw new DTClientErrorException(ClientErrorCode.UNAUTHORIZED_MEMBER); + } + } +} diff --git a/src/main/java/com/debatetimer/controller/tool/jwt/TokenType.java b/src/main/java/com/debatetimer/controller/tool/jwt/TokenType.java new file mode 100644 index 00000000..1112e4b9 --- /dev/null +++ b/src/main/java/com/debatetimer/controller/tool/jwt/TokenType.java @@ -0,0 +1,7 @@ +package com.debatetimer.controller.tool.jwt; + +public enum TokenType { + + ACCESS_TOKEN, + REFRESH_TOKEN +} diff --git a/src/main/java/com/debatetimer/domain/member/Member.java b/src/main/java/com/debatetimer/domain/member/Member.java index 29b9a3d1..1366b91e 100644 --- a/src/main/java/com/debatetimer/domain/member/Member.java +++ b/src/main/java/com/debatetimer/domain/member/Member.java @@ -1,11 +1,11 @@ package com.debatetimer.domain.member; -import com.debatetimer.exception.custom.DTClientErrorException; -import com.debatetimer.exception.errorcode.ClientErrorCode; +import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotNull; import lombok.AccessLevel; import lombok.Getter; @@ -16,33 +16,25 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Member { - private static final String NICKNAME_REGEX = "^[a-zA-Z가-힣 ]+$"; - public static final int NICKNAME_MAX_LENGTH = 10; - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @Email + @Column(unique = true) @NotNull - private String nickname; + private String email; - public Member(long id, String nickname) { - validate(nickname); + public Member(long id, String email) { this.id = id; - this.nickname = nickname; + this.email = email; } - public Member(String nickname) { - validate(nickname); - this.nickname = nickname; + public Member(String email) { + this.email = email; } - private void validate(String nickname) { - if (nickname.isEmpty() || nickname.length() > NICKNAME_MAX_LENGTH) { - throw new DTClientErrorException(ClientErrorCode.INVALID_MEMBER_NICKNAME_LENGTH); - } - if (!nickname.matches(NICKNAME_REGEX)) { - throw new DTClientErrorException(ClientErrorCode.INVALID_MEMBER_NICKNAME_FORM); - } + public boolean isSameMember(String email) { + return this.email.equals(email); } } diff --git a/src/main/java/com/debatetimer/domain/parliamentary/ParliamentaryTable.java b/src/main/java/com/debatetimer/domain/parliamentary/ParliamentaryTable.java index 5304647e..e97dbd0e 100644 --- a/src/main/java/com/debatetimer/domain/parliamentary/ParliamentaryTable.java +++ b/src/main/java/com/debatetimer/domain/parliamentary/ParliamentaryTable.java @@ -39,15 +39,27 @@ public class ParliamentaryTable { @NotNull private String agenda; - @NotNull private int duration; - public ParliamentaryTable(Member member, String name, String agenda, int duration) { + private boolean warningBell; + + private boolean finishBell; + + public ParliamentaryTable( + Member member, + String name, + String agenda, + int duration, + boolean warningBell, + boolean finishBell + ) { validate(name, duration); this.member = member; this.name = name; this.agenda = agenda; this.duration = duration; + this.warningBell = warningBell; + this.finishBell = finishBell; } private void validate(String name, int duration) { @@ -66,6 +78,8 @@ public void update(ParliamentaryTable renewTable) { this.name = renewTable.getName(); this.agenda = renewTable.getAgenda(); this.duration = renewTable.getDuration(); + this.warningBell = renewTable.isWarningBell(); + this.finishBell = renewTable.isFinishBell(); } public boolean isOwner(long memberId) { diff --git a/src/main/java/com/debatetimer/dto/member/JwtTokenResponse.java b/src/main/java/com/debatetimer/dto/member/JwtTokenResponse.java new file mode 100644 index 00000000..dd109b43 --- /dev/null +++ b/src/main/java/com/debatetimer/dto/member/JwtTokenResponse.java @@ -0,0 +1,5 @@ +package com.debatetimer.dto.member; + +public record JwtTokenResponse(String accessToken, String refreshToken) { + +} diff --git a/src/main/java/com/debatetimer/dto/member/MemberCreateRequest.java b/src/main/java/com/debatetimer/dto/member/MemberCreateRequest.java index 561246e1..d9923d1a 100644 --- a/src/main/java/com/debatetimer/dto/member/MemberCreateRequest.java +++ b/src/main/java/com/debatetimer/dto/member/MemberCreateRequest.java @@ -1,11 +1,10 @@ package com.debatetimer.dto.member; -import com.debatetimer.domain.member.Member; import jakarta.validation.constraints.NotBlank; -public record MemberCreateRequest(@NotBlank String nickname) { +public record MemberCreateRequest( + @NotBlank String code, + @NotBlank String redirectUrl +) { - public Member toMember() { - return new Member(nickname); - } } diff --git a/src/main/java/com/debatetimer/dto/member/MemberCreateResponse.java b/src/main/java/com/debatetimer/dto/member/MemberCreateResponse.java index 7a4557ef..bde7ca76 100644 --- a/src/main/java/com/debatetimer/dto/member/MemberCreateResponse.java +++ b/src/main/java/com/debatetimer/dto/member/MemberCreateResponse.java @@ -2,9 +2,9 @@ import com.debatetimer.domain.member.Member; -public record MemberCreateResponse(long id, String nickname) { +public record MemberCreateResponse(long id, String email) { public MemberCreateResponse(Member member) { - this(member.getId(), member.getNickname()); + this(member.getId(), member.getEmail()); } } diff --git a/src/main/java/com/debatetimer/dto/member/MemberInfo.java b/src/main/java/com/debatetimer/dto/member/MemberInfo.java new file mode 100644 index 00000000..ecf509b6 --- /dev/null +++ b/src/main/java/com/debatetimer/dto/member/MemberInfo.java @@ -0,0 +1,16 @@ +package com.debatetimer.dto.member; + +import com.debatetimer.domain.member.Member; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public record MemberInfo(String email) { + + public MemberInfo(Member member) { + this(member.getEmail()); + } + + public Member toMember() { + return new Member(email); + } +} diff --git a/src/main/java/com/debatetimer/dto/member/OAuthToken.java b/src/main/java/com/debatetimer/dto/member/OAuthToken.java new file mode 100644 index 00000000..51805d45 --- /dev/null +++ b/src/main/java/com/debatetimer/dto/member/OAuthToken.java @@ -0,0 +1,8 @@ +package com.debatetimer.dto.member; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public record OAuthToken(String access_token) { + +} diff --git a/src/main/java/com/debatetimer/dto/parliamentary/request/ParliamentaryTableCreateRequest.java b/src/main/java/com/debatetimer/dto/parliamentary/request/ParliamentaryTableCreateRequest.java index 28bd6722..5d6b9f9e 100644 --- a/src/main/java/com/debatetimer/dto/parliamentary/request/ParliamentaryTableCreateRequest.java +++ b/src/main/java/com/debatetimer/dto/parliamentary/request/ParliamentaryTableCreateRequest.java @@ -10,7 +10,7 @@ public record ParliamentaryTableCreateRequest(TableInfoCreateRequest info, List table) { public ParliamentaryTable toTable(Member member) { - return info.toTable(member, sumOfTime()); + return info.toTable(member, sumOfTime(), info.warningBell(), info().finishBell()); } private int sumOfTime() { diff --git a/src/main/java/com/debatetimer/dto/parliamentary/request/TableInfoCreateRequest.java b/src/main/java/com/debatetimer/dto/parliamentary/request/TableInfoCreateRequest.java index dceed3df..390af80d 100644 --- a/src/main/java/com/debatetimer/dto/parliamentary/request/TableInfoCreateRequest.java +++ b/src/main/java/com/debatetimer/dto/parliamentary/request/TableInfoCreateRequest.java @@ -10,10 +10,13 @@ public record TableInfoCreateRequest( String name, @NotNull - String agenda + String agenda, + + boolean warningBell, + boolean finishBell ) { - public ParliamentaryTable toTable(Member member, int duration) { - return new ParliamentaryTable(member, name, agenda, duration); + public ParliamentaryTable toTable(Member member, int duration, boolean warningBell, boolean finishBell) { + return new ParliamentaryTable(member, name, agenda, duration, warningBell, finishBell); } } diff --git a/src/main/java/com/debatetimer/dto/parliamentary/response/TableInfoResponse.java b/src/main/java/com/debatetimer/dto/parliamentary/response/TableInfoResponse.java index e1369eb5..615cf99b 100644 --- a/src/main/java/com/debatetimer/dto/parliamentary/response/TableInfoResponse.java +++ b/src/main/java/com/debatetimer/dto/parliamentary/response/TableInfoResponse.java @@ -2,9 +2,14 @@ import com.debatetimer.domain.parliamentary.ParliamentaryTable; -public record TableInfoResponse(String name, String agenda) { +public record TableInfoResponse(String name, String agenda, boolean warningBell, boolean finishBell) { public TableInfoResponse(ParliamentaryTable parliamentaryTable) { - this(parliamentaryTable.getName(), parliamentaryTable.getAgenda()); + this( + parliamentaryTable.getName(), + parliamentaryTable.getAgenda(), + parliamentaryTable.isWarningBell(), + parliamentaryTable.isFinishBell() + ); } } diff --git a/src/main/java/com/debatetimer/exception/errorcode/ClientErrorCode.java b/src/main/java/com/debatetimer/exception/errorcode/ClientErrorCode.java index bdaf2be2..db0ce1bf 100644 --- a/src/main/java/com/debatetimer/exception/errorcode/ClientErrorCode.java +++ b/src/main/java/com/debatetimer/exception/errorcode/ClientErrorCode.java @@ -1,6 +1,5 @@ package com.debatetimer.exception.errorcode; -import com.debatetimer.domain.member.Member; import com.debatetimer.domain.parliamentary.ParliamentaryTable; import lombok.Getter; import org.springframework.http.HttpStatus; @@ -8,12 +7,6 @@ @Getter public enum ClientErrorCode implements ErrorCode { - INVALID_MEMBER_NICKNAME_LENGTH( - HttpStatus.BAD_REQUEST, - "닉네임은 1자 이상 %d자 이하여야 합니다".formatted(Member.NICKNAME_MAX_LENGTH) - ), - INVALID_MEMBER_NICKNAME_FORM(HttpStatus.BAD_REQUEST, "닉네임은 영문/한글만 가능합니다"), - INVALID_TABLE_NAME_LENGTH( HttpStatus.BAD_REQUEST, "테이블 이름은 1자 이상 %d자 이하여야 합니다".formatted(ParliamentaryTable.NAME_MAX_LENGTH) @@ -39,6 +32,8 @@ public enum ClientErrorCode implements ErrorCode { TABLE_NOT_FOUND(HttpStatus.NOT_FOUND, "토론 테이블을 찾을 수 없습니다."), NOT_TABLE_OWNER(HttpStatus.UNAUTHORIZED, "테이블을 소유한 회원이 아닙니다."), UNAUTHORIZED_MEMBER(HttpStatus.UNAUTHORIZED, "접근 권한이 없습니다"), + EXPIRED_TOKEN(HttpStatus.UNAUTHORIZED, "토큰 기한이 만료되었습니다"), + EMPTY_COOKIE(HttpStatus.UNAUTHORIZED, "쿠키에 값이 없습니다"), MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 회원이 존재하지 않습니다"), ; diff --git a/src/main/java/com/debatetimer/repository/member/MemberRepository.java b/src/main/java/com/debatetimer/repository/member/MemberRepository.java index de78ccff..e09d370c 100644 --- a/src/main/java/com/debatetimer/repository/member/MemberRepository.java +++ b/src/main/java/com/debatetimer/repository/member/MemberRepository.java @@ -17,5 +17,10 @@ default Member getById(long id) { .orElseThrow(() -> new DTClientErrorException(ClientErrorCode.MEMBER_NOT_FOUND)); } - Optional findByNickname(String nickname); + Optional findByEmail(String email); + + default Member getByEmail(String email) { + return findByEmail(email) + .orElseThrow(() -> new DTClientErrorException(ClientErrorCode.MEMBER_NOT_FOUND)); + } } diff --git a/src/main/java/com/debatetimer/repository/parliamentary/ParliamentaryTimeBoxRepository.java b/src/main/java/com/debatetimer/repository/parliamentary/ParliamentaryTimeBoxRepository.java index c641c242..a2ebb2d9 100644 --- a/src/main/java/com/debatetimer/repository/parliamentary/ParliamentaryTimeBoxRepository.java +++ b/src/main/java/com/debatetimer/repository/parliamentary/ParliamentaryTimeBoxRepository.java @@ -28,7 +28,7 @@ default ParliamentaryTimeBoxes findTableTimeBoxes(ParliamentaryTable table) { } @Query("DELETE FROM ParliamentaryTimeBox ptb WHERE ptb IN :timeBoxes") - @Modifying(clearAutomatically = true) + @Modifying(clearAutomatically = true, flushAutomatically = true) @Transactional void deleteAll(List timeBoxes); } diff --git a/src/main/java/com/debatetimer/service/auth/AuthService.java b/src/main/java/com/debatetimer/service/auth/AuthService.java new file mode 100644 index 00000000..aec9e8e1 --- /dev/null +++ b/src/main/java/com/debatetimer/service/auth/AuthService.java @@ -0,0 +1,35 @@ +package com.debatetimer.service.auth; + +import com.debatetimer.client.OAuthClient; +import com.debatetimer.domain.member.Member; +import com.debatetimer.dto.member.MemberCreateRequest; +import com.debatetimer.dto.member.MemberInfo; +import com.debatetimer.dto.member.OAuthToken; +import com.debatetimer.exception.custom.DTClientErrorException; +import com.debatetimer.exception.errorcode.ClientErrorCode; +import com.debatetimer.repository.member.MemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class AuthService { + + private final OAuthClient oauthClient; + private final MemberRepository memberRepository; + + public MemberInfo getMemberInfo(MemberCreateRequest request) { + OAuthToken oauthToken = oauthClient.requestToken(request); + return oauthClient.requestMemberInfo(oauthToken); + } + + public Member getMember(String email) { + return memberRepository.getByEmail(email); + } + + public void logout(Member member, String email) { + if (!member.isSameMember(email)) { + throw new DTClientErrorException(ClientErrorCode.UNAUTHORIZED_MEMBER); + } + } +} diff --git a/src/main/java/com/debatetimer/service/member/MemberService.java b/src/main/java/com/debatetimer/service/member/MemberService.java index 8bfec683..06796d00 100644 --- a/src/main/java/com/debatetimer/service/member/MemberService.java +++ b/src/main/java/com/debatetimer/service/member/MemberService.java @@ -2,8 +2,8 @@ import com.debatetimer.domain.member.Member; import com.debatetimer.domain.parliamentary.ParliamentaryTable; -import com.debatetimer.dto.member.MemberCreateRequest; import com.debatetimer.dto.member.MemberCreateResponse; +import com.debatetimer.dto.member.MemberInfo; import com.debatetimer.dto.member.TableResponses; import com.debatetimer.repository.member.MemberRepository; import com.debatetimer.repository.parliamentary.ParliamentaryTableRepository; @@ -27,10 +27,9 @@ public TableResponses getTables(Long memberId) { } @Transactional - public MemberCreateResponse createMember(MemberCreateRequest request) { - // TODO OAuth 로직 들어오면서 수정 예정 - Member member = memberRepository.findByNickname(request.nickname()) - .orElseGet(() -> memberRepository.save(request.toMember())); + public MemberCreateResponse createMember(MemberInfo memberInfo) { + Member member = memberRepository.findByEmail(memberInfo.email()) + .orElseGet(() -> memberRepository.save(memberInfo.toMember())); return new MemberCreateResponse(member); } } diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 01065082..5aa0987d 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -11,9 +11,21 @@ spring: properties: hibernate: format_sql: true + dialect: org.hibernate.dialect.MySQLDialect hibernate: ddl-auto: update defer-datasource-initialization: true cors: origin: ${secret.cors.origin} + +oauth: + client_id: ${secret.oauth.client_id} + client_secret: ${secret.oauth.client_secret} + redirect_uri: ${secret.oauth.redirect_uri} + grant_type: ${secret.oauth.grant_type} + +jwt: + secret_key: ${secret.jwt.secret_key} + access_token_expiration_millis: ${secret.jwt.access_token_expiration_millis} + refresh_token_expiration_millis: ${secret.jwt.refresh_token_expiration_millis} diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 751b8a26..6c0b99ea 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -18,4 +18,4 @@ spring: defer-datasource-initialization: true cors: - origin: * + origin: '*' diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 1dce30a9..c23b5bde 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -17,3 +17,15 @@ spring: cors: origin: ${secret.cors.origin} + +oauth: + client_id: ${secret.oauth.client_id} + client_secret: ${secret.oauth.client_secret} + redirect_uri: ${secret.oauth.redirect_uri} + grant_type: ${secret.oauth.grant_type} + +jwt: + secret_key: ${secret.jwt.secret_key} + access_token_expiration_millis: ${secret.jwt.access_token_expiration_millis} + refresh_token_expiration_millis: ${secret.jwt.refresh_token_expiration_millis} + diff --git a/src/test/java/com/debatetimer/controller/BaseControllerTest.java b/src/test/java/com/debatetimer/controller/BaseControllerTest.java index 0885791e..17084b51 100644 --- a/src/test/java/com/debatetimer/controller/BaseControllerTest.java +++ b/src/test/java/com/debatetimer/controller/BaseControllerTest.java @@ -1,9 +1,13 @@ package com.debatetimer.controller; import com.debatetimer.DataBaseCleaner; +import com.debatetimer.client.OAuthClient; +import com.debatetimer.fixture.CookieGenerator; +import com.debatetimer.fixture.HeaderGenerator; import com.debatetimer.fixture.MemberGenerator; import com.debatetimer.fixture.ParliamentaryTableGenerator; import com.debatetimer.fixture.ParliamentaryTimeBoxGenerator; +import com.debatetimer.fixture.TokenGenerator; import com.debatetimer.repository.member.MemberRepository; import com.debatetimer.repository.parliamentary.ParliamentaryTableRepository; import io.restassured.RestAssured; @@ -16,8 +20,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.restdocs.RestDocumentationContextProvider; -import org.springframework.restdocs.restassured.RestDocumentationFilter; +import org.springframework.test.context.bean.override.mockito.MockitoBean; @ExtendWith(DataBaseCleaner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @@ -38,6 +41,18 @@ public abstract class BaseControllerTest { @Autowired protected ParliamentaryTimeBoxGenerator timeBoxGenerator; + @Autowired + protected HeaderGenerator headerGenerator; + + @Autowired + protected CookieGenerator cookieGenerator; + + @Autowired + protected TokenGenerator tokenGenerator; + + @MockitoBean + protected OAuthClient oAuthClient; + @LocalServerPort private int port; diff --git a/src/test/java/com/debatetimer/controller/BaseDocumentTest.java b/src/test/java/com/debatetimer/controller/BaseDocumentTest.java index 46c879a9..f7d69304 100644 --- a/src/test/java/com/debatetimer/controller/BaseDocumentTest.java +++ b/src/test/java/com/debatetimer/controller/BaseDocumentTest.java @@ -1,24 +1,32 @@ package com.debatetimer.controller; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.doReturn; import static org.springframework.restdocs.payload.JsonFieldType.STRING; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import com.debatetimer.controller.tool.cookie.CookieManager; +import com.debatetimer.controller.tool.jwt.AuthManager; import com.debatetimer.domain.member.Member; +import com.debatetimer.dto.member.JwtTokenResponse; import com.debatetimer.exception.errorcode.ClientErrorCode; -import com.debatetimer.repository.member.MemberRepository; +import com.debatetimer.service.auth.AuthService; import com.debatetimer.service.member.MemberService; import com.debatetimer.service.parliamentary.ParliamentaryService; import io.restassured.RestAssured; import io.restassured.builder.RequestSpecBuilder; import io.restassured.filter.log.RequestLoggingFilter; import io.restassured.filter.log.ResponseLoggingFilter; +import io.restassured.http.Header; +import io.restassured.http.Headers; import io.restassured.specification.RequestSpecification; +import jakarta.servlet.http.Cookie; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseCookie; import org.springframework.restdocs.RestDocumentationContextProvider; import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.restdocs.restassured.RestAssuredRestDocumentation; @@ -31,22 +39,37 @@ public abstract class BaseDocumentTest { protected static long EXIST_MEMBER_ID = 123L; - protected static Member EXIST_MEMBER = new Member(EXIST_MEMBER_ID, "존재하는 멤버"); + protected static String EXIST_MEMBER_EMAIL = "abcde@gmail.com"; + protected static Member EXIST_MEMBER = new Member(EXIST_MEMBER_ID, EXIST_MEMBER_EMAIL); + protected static String EXIST_MEMBER_ACCESS_TOKEN = "dflskgnkds"; + protected static String EXIST_MEMBER_REFRESH_TOKEN = "dfsfsdgrs"; + protected static JwtTokenResponse EXIST_MEMBER_TOKEN_RESPONSE = new JwtTokenResponse(EXIST_MEMBER_ACCESS_TOKEN, + EXIST_MEMBER_REFRESH_TOKEN); + protected static Headers EXIST_MEMBER_HEADER = new Headers( + new Header(HttpHeaders.AUTHORIZATION, EXIST_MEMBER_ACCESS_TOKEN)); + protected static Cookie EXIST_MEMBER_COOKIE = new Cookie("refreshToken", EXIST_MEMBER_REFRESH_TOKEN); + protected static Cookie DELETE_MEMBER_COOKIE = new Cookie("refreshToken", ""); protected static RestDocumentationResponse ERROR_RESPONSE = new RestDocumentationResponse() .responseBodyField( fieldWithPath("message").type(STRING).description("에러 메시지") ); - @MockitoBean - private MemberRepository memberRepository; - @MockitoBean protected MemberService memberService; @MockitoBean protected ParliamentaryService parliamentaryService; + @MockitoBean + protected AuthService authService; + + @MockitoBean + protected AuthManager authManager; + + @MockitoBean + protected CookieManager cookieManager; + @LocalServerPort private int port; @@ -70,7 +93,17 @@ private void setRestAssured(RestDocumentationContextProvider restDocumentation) } private void setLoginMember() { - when(memberRepository.getById(EXIST_MEMBER_ID)).thenReturn(EXIST_MEMBER); + doReturn(EXIST_MEMBER_EMAIL).when(authManager).resolveAccessToken(EXIST_MEMBER_ACCESS_TOKEN); + doReturn(EXIST_MEMBER).when(authService).getMember(EXIST_MEMBER_EMAIL); + } + + protected ResponseCookie responseCookie(String token, int maxAge) { + return ResponseCookie.from("refreshToken", token) + .path("/") + .maxAge(maxAge) + .sameSite("None") + .secure(true) + .build(); } protected RestDocumentationRequest request() { diff --git a/src/test/java/com/debatetimer/controller/GlobalControllerTest.java b/src/test/java/com/debatetimer/controller/GlobalControllerTest.java index d4347a38..6c9c041f 100644 --- a/src/test/java/com/debatetimer/controller/GlobalControllerTest.java +++ b/src/test/java/com/debatetimer/controller/GlobalControllerTest.java @@ -25,7 +25,8 @@ class CorsConfigTest { .when().options("/") .then().statusCode(200) .headers("Access-Control-Allow-Origin", corsOrigin) - .header("Access-Control-Allow-Methods", containsString(allowedMethod)); + .header("Access-Control-Allow-Methods", containsString(allowedMethod)) + .header("Access-Control-Expose-Headers", containsString("Authorization")); } @Test diff --git a/src/test/java/com/debatetimer/controller/RestDocumentationRequest.java b/src/test/java/com/debatetimer/controller/RestDocumentationRequest.java index 6157034f..372422d5 100644 --- a/src/test/java/com/debatetimer/controller/RestDocumentationRequest.java +++ b/src/test/java/com/debatetimer/controller/RestDocumentationRequest.java @@ -1,5 +1,6 @@ package com.debatetimer.controller; +import static org.springframework.restdocs.cookies.CookieDocumentation.requestCookies; import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; @@ -8,6 +9,7 @@ import com.epages.restdocs.apispec.ResourceSnippetParametersBuilder; import java.util.LinkedList; import java.util.List; +import org.springframework.restdocs.cookies.CookieDescriptor; import org.springframework.restdocs.headers.HeaderDescriptor; import org.springframework.restdocs.payload.FieldDescriptor; import org.springframework.restdocs.request.ParameterDescriptor; @@ -48,6 +50,11 @@ public RestDocumentationRequest requestHeader(HeaderDescriptor... descriptors) { return this; } + public RestDocumentationRequest requestCookie(CookieDescriptor... descriptors) { + snippets.add(requestCookies(descriptors)); + return this; + } + public RestDocumentationRequest requestBodyField(FieldDescriptor... descriptors) { snippets.add(requestFields(descriptors)); return this; diff --git a/src/test/java/com/debatetimer/controller/RestDocumentationResponse.java b/src/test/java/com/debatetimer/controller/RestDocumentationResponse.java index 2e25a24c..1ddfe4d7 100644 --- a/src/test/java/com/debatetimer/controller/RestDocumentationResponse.java +++ b/src/test/java/com/debatetimer/controller/RestDocumentationResponse.java @@ -1,10 +1,12 @@ package com.debatetimer.controller; +import static org.springframework.restdocs.cookies.CookieDocumentation.responseCookies; import static org.springframework.restdocs.headers.HeaderDocumentation.responseHeaders; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import java.util.LinkedList; import java.util.List; +import org.springframework.restdocs.cookies.CookieDescriptor; import org.springframework.restdocs.headers.HeaderDescriptor; import org.springframework.restdocs.payload.FieldDescriptor; import org.springframework.restdocs.snippet.Snippet; @@ -22,6 +24,11 @@ public RestDocumentationResponse responseHeader(HeaderDescriptor... descriptors) return this; } + public RestDocumentationResponse responseCookie(CookieDescriptor... descriptors) { + snippets.add(responseCookies(descriptors)); + return this; + } + public RestDocumentationResponse responseBodyField(FieldDescriptor... descriptors) { snippets.add(responseFields(descriptors)); return this; diff --git a/src/test/java/com/debatetimer/controller/member/MemberControllerTest.java b/src/test/java/com/debatetimer/controller/member/MemberControllerTest.java index a0251731..8f463980 100644 --- a/src/test/java/com/debatetimer/controller/member/MemberControllerTest.java +++ b/src/test/java/com/debatetimer/controller/member/MemberControllerTest.java @@ -1,54 +1,92 @@ package com.debatetimer.controller.member; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.doReturn; import com.debatetimer.controller.BaseControllerTest; import com.debatetimer.domain.member.Member; import com.debatetimer.domain.parliamentary.ParliamentaryTable; import com.debatetimer.dto.member.MemberCreateRequest; -import com.debatetimer.dto.member.MemberCreateResponse; +import com.debatetimer.dto.member.MemberInfo; +import com.debatetimer.dto.member.OAuthToken; import com.debatetimer.dto.member.TableResponses; import io.restassured.http.ContentType; +import io.restassured.http.Headers; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; class MemberControllerTest extends BaseControllerTest { + @Nested + class GetTables { + + @Test + void 회원의_전체_토론_시간표를_조회한다() { + Member member = memberGenerator.generate("default@gmail.com"); + parliamentaryTableRepository.save(new ParliamentaryTable(member, "토론 시간표 A", "주제", 1800, false, false)); + parliamentaryTableRepository.save(new ParliamentaryTable(member, "토론 시간표 B", "주제", 1900, false, false)); + + Headers headers = headerGenerator.generateAccessTokenHeader(member); + + TableResponses response = given() + .contentType(ContentType.JSON) + .headers(headers) + .when().get("/api/table") + .then().statusCode(200) + .extract().as(TableResponses.class); + + assertThat(response.tables()).hasSize(2); + } + } + @Nested class CreateMember { @Test void 회원을_생성한다() { - MemberCreateRequest request = new MemberCreateRequest("커찬"); + MemberCreateRequest request = new MemberCreateRequest("gnkldsnglnksl", "http://redirectUrl"); + OAuthToken oAuthToken = new OAuthToken("accessToken"); + MemberInfo memberInfo = new MemberInfo("default@gmail.com"); + doReturn(oAuthToken).when(oAuthClient).requestToken(request); + doReturn(memberInfo).when(oAuthClient).requestMemberInfo(oAuthToken); - MemberCreateResponse response = given() + given() .contentType(ContentType.JSON) .body(request) .when().post("/api/member") - .then().statusCode(201) - .extract().as(MemberCreateResponse.class); - - assertThat(response.nickname()).isEqualTo(request.nickname()); + .then().statusCode(201); } } @Nested - class GetTables { + class ReissueAccessToken { @Test - void 회원의_전체_토론_시간표를_조회한다() { - Member member = memberRepository.save(new Member("커찬")); - parliamentaryTableRepository.save(new ParliamentaryTable(member, "토론 시간표 A", "주제", 1800)); - parliamentaryTableRepository.save(new ParliamentaryTable(member, "토론 시간표 B", "주제", 1900)); + void 토큰을_갱신한다() { + Member bito = memberGenerator.generate("bito@gmail.com"); + String refreshToken = tokenGenerator.generateRefreshToken(bito.getEmail()); - TableResponses response = given() - .contentType(ContentType.JSON) - .queryParam("memberId", member.getId()) - .when().get("/api/table") - .then().statusCode(200) - .extract().as(TableResponses.class); + given() + .cookie("refreshToken", refreshToken) + .when().post("/api/member/reissue") + .then().statusCode(200); + } + } - assertThat(response.tables()).hasSize(2); + @Nested + class Logout { + + @Test + void 로그아웃한다() { + Member bito = memberGenerator.generate("bito@gmail.com"); + Headers headers = headerGenerator.generateAccessTokenHeader(bito); + String refreshToken = tokenGenerator.generateRefreshToken(bito.getEmail()); + + given() + .cookie("refreshToken", refreshToken) + .headers(headers) + .when().post("/api/member/logout") + .then().statusCode(204); } } } diff --git a/src/test/java/com/debatetimer/controller/member/MemberDocumentTest.java b/src/test/java/com/debatetimer/controller/member/MemberDocumentTest.java index a9edd6d2..c4504b6f 100644 --- a/src/test/java/com/debatetimer/controller/member/MemberDocumentTest.java +++ b/src/test/java/com/debatetimer/controller/member/MemberDocumentTest.java @@ -1,11 +1,14 @@ package com.debatetimer.controller.member; -import static org.mockito.Mockito.when; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.springframework.restdocs.cookies.CookieDocumentation.cookieWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; import static org.springframework.restdocs.payload.JsonFieldType.ARRAY; import static org.springframework.restdocs.payload.JsonFieldType.NUMBER; import static org.springframework.restdocs.payload.JsonFieldType.STRING; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; -import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import com.debatetimer.controller.BaseDocumentTest; import com.debatetimer.controller.RestDocumentationRequest; @@ -13,6 +16,7 @@ import com.debatetimer.controller.Tag; import com.debatetimer.dto.member.MemberCreateRequest; import com.debatetimer.dto.member.MemberCreateResponse; +import com.debatetimer.dto.member.MemberInfo; import com.debatetimer.dto.member.TableResponse; import com.debatetimer.dto.member.TableResponses; import com.debatetimer.dto.member.TableType; @@ -24,6 +28,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; +import org.springframework.http.HttpHeaders; public class MemberDocumentTest extends BaseDocumentTest { @@ -34,25 +39,27 @@ class CreateMember { .tag(Tag.MEMBER_API) .summary("멤버 생성") .requestBodyField( - fieldWithPath("nickname").type(STRING).description("멤버 닉네임") + fieldWithPath("code").type(STRING).description("인가 코드"), + fieldWithPath("redirectUrl").type(STRING).description("리다이렉트 URL") ); private final RestDocumentationResponse responseDocument = response() .responseBodyField( fieldWithPath("id").type(NUMBER).description("멤버 ID"), - fieldWithPath("nickname").type(STRING).description("멤버 닉네임") + fieldWithPath("email").type(STRING).description("멤버 이메일") ); @Test void 회원_생성_성공() { - MemberCreateRequest request = new MemberCreateRequest("커찬"); - MemberCreateResponse response = new MemberCreateResponse(1L, "커찬"); - when(memberService.createMember(request)).thenReturn(response); + MemberCreateRequest request = new MemberCreateRequest("dfsfgdsg", "http://redirectUrl"); + MemberInfo memberInfo = new MemberInfo(EXIST_MEMBER_EMAIL); + MemberCreateResponse response = new MemberCreateResponse(EXIST_MEMBER_ID, EXIST_MEMBER_EMAIL); + doReturn(memberInfo).when(authService).getMemberInfo(request); + doReturn(response).when(memberService).createMember(memberInfo); + doReturn(EXIST_MEMBER_TOKEN_RESPONSE).when(authManager).issueToken(memberInfo); + doReturn(responseCookie(EXIST_MEMBER_REFRESH_TOKEN, 500)).when(cookieManager).createRefreshTokenCookie(EXIST_MEMBER_REFRESH_TOKEN); - var document = document("member/create", 201) - .request(requestDocument) - .response(responseDocument) - .build(); + var document = document("member/create", 201).request(requestDocument).response(responseDocument).build(); given(document) .contentType(ContentType.JSON) @@ -60,82 +67,188 @@ class CreateMember { .when().post("/api/member") .then().statusCode(201); } + } + + @Nested + class GetTables { + + private final RestDocumentationRequest requestDocument = request() + .tag(Tag.MEMBER_API) + .summary("멤버의 토론 시간표 조회") + .requestHeader( + headerWithName(HttpHeaders.AUTHORIZATION).description("액세스 토큰") + ); - @EnumSource( - value = ClientErrorCode.class, - names = {"INVALID_MEMBER_NICKNAME_LENGTH", "INVALID_MEMBER_NICKNAME_FORM"} - ) + private final RestDocumentationResponse responseDocument = response().responseBodyField( + fieldWithPath("tables").type(ARRAY).description("멤버의 토론 테이블들"), + fieldWithPath("tables[].id").type(NUMBER).description("토론 테이블 ID (토론 타입 별로 ID를 가짐)"), + fieldWithPath("tables[].name").type(STRING).description("토론 테이블 이름"), + fieldWithPath("tables[].type").type(STRING).description("토론 타입"), + fieldWithPath("tables[].duration").type(NUMBER).description("소요 시간 (초)")); + + @Test + void 테이블_조회_성공() { + TableResponses response = new TableResponses( + List.of(new TableResponse(1L, "토론 테이블 1", TableType.PARLIAMENTARY, 1800), + new TableResponse(2L, "토론 테이블 2", TableType.PARLIAMENTARY, 2000)) + ); + doReturn(response).when(memberService).getTables(EXIST_MEMBER_ID); + + var document = document("member/table", 200) + .request(requestDocument) + .response(responseDocument) + .build(); + + given(document) + .headers(EXIST_MEMBER_HEADER) + .when().get("/api/table") + .then().statusCode(200); + } + + @EnumSource(value = ClientErrorCode.class, names = {"MEMBER_NOT_FOUND"}) @ParameterizedTest - void 회원_생성_실패(ClientErrorCode errorCode) { - MemberCreateRequest request = new MemberCreateRequest("커찬"); - when(memberService.createMember(request)).thenThrow(new DTClientErrorException(errorCode)); + void 테이블_조회_실패(ClientErrorCode errorCode) { + doThrow(new DTClientErrorException(errorCode)).when(memberService).getTables(EXIST_MEMBER_ID); - var document = document("member/create", errorCode) + var document = document("member/table", errorCode) .request(requestDocument) .response(ERROR_RESPONSE) .build(); given(document) .contentType(ContentType.JSON) - .body(request) - .when().post("/api/member") + .headers(EXIST_MEMBER_HEADER) + .when().get("/api/table") .then().statusCode(errorCode.getStatus().value()); } } @Nested - class GetTables { - + class ReissueAccessToken { private final RestDocumentationRequest requestDocument = request() .tag(Tag.MEMBER_API) - .summary("멤버의 토론 시간표 조회") - .queryParameter( - parameterWithName("memberId").description("멤버 ID") + .summary("토큰 갱신") + .requestCookie( + cookieWithName("refreshToken").description("리프레시 토큰") ); private final RestDocumentationResponse responseDocument = response() - .responseBodyField( - fieldWithPath("tables").type(ARRAY).description("멤버의 토론 테이블들"), - fieldWithPath("tables[].id").type(NUMBER).description("토론 테이블 ID (토론 타입 별로 ID를 가짐)"), - fieldWithPath("tables[].name").type(STRING).description("토론 테이블 이름"), - fieldWithPath("tables[].type").type(STRING).description("토론 타입"), - fieldWithPath("tables[].duration").type(NUMBER).description("소요 시간 (초)") + .responseHeader( + headerWithName(HttpHeaders.AUTHORIZATION).description("액세스 토큰") + ) + .responseCookie( + cookieWithName("refreshToken").description("리프레시 토큰") ); @Test - void 테이블_조회_성공() { - TableResponses response = new TableResponses(List.of( - new TableResponse(1L, "토론 테이블 1", TableType.PARLIAMENTARY, 1800), - new TableResponse(2L, "토론 테이블 2", TableType.PARLIAMENTARY, 2000) - )); - when(memberService.getTables(EXIST_MEMBER_ID)).thenReturn(response); + void 토큰_갱신_성공() { + doReturn(EXIST_MEMBER_TOKEN_RESPONSE).when(authManager).reissueToken(any()); + doReturn(responseCookie(EXIST_MEMBER_REFRESH_TOKEN, 500)).when(cookieManager).createRefreshTokenCookie(any()); - var document = document("member/table", 200) + var document = document("member/logout", 204) .request(requestDocument) .response(responseDocument) .build(); given(document) - .contentType(ContentType.JSON) - .queryParam("memberId", EXIST_MEMBER_ID) - .when().get("/api/table") + .headers(EXIST_MEMBER_HEADER) + .cookie("refreshToken") + .when().post("/api/member/reissue") .then().statusCode(200); } - @EnumSource(value = ClientErrorCode.class, names = {"MEMBER_NOT_FOUND"}) + @EnumSource(value = ClientErrorCode.class, names = {"EMPTY_COOKIE"}) @ParameterizedTest - void 테이블_조회_실패(ClientErrorCode errorCode) { - when(memberService.getTables(EXIST_MEMBER_ID)).thenThrow(new DTClientErrorException(errorCode)); + void 토큰_갱신_실패_쿠키_추출(ClientErrorCode errorCode) { + doThrow(new DTClientErrorException(errorCode)).when(cookieManager).extractRefreshToken(any()); - var document = document("member/table", errorCode) + var document = document("member/reissue", errorCode) .request(requestDocument) .response(ERROR_RESPONSE) .build(); given(document) - .contentType(ContentType.JSON) - .queryParam("memberId", EXIST_MEMBER_ID) - .when().get("/api/table") + .cookie("refreshToken") + .when().post("/api/member/reissue") + .then().statusCode(errorCode.getStatus().value()); + } + + @EnumSource(value = ClientErrorCode.class, names = {"EXPIRED_TOKEN", "UNAUTHORIZED_MEMBER"}) + @ParameterizedTest + void 토큰_갱신_실패_토큰_갱신(ClientErrorCode errorCode) { + doThrow(new DTClientErrorException(errorCode)).when(authManager).reissueToken(any()); + + var document = document("member/reissue", errorCode) + .request(requestDocument) + .response(ERROR_RESPONSE) + .build(); + + given(document) + .cookie("refreshToken") + .when().post("/api/member/reissue") + .then().statusCode(errorCode.getStatus().value()); + } + } + + @Nested + class Logout { + + private final RestDocumentationRequest requestDocument = request() + .tag(Tag.MEMBER_API) + .summary("로그아웃") + .requestHeader( + headerWithName(HttpHeaders.AUTHORIZATION).description("액세스 토큰") + ) + .requestCookie( + cookieWithName("refreshToken").description("리프레시 토큰") + ); + + @Test + void 로그아웃_성공() { + doReturn(responseCookie(EXIST_MEMBER_REFRESH_TOKEN, 0)).when(cookieManager).deleteRefreshTokenCookie(); + + var document = document("member/logout", 204) + .request(requestDocument) + .build(); + + given(document) + .headers(EXIST_MEMBER_HEADER) + .cookie("refreshToken") + .when().post("/api/member/logout") + .then().statusCode(204); + } + + @EnumSource(value = ClientErrorCode.class, names = {"EMPTY_COOKIE"}) + @ParameterizedTest + void 로그아웃_실패_쿠키_추출(ClientErrorCode errorCode) { + doThrow(new DTClientErrorException(errorCode)).when(cookieManager).extractRefreshToken(any()); + + var document = document("member/logout", errorCode) + .request(requestDocument) + .response(ERROR_RESPONSE) + .build(); + + given(document) + .headers(EXIST_MEMBER_HEADER) + .cookie("refreshToken") + .when().post("/api/member/logout") + .then().statusCode(errorCode.getStatus().value()); + } + + @EnumSource(value = ClientErrorCode.class, names = {"UNAUTHORIZED_MEMBER", "EXPIRED_TOKEN"}) + @ParameterizedTest + void 로그아웃_실패(ClientErrorCode errorCode) { + doThrow(new DTClientErrorException(errorCode)).when(authService).logout(any(), any()); + + var document = document("member/logout", errorCode) + .request(requestDocument) + .response(ERROR_RESPONSE) + .build(); + + given(document) + .headers(EXIST_MEMBER_HEADER) + .cookie("refreshToken") + .when().post("/api/member/logout") .then().statusCode(errorCode.getStatus().value()); } } diff --git a/src/test/java/com/debatetimer/controller/parliamentary/ParliamentaryControllerTest.java b/src/test/java/com/debatetimer/controller/parliamentary/ParliamentaryControllerTest.java index 2a13970a..fdf092b3 100644 --- a/src/test/java/com/debatetimer/controller/parliamentary/ParliamentaryControllerTest.java +++ b/src/test/java/com/debatetimer/controller/parliamentary/ParliamentaryControllerTest.java @@ -13,6 +13,7 @@ import com.debatetimer.dto.parliamentary.request.TimeBoxCreateRequest; import com.debatetimer.dto.parliamentary.response.ParliamentaryTableResponse; import io.restassured.http.ContentType; +import io.restassured.http.Headers; import java.util.List; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -24,18 +25,19 @@ class Save { @Test void 의회식_테이블을_생성한다() { - Member bito = memberGenerator.generate("비토"); + Member bito = memberGenerator.generate("default@gmail.com"); ParliamentaryTableCreateRequest request = new ParliamentaryTableCreateRequest( - new TableInfoCreateRequest("비토 테이블", "주제"), + new TableInfoCreateRequest("비토 테이블", "주제", true, true), List.of( new TimeBoxCreateRequest(Stance.PROS, BoxType.OPENING, 3, 1), new TimeBoxCreateRequest(Stance.CONS, BoxType.OPENING, 3, 1) ) ); + Headers headers = headerGenerator.generateAccessTokenHeader(bito); ParliamentaryTableResponse response = given() .contentType(ContentType.JSON) - .queryParam("memberId", bito.getId()) + .headers(headers) .body(request) .when().post("/api/table/parliamentary") .then().statusCode(201) @@ -53,15 +55,16 @@ class GetTable { @Test void 의회식_테이블을_조회한다() { - Member bito = memberGenerator.generate("비토"); + Member bito = memberGenerator.generate("default@gmail.com"); ParliamentaryTable bitoTable = tableGenerator.generate(bito); timeBoxGenerator.generate(bitoTable, 1); timeBoxGenerator.generate(bitoTable, 2); + Headers headers = headerGenerator.generateAccessTokenHeader(bito); ParliamentaryTableResponse response = given() .contentType(ContentType.JSON) .pathParam("tableId", bitoTable.getId()) - .queryParam("memberId", bito.getId()) + .headers(headers) .when().get("/api/table/parliamentary/{tableId}") .then().statusCode(200) .extract().as(ParliamentaryTableResponse.class); @@ -78,22 +81,21 @@ class UpdateTable { @Test void 의회식_토론_테이블을_업데이트한다() { - Member bito = memberGenerator.generate("비토"); + Member bito = memberGenerator.generate("default@gmail.com"); ParliamentaryTable bitoTable = tableGenerator.generate(bito); - TableInfoCreateRequest renewTableInfo = new TableInfoCreateRequest("비토 테이블", "주제"); - List renewTimeBoxes = List.of( - new TimeBoxCreateRequest(Stance.PROS, BoxType.OPENING, 3, 1), - new TimeBoxCreateRequest(Stance.CONS, BoxType.OPENING, 3, 1) - ); ParliamentaryTableCreateRequest renewTableRequest = new ParliamentaryTableCreateRequest( - renewTableInfo, - renewTimeBoxes + new TableInfoCreateRequest("비토 테이블", "주제", true, true), + List.of( + new TimeBoxCreateRequest(Stance.PROS, BoxType.OPENING, 3, 1), + new TimeBoxCreateRequest(Stance.CONS, BoxType.OPENING, 3, 1) + ) ); + Headers headers = headerGenerator.generateAccessTokenHeader(bito); ParliamentaryTableResponse response = given() .contentType(ContentType.JSON) .pathParam("tableId", bitoTable.getId()) - .queryParam("memberId", bito.getId()) + .headers(headers) .body(renewTableRequest) .when().put("/api/table/parliamentary/{tableId}") .then().statusCode(200) @@ -101,8 +103,8 @@ class UpdateTable { assertAll( () -> assertThat(response.id()).isEqualTo(bitoTable.getId()), - () -> assertThat(response.info().name()).isEqualTo(renewTableInfo.name()), - () -> assertThat(response.table()).hasSize(renewTimeBoxes.size()) + () -> assertThat(response.info().name()).isEqualTo(renewTableRequest.info().name()), + () -> assertThat(response.table()).hasSize(renewTableRequest.table().size()) ); } } @@ -112,15 +114,16 @@ class DeleteTable { @Test void 의회식_토론_테이블을_삭제한다() { - Member bito = memberGenerator.generate("비토"); + Member bito = memberGenerator.generate("default@gmail.com"); ParliamentaryTable bitoTable = tableGenerator.generate(bito); timeBoxGenerator.generate(bitoTable, 1); timeBoxGenerator.generate(bitoTable, 2); + Headers headers = headerGenerator.generateAccessTokenHeader(bito); given() .contentType(ContentType.JSON) .pathParam("tableId", bitoTable.getId()) - .queryParam("memberId", bito.getId()) + .headers(headers) .when().delete("/api/table/parliamentary/{tableId}") .then().statusCode(204); } diff --git a/src/test/java/com/debatetimer/controller/parliamentary/ParliamentaryDocumentTest.java b/src/test/java/com/debatetimer/controller/parliamentary/ParliamentaryDocumentTest.java index 2003e00b..be8ea8dd 100644 --- a/src/test/java/com/debatetimer/controller/parliamentary/ParliamentaryDocumentTest.java +++ b/src/test/java/com/debatetimer/controller/parliamentary/ParliamentaryDocumentTest.java @@ -3,9 +3,11 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.when; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; import static org.springframework.restdocs.payload.JsonFieldType.ARRAY; +import static org.springframework.restdocs.payload.JsonFieldType.BOOLEAN; import static org.springframework.restdocs.payload.JsonFieldType.NUMBER; import static org.springframework.restdocs.payload.JsonFieldType.OBJECT; import static org.springframework.restdocs.payload.JsonFieldType.STRING; @@ -32,6 +34,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; +import org.springframework.http.HttpHeaders; public class ParliamentaryDocumentTest extends BaseDocumentTest { @@ -41,13 +44,15 @@ class Save { private final RestDocumentationRequest requestDocument = request() .tag(Tag.PARLIAMENTARY_API) .summary("새로운 의회식 토론 시간표 생성") - .queryParameter( - parameterWithName("memberId").description("멤버 ID") + .requestHeader( + headerWithName(HttpHeaders.AUTHORIZATION).description("액세스 토큰") ) .requestBodyField( fieldWithPath("info").type(OBJECT).description("토론 테이블 정보"), fieldWithPath("info.name").type(STRING).description("테이블 이름"), fieldWithPath("info.agenda").type(STRING).description("토론 주제"), + fieldWithPath("info.warningBell").type(BOOLEAN).description("30초 종소리 유무"), + fieldWithPath("info.finishBell").type(BOOLEAN).description("발언 종료 종소리 유무"), fieldWithPath("table").type(ARRAY).description("토론 테이블 구성"), fieldWithPath("table[].stance").type(STRING).description("입장"), fieldWithPath("table[].type").type(STRING).description("발언 유형"), @@ -61,6 +66,8 @@ class Save { fieldWithPath("info").type(OBJECT).description("토론 테이블 정보"), fieldWithPath("info.name").type(STRING).description("테이블 이름"), fieldWithPath("info.agenda").type(STRING).description("토론 주제"), + fieldWithPath("info.warningBell").type(BOOLEAN).description("30초 종소리 유무"), + fieldWithPath("info.finishBell").type(BOOLEAN).description("발언 종료 종소리 유무"), fieldWithPath("table").type(ARRAY).description("토론 테이블 구성"), fieldWithPath("table[].stance").type(STRING).description("입장"), fieldWithPath("table[].type").type(STRING).description("발언 유형"), @@ -71,7 +78,7 @@ class Save { @Test void 의회식_테이블_생성_성공() { ParliamentaryTableCreateRequest request = new ParliamentaryTableCreateRequest( - new TableInfoCreateRequest("비토 테이블 1", "토론 주제"), + new TableInfoCreateRequest("비토 테이블 1", "토론 주제", true, true), List.of( new TimeBoxCreateRequest(Stance.PROS, BoxType.OPENING, 3, 1), new TimeBoxCreateRequest(Stance.CONS, BoxType.OPENING, 3, 1) @@ -79,13 +86,13 @@ class Save { ); ParliamentaryTableResponse response = new ParliamentaryTableResponse( 5L, - new TableInfoResponse("비토 테이블 1", "토론 주제"), + new TableInfoResponse("비토 테이블 1", "토론 주제", true, true), List.of( new TimeBoxResponse(Stance.PROS, BoxType.OPENING, 3, 1), new TimeBoxResponse(Stance.CONS, BoxType.OPENING, 3, 1) ) ); - when(parliamentaryService.save(eq(request), any())).thenReturn(response); + doReturn(response).when(parliamentaryService).save(eq(request), any()); var document = document("parliamentary/post", 201) .request(requestDocument) @@ -94,7 +101,7 @@ class Save { given(document) .contentType(ContentType.JSON) - .queryParam("memberId", EXIST_MEMBER_ID) + .headers(EXIST_MEMBER_HEADER) .body(request) .when().post("/api/table/parliamentary") .then().statusCode(201); @@ -113,13 +120,13 @@ class Save { @ParameterizedTest void 의회식_테이블_생성_실패(ClientErrorCode errorCode) { ParliamentaryTableCreateRequest request = new ParliamentaryTableCreateRequest( - new TableInfoCreateRequest("비토 테이블 1", "토론 주제"), + new TableInfoCreateRequest("비토 테이블 1", "토론 주제", true, true), List.of( new TimeBoxCreateRequest(Stance.PROS, BoxType.OPENING, 3, 1), new TimeBoxCreateRequest(Stance.CONS, BoxType.OPENING, 3, 1) ) ); - when(parliamentaryService.save(eq(request), any())).thenThrow(new DTClientErrorException(errorCode)); + doThrow(new DTClientErrorException(errorCode)).when(parliamentaryService).save(eq(request), any()); var document = document("parliamentary/post", errorCode) .request(requestDocument) @@ -128,7 +135,7 @@ class Save { given(document) .contentType(ContentType.JSON) - .queryParam("memberId", EXIST_MEMBER_ID) + .headers(EXIST_MEMBER_HEADER) .body(request) .when().post("/api/table/parliamentary") .then().statusCode(errorCode.getStatus().value()); @@ -141,11 +148,11 @@ class GetTable { private final RestDocumentationRequest requestDocument = request() .summary("의회식 토론 시간표 조회") .tag(Tag.PARLIAMENTARY_API) + .requestHeader( + headerWithName(HttpHeaders.AUTHORIZATION).description("액세스 토큰") + ) .pathParameter( parameterWithName("tableId").description("테이블 ID") - ) - .queryParameter( - parameterWithName("memberId").description("멤버 ID") ); private final RestDocumentationResponse responseDocument = response() @@ -154,6 +161,8 @@ class GetTable { fieldWithPath("info").type(OBJECT).description("토론 테이블 정보"), fieldWithPath("info.name").type(STRING).description("테이블 이름"), fieldWithPath("info.agenda").type(STRING).description("토론 주제"), + fieldWithPath("info.warningBell").type(BOOLEAN).description("30초 종소리 유무"), + fieldWithPath("info.finishBell").type(BOOLEAN).description("발언 종료 종소리 유무"), fieldWithPath("table").type(ARRAY).description("토론 테이블 구성"), fieldWithPath("table[].stance").type(STRING).description("입장"), fieldWithPath("table[].type").type(STRING).description("발언 유형"), @@ -167,13 +176,13 @@ class GetTable { long tableId = 5L; ParliamentaryTableResponse response = new ParliamentaryTableResponse( 5L, - new TableInfoResponse("비토 테이블 1", "토론 주제"), + new TableInfoResponse("비토 테이블 1", "토론 주제", true, true), List.of( new TimeBoxResponse(Stance.PROS, BoxType.OPENING, 3, 1), new TimeBoxResponse(Stance.CONS, BoxType.OPENING, 3, 1) ) ); - when(parliamentaryService.findTable(eq(tableId), any())).thenReturn(response); + doReturn(response).when(parliamentaryService).findTable(eq(tableId), any()); var document = document("parliamentary/get", 200) .request(requestDocument) @@ -182,8 +191,8 @@ class GetTable { given(document) .contentType(ContentType.JSON) + .headers(EXIST_MEMBER_HEADER) .pathParam("tableId", tableId) - .queryParam("memberId", memberId) .when().get("/api/table/parliamentary/{tableId}") .then().statusCode(200); } @@ -193,7 +202,7 @@ class GetTable { void 의회식_테이블_조회_실패(ClientErrorCode errorCode) { long memberId = 4L; long tableId = 5L; - when(parliamentaryService.findTable(eq(tableId), any())).thenThrow(new DTClientErrorException(errorCode)); + doThrow(new DTClientErrorException(errorCode)).when(parliamentaryService).findTable(eq(tableId), any()); var document = document("parliamentary/get", errorCode) .request(requestDocument) @@ -202,8 +211,8 @@ class GetTable { given(document) .contentType(ContentType.JSON) + .headers(EXIST_MEMBER_HEADER) .pathParam("tableId", tableId) - .queryParam("memberId", memberId) .when().get("/api/table/parliamentary/{tableId}") .then().statusCode(errorCode.getStatus().value()); } @@ -215,16 +224,18 @@ class UpdateTable { private final RestDocumentationRequest requestDocument = request() .tag(Tag.PARLIAMENTARY_API) .summary("의회식 토론 시간표 수정") + .requestHeader( + headerWithName(HttpHeaders.AUTHORIZATION).description("액세스 토큰") + ) .pathParameter( parameterWithName("tableId").description("테이블 ID") ) - .queryParameter( - parameterWithName("memberId").description("멤버 ID") - ) .requestBodyField( fieldWithPath("info").type(OBJECT).description("토론 테이블 정보"), fieldWithPath("info.name").type(STRING).description("테이블 이름"), fieldWithPath("info.agenda").type(STRING).description("토론 주제"), + fieldWithPath("info.warningBell").type(BOOLEAN).description("30초 종소리 유무"), + fieldWithPath("info.finishBell").type(BOOLEAN).description("발언 종료 종소리 유무"), fieldWithPath("table").type(ARRAY).description("토론 테이블 구성"), fieldWithPath("table[].stance").type(STRING).description("입장"), fieldWithPath("table[].type").type(STRING).description("발언 유형"), @@ -238,6 +249,8 @@ class UpdateTable { fieldWithPath("info").type(OBJECT).description("토론 테이블 정보"), fieldWithPath("info.name").type(STRING).description("테이블 이름"), fieldWithPath("info.agenda").type(STRING).description("토론 주제"), + fieldWithPath("info.warningBell").type(BOOLEAN).description("30초 종소리 유무"), + fieldWithPath("info.finishBell").type(BOOLEAN).description("발언 종료 종소리 유무"), fieldWithPath("table").type(ARRAY).description("토론 테이블 구성"), fieldWithPath("table[].stance").type(STRING).description("입장"), fieldWithPath("table[].type").type(STRING).description("발언 유형"), @@ -250,7 +263,7 @@ class UpdateTable { long memberId = 4L; long tableId = 5L; ParliamentaryTableCreateRequest request = new ParliamentaryTableCreateRequest( - new TableInfoCreateRequest("비토 테이블 2", "토론 주제 2"), + new TableInfoCreateRequest("비토 테이블 2", "토론 주제 2", true, true), List.of( new TimeBoxCreateRequest(Stance.PROS, BoxType.OPENING, 300, 1), new TimeBoxCreateRequest(Stance.CONS, BoxType.OPENING, 300, 1) @@ -258,13 +271,13 @@ class UpdateTable { ); ParliamentaryTableResponse response = new ParliamentaryTableResponse( 5L, - new TableInfoResponse("비토 테이블 2", "토론 주제 2"), + new TableInfoResponse("비토 테이블 2", "토론 주제 2", true, true), List.of( new TimeBoxResponse(Stance.PROS, BoxType.OPENING, 300, 1), new TimeBoxResponse(Stance.CONS, BoxType.OPENING, 300, 1) ) ); - when(parliamentaryService.updateTable(eq(request), eq(tableId), any())).thenReturn(response); + doReturn(response).when(parliamentaryService).updateTable(eq(request), eq(tableId), any()); var document = document("parliamentary/put", 200) .request(requestDocument) @@ -273,7 +286,7 @@ class UpdateTable { given(document) .contentType(ContentType.JSON) - .queryParam("memberId", memberId) + .headers(EXIST_MEMBER_HEADER) .pathParam("tableId", tableId) .body(request) .when().put("/api/table/parliamentary/{tableId}") @@ -296,14 +309,14 @@ class UpdateTable { long memberId = 4L; long tableId = 5L; ParliamentaryTableCreateRequest request = new ParliamentaryTableCreateRequest( - new TableInfoCreateRequest("비토 테이블 2", "토론 주제 2"), + new TableInfoCreateRequest("비토 테이블 2", "토론 주제 2", true, true), List.of( new TimeBoxCreateRequest(Stance.PROS, BoxType.OPENING, 300, 1), new TimeBoxCreateRequest(Stance.CONS, BoxType.OPENING, 300, 1) ) ); - when(parliamentaryService.updateTable(eq(request), eq(tableId), any())) - .thenThrow(new DTClientErrorException(errorCode)); + doThrow(new DTClientErrorException(errorCode)).when(parliamentaryService) + .updateTable(eq(request), eq(tableId), any()); var document = document("parliamentary/put", errorCode) .request(requestDocument) @@ -312,8 +325,8 @@ class UpdateTable { given(document) .contentType(ContentType.JSON) + .headers(EXIST_MEMBER_HEADER) .pathParam("tableId", tableId) - .queryParam("memberId", memberId) .body(request) .when().put("/api/table/parliamentary/{tableId}") .then().statusCode(errorCode.getStatus().value()); @@ -326,11 +339,11 @@ class DeleteTable { private final RestDocumentationRequest requestDocument = request() .tag(Tag.PARLIAMENTARY_API) .summary("의회식 토론 시간표 삭제") + .requestHeader( + headerWithName(HttpHeaders.AUTHORIZATION).description("액세스 토큰") + ) .pathParameter( parameterWithName("tableId").description("테이블 ID") - ) - .queryParameter( - parameterWithName("memberId").description("멤버 ID") ); @Test @@ -344,8 +357,8 @@ class DeleteTable { .build(); given(document) + .headers(EXIST_MEMBER_HEADER) .pathParam("tableId", tableId) - .queryParam("memberId", memberId) .when().delete("/api/table/parliamentary/{tableId}") .then().statusCode(204); } @@ -363,8 +376,8 @@ class DeleteTable { .build(); given(document) + .headers(EXIST_MEMBER_HEADER) .pathParam("tableId", tableId) - .queryParam("memberId", memberId) .when().delete("/api/table/parliamentary/{tableId}") .then().statusCode(errorCode.getStatus().value()); } diff --git a/src/test/java/com/debatetimer/controller/tool/cookie/CookieExtractorTest.java b/src/test/java/com/debatetimer/controller/tool/cookie/CookieExtractorTest.java new file mode 100644 index 00000000..84eb34ff --- /dev/null +++ b/src/test/java/com/debatetimer/controller/tool/cookie/CookieExtractorTest.java @@ -0,0 +1,48 @@ +package com.debatetimer.controller.tool.cookie; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.debatetimer.exception.custom.DTClientErrorException; +import com.debatetimer.exception.errorcode.ClientErrorCode; +import com.debatetimer.fixture.CookieGenerator; +import jakarta.servlet.http.Cookie; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class CookieExtractorTest { + + private CookieGenerator cookieGenerator; + private CookieExtractor cookieExtractor; + + @BeforeEach + void setUp() { + this.cookieGenerator = new CookieGenerator(); + this.cookieExtractor = new CookieExtractor(); + } + + @Nested + class ExtractCookie { + + @Test + void 쿠키에서_해당하는_키의_값을_추출한다() { + String key = "key"; + String value = "value"; + Cookie[] cookies = cookieGenerator.generateCookie(key, value, 100000); + + assertThat(cookieExtractor.extractCookie(key, cookies)).isEqualTo(value); + } + + @Test + void 쿠키에서_해당하는_값이_없으면_예외를_발생시킨다() { + String key = "key"; + String value = "value"; + Cookie[] cookies = cookieGenerator.generateCookie("token", value, 100000); + + assertThatThrownBy(() -> cookieExtractor.extractCookie(key, cookies)) + .isInstanceOf(DTClientErrorException.class) + .hasMessage(ClientErrorCode.EMPTY_COOKIE.getMessage()); + } + } +} diff --git a/src/test/java/com/debatetimer/controller/tool/jwt/JwtTokenResolverTest.java b/src/test/java/com/debatetimer/controller/tool/jwt/JwtTokenResolverTest.java new file mode 100644 index 00000000..7e81bae8 --- /dev/null +++ b/src/test/java/com/debatetimer/controller/tool/jwt/JwtTokenResolverTest.java @@ -0,0 +1,84 @@ +package com.debatetimer.controller.tool.jwt; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.debatetimer.exception.custom.DTClientErrorException; +import com.debatetimer.exception.errorcode.ClientErrorCode; +import com.debatetimer.fixture.JwtTokenFixture; +import com.debatetimer.fixture.TokenGenerator; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class JwtTokenResolverTest { + + private TokenGenerator tokenGenerator; + private JwtTokenResolver jwtTokenResolver; + + @BeforeEach + void setUp() { + this.tokenGenerator = new TokenGenerator(); + this.jwtTokenResolver = new JwtTokenResolver(JwtTokenFixture.TEST_TOKEN_PROPERTIES); + } + + @Nested + class ResolveAccessToken { + + @Test + void 액세스_토큰에서_이메일을_가져온다() { + String email = "bito@gmail.com"; + String accessToken = tokenGenerator.generateAccessToken(email); + + assertThat(jwtTokenResolver.resolveAccessToken(accessToken)).isEqualTo(email); + } + + @Test + void 기한이_만료된_토큰이면_예외를_발생시킨다() { + String expiredToken = tokenGenerator.generateExpiredToken(); + + assertThatThrownBy(() -> jwtTokenResolver.resolveAccessToken(expiredToken)) + .isInstanceOf(DTClientErrorException.class) + .hasMessage(ClientErrorCode.EXPIRED_TOKEN.getMessage()); + } + + @Test + void 액세스_토큰이_아니면_예외를_발생시킨다() { + String refreshToken = tokenGenerator.generateRefreshToken("bito@gmail.com"); + + assertThatThrownBy(() -> jwtTokenResolver.resolveAccessToken(refreshToken)) + .isInstanceOf(DTClientErrorException.class) + .hasMessage(ClientErrorCode.UNAUTHORIZED_MEMBER.getMessage()); + } + } + + @Nested + class ResolveRefreshToken { + + @Test + void 리프레시_토큰에서_이메일을_가져온다() { + String email = "bito@gmail.com"; + String accessToken = tokenGenerator.generateRefreshToken(email); + + assertThat(jwtTokenResolver.resolveRefreshToken(accessToken)).isEqualTo(email); + } + + @Test + void 기한이_만료된_토큰이면_예외를_발생시킨다() { + String expiredToken = tokenGenerator.generateExpiredToken(); + + assertThatThrownBy(() -> jwtTokenResolver.resolveRefreshToken(expiredToken)) + .isInstanceOf(DTClientErrorException.class) + .hasMessage(ClientErrorCode.EXPIRED_TOKEN.getMessage()); + } + + @Test + void 리프레시_토큰이_아니면_예외를_발생시킨다() { + String accessToken = tokenGenerator.generateAccessToken("default@gmail.com"); + + assertThatThrownBy(() -> jwtTokenResolver.resolveRefreshToken(accessToken)) + .isInstanceOf(DTClientErrorException.class) + .hasMessage(ClientErrorCode.UNAUTHORIZED_MEMBER.getMessage()); + } + } +} diff --git a/src/test/java/com/debatetimer/domain/member/MemberTest.java b/src/test/java/com/debatetimer/domain/member/MemberTest.java deleted file mode 100644 index 0637e45e..00000000 --- a/src/test/java/com/debatetimer/domain/member/MemberTest.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.debatetimer.domain.member; - -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import com.debatetimer.exception.custom.DTClientErrorException; -import com.debatetimer.exception.errorcode.ClientErrorCode; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -class MemberTest { - - @Nested - class Validate { - - @ParameterizedTest - @ValueSource(strings = {"a bc가다", "가나 다ab"}) - void 닉네임은_영문과_한글_띄어쓰기만_가능하다(String nickname) { - assertThatCode(() -> new Member(nickname)) - .doesNotThrowAnyException(); - } - - @ParameterizedTest - @ValueSource(ints = {0, Member.NICKNAME_MAX_LENGTH + 1}) - void 닉네임은_정해진_길이_이내여야_한다(int length) { - assertThatThrownBy(() -> new Member("f".repeat(length))) - .isInstanceOf(DTClientErrorException.class) - .hasMessage(ClientErrorCode.INVALID_MEMBER_NICKNAME_LENGTH.getMessage()); - } - - @ParameterizedTest - @ValueSource(strings = {"abc12", "가나다12"}) - void 닉네임은_영문과_한글만_가능하다(String nickname) { - assertThatThrownBy(() -> new Member(nickname)) - .isInstanceOf(DTClientErrorException.class) - .hasMessage(ClientErrorCode.INVALID_MEMBER_NICKNAME_FORM.getMessage()); - } - } -} diff --git a/src/test/java/com/debatetimer/domain/parliamentary/ParliamentaryTableTest.java b/src/test/java/com/debatetimer/domain/parliamentary/ParliamentaryTableTest.java index a3521064..84bc003d 100644 --- a/src/test/java/com/debatetimer/domain/parliamentary/ParliamentaryTableTest.java +++ b/src/test/java/com/debatetimer/domain/parliamentary/ParliamentaryTableTest.java @@ -18,16 +18,16 @@ class Validate { @ParameterizedTest @ValueSource(strings = {"a bc가다9", "가0나 다ab"}) void 테이블_이름은_영문과_한글_숫자_띄어쓰기만_가능하다(String name) { - Member member = new Member("member"); - assertThatCode(() -> new ParliamentaryTable(member, name, "agenda", 10)) + Member member = new Member("default@gmail.com"); + assertThatCode(() -> new ParliamentaryTable(member, name, "agenda", 10, true, true)) .doesNotThrowAnyException(); } @ParameterizedTest @ValueSource(ints = {0, ParliamentaryTable.NAME_MAX_LENGTH + 1}) void 테이블_이름은_정해진_길이_이내여야_한다(int length) { - Member member = new Member("member"); - assertThatThrownBy(() -> new ParliamentaryTable(member, "f".repeat(length), "agenda", 10)) + Member member = new Member("default@gmail.com"); + assertThatThrownBy(() -> new ParliamentaryTable(member, "f".repeat(length), "agenda", 10, true, true)) .isInstanceOf(DTClientErrorException.class) .hasMessage(ClientErrorCode.INVALID_TABLE_NAME_LENGTH.getMessage()); } @@ -35,8 +35,8 @@ class Validate { @ParameterizedTest @ValueSource(strings = {"", "\t", "\n"}) void 테이블_이름은_적어도_한_자_있어야_한다(String name) { - Member member = new Member("member"); - assertThatThrownBy(() -> new ParliamentaryTable(member, name, "agenda", 10)) + Member member = new Member("default@gmail.com"); + assertThatThrownBy(() -> new ParliamentaryTable(member, name, "agenda", 10, true, true)) .isInstanceOf(DTClientErrorException.class) .hasMessage(ClientErrorCode.INVALID_TABLE_NAME_LENGTH.getMessage()); } @@ -44,8 +44,8 @@ class Validate { @ParameterizedTest @ValueSource(strings = {"abc@", "가나다*", "abc\tde"}) void 허용된_글자_이외의_문자는_불가능하다(String name) { - Member member = new Member("member"); - assertThatThrownBy(() -> new ParliamentaryTable(member, name, "agenda", 10)) + Member member = new Member("default@gmail.com"); + assertThatThrownBy(() -> new ParliamentaryTable(member, name, "agenda", 10, true, true)) .isInstanceOf(DTClientErrorException.class) .hasMessage(ClientErrorCode.INVALID_TABLE_NAME_FORM.getMessage()); } @@ -53,8 +53,8 @@ class Validate { @ParameterizedTest @ValueSource(ints = {0, -1, -60}) void 테이블_시간은_양수만_가능하다(int duration) { - Member member = new Member("member"); - assertThatThrownBy(() -> new ParliamentaryTable(member, "name", "agenda", duration)) + Member member = new Member("default@gmail.com"); + assertThatThrownBy(() -> new ParliamentaryTable(member, "nickname", "agenda", duration, true, true)) .isInstanceOf(DTClientErrorException.class) .hasMessage(ClientErrorCode.INVALID_TABLE_TIME.getMessage()); } diff --git a/src/test/java/com/debatetimer/domain/parliamentary/ParliamentaryTimeBoxesTest.java b/src/test/java/com/debatetimer/domain/parliamentary/ParliamentaryTimeBoxesTest.java index 9376f493..c64fa43b 100644 --- a/src/test/java/com/debatetimer/domain/parliamentary/ParliamentaryTimeBoxesTest.java +++ b/src/test/java/com/debatetimer/domain/parliamentary/ParliamentaryTimeBoxesTest.java @@ -18,10 +18,12 @@ class SortedBySequence { @Test void 타임박스의_순서에_따라_정렬된다() { - Member member = new Member("콜리"); - ParliamentaryTable testTable = new ParliamentaryTable(member, "토론 테이블", "주제", 1800); - ParliamentaryTimeBox firstBox = new ParliamentaryTimeBox(testTable, 1, Stance.PROS, BoxType.OPENING, 300, 1); - ParliamentaryTimeBox secondBox = new ParliamentaryTimeBox(testTable, 2, Stance.PROS, BoxType.OPENING, 300, 1); + Member member = new Member("default@gmail.com"); + ParliamentaryTable testTable = new ParliamentaryTable(member, "토론 테이블", "주제", 1800, true, true); + ParliamentaryTimeBox firstBox = new ParliamentaryTimeBox(testTable, 1, Stance.PROS, BoxType.OPENING, 300, + 1); + ParliamentaryTimeBox secondBox = new ParliamentaryTimeBox(testTable, 2, Stance.PROS, BoxType.OPENING, 300, + 1); List timeBoxes = new ArrayList<>(Arrays.asList(secondBox, firstBox)); ParliamentaryTimeBoxes actual = new ParliamentaryTimeBoxes(timeBoxes); diff --git a/src/test/java/com/debatetimer/fixture/CookieGenerator.java b/src/test/java/com/debatetimer/fixture/CookieGenerator.java new file mode 100644 index 00000000..b8999b4e --- /dev/null +++ b/src/test/java/com/debatetimer/fixture/CookieGenerator.java @@ -0,0 +1,36 @@ +package com.debatetimer.fixture; + +import com.debatetimer.controller.tool.cookie.CookieProvider; +import com.debatetimer.controller.tool.jwt.JwtTokenProvider; +import com.debatetimer.dto.member.MemberInfo; +import jakarta.servlet.http.Cookie; +import org.springframework.http.ResponseCookie; +import org.springframework.stereotype.Component; + +@Component +public class CookieGenerator { + + private final JwtTokenProvider jwtTokenProvider; + private final CookieProvider cookieProvider; + + public CookieGenerator() { + this.jwtTokenProvider = new JwtTokenProvider(JwtTokenFixture.TEST_TOKEN_PROPERTIES); + this.cookieProvider = new CookieProvider(); + } + + public Cookie[] generateRefreshCookie(String email) { + String refreshToken = jwtTokenProvider.createRefreshToken(new MemberInfo(email)); + return generateCookie("refreshToken", refreshToken, 100000); + } + + public Cookie[] generateCookie(String cookieName, String value, long expirationMills) { + ResponseCookie responseCookie = cookieProvider.createCookie(cookieName, value, expirationMills); + + Cookie servletCookie = new Cookie(responseCookie.getName(), responseCookie.getValue()); + servletCookie.setMaxAge((int) (expirationMills / 1000)); + servletCookie.setPath(responseCookie.getPath()); + servletCookie.setSecure(responseCookie.isSecure()); + servletCookie.setHttpOnly(responseCookie.isHttpOnly()); + return new Cookie[]{servletCookie}; + } +} diff --git a/src/test/java/com/debatetimer/fixture/HeaderGenerator.java b/src/test/java/com/debatetimer/fixture/HeaderGenerator.java new file mode 100644 index 00000000..4e641ae5 --- /dev/null +++ b/src/test/java/com/debatetimer/fixture/HeaderGenerator.java @@ -0,0 +1,27 @@ +package com.debatetimer.fixture; + +import com.debatetimer.domain.member.Member; +import com.debatetimer.dto.member.MemberInfo; +import com.debatetimer.controller.tool.jwt.JwtTokenProvider; +import com.debatetimer.controller.tool.cookie.CookieProvider; +import io.restassured.http.Header; +import io.restassured.http.Headers; +import org.springframework.http.HttpHeaders; +import org.springframework.stereotype.Component; + +@Component +public class HeaderGenerator { + + private final JwtTokenProvider jwtTokenProvider; + private final CookieProvider cookieProvider; + + public HeaderGenerator(JwtTokenProvider jwtTokenProvider, CookieProvider cookieProvider) { + this.jwtTokenProvider = jwtTokenProvider; + this.cookieProvider = cookieProvider; + } + + public Headers generateAccessTokenHeader(Member member) { + String accessToken = jwtTokenProvider.createAccessToken(new MemberInfo(member)); + return new Headers(new Header(HttpHeaders.AUTHORIZATION, accessToken)); + } +} diff --git a/src/test/java/com/debatetimer/fixture/JwtTokenFixture.java b/src/test/java/com/debatetimer/fixture/JwtTokenFixture.java new file mode 100644 index 00000000..0c9eedd0 --- /dev/null +++ b/src/test/java/com/debatetimer/fixture/JwtTokenFixture.java @@ -0,0 +1,12 @@ +package com.debatetimer.fixture; + +import com.debatetimer.controller.tool.jwt.JwtTokenProperties; + +public class JwtTokenFixture { + + public static final JwtTokenProperties TEST_TOKEN_PROPERTIES = new JwtTokenProperties( + "test".repeat(8), + 5000, + 10000 + ); +} diff --git a/src/test/java/com/debatetimer/fixture/MemberGenerator.java b/src/test/java/com/debatetimer/fixture/MemberGenerator.java index 8af700ff..d0aa97bb 100644 --- a/src/test/java/com/debatetimer/fixture/MemberGenerator.java +++ b/src/test/java/com/debatetimer/fixture/MemberGenerator.java @@ -13,8 +13,8 @@ public MemberGenerator(MemberRepository memberRepository) { this.memberRepository = memberRepository; } - public Member generate(String nickName) { - Member member = new Member(nickName); + public Member generate(String email) { + Member member = new Member(email); return memberRepository.save(member); } } diff --git a/src/test/java/com/debatetimer/fixture/ParliamentaryTableGenerator.java b/src/test/java/com/debatetimer/fixture/ParliamentaryTableGenerator.java index cbb0340f..0f0aba43 100644 --- a/src/test/java/com/debatetimer/fixture/ParliamentaryTableGenerator.java +++ b/src/test/java/com/debatetimer/fixture/ParliamentaryTableGenerator.java @@ -15,7 +15,14 @@ public ParliamentaryTableGenerator(ParliamentaryTableRepository parliamentaryTab } public ParliamentaryTable generate(Member member) { - ParliamentaryTable table = new ParliamentaryTable(member, "토론 테이블", "주제", 1800); + ParliamentaryTable table = new ParliamentaryTable( + member, + "토론 테이블", + "주제", + 1800, + false, + false + ); return parliamentaryTableRepository.save(table); } } diff --git a/src/test/java/com/debatetimer/fixture/TokenGenerator.java b/src/test/java/com/debatetimer/fixture/TokenGenerator.java new file mode 100644 index 00000000..67fd994a --- /dev/null +++ b/src/test/java/com/debatetimer/fixture/TokenGenerator.java @@ -0,0 +1,40 @@ +package com.debatetimer.fixture; + +import com.debatetimer.controller.tool.jwt.JwtTokenProvider; +import com.debatetimer.dto.member.MemberInfo; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import java.util.Date; +import javax.crypto.SecretKey; +import org.springframework.stereotype.Component; + +@Component +public class TokenGenerator { + + private final JwtTokenProvider jwtTokenProvider; + + public TokenGenerator() { + this.jwtTokenProvider = new JwtTokenProvider(JwtTokenFixture.TEST_TOKEN_PROPERTIES); + } + + public String generateRefreshToken(String email) { + return jwtTokenProvider.createRefreshToken(new MemberInfo(email)); + } + + public String generateAccessToken(String email) { + return jwtTokenProvider.createAccessToken(new MemberInfo(email)); + } + + public String generateExpiredToken() { + Date now = new Date(); + Date expiredDate = new Date(now.getTime() - 1000); + SecretKey secretKey = Keys.hmacShaKeyFor("test".repeat(8).getBytes()); + return Jwts.builder() + .setSubject("") + .setIssuedAt(now) + .setExpiration(expiredDate) + .claim("type", "") + .signWith(secretKey) + .compact(); + } +} diff --git a/src/test/java/com/debatetimer/repository/member/MemberRepositoryTest.java b/src/test/java/com/debatetimer/repository/member/MemberRepositoryTest.java new file mode 100644 index 00000000..47f08000 --- /dev/null +++ b/src/test/java/com/debatetimer/repository/member/MemberRepositoryTest.java @@ -0,0 +1,58 @@ +package com.debatetimer.repository.member; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.debatetimer.domain.member.Member; +import com.debatetimer.exception.custom.DTClientErrorException; +import com.debatetimer.exception.errorcode.ClientErrorCode; +import com.debatetimer.repository.BaseRepositoryTest; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +class MemberRepositoryTest extends BaseRepositoryTest { + + @Autowired + private MemberRepository memberRepository; + + @Nested + class GetById { + + @Test + void 아이디에_해당하는_멤버를_반환한다() { + Member bito = memberGenerator.generate("default@gmail.com"); + + assertThat(memberRepository.getById(bito.getId()).getId()).isEqualTo(bito.getId()); + } + + @Test + void 아이디에_해당하는_멤버가_없으면_예외를_발생시킨다() { + Member bito = memberGenerator.generate("default@gmail.com"); + + assertThatThrownBy(() -> memberRepository.getById(bito.getId() + 1)) + .isInstanceOf(DTClientErrorException.class) + .hasMessage(ClientErrorCode.MEMBER_NOT_FOUND.getMessage()); + } + } + + @Nested + class GetByNickname { + + @Test + void 닉네임에_해당하는_멤버를_반환한다() { + Member bito = memberGenerator.generate("bito@gmail.com"); + + assertThat(memberRepository.getByEmail(bito.getEmail()).getId()).isEqualTo(bito.getId()); + } + + @Test + void 닉네임에_해당하는_멤버가_없으면_예외를_발생시킨다() { + memberGenerator.generate("default@gmail.com"); + + assertThatThrownBy(() -> memberRepository.getByEmail("notbito@gmail.com")) + .isInstanceOf(DTClientErrorException.class) + .hasMessage(ClientErrorCode.MEMBER_NOT_FOUND.getMessage()); + } + } +} diff --git a/src/test/java/com/debatetimer/repository/parliamentary/ParliamentaryTableRepositoryTest.java b/src/test/java/com/debatetimer/repository/parliamentary/ParliamentaryTableRepositoryTest.java index 0ccdb063..246a1212 100644 --- a/src/test/java/com/debatetimer/repository/parliamentary/ParliamentaryTableRepositoryTest.java +++ b/src/test/java/com/debatetimer/repository/parliamentary/ParliamentaryTableRepositoryTest.java @@ -23,8 +23,8 @@ class FindAllByMember { @Test void 특정_회원의_테이블만_조회한다() { - Member chan = memberGenerator.generate("커찬"); - Member bito = memberGenerator.generate("비토"); + Member chan = memberGenerator.generate("default@gmail.com"); + Member bito = memberGenerator.generate("default2@gmail.com"); ParliamentaryTable chanTable1 = tableGenerator.generate(chan); ParliamentaryTable chanTable2 = tableGenerator.generate(chan); ParliamentaryTable bitoTable = tableGenerator.generate(bito); @@ -40,10 +40,10 @@ class GetById { @Test void 특정_아이디의_테이블을_조회한다() { - Member chan = memberGenerator.generate("커찬"); + Member chan = memberGenerator.generate("default@gmail.com"); ParliamentaryTable chanTable = tableGenerator.generate(chan); - ParliamentaryTable foundChanTable = tableRepository.getById(chanTable.getId().longValue()); + ParliamentaryTable foundChanTable = tableRepository.getById(chanTable.getId()); assertThat(foundChanTable).usingRecursiveComparison().isEqualTo(chanTable); } diff --git a/src/test/java/com/debatetimer/repository/parliamentary/ParliamentaryTimeBoxRepositoryTest.java b/src/test/java/com/debatetimer/repository/parliamentary/ParliamentaryTimeBoxRepositoryTest.java index 07cac513..17c09566 100644 --- a/src/test/java/com/debatetimer/repository/parliamentary/ParliamentaryTimeBoxRepositoryTest.java +++ b/src/test/java/com/debatetimer/repository/parliamentary/ParliamentaryTimeBoxRepositoryTest.java @@ -21,8 +21,8 @@ class FindAllByParliamentaryTable { @Test void 특정_테이블의_타임박스를_모두_조회한다() { - Member chan = memberGenerator.generate("커찬"); - Member bito = memberGenerator.generate("비토"); + Member chan = memberGenerator.generate("default@gmail.com"); + Member bito = memberGenerator.generate("default2@gmail.com"); ParliamentaryTable chanTable = tableGenerator.generate(chan); ParliamentaryTable bitoTable = tableGenerator.generate(bito); ParliamentaryTimeBox chanBox1 = timeBoxGenerator.generate(chanTable, 1); diff --git a/src/test/java/com/debatetimer/service/BaseServiceTest.java b/src/test/java/com/debatetimer/service/BaseServiceTest.java index e68e262a..4e2a2d65 100644 --- a/src/test/java/com/debatetimer/service/BaseServiceTest.java +++ b/src/test/java/com/debatetimer/service/BaseServiceTest.java @@ -1,9 +1,11 @@ package com.debatetimer.service; import com.debatetimer.DataBaseCleaner; +import com.debatetimer.fixture.CookieGenerator; import com.debatetimer.fixture.MemberGenerator; import com.debatetimer.fixture.ParliamentaryTableGenerator; import com.debatetimer.fixture.ParliamentaryTimeBoxGenerator; +import com.debatetimer.fixture.TokenGenerator; import com.debatetimer.repository.member.MemberRepository; import com.debatetimer.repository.parliamentary.ParliamentaryTableRepository; import com.debatetimer.repository.parliamentary.ParliamentaryTimeBoxRepository; @@ -32,4 +34,10 @@ public abstract class BaseServiceTest { @Autowired protected ParliamentaryTimeBoxGenerator timeBoxGenerator; + + @Autowired + protected TokenGenerator tokenGenerator; + + @Autowired + protected CookieGenerator cookieGenerator; } diff --git a/src/test/java/com/debatetimer/service/auth/AuthServiceTest.java b/src/test/java/com/debatetimer/service/auth/AuthServiceTest.java new file mode 100644 index 00000000..97f545a9 --- /dev/null +++ b/src/test/java/com/debatetimer/service/auth/AuthServiceTest.java @@ -0,0 +1,43 @@ +package com.debatetimer.service.auth; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.debatetimer.domain.member.Member; +import com.debatetimer.exception.custom.DTClientErrorException; +import com.debatetimer.exception.errorcode.ClientErrorCode; +import com.debatetimer.service.BaseServiceTest; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +class AuthServiceTest extends BaseServiceTest { + + @Autowired + private AuthService authService; + + @Nested + class GetMember { + + @Test + void 이메일에_해당하는_멤버가_있으면_해당_멤버를_반환한다() { + Member bito = memberGenerator.generate("default@gmail.com"); + + assertThat(authService.getMember(bito.getEmail()).getId()).isEqualTo(bito.getId()); + } + } + + @Nested + class Logout { + + @Test + void 이메일과_멤버의_정보가_다르면_예외를_발생시킨다() { + Member bito = memberGenerator.generate("bito@gmail.com"); + String email = "default@gmail.com"; + + assertThatThrownBy(() -> authService.logout(bito, email)) + .isInstanceOf(DTClientErrorException.class) + .hasMessage(ClientErrorCode.UNAUTHORIZED_MEMBER.getMessage()); + } + } +} diff --git a/src/test/java/com/debatetimer/service/member/MemberServiceTest.java b/src/test/java/com/debatetimer/service/member/MemberServiceTest.java index be13f55e..43ea0767 100644 --- a/src/test/java/com/debatetimer/service/member/MemberServiceTest.java +++ b/src/test/java/com/debatetimer/service/member/MemberServiceTest.java @@ -5,8 +5,8 @@ import com.debatetimer.domain.member.Member; import com.debatetimer.domain.parliamentary.ParliamentaryTable; -import com.debatetimer.dto.member.MemberCreateRequest; import com.debatetimer.dto.member.MemberCreateResponse; +import com.debatetimer.dto.member.MemberInfo; import com.debatetimer.dto.member.TableResponses; import com.debatetimer.service.BaseServiceTest; import java.util.Optional; @@ -24,21 +24,21 @@ class CreateMember { @Test void 회원를_생성한다() { - MemberCreateRequest request = new MemberCreateRequest("커찬"); + MemberInfo request = new MemberInfo("default@gmail.com"); MemberCreateResponse actual = memberService.createMember(request); Optional foundMember = memberRepository.findById(actual.id()); assertAll( - () -> assertThat(actual.nickname()).isEqualTo(request.nickname()), + () -> assertThat(actual.email()).isEqualTo(request.email()), () -> assertThat(foundMember).isPresent() ); } @Test void 기존_닉네임을_가진_회원이_있다면_해당_회원을_반환한다() { - Member existedMember = memberGenerator.generate("커찬"); - MemberCreateRequest request = new MemberCreateRequest("커찬"); + Member existedMember = memberGenerator.generate("default@gmail.com"); + MemberInfo request = new MemberInfo("default@gmail.com"); MemberCreateResponse actual = memberService.createMember(request); @@ -51,9 +51,9 @@ class GetTables { @Test void 회원의_전체_토론_시간표를_조회한다() { - Member member = memberRepository.save(new Member("커찬")); - parliamentaryTableRepository.save(new ParliamentaryTable(member, "토론 시간표 A", "주제", 1800)); - parliamentaryTableRepository.save(new ParliamentaryTable(member, "토론 시간표 B", "주제", 1900)); + Member member = memberRepository.save(new Member("default@gmail.com")); + parliamentaryTableRepository.save(new ParliamentaryTable(member, "토론 시간표 A", "주제", 1800, true, true)); + parliamentaryTableRepository.save(new ParliamentaryTable(member, "토론 시간표 B", "주제", 1900, true, true)); TableResponses response = memberService.getTables(member.getId()); diff --git a/src/test/java/com/debatetimer/service/parliamentary/ParliamentaryServiceTest.java b/src/test/java/com/debatetimer/service/parliamentary/ParliamentaryServiceTest.java index 89196065..1539ed88 100644 --- a/src/test/java/com/debatetimer/service/parliamentary/ParliamentaryServiceTest.java +++ b/src/test/java/com/debatetimer/service/parliamentary/ParliamentaryServiceTest.java @@ -32,24 +32,24 @@ class Save { @Test void 의회식_토론_테이블을_생성한다() { - Member chan = memberGenerator.generate("커찬"); - TableInfoCreateRequest requestTableInfo = new TableInfoCreateRequest("커찬의 테이블", "주제"); + Member chan = memberGenerator.generate("default@gmail.com"); + TableInfoCreateRequest requestTableInfo = new TableInfoCreateRequest("커찬의 테이블", "주제", true, true); List requestTimeBoxes = List.of( new TimeBoxCreateRequest(Stance.PROS, BoxType.OPENING, 3, 1), new TimeBoxCreateRequest(Stance.CONS, BoxType.OPENING, 3, 1) ); ParliamentaryTableCreateRequest chanTableRequest = new ParliamentaryTableCreateRequest( - requestTableInfo, - requestTimeBoxes - ); + new TableInfoCreateRequest("커찬의 테이블", "주제", true, true), + List.of(new TimeBoxCreateRequest(Stance.PROS, BoxType.OPENING, 3, 1), + new TimeBoxCreateRequest(Stance.CONS, BoxType.OPENING, 3, 1))); ParliamentaryTableResponse savedTableResponse = parliamentaryService.save(chanTableRequest, chan); Optional foundTable = parliamentaryTableRepository.findById(savedTableResponse.id()); List foundTimeBoxes = timeBoxRepository.findAllByParliamentaryTable(foundTable.get()); assertAll( - () -> assertThat(foundTable.get().getName()).isEqualTo(requestTableInfo.name()), - () -> assertThat(foundTimeBoxes).hasSize(requestTimeBoxes.size()) + () -> assertThat(foundTable.get().getName()).isEqualTo(chanTableRequest.info().name()), + () -> assertThat(foundTimeBoxes).hasSize(chanTableRequest.table().size()) ); } } @@ -59,7 +59,7 @@ class FindTable { @Test void 의회식_토론_테이블을_조회한다() { - Member chan = memberGenerator.generate("커찬"); + Member chan = memberGenerator.generate("default@gmail.com"); ParliamentaryTable chanTable = tableGenerator.generate(chan); timeBoxGenerator.generate(chanTable, 1); timeBoxGenerator.generate(chanTable, 2); @@ -74,8 +74,8 @@ class FindTable { @Test void 회원_소유가_아닌_테이블_조회_시_예외를_발생시킨다() { - Member chan = memberGenerator.generate("커찬"); - Member coli = memberGenerator.generate("콜리"); + Member chan = memberGenerator.generate("default@gmail.com"); + Member coli = memberGenerator.generate("default2@gmail.com"); ParliamentaryTable chanTable = tableGenerator.generate(chan); long chanTableId = chanTable.getId(); @@ -90,43 +90,36 @@ class UpdateTable { @Test void 의회식_토론_테이블을_수정한다() { - Member chan = memberGenerator.generate("커찬"); + Member chan = memberGenerator.generate("default@gmail.com"); ParliamentaryTable chanTable = tableGenerator.generate(chan); - TableInfoCreateRequest renewTableInfo = new TableInfoCreateRequest("커찬 테이블", "주제"); - List renewTimeBoxes = List.of( - new TimeBoxCreateRequest(Stance.PROS, BoxType.OPENING, 3, 1), - new TimeBoxCreateRequest(Stance.CONS, BoxType.OPENING, 3, 1) - ); ParliamentaryTableCreateRequest renewTableRequest = new ParliamentaryTableCreateRequest( - renewTableInfo, - renewTimeBoxes - ); + new TableInfoCreateRequest("커찬의 테이블", "주제", true, true), + List.of(new TimeBoxCreateRequest(Stance.PROS, BoxType.OPENING, 3, 1), + new TimeBoxCreateRequest(Stance.CONS, BoxType.OPENING, 3, 1))); - ParliamentaryTableResponse updatedTable = parliamentaryService.updateTable(renewTableRequest, - chanTable.getId(), chan); + parliamentaryService.updateTable(renewTableRequest, chanTable.getId(), chan); + + Optional updatedTable = parliamentaryTableRepository.findById(chanTable.getId()); + List updatedTimeBoxes = timeBoxRepository.findAllByParliamentaryTable( + updatedTable.get()); assertAll( - () -> assertThat(updatedTable.id()).isEqualTo(chanTable.getId()), - () -> assertThat(updatedTable.info().name()).isEqualTo(renewTableInfo.name()), - () -> assertThat(updatedTable.table()).hasSize(renewTimeBoxes.size()) + () -> assertThat(updatedTable.get().getId()).isEqualTo(chanTable.getId()), + () -> assertThat(updatedTable.get().getName()).isEqualTo(renewTableRequest.info().name()), + () -> assertThat(updatedTimeBoxes).hasSize(renewTableRequest.table().size()) ); } @Test void 회원_소유가_아닌_테이블_수정_시_예외를_발생시킨다() { - Member chan = memberGenerator.generate("커찬"); - Member coli = memberGenerator.generate("콜리"); + Member chan = memberGenerator.generate("default@gmail.com"); + Member coli = memberGenerator.generate("default2@gmail.com"); ParliamentaryTable chanTable = tableGenerator.generate(chan); long chanTableId = chanTable.getId(); - TableInfoCreateRequest renewTableInfo = new TableInfoCreateRequest("새로운 테이블", "주제"); - List renewTimeBoxes = List.of( - new TimeBoxCreateRequest(Stance.PROS, BoxType.OPENING, 3, 1), - new TimeBoxCreateRequest(Stance.CONS, BoxType.OPENING, 3, 1) - ); ParliamentaryTableCreateRequest renewTableRequest = new ParliamentaryTableCreateRequest( - renewTableInfo, - renewTimeBoxes - ); + new TableInfoCreateRequest("새로운 테이블", "주제", true, true), + List.of(new TimeBoxCreateRequest(Stance.PROS, BoxType.OPENING, 3, 1), + new TimeBoxCreateRequest(Stance.CONS, BoxType.OPENING, 3, 1))); assertThatThrownBy(() -> parliamentaryService.updateTable(renewTableRequest, chanTableId, coli)) .isInstanceOf(DTClientErrorException.class) @@ -139,7 +132,7 @@ class DeleteTable { @Test void 의회식_토론_테이블을_삭제한다() { - Member chan = memberGenerator.generate("커찬"); + Member chan = memberGenerator.generate("default@gmail.com"); ParliamentaryTable chanTable = tableGenerator.generate(chan); timeBoxGenerator.generate(chanTable, 1); timeBoxGenerator.generate(chanTable, 2); @@ -157,8 +150,8 @@ class DeleteTable { @Test void 회원_소유가_아닌_테이블_삭제_시_예외를_발생시킨다() { - Member chan = memberGenerator.generate("커찬"); - Member coli = memberGenerator.generate("콜리"); + Member chan = memberGenerator.generate("default@gmail.com"); + Member coli = memberGenerator.generate("default2@gmail.com"); ParliamentaryTable chanTable = tableGenerator.generate(chan); Long chanTableId = chanTable.getId(); diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index e105e48e..1b0ceaf0 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -24,3 +24,8 @@ spring: cors: origin: http://test.debate-timer.com + +jwt: + secret_key: testtesttesttesttesttesttesttest + access_token_expiration_millis: 5000 + refresh_token_expiration_millis: 10000