[+/M] Add new node /user/login and test, reformat code.

This commit is contained in:
VergeDX
2021-01-22 22:27:09 +08:00
parent 6f9cc3d2a3
commit 2fb8280a3e
3 changed files with 43 additions and 8 deletions
@@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.RestController;
import javax.validation.constraints.Pattern; import javax.validation.constraints.Pattern;
import java.util.List; import java.util.List;
import java.util.function.Function;
@Validated @Validated
@RestController @RestController
@@ -32,7 +33,7 @@ public class UserController {
// TODO: This method should be synchronized to avoid race condition. // TODO: This method should be synchronized to avoid race condition.
// Also, this method should not be private, or else cannot use userRepository. // Also, this method should not be private, or else cannot use userRepository.
// TODO: 2021/1/22 Need a better design! // TODO: 2021/1/22 Need a better design!
// Controller Return error code list as List<String>, or return uuid as String. // Controller Return error code list as List<String>, or return uuid as String.
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
public synchronized ResponseEntity register( public synchronized ResponseEntity register(
@@ -65,16 +66,30 @@ public class UserController {
return DigestUtils.md5DigestAsHex(beforeMd5.getBytes()).toLowerCase(); return DigestUtils.md5DigestAsHex(beforeMd5.getBytes()).toLowerCase();
} }
@PostMapping("/delete") // Check username & password.
public synchronized ResponseEntity<String> delete(@RequestHeader String username, @RequestHeader String password) { // user not exists -> http 404, password not match -> http 401; all match -> do and return do's result String.
private ResponseEntity<String> checkPasswordAndDo(String username, String password,
Function<User, String> operation) {
User user = userRepository.queryByUsername(username); User user = userRepository.queryByUsername(username);
if (user == null) return ResponseEntity.notFound().build(); if (user == null) return ResponseEntity.notFound().build();
if (user.getPasswordMd5().equals(userToSaltedMd5(username, password))) { if (!user.getPasswordMd5().equals(userToSaltedMd5(username, password)))
userRepository.delete(user); return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
return ResponseEntity.ok("");
}
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(""); String result = operation.apply(user);
return ResponseEntity.ok(result);
}
@PostMapping("/delete")
public synchronized ResponseEntity<String> delete(@RequestHeader String username, @RequestHeader String password) {
return checkPasswordAndDo(username, password, user -> {
userRepository.delete(user);
return "";
});
}
@PostMapping("login")
public synchronized ResponseEntity<String> login(@RequestHeader String username, @RequestHeader String password) {
return checkPasswordAndDo(username, password, user -> userRepository.queryByUsername(username).getUuid());
} }
} }
@@ -8,5 +8,6 @@ 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); User queryByUsername(String username);
} }
@@ -1,6 +1,7 @@
package org.hydev.clock_api package org.hydev.clock_api
import org.hamcrest.Matchers import org.hamcrest.Matchers
import org.hamcrest.Matchers.matchesPattern
import org.hydev.clock_api.entity.User 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
@@ -44,6 +45,7 @@ class UserRegisterDeleteNodeTest {
private const val TEST_NODE = "/user" private const val TEST_NODE = "/user"
private const val REGISTER_NODE = "${TEST_NODE}/register" private const val REGISTER_NODE = "${TEST_NODE}/register"
private const val DELETE_NODE = "${TEST_NODE}/delete" private const val DELETE_NODE = "${TEST_NODE}/delete"
private const val LOGIN_NODE = "${TEST_NODE}/login"
private const val H_USERNAME = "username" private const val H_USERNAME = "username"
private const val V_USERNAME = "vanilla" private const val V_USERNAME = "vanilla"
@@ -194,4 +196,21 @@ class UserRegisterDeleteNodeTest {
assertEquals(false, userRepository.existsByUsername(V_USERNAME)) assertEquals(false, userRepository.existsByUsername(V_USERNAME))
} }
} }
@Test
fun testUserLogin() {
mockMvc.perform(post(REGISTER_NODE).header(H_USERNAME, V_USERNAME).header(H_PASSWORD, V_PASSWORD))
// https://stackoverflow.com/questions/49722217/how-do-i-validate-a-json-field-is-formatted-correctly-using-mockmvcresultmatcher
.andExpect(status().isOk)
.andExpect(content().string(matchesPattern(R_UUID)))
.andDo {
// Missing field, username not exists, password not match cases already test in testDeleteUser().
// Test successful login cases, status 200 and current user's uuid.
mockMvc.perform(post(LOGIN_NODE).header(H_USERNAME, V_USERNAME).header(H_PASSWORD, V_PASSWORD))
.andExpect(status().isOk)
.andExpect(content().string(it.response.contentAsString))
userRepository.deleteById(it.response.contentAsString)
}
}
} }