diff --git a/build.gradle b/build.gradle index ccfd7b6..6fcc524 100644 --- a/build.gradle +++ b/build.gradle @@ -33,5 +33,6 @@ dependencies { runtimeOnly 'com.h2database:h2' runtimeOnly 'mysql:mysql-connector-java' annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.security:spring-security-test' testImplementation 'org.springframework.boot:spring-boot-starter-test' } diff --git a/src/main/java/com/rest/api/config/security/SecurityConfiguration.java b/src/main/java/com/rest/api/config/security/SecurityConfiguration.java index bf8923d..ca31785 100644 --- a/src/main/java/com/rest/api/config/security/SecurityConfiguration.java +++ b/src/main/java/com/rest/api/config/security/SecurityConfiguration.java @@ -32,8 +32,7 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter { .and() .authorizeRequests() // 다음 리퀘스트에 대한 사용권한 체크 .antMatchers("/*/signin", "/*/signup").permitAll() // 가입 및 인증 주소는 누구나 접근가능 - .antMatchers(HttpMethod.GET, "helloworld/**").permitAll() // hellowworld로 시작하는 GET요청 리소스는 누구나 접근가능 - .antMatchers("/*/users").hasRole("ADMIN") + .antMatchers(HttpMethod.GET, "/helloworld/**").permitAll() // hellowworld로 시작하는 GET요청 리소스는 누구나 접근가능 .anyRequest().hasRole("USER") // 그외 나머지 요청은 모두 인증된 회원만 접근 가능 .and() .exceptionHandling().accessDeniedHandler(new CustomAccessDeniedHandler()) diff --git a/src/main/java/com/rest/api/controller/v1/SignController.java b/src/main/java/com/rest/api/controller/v1/SignController.java index 54b6cf6..82e2528 100644 --- a/src/main/java/com/rest/api/controller/v1/SignController.java +++ b/src/main/java/com/rest/api/controller/v1/SignController.java @@ -12,10 +12,7 @@ import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import java.util.Collections; @@ -31,7 +28,7 @@ public class SignController { private final PasswordEncoder passwordEncoder; @ApiOperation(value = "로그인", notes = "이메일 회원 로그인을 한다.") - @GetMapping(value = "/signin") + @PostMapping(value = "/signin") public SingleResult signin(@ApiParam(value = "회원ID : 이메일", required = true) @RequestParam String id, @ApiParam(value = "비밀번호", required = true) @RequestParam String password) { @@ -43,8 +40,8 @@ public class SignController { } @ApiOperation(value = "가입", notes = "회원가입을 한다.") - @GetMapping(value = "/signup") - public CommonResult signin(@ApiParam(value = "회원ID : 이메일", required = true) @RequestParam String id, + @PostMapping(value = "/signup") + public CommonResult signup(@ApiParam(value = "회원ID : 이메일", required = true) @RequestParam String id, @ApiParam(value = "비밀번호", required = true) @RequestParam String password, @ApiParam(value = "이름", required = true) @RequestParam String name) { diff --git a/src/main/java/com/rest/api/controller/v1/UserController.java b/src/main/java/com/rest/api/controller/v1/UserController.java index 6e91ad3..c5c4541 100644 --- a/src/main/java/com/rest/api/controller/v1/UserController.java +++ b/src/main/java/com/rest/api/controller/v1/UserController.java @@ -33,11 +33,11 @@ public class UserController { } @ApiImplicitParams({ - @ApiImplicitParam(name = "X-AUTH-TOKEN", value = "로그인 성공 후 access_token", required = false, dataType = "String", paramType = "header") + @ApiImplicitParam(name = "X-AUTH-TOKEN", value = "로그인 성공 후 access_token", required = true, dataType = "String", paramType = "header") }) @ApiOperation(value = "회원 단건 조회", notes = "회원번호(msrl)로 회원을 조회한다") @GetMapping(value = "/user") - public SingleResult findUserById(@ApiParam(value = "언어", defaultValue = "ko") @RequestParam String lang) { + public SingleResult findUser() { // SecurityContext에서 인증받은 회원의 정보를 얻어온다. Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); String id = authentication.getName(); diff --git a/src/main/java/com/rest/api/entity/User.java b/src/main/java/com/rest/api/entity/User.java index e4c0f4c..83689e9 100644 --- a/src/main/java/com/rest/api/entity/User.java +++ b/src/main/java/com/rest/api/entity/User.java @@ -25,7 +25,7 @@ public class User implements UserDetails { @Id // pk @GeneratedValue(strategy = GenerationType.IDENTITY) private long msrl; - @Column(nullable = false, unique = true, length = 30) + @Column(nullable = false, unique = true, length = 50) private String uid; @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) @Column(nullable = false, length = 100) diff --git a/src/main/java/com/rest/api/service/ResponseService.java b/src/main/java/com/rest/api/service/ResponseService.java index fdc196b..a86f7cf 100644 --- a/src/main/java/com/rest/api/service/ResponseService.java +++ b/src/main/java/com/rest/api/service/ResponseService.java @@ -12,7 +12,7 @@ public class ResponseService { // enum으로 api 요청 결과에 대한 code, message를 정의합니다. public enum CommonResponse { - SUCCESS(0, "성공하였습니디."); + SUCCESS(0, "성공하였습니다."); int code; String msg; diff --git a/src/test/java/com/rest/api/SpringRestApiApplicationTests.java b/src/test/java/com/rest/api/SpringRestApiApplicationTests.java deleted file mode 100644 index 607846f..0000000 --- a/src/test/java/com/rest/api/SpringRestApiApplicationTests.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.rest.api; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class SpringRestApiApplicationTests { - - @Test - public void contextLoads() { - } - -} diff --git a/src/test/java/com/rest/api/controller/HelloControllerTest.java b/src/test/java/com/rest/api/controller/HelloControllerTest.java new file mode 100644 index 0000000..e03c475 --- /dev/null +++ b/src/test/java/com/rest/api/controller/HelloControllerTest.java @@ -0,0 +1,52 @@ +package com.rest.api.controller; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@RunWith(SpringRunner.class) +@SpringBootTest +@AutoConfigureMockMvc +@Transactional +public class HelloControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Test + public void helloworldString() throws Exception { + mockMvc.perform(get("/helloworld/string")) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(content().contentType("text/plain;charset=UTF-8")) + .andExpect(content().string("helloworld")); + } + + @Test + public void helloworldJson() throws Exception { + mockMvc.perform(get("/helloworld/json")) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/json;charset=utf-8")) + .andExpect(jsonPath("$.message").value("helloworld")); + } + + @Test + public void helloworldPage() throws Exception { + mockMvc.perform(get("/helloworld/page")) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(content().contentType("text/html;charset=UTF-8")) + .andExpect(view().name("helloworld")) + .andExpect(content().string("helloworld")); + } +} \ No newline at end of file diff --git a/src/test/java/com/rest/api/controller/v1/SignControllerTest.java b/src/test/java/com/rest/api/controller/v1/SignControllerTest.java new file mode 100644 index 0000000..094ebf3 --- /dev/null +++ b/src/test/java/com/rest/api/controller/v1/SignControllerTest.java @@ -0,0 +1,89 @@ +package com.rest.api.controller.v1; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.context.WebApplicationContext; + +import java.time.LocalDateTime; +import java.time.ZoneId; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@RunWith(SpringRunner.class) +@SpringBootTest +@AutoConfigureMockMvc +@Transactional +public class SignControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Test + public void signin() throws Exception { + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("id", "happydaddy@naver.com"); + params.add("password", "1234"); + mockMvc.perform(post("/v1/signin").params(params)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.msg").exists()) + .andExpect(jsonPath("$.data").exists()); + } + + @Test + public void signinFail() throws Exception { + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("id", "happydaddy@naver.com"); + params.add("password", "12345"); + mockMvc.perform(post("/v1/signin").params(params)) + .andDo(print()) + .andExpect(status().is5xxServerError()) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.code").value(-1001)) + .andExpect(jsonPath("$.msg").exists()); + } + + @Test + public void signup() throws Exception { + long epochTime = LocalDateTime.now().atZone(ZoneId.systemDefault()).toEpochSecond(); + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("id", "happydaddy_" + epochTime + "@naver.com"); + params.add("password", "12345"); + params.add("name", "happydaddy_" + epochTime); + mockMvc.perform(post("/v1/signup").params(params)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.msg").exists()); + } + + @Test + public void signupFail() throws Exception { + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("id", "happydaddy@naver.com"); + params.add("password", "12345"); + params.add("name", "happydaddy"); + mockMvc.perform(post("/v1/signup").params(params)) + .andDo(print()) + .andExpect(status().is5xxServerError()) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.code").value(-9999)); + } +} \ No newline at end of file diff --git a/src/test/java/com/rest/api/controller/v1/UserControllerTest.java b/src/test/java/com/rest/api/controller/v1/UserControllerTest.java new file mode 100644 index 0000000..f812038 --- /dev/null +++ b/src/test/java/com/rest/api/controller/v1/UserControllerTest.java @@ -0,0 +1,118 @@ +package com.rest.api.controller.v1; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.json.JacksonJsonParser; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@RunWith(SpringRunner.class) +@SpringBootTest +@AutoConfigureMockMvc +@Transactional +public class UserControllerTest { + + @Autowired + private MockMvc mockMvc; + + private String token; + + @Before + public void setUp() throws Exception { + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("id", "happydaddy@naver.com"); + params.add("password", "1234"); + ResultActions result = mockMvc.perform(post("/v1/signin").params(params)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.msg").exists()) + .andExpect(jsonPath("$.data").exists()); + + String resultString = result.andReturn().getResponse().getContentAsString(); + JacksonJsonParser jsonParser = new JacksonJsonParser(); + token = jsonParser.parseMap(resultString).get("data").toString(); + } + + @Test + public void invalidToken() throws Exception { + mockMvc.perform(MockMvcRequestBuilders + .get("/v1/users") + .header("X-AUTH-TOKEN", "XXXXXXXXXX")) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(forwardedUrl("/exception/entrypoint")); + } + + @Test + @WithMockUser(username = "mockUser", roles = {"ADMIN"}) // 가상의 Mock 유저 대입 + public void accessdenied() throws Exception { + mockMvc.perform(MockMvcRequestBuilders + .get("/v1/users")) + //.header("X-AUTH-TOKEN", token)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(forwardedUrl("/exception/accessdenied")); + } + + @Test + public void findAllUser() throws Exception { + mockMvc.perform(MockMvcRequestBuilders + .get("/v1/users") + .header("X-AUTH-TOKEN", token)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.list").exists()); + } + + @Test + public void findUser() throws Exception { + mockMvc.perform(MockMvcRequestBuilders + .get("/v1/user") + .header("X-AUTH-TOKEN", token)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.data").exists()); + } + + @Test + public void modify() throws Exception { + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("msrl", "1"); + params.add("name", "행복전도사"); + mockMvc.perform(MockMvcRequestBuilders + .put("/v1/user") + .header("X-AUTH-TOKEN", token) + .params(params)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)); + } + + @Test + public void delete() throws Exception { + mockMvc.perform(MockMvcRequestBuilders + .delete("/v1/user/2") + .header("X-AUTH-TOKEN", token)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)); + } +} \ No newline at end of file diff --git a/src/test/java/com/rest/api/repo/UserJpaRepoTest.java b/src/test/java/com/rest/api/repo/UserJpaRepoTest.java new file mode 100644 index 0000000..f94ed4c --- /dev/null +++ b/src/test/java/com/rest/api/repo/UserJpaRepoTest.java @@ -0,0 +1,46 @@ +package com.rest.api.repo; + +import com.rest.api.entity.User; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.Collections; +import java.util.Optional; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +@RunWith(SpringRunner.class) +@DataJpaTest +public class UserJpaRepoTest { + + @Autowired + private UserJpaRepo userJpaRepo; + + @Autowired + private PasswordEncoder passwordEncoder; + + @Test + public void whenFindByUid_thenReturnUser() { + String uid = "angrydaddy@gmail.com"; + String name = "angrydaddy"; + // given + userJpaRepo.save(User.builder() + .uid(uid) + .password(passwordEncoder.encode("1234")) + .name(name) + .roles(Collections.singletonList("ROLE_USER")) + .build()); + // when + Optional user = userJpaRepo.findByUid(uid); + // then + assertTrue(user.isPresent()); + assertThat(user.get().getName(), is(name)); + } + +} \ No newline at end of file