[+/-] Rename node name, add node /user/detele.

This commit is contained in:
VergeDX
2021-01-19 07:31:28 +08:00
parent c61dbbdb5c
commit c8ecf53ab5
3 changed files with 70 additions and 17 deletions
@@ -4,12 +4,13 @@ import org.hydev.clock_api.entity.User;
import org.hydev.clock_api.error.ErrorCode; import org.hydev.clock_api.error.ErrorCode;
import org.hydev.clock_api.repository.UserRepository; import org.hydev.clock_api.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.util.DigestUtils; import org.springframework.util.DigestUtils;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
@@ -17,6 +18,7 @@ import javax.validation.constraints.Pattern;
@Validated @Validated
@RestController @RestController
@RequestMapping("/user")
public class RegistryController { public class RegistryController {
private final UserRepository userRepository; private final UserRepository userRepository;
@@ -43,12 +45,29 @@ public class RegistryController {
User user = new User(); User user = new User();
user.setUsername(username); user.setUsername(username);
user.setPasswordMd5(userToSaltedMd5(username, password));
// TODO: Using Spring Security instead.
user.setPasswordMd5(DigestUtils.md5DigestAsHex(password.getBytes()).toLowerCase());
// After save and flush, uuid field will be generated automatically. // After save and flush, uuid field will be generated automatically.
userRepository.saveAndFlush(user); userRepository.saveAndFlush(user);
return ResponseEntity.ok(user.getUuid()); return ResponseEntity.ok(user.getUuid());
} }
// Format: "$username + $password".toLowerMd5();
private String userToSaltedMd5(String username, String password) {
String beforeMd5 = String.format("%s + %s", username, password);
return DigestUtils.md5DigestAsHex(beforeMd5.getBytes()).toLowerCase();
}
@PostMapping("/delete")
public synchronized ResponseEntity<String> delete(@RequestHeader String username, @RequestHeader String password) {
User user = userRepository.queryByUsername(username);
if (user == null) return ResponseEntity.notFound().build();
if (user.getPasswordMd5().equals(userToSaltedMd5(username, password))) {
userRepository.delete(user);
return ResponseEntity.ok("");
}
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("");
}
} }
@@ -8,4 +8,5 @@ import org.springframework.stereotype.Repository;
public interface UserRepository extends JpaRepository<User, String> { public interface UserRepository extends JpaRepository<User, String> {
// https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods // https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods
boolean existsByUsername(String username); boolean existsByUsername(String username);
User queryByUsername(String username);
} }
@@ -37,9 +37,11 @@ import javax.validation.ConstraintViolationException
// https://spring.io/guides/gs/testing-web/ // https://spring.io/guides/gs/testing-web/
@AutoConfigureMockMvc @AutoConfigureMockMvc
class RegisterNodeTest { class UserRegisterDeleteNodeTest {
companion object { companion object {
private const val TEST_NODE = "/register" private const val TEST_NODE = "/user"
private const val REGISTER_NODE = "${TEST_NODE}/register"
private const val DELETE_NODE = "${TEST_NODE}/delete"
private const val H_USERNAME = "username" private const val H_USERNAME = "username"
private const val V_USERNAME = "vanilla" private const val V_USERNAME = "vanilla"
@@ -61,16 +63,17 @@ class RegisterNodeTest {
@Autowired @Autowired
private lateinit var restTemplate: TestRestTemplate private lateinit var restTemplate: TestRestTemplate
// Post with headers, expect 406 and ErrorCodes. // Post to register node with headers, expect 406 and ErrorCodes.
// todo: Using List instead of Array. // todo: Using List instead of Array.
private fun pWHsE406AECs(headerMap: Map<String, String>, expectedECList: Array<String>) { private fun pTRWHsE406AECs(headerMap: Map<String, String>, expectedECList: Array<String>) {
val tempMultiValueMap = LinkedMultiValueMap<String, String>() val tempMultiValueMap = LinkedMultiValueMap<String, String>()
headerMap.forEach { tempMultiValueMap[it.key] = listOf(it.value) } headerMap.forEach { tempMultiValueMap[it.key] = listOf(it.value) }
val httpEntity = HttpEntity<String>(tempMultiValueMap) val httpEntity = HttpEntity<String>(tempMultiValueMap)
// Using exchange to custom headers, etc. Args: (node, method, headers, forObject). // Using exchange to custom headers, etc. Args: (node, method, headers, forObject).
// https://stackoverflow.com/questions/16781680/http-get-with-headers-using-resttemplate // https://stackoverflow.com/questions/16781680/http-get-with-headers-using-resttemplate
val responseEntity = restTemplate.exchange(TEST_NODE, HttpMethod.POST, httpEntity, Array<String>::class.java) val responseEntity =
restTemplate.exchange(REGISTER_NODE, HttpMethod.POST, httpEntity, Array<String>::class.java)
// Expect http status is 406 NOT ACCEPTABLE, and ErrorCode array are same. // Expect http status is 406 NOT ACCEPTABLE, and ErrorCode array are same.
assertEquals(HttpStatus.NOT_ACCEPTABLE, responseEntity.statusCode) assertEquals(HttpStatus.NOT_ACCEPTABLE, responseEntity.statusCode)
@@ -80,17 +83,17 @@ class RegisterNodeTest {
@Test @Test
// [A0101, A0102, A0101 + A0102] M1 * 2 + M2. // [A0101, A0102, A0101 + A0102] M1 * 2 + M2.
fun testWhenMissingField() { fun testWhenMissingField() {
pWHsE406AECs(mapOf(H_PASSWORD to V_PASSWORD), arrayOf(USER_NAME_IS_NULL)) pTRWHsE406AECs(mapOf(H_PASSWORD to V_PASSWORD), arrayOf(USER_NAME_IS_NULL))
pWHsE406AECs(mapOf(H_USERNAME to V_USERNAME), arrayOf(USER_PASSWORD_IS_NULL)) pTRWHsE406AECs(mapOf(H_USERNAME to V_USERNAME), arrayOf(USER_PASSWORD_IS_NULL))
pWHsE406AECs(mapOf(), arrayOf(USER_NAME_IS_NULL, USER_PASSWORD_IS_NULL)) pTRWHsE406AECs(mapOf(), arrayOf(USER_NAME_IS_NULL, USER_PASSWORD_IS_NULL))
} }
@Test @Test
// [A0111, A0112, A0111 + A0112] W1 * 2 + W2. // [A0111, A0112, A0111 + A0112] W1 * 2 + W2.
fun testWhenNotMatchRegex() { fun testWhenNotMatchRegex() {
pWHsE406AECs(mapOf(H_USERNAME to "", H_PASSWORD to V_PASSWORD), arrayOf(USER_NAME_NOT_MATCH_REGEX)) pTRWHsE406AECs(mapOf(H_USERNAME to "", H_PASSWORD to V_PASSWORD), arrayOf(USER_NAME_NOT_MATCH_REGEX))
pWHsE406AECs(mapOf(H_USERNAME to V_USERNAME, H_PASSWORD to ""), arrayOf(USER_PASSWORD_NOT_MATCH_REGEX)) pTRWHsE406AECs(mapOf(H_USERNAME to V_USERNAME, H_PASSWORD to ""), arrayOf(USER_PASSWORD_NOT_MATCH_REGEX))
pWHsE406AECs( pTRWHsE406AECs(
mapOf(H_USERNAME to "", H_PASSWORD to ""), mapOf(H_USERNAME to "", H_PASSWORD to ""),
arrayOf(USER_NAME_NOT_MATCH_REGEX, USER_PASSWORD_NOT_MATCH_REGEX) arrayOf(USER_NAME_NOT_MATCH_REGEX, USER_PASSWORD_NOT_MATCH_REGEX)
) )
@@ -99,13 +102,13 @@ class RegisterNodeTest {
@Test @Test
// [A0121] Insert user, check it if already exists. // [A0121] Insert user, check it if already exists.
fun testWhenUserAlreadyExists() { fun testWhenUserAlreadyExists() {
mockMvc.perform(post(TEST_NODE).header(H_USERNAME, V_USERNAME).header(H_PASSWORD, V_PASSWORD)) mockMvc.perform(post(REGISTER_NODE).header(H_USERNAME, V_USERNAME).header(H_PASSWORD, V_PASSWORD))
.andExpect(status().isOk) .andExpect(status().isOk)
.andExpect(content().string(Matchers.matchesPattern(R_UUID))) .andExpect(content().string(Matchers.matchesPattern(R_UUID)))
.andDo { result: MvcResult -> .andDo { result: MvcResult ->
// Notice: inserted user should be delete. // Notice: inserted user should be delete.
val tempUuid = result.response.contentAsString val tempUuid = result.response.contentAsString
mockMvc.perform(post(TEST_NODE).header(H_USERNAME, V_USERNAME).header(H_PASSWORD, V_PASSWORD)) mockMvc.perform(post(REGISTER_NODE).header(H_USERNAME, V_USERNAME).header(H_PASSWORD, V_PASSWORD))
.andExpect(status().isNotAcceptable) .andExpect(status().isNotAcceptable)
.andExpect(content().json(String.format("[\"%s\"]", USER_NAME_ALREADY_EXISTS))) .andExpect(content().json(String.format("[\"%s\"]", USER_NAME_ALREADY_EXISTS)))
userRepository.deleteById(tempUuid) userRepository.deleteById(tempUuid)
@@ -143,4 +146,34 @@ class RegisterNodeTest {
setOf(INNER_USERNAME_NOT_MATCH_REGEX, INNER_PASSWORD_MD5_NOT_MATCH_REGEX) setOf(INNER_USERNAME_NOT_MATCH_REGEX, INNER_PASSWORD_MD5_NOT_MATCH_REGEX)
) )
} }
// Post to delete node with headers and expect HttpStatus.
private fun pTDWHsAEHS(headerMap: Map<String, String>, expectedHttpStatus: HttpStatus) {
val tempMultiValueMap = LinkedMultiValueMap<String, String>()
headerMap.forEach { tempMultiValueMap[it.key] = listOf(it.value) }
val httpEntity = HttpEntity<String>(tempMultiValueMap)
val responseEntity = restTemplate.exchange(DELETE_NODE, HttpMethod.POST, httpEntity, String::class.java)
assertEquals(expectedHttpStatus, responseEntity.statusCode)
}
@Test
fun testDeleteUser() {
mockMvc.perform(post(REGISTER_NODE).header(H_USERNAME, V_USERNAME).header(H_PASSWORD, V_PASSWORD))
.andExpect(status().isOk)
.andExpect(content().string(Matchers.matchesPattern(R_UUID)))
.andDo {
// Missing headers, 400 bad request.
pTDWHsAEHS(mapOf(H_USERNAME to ""), HttpStatus.BAD_REQUEST)
// Username not exist, 404 not found.
pTDWHsAEHS(mapOf(H_USERNAME to "", H_PASSWORD to ""), HttpStatus.NOT_FOUND)
// Username exist, but password not match. 401 unauthorized.
pTDWHsAEHS(mapOf(H_USERNAME to V_USERNAME, H_PASSWORD to ""), HttpStatus.UNAUTHORIZED)
// Username & password matched, delete user and 200 ok.
pTDWHsAEHS(mapOf(H_USERNAME to V_USERNAME, H_PASSWORD to V_PASSWORD), HttpStatus.OK)
// And assert user is gone.
assertEquals(false, userRepository.existsByUsername(V_USERNAME))
}
}
} }