diff --git a/README.md b/README.md index 3025b72f06..1de7331df3 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ +# 쿠키 세션 구현하기 +- [x] HttpCookie에 JSESSION 값 저장하기 + - [x] HttpCookie 테스트 코드 작성 +- [x] HttpSession 구현하기 +- [x] HttpCookie, HttpSession으로 변경된 객체 테스트 코드 작성 + # HTTP 서버 구현하기 - [x] 서버를 실행시켜서 브라우저로 서버(`http://localhost:8080/index.html`)에 접속하면 index.html 페이지를 보여준다. diff --git a/app/src/main/java/nextstep/jwp/controller/Controller.java b/app/src/main/java/nextstep/jwp/controller/Controller.java index 41b29eb98b..f7b2643b88 100644 --- a/app/src/main/java/nextstep/jwp/controller/Controller.java +++ b/app/src/main/java/nextstep/jwp/controller/Controller.java @@ -6,7 +6,5 @@ public interface Controller { - boolean matchUri(String uri); - - HttpResponse doService(HttpRequest httpRequest) throws IOException; + HttpResponse service(HttpRequest httpRequest) throws IOException; } diff --git a/app/src/main/java/nextstep/jwp/controller/Controllers.java b/app/src/main/java/nextstep/jwp/controller/Controllers.java deleted file mode 100644 index 61f3fb0c8f..0000000000 --- a/app/src/main/java/nextstep/jwp/controller/Controllers.java +++ /dev/null @@ -1,50 +0,0 @@ -package nextstep.jwp.controller; - -import java.io.IOException; -import java.util.Arrays; -import java.util.List; -import nextstep.jwp.db.InMemoryUserRepository; -import nextstep.jwp.http.request.HttpRequest; -import nextstep.jwp.http.response.HttpResponse; -import nextstep.jwp.service.LoginService; -import nextstep.jwp.service.RegisterService; -import nextstep.jwp.service.StaticResourceService; - -public class Controllers { - - private final List restControllers; - private final Controller staticResourceController; - - private Controllers(List restControllers, Controller staticResourceController) { - this.restControllers = restControllers; - this.staticResourceController = staticResourceController; - } - - public static Controllers loadContext() { - InMemoryUserRepository userRepository = InMemoryUserRepository.initialize(); - LoginService loginService = new LoginService(userRepository); - RegisterService registerService = new RegisterService(userRepository); - StaticResourceService staticResourceService = new StaticResourceService(); - - Controller staticResourceController = new StaticResourceController(staticResourceService); - - Controller loginController = new LoginController(loginService, staticResourceService); - Controller registerController = new RegisterController(registerService, staticResourceService); - List controllers = Arrays.asList(loginController, registerController); - - return new Controllers(controllers, staticResourceController); - } - - public HttpResponse doService(HttpRequest httpRequest) throws IOException { - Controller controller = findByUri(httpRequest.getUri()); - - return controller.doService(httpRequest); - } - - private Controller findByUri(String requestUri) { - return restControllers.stream() - .filter(controller -> controller.matchUri(requestUri)) - .findAny() - .orElse(staticResourceController); - } -} diff --git a/app/src/main/java/nextstep/jwp/controller/LoginController.java b/app/src/main/java/nextstep/jwp/controller/LoginController.java index 9ca220d746..1e548e0a45 100644 --- a/app/src/main/java/nextstep/jwp/controller/LoginController.java +++ b/app/src/main/java/nextstep/jwp/controller/LoginController.java @@ -1,10 +1,20 @@ package nextstep.jwp.controller; +import static nextstep.jwp.controller.StaticResourcePath.INDEX_PAGE; +import static nextstep.jwp.controller.StaticResourcePath.NOT_FOUND_PAGE; +import static nextstep.jwp.controller.StaticResourcePath.UNAUTHORIZED_PAGE; +import static nextstep.jwp.http.common.HttpStatus.FOUND; +import static nextstep.jwp.http.common.HttpStatus.OK; + +import java.io.IOException; import nextstep.jwp.controller.request.LoginRequest; +import nextstep.jwp.controller.response.LoginResponse; +import nextstep.jwp.exception.StaticResourceNotFoundException; import nextstep.jwp.exception.UnauthorizedException; import nextstep.jwp.http.common.HttpStatus; import nextstep.jwp.http.request.HttpRequest; import nextstep.jwp.http.response.HttpResponse; +import nextstep.jwp.model.StaticResource; import nextstep.jwp.service.LoginService; import nextstep.jwp.service.StaticResourceService; import org.slf4j.Logger; @@ -15,25 +25,51 @@ public class LoginController extends RestController { private static final Logger LOGGER = LoggerFactory.getLogger(LoginController.class); private final LoginService loginService; + private final StaticResourceService staticResourceService; public LoginController(LoginService loginService, StaticResourceService staticResourceService) { - super(staticResourceService); this.loginService = loginService; + this.staticResourceService = staticResourceService; + } + + @Override + protected HttpResponse doGet(HttpRequest httpRequest) throws IOException { + try { + if (httpRequest.hasCookie() && loginService.isAlreadyLogin(httpRequest.getCookie())) { + return HttpResponse.redirect(FOUND, INDEX_PAGE.getValue()); + } + + StaticResource staticResource = staticResourceService.findByPathWithExtension(httpRequest.getUri(), ".html"); + + return HttpResponse.withBody(HttpStatus.OK, staticResource); + } catch (StaticResourceNotFoundException e) { + StaticResource staticResource = staticResourceService.findByPath(NOT_FOUND_PAGE.getValue()); + + LOGGER.warn(e.getMessage()); + + return HttpResponse.withBody(HttpStatus.NOT_FOUND, staticResource); + } } @Override - protected HttpResponse doPost(HttpRequest httpRequest) { + protected HttpResponse doPost(HttpRequest httpRequest) throws IOException { try { + if (httpRequest.hasCookie() && loginService.isAlreadyLogin(httpRequest.getCookie())) { + return HttpResponse.redirect(FOUND, INDEX_PAGE.getValue()); + } + LoginRequest loginRequest = getLoginRequest(httpRequest); - loginService.login(loginRequest); + LoginResponse loginResponse = loginService.login(loginRequest); LOGGER.debug("Login Success."); - return HttpResponse.redirect(HttpStatus.FOUND, "/index.html"); + StaticResource resource = staticResourceService.findByPath(INDEX_PAGE.getValue()); + return HttpResponse.withBodyAndCookie(OK, resource, loginResponse.toCookieString()); } catch (UnauthorizedException e) { LOGGER.debug("Login Failed."); - return HttpResponse.redirect(HttpStatus.UNAUTHORIZED, "/401.html"); + StaticResource resource = staticResourceService.findByPath(UNAUTHORIZED_PAGE.getValue()); + return HttpResponse.withBody(HttpStatus.UNAUTHORIZED, resource); } } @@ -45,9 +81,4 @@ private LoginRequest getLoginRequest(HttpRequest httpRequest) { return new LoginRequest(account, password); } - - @Override - public boolean matchUri(String uri) { - return uri.startsWith("/login"); - } } diff --git a/app/src/main/java/nextstep/jwp/controller/RegisterController.java b/app/src/main/java/nextstep/jwp/controller/RegisterController.java index 490ce4f005..8869a9a340 100644 --- a/app/src/main/java/nextstep/jwp/controller/RegisterController.java +++ b/app/src/main/java/nextstep/jwp/controller/RegisterController.java @@ -1,10 +1,17 @@ package nextstep.jwp.controller; +import static nextstep.jwp.controller.StaticResourcePath.CONFLICT_PAGE; +import static nextstep.jwp.controller.StaticResourcePath.INDEX_PAGE; +import static nextstep.jwp.controller.StaticResourcePath.NOT_FOUND_PAGE; + +import java.io.IOException; import nextstep.jwp.controller.request.RegisterRequest; import nextstep.jwp.exception.DuplicateAccountException; +import nextstep.jwp.exception.StaticResourceNotFoundException; import nextstep.jwp.http.common.HttpStatus; import nextstep.jwp.http.request.HttpRequest; import nextstep.jwp.http.response.HttpResponse; +import nextstep.jwp.model.StaticResource; import nextstep.jwp.service.RegisterService; import nextstep.jwp.service.StaticResourceService; import org.slf4j.Logger; @@ -15,25 +22,42 @@ public class RegisterController extends RestController { private static final Logger LOGGER = LoggerFactory.getLogger(RegisterController.class); private final RegisterService registerService; + private final StaticResourceService staticResourceService; public RegisterController(RegisterService registerService, StaticResourceService staticResourceService) { - super(staticResourceService); this.registerService = registerService; + this.staticResourceService = staticResourceService; + } + + @Override + protected HttpResponse doGet(HttpRequest httpRequest) throws IOException { + try { + StaticResource staticResource = staticResourceService.findByPathWithExtension(httpRequest.getUri(), ".html"); + + return HttpResponse.withBody(HttpStatus.OK, staticResource); + } catch (StaticResourceNotFoundException e) { + StaticResource staticResource = staticResourceService.findByPath(NOT_FOUND_PAGE.getValue()); + + LOGGER.warn(e.getMessage()); + + return HttpResponse.withBody(HttpStatus.NOT_FOUND, staticResource); + } } @Override - protected HttpResponse doPost(HttpRequest httpRequest) { + protected HttpResponse doPost(HttpRequest httpRequest) throws IOException { try { RegisterRequest registerRequest = getRegisterRequest(httpRequest); registerService.registerUser(registerRequest); LOGGER.debug("Register Success."); - return HttpResponse.redirect(HttpStatus.MOVED_PERMANENTLY, "/index.html"); + return HttpResponse.redirect(HttpStatus.MOVED_PERMANENTLY, INDEX_PAGE.getValue()); } catch (DuplicateAccountException e) { LOGGER.debug("Register Failed."); - return HttpResponse.redirect(HttpStatus.CONFLICT, "/409.html"); + StaticResource resource = staticResourceService.findByPath(CONFLICT_PAGE.getValue()); + return HttpResponse.withBody(HttpStatus.CONFLICT, resource); } } @@ -46,9 +70,4 @@ private RegisterRequest getRegisterRequest(HttpRequest httpRequest) { return new RegisterRequest(account, password, email); } - - @Override - public boolean matchUri(String uri) { - return uri.startsWith("/register"); - } } diff --git a/app/src/main/java/nextstep/jwp/controller/RestController.java b/app/src/main/java/nextstep/jwp/controller/RestController.java index d4ac6bf2bd..7d0b7f7855 100644 --- a/app/src/main/java/nextstep/jwp/controller/RestController.java +++ b/app/src/main/java/nextstep/jwp/controller/RestController.java @@ -3,47 +3,20 @@ import static nextstep.jwp.http.request.Method.GET; import java.io.IOException; -import nextstep.jwp.exception.StaticResourceNotFoundException; -import nextstep.jwp.http.common.HttpStatus; import nextstep.jwp.http.request.HttpRequest; import nextstep.jwp.http.response.HttpResponse; -import nextstep.jwp.model.StaticResource; -import nextstep.jwp.service.StaticResourceService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public abstract class RestController implements Controller { - private static final Logger LOGGER = LoggerFactory.getLogger(RestController.class); - - private final StaticResourceService staticResourceService; - - public RestController(StaticResourceService staticResourceService) { - this.staticResourceService = staticResourceService; - } - - protected abstract HttpResponse doPost(HttpRequest httpRequest); - - private HttpResponse doGet(HttpRequest httpRequest) throws IOException { - try { - StaticResource staticResource = staticResourceService.findByPathWithExtension( - httpRequest.getUri(), ".html"); - - return HttpResponse.withBody(HttpStatus.OK, staticResource); - } catch (StaticResourceNotFoundException e) { - StaticResource staticResource = staticResourceService.findByPath("/404.html"); - - LOGGER.warn(e.getMessage()); - - return HttpResponse.withBody(HttpStatus.NOT_FOUND, staticResource); - } - } - - public HttpResponse doService(HttpRequest httpRequest) throws IOException { + public HttpResponse service(HttpRequest httpRequest) throws IOException { if (httpRequest.hasMethod(GET)) { return doGet(httpRequest); } return doPost(httpRequest); } + + protected abstract HttpResponse doGet(HttpRequest httpRequest) throws IOException; + + protected abstract HttpResponse doPost(HttpRequest httpRequest) throws IOException; } diff --git a/app/src/main/java/nextstep/jwp/controller/StaticResourceController.java b/app/src/main/java/nextstep/jwp/controller/StaticResourceController.java index 61974d43fe..992bb8d4a5 100644 --- a/app/src/main/java/nextstep/jwp/controller/StaticResourceController.java +++ b/app/src/main/java/nextstep/jwp/controller/StaticResourceController.java @@ -1,5 +1,7 @@ package nextstep.jwp.controller; +import static nextstep.jwp.controller.StaticResourcePath.NOT_FOUND_PAGE; + import java.io.IOException; import nextstep.jwp.exception.StaticResourceNotFoundException; import nextstep.jwp.http.common.HttpStatus; @@ -26,7 +28,7 @@ private HttpResponse doGet(HttpRequest httpRequest) throws IOException { return HttpResponse.withBody(HttpStatus.OK, staticResource); } catch (StaticResourceNotFoundException e) { - StaticResource staticResource = staticResourceService.findByPath("/404.html"); + StaticResource staticResource = staticResourceService.findByPath(NOT_FOUND_PAGE.getValue()); LOGGER.warn(e.getMessage()); @@ -35,12 +37,7 @@ private HttpResponse doGet(HttpRequest httpRequest) throws IOException { } @Override - public HttpResponse doService(HttpRequest httpRequest) throws IOException { + public HttpResponse service(HttpRequest httpRequest) throws IOException { return doGet(httpRequest); } - - @Override - public boolean matchUri(String uri) { - return false; - } } diff --git a/app/src/main/java/nextstep/jwp/controller/StaticResourcePath.java b/app/src/main/java/nextstep/jwp/controller/StaticResourcePath.java new file mode 100644 index 0000000000..05563e7312 --- /dev/null +++ b/app/src/main/java/nextstep/jwp/controller/StaticResourcePath.java @@ -0,0 +1,19 @@ +package nextstep.jwp.controller; + +public enum StaticResourcePath { + + INDEX_PAGE("/index.html"), + UNAUTHORIZED_PAGE("/401.html"), + NOT_FOUND_PAGE("/404.html"), + CONFLICT_PAGE("/409.html"); + + private final String value; + + StaticResourcePath(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/app/src/main/java/nextstep/jwp/controller/response/LoginResponse.java b/app/src/main/java/nextstep/jwp/controller/response/LoginResponse.java new file mode 100644 index 0000000000..afc93617f8 --- /dev/null +++ b/app/src/main/java/nextstep/jwp/controller/response/LoginResponse.java @@ -0,0 +1,16 @@ +package nextstep.jwp.controller.response; + +public class LoginResponse { + + private final String sessionKey; + private final String sessionValue; + + public LoginResponse(String sessionKey, String sessionValue) { + this.sessionKey = sessionKey; + this.sessionValue = sessionValue; + } + + public String toCookieString() { + return String.format("%s=%s;", sessionKey, sessionValue); + } +} diff --git a/app/src/main/java/nextstep/jwp/db/InMemoryUserRepository.java b/app/src/main/java/nextstep/jwp/db/InMemoryUserRepository.java index c6aa8b4307..95ce56d633 100644 --- a/app/src/main/java/nextstep/jwp/db/InMemoryUserRepository.java +++ b/app/src/main/java/nextstep/jwp/db/InMemoryUserRepository.java @@ -1,25 +1,30 @@ package nextstep.jwp.db; -import nextstep.jwp.model.User; - import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; +import nextstep.jwp.exception.DuplicateAccountException; +import nextstep.jwp.model.User; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class InMemoryUserRepository { + private static final Logger LOGGER = LoggerFactory.getLogger(InMemoryUserRepository.class); + private final Map database; - private Long autoIncrementId; + private final AtomicLong autoIncrementId; - public InMemoryUserRepository(Map database, Long autoIncrementId) { + public InMemoryUserRepository(Map database, AtomicLong autoIncrementId) { this.database = database; this.autoIncrementId = autoIncrementId; } public static InMemoryUserRepository initialize() { - Long id = 1L; - User user = new User(id++, "gugu", "password", "hkkang@woowahan.com"); + AtomicLong id = new AtomicLong(1); + User user = new User(id.getAndIncrement(), "gugu", "password", "hkkang@woowahan.com"); Map database = new ConcurrentHashMap<>(); database.put(user.getAccount(), user); @@ -28,7 +33,12 @@ public static InMemoryUserRepository initialize() { } public void save(User user) { - User newUser = User.withId(autoIncrementId++, user); + if (database.containsKey(user.getAccount())) { + LOGGER.debug("Duplicate account already exist => {}", user.getAccount()); + throw new DuplicateAccountException(); + } + + User newUser = User.withId(autoIncrementId.getAndIncrement(), user); database.put(newUser.getAccount(), newUser); } diff --git a/app/src/main/java/nextstep/jwp/exception/Exception.java b/app/src/main/java/nextstep/jwp/exception/Exception.java index 7efed2246e..7313ba848e 100644 --- a/app/src/main/java/nextstep/jwp/exception/Exception.java +++ b/app/src/main/java/nextstep/jwp/exception/Exception.java @@ -17,6 +17,8 @@ public enum Exception { UNAUTHORIZED(401, "Unauthorized.", UnauthorizedException.class), QUERY_PARAMETER_NOT_FOUND(404, "Query Parameter Not Found.", QueryParameterNotFoundException.class), STATIC_RESOURCE_NOT_FOUND(404, "Static Resource Not Found.", StaticResourceNotFoundException.class), + SESSION_NOT_FOUND(404, "Session Not Found.", SessionNotFoundException.class), + SESSION_ATTRIBUTE_NOT_FOUND(404, "Session Attribute Not Found.", SessionAttributeNotFoundException.class), DUPLICATE_ACCOUNT(409, "Duplicate Account.", DuplicateAccountException.class), NO_RESPONSE_BODY(500, "No Response Body.", NoResponseBodyException.class), diff --git a/app/src/main/java/nextstep/jwp/exception/SessionAttributeNotFoundException.java b/app/src/main/java/nextstep/jwp/exception/SessionAttributeNotFoundException.java new file mode 100644 index 0000000000..8464ff857a --- /dev/null +++ b/app/src/main/java/nextstep/jwp/exception/SessionAttributeNotFoundException.java @@ -0,0 +1,5 @@ +package nextstep.jwp.exception; + +public class SessionAttributeNotFoundException extends RuntimeException { + +} diff --git a/app/src/main/java/nextstep/jwp/exception/SessionNotFoundException.java b/app/src/main/java/nextstep/jwp/exception/SessionNotFoundException.java new file mode 100644 index 0000000000..6b0a1d8362 --- /dev/null +++ b/app/src/main/java/nextstep/jwp/exception/SessionNotFoundException.java @@ -0,0 +1,5 @@ +package nextstep.jwp.exception; + +public class SessionNotFoundException extends RuntimeException { + +} diff --git a/app/src/main/java/nextstep/jwp/http/common/HttpCookie.java b/app/src/main/java/nextstep/jwp/http/common/HttpCookie.java new file mode 100644 index 0000000000..06f5894b79 --- /dev/null +++ b/app/src/main/java/nextstep/jwp/http/common/HttpCookie.java @@ -0,0 +1,55 @@ +package nextstep.jwp.http.common; + +import java.util.HashMap; +import java.util.Map; +import nextstep.jwp.exception.InvalidRequestHeader; +import nextstep.jwp.exception.QueryParameterNotFoundException; + +public class HttpCookie { + + private static final int KEY_INDEX = 0; + private static final int VALUE_INDEX = 1; + private static final int EXPECT_LINE_LENGTH = 2; + + private final Map cookies; + + private HttpCookie(Map cookies) { + this.cookies = cookies; + } + + public static HttpCookie parse(String requestCookie) { + String[] splitedCookies = requestCookie.split(";"); + + Map cookies = new HashMap<>(); + for (String cookie : splitedCookies) { + String[] splitedCookie = splitCookie(cookie); + + String cookieKey = splitedCookie[KEY_INDEX]; + String cookieValue = splitedCookie[VALUE_INDEX]; + + cookies.put(cookieKey, cookieValue); + } + + return new HttpCookie(cookies); + } + + private static String[] splitCookie(String cookie) { + String[] splitedCookie = cookie.trim() + .toLowerCase() + .split("="); + + if (splitedCookie.length != EXPECT_LINE_LENGTH) { + throw new InvalidRequestHeader(); + } + + return splitedCookie; + } + + public String getParameter(String parameter) { + if (cookies.containsKey(parameter.toLowerCase())) { + return cookies.get(parameter.toLowerCase()); + } + + throw new QueryParameterNotFoundException(); + } +} diff --git a/app/src/main/java/nextstep/jwp/http/common/HttpHeader.java b/app/src/main/java/nextstep/jwp/http/common/HttpHeader.java new file mode 100644 index 0000000000..9c4d2f0139 --- /dev/null +++ b/app/src/main/java/nextstep/jwp/http/common/HttpHeader.java @@ -0,0 +1,25 @@ +package nextstep.jwp.http.common; + +public enum HttpHeader { + + COOKIE("Cookie"), + CONTENT_TYPE("Content-Type"), + CONTENT_LENGTH("Content-Length"), + LOCATION("Location"), + SET_COOKIE("Set-Cookie"), + TRANSFER_ENCODING("Transfer-Encoding"); + + private final String value; + + HttpHeader(String value) { + this.value = value; + } + + public String toLowerString() { + return value.toLowerCase(); + } + + public String toRawString() { + return value; + } +} diff --git a/app/src/main/java/nextstep/jwp/http/common/HttpVersion.java b/app/src/main/java/nextstep/jwp/http/common/HttpVersion.java index 0527a30b88..bc3ad4d8d1 100644 --- a/app/src/main/java/nextstep/jwp/http/common/HttpVersion.java +++ b/app/src/main/java/nextstep/jwp/http/common/HttpVersion.java @@ -1,6 +1,5 @@ package nextstep.jwp.http.common; -import java.util.Arrays; import java.util.HashMap; import java.util.Map; import nextstep.jwp.exception.NotAllowedHttpVersionException; diff --git a/app/src/main/java/nextstep/jwp/http/request/HttpRequest.java b/app/src/main/java/nextstep/jwp/http/request/HttpRequest.java index e1f867f927..17cf3b6c66 100644 --- a/app/src/main/java/nextstep/jwp/http/request/HttpRequest.java +++ b/app/src/main/java/nextstep/jwp/http/request/HttpRequest.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import nextstep.jwp.http.common.HttpCookie; public class HttpRequest { @@ -14,7 +15,8 @@ public class HttpRequest { private final RequestHeaders requestHeaders; private final RequestBody requestBody; - private HttpRequest(RequestLine requestLine, RequestHeaders requestHeaders, RequestBody requestBody) { + private HttpRequest(RequestLine requestLine, RequestHeaders requestHeaders, + RequestBody requestBody) { this.requestLine = requestLine; this.requestHeaders = requestHeaders; this.requestBody = requestBody; @@ -30,7 +32,8 @@ public static HttpRequest parse(InputStream inputStream) throws IOException { return new HttpRequest(requestLine, requestHeaders, requestBody); } - private static RequestBody extractBody(BufferedReader bufferedReader, RequestHeaders requestHeaders) throws IOException { + private static RequestBody extractBody(BufferedReader bufferedReader, + RequestHeaders requestHeaders) throws IOException { if (requestHeaders.requestHasBody()) { int contentLength = requestHeaders.getContentLength(); char[] buffer = new char[contentLength]; @@ -50,6 +53,10 @@ public boolean hasQueryParam() { return requestLine.hasQueryParam(); } + public boolean hasCookie() { + return requestHeaders.hasCookie(); + } + public String getUri() { return requestLine.getUri(); } @@ -58,6 +65,10 @@ public String getUriParameter(String parameter) { return requestLine.getUriParameter(parameter); } + public HttpCookie getCookie() { + return requestHeaders.getCookie(); + } + public String getBodyParameter(String parameter) { return requestBody.getParameter(parameter); } diff --git a/app/src/main/java/nextstep/jwp/http/request/RequestHeaders.java b/app/src/main/java/nextstep/jwp/http/request/RequestHeaders.java index 8bdec80af2..3614222fcd 100644 --- a/app/src/main/java/nextstep/jwp/http/request/RequestHeaders.java +++ b/app/src/main/java/nextstep/jwp/http/request/RequestHeaders.java @@ -1,5 +1,9 @@ package nextstep.jwp.http.request; +import static nextstep.jwp.http.common.HttpHeader.CONTENT_LENGTH; +import static nextstep.jwp.http.common.HttpHeader.COOKIE; +import static nextstep.jwp.http.common.HttpHeader.TRANSFER_ENCODING; + import java.io.BufferedReader; import java.io.IOException; import java.util.HashMap; @@ -8,14 +12,14 @@ import java.util.StringJoiner; import nextstep.jwp.exception.HttpRequestNotHaveBodyException; import nextstep.jwp.exception.InvalidRequestHeader; +import nextstep.jwp.exception.QueryParameterNotFoundException; +import nextstep.jwp.http.common.HttpCookie; public class RequestHeaders { private static final int KEY_INDEX = 0; private static final int VALUE_INDEX = 1; private static final int EXPECT_LINE_LENGTH = 2; - private static final String CONTENT_LENGTH = "content-length"; - private static final String TRANSFER_ENCODING = "transfer-encoding"; private static final String NEW_LINE = System.getProperty("line.separator"); private final Map headers; @@ -55,20 +59,30 @@ private static String[] splitLine(String line) { return splitedLine; } + public boolean hasCookie() { + return headers.containsKey(COOKIE.toLowerString()); + } + public boolean requestHasBody() { - return headers.containsKey(CONTENT_LENGTH) || headers.containsKey(TRANSFER_ENCODING); + return headers.containsKey(CONTENT_LENGTH.toLowerString()) + || headers.containsKey(TRANSFER_ENCODING.toLowerString()); } - private boolean requestNotHaveBody() { - return !headers.containsKey(CONTENT_LENGTH) || headers.containsKey(TRANSFER_ENCODING); + public HttpCookie getCookie() { + if (hasCookie()) { + String cookieHeader = headers.get(COOKIE.toLowerString()); + return HttpCookie.parse(cookieHeader); + } + + throw new QueryParameterNotFoundException(); } public int getContentLength() { - if (requestNotHaveBody()) { - throw new HttpRequestNotHaveBodyException(); + if (requestHasBody()) { + return Integer.parseInt(headers.get(CONTENT_LENGTH.toLowerString())); } - return Integer.parseInt(headers.get(CONTENT_LENGTH)); + throw new HttpRequestNotHaveBodyException(); } @Override diff --git a/app/src/main/java/nextstep/jwp/http/response/HttpResponse.java b/app/src/main/java/nextstep/jwp/http/response/HttpResponse.java index 9f0fdc7134..6a736ea509 100644 --- a/app/src/main/java/nextstep/jwp/http/response/HttpResponse.java +++ b/app/src/main/java/nextstep/jwp/http/response/HttpResponse.java @@ -13,7 +13,8 @@ public class HttpResponse { private final ResponseHeaders responseHeaders; private final ResponseBody responseBody; - public HttpResponse(StatusLine statusLine, ResponseHeaders responseHeaders, ResponseBody responseBody) { + public HttpResponse(StatusLine statusLine, ResponseHeaders responseHeaders, + ResponseBody responseBody) { this.statusLine = statusLine; this.responseHeaders = responseHeaders; this.responseBody = responseBody; @@ -27,6 +28,14 @@ public static HttpResponse withBody(HttpStatus httpStatus, StaticResource static return new HttpResponse(statusLine, responseHeaders, responseBody); } + public static HttpResponse withBodyAndCookie(HttpStatus httpStatus, StaticResource staticResource, String cookie) { + StatusLine statusLine = StatusLine.from(httpStatus); + ResponseHeaders responseHeaders = ResponseHeaders.ofBodyAndSetCookie(staticResource, cookie); + ResponseBody responseBody = new ResponseBody(staticResource.getContent()); + + return new HttpResponse(statusLine, responseHeaders, responseBody); + } + public static HttpResponse redirect(HttpStatus httpStatus, String location) { StatusLine statusLine = StatusLine.from(httpStatus); ResponseHeaders responseHeaders = ResponseHeaders.ofRedirect(location); @@ -39,6 +48,14 @@ public byte[] toBytes() { return toString().getBytes(StandardCharsets.UTF_8); } + public String getStatusLine() { + return statusLine.toString(); + } + + public String getResponseHeaders() { + return responseHeaders.toString(); + } + @Override public String toString() { if (responseBody.isEmpty()) { diff --git a/app/src/main/java/nextstep/jwp/http/response/ResponseHeaders.java b/app/src/main/java/nextstep/jwp/http/response/ResponseHeaders.java index 8fa0f394a5..4e0c07caa7 100644 --- a/app/src/main/java/nextstep/jwp/http/response/ResponseHeaders.java +++ b/app/src/main/java/nextstep/jwp/http/response/ResponseHeaders.java @@ -1,5 +1,10 @@ package nextstep.jwp.http.response; +import static nextstep.jwp.http.common.HttpHeader.CONTENT_LENGTH; +import static nextstep.jwp.http.common.HttpHeader.CONTENT_TYPE; +import static nextstep.jwp.http.common.HttpHeader.LOCATION; +import static nextstep.jwp.http.common.HttpHeader.SET_COOKIE; + import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; @@ -19,8 +24,18 @@ private ResponseHeaders(Map headers) { public static ResponseHeaders ofBody(StaticResource staticResource) { Map headers = new HashMap<>(); - headers.put("Content-Type", staticResource.getContentType()); - headers.put("Content-Length", staticResource.getContentLength()); + headers.put(CONTENT_TYPE.toRawString(), staticResource.getContentType()); + headers.put(CONTENT_LENGTH.toRawString(), staticResource.getContentLength()); + + return new ResponseHeaders(headers); + } + + public static ResponseHeaders ofBodyAndSetCookie(StaticResource staticResource, String cookie) { + Map headers = new HashMap<>(); + + headers.put(CONTENT_TYPE.toRawString(), staticResource.getContentType()); + headers.put(CONTENT_LENGTH.toRawString(), staticResource.getContentLength()); + headers.put(SET_COOKIE.toRawString(), cookie); return new ResponseHeaders(headers); } @@ -28,7 +43,7 @@ public static ResponseHeaders ofBody(StaticResource staticResource) { public static ResponseHeaders ofRedirect(String location) { Map headers = new HashMap<>(); - headers.put("Location", location); + headers.put(LOCATION.toRawString(), location); return new ResponseHeaders(headers); } diff --git a/app/src/main/java/nextstep/jwp/model/ContentType.java b/app/src/main/java/nextstep/jwp/model/ContentType.java index ce698a23c1..a3504bf51e 100644 --- a/app/src/main/java/nextstep/jwp/model/ContentType.java +++ b/app/src/main/java/nextstep/jwp/model/ContentType.java @@ -1,10 +1,8 @@ package nextstep.jwp.model; -import java.util.Arrays; import java.util.HashMap; import java.util.Map; import nextstep.jwp.exception.InvalidFileExtensionException; -import org.checkerframework.checker.units.qual.C; public enum ContentType { HTML("html", "text/html; charset=UTF-8"), diff --git a/app/src/main/java/nextstep/jwp/model/User.java b/app/src/main/java/nextstep/jwp/model/User.java index 7f55556856..d30e2ec2a4 100644 --- a/app/src/main/java/nextstep/jwp/model/User.java +++ b/app/src/main/java/nextstep/jwp/model/User.java @@ -31,22 +31,22 @@ public void checkPassword(String password) { } } - public String getAccount() { - return account; - } - public Long getId() { return id; } + public String getAccount() { + return account; + } + @Override public String toString() { return "User{" + - "id=" + id + - ", account='" + account + '\'' + - ", email='" + email + '\'' + - ", password='" + password + '\'' + - '}'; + "id=" + id + + ", account='" + account + '\'' + + ", email='" + email + '\'' + + ", password='" + password + '\'' + + '}'; } @Override diff --git a/app/src/main/java/nextstep/jwp/server/HttpSession.java b/app/src/main/java/nextstep/jwp/server/HttpSession.java new file mode 100644 index 0000000000..ac854b85d7 --- /dev/null +++ b/app/src/main/java/nextstep/jwp/server/HttpSession.java @@ -0,0 +1,36 @@ +package nextstep.jwp.server; + +import java.util.HashMap; +import java.util.Map; +import nextstep.jwp.exception.SessionAttributeNotFoundException; + +public class HttpSession { + + private final String id; + private final Map values; + + public HttpSession(String id) { + this.id = id; + this.values = new HashMap<>(); + } + + public String getId() { + return id; + } + + public boolean hasAttribute(String name) { + return values.containsKey(name); + } + + public void setAttribute(String name, Object value) { + values.put(name, value); + } + + public Object getAttribute(String name) { + if (values.containsKey(name)) { + return values.get(name); + } + + throw new SessionAttributeNotFoundException(); + } +} diff --git a/app/src/main/java/nextstep/jwp/server/HttpSessions.java b/app/src/main/java/nextstep/jwp/server/HttpSessions.java new file mode 100644 index 0000000000..66cae961f8 --- /dev/null +++ b/app/src/main/java/nextstep/jwp/server/HttpSessions.java @@ -0,0 +1,41 @@ +package nextstep.jwp.server; + +import java.util.HashMap; +import java.util.Map; +import nextstep.jwp.exception.SessionNotFoundException; + +public class HttpSessions { + + private final Map sessions; + + + public HttpSessions() { + this(new HashMap<>()); + } + + public HttpSessions(Map sessions) { + this.sessions = sessions; + } + + public void addSession(HttpSession httpSession) { + sessions.put(httpSession.getId(), httpSession); + } + + public boolean hasObject(String sessionId, String attributeName) { + if (sessions.containsKey(sessionId)) { + HttpSession httpSession = sessions.get(sessionId); + return httpSession.hasAttribute(attributeName); + } + + return false; + } + + public Object findObject(String sessionId, String attributeName) { + if (sessions.containsKey(sessionId)) { + HttpSession httpSession = sessions.get(sessionId); + return httpSession.getAttribute(attributeName); + } + + throw new SessionNotFoundException(); + } +} diff --git a/app/src/main/java/nextstep/jwp/server/RequestHandler.java b/app/src/main/java/nextstep/jwp/server/RequestHandler.java index 107d6c5c40..603952487b 100644 --- a/app/src/main/java/nextstep/jwp/server/RequestHandler.java +++ b/app/src/main/java/nextstep/jwp/server/RequestHandler.java @@ -1,42 +1,43 @@ package nextstep.jwp.server; import java.io.BufferedOutputStream; -import nextstep.jwp.controller.Controllers; -import nextstep.jwp.exception.InvalidHttpRequestException; -import nextstep.jwp.http.request.HttpRequest; -import nextstep.jwp.http.response.HttpResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.util.Objects; +import nextstep.jwp.exception.InvalidHttpRequestException; +import nextstep.jwp.http.request.HttpRequest; +import nextstep.jwp.http.response.HttpResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class RequestHandler implements Runnable { private static final Logger LOGGER = LoggerFactory.getLogger(RequestHandler.class); + private static final String REQUEST_LOG_FORMAT = "Parsed HTTP Request!!!\n\n{}\n\n"; + private static final String RESPONSE_LOG_FORMAT = "Return HTTP Response!!!\n\n{}\n{}\n\n"; private final Socket connection; - private final Controllers controllers; + private final RequestMapping requestMapping; - public RequestHandler(Socket connection, Controllers controllers) { + public RequestHandler(Socket connection, RequestMapping requestMapping) { this.connection = Objects.requireNonNull(connection); - this.controllers = controllers; + this.requestMapping = requestMapping; } @Override public void run() { - LOGGER.debug("New Client Connect! Connected IP : {}, Port : {}", connection.getInetAddress(), connection.getPort()); + LOGGER.debug("New Client Connect! Connected IP : {}, Port : {}", + connection.getInetAddress(), connection.getPort()); try (final InputStream inputStream = connection.getInputStream(); - final OutputStream outputStream = connection.getOutputStream()) { + final OutputStream outputStream = connection.getOutputStream()) { HttpRequest httpRequest = HttpRequest.parse(inputStream); - LOGGER.info("Parsed HTTP Request!!!\n\n{}\n\n", httpRequest); - HttpResponse httpResponse = controllers.doService(httpRequest); - LOGGER.info("Return HTTP Response!!!\n\n{}\n\n", httpResponse); + LOGGER.info(REQUEST_LOG_FORMAT, httpRequest); + HttpResponse httpResponse = requestMapping.doService(httpRequest); + LOGGER.info(RESPONSE_LOG_FORMAT, httpResponse.getStatusLine(), httpResponse.getResponseHeaders()); flushBytes(outputStream, httpResponse); } catch (IOException | InvalidHttpRequestException exception) { diff --git a/app/src/main/java/nextstep/jwp/server/RequestMapping.java b/app/src/main/java/nextstep/jwp/server/RequestMapping.java new file mode 100644 index 0000000000..6c4c264ae3 --- /dev/null +++ b/app/src/main/java/nextstep/jwp/server/RequestMapping.java @@ -0,0 +1,63 @@ +package nextstep.jwp.server; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import nextstep.jwp.controller.Controller; +import nextstep.jwp.controller.LoginController; +import nextstep.jwp.controller.RegisterController; +import nextstep.jwp.controller.StaticResourceController; +import nextstep.jwp.db.InMemoryUserRepository; +import nextstep.jwp.http.request.HttpRequest; +import nextstep.jwp.http.response.HttpResponse; +import nextstep.jwp.service.LoginService; +import nextstep.jwp.service.RegisterService; +import nextstep.jwp.service.StaticResourceService; + +public class RequestMapping { + + private static final int ROOT_PATH_INDEX = 1; + + private final Map restControllers; + private final Controller staticResourceController; + + public RequestMapping(Map restControllers, Controller staticResourceController) { + this.restControllers = restControllers; + this.staticResourceController = staticResourceController; + } + + public static RequestMapping loadContext() { + InMemoryUserRepository userRepository = InMemoryUserRepository.initialize(); + HttpSessions httpSessions = new HttpSessions(); + LoginService loginService = new LoginService(userRepository, httpSessions); + RegisterService registerService = new RegisterService(userRepository); + StaticResourceService staticResourceService = new StaticResourceService(); + + Map restControllers = new HashMap<>(); + Controller loginController = new LoginController(loginService, staticResourceService); + restControllers.put("login", loginController); + Controller registerController = new RegisterController(registerService, staticResourceService); + restControllers.put("register", registerController); + + Controller staticResourceController = new StaticResourceController(staticResourceService); + + return new RequestMapping(restControllers, staticResourceController); + } + + public HttpResponse doService(HttpRequest httpRequest) throws IOException { + String requestUri = httpRequest.getUri(); + String rootUri = requestUri.split("/")[ROOT_PATH_INDEX]; + + Controller controller = findControllerByUri(rootUri); + + return controller.service(httpRequest); + } + + private Controller findControllerByUri(String rootUri) { + if (restControllers.containsKey(rootUri)) { + return restControllers.get(rootUri); + } + + return staticResourceController; + } +} diff --git a/app/src/main/java/nextstep/jwp/server/WebServer.java b/app/src/main/java/nextstep/jwp/server/WebServer.java index d785289752..c17c260091 100644 --- a/app/src/main/java/nextstep/jwp/server/WebServer.java +++ b/app/src/main/java/nextstep/jwp/server/WebServer.java @@ -1,13 +1,11 @@ package nextstep.jwp.server; -import nextstep.jwp.controller.Controllers; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.stream.Stream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class WebServer { @@ -15,11 +13,11 @@ public class WebServer { private static final int DEFAULT_PORT = 8080; private final int port; - private final Controllers controllers; + private final RequestMapping requestMapping; public WebServer(int port) { this.port = checkPort(port); - this.controllers = Controllers.loadContext(); + this.requestMapping = RequestMapping.loadContext(); } public void run() { @@ -36,15 +34,15 @@ public void run() { private void handle(ServerSocket serverSocket) throws IOException { Socket connection; while ((connection = serverSocket.accept()) != null) { - new Thread(new RequestHandler(connection, controllers)).start(); + new Thread(new RequestHandler(connection, requestMapping)).start(); } } public static int defaultPortIfNull(String[] args) { return Stream.of(args) - .findFirst() - .map(Integer::parseInt) - .orElse(WebServer.DEFAULT_PORT); + .findFirst() + .map(Integer::parseInt) + .orElse(WebServer.DEFAULT_PORT); } private int checkPort(int port) { diff --git a/app/src/main/java/nextstep/jwp/service/LoginService.java b/app/src/main/java/nextstep/jwp/service/LoginService.java index 7aef2c9e0c..8d7dd0ccb5 100644 --- a/app/src/main/java/nextstep/jwp/service/LoginService.java +++ b/app/src/main/java/nextstep/jwp/service/LoginService.java @@ -1,25 +1,47 @@ package nextstep.jwp.service; +import java.util.UUID; import nextstep.jwp.controller.request.LoginRequest; +import nextstep.jwp.controller.response.LoginResponse; import nextstep.jwp.db.InMemoryUserRepository; import nextstep.jwp.exception.UnauthorizedException; +import nextstep.jwp.http.common.HttpCookie; import nextstep.jwp.model.User; +import nextstep.jwp.server.HttpSession; +import nextstep.jwp.server.HttpSessions; public class LoginService { + private static final String SESSION_PARAMETER = "JSESSIONID"; + private final InMemoryUserRepository userRepository; + private final HttpSessions httpSessions; - public LoginService(InMemoryUserRepository userRepository) { + public LoginService(InMemoryUserRepository userRepository, HttpSessions httpSessions) { this.userRepository = userRepository; + this.httpSessions = httpSessions; } - public void login(LoginRequest loginRequest) { - User user = findByUserAccount(loginRequest.getAccount()); + public LoginResponse login(LoginRequest loginRequest) { + User user = findUserByAccount(loginRequest.getAccount()); user.checkPassword(loginRequest.getPassword()); + + UUID uuid = UUID.randomUUID(); + HttpSession httpSession = new HttpSession(uuid.toString()); + httpSession.setAttribute("user", user); + httpSessions.addSession(httpSession); + + return new LoginResponse(SESSION_PARAMETER, httpSession.getId()); } - private User findByUserAccount(String account) { + private User findUserByAccount(String account) { return userRepository.findByAccount(account) .orElseThrow(UnauthorizedException::new); } + + public boolean isAlreadyLogin(HttpCookie httpCookie) { + String sessionId = httpCookie.getParameter(SESSION_PARAMETER); + + return httpSessions.hasObject(sessionId, "user"); + } } diff --git a/app/src/main/java/nextstep/jwp/service/RegisterService.java b/app/src/main/java/nextstep/jwp/service/RegisterService.java index 9888622fc8..fed1243311 100644 --- a/app/src/main/java/nextstep/jwp/service/RegisterService.java +++ b/app/src/main/java/nextstep/jwp/service/RegisterService.java @@ -2,7 +2,6 @@ import nextstep.jwp.controller.request.RegisterRequest; import nextstep.jwp.db.InMemoryUserRepository; -import nextstep.jwp.exception.DuplicateAccountException; import nextstep.jwp.model.User; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -19,15 +18,8 @@ public RegisterService(InMemoryUserRepository userRepository) { public void registerUser(RegisterRequest registerRequest) { User user = registerRequest.toEntity(); - validateDuplicateAccount(user); - userRepository.save(user); - } - private void validateDuplicateAccount(User user) { - if (userRepository.findByAccount(user.getAccount()).isPresent()) { - LOGGER.debug("Duplicate account already exist => {}", user.getAccount()); - throw new DuplicateAccountException(); - } + LOGGER.debug("{} Account User Successful Register.", user.getAccount()); } } diff --git a/app/src/test/java/nextstep/jwp/controller/LoginControllerTest.java b/app/src/test/java/nextstep/jwp/controller/LoginControllerTest.java index c06b720872..84464bb116 100644 --- a/app/src/test/java/nextstep/jwp/controller/LoginControllerTest.java +++ b/app/src/test/java/nextstep/jwp/controller/LoginControllerTest.java @@ -7,45 +7,41 @@ import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.HashMap; +import java.util.concurrent.atomic.AtomicLong; import nextstep.jwp.db.InMemoryUserRepository; import nextstep.jwp.http.request.HttpRequest; import nextstep.jwp.http.response.HttpResponse; import nextstep.jwp.model.User; +import nextstep.jwp.server.HttpSessions; import nextstep.jwp.service.LoginService; import nextstep.jwp.service.StaticResourceService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -import org.junit.jupiter.params.provider.ValueSource; class LoginControllerTest { private static final String NEW_LINE = System.getProperty("line.separator"); + private static final String ACCOUNT = "account"; + private static final String PASSWORD = "password"; private LoginController loginController; private InMemoryUserRepository userRepository; + private HttpRequest httpRequest; @BeforeEach void setUp() { HashMap database = new HashMap<>(); - userRepository = new InMemoryUserRepository(database, 1L); + userRepository = new InMemoryUserRepository(database, new AtomicLong(1)); - LoginService loginService = new LoginService(userRepository); + HttpSessions httpSessions = new HttpSessions(); + LoginService loginService = new LoginService(userRepository, httpSessions); StaticResourceService staticResourceService = new StaticResourceService(); loginController = new LoginController(loginService, staticResourceService); } - @DisplayName("'/login'으로 시작되는 Uri와 매칭된다.") - @ParameterizedTest - @ValueSource(strings = {"/login", "/login/now", "/login?abc=def"}) - void matchUri(String uri) { - assertThat(loginController.matchUri(uri)).isTrue(); - } - @DisplayName("HttpRequest Method에 따라 Service 분기") @Nested class Service { @@ -54,8 +50,6 @@ class Service { @Nested class Get { - private HttpRequest httpRequest; - @DisplayName("요청한 파일이 존재하면 관련 response를 반환 받는다.") @Test void findLoginHtmlSuccess() throws IOException { @@ -73,17 +67,84 @@ void findLoginHtmlSuccess() throws IOException { + "login is good"; // when - HttpResponse httpResponse = loginController.doService(httpRequest); + HttpResponse httpResponse = loginController.service(httpRequest); // then - assertThat(httpResponse.toBytes()).isEqualTo(expectString.getBytes(StandardCharsets.UTF_8)); + assertThat(httpResponse.toBytes()).isEqualTo( + expectString.getBytes(StandardCharsets.UTF_8)); + } + + @DisplayName("이미 로그인 되어 있을 경우 '/index.html' redirect response를 반환 받는다.") + @Test + void alreadyLogin() throws IOException { + // given + userRepository.save(new User(ACCOUNT, PASSWORD, "email")); + + getLoginRequest(); + HttpResponse httpResponse = loginController.service(httpRequest); + getReLoginRequest(httpResponse); + + String expectStatusLine = "HTTP/1.1 302 Found \n" + + "Location: /index.html "; + + // when + HttpResponse response = loginController.doPost(httpRequest); + + // then + assertThat(response.toBytes()).isEqualTo( + expectStatusLine.getBytes(StandardCharsets.UTF_8)); + } + + private void getLoginRequest() throws IOException { + String body = String.format("account=%s&password=%s", ACCOUNT, PASSWORD); + int contentLength = body.getBytes(StandardCharsets.UTF_8).length; + + String requestString = String.join(NEW_LINE, + "POST /login HTTP/1.1", + String.format("Content-Length: %d", contentLength), + "", + body + ); + + try (InputStream inputStream = new ByteArrayInputStream(requestString.getBytes( + StandardCharsets.UTF_8))) { + httpRequest = HttpRequest.parse(inputStream); + } + } + + private void getReLoginRequest(HttpResponse httpResponse) throws IOException { + String cookie = getParsingCookie(httpResponse); + + String body = String.format("account=%s&password=%s", ACCOUNT, PASSWORD); + int contentLength = body.getBytes(StandardCharsets.UTF_8).length; + + String requestString = String.join(NEW_LINE, + "GET /login HTTP/1.1", + String.format("Cookie: %s", cookie), + String.format("Content-Length: %d", contentLength), + "", + body + ); + + try (InputStream inputStream = new ByteArrayInputStream(requestString.getBytes( + StandardCharsets.UTF_8))) { + httpRequest = HttpRequest.parse(inputStream); + } + } + + private String getParsingCookie(HttpResponse httpResponse) { + String CookieHeader = httpResponse.toString().split("\n")[1]; + String cookieValue = CookieHeader.split(":")[1]; + + return cookieValue.trim(); } @DisplayName("요청한 파일이 없으면 '404.html' response를 반환 받는다.") @Test void findLoginHtmlFail() throws IOException { // given - String requestString = String.join(NEW_LINE, "GET /login/something HTTP/1.1", "", ""); + String requestString = String.join(NEW_LINE, "GET /login/something HTTP/1.1", "", + ""); try (InputStream inputStream = new ByteArrayInputStream(requestString.getBytes( StandardCharsets.UTF_8))) { httpRequest = HttpRequest.parse(inputStream); @@ -96,10 +157,11 @@ void findLoginHtmlFail() throws IOException { + "NOT FOUND"; // when - HttpResponse httpResponse = loginController.doService(httpRequest); + HttpResponse httpResponse = loginController.service(httpRequest); // then - assertThat(httpResponse.toBytes()).isEqualTo(expectString.getBytes(StandardCharsets.UTF_8)); + assertThat(httpResponse.toBytes()).isEqualTo( + expectString.getBytes(StandardCharsets.UTF_8)); } } @@ -107,11 +169,6 @@ void findLoginHtmlFail() throws IOException { @Nested class Post { - private static final String ACCOUNT = "account"; - private static final String PASSWORD = "password"; - - private HttpRequest httpRequest; - @BeforeEach void setUp() throws IOException { String body = String.format("account=%s&password=%s", ACCOUNT, PASSWORD); @@ -130,34 +187,96 @@ void setUp() throws IOException { } } - @DisplayName("로그인 성공시 '/index.html' redirect response를 반환 받는다.") + @DisplayName("로그인 성공시 쿠키와 함께 '/index.html' response를 반환 받는다.") @Test void loginSuccess() throws IOException { // given userRepository.save(new User(ACCOUNT, PASSWORD, "email")); - String expectString = "HTTP/1.1 302 Found \n" + String expectStatusLine = "HTTP/1.1 200 OK "; + String expectCookie = "Set-Cookie:"; + String expectContentLength = "Content-Length: 11 "; + String expectContentType = "Content-Type: text/html; charset=UTF-8 "; + String expectEnterLine = ""; + String expectBody = "hihi hello!"; + + // when + HttpResponse httpResponse = loginController.service(httpRequest); + String response = new String(httpResponse.toBytes()); + String[] splitedResponse = response.split("\n"); + + // then + assertThat(splitedResponse[0]).isEqualTo(expectStatusLine); + assertThat(splitedResponse[1].startsWith(expectCookie)).isTrue(); + assertThat(splitedResponse[2]).isEqualTo(expectContentLength); + assertThat(splitedResponse[3]).isEqualTo(expectContentType); + assertThat(splitedResponse[4]).isEqualTo(expectEnterLine); + assertThat(splitedResponse[5]).isEqualTo(expectBody); + } + + @DisplayName("이미 로그인 되어 있을 경우 '/index.html' redirect response를 반환 받는다.") + @Test + void alreadyLogin() throws IOException { + // given + userRepository.save(new User(ACCOUNT, PASSWORD, "email")); + + HttpResponse httpResponse = loginController.service(httpRequest); + String cookie = getParsingCookie(httpResponse); + + String body = String.format("account=%s&password=%s", ACCOUNT, PASSWORD); + int contentLength = body.getBytes(StandardCharsets.UTF_8).length; + + String requestString = String.join(NEW_LINE, + "POST /login HTTP/1.1", + String.format("Cookie: %s", cookie), + String.format("Content-Length: %d", contentLength), + "", + body + ); + + try (InputStream inputStream = new ByteArrayInputStream(requestString.getBytes( + StandardCharsets.UTF_8))) { + httpRequest = HttpRequest.parse(inputStream); + } + + String expectStatusLine = "HTTP/1.1 302 Found \n" + "Location: /index.html "; // when - HttpResponse httpResponse = loginController.doService(httpRequest); + HttpResponse response = loginController.doPost(httpRequest); // then - assertThat(httpResponse.toBytes()).isEqualTo(expectString.getBytes(StandardCharsets.UTF_8)); + assertThat(response.toBytes()).isEqualTo( + expectStatusLine.getBytes(StandardCharsets.UTF_8)); + } + + private String getParsingCookie(HttpResponse httpResponse) { + String CookieHeader = httpResponse.toString().split("\n")[1]; + String cookieValue = CookieHeader.split(":")[1]; + + return cookieValue.trim(); } - @DisplayName("로그인 실패시 '/401.html' redirect response를 반환 받는다.") + @DisplayName("로그인 실패시 '/401.html' response를 반환 받는다.") @Test void loginFail() throws IOException { // given - String expectString = "HTTP/1.1 401 Unauthorized \n" - + "Location: /401.html "; + String expectStatusLine = "HTTP/1.1 401 Unauthorized "; + String expectContentLength = "Content-Length: 20 "; + String expectContentType = "Content-Type: text/html; charset=UTF-8 "; + String expectEnterLine = ""; + String expectBody = "401 is Unauthorized."; // when - HttpResponse httpResponse = loginController.doService(httpRequest); + HttpResponse httpResponse = loginController.service(httpRequest); + String[] splitedResponse = httpResponse.toString().split("\n"); // then - assertThat(httpResponse.toBytes()).isEqualTo(expectString.getBytes(StandardCharsets.UTF_8)); + assertThat(splitedResponse[0]).isEqualTo(expectStatusLine); + assertThat(splitedResponse[1]).isEqualTo(expectContentLength); + assertThat(splitedResponse[2]).isEqualTo(expectContentType); + assertThat(splitedResponse[3]).isEqualTo(expectEnterLine); + assertThat(splitedResponse[4]).isEqualTo(expectBody); } } } diff --git a/app/src/test/java/nextstep/jwp/controller/RegisterControllerTest.java b/app/src/test/java/nextstep/jwp/controller/RegisterControllerTest.java index d346c8e81e..be4af8de03 100644 --- a/app/src/test/java/nextstep/jwp/controller/RegisterControllerTest.java +++ b/app/src/test/java/nextstep/jwp/controller/RegisterControllerTest.java @@ -7,6 +7,7 @@ import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.HashMap; +import java.util.concurrent.atomic.AtomicLong; import nextstep.jwp.db.InMemoryUserRepository; import nextstep.jwp.http.request.HttpRequest; import nextstep.jwp.http.response.HttpResponse; @@ -17,8 +18,6 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; class RegisterControllerTest { @@ -30,7 +29,7 @@ class RegisterControllerTest { @BeforeEach void setUp() { HashMap database = new HashMap<>(); - userRepository = new InMemoryUserRepository(database, 1L); + userRepository = new InMemoryUserRepository(database, new AtomicLong(1)); RegisterService registerService = new RegisterService(userRepository); StaticResourceService staticResourceService = new StaticResourceService(); @@ -38,13 +37,6 @@ void setUp() { registerController = new RegisterController(registerService, staticResourceService); } - @DisplayName("'/register'으로 시작되는 Uri와 매칭된다.") - @ParameterizedTest - @ValueSource(strings = {"/register", "/register/wow", "/register?abc=def"}) - void matchUri(String uri) { - assertThat(registerController.matchUri(uri)).isTrue(); - } - @DisplayName("HttpRequest Method에 따라 Service 분기") @Nested class Service { @@ -72,10 +64,11 @@ void findRegisterHtmlSuccess() throws IOException { + "register is good"; // when - HttpResponse httpResponse = registerController.doService(httpRequest); + HttpResponse httpResponse = registerController.service(httpRequest); // then - assertThat(httpResponse.toBytes()).isEqualTo(expectString.getBytes(StandardCharsets.UTF_8)); + assertThat(httpResponse.toBytes()).isEqualTo( + expectString.getBytes(StandardCharsets.UTF_8)); } @DisplayName("요청한 파일이 없으면 '404.html' response를 반환 받는다.") @@ -96,10 +89,11 @@ void findRegisterHtmlFail() throws IOException { + "NOT FOUND"; // when - HttpResponse httpResponse = registerController.doService(httpRequest); + HttpResponse httpResponse = registerController.service(httpRequest); // then - assertThat(httpResponse.toBytes()).isEqualTo(expectString.getBytes(StandardCharsets.UTF_8)); + assertThat(httpResponse.toBytes()).isEqualTo( + expectString.getBytes(StandardCharsets.UTF_8)); } } @@ -140,26 +134,31 @@ void registerSuccess() throws IOException { + "Location: /index.html "; // when - HttpResponse httpResponse = registerController.doService(httpRequest); + HttpResponse httpResponse = registerController.service(httpRequest); // then - assertThat(httpResponse.toBytes()).isEqualTo(expectString.getBytes(StandardCharsets.UTF_8)); + assertThat(httpResponse.toBytes()).isEqualTo( + expectString.getBytes(StandardCharsets.UTF_8)); } - @DisplayName("회원가입 실패시 '/409.html' redirect response를 반환 받는다.") + @DisplayName("회원가입 실패시 '/409.html' response를 반환 받는다.") @Test void registerFail() throws IOException { // given userRepository.save(new User(ACCOUNT, PASSWORD, EMAIL)); String expectString = "HTTP/1.1 409 Conflict \n" - + "Location: /409.html "; + + "Content-Length: 16 \n" + + "Content-Type: text/html; charset=UTF-8 \n" + + "\n" + + "409 is Conflict."; // when - HttpResponse httpResponse = registerController.doService(httpRequest); + HttpResponse httpResponse = registerController.service(httpRequest); // then - assertThat(httpResponse.toBytes()).isEqualTo(expectString.getBytes(StandardCharsets.UTF_8)); + assertThat(httpResponse.toBytes()).isEqualTo( + expectString.getBytes(StandardCharsets.UTF_8)); } } } diff --git a/app/src/test/java/nextstep/jwp/controller/StaticResourceControllerTest.java b/app/src/test/java/nextstep/jwp/controller/StaticResourceControllerTest.java index 30a7e0f12d..10fb52e359 100644 --- a/app/src/test/java/nextstep/jwp/controller/StaticResourceControllerTest.java +++ b/app/src/test/java/nextstep/jwp/controller/StaticResourceControllerTest.java @@ -1,7 +1,6 @@ package nextstep.jwp.controller; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -46,7 +45,7 @@ void notFoundFile() throws IOException { + "NOT FOUND"; // when - HttpResponse httpResponse = staticResourceController.doService(httpRequest); + HttpResponse httpResponse = staticResourceController.service(httpRequest); // then assertThat(httpResponse.toBytes()).isEqualTo(expectString.getBytes(StandardCharsets.UTF_8)); @@ -73,10 +72,11 @@ void getMethod() throws IOException { + "static page is good!"; // when - HttpResponse httpResponse = staticResourceController.doService(httpRequest); + HttpResponse httpResponse = staticResourceController.service(httpRequest); // then - assertThat(httpResponse.toBytes()).isEqualTo(expectString.getBytes(StandardCharsets.UTF_8)); + assertThat(httpResponse.toBytes()).isEqualTo( + expectString.getBytes(StandardCharsets.UTF_8)); } @DisplayName("POST 요청") @@ -96,10 +96,11 @@ void postMethod() throws IOException { + "static page is good!"; // when - HttpResponse httpResponse = staticResourceController.doService(httpRequest); + HttpResponse httpResponse = staticResourceController.service(httpRequest); // then - assertThat(httpResponse.toBytes()).isEqualTo(expectString.getBytes(StandardCharsets.UTF_8)); + assertThat(httpResponse.toBytes()).isEqualTo( + expectString.getBytes(StandardCharsets.UTF_8)); } } } diff --git a/app/src/test/java/nextstep/jwp/db/InMemoryUserRepositoryTest.java b/app/src/test/java/nextstep/jwp/db/InMemoryUserRepositoryTest.java index cdc22e8e3c..0e6a61d3be 100644 --- a/app/src/test/java/nextstep/jwp/db/InMemoryUserRepositoryTest.java +++ b/app/src/test/java/nextstep/jwp/db/InMemoryUserRepositoryTest.java @@ -1,9 +1,12 @@ package nextstep.jwp.db; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; +import nextstep.jwp.exception.DuplicateAccountException; import nextstep.jwp.exception.UnauthorizedException; import nextstep.jwp.model.User; import org.junit.jupiter.api.BeforeEach; @@ -17,7 +20,7 @@ class InMemoryUserRepositoryTest { @BeforeEach void setUp() { Map database = new HashMap<>(); - userRepository = new InMemoryUserRepository(database, 1L); + userRepository = new InMemoryUserRepository(database, new AtomicLong(1)); } @DisplayName("신규 User 저장") @@ -33,6 +36,22 @@ void save() { assertThat(userRepository.findByAccount(user.getAccount())).isPresent(); } + @DisplayName("신규 User 저장시 중복된 Account가 존재할 경우 예외처리") + @Test + void saveException() { + // given + String sameAccount = "account"; + User user1 = new User(sameAccount, "password1", "email1"); + User user2 = new User(sameAccount, "password2", "email2"); + + // when + userRepository.save(user1); + + // then + assertThatThrownBy(() -> userRepository.save(user2)) + .isExactlyInstanceOf(DuplicateAccountException.class); + } + @DisplayName("Account를 이용한 User 조회") @Test void findByAccount() { diff --git a/app/src/test/java/nextstep/jwp/http/common/HttpCookieTest.java b/app/src/test/java/nextstep/jwp/http/common/HttpCookieTest.java new file mode 100644 index 0000000000..396ea7c45c --- /dev/null +++ b/app/src/test/java/nextstep/jwp/http/common/HttpCookieTest.java @@ -0,0 +1,55 @@ +package nextstep.jwp.http.common; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import nextstep.jwp.exception.InvalidRequestHeader; +import nextstep.jwp.exception.QueryParameterNotFoundException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class HttpCookieTest { + + @DisplayName("Cookie를 생성할 때 유효하지 않은 경우 예외가 발생한다.") + @ParameterizedTest + @ValueSource(strings = {"1234", "a=", "A=1; b", "A=1; B="}) + void parseException(String cookieString) { + assertThatThrownBy(() -> HttpCookie.parse(cookieString)) + .isExactlyInstanceOf(InvalidRequestHeader.class); + } + + @DisplayName("Cookie에서 Parameter를 조회할 때") + @Nested + class getParameter { + + @DisplayName("해당 Parameter가 있으면 조회에 성공한다.") + @Test + void getParameter() { + // given + String parameterKey = "JSESSION"; + String parameterValue = "1234"; + String cookieString = String.format("%s=%s;", parameterKey, parameterValue); + + // when + HttpCookie httpCookie = HttpCookie.parse(cookieString); + String foundCookieValue = httpCookie.getParameter(parameterKey); + + // then + assertThat(foundCookieValue).isEqualTo(parameterValue); + } + + @DisplayName("해당 Parameter가 없으면 예외가 발생한다.") + @Test + void getParameterException() { + // given + HttpCookie httpCookie = HttpCookie.parse("JSESSION=1234;"); + + // when, then + assertThatThrownBy(() -> httpCookie.getParameter("none")) + .isExactlyInstanceOf(QueryParameterNotFoundException.class); + } + } +} \ No newline at end of file diff --git a/app/src/test/java/nextstep/jwp/http/request/HttpRequestTest.java b/app/src/test/java/nextstep/jwp/http/request/HttpRequestTest.java index 0726cf6746..ec138022eb 100644 --- a/app/src/test/java/nextstep/jwp/http/request/HttpRequestTest.java +++ b/app/src/test/java/nextstep/jwp/http/request/HttpRequestTest.java @@ -9,6 +9,7 @@ import java.nio.charset.StandardCharsets; import nextstep.jwp.exception.EmptyQueryParametersException; import nextstep.jwp.exception.QueryParameterNotFoundException; +import nextstep.jwp.http.common.HttpCookie; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -30,7 +31,8 @@ void setUp() throws IOException { "", ""); - try (InputStream inputStream = new ByteArrayInputStream(requestString.getBytes(StandardCharsets.UTF_8))) { + try (InputStream inputStream = new ByteArrayInputStream( + requestString.getBytes(StandardCharsets.UTF_8))) { httpRequest = HttpRequest.parse(inputStream); } } @@ -57,6 +59,47 @@ void getUri() { assertThat(uri).isEqualTo(URI); } + @DisplayName("Cookie 정보 요청시") + @Nested + class GetCookie { + + @DisplayName("Cookie를 가지고 있다면 HttpCookie를 반환한다.") + @Test + void getCookie() throws IOException { + // given + String requestLineWithQuery = String.format("%s %s %s", GET, URI, HTTP_VERSION); + String requestString = String.join(NEW_LINE, requestLineWithQuery, + "Cookie: wow=1234; ", "", ""); + + try (InputStream inputStream = new ByteArrayInputStream( + requestString.getBytes(StandardCharsets.UTF_8))) { + httpRequest = HttpRequest.parse(inputStream); + } + + // then + assertThat(httpRequest.hasCookie()).isTrue(); + assertThat(httpRequest.getCookie()).isExactlyInstanceOf(HttpCookie.class); + } + + @DisplayName("Cookie가 없다면 예외가 발생한다.") + @Test + void getCookieException() throws IOException { + // given + String requestLineWithQuery = String.format("%s %s %s", GET, URI, HTTP_VERSION); + String requestString = String.join(NEW_LINE, requestLineWithQuery, "", ""); + + try (InputStream inputStream = new ByteArrayInputStream( + requestString.getBytes(StandardCharsets.UTF_8))) { + httpRequest = HttpRequest.parse(inputStream); + } + + // then + assertThat(httpRequest.hasCookie()).isFalse(); + assertThatThrownBy(httpRequest::getCookie) + .isExactlyInstanceOf(QueryParameterNotFoundException.class); + } + } + @DisplayName("Uri Query와 함께 요청시") @Nested class RequestWithUriQuery { @@ -67,10 +110,12 @@ class RequestWithUriQuery { @BeforeEach void setUp() throws IOException { String uriWithQuery = String.format("%s?%s=%s", URI, QUERY, PARAMETER); - String requestLineWithQuery = String.format("%s %s %s", GET, uriWithQuery, HTTP_VERSION); + String requestLineWithQuery = String.format("%s %s %s", GET, uriWithQuery, + HTTP_VERSION); String requestString = String.join(NEW_LINE, requestLineWithQuery, "", ""); - try (InputStream inputStream = new ByteArrayInputStream(requestString.getBytes(StandardCharsets.UTF_8))) { + try (InputStream inputStream = new ByteArrayInputStream( + requestString.getBytes(StandardCharsets.UTF_8))) { httpRequest = HttpRequest.parse(inputStream); } } @@ -99,13 +144,15 @@ void getUriParameterException() { @Nested class RequestWithOutUriQuery { - private final String requestLineWithOutQuery = String.format("%s %s %s", GET, URI, HTTP_VERSION); + private final String requestLineWithOutQuery = String.format("%s %s %s", GET, URI, + HTTP_VERSION); @BeforeEach void setUp() throws IOException { String requestString = String.join(NEW_LINE, requestLineWithOutQuery, "", ""); - try (InputStream inputStream = new ByteArrayInputStream(requestString.getBytes(StandardCharsets.UTF_8))) { + try (InputStream inputStream = new ByteArrayInputStream( + requestString.getBytes(StandardCharsets.UTF_8))) { httpRequest = HttpRequest.parse(inputStream); } } @@ -126,13 +173,16 @@ void getUriParameterException() { @DisplayName("Request-Body가 없다면") @Nested class RequestWithOutBody { - private final String requestString = String.join(NEW_LINE, requestLineWithOutQuery, "", ""); + + private final String requestString = String.join(NEW_LINE, requestLineWithOutQuery, "", + ""); @DisplayName("body parameter 요청시 예외가 발생한다.") @Test void getBodyParameterException() throws IOException { // given - try (InputStream inputStream = new ByteArrayInputStream(requestString.getBytes(StandardCharsets.UTF_8))) { + try (InputStream inputStream = new ByteArrayInputStream( + requestString.getBytes(StandardCharsets.UTF_8))) { httpRequest = HttpRequest.parse(inputStream); } @@ -161,7 +211,8 @@ void setUp() throws IOException { body ); - try (InputStream inputStream = new ByteArrayInputStream(requestString.getBytes(StandardCharsets.UTF_8))) { + try (InputStream inputStream = new ByteArrayInputStream( + requestString.getBytes(StandardCharsets.UTF_8))) { httpRequest = HttpRequest.parse(inputStream); } } diff --git a/app/src/test/java/nextstep/jwp/http/request/RequestBodyTest.java b/app/src/test/java/nextstep/jwp/http/request/RequestBodyTest.java index 75eead4ade..8f5a93db43 100644 --- a/app/src/test/java/nextstep/jwp/http/request/RequestBodyTest.java +++ b/app/src/test/java/nextstep/jwp/http/request/RequestBodyTest.java @@ -18,7 +18,8 @@ class includeBody { private final static String QUERY = "aaa"; private final static String PARAMETER = "bbb"; - private final RequestBody body = RequestBody.parse(String.format("%s=%s", QUERY, PARAMETER)); + private final RequestBody body = RequestBody.parse( + String.format("%s=%s", QUERY, PARAMETER)); @DisplayName("parameter 요청시 결과를 반환한다.") @Test diff --git a/app/src/test/java/nextstep/jwp/http/request/RequestHeadersTest.java b/app/src/test/java/nextstep/jwp/http/request/RequestHeadersTest.java index 03efc1593b..8a4402b610 100644 --- a/app/src/test/java/nextstep/jwp/http/request/RequestHeadersTest.java +++ b/app/src/test/java/nextstep/jwp/http/request/RequestHeadersTest.java @@ -10,6 +10,8 @@ import java.io.InputStreamReader; import nextstep.jwp.exception.HttpRequestNotHaveBodyException; import nextstep.jwp.exception.InvalidRequestHeader; +import nextstep.jwp.exception.QueryParameterNotFoundException; +import nextstep.jwp.http.common.HttpCookie; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -49,12 +51,62 @@ void InvalidFormatHeader() throws IOException { .isExactlyInstanceOf(InvalidRequestHeader.class); } + @DisplayName("Cookie 헤더 요청시") + @Nested + class includeCookie { + + @DisplayName("Cookie 헤더가 있다면 값을 반환 받는다.") + @Test + void getCookie() throws IOException { + // given + String cookieKey = "JSESSIONID"; + String cookieValue = "1234"; + String cookieHeader = String.format("Cookie: %s=%s;", cookieKey, cookieValue); + + String inputHeaders = String.join(NEW_LINE, cookieHeader, "", ""); + + RequestHeaders requestHeaders; + + try (InputStream inputStream = new ByteArrayInputStream(inputHeaders.getBytes())) { + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + requestHeaders = RequestHeaders.parse(bufferedReader); + } + + // when + HttpCookie cookie = requestHeaders.getCookie(); + + // then + assertThat(requestHeaders.hasCookie()).isTrue(); + assertThat(cookie.getParameter(cookieKey)).isEqualTo(cookieValue); + } + + @DisplayName("Cookie 헤더가 없다면 예외가 발생한다.") + @Test + void getCookieException() throws IOException { + // given + String inputHeaders = String.join(NEW_LINE, "Content-length: wow", "", ""); + + RequestHeaders requestHeaders; + + try (InputStream inputStream = new ByteArrayInputStream(inputHeaders.getBytes())) { + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + requestHeaders = RequestHeaders.parse(bufferedReader); + } + + // then + assertThat(requestHeaders.hasCookie()).isFalse(); + assertThatThrownBy(requestHeaders::getCookie) + .isExactlyInstanceOf(QueryParameterNotFoundException.class); + } + } + @DisplayName("Content-Length 헤더가 포함되었다면") @Nested class includeContentLength { private final int contentLengthValue = 100; - private final String contentLength = String.format("Content-Length: %d", contentLengthValue); + private final String contentLength = String.format("Content-Length: %d", + contentLengthValue); private final String inputHeaders = String.join(NEW_LINE, contentLength, "", ""); @DisplayName("Content-Length 헤더로 Request-Body 가 있음을 판단한다.") @@ -63,7 +115,8 @@ void requestHasBody() throws IOException { RequestHeaders requestHeaders; try (InputStream inputStream = new ByteArrayInputStream(inputHeaders.getBytes())) { - BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + BufferedReader bufferedReader = new BufferedReader( + new InputStreamReader(inputStream)); requestHeaders = RequestHeaders.parse(bufferedReader); } @@ -76,7 +129,8 @@ void getContentLength() throws IOException { RequestHeaders requestHeaders; try (InputStream inputStream = new ByteArrayInputStream(inputHeaders.getBytes())) { - BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + BufferedReader bufferedReader = new BufferedReader( + new InputStreamReader(inputStream)); requestHeaders = RequestHeaders.parse(bufferedReader); } @@ -96,7 +150,8 @@ void requestHasBody() throws IOException { RequestHeaders requestHeaders; try (InputStream inputStream = new ByteArrayInputStream(inputHeaders.getBytes())) { - BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + BufferedReader bufferedReader = new BufferedReader( + new InputStreamReader(inputStream)); requestHeaders = RequestHeaders.parse(bufferedReader); } @@ -109,7 +164,8 @@ void getContentLength() throws IOException { RequestHeaders requestHeaders; try (InputStream inputStream = new ByteArrayInputStream(inputHeaders.getBytes())) { - BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + BufferedReader bufferedReader = new BufferedReader( + new InputStreamReader(inputStream)); requestHeaders = RequestHeaders.parse(bufferedReader); } diff --git a/app/src/test/java/nextstep/jwp/http/request/RequestLineTest.java b/app/src/test/java/nextstep/jwp/http/request/RequestLineTest.java index 324a338be1..8a6a64ad93 100644 --- a/app/src/test/java/nextstep/jwp/http/request/RequestLineTest.java +++ b/app/src/test/java/nextstep/jwp/http/request/RequestLineTest.java @@ -77,7 +77,8 @@ void getUriParameterException() { @Nested class RequestLineWithOutQueryParam { - private final RequestLine requestLine = RequestLine.parse(String.format("GET %s HTTP/1.1", URI)); + private final RequestLine requestLine = RequestLine.parse( + String.format("GET %s HTTP/1.1", URI)); @DisplayName("QueryParameter를 포함하고 있지 않다.") @Test diff --git a/app/src/test/java/nextstep/jwp/http/request/RequestUriTest.java b/app/src/test/java/nextstep/jwp/http/request/RequestUriTest.java index 1530ff57c4..847dd8e0ee 100644 --- a/app/src/test/java/nextstep/jwp/http/request/RequestUriTest.java +++ b/app/src/test/java/nextstep/jwp/http/request/RequestUriTest.java @@ -46,7 +46,8 @@ class UriWithQueryParam { private static final String QUERY = "query"; private static final String PARAMETER = "param"; - private final RequestUri requestUri = RequestUri.parse(String.format("/path?%s=%s", QUERY, PARAMETER)); + private final RequestUri requestUri = RequestUri.parse( + String.format("/path?%s=%s", QUERY, PARAMETER)); @DisplayName("queryParameters를 가진다.") @Test diff --git a/app/src/test/java/nextstep/jwp/http/response/HttpResponseTest.java b/app/src/test/java/nextstep/jwp/http/response/HttpResponseTest.java index 80906551a9..6114b31a0e 100644 --- a/app/src/test/java/nextstep/jwp/http/response/HttpResponseTest.java +++ b/app/src/test/java/nextstep/jwp/http/response/HttpResponseTest.java @@ -38,7 +38,8 @@ void toBytes() throws IOException { httpResponse = HttpResponse.withBody(HttpStatus.OK, staticResource); // then - assertThat(httpResponse.toBytes()).isEqualTo(expectString.getBytes(StandardCharsets.UTF_8)); + assertThat(httpResponse.toBytes()).isEqualTo( + expectString.getBytes(StandardCharsets.UTF_8)); } } @@ -59,7 +60,8 @@ void toBytes() { httpResponse = HttpResponse.redirect(HttpStatus.MOVED_PERMANENTLY, location); // then - assertThat(httpResponse.toBytes()).isEqualTo(expectString.getBytes(StandardCharsets.UTF_8)); + assertThat(httpResponse.toBytes()).isEqualTo( + expectString.getBytes(StandardCharsets.UTF_8)); } } } \ No newline at end of file diff --git a/app/src/test/java/nextstep/jwp/http/response/ResponseHeadersTest.java b/app/src/test/java/nextstep/jwp/http/response/ResponseHeadersTest.java index 3c03b27837..9e583cb649 100644 --- a/app/src/test/java/nextstep/jwp/http/response/ResponseHeadersTest.java +++ b/app/src/test/java/nextstep/jwp/http/response/ResponseHeadersTest.java @@ -32,7 +32,8 @@ void toStringTest() throws IOException { byte[] bytes = Files.readAllBytes(file.toPath()); StaticResource staticResource = StaticResource.from(file); - String expectContentTypeString = String.format("Content-Type: %s ", contentType.getType()); + String expectContentTypeString = String.format("Content-Type: %s ", + contentType.getType()); String expectContentLengthString = String.format("Content-Length: %d ", bytes.length); // when @@ -41,7 +42,8 @@ void toStringTest() throws IOException { String[] headersStrings = headersString.split(NEW_LINE); // then - assertThat(headersStrings).containsExactlyInAnyOrder(expectContentTypeString, expectContentLengthString); + assertThat(headersStrings).containsExactlyInAnyOrder(expectContentTypeString, + expectContentLengthString); } } diff --git a/app/src/test/java/nextstep/jwp/http/response/StatusLineTest.java b/app/src/test/java/nextstep/jwp/http/response/StatusLineTest.java index 6876b739e4..48e6aa420a 100644 --- a/app/src/test/java/nextstep/jwp/http/response/StatusLineTest.java +++ b/app/src/test/java/nextstep/jwp/http/response/StatusLineTest.java @@ -1,6 +1,8 @@ package nextstep.jwp.http.response; -import static nextstep.jwp.http.common.HttpStatus.*; +import static nextstep.jwp.http.common.HttpStatus.BAD_REQUEST; +import static nextstep.jwp.http.common.HttpStatus.NO_CONTENT; +import static nextstep.jwp.http.common.HttpStatus.OK; import static org.assertj.core.api.Assertions.assertThat; import nextstep.jwp.http.common.HttpVersion; @@ -18,11 +20,13 @@ void toStringTest() { StatusLine badRequestStatusLine = StatusLine.from(BAD_REQUEST); String expectOkStatusLineString = String.format("%s %s %s ", - HttpVersion.HTTP_1_1.getValue(), OK.getCodeString(), OK.getReasonPhrase()); + HttpVersion.HTTP_1_1.getValue(), OK.getCodeString(), OK.getReasonPhrase()); String expectNoContentStatusLineString = String.format("%s %s %s ", - HttpVersion.HTTP_1_1.getValue(), NO_CONTENT.getCodeString(), NO_CONTENT.getReasonPhrase()); + HttpVersion.HTTP_1_1.getValue(), NO_CONTENT.getCodeString(), + NO_CONTENT.getReasonPhrase()); String expectBadRequestStatusLineString = String.format("%s %s %s ", - HttpVersion.HTTP_1_1.getValue(), BAD_REQUEST.getCodeString(), BAD_REQUEST.getReasonPhrase()); + HttpVersion.HTTP_1_1.getValue(), BAD_REQUEST.getCodeString(), + BAD_REQUEST.getReasonPhrase()); // when, then assertThat(okStatusLine.toString()).isEqualTo(expectOkStatusLineString); diff --git a/app/src/test/java/nextstep/jwp/model/UserTest.java b/app/src/test/java/nextstep/jwp/model/UserTest.java index e164c80858..3f86830118 100644 --- a/app/src/test/java/nextstep/jwp/model/UserTest.java +++ b/app/src/test/java/nextstep/jwp/model/UserTest.java @@ -40,6 +40,7 @@ void checkPasswordException() { User user = new User(ACCOUNT, EMAIL, PASSWORD); // when, then - assertThatThrownBy(() -> user.checkPassword("WRONG_PASSWORD")).isExactlyInstanceOf(UnauthorizedException.class); + assertThatThrownBy(() -> user.checkPassword("WRONG_PASSWORD")).isExactlyInstanceOf( + UnauthorizedException.class); } } \ No newline at end of file diff --git a/app/src/test/java/nextstep/jwp/server/HttpSessionTest.java b/app/src/test/java/nextstep/jwp/server/HttpSessionTest.java new file mode 100644 index 0000000000..37219884fd --- /dev/null +++ b/app/src/test/java/nextstep/jwp/server/HttpSessionTest.java @@ -0,0 +1,76 @@ +package nextstep.jwp.server; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import nextstep.jwp.exception.SessionAttributeNotFoundException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class HttpSessionTest { + + private HttpSession httpSession; + + @BeforeEach + void setUp() { + httpSession = new HttpSession("1L"); + } + + @DisplayName("Attribute를 가지고 있는지 물어봤을 때") + @Nested + class hasAttribute { + + @DisplayName("가지고 있다면 true를 반환한다.") + @Test + void hasAttributeTrue() { + // given + String name = "attribute-name"; + httpSession.setAttribute(name, "value"); + + // when, then + assertThat(httpSession.hasAttribute(name)).isTrue(); + } + + @DisplayName("가지고 있지 않다면 false를 반환한다.") + @Test + void hasAttributeFalse() { + // given + String name = "attribute-name"; + + // when, then + assertThat(httpSession.hasAttribute(name)).isFalse(); + } + } + + @DisplayName("Attribute를 요청했을 때") + @Nested + class GetAttribute { + + @DisplayName("해당하는 Attribute가 있다면 반환한다.") + @Test + void getAttribute() { + // given + String attributeName = "A is"; + String attributeValue = "Apple"; + + httpSession.setAttribute("A is", "Apple"); + + // when + String foundAttribute = (String) httpSession.getAttribute(attributeName); + + // then + assertThat(foundAttribute).isEqualTo(attributeValue); + } + + @DisplayName("해당하는 Attribute가 없다면 예외가 발생한다.") + @Test + void getAttributeException() { + // when, then + assertThatThrownBy(() -> httpSession.getAttribute("someThing")) + .isExactlyInstanceOf(SessionAttributeNotFoundException.class); + + } + } +} \ No newline at end of file diff --git a/app/src/test/java/nextstep/jwp/server/HttpSessionsTest.java b/app/src/test/java/nextstep/jwp/server/HttpSessionsTest.java new file mode 100644 index 0000000000..0dc52a0b7e --- /dev/null +++ b/app/src/test/java/nextstep/jwp/server/HttpSessionsTest.java @@ -0,0 +1,96 @@ +package nextstep.jwp.server; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import nextstep.jwp.exception.SessionAttributeNotFoundException; +import nextstep.jwp.exception.SessionNotFoundException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class HttpSessionsTest { + + private static final String SESSION_ID = "1"; + private static final String ATTRIBUTE_NAME = "A is"; + private static final String ATTRIBUTE_VALUE = "Apple"; + + private HttpSessions httpSessions; + + @BeforeEach + void setUp() { + HttpSession httpSession = new HttpSession(SESSION_ID); + httpSession.setAttribute(ATTRIBUTE_NAME, ATTRIBUTE_VALUE); + + httpSessions = new HttpSessions(); + httpSessions.addSession(httpSession); + } + + @DisplayName("sessionId와 attributeName을 통해 Object가 있는지 질문시") + @Nested + class hasObject { + @DisplayName("해당하는 session이 있을 때") + @Nested + class hasSession { + @DisplayName("해당하는 attribute가 있다면 true를 반환한다.") + @Test + void returnTrue() { + assertThat(httpSessions.hasObject(SESSION_ID, ATTRIBUTE_NAME)).isTrue(); + } + + @DisplayName("해당하는 attribute가 없다면 false를 반환한다.") + @Test + void returnFalse() { + assertThat(httpSessions.hasObject(SESSION_ID, "noAttribute")).isFalse(); + } + } + + @DisplayName("해당되는 session이 없을 때") + @Nested + class haveNotSession { + + @DisplayName("false를 반환한다.") + @Test + void returnFalse() { + assertThat(httpSessions.hasObject("noSession", "noName")).isFalse(); + } + } + } + + @DisplayName("sessionId와 attributeName을 통해 Object 조회 요청시") + @Nested + class FindObject { + + @DisplayName("sessionId에 해당하는 Session을 찾았을 때") + @Nested + class containsSession { + + @DisplayName("attributeName에 해당하는 attribute를 찾으면 Object를 반환한다.") + @Test + void getAttribute() { + // when + String attribute = (String) httpSessions.findObject(SESSION_ID, ATTRIBUTE_NAME); + + // then + assertThat(attribute).isEqualTo(ATTRIBUTE_VALUE); + } + + @DisplayName("attributeName에 해당하는 attribute가 없으면 예외가 발생한다.") + @Test + void getAttributeException() { + // when, then + assertThatThrownBy(() -> httpSessions.findObject(SESSION_ID, "Something")) + .isExactlyInstanceOf(SessionAttributeNotFoundException.class); + } + } + + @DisplayName("sessionId에 해당하는 Session을 찾지 못했을 때 예외가 발생한다.") + @Test + void findObjectException() { + // when, then + assertThatThrownBy(() -> httpSessions.findObject("Another id", ATTRIBUTE_NAME)) + .isExactlyInstanceOf(SessionNotFoundException.class); + } + } +} \ No newline at end of file diff --git a/app/src/test/java/nextstep/jwp/RequestHandlerTest.java b/app/src/test/java/nextstep/jwp/server/RequestHandlerTest.java similarity index 68% rename from app/src/test/java/nextstep/jwp/RequestHandlerTest.java rename to app/src/test/java/nextstep/jwp/server/RequestHandlerTest.java index ed6f28925d..3b49ff620a 100644 --- a/app/src/test/java/nextstep/jwp/RequestHandlerTest.java +++ b/app/src/test/java/nextstep/jwp/server/RequestHandlerTest.java @@ -1,19 +1,18 @@ -package nextstep.jwp; - -import nextstep.jwp.controller.Controllers; -import nextstep.jwp.server.RequestHandler; -import org.junit.jupiter.api.Test; +package nextstep.jwp.server; import static org.assertj.core.api.Assertions.assertThat; +import nextstep.jwp.MockSocket; +import org.junit.jupiter.api.Test; + class RequestHandlerTest { @Test void run() { // given final MockSocket socket = new MockSocket(); - final Controllers controllers = Controllers.loadContext(); - final RequestHandler requestHandler = new RequestHandler(socket, controllers); + final RequestMapping requestMapping = RequestMapping.loadContext(); + final RequestHandler requestHandler = new RequestHandler(socket, requestMapping); // when requestHandler.run(); @@ -31,16 +30,16 @@ void run() { @Test void index() { // given - final String httpRequest= String.join("\r\n", - "GET /index.html HTTP/1.1 ", - "Host: localhost:8080 ", - "Connection: keep-alive ", - "", - ""); + final String httpRequest = String.join("\r\n", + "GET /index.html HTTP/1.1 ", + "Host: localhost:8080 ", + "Connection: keep-alive ", + "", + ""); final MockSocket socket = new MockSocket(httpRequest); - final Controllers controllers = Controllers.loadContext(); - final RequestHandler requestHandler = new RequestHandler(socket, controllers); + final RequestMapping requestMapping = RequestMapping.loadContext(); + final RequestHandler requestHandler = new RequestHandler(socket, requestMapping); // when requestHandler.run(); diff --git a/app/src/test/java/nextstep/jwp/controller/ControllersTest.java b/app/src/test/java/nextstep/jwp/server/RequestMappingTest.java similarity index 67% rename from app/src/test/java/nextstep/jwp/controller/ControllersTest.java rename to app/src/test/java/nextstep/jwp/server/RequestMappingTest.java index 7475cff3a7..39e52c34a7 100644 --- a/app/src/test/java/nextstep/jwp/controller/ControllersTest.java +++ b/app/src/test/java/nextstep/jwp/server/RequestMappingTest.java @@ -1,4 +1,4 @@ -package nextstep.jwp.controller; +package nextstep.jwp.server; import static org.assertj.core.api.Assertions.assertThat; @@ -12,17 +12,33 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; -class ControllersTest { +class RequestMappingTest { private static final String NEW_LINE = System.getProperty("line.separator"); - private Controllers controllers; + private RequestMapping requestMapping; private HttpRequest httpRequest; @BeforeEach void setUp() { - controllers = Controllers.loadContext(); + requestMapping = RequestMapping.loadContext(); + } + + @DisplayName("split 메서드를 이용해 URI을 파싱한다.") + @ParameterizedTest + @ValueSource(strings = {"/login", "/login/abc/f", "/login/"}) + void splitUri(String requestUri) { + // given + String expectResult = "login"; + + // when + String[] splitedUri = requestUri.split("/"); + + // then + assertThat(splitedUri[1]).isEqualTo(expectResult); } @DisplayName("URI에 따라 컨트롤러를 찾아 요청을 수행한다.") @@ -53,10 +69,11 @@ void get() throws IOException { + "login is good"; // when - HttpResponse httpResponse = controllers.doService(httpRequest); + HttpResponse httpResponse = requestMapping.doService(httpRequest); // then - assertThat(httpResponse.toBytes()).isEqualTo(expectString.getBytes(StandardCharsets.UTF_8)); + assertThat(httpResponse.toBytes()).isEqualTo( + expectString.getBytes(StandardCharsets.UTF_8)); } @DisplayName("POST 요청") @@ -73,14 +90,25 @@ void post() throws IOException { httpRequest = HttpRequest.parse(inputStream); } - String expectString = "HTTP/1.1 302 Found \n" - + "Location: /index.html "; + String expectStatusLine = "HTTP/1.1 200 OK "; + String expectCookie = "Set-Cookie:"; + String expectContentLength = "Content-Length: 11 "; + String expectContentType = "Content-Type: text/html; charset=UTF-8 "; + String expectEnterLine = ""; + String expectBody = "hihi hello!"; // when - HttpResponse httpResponse = controllers.doService(httpRequest); + HttpResponse httpResponse = requestMapping.doService(httpRequest); + String response = new String(httpResponse.toBytes()); + String[] splitedResponse = response.split("\n"); // then - assertThat(httpResponse.toBytes()).isEqualTo(expectString.getBytes(StandardCharsets.UTF_8)); + assertThat(splitedResponse[0]).isEqualTo(expectStatusLine); + assertThat(splitedResponse[1].startsWith(expectCookie)).isTrue(); + assertThat(splitedResponse[2]).isEqualTo(expectContentLength); + assertThat(splitedResponse[3]).isEqualTo(expectContentType); + assertThat(splitedResponse[4]).isEqualTo(expectEnterLine); + assertThat(splitedResponse[5]).isEqualTo(expectBody); } } @@ -108,10 +136,11 @@ void get() throws IOException { + "register is good"; // when - HttpResponse httpResponse = controllers.doService(httpRequest); + HttpResponse httpResponse = requestMapping.doService(httpRequest); // then - assertThat(httpResponse.toBytes()).isEqualTo(expectString.getBytes(StandardCharsets.UTF_8)); + assertThat(httpResponse.toBytes()).isEqualTo( + expectString.getBytes(StandardCharsets.UTF_8)); } @DisplayName("POST 요청") @@ -132,10 +161,11 @@ void post() throws IOException { + "Location: /index.html "; // when - HttpResponse httpResponse = controllers.doService(httpRequest); + HttpResponse httpResponse = requestMapping.doService(httpRequest); // then - assertThat(httpResponse.toBytes()).isEqualTo(expectString.getBytes(StandardCharsets.UTF_8)); + assertThat(httpResponse.toBytes()).isEqualTo( + expectString.getBytes(StandardCharsets.UTF_8)); } } @@ -147,7 +177,8 @@ class StaticResource { @Test void get() throws IOException { // given - String requestString = String.join(NEW_LINE, "GET /static-page.html HTTP/1.1", "", ""); + String requestString = String.join(NEW_LINE, "GET /static-page.html HTTP/1.1", "", + ""); try (InputStream inputStream = new ByteArrayInputStream(requestString.getBytes( StandardCharsets.UTF_8))) { httpRequest = HttpRequest.parse(inputStream); @@ -160,17 +191,19 @@ void get() throws IOException { + "static page is good!"; // when - HttpResponse httpResponse = controllers.doService(httpRequest); + HttpResponse httpResponse = requestMapping.doService(httpRequest); // then - assertThat(httpResponse.toBytes()).isEqualTo(expectString.getBytes(StandardCharsets.UTF_8)); + assertThat(httpResponse.toBytes()).isEqualTo( + expectString.getBytes(StandardCharsets.UTF_8)); } @DisplayName("POST 요청") @Test void post() throws IOException { // given - String requestString = String.join(NEW_LINE, "POST /static-page.html HTTP/1.1", "", ""); + String requestString = String.join(NEW_LINE, "POST /static-page.html HTTP/1.1", "", + ""); try (InputStream inputStream = new ByteArrayInputStream(requestString.getBytes( StandardCharsets.UTF_8))) { httpRequest = HttpRequest.parse(inputStream); @@ -183,10 +216,11 @@ void post() throws IOException { + "static page is good!"; // when - HttpResponse httpResponse = controllers.doService(httpRequest); + HttpResponse httpResponse = requestMapping.doService(httpRequest); // then - assertThat(httpResponse.toBytes()).isEqualTo(expectString.getBytes(StandardCharsets.UTF_8)); + assertThat(httpResponse.toBytes()).isEqualTo( + expectString.getBytes(StandardCharsets.UTF_8)); } } } diff --git a/app/src/test/java/nextstep/jwp/service/LoginServiceTest.java b/app/src/test/java/nextstep/jwp/service/LoginServiceTest.java index 56a4b4ed3d..7e94252131 100644 --- a/app/src/test/java/nextstep/jwp/service/LoginServiceTest.java +++ b/app/src/test/java/nextstep/jwp/service/LoginServiceTest.java @@ -1,13 +1,18 @@ package nextstep.jwp.service; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; import nextstep.jwp.controller.request.LoginRequest; +import nextstep.jwp.controller.response.LoginResponse; import nextstep.jwp.db.InMemoryUserRepository; import nextstep.jwp.exception.UnauthorizedException; +import nextstep.jwp.http.common.HttpCookie; import nextstep.jwp.model.User; +import nextstep.jwp.server.HttpSessions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -21,24 +26,28 @@ class LoginServiceTest { @BeforeEach void setUp() { Map database = new HashMap<>(); - userRepository = new InMemoryUserRepository(database, 1L); + userRepository = new InMemoryUserRepository(database, new AtomicLong(1)); - loginService = new LoginService(userRepository); + HttpSessions httpSessions = new HttpSessions(); + loginService = new LoginService(userRepository, httpSessions); } @DisplayName("login 시도시") @Nested class login { - @DisplayName("일치하는 account가 없다면 예외가 발생한다.") + @DisplayName("성공하면 LoginResponse를 반환한다.") @Test - void notFoundAccount() { + void success() { // given - LoginRequest loginRequest = new LoginRequest("account", "password"); + String account = "account"; + String password = "password"; + userRepository.save(new User(account, password, "email")); + + LoginRequest loginRequest = new LoginRequest(account, password); // when, then - assertThatThrownBy(() -> loginService.login(loginRequest)) - .isExactlyInstanceOf(UnauthorizedException.class); + assertThat(loginService.login(loginRequest)).isExactlyInstanceOf(LoginResponse.class); } @DisplayName("password가 일치하지 않는다면 예외가 발생한다.") @@ -55,5 +64,53 @@ void unmatchedPassword() { assertThatThrownBy(() -> loginService.login(loginRequest)) .isExactlyInstanceOf(UnauthorizedException.class); } + + @DisplayName("일치하는 account가 없다면 예외가 발생한다.") + @Test + void notFoundAccount() { + // given + LoginRequest loginRequest = new LoginRequest("account", "password"); + + // when, then + assertThatThrownBy(() -> loginService.login(loginRequest)) + .isExactlyInstanceOf(UnauthorizedException.class); + } + } + + @DisplayName("이미 로그인 중인지 확인했을 때") + @Nested + class IsAlreadyLogin { + + private static final String account = "account"; + private static final String password = "password"; + + @DisplayName("로그인 중이었다면 true를 반환한다.") + @Test + void isTrue() { + // given + userRepository.save(new User(account, password, "email")); + LoginResponse loginResponse = loginService.login(new LoginRequest(account, password)); + + HttpCookie httpCookie = HttpCookie.parse(loginResponse.toCookieString()); + + // when + boolean alreadyLogin = loginService.isAlreadyLogin(httpCookie); + + // then + assertThat(alreadyLogin).isTrue(); + } + + @DisplayName("로그인 중이 아니라면 false를 반환한다.") + @Test + void isFalse() { + // given + HttpCookie httpCookie = HttpCookie.parse("JSESSIONID=1234;"); + + // when + boolean alreadyLogin = loginService.isAlreadyLogin(httpCookie); + + // then + assertThat(alreadyLogin).isFalse(); + } } } diff --git a/app/src/test/java/nextstep/jwp/service/RegisterServiceTest.java b/app/src/test/java/nextstep/jwp/service/RegisterServiceTest.java index 82000720d4..6088cab573 100644 --- a/app/src/test/java/nextstep/jwp/service/RegisterServiceTest.java +++ b/app/src/test/java/nextstep/jwp/service/RegisterServiceTest.java @@ -4,6 +4,7 @@ import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; import nextstep.jwp.controller.request.RegisterRequest; import nextstep.jwp.db.InMemoryUserRepository; import nextstep.jwp.exception.DuplicateAccountException; @@ -20,7 +21,7 @@ class RegisterServiceTest { @BeforeEach void setUp() { Map database = new HashMap<>(); - userRepository = new InMemoryUserRepository(database, 1L); + userRepository = new InMemoryUserRepository(database, new AtomicLong(1)); registerService = new RegisterService(userRepository); } diff --git a/app/src/test/java/nextstep/jwp/service/StaticResourceServiceTest.java b/app/src/test/java/nextstep/jwp/service/StaticResourceServiceTest.java index cac46fe85d..438e49133e 100644 --- a/app/src/test/java/nextstep/jwp/service/StaticResourceServiceTest.java +++ b/app/src/test/java/nextstep/jwp/service/StaticResourceServiceTest.java @@ -15,7 +15,7 @@ class StaticResourceServiceTest { - private StaticResourceService staticResourceService = new StaticResourceService(); + private final StaticResourceService staticResourceService = new StaticResourceService(); @DisplayName("Uri 경로를 이용한 파일 탐색시") @Nested diff --git a/app/src/test/java/nextstep/learning/http/FileTest.java b/app/src/test/java/nextstep/learning/http/FileTest.java index 2d78c9c7ae..d3fcc25829 100644 --- a/app/src/test/java/nextstep/learning/http/FileTest.java +++ b/app/src/test/java/nextstep/learning/http/FileTest.java @@ -18,8 +18,7 @@ class FileTest { /** - * File 객체를 생성하려면 파일의 경로를 알아야 한다.
자바 애플리케이션은 resource 디렉터리에 정적 파일을 저장한다.
resource 디렉터리의 - * 경로는 어떻게 알아낼 수 있을까? + * File 객체를 생성하려면 파일의 경로를 알아야 한다.
자바 애플리케이션은 resource 디렉터리에 정적 파일을 저장한다.
resource 디렉터리의 경로는 어떻게 알아낼 수 있을까? */ @Test void resource_디렉터리에_있는_파일의_경로를_찾는다() { diff --git a/app/src/test/java/nextstep/learning/http/IOStreamTest.java b/app/src/test/java/nextstep/learning/http/IOStreamTest.java index c900ab8d16..d712cdc39c 100644 --- a/app/src/test/java/nextstep/learning/http/IOStreamTest.java +++ b/app/src/test/java/nextstep/learning/http/IOStreamTest.java @@ -1,42 +1,49 @@ package nextstep.learning.http; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import java.io.*; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; - /** - * 입출력(I/O)은 하나의 시스템에서 다른 시스템으로 데이터를 이동 시킬 때 사용한다.
- * 자바는 스트림(Stream)으로부터 I/O를 사용한다.
+ * 입출력(I/O)은 하나의 시스템에서 다른 시스템으로 데이터를 이동 시킬 때 사용한다.
자바는 스트림(Stream)으로부터 I/O를 사용한다.
* * InputStream은 데이터를 읽고, OutputStream은 데이터를 쓴다.
* FilterStream은 InputStream이나 OutputStream에 연결될 수 있다.
* FilterStream은 읽거나 쓰는 데이터를 수정할 때 사용한다. (e.g. 암호화, 압축, 포맷 변환)
- *
+ *
* Stream은 데이터를 바이트로 읽고 쓴다.
- * 바이트가 아닌 텍스트(문자)를 읽고 쓰려면 Reader와 Writer 클래스를 연결한다.
- * Reader, Writer는 다양한 문자 인코딩(e.g. UTF-8)을 처리할 수 있다. + * 바이트가 아닌 텍스트(문자)를 읽고 쓰려면 Reader와 Writer 클래스를 연결한다.
Reader, Writer는 다양한 문자 인코딩(e.g. UTF-8)을 처리할 수 + * 있다. */ @DisplayName("Java I/O Stream 클래스 학습 테스트") class IOStreamTest { /** - * 자바의 기본 출력 클래스는 java.io.OutputStream이다.
- * OutputStream의 write(int b) 메서드는 기반 메서드이다.

+ * 자바의 기본 출력 클래스는 java.io.OutputStream이다.
OutputStream의 write(int b) 메서드는 기반 메서드이다.

* public abstract void write(int b) throws IOException;
*/ @Nested class OutputStream_학습_테스트 { /** - * OutputStream은 다른 매체에 바이트로 데이터를 쓸 때 사용한다.
- * OutputStream의 서브 클래스(subclass)는 특정 매체에 데이터를 쓰기 위해 write(int b) 메서드를 사용한다.
- * 예를 들어, FilterOutputStream은 파일로 데이터를 쓸 때,
- * DataOutputStream은 자바의 primitive type data를 다른 매체로 데이터를 쓸 때 사용한다.
+ * OutputStream은 다른 매체에 바이트로 데이터를 쓸 때 사용한다.
OutputStream의 서브 클래스(subclass)는 특정 매체에 데이터를 쓰기 위해 write(int + * b) 메서드를 사용한다.
예를 들어, FilterOutputStream은 파일로 데이터를 쓸 때,
DataOutputStream은 자바의 primitive type data를 + * 다른 매체로 데이터를 쓸 때 사용한다.
*
* write 메서드는 데이터를 바이트로 출력하기 때문에 비효율적이다.
* write(byte[] data)write(byte b[], int off, int len) 메서드는
@@ -84,8 +91,7 @@ class OutputStream_학습_테스트 { } /** - * 스트림 사용이 끝나면 항상 close() 메서드를 호출하여 스트림을 닫는다.
- * 장시간 스트림을 닫지 않으면 파일, 포트 등 다양한 리소스에서 누수(leak)가 발생한다. + * 스트림 사용이 끝나면 항상 close() 메서드를 호출하여 스트림을 닫는다.
장시간 스트림을 닫지 않으면 파일, 포트 등 다양한 리소스에서 누수(leak)가 발생한다. */ @Test void OutputStream은_사용하고_나서_close_처리를_해준다() throws IOException { @@ -104,9 +110,8 @@ class OutputStream_학습_테스트 { } /** - * 자바의 기본 입력 클래스는 java.io.InputStream이다.
- * InputStream은 다른 매체로부터 바이트로 데이터를 읽을 때 사용한다.
- * InputStream의 read() 메서드는 기반 메서드이다.

+ * 자바의 기본 입력 클래스는 java.io.InputStream이다.
InputStream은 다른 매체로부터 바이트로 데이터를 읽을 때 사용한다.
InputStream의 + * read() 메서드는 기반 메서드이다.

* public abstract int read() throws IOException;
*
* InputStream의 서브 클래스(subclass)는 특정 매체에 데이터를 읽기 위해 read() 메서드를 사용한다.
@@ -115,9 +120,8 @@ class OutputStream_학습_테스트 { class InputStream_학습_테스트 { /** - * read() 메서드는 매체로부터 단일 바이트를 읽는데, 0부터 255 사이의 값을 int 타입으로 반환한다.
- * int 값을 byte 타입으로 변환하면 -128부터 127 사이의 값으로 변환된다.
- * 그리고 Stream 끝에 도달하면 -1을 반환한다.
+ * read() 메서드는 매체로부터 단일 바이트를 읽는데, 0부터 255 사이의 값을 int 타입으로 반환한다.
int 값을 byte 타입으로 변환하면 -128부터 127 사이의 값으로 + * 변환된다.
그리고 Stream 끝에 도달하면 -1을 반환한다.
*/ @Test void InputStream은_데이터를_바이트로_읽는다() throws IOException { @@ -132,8 +136,7 @@ class InputStream_학습_테스트 { } /** - * 스트림 사용이 끝나면 항상 close() 메서드를 호출하여 스트림을 닫는다.
- * 장시간 스트림을 닫지 않으면 파일, 포트 등 다양한 리소스에서 누수(leak)가 발생한다. + * 스트림 사용이 끝나면 항상 close() 메서드를 호출하여 스트림을 닫는다.
장시간 스트림을 닫지 않으면 파일, 포트 등 다양한 리소스에서 누수(leak)가 발생한다. */ @Test void InputStream은_사용하고_나서_close_처리를_해준다() throws IOException { @@ -150,17 +153,15 @@ class InputStream_학습_테스트 { } /** - * 필터는 필터 스트림, reader, writer로 나뉜다.
- * 필터는 바이트를 다른 데이터 형식으로 변환 할 때 사용한다.
- * reader, writer는 UTF-8, ISO 8859-1 같은 형식으로 인코딩된 텍스트를 처리하는 데 사용된다. + * 필터는 필터 스트림, reader, writer로 나뉜다.
필터는 바이트를 다른 데이터 형식으로 변환 할 때 사용한다.
reader, writer는 UTF-8, ISO 8859-1 같은 + * 형식으로 인코딩된 텍스트를 처리하는 데 사용된다. */ @Nested class FilterStream_학습_테스트 { /** * BufferedInputStream은 데이터 처리 속도를 높이기 위해 데이터를 버퍼에 저장한다.
- * InputStream 객체를 생성하고 필터 생성자에 전달하면 필터에 연결된다.
- * 버퍼 크기를 지정하지 않으면 버퍼의 기본 사이즈는 얼마일까? + * InputStream 객체를 생성하고 필터 생성자에 전달하면 필터에 연결된다.
버퍼 크기를 지정하지 않으면 버퍼의 기본 사이즈는 얼마일까? */ @Test void 필터인_BufferedInputStream를_사용해보자() throws IOException { @@ -177,10 +178,8 @@ class FilterStream_학습_테스트 { } /** - * 자바의 기본 문자열은 UTF-16 유니코드 인코딩을 사용한다. - * 바이트를 문자(char)로 처리하려면 인코딩을 신경 써야 한다. - * InputStreamReader를 사용하면 지정된 인코딩에 따라 유니코드 문자로 변환할 수 있다. - * reader, writer를 사용하면 입출력 스트림을 바이트가 아닌 문자 단위로 데이터를 처리하게 된다. + * 자바의 기본 문자열은 UTF-16 유니코드 인코딩을 사용한다. 바이트를 문자(char)로 처리하려면 인코딩을 신경 써야 한다. InputStreamReader를 사용하면 지정된 인코딩에 따라 유니코드 + * 문자로 변환할 수 있다. reader, writer를 사용하면 입출력 스트림을 바이트가 아닌 문자 단위로 데이터를 처리하게 된다. */ @Nested class InputStreamReader_학습_테스트 { @@ -192,13 +191,14 @@ class InputStreamReader_학습_테스트 { @Test void BufferedReader를_사용하여_문자열을_읽어온다() throws IOException { final String emoji = String.join("\r\n", - "😀😃😄😁😆😅😂🤣🥲☺️😊", - "😇🙂🙃😉😌😍🥰😘😗😙😚", - "😋😛😝😜🤪🤨🧐🤓😎🥸🤩", - ""); + "😀😃😄😁😆😅😂🤣🥲☺️😊", + "😇🙂🙃😉😌😍🥰😘😗😙😚", + "😋😛😝😜🤪🤨🧐🤓😎🥸🤩", + ""); final InputStream inputStream = new ByteArrayInputStream(emoji.getBytes()); - final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + final BufferedReader bufferedReader = new BufferedReader( + new InputStreamReader(inputStream)); final StringBuilder actual = new StringBuilder(); try (inputStream; bufferedReader) { diff --git a/app/src/test/java/nextstep/learning/http/ThreadTest.java b/app/src/test/java/nextstep/learning/http/ThreadTest.java index fc6dd46409..a577f90e58 100644 --- a/app/src/test/java/nextstep/learning/http/ThreadTest.java +++ b/app/src/test/java/nextstep/learning/http/ThreadTest.java @@ -1,12 +1,11 @@ package nextstep.learning.http; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; - -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.Test; class ThreadTest { diff --git a/app/src/test/resources/junit-platform.properties b/app/src/test/resources/junit-platform.properties index 5ea839d4c9..f0ee14f9b9 100644 --- a/app/src/test/resources/junit-platform.properties +++ b/app/src/test/resources/junit-platform.properties @@ -1 +1 @@ -junit.jupiter.displayname.generator.default = org.junit.jupiter.api.DisplayNameGenerator$ReplaceUnderscores +junit.jupiter.displayname.generator.default=org.junit.jupiter.api.DisplayNameGenerator$ReplaceUnderscores diff --git a/app/src/test/resources/static/401.html b/app/src/test/resources/static/401.html new file mode 100644 index 0000000000..0420dc6455 --- /dev/null +++ b/app/src/test/resources/static/401.html @@ -0,0 +1 @@ +401 is Unauthorized. \ No newline at end of file diff --git a/app/src/test/resources/static/409.html b/app/src/test/resources/static/409.html new file mode 100644 index 0000000000..3925adeba0 --- /dev/null +++ b/app/src/test/resources/static/409.html @@ -0,0 +1 @@ +409 is Conflict. \ No newline at end of file