diff --git a/src/main/java/roomescape/auth/AdminInterceptor.java b/src/main/java/roomescape/auth/AdminInterceptor.java new file mode 100644 index 00000000..b4bb6b9a --- /dev/null +++ b/src/main/java/roomescape/auth/AdminInterceptor.java @@ -0,0 +1,32 @@ +package roomescape.auth; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +@Component +public class AdminInterceptor implements HandlerInterceptor { + private final AuthService authService; + private final AuthorizationExtractor authorizationExtractor; + + public AdminInterceptor(AuthService authService, AuthorizationExtractor authorizationExtractor) { + this.authService = authService; + this.authorizationExtractor = authorizationExtractor; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) + throws Exception { + + String token = authorizationExtractor.extract(request); + LoginMember loginMember = authService.createAuthentication(token); + + if (!Role.isAdmin(loginMember.role())) { + response.setStatus(401); + return false; + } + + return true; + } +} diff --git a/src/main/java/roomescape/auth/AuthController.java b/src/main/java/roomescape/auth/AuthController.java index cb21fc3f..674a528c 100644 --- a/src/main/java/roomescape/auth/AuthController.java +++ b/src/main/java/roomescape/auth/AuthController.java @@ -3,11 +3,9 @@ import io.jsonwebtoken.JwtException; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletResponse; -import java.util.Map; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -36,10 +34,9 @@ public ResponseEntity login(@RequestBody LoginRequest loginRequest, HttpServletR } @GetMapping("/login/check") - public ResponseEntity check(@CookieValue(name = TOKEN_COOKIE) String token) { + public ResponseEntity check(LoginMember loginMember) { try { - Map claims = authService.extractClaims(token); - return ResponseEntity.ok().body(claims); + return ResponseEntity.ok().body(loginMember); } catch (JwtException | IllegalArgumentException e) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid token"); } diff --git a/src/main/java/roomescape/auth/AuthService.java b/src/main/java/roomescape/auth/AuthService.java index eadfabb8..23d47114 100644 --- a/src/main/java/roomescape/auth/AuthService.java +++ b/src/main/java/roomescape/auth/AuthService.java @@ -1,6 +1,6 @@ package roomescape.auth; -import java.util.Map; +import io.jsonwebtoken.Claims; import org.springframework.stereotype.Service; import roomescape.member.Member; import roomescape.member.MemberDao; @@ -20,7 +20,11 @@ public String createToken(String email, String password) { return jwtTokenProvider.createToken(member); } - public Map extractClaims(String token) { - return jwtTokenProvider.getClaims(token); + public LoginMember createAuthentication(String token) { + Claims claims = jwtTokenProvider.getClaims(token); + return new LoginMember( + JwtTokenProvider.extract(claims, "sub"), + JwtTokenProvider.extract(claims, "name"), + Role.valueOf(JwtTokenProvider.extract(claims, "role"))); } } diff --git a/src/main/java/roomescape/auth/AuthorizationExtractor.java b/src/main/java/roomescape/auth/AuthorizationExtractor.java new file mode 100644 index 00000000..990887e5 --- /dev/null +++ b/src/main/java/roomescape/auth/AuthorizationExtractor.java @@ -0,0 +1,7 @@ +package roomescape.auth; + +import jakarta.servlet.http.HttpServletRequest; + +public interface AuthorizationExtractor { + String extract(HttpServletRequest request); +} diff --git a/src/main/java/roomescape/auth/CookieAuthorizationExtractor.java b/src/main/java/roomescape/auth/CookieAuthorizationExtractor.java new file mode 100644 index 00000000..5e6e2fc0 --- /dev/null +++ b/src/main/java/roomescape/auth/CookieAuthorizationExtractor.java @@ -0,0 +1,20 @@ +package roomescape.auth; + +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import java.util.Arrays; +import org.springframework.stereotype.Component; + +@Component +public class CookieAuthorizationExtractor implements AuthorizationExtractor { + private static final String AUTHORIZATION_NAME = "token"; + + @Override + public String extract(HttpServletRequest request) { + return Arrays.stream(request.getCookies()) + .filter(cookie -> cookie.getName().equals(AUTHORIZATION_NAME)) + .map(Cookie::getValue) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Empty cookie")); + } +} diff --git a/src/main/java/roomescape/auth/JwtTokenProvider.java b/src/main/java/roomescape/auth/JwtTokenProvider.java index e028eb5b..fc379dc6 100644 --- a/src/main/java/roomescape/auth/JwtTokenProvider.java +++ b/src/main/java/roomescape/auth/JwtTokenProvider.java @@ -4,7 +4,7 @@ import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.Keys; import java.util.Date; -import java.util.Map; +import java.util.Optional; import javax.crypto.SecretKey; import org.springframework.stereotype.Component; import roomescape.member.Member; @@ -14,15 +14,17 @@ public class JwtTokenProvider { private final SecretKey secretKey; private final long validityInMilliseconds; + private final TimeProvider timeProvider; - public JwtTokenProvider(JwtProperties jwtProperties) { + public JwtTokenProvider(JwtProperties jwtProperties, TimeProvider timeProvider) { this.secretKey = Keys.hmacShaKeyFor(jwtProperties.getSecretKey().getBytes()); this.validityInMilliseconds = jwtProperties.getValidityInMilliseconds(); + this.timeProvider = timeProvider; } public String createToken(Member member) { Claims claims = Jwts.claims().setSubject(member.getEmail()); - Date now = new Date(); + Date now = timeProvider.now(); Date validity = new Date(now.getTime() + validityInMilliseconds); return Jwts.builder() @@ -35,11 +37,16 @@ public String createToken(Member member) { .compact(); } - public Map getClaims(String token) { + public Claims getClaims(String token) { return Jwts.parserBuilder() .setSigningKey(secretKey) .build() .parseClaimsJws(token) .getBody(); } + + public static String extract(Claims claims, String key) { + return Optional.ofNullable(claims.get(key, String.class)) + .orElseThrow(() -> new IllegalArgumentException("Invalid claims")); + } } diff --git a/src/main/java/roomescape/auth/LoginMember.java b/src/main/java/roomescape/auth/LoginMember.java new file mode 100644 index 00000000..f05c2f60 --- /dev/null +++ b/src/main/java/roomescape/auth/LoginMember.java @@ -0,0 +1,34 @@ +package roomescape.auth; + +public record LoginMember( + String email, + String name, + Role role +) { + public LoginMember(String email, String name, Role role) { + validateEmail(email); + validateName(name); + validateRole(role); + this.email = email; + this.name = name; + this.role = role; + } + + private void validateEmail(String email) { + if (email == null || email.isBlank()) { + throw new IllegalArgumentException("Invalid email"); + } + } + + private void validateName(String name) { + if (name == null || name.isBlank()) { + throw new IllegalArgumentException("Invalid name"); + } + } + + private void validateRole(Role role) { + if (role == null) { + throw new IllegalArgumentException("Invalid role"); + } + } +} diff --git a/src/main/java/roomescape/auth/LoginMemberArgumentResolver.java b/src/main/java/roomescape/auth/LoginMemberArgumentResolver.java new file mode 100644 index 00000000..301bd030 --- /dev/null +++ b/src/main/java/roomescape/auth/LoginMemberArgumentResolver.java @@ -0,0 +1,32 @@ +package roomescape.auth; + +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.core.MethodParameter; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +@Component +public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver { + private final AuthService authService; + private final AuthorizationExtractor authorizationExtractor; + + public LoginMemberArgumentResolver(AuthService authService, AuthorizationExtractor authorizationExtractor) { + this.authService = authService; + this.authorizationExtractor = authorizationExtractor; + } + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.getParameterType().equals(LoginMember.class); + } + + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + String token = authorizationExtractor.extract((HttpServletRequest) webRequest.getNativeRequest()); + return authService.createAuthentication(token); + } +} diff --git a/src/main/java/roomescape/auth/Role.java b/src/main/java/roomescape/auth/Role.java new file mode 100644 index 00000000..780cf868 --- /dev/null +++ b/src/main/java/roomescape/auth/Role.java @@ -0,0 +1,9 @@ +package roomescape.auth; + +public enum Role { + ADMIN, USER; + + static boolean isAdmin(Role role) { + return role == ADMIN; + } +} diff --git a/src/main/java/roomescape/auth/SystemTimeProvider.java b/src/main/java/roomescape/auth/SystemTimeProvider.java new file mode 100644 index 00000000..43f10890 --- /dev/null +++ b/src/main/java/roomescape/auth/SystemTimeProvider.java @@ -0,0 +1,12 @@ +package roomescape.auth; + +import java.util.Date; +import org.springframework.stereotype.Component; + +@Component +public class SystemTimeProvider implements TimeProvider { + @Override + public Date now() { + return new Date(); + } +} diff --git a/src/main/java/roomescape/auth/TimeProvider.java b/src/main/java/roomescape/auth/TimeProvider.java new file mode 100644 index 00000000..40e3751d --- /dev/null +++ b/src/main/java/roomescape/auth/TimeProvider.java @@ -0,0 +1,7 @@ +package roomescape.auth; + +import java.util.Date; + +public interface TimeProvider { + Date now(); +} diff --git a/src/main/java/roomescape/auth/WebConfig.java b/src/main/java/roomescape/auth/WebConfig.java new file mode 100644 index 00000000..aeb29d97 --- /dev/null +++ b/src/main/java/roomescape/auth/WebConfig.java @@ -0,0 +1,29 @@ +package roomescape.auth; + +import java.util.List; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + private final LoginMemberArgumentResolver loginMemberArgumentResolver; + private final AdminInterceptor adminInterceptor; + + public WebConfig(LoginMemberArgumentResolver loginMemberArgumentResolver, AdminInterceptor adminInterceptor) { + this.loginMemberArgumentResolver = loginMemberArgumentResolver; + this.adminInterceptor = adminInterceptor; + } + + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(loginMemberArgumentResolver); + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(adminInterceptor) + .addPathPatterns("/admin/**"); + } +} diff --git a/src/main/java/roomescape/reservation/ReservationController.java b/src/main/java/roomescape/reservation/ReservationController.java index b3bef399..42d50e9c 100644 --- a/src/main/java/roomescape/reservation/ReservationController.java +++ b/src/main/java/roomescape/reservation/ReservationController.java @@ -1,5 +1,7 @@ package roomescape.reservation; +import java.net.URI; +import java.util.List; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -7,9 +9,7 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; - -import java.net.URI; -import java.util.List; +import roomescape.auth.LoginMember; @RestController public class ReservationController { @@ -26,13 +26,8 @@ public List list() { } @PostMapping("/reservations") - public ResponseEntity create(@RequestBody ReservationRequest reservationRequest) { - if (reservationRequest.getName() == null - || reservationRequest.getDate() == null - || reservationRequest.getTheme() == null - || reservationRequest.getTime() == null) { - return ResponseEntity.badRequest().build(); - } + public ResponseEntity create(@RequestBody ReservationRequest reservationRequest, LoginMember loginMember) { + reservationRequest.checkName(loginMember.name()); ReservationResponse reservation = reservationService.save(reservationRequest); return ResponseEntity.created(URI.create("/reservations/" + reservation.getId())).body(reservation); diff --git a/src/main/java/roomescape/reservation/ReservationRequest.java b/src/main/java/roomescape/reservation/ReservationRequest.java index 19f44124..a0b27144 100644 --- a/src/main/java/roomescape/reservation/ReservationRequest.java +++ b/src/main/java/roomescape/reservation/ReservationRequest.java @@ -6,6 +6,16 @@ public class ReservationRequest { private Long theme; private Long time; + public ReservationRequest(String name, String date, Long theme, Long time) { + validateDate(date); + validateTheme(theme); + validateTime(time); + this.name = name; + this.date = date; + this.theme = theme; + this.time = time; + } + public String getName() { return name; } @@ -21,4 +31,28 @@ public Long getTheme() { public Long getTime() { return time; } + + public void checkName(String name) { + if (this.name == null) { + this.name = name; + } + } + + private void validateDate(String date) { + if (date == null) { + throw new IllegalArgumentException("Invalid date"); + } + } + + private void validateTheme(Long theme) { + if (theme == null) { + throw new IllegalArgumentException("Invalid theme"); + } + } + + private void validateTime(Long time) { + if (time == null) { + throw new IllegalArgumentException("Invalid time"); + } + } } diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index 2413ff5a..0813ec3a 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -11,6 +11,7 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; +import roomescape.reservation.ReservationResponse; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) @@ -43,4 +44,73 @@ public class MissionStepTest { assertThat(checkResponse.body().jsonPath().getString("name")).isEqualTo("어드민"); } + + @Test + void 이단계() { + String token = createToken("admin@email.com", "password"); // 일단계에서 토큰을 추출하는 로직을 메서드로 따로 만들어서 활용하세요. + + Map params = new HashMap<>(); + params.put("date", "2024-03-01"); + params.put("time", "1"); + params.put("theme", "1"); + + ExtractableResponse response = RestAssured.given().log().all() + .body(params) + .cookie("token", token) + .contentType(ContentType.JSON) + .post("/reservations") + .then().log().all() + .extract(); + + assertThat(response.statusCode()).isEqualTo(201); + assertThat(response.as(ReservationResponse.class).getName()).isEqualTo("어드민"); + + params.put("name", "브라운"); + + ExtractableResponse adminResponse = RestAssured.given().log().all() + .body(params) + .cookie("token", token) + .contentType(ContentType.JSON) + .post("/reservations") + .then().log().all() + .extract(); + + assertThat(adminResponse.statusCode()).isEqualTo(201); + assertThat(adminResponse.as(ReservationResponse.class).getName()).isEqualTo("브라운"); + } + + private String createToken(String email, String password) { + Map params = new HashMap<>(); + params.put("email", email); + params.put("password", password); + + ExtractableResponse response = RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(params) + .when().post("/login") + .then().log().all() + .statusCode(200) + .extract(); + + return response.headers().get("Set-Cookie").getValue().split(";")[0].split("=")[1]; + } + + @Test + void 삼단계() { + String brownToken = createToken("brown@email.com", "password"); + + RestAssured.given().log().all() + .cookie("token", brownToken) + .get("/admin") + .then().log().all() + .statusCode(401); + + String adminToken = createToken("admin@email.com", "password"); + + RestAssured.given().log().all() + .cookie("token", adminToken) + .get("/admin") + .then().log().all() + .statusCode(200); + } } diff --git a/src/test/java/roomescape/auth/AuthServiceTest.java b/src/test/java/roomescape/auth/AuthServiceTest.java new file mode 100644 index 00000000..3a7e3589 --- /dev/null +++ b/src/test/java/roomescape/auth/AuthServiceTest.java @@ -0,0 +1,42 @@ +package roomescape.auth; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import java.util.Date; +import org.junit.jupiter.api.Test; +import org.springframework.jdbc.core.JdbcTemplate; +import roomescape.member.Member; +import roomescape.member.MemberDao; + +class AuthServiceTest { + private final String originSecretKey = "ThisIsATestKeyForJsonWebTokenProvider"; + private final long originValidity = 6000; + private final JwtTokenProvider jwtTokenProvider = new JwtTokenProvider( + new JwtProperties(originSecretKey, originValidity), + new SystemTimeProvider()); + + private final Member member = new Member(1L, "test", "test@email.com", "ADMIN"); + private final MemberDao memberDao = new MemberDao(new JdbcTemplate()); + private final AuthService authService = new AuthService(jwtTokenProvider, memberDao); + + @Test + void 토큰의_키가_존재하지_않는_경우_토큰_정보_조회에_실패한다() { + //given + Date now = new SystemTimeProvider().now(); + Date validity = new Date(now.getTime() + originValidity); + + String token = Jwts.builder() + .setSubject(member.getEmail()) + .claim("name", member.getName()) + .setIssuedAt(now) + .setExpiration(validity) + .signWith(Keys.hmacShaKeyFor(originSecretKey.getBytes())) + .compact(); + + //when, then + assertThatThrownBy(() -> authService.createAuthentication(token)) + .isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/roomescape/auth/JwtTokenProviderTest.java b/src/test/java/roomescape/auth/JwtTokenProviderTest.java index 4e1ff9e9..45513e72 100644 --- a/src/test/java/roomescape/auth/JwtTokenProviderTest.java +++ b/src/test/java/roomescape/auth/JwtTokenProviderTest.java @@ -5,6 +5,7 @@ import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.security.SignatureException; +import java.util.Date; import java.util.Map; import org.junit.jupiter.api.Test; import roomescape.member.Member; @@ -12,12 +13,22 @@ class JwtTokenProviderTest { private final String originSecretKey = "ThisIsATestKeyForJsonWebTokenProvider"; private final long originValidity = 6000; - private final JwtTokenProvider jwtTokenProvider; - private final Member member; + private final JwtTokenProvider jwtTokenProvider = new JwtTokenProvider( + new JwtProperties(originSecretKey, originValidity), + new SystemTimeProvider()); + private final Member member = new Member(1L, "test", "test@email.com", "ADMIN"); - public JwtTokenProviderTest() { - this.jwtTokenProvider = new JwtTokenProvider(new JwtProperties(originSecretKey, originValidity)); - this.member = new Member(1L, "test", "test@email.com", "ADMIN"); + static class MockTimeProvider implements TimeProvider { + private final Date mockDate; + + public MockTimeProvider(Date mockDate) { + this.mockDate = mockDate; + } + + @Override + public Date now() { + return mockDate; + } } @Test @@ -46,7 +57,9 @@ public JwtTokenProviderTest() { @Test void 토큰이_만료된_경우_토큰_정보_조회에_실패한다() { //given - JwtTokenProvider otherProvider = new JwtTokenProvider(new JwtProperties(originSecretKey, 0)); + JwtTokenProvider otherProvider = new JwtTokenProvider( + new JwtProperties(originSecretKey, originValidity), + new MockTimeProvider(new Date(new Date().getTime() - originValidity))); String expireToken = otherProvider.createToken(member); //when, then @@ -58,7 +71,8 @@ public JwtTokenProviderTest() { void 토큰의_서명이_다른_경우_토큰_정보_조회에_실패한다() { //given JwtTokenProvider otherProvider = new JwtTokenProvider( - new JwtProperties(originSecretKey + " ", originValidity)); + new JwtProperties(originSecretKey + " ", originValidity), + new SystemTimeProvider()); String alteredSignatureToken = otherProvider.createToken(member); //when, then diff --git a/src/test/java/roomescape/auth/LoginCheckTest.java b/src/test/java/roomescape/auth/LoginCheckTest.java index d9750600..9b0d7ea8 100644 --- a/src/test/java/roomescape/auth/LoginCheckTest.java +++ b/src/test/java/roomescape/auth/LoginCheckTest.java @@ -6,12 +6,31 @@ import io.restassured.http.ContentType; import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; +import java.util.HashMap; +import java.util.Map; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) public class LoginCheckTest { + @Test + void 인증_정보_조회_성공() { + String token = createToken("admin@email.com", "password"); + + LoginMember loginMember = RestAssured.given().log().all() + .contentType(ContentType.JSON) + .cookie("token", token) + .when().get("/login/check") + .then().log().all() + .statusCode(200) + .extract().as(LoginMember.class); + + assertThat(loginMember.name()).isEqualTo("어드민"); + assertThat(loginMember.email()).isEqualTo("admin@email.com"); + assertThat(loginMember.role()).isEqualTo(Role.ADMIN); + } + @Test void 인증_토큰_쿠키가_비어있는_경우_인증_정보_조회에_실패한다() { //when @@ -24,4 +43,20 @@ public class LoginCheckTest { //then assertThat(response.statusCode()).isEqualTo(400); } + + private String createToken(String email, String password) { + Map params = new HashMap<>(); + params.put("email", email); + params.put("password", password); + + ExtractableResponse response = RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(params) + .when().post("/login") + .then().log().all() + .statusCode(200) + .extract(); + + return response.headers().get("Set-Cookie").getValue().split(";")[0].split("=")[1]; + } } diff --git a/src/test/java/roomescape/auth/LoginMemberTest.java b/src/test/java/roomescape/auth/LoginMemberTest.java new file mode 100644 index 00000000..5afc49e3 --- /dev/null +++ b/src/test/java/roomescape/auth/LoginMemberTest.java @@ -0,0 +1,33 @@ +package roomescape.auth; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class LoginMemberTest { + + @ParameterizedTest + @ValueSource(strings = {""}) + void 이메일이_유효하지_않은_경우_예외가_발생한다(String email) { + assertThatThrownBy(() -> new LoginMember( + email, "어드민", Role.ADMIN + )).isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @ValueSource(strings = {""}) + void 이름이_유효하지_않은_경우_예외가_발생한다(String name) { + assertThatThrownBy(() -> new LoginMember( + "admin@email.com", name, Role.ADMIN + )).isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @ValueSource(strings = {""}) + void 역할이_유효하지_않은_경우_예외가_발생한다(String role) { + assertThatThrownBy(() -> new LoginMember( + "admin@email.com", "어드민", Role.valueOf(role) + )).isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/roomescape/auth/LoginTest.java b/src/test/java/roomescape/auth/LoginTest.java index c6cec621..05cce246 100644 --- a/src/test/java/roomescape/auth/LoginTest.java +++ b/src/test/java/roomescape/auth/LoginTest.java @@ -14,7 +14,7 @@ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) class LoginTest { @Test - void 로그인_성공() { + void 로그인에_성공한_경우_토큰을_쿠키에_저장한다() { //given Map validCredentials = new HashMap<>(); validCredentials.put("email", "admin@email.com"); diff --git a/src/test/java/roomescape/reservation/CreateReservations.java b/src/test/java/roomescape/reservation/CreateReservations.java new file mode 100644 index 00000000..c2d6fc31 --- /dev/null +++ b/src/test/java/roomescape/reservation/CreateReservations.java @@ -0,0 +1,146 @@ +package roomescape.reservation; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import java.util.HashMap; +import java.util.Map; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) +class CreateReservations { + + @Test + void 로그인_상태에서_예약자명이_존재하지않는_경우_로그인_정보명으로_예약된다() { + //given + String token = createToken("admin@email.com", "password"); + Map reservationRequest = createReservationRequest("2024-03-01", null, "1", "1"); + + //when + ReservationResponse reservationResponse = sendCreateReservationsRequest(reservationRequest, token).as( + ReservationResponse.class); + + //then + assertThat(reservationResponse.getName()).isEqualTo("어드민"); + } + + @Test + void 로그인_상태에서_예약자명이_존재하는_경우_예약자명으로_예약된다() { + //given + String token = createToken("admin@email.com", "password"); + Map reservationRequest = createReservationRequest("2024-03-01", "브라운", "1", "1"); + + //when + ReservationResponse reservationResponse = sendCreateReservationsRequest(reservationRequest, token).as( + ReservationResponse.class); + + //then + assertThat(reservationResponse.getName()).isEqualTo("브라운"); + } + + @Test + void 비로그인_상태에서_예약할_경우_예약에_실패한다() { + //given + Map reservationRequest = createReservationRequest("2024-03-01", "브라운", "1", "1"); + + //when + ExtractableResponse response = RestAssured.given().log().all() + .body(reservationRequest) + .contentType(ContentType.JSON) + .post("/reservations") + .then().log().all() + .extract(); + + //then + assertThat(response.statusCode()).isEqualTo(400); + } + + @Test + void 유효하지_않은_예약_날짜인_경우_예약에_실패한다() { + //given + String token = createToken("admin@email.com", "password"); + Map reservationRequest = createReservationRequest(null, "브라운", "1", "1"); + + //when + ExtractableResponse response = sendCreateReservationsRequest(reservationRequest, token); + + //then + assertThat(response.statusCode()).isEqualTo(400); + } + + @Test + void 유효하지_않은_예약_시간인_경우_예약에_실패한다() { + //given + String token = createToken("admin@email.com", "password"); + Map reservationRequest = createReservationRequest("2024-03-01", "브라운", null, "1"); + + //when + ExtractableResponse response = sendCreateReservationsRequest(reservationRequest, token); + + //then + assertThat(response.statusCode()).isEqualTo(400); + } + + @Test + void 유효하지_않은_예약_테마인_경우_예약에_실패한다() { + //given + String token = createToken("admin@email.com", "password"); + Map reservationRequest = createReservationRequest("2024-03-01", "브라운", "1", null); + + //when + ExtractableResponse response = sendCreateReservationsRequest(reservationRequest, token); + + //then + assertThat(response.statusCode()).isEqualTo(400); + } + + @NotNull + private Map createReservationRequest(String date, String name, String time, String theme) { + Map reservationRequest = new HashMap<>(); + if (date != null) { + reservationRequest.put("date", date); + } + if (name != null) { + reservationRequest.put("name", name); + } + if (time != null) { + reservationRequest.put("time", time); + } + if (theme != null) { + reservationRequest.put("theme", theme); + } + return reservationRequest; + } + + private ExtractableResponse sendCreateReservationsRequest(Map reservationRequest, + String token) { + return RestAssured.given().log().all() + .body(reservationRequest) + .cookie("token", token) + .contentType(ContentType.JSON) + .post("/reservations") + .then().log().all() + .extract(); + } + + private String createToken(String email, String password) { + Map params = new HashMap<>(); + params.put("email", email); + params.put("password", password); + + ExtractableResponse response = RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(params) + .when().post("/login") + .then().log().all() + .statusCode(200) + .extract(); + + return response.headers().get("Set-Cookie").getValue().split(";")[0].split("=")[1]; + } +}