[+/-] 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.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.DigestUtils;
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.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.constraints.NotNull;
@@ -17,6 +18,7 @@ import javax.validation.constraints.Pattern;
@Validated
@RestController
@RequestMapping("/user")
public class RegistryController {
private final UserRepository userRepository;
@@ -43,12 +45,29 @@ public class RegistryController {
User user = new User();
user.setUsername(username);
// TODO: Using Spring Security instead.
user.setPasswordMd5(DigestUtils.md5DigestAsHex(password.getBytes()).toLowerCase());
user.setPasswordMd5(userToSaltedMd5(username, password));
// After save and flush, uuid field will be generated automatically.
userRepository.saveAndFlush(user);
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> {
// https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods
boolean existsByUsername(String username);
User queryByUsername(String username);
}
@@ -37,9 +37,11 @@ import javax.validation.ConstraintViolationException
// https://spring.io/guides/gs/testing-web/
@AutoConfigureMockMvc
class RegisterNodeTest {
class UserRegisterDeleteNodeTest {
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 V_USERNAME = "vanilla"
@@ -61,16 +63,17 @@ class RegisterNodeTest {
@Autowired
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.
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>()
headerMap.forEach { tempMultiValueMap[it.key] = listOf(it.value) }
val httpEntity = HttpEntity<String>(tempMultiValueMap)
// Using exchange to custom headers, etc. Args: (node, method, headers, forObject).
// 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.
assertEquals(HttpStatus.NOT_ACCEPTABLE, responseEntity.statusCode)
@@ -80,17 +83,17 @@ class RegisterNodeTest {
@Test
// [A0101, A0102, A0101 + A0102] M1 * 2 + M2.
fun testWhenMissingField() {
pWHsE406AECs(mapOf(H_PASSWORD to V_PASSWORD), arrayOf(USER_NAME_IS_NULL))
pWHsE406AECs(mapOf(H_USERNAME to V_USERNAME), arrayOf(USER_PASSWORD_IS_NULL))
pWHsE406AECs(mapOf(), arrayOf(USER_NAME_IS_NULL, USER_PASSWORD_IS_NULL))
pTRWHsE406AECs(mapOf(H_PASSWORD to V_PASSWORD), arrayOf(USER_NAME_IS_NULL))
pTRWHsE406AECs(mapOf(H_USERNAME to V_USERNAME), arrayOf(USER_PASSWORD_IS_NULL))
pTRWHsE406AECs(mapOf(), arrayOf(USER_NAME_IS_NULL, USER_PASSWORD_IS_NULL))
}
@Test
// [A0111, A0112, A0111 + A0112] W1 * 2 + W2.
fun testWhenNotMatchRegex() {
pWHsE406AECs(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))
pWHsE406AECs(
pTRWHsE406AECs(mapOf(H_USERNAME to "", H_PASSWORD to V_PASSWORD), arrayOf(USER_NAME_NOT_MATCH_REGEX))
pTRWHsE406AECs(mapOf(H_USERNAME to V_USERNAME, H_PASSWORD to ""), arrayOf(USER_PASSWORD_NOT_MATCH_REGEX))
pTRWHsE406AECs(
mapOf(H_USERNAME to "", H_PASSWORD to ""),
arrayOf(USER_NAME_NOT_MATCH_REGEX, USER_PASSWORD_NOT_MATCH_REGEX)
)
@@ -99,13 +102,13 @@ class RegisterNodeTest {
@Test
// [A0121] Insert user, check it if already exists.
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(content().string(Matchers.matchesPattern(R_UUID)))
.andDo { result: MvcResult ->
// Notice: inserted user should be delete.
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(content().json(String.format("[\"%s\"]", USER_NAME_ALREADY_EXISTS)))
userRepository.deleteById(tempUuid)
@@ -143,4 +146,34 @@ class RegisterNodeTest {
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))
}
}
}